エクト クエリを記述し、データベースと対話するためのドメイン固有言語です。 Elixir言語 。最新バージョン(2.0)はPostgreSQLとMySQLをサポートしています。 (MSSQL、SQLite、およびMongoDBのサポートは将来利用可能になる予定です)。初めての方へ エリクサー または、経験が少ない場合は、Kleber VirgilioCorreiaの記事を読むことをお勧めします。 Elixirプログラミング言語入門 。
Ectoは、次の4つの主要コンポーネントで構成されています。
このチュートリアルでは、次のものが必要です。
手始めに、Mixを使用してスーパーバイザーと一緒に新しいアプリを作成しましょう。 ミックス は、Elixirに付属するビルドツールであり、アプリケーションの作成、コンパイル、テスト、依存関係の管理などのタスクを提供します。
mix new cart --sup
これにより、初期プロジェクトファイルを含むディレクトリカートが作成されます。
* creating README.md * creating .gitignore * creating mix.exs * creating config * creating config/config.exs * creating lib * creating lib/ecto_tut.ex * creating test * creating test/test_helper.exs * creating test/ecto_tut_test.exs
--sup
を使用していますデータベースへの接続を維持するスーパーバイザーツリーが必要なため、オプション。次に、cart
に移動しますcd cart
のあるディレクトリファイルを開きますmix.exs
内容を置き換えます。
defmodule Cart.Mixfile do use Mix.Project def project do [app: :cart, version: '0.0.1', elixir: '~> 1.2', build_embedded: Mix.env == :prod, start_permanent: Mix.env == :prod, deps: deps] end def application do [applications: [:logger, :ecto, :postgrex], mod: {Cart, []}] end # Type 'mix help deps' for more examples and options defp deps do [{:postgrex, '>= 0.11.1'}, {:ecto, '~> 2.0'}] end end
def application do
でアプリケーションとして追加する必要があります:postgrex, :ecto
したがって、これらはアプリケーション内で使用できます。また、defp deps do
を追加して、それらを依存関係として追加する必要があります。 postgrex (これはデータベースアダプタです)および エクト 。ファイルを編集したら、コンソールで実行します。
mix deps.get
これにより、すべての依存関係がインストールされ、ファイルが作成されますmix.lock
インストールされたパッケージのすべての依存関係とサブ依存関係を格納します(bundleのGemfile.lock
と同様)。
次に、アプリケーションでリポジトリを定義する方法を見ていきます。複数のリポジトリを持つことができます。つまり、複数のデータベースに接続できます。ファイルconfig/config.exs
でデータベースを構成する必要があります。
use Mix.Config config :cart, ecto_repos: [Cart.Repo]
最小値を設定しているだけなので、次のコマンドを実行できます。行:cart, cart_repos: [Cart.Repo]
使用しているリポジトリをEctoに通知しています。これは、多くのリポジトリを持つことができる、つまり複数のデータベースに接続できるため、優れた機能です。
設計において比率が重要な理由
次に、次のコマンドを実行します。
mix ecto.gen.repo
==> connection Compiling 1 file (.ex) Generated connection app ==> poolboy (compile) Compiled src/poolboy_worker.erl Compiled src/poolboy_sup.erl Compiled src/poolboy.erl ==> decimal Compiling 1 file (.ex) Generated decimal app ==> db_connection Compiling 23 files (.ex) Generated db_connection app ==> postgrex Compiling 43 files (.ex) Generated postgrex app ==> ecto Compiling 68 files (.ex) Generated ecto app ==> cart * creating lib/cart * creating lib/cart/repo.ex * updating config/config.exs Don't forget to add your new repo to your supervision tree (typically in lib/cart.ex): supervisor(Cart.Repo, []) And to add it to the list of ecto repositories in your configuration files (so Ecto tasks work as expected): config :cart, ecto_repos: [Cart.Repo]
このコマンドはリポジトリを生成します。出力を読み取ると、アプリにスーパーバイザーとリポジトリを追加するように指示されます。スーパーバイザーから始めましょう。 lib/cart.ex
を編集します:
defmodule Cart do use Application def start(_type, _args) do import Supervisor.Spec, warn: false children = [ supervisor(Cart.Repo, []) ] opts = [strategy: :one_for_one, name: Cart.Supervisor] Supervisor.start_link(children, opts) end end
このファイルでは、スーパーバイザーを定義していますsupervisor(Cart.Repo, [])
そしてそれを子リストに追加します(Elixirでは、リストは配列に似ています)。戦略で監督される子供たちを定義しますstrategy: :one_for_one
つまり、監視対象プロセスの1つに障害が発生した場合、スーパーバイザーはそのプロセスのみをデフォルト状態に再起動します。あなたは監督者についてもっと学ぶことができます ここに 。 lib/cart/repo.ex
を見ればこのファイルはすでに作成されていることがわかります。つまり、 レポ 私たちのアプリケーションのために。
defmodule Cart.Repo do use Ecto.Repo, otp_app: :cart end
それでは、構成ファイルを編集しましょうconfig/config.exs
:
use Mix.Config config :cart, ecto_repos: [Cart.Repo] config :cart, Cart.Repo, adapter: Ecto.Adapters.Postgres, database: 'cart_dev', username: 'postgres', password: 'postgres', hostname: 'localhost'
データベースのすべての構成を定義したら、次を実行してデータベースを生成できます。
mix ecto.create
このコマンドはデータベースを作成し、これで基本的に構成が完了しました。これでコーディングを開始する準備が整いましたが、最初にアプリのスコープを定義しましょう。
デモアプリケーションでは、簡単な請求ツールを作成します。チェンジセット(モデル)については、 請求書 、 項目 そして InvoiceItem 。 InvoiceItem 属する 請求書 そして 項目 。この図は、モデルが互いにどのように関連するかを表しています。
ダイアグラムは非常に単純です。テーブルがあります 請求書 それはたくさんあります invoice_items ここにすべての詳細とテーブルを保存します アイテム それはたくさんあります invoice_items 。あなたはそのタイプが invoice_id そして item_id に invoice_items テーブルはUUIDです。 UUIDを使用しているのは、APIを介してアプリを公開する場合にルートを難読化するのに役立ち、連番に依存しないため同期が簡単になるためです。それでは、ミックスタスクを使用してテーブルを作成しましょう。
移行は、データベーススキーマを変更するために使用されるファイルです。 Ecto.Migration テーブルの作成、インデックスの追加、制約の作成、およびその他のスキーマ関連のものを作成するための一連のメソッドを提供します。移行は、アプリケーションとデータベースの同期を維持するのに役立ちます。最初のテーブルの移行スクリプトを作成しましょう。
mix ecto.gen.migration create_invoices
これにより、priv/repo/migrations/20160614115844_create_invoices.exs
のようなファイルが生成されます。ここで、移行を定義します。生成されたファイルを開き、その内容を次のように変更します。
defmodule Cart.Repo.Migrations.CreateInvoices do use Ecto.Migration def change do create table(:invoices, primary_key: false) do add :id, :uuid, primary_key: true add :customer, :text add :amount, :decimal, precision: 12, scale: 2 add :balance, :decimal, precision: 12, scale: 2 add :date, :date timestamps end end end
内部メソッドdef change do
データベースのSQLを生成するスキーマを定義します。 create table(:invoices, primary_key: false) do
テーブルを作成します 請求書 。 primary_key: false
を設定しましたただし、タイプのIDフィールドを追加します UUID 、タイプテキストの顧客フィールド、タイプ日付の日付フィールド。 timestamps
メソッドはフィールドを生成しますinserted_at
およびupdated_at
Ectoは、レコードが挿入された時刻と更新された時刻をそれぞれ自動的に入力します。次に、コンソールに移動して移行を実行します。
mix ecto.migrate
定義されたすべてのフィールドを含むテーブルinvoice
sを作成しました。を作成しましょう アイテム テーブル:
mix ecto.gen.migration create_items
次に、生成された移行スクリプトを編集します。
defmodule Cart.Repo.Migrations.CreateItems do use Ecto.Migration def change do create table(:items, primary_key: false) do add :id, :uuid, primary_key: true add :name, :text add :price, :decimal, precision: 12, scale: 2 timestamps end end end
ここでの新しいことは、12桁の数値を許可する10進数フィールドであり、そのうちの2つは数値の小数部分用です。移行をもう一度実行してみましょう。
mix ecto.migrate
今、私たちは作成しました アイテム テーブルと最後に作成しましょう invoice_items テーブル:
mix ecto.gen.migration create_invoice_items
移行を編集します。
defmodule Cart.Repo.Migrations.CreateInvoiceItems do use Ecto.Migration def change do create table(:invoice_items, primary_key: false) do add :id, :uuid, primary_key: true add :invoice_id, references(:invoices, type: :uuid, null: false) add :item_id, references(:items, type: :uuid, null: false) add :price, :decimal, precision: 12, scale: 2 add :quantity, :decimal, precision: 12, scale: 2 add :subtotal, :decimal, precision: 12, scale: 2 timestamps end create index(:invoice_items, [:invoice_id]) create index(:invoice_items, [:item_id]) end end
ご覧のとおり、この移行にはいくつかの新しい部分があります。最初に気付くのはadd :invoice_id, references(:invoices, type: :uuid, null: false)
です。これにより、フィールドが作成されます invoice_id を参照するデータベース内の制約付き 請求書 テーブル。同じパターンがあります item_id フィールド。異なるもう1つの点は、インデックスの作成方法です。create index(:invoice_items, [:invoice_id])
インデックスを作成します invoice_items_invoice_id_index 。
Ectoでは、Ecto.Model
Ecto.Schema
の使用を優先して非推奨になっているため、モデルではなくモジュールスキーマを呼び出します。チェンジセットを作成しましょう。最も単純なチェンジセットアイテムから始めて、ファイルlib/cart/item.ex
を作成します。
defmodule Cart.Item do use Ecto.Schema import Ecto.Changeset alias Cart.InvoiceItem @primary_key {:id, :binary_id, autogenerate: true} schema 'items' do field :name, :string field :price, :decimal, precision: 12, scale: 2 has_many :invoice_items, InvoiceItem timestamps end @fields ~w(name price) def changeset(data, params \ %{}) do data |> cast(params, @fields) |> validate_required([:name, :price]) |> validate_number(:price, greater_than_or_equal_to: Decimal.new(0)) end end
上部では、use Ecto.Schema
を使用してチェンジセットにコードを挿入します。 import Ecto.Changeset
も使用していますから機能をインポートするには Ecto.Changeset 。インポートする特定のメソッドを指定することもできますが、シンプルにしましょう。 alias Cart.InvoiceItem
チェンジセット内に直接書き込むことができます InvoiceItem 、すぐにわかるように。
@primary_key {:id, :binary_id, autogenerate: true}
主キーが自動生成されることを指定します。 UUIDタイプを使用しているため、スキーマをschema 'items' do
で定義します。ブロック内で、各フィールドと関係を定義します。定義しました 名前 文字列として 価格 10進数として、移行と非常によく似ています。次に、マクロhas_many :invoice_items, InvoiceItem
間の関係を示します 項目 そして InvoiceItem 。慣例により、フィールドに名前を付けたので item_id の中に invoice_items テーブルでは、外部キーを構成する必要はありません。最後に タイムスタンプ メソッドはを設定します insert_at そして update_at 田畑。
def changeset(data, params \ %{}) do
関数は、パラメータを含むElixir構造体を受け取ります。 パイプ さまざまな機能を介して。 cast(params, @fields)
値を正しいタイプにキャストします。たとえば、paramsで渡すことができるのは文字列のみであり、それらはスキーマで定義された正しい型に変換されます。 validate_required([:name, :price])
を検証します 名前 そして 価格 フィールドが存在します、validate_number(:price, greater_than_or_equal_to: Decimal.new(0))
数値が0以上、この場合はDecimal.new(0)
であることを検証します。
それは多くのことを理解する必要があったので、概念をよりよく理解できるように、例を使用してコンソールでこれを見てみましょう。
iex -S mix
これにより、コンソールがロードされます。 -S mix
現在のプロジェクトをiexREPLにロードします。
CFOの責任は何ですか
iex(0)> item = Cart.Item.changeset(%Cart.Item{}, %{name: 'Paper', price: '2.5'}) #Ecto.Changeset
これはEcto.Changeset
を返しますエラーなしで有効な構造体。それを保存しましょう:
iex(1)> item = Cart.Repo.insert!(item) %Cart.Item{__meta__: #Ecto.Schema.Metadata, id: '66ab2ab7-966d-4b11-b359-019a422328d7', inserted_at: #Ecto.DateTime, invoice_items: #Ecto.Association.NotLoaded, name: 'Paper', price: #Decimal, updated_at: #Ecto.DateTime}
簡潔にするためにSQLは示していません。この場合、それは Cart.Item すべての値が設定された構造体、あなたはそれを見ることができます insert_at そして update_at タイムスタンプと id フィールドにはUUID値があります。他のいくつかのケースを見てみましょう:
iex(3)> item2 = Cart.Item.changeset(%Cart.Item{price: Decimal.new(20)}, %{name: 'Scissors'}) #Ecto.Changeset iex(4)> Cart.Repo.insert(item2)
これでScissors
を設定しました別の方法でアイテムを購入し、価格を直接設定します%Cart.Item{price: Decimal.new(20)}
。文字列を価格として渡した最初のアイテムとは異なり、正しいタイプを設定する必要があります。 floatを渡すこともでき、これは10進型にキャストされます。たとえば%Cart.Item{price: 12.5}
を渡すと、アイテムを挿入すると、タイプが一致しないことを示す例外がスローされます。
iex(4)> invalid_item = Cart.Item.changeset(%Cart.Item{}, %{name: 'Scissors', price: -1.5}) #Ecto.Changeset
コンソールを終了するには、Ctrl + Cを2回押します。検証が機能しており、価格がゼロ(0)以上である必要があることがわかります。ご覧のとおり、すべてのスキーマを定義しました Ecto.Schema これは、モジュールの構造とチェンジセットの定義方法に関連する部分です。 Ecto.Changeset これはすべて検証とキャストです。続けてファイルを作成しましょうlib/cart/invoice_item.ex
:
defmodule Cart.InvoiceItem do use Ecto.Schema import Ecto.Changeset @primary_key {:id, :binary_id, autogenerate: true} schema 'invoice_items' do belongs_to :invoice, Cart.Invoice, type: :binary_id belongs_to :item, Cart.Item, type: :binary_id field :quantity, :decimal, precision: 12, scale: 2 field :price, :decimal, precision: 12, scale: 2 field :subtotal, :decimal, precision: 12, scale: 2 timestamps end @fields ~w(item_id price quantity) @zero Decimal.new(0) def changeset(data, params \ %{}) do data |> cast(params, @fields) |> validate_required([:item_id, :price, :quantity]) |> validate_number(:price, greater_than_or_equal_to: @zero) |> validate_number(:quantity, greater_than_or_equal_to: @zero) |> foreign_key_constraint(:invoice_id, message: 'Select a valid invoice') |> foreign_key_constraint(:item_id, message: 'Select a valid item') |> set_subtotal end def set_subtotal(cs) do case cs.data.price), (cs.changes[:quantity] do {_price, nil} -> cs {nil, _quantity} -> cs {price, quantity} -> put_change(cs, :subtotal, Decimal.mult(price, quantity)) end end end
このチェンジセットはもっと大きいですが、あなたはすでにそれのほとんどに精通しているはずです。ここbelongs_to :invoice, Cart.Invoice, type: :binary_id
との「所属」関係を定義します Cart.Invoice 間もなく作成するチェンジセット。次のbelongs_to :item
itemsテーブルとの関係を作成します。 @zero Decimal.new(0)
を定義しました。この場合、 @ゼロ モジュール内でアクセスできる定数のようなものです。チェンジセット関数には新しい部分があり、そのうちの1つはforeign_key_constraint(:invoice_id, message: 'Select a valid invoice')
です。これにより、制約が満たされていない場合に例外を生成する代わりに、エラーメッセージを生成できます。そして最後に、方法 set_subtotal 小計を計算します。チェンジセットを渡し、価格と数量の両方がある場合に計算された小計を使用して新しいチェンジセットを返します。
それでは、を作成しましょう Cart.Invoice 。したがって、ファイルを作成して編集しますlib/cart/invoice.ex
以下を含む:
defmodule Cart.Invoice do use Ecto.Schema import Ecto.Changeset alias Cart.{Invoice, InvoiceItem, Repo} @primary_key {:id, :binary_id, autogenerate: true} schema 'invoices' do field :customer, :string field :amount, :decimal, precision: 12, scale: 2 field :balance, :decimal, precision: 12, scale: 2 field :date, Ecto.Date has_many :invoice_items, InvoiceItem, on_delete: :delete_all timestamps end @fields ~w(customer amount balance date) def changeset(data, params \ %{}) do data |> cast(params, @fields) |> validate_required([:customer, :date]) end def create(params) do cs = changeset(%Invoice{}, params) |> validate_item_count(params) |> put_assoc(:invoice_items, get_items(params)) if cs.valid? do Repo.insert(cs) else cs end end defp get_items(params) do items = params[:invoice_items] || params['invoice_items'] Enum.map(items, fn(item)-> InvoiceItem.changeset(%InvoiceItem{}, item) end) end defp validate_item_count(cs, params) do items = params[:invoice_items] || params['invoice_items'] if Enum.count(items) <= 0 do add_error(cs, :invoice_items, 'Invalid number of items') else cs end end end
Cart.Invoice チェンジセットにはいくつかの違いがあります。最初のものは中にあります スキーマ :has_many :invoice_items, InvoiceItem, on_delete: :delete_all
請求書を削除すると、関連するすべての invoice_items 削除されます。ただし、これはデータベースで定義されている制約ではないことに注意してください。
物事をよりよく理解するために、コンソールのcreateメソッドを試してみましょう。ここで使用するアイテム(「紙」、「はさみ」)を作成した可能性があります。
iex(0)> item_ids = Enum.map(Cart.Repo.all(Cart.Item), fn(item)-> item.id end) iex(1)> {id1, id2} = {Enum.at(item_ids, 0), Enum.at(item_ids, 1) }
すべてのアイテムをフェッチしました Cart.Repo.all と Enum.map item.id
を取得する関数各アイテムの。 2行目では、id1
を割り当てるだけです。およびid2
それぞれ、最初と2番目のitem_idsを使用します。
iex(2)> inv_items = [%{item_id: id1, price: 2.5, quantity: 2}, %{item_id: id2, price: 20, quantity: 1}] iex(3)> {:ok, inv} = Cart.Invoice.create(%{customer: 'James Brown', date: Ecto.Date.utc, invoice_items: inv_items})
請求書はinvoice_itemsで作成されており、これですべての請求書を取得できます。
iex(4)> alias Cart.{Repo, Invoice} iex(5)> Repo.all(Invoice)
あなたはそれが返すのを見ることができます 請求書 しかし、私たちはまた見たいです invoice_items :
次のうち、一般的なセキュリティの問題と見なされるのはどれですか? (該当するものをすべて選択してください。)
iex(6)> Repo.all(Invoice) |> Repo.preload(:invoice_items)
とともに Repo.preload 関数、invoice_items
を取得できます。これにより、クエリを同時に処理できることに注意してください。私の場合、クエリは次のようになりました。
iex(7)> Repo.get(Invoice, '5d573153-b3d6-46bc-a2c0-6681102dd3ab') |> Repo.preload(:invoice_items)
これまで、関係を使用して新しいアイテムと新しい請求書を作成する方法を示してきました。しかし、クエリはどうですか?さて、紹介させてください Ecto.Query これはデータベースへのクエリを作成するのに役立ちますが、最初に、より適切に説明するためにより多くのデータが必要です。
iex(1)> alias Cart.{Repo, Item, Invoice, InvoiceItem} iex(2)> Repo.insert(%Item{name: 'Chocolates', price: Decimal.new('5')}) iex(3)> Repo.insert(%Item{name: 'Gum', price: Decimal.new('2.5')}) iex(4)> Repo.insert(%Item{name: 'Milk', price: Decimal.new('1.5')}) iex(5)> Repo.insert(%Item{name: 'Rice', price: Decimal.new('2')}) iex(6)> Repo.insert(%Item{name: 'Chocolates', price: Decimal.new('10')})
これで8つのアイテムができ、「チョコレート」が繰り返されます。どの項目が繰り返されているかを知りたい場合があります。それでは、このクエリを試してみましょう。
iex(7)> import Ecto.Query iex(8)> q = from(i in Item, select: %{name: i.name, count: (i.name)}, group_by: i.name) iex(9)> Repo.all(q) 19:12:15.739 [debug] QUERY OK db=2.7ms SELECT i0.'name', count(i0.'name') FROM 'items' AS i0 GROUP BY i0.'name' [] [%{count: 1, name: 'Scissors'}, %{count: 1, name: 'Gum'}, %{count: 2, name: 'Chocolates'}, %{count: 1, name: 'Paper'}, %{count: 1, name: 'Milk'}, %{count: 1, name: 'Test'}, %{count: 1, name: 'Rice'}]
クエリで、アイテムの名前とアイテムテーブルに表示された回数を含むマップを返したいことがわかります。あるいは、最も売れている製品を確認することに興味があるかもしれません。そのために、いくつかの請求書を作成しましょう。まず、item_id
にアクセスするための地図を作成して、私たちの生活を楽にしましょう。
iex(10)> l = Repo.all(from(i in Item, select: {i.name, i.id})) iex(11)> items = for {k, v} '8fde33d3-6e09-4926-baff-369b6d92013c', 'Gum' => 'cb1c5a93-ecbf-4e4b-8588-cc40f7d12364', 'Milk' => '7f9da795-4d57-4b46-9b57-a40cd09cf67f', 'Paper' => '66ab2ab7-966d-4b11-b359-019a422328d7', 'Rice' => 'ff0b14d2-1918-495e-9817-f3b08b3fa4a4', 'Scissors' => '397b0bb4-2b04-46df-84d6-d7b1360b6c72', 'Test' => '9f832a81-f477-4912-be2f-eac0ec4f8e8f'}
ご覧のとおり、を使用してマップを作成しました 理解
iex(12)> line_items = [%{item_id: items['Chocolates'], quantity: 2}]
invoice_items
に価格を追加する必要があります請求書を作成するためのパラメータですが、アイテムのIDを渡して、価格を自動的に入力する方がよいでしょう。変更を加えます Cart.Invoice これを達成するためのモジュール:
defmodule Cart.Invoice do use Ecto.Schema import Ecto.Changeset import Ecto.Query # We add to query # .... # schema, changeset and create functions don't change # The new function here is items_with_prices defp get_items(params) do items = items_with_prices(params[:invoice_items] || params['invoice_items']) Enum.map(items, fn(item)-> InvoiceItem.changeset(%InvoiceItem{}, item) end) end # new function to get item prices defp items_with_prices(items) do item_ids = Enum.map(items, fn(item) -> item[:item_id] || item['item_id'] end) q = from(i in Item, select: %{id: i.id, price: i.price}, where: i.id in ^item_ids) prices = Repo.all(q) Enum.map(items, fn(item) -> item_id = item[:item_id] || item['item_id'] % end) end
最初に気付くのは、追加したことです Ecto.Query 、これにより、データベースにクエリを実行できるようになります。新しい関数はdefp items_with_prices(items) do
です。アイテムを検索し、各アイテムの価格を見つけて設定します。
まず、defp items_with_prices(items) do
引数としてリストを受け取ります。 item_ids = Enum.map(items, fn(item) -> item[:item_id] || item['item_id'] end)
を使用すると、すべてのアイテムを反復処理して、 item_id 。ご覧のとおり、アトム:item_id
でアクセスします。または文字列「item_id」。マップはこれらのいずれかをキーとして持つことができるためです。クエリq = from(i in Item, select: %{id: i.id, price: i.price}, where: i.id in ^item_ids)
item_ids
にあるすべてのアイテムを検索しますitem.id
のマップを返しますおよびitem.price
。次に、クエリを実行できますprices = Repo.all(q)
マップのリストを返します。次に、アイテムを繰り返し処理し、価格を追加する新しいリストを作成する必要があります。 Enum.map(items, fn(item) ->
各アイテムを反復処理し、価格Enum.find(prices, fn(p) -> p[:id] == item_id end)[:price] || 0
を見つけて、item_id
、数量、および価格で新しいリストを作成します。これで、invoice_items
のそれぞれに価格を追加する必要がなくなりました。
ご存知のように、以前に地図を作成しました アイテム これにより、 id アイテム名を使用します。つまり、items['Gum']
「cb1c5a93-ecbf-4e4b-8588-cc40f7d12364」。これにより、作成が簡単になります invoice_items 。さらに請求書を作成しましょう。コンソールを再度起動して、以下を実行します。
Iex -S mix
iex(1)> Repo.delete_all(InvoiceItem); Repo.delete_all(Invoice)
すべて削除します invoice_items 白紙の状態の請求書:
iex(2)> li = [%{item_id: items['Gum'], quantity: 2}, %{item_id: items['Milk'], quantity: 1}] iex(3)> Invoice.create(%{customer: 'Mary Jane', date: Ecto.Date.utc, invoice_items: li}) iex(4)> li2 = [%{item_id: items['Chocolates'], quantity: 2}| li] iex(5)> Invoice.create(%{customer: 'Mary Jane', date: Ecto.Date.utc, invoice_items: li2}) iex(5)> li3 = li2 ++ [%{item_id: items['Paper'], quantity: 3 }, %{item_id: items['Rice'], quantity: 1}, %{item_id: items['Scissors'], quantity: 1}] iex(6)> Invoice.create(%{customer: 'Juan Perez', date: Ecto.Date.utc, invoice_items: li3})
これで3つの請求書ができました。 1つ目は2アイテム、2つ目は3アイテム、3つ目は6アイテムです。どの商品が最も売れている商品か知りたいのですが?これに答えるために、数量および小計(価格x数量)で最も売れているアイテムを見つけるクエリを作成します。
defmodule Cart.Item do use Ecto.Schema import Ecto.Changeset import Ecto.Query alias Cart.{InvoiceItem, Item, Repo} # schema and changeset don't change # ... def items_by_quantity, do: Repo.all items_by(:quantity) def items_by_subtotal, do: Repo.all items_by(:subtotal) defp items_by(type) do from i in Item, join: ii in InvoiceItem, on: ii.item_id == i.id, select: %{id: i.id, name: i.name, total: sum(field(ii, ^type))}, group_by: i.id, order_by: [desc: sum(field(ii, ^type))] end end
輸入 Ecto.Query そしてalias Cart.{InvoiceItem, Item, Repo}
したがって、各モジュールの最初にカートを追加する必要はありません。最初の機能 items_by_quantity items_by
を呼び出します関数、:quantity
を渡しますパラメータと呼び出し Repo.all クエリを実行します。関数 items_by_subtotal 前の関数と似ていますが、:subtotal
を渡しますパラメータ。それでは説明しましょう items_by :
from i in Item
、このマクロはアイテムモジュールを選択しますjoin: ii in InvoiceItem, on: ii.item_id == i.id
、条件「items.id = invoice_items.item_id」で結合を作成しますselect: %{id: i.id, name: i.name, total: sum(field(ii, ^type))}
、最初に必要なすべてのフィールドを含むマップを生成しています。最初にItemからIDと名前を選択し、演算子の合計を実行します。 field(ii、^ type)は、マクロフィールドを使用してフィールドに動的にアクセスしますgroup_by: i.id
、items.idでグループ化しますorder_by: [desc: sum(field(ii, ^type))]
そして最後に合計で降順で並べ替えますこれまで、クエリをリストスタイルで記述しましたが、マクロスタイルで書き直すこともできます。
defp items_by(type) do Item |> join(:inner, [i], ii in InvoiceItem, ii.item_id == i.id) |> select([i, ii], %{id: i.id, name: i.name, total: sum(field(ii, ^type))}) |> group_by([i, _], i.id) |> order_by([_, ii], [desc: sum(field(ii, ^type))]) end
クエリは読みやすいので、リスト形式で書く方が好きです。
Ectoを使用してアプリで実行できることの大部分について説明しました。もちろん、あなたが学ぶことができることはもっとたくさんあります Ectoドキュメント 。 Ectoを使用すると、Erlang仮想マシンのおかげで簡単に拡張できる、わずかな労力で同時のフォールトトレラントアプリケーションを作成できます。 Ectoは、Elixirアプリケーションのストレージの基盤を提供し、データを簡単に管理するための関数とマクロを提供します。
このチュートリアルでは、 Ecto.Schema 、 Ecto.Changeset 、 Ecto.Migration 、 Ecto.Query 、および Ecto.Repo 。これらの各モジュールは、アプリケーションのさまざまな部分で役立ち、コードをより明確にし、保守と理解を容易にします。
チュートリアルのコードをチェックしたい場合は、それを見つけることができます ここに GitHubで。
このチュートリアルが好きで、より多くの情報に興味があるなら、私はお勧めします フェニックス (素晴らしいプロジェクトのリストについて)、 素晴らしいエリクサー 、および この話 ActiveRecordとEctoを比較します。