エントリー
データベースとの通信用にElixir DSLで書かれたEcto。 EctoはORMではありません。 なんで? はい、Elixirはオブジェクト指向言語ではなく、Ectoはオブジェクトリレーショナルマッピング(オブジェクトリレーショナルマッピング)にできないためです。 Ectoは、移行の作成、モデル(スキーム)の宣言、データの追加と更新、およびそれらへの要求の送信を可能にするいくつかの大きなモジュールで構成されるデータベースの抽象化です。
Railsに精通している場合は、もちろん、最も近い類推はそのORM ActiveRecordです。 しかし、これらの2つのシステムは相互のコピーではないため、基本的な言語で使用するのに適しています。 現在、Ecto 2の現在のバージョンは、PostgreSQLおよびMySQLと互換性があります。 以前のバージョンは、MSSQL、SQLite3、MongoDBとさらに互換性があります。 使用されるDBMSに関係なく、Ecto関数の形式は常に同じです。 Ectoは、Phoenixですぐに使用できる標準的なソリューションです。
パンフレットを展開することに決めた場合は、このリポジトリの開発に参加することを歓迎しますhttps://github.com/wunsh/ecto-book-ru
イノベーションEcto 2.X
- 更新されたEcto.Changesetモジュール
- Ecto.Queryモジュールの新しいサブクエリ/ 1機能
- Ecto.Repoモジュールの新しいinsert_all / 3関数
- 多対多の関連付けを追加
- 関連付けの改善された作業
- Ectoモジュールの新しいassoc / 2プリロード機能
- アップサート
- 新しいor_whereおよびor_havingフェッチ条件
- ステップごとにクエリを作成する機能を追加しました
更新されたEcto.Changesetモジュール
- changeset.modelはchangeset.dataに名前が変更されました(以降、Ectoには「モデル」はありません)。
-
cast/4
への必須フィールドとオプションの転送は廃止されたと見なされます;以降、cast/3
およびvalidate_required/3
を使用する必要があります。 - Atom
:empty
cast(source, :empty, required, optional)
:empty
cast(source, :empty, required, optional)
推奨されなくなったため、empty map
を使用するか、代わりに:invalid
にすることをお勧めします。
その結果、代わりに:
def changeset(user, params \\ :empty) do user |> cast(params, [:name], [:age]) end
次のように改善することをお勧めします。
def changeset(user, params \\ %{}) do user |> cast(params, [:name, :age]) |> validate_required([:name]) end
Ecto.Queryモジュールの新しいサブクエリ/ 1機能
Ecto.Query.subquery/1
関数を使用すると、クエリをサブクエリに変換できます。 たとえば、出版物の平均閲覧数を計算する場合、次のように記述できます。
query = from p in Post, select: avg(p.visits) TestRepo.all(query) #=> [#Decimal<1743>]
ただし、最も人気のある10件の投稿のみの平均ビュー数を計算する場合は、サブクエリが必要になります。
query = from p in Post, select: [:visits], order_by: [desc: :visits], limit: 10 TestRepo.all(from p in subquery(query), select: avg(p.visits)) #=> [#Decimal<4682>]
実際の例として、 Repo.aggregate
関数を使用して集計データを計算する場合:
# - TestRepo.aggregate(Post, :avg, :visits) #=> #Decimal<1743>
# - 10 query = from Post, order_by: [desc: :visits], limit: 10 TestRepo.aggregate(query, :avg, :visits) #=> #Decimal<4682>
subquery/1
では、サブクエリのフィールドに名前を付けることができます。 競合する名前を持つテーブルを処理できるもの:
posts_with_private = from p in Post, select: %{title: p.title, public: not p.private} from p in subquery(posts_with_private), where: p.public, select: p
Ecto.Repoモジュールの新しいinsert_all / 3関数
Ecto.Repo.insert_all/3
関数は、単一の要求内にレコードを複数挿入することを目的としています。
Ecto.Repo.insert_all Post, [%{title: "foo"}, %{title: "bar"}]
insert_all/3
を介して行を挿入する場合、 inserted_at
やupdated_at
などの自動生成フィールドは処理さupdated_at
ないことを考慮する価値updated_at
あります。 また、 insert_all/3
すると、テーブル名を指定するだけでEcto.Schema
バイパスして、データベースに行を挿入できます。
Ecto.Repo.insert_all "some_table", [%{hello: "foo"}, %{hello: "bar"}]
多対多の関連付けを追加
many_to_many
はmany_to_many
アソシエーションをサポートするmany_to_many
になりました:
defmodule Post do use Ecto.Schema schema "posts" do many_to_many :tags, Tag, join_through: "posts_tags" end end
join_through
オプションの値は、 join_through
tag_id
とtag_id
を含むテーブルの名前、または外部キーと自動生成列を含むPostTag
などのスキーマです。
関連付けの改善された作業
belongs_to
、 many_to_many
はchangeset
介して、 belongs_to
およびmany_to_many
関連付けに行を挿入および変更できるようにします。 さらに、Ectoは、挿入のためにデータ構造内で直接関連付けの定義をサポートします。 例:
Repo.insert! %Permalink{ url: "//root", post: %Post{ title: "A permalink belongs to a post which we are inserting", comments: [ %Comment{text: "child 1"}, %Comment{text: "child 2"}, ] } }
この改善により、ツリー構造をデータベースに挿入しやすくなりました。
Ectoモジュールの新しいassoc / 2プリロード機能
Ecto.assoc/2
関数を使用すると、レコードを選択するためにロードする必要がある2次の関係を定義できます。 例として、選択した出版物の著者とコメントを取得できます。
posts = Repo.all from p in Post, where: is_nil(p.published_at) Repo.all assoc(posts, [:comments, :author])
選択スキームにフィールドを追加する必要がないため、関連付けを介してリンクをロードすることをお勧めします。
アップサート
関数Ecto.Repo.insert/2
およびEcto.Repo.insert_all/3
は、オプション:on_conflict
および:conflict_target
使用してアップサート(挿入および更新)をサポートし始めました。
オプション:on_conflict
は、主キーが一致した場合のデータベースの動作を決定します。
オプション:conflict_target
、新しい行を挿入するときに競合をチェックするために使用するフィールドを決定します。
# {:ok, inserted} = MyRepo.insert(%Post{title: "inserted"}) # . {:ok, upserted} = MyRepo.insert(%Post{id: inserted.id, title: "updated"}, on_conflict: :nothing) # , title. on_conflict = [set: [title: "updated"]] {:ok, updated} = MyRepo.insert(%Post{id: inserted.id, title: "updated"}, on_conflict: on_conflict, conflict_target: :id)
新しいor_whereおよびor_havingフェッチ条件
Ecto.Query.or_where/3
は、式「 Ecto.Query.or_where/3
および「 Ecto.Query.or_having
」を追加しました。これらは、「OR」を通じて既存の条件に新しいフィルターを追加します。
from(c in City, where: [state: "Sweden"], or_where: [state: "Brazil"])
ステップごとにクエリを作成する機能を追加しました
この手法を使用すると、式を少しずつ作成して、一般的なクエリに後で補間することができます。
たとえば、クエリを作成する条件のセットがありますが、コンテキストに応じてそのうちのいくつかを選択するだけで済みます。
dynamic = false dynamic = if params["is_public"] do dynamic([p], p.is_public or ^dynamic) else dynamic end dynamic = if params["allow_reviewers"] do dynamic([p, a], a.reviewer == true or ^dynamic) else dynamic end from query, where: ^dynamic
上記の例は、外部条件を考慮して、段階的にクエリを作成し、最後に1つのクエリ内のすべてを補間する方法を示しています。
動的式は、別の動的式内where
またはwhere
、 having
、 update
またはjoin
on
having
内where
常に補間できます。
クエリインターフェース
HexDocsのEcto.QueryおよびEcto.Repoモジュールのドキュメントの分析は、RusRailsGuide のActiveRecord Query Interfaceの方法で行われます。 以下のテキストは、Ectoを使用してデータベースからデータを取得するさまざまな方法を開示しています。 以下のテキストの後半のコード例は、これらのモデルの一部に適用されます。
id , . defmodule Showcase.Client do use Ecto.Schema import Ecto.Query schema "clients" do field :name, :string field :age, :integer, default: 0 field :sex, :integer, default: 0 field :state, :string, default: "new" has_many :orders, Showcase.Order end # ... end defmodule Showcase.Order do use Ecto.Schema import Ecto.Query schema "orders" do field :description, :text field :total_cost, :integer field :state, :string, default: "pending" belongs_to :client, Showcase.Client has_many :products, Showcase.Product end # ... end
Ectoは、データベースからオブジェクトを取得するためのいくつかの検索機能を提供します。 引数を各検索関数に渡して、純粋なSQLを記述することなくデータベースに特定のクエリを実行できます。 Ectoは、キーワードの使用と式(関数/マクロ)を使用した2つのスタイルのクエリ作成を提供します。
以下はEctoが提供する式の一部で、2つのモジュールで宣言されています:
Ecto.Repo:
- 取得/ 3
- get_by / 3
- 1/2
- すべて/ 3
Ecto.Query:
- どこ/ 3
- or_where / 3
- order_by / 3
- 選択/ 3
- group_by / 3
- 制限/ 3
- オフセット/ 3
- 参加する/ 5
- 除外/ 2
1.単一の行を取得する
取得/ 3
get/3
関数を使用すると、特定の主キーに対応するレコードを取得できます。 例:
# (id) 10. client = Ecto.Repo.get(Client, 10) => %Client{id: 10, name: "Cain Ramirez", age: 34, sex: 1, state: "good"}
レコードが見つからない場合、 get/3
関数はnil
を返します。 要求にprimary key
がない場合、またはprimary key
が複数ある場合、関数はargument error
を引き起こしargument error
。
get!/3
関数はget/3
と同じようget/3
動作しますが、一致するエントリが見つからない場合Ecto.NoResultsError
をEcto.NoResultsError
ます。
この関数のActiveRecordに最も近いものは、 find
メソッドです。
get_by / 3
get_by/3
関数を使用すると、提供された選択条件に一致するレコードを取得できます。 例:
# (name) "Cain Ramirez". client = Ecto.Repo.get_by(Client, name: "Cain Ramirez") => %Client{id: 10, name: "Cain Ramirez", age: 34, sex: 1, state: "good"}
レコードが見つからない場合、 get_by/3
関数get_by/3
nil
get_by/3
返します。
get_by!/3
関数はget_by/3
ように動作しますが、一致するレコードが見つからない場合Ecto.NoResultsError
をEcto.NoResultsError
ます。
この関数のActiveRecordに最も近いものは、 find_by_*
メソッドです。
1/2
1/2関数を使用すると、提供された選択条件に一致する1つのレコードを取得できます。 例:
# . query = Ecto.Query.from(c in Client, where: c.name == "Jean Rousey") client = Ecto.Repo.one(query) => %Client{id: 1, name: "Jean Rousey", age: 29, sex: -1, state: "good"}
nil
関数は、レコードが見つからない場合はnil
を返します。 また、要求時に複数のレコードが見つかった場合、関数はエラーをスローします。
one!/2
関数はone/2
ように動作しますが、一致するエントリが見つからない場合Ecto.NoResultsError
をEcto.NoResultsError
ます。
ActiveRecord
のこの関数に最も近いものは、現在の制限ではなく、選択条件を受け入れた<4バージョンのfirst
メソッドです。
2.複数の行を取得する
すべて/ 3
all/3
関数を使用すると、指定されたクエリ条件に一致するすべてのレコードを取得できます。 例:
# . query = Ecto.Query.from(c in Client) clients = Ecto.Repo.all(query) => [%Client{id: 1, name: "Jean Rousey", age: 29, sex: -1, state: "good"}, ..., %Client{id: 10, name: "Cain Ramirez", age: 34, sex: 1, state: "good"}]
all/3
関数は、リクエストが検証に失敗した場合にEcto.QueryError
を返します。
この関数のActiveRecordに最も近いものは、選択条件を受け入れたバージョン<4のall
メソッドです。
3.行選択条件
どこ/ 3
where/3
式を使用すると、SQL式のWHERE部分が表す返されるレコードを制限するための条件を定義できます。 複数の選択条件が送信される場合、それらはAND
演算子によって結合されます。
where:
キーワードの呼び出しは、 from/2
マクロの不可欠な部分です。
from(c in Client, where: c.name == "Cain Ramirez") from(c in Client, where: [name: "Cain Ramirez"])
サンプリング条件を使用してリストを補間することができます。これにより、必要な制限を事前に収集できます。
filters = [name: "Cain Ramirez"] from(c in Client, where: ^filters)
マクロ呼び出し// where/3
:
Client |> where([c], c.name == "Cain Ramirez") Client |> where(name: "Cain Ramirez")
or_where / 3
or_where/3
式を使用すると、SQL式のWHERE部分が表す返されるレコードを制限するためのより柔軟な条件を定義できます。 where/3
とor_where/3
の違いor_where/3
最小限ですが、基本的です。 転送された条件は、 OR
演算子を介して既存の条件に追加されます。 複数の選択条件がor_where/3
れる場合、それらはAND
演算子によって互いに結合されます。
or_where:
キーワードを使用した呼び出しは、 from/2
マクロの不可欠な部分です。
from(c in Client, where: [name: "Cain Ramirez"], or_where: [name: "Jean Rousey"])
サンプリング条件を使用してリストを補間することができます。これにより、必要な制限を事前に収集できます。 リスト内の条件はAND
を介して相互接続され、 OR
介して既存の条件を結合します。
filters = [sex: 1, state: "good"] from(c in Client, where: [name: "Cain Ramirez"], or_where: ^filters)
...この式は次と同等です:
from c in Client, where: (c.name == "Cain Ramirez") or (c.sex == 1 and c.state == "good")
or_where/3
呼び出しor_where/3
:
Client |> where([c], c.name == "Jean Rousey") |> or_where([c], c.name == "Cain Ramirez")
4.文字列ソート
式order_by/3
使用すると、データベースから受信したレコードのソート条件を定義できます。 order_by/3
は、SQLクエリのORDER BY
部分を指定します。
一度に複数のフィールドで並べ替えることができます。 デフォルトのソート方向は昇順( :asc
)であり、降順( :desc
)で再定義できます。 フィールドごとに、独自のソート方向を設定できます。
キーワード呼び出しorder_by:
は、 from/2
マクロの不可欠な部分です。
from(c in Client, order_by: c.name, order_by: c.age) from(c in Client, order_by: [c.name, c.age]) from(c in Client, order_by: [asc: c.name, desc: c.age]) from(c in Client, order_by: [:name, :age]) from(c in Client, order_by: [asc: :name, desc: :age])
ソートフィールドを使用してリストを補間することができます。これにより、必要な選択条件を事前に収集できます。
values = [asc: :name, desc: :age] from(c in Client, order_by: ^values)
マクロ呼び出しorder_by/3
:
Client |> order_by([c], asc: c.name, desc: c.age) Client |> order_by(asc: :name)
5.特定の行フィールドを選択します
select/3
式を使用すると、データベースからレコードを取得するために返すテーブルのフィールドを定義できます。 select/3
は、SQLクエリのSELECT
部分を設定します。 デフォルトでは、 Ecto
select *
を使用select *
結果フィールドのセット全体をselect *
しselect *
。
select:
キーワードの呼び出しは、 from/2
マクロの不可欠な部分です。
from(c in Client, select: c) from(c in Client, select: {c.name, c.age}) from(c in Client, select: [c.name, c.state]) from(c in Client, select: {c.name, ^to_string(40 + 2), 43}) from(c in Client, select: %{name: c.name, order_counts: 42})
select/3
をselect/3
マクロ呼び出し:
Client |> select([c], c) Client |> select([c], {c.name, c.age}) Client |> select([c], %{"name" => c.name}) Client |> select([:name]) Client |> select([c], struct(c, [:name])) Client |> select([c], map(c, [:name]))
重要:関連付けの選択フィールドを制限する場合、外部リンクキーを選択することが重要です。そうしないと、 Ecto
は関連オブジェクトを見つけることができません。
6.文字列のグループ化
SQLクエリでGROUP BY
を定義するには、 group_by/3
マクロがgroup_by/3
ます。 SELECT
記載されているすべての列は、 group_by/3
渡す必要があります。 これは、集計関数の一般的なルールです。
キーワードgroup_by:
呼び出しは、 from/2
マクロの不可欠な部分です。
from(c in Client, group_by: c.age, select: {c.age, count(c.id)}) from(c in Client, group_by: :sex, select: {c.sex, count(c.id)})
group_by/3
呼び出し:
Client |> group_by([c], c.age) |> select([c], count(c.id))
7.選択した行の制限とシフト
SQLクエリのLIMITを決定するには、式limit/3
使用して、受信される必要なレコードの数を決定します。
limit/3
2回渡されると、最初の値が2番目の値によってオーバーライドされます。
from(c in Client, where: c.age == 29, limit: 1) Client |> where([c], c.age == 29) |> limit(1)
SQLクエリでOFFSETを決定するには、 offset/3
式を使用します。これは、返されるレコードが開始される前にスキップされるレコードの数を決定します。
offset/3
2回渡されると、最初の値は2番目の値によってオーバーライドされます。
from(c in Client, limit: 10, offset: 30) Client |> limit(10) |> offset(30)
8.テーブルを結合する
クエリは多くの場合、いくつかのテーブルを参照します;そのようなクエリはJOIN
構造を使用して構築されます。 Ectoでは、 join/5
そのような構造を定義することを目的としています。 デフォルトでは、テーブル結合戦略はINNER JOINであり、 :inner
、:left 、: right 、: :cross
または:full
再定義できます。 キーによってクエリを構築する場合:inner_join
:join
は、:: :inner_join
:left_join
:right_join
:cross_join
または:full_join
置き換えることができます。
join:
キーワード呼び出しは、 from/2
マクロの不可欠な部分です。
from c in Comment, join: p in Post, on: p.id == c.post_id, select: {p.title, c.text} from p in Post, left_join: c in assoc(p, :comments), select: {p, c} from c in Comment, join: p in Post, on: [id: c.post_id], select: {p.title, c.text}
on
渡されるすべてのキーは接続条件と見なされます。
in
に関して右側を補間することが可能です。 例:
posts = Post from c in Comment, join: p in ^posts, on: [id: c.post_id], select: {p.title, c.text}
マクロ呼び出しjoin/5
:
Comment |> join(:inner, [c], p in Post, c.post_id == p.id) |> select([c, p], {p.title, c.text}) Post |> join(:left, [p], c in assoc(p, :comments)) |> select([p, c], {p, c}) Post |> join(:left, [p], c in Comment, c.post_id == p.id and c.is_visible == true) |> select([p, c], {p, c})
9.オーバーライド条件
Ectoを使用すると、リクエストで既に定義されている条件を削除したり、デフォルト値を返すことができます。これにはexclude/2
式を使用します。
query |> Ecto.Query.exclude(:select) Ecto.Query.exclude(query, :select)
一括行操作のコマンド
一括挿入
Ecto.Repo.insert_all/3
関数は、転送されたすべてのレコードを挿入します。
Repo.insert_all(Client, [[name: "Cain Ramirez", age: 34], [name: "Jean Rousey", age: 29]]) Repo.insert_all(Client, [%{name: "Cain Ramirez", age: 34}, %{name: "Jean Rousey", age: 29}])
insert_all/3
関数は、 inserted_at
やupdated_at
などの自動insert_all/3
フィールドを処理しません。
一括更新
Ecto.Repo.update_all/3
関数Ecto.Repo.update_all/3
、渡されたフィールド値のクエリ条件に該当するすべての行を更新します。
Repo.update_all(Client, set: [state: "new"]) Repo.update_all(Client, inc: [age: 1]) from(c in Client, where: p.sex < 0) |> Repo.update_all(set: [state: "new"]) from(c in Client, where: p.sex > 0, update: [set: [state: "new"]]) |> Repo.update_all([]) from(c in Client, where: c.id < 10, update: [set: [state: fragment("?", new)]]) |> Repo.update_all([])
一括削除
Ecto.Repo.delete_all/2
関数は、クエリ条件に該当するすべての行を削除します。
Repo.delete_all(Client) from(p in Client, where: p.age == 0) |> Repo.delete_all
実用例
リクエストの構成
query = from p in App.Product, select: p query2 = from p in query, where: p.state == "published" App.Repo.all(query2)
ページネーション機能
defmodule Finders.Common.Paging do import Ecto.Query def page(query), do: page(query, 1) def page(query, page), do: page(query, page, 10) def page(query, page, per_page) do offset = per_page * (page-1) query |> offset([_], ^offset) |> limit([_], ^per_page) end end
# With Posts: second page, five per page posts = Post |> Finders.Common.Paging.page(2, 5) |> Repo.all # With Tags: third page, 10 per page tags = Tag |> Finders.Common.Paging.page(3) |> Repo.all
Query.API
比較演算子: ==
!=
、 <=
、 >=
、 <
、 >
ブール演算子: and
、 or
、 not
スイッチオペレーター: in/2
検索機能: like/2
およびilike/2
ヌルチェック: is_nil/1
アグリゲーター: count/1
、 avg/1
、 sum/1
、 min/1
、 max/1
任意のSQLサブクエリの関数: fragment/1
from p in Post, where: p.published_at > ago(3, "month") from p in Post, where: p.id in [1, 2, 3] from p in Payment, select: avg(p.value) from p in Post, where: p.published_at > datetime_add(^Ecto.DateTime.utc, -1, "month") from p in Post, where: is_nil(p.published_at) from p in Post, where: ilike(p.body, "Chapter%") from p in Post, where: is_nil(p.published_at) and fragment("lower(?)", p.title) == "title"
Ecto.Adapters.SQLからの追加
Ecto.Adapters.SQL.query/4
転送されたリポジトリ内で任意のSQLクエリを実行します。
Ecto.Adapters.SQL.query(Showcase, "SELECT $1::integer + $2", [40, 2]) => {:ok, %{rows: [{42}], num_rows: 1}}
この関数のActiveRecordに最も近いものは、 find_by_sql
メソッドです。
Ecto.Adapters.SQL.to_sql/3
式から構築されたクエリをSQLに変換します。
Ecto.Adapters.SQL.to_sql(:all, repo, Showcase.Client) => {"SELECT c.id, c.name, c.age, c.sex, c.state, c.inserted_at, c.created_at FROM clients as c", []} Ecto.Adapters.SQL.to_sql(:update_all, repo, from(c in Showcase.Client, update: [set: [state: ^"new"]])) => {"UPDATE clients AS c SET state = $1", ["new"]}
この関数は、ActiveRecord :: Relationの同じメソッドです。
文学
http://guides.rubyonrails.org/active_record_querying.html
https://hexdocs.pm/ecto/Ecto.html
https://github.com/elixir-ecto/ecto
https://blog.drewolson.org/composable-queries-ecto/
http://blog.plataformatec.com.br/2016/05/ectos-insert_all-and-schemaless-queries/
あとがき
関数型プログラミング言語のElixirに興味がある場合、または単に共感している場合は、 Wunsh && ElixirおよびProElixir Telegramチャットに参加することをお勧めします。
国内のElixirコミュニティは、プロジェクトWunsh.ruに直面して単一のプラットフォームとして登場し始めています。 現在、プロジェクトにはテーマに関するニュースレターがあり、違法なものはありません。週に一度、ロシア語でエリキシルに関する記事の選択を含む手紙が届きます。
UPD:
pure_evilからの更新-MongoDBでは、 Ectoの2番目のバージョンが機能しますが、これまでのところフォークの形式になっています: https : //github.com/michalmuskala/mongodb_ecto/pull/91