Ecto Brochure-Elixir Database Interface

エクト







エントリー



データベースとの通信用に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モジュール



  1. changeset.modelはchangeset.dataに名前が変更されました(以降、Ectoには「モデル」はありません)。
  2. cast/4



    への必須フィールドとオプションの転送は廃止されたと見なされます;以降、 cast/3



    およびvalidate_required/3



    を使用する必要があります。
  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:









Ecto.Query:









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/







jbを使用した学習







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








All Articles