web-dev-qa-db-ja.com

関連するモデルをEctoに挿入する

請求書構造体とそれに関連する請求書アイテムを挿入しようとしています。請求書データを挿入し、無名関数を呼び出して各アイテムを検証、キャスト、挿入することができます。 insert/2は返品を生成しないので、1つのアイテムが検証または挿入に失敗した場合でも、トランザクション全体をロールバックできる一方で、アイテムのinvoice_idを取得するにはどうすればよいですか?

私は自分のリポジトリにコードを入れました、ここにあります:

def insertassoc(params) do
 Repo.transaction(fn ->    
  i = Invoice.changeset(params["data"], :create)
    if i.valid? do
      Repo.insert(i)
    else
      Repo.rollback(i.errors)
    end

  insert_include = fn k ->
    c = InvoiceItem.changeset(k, :create)
    if c.valid? do
      Repo.insert(c)
    else
      Repo.rollback(c.errors)
    end
  end

  for include <- params["includes"] do
    insert_include.(Map.merge(include, %{"invoice_id" => ????}))
  end

 end)
end

これが私のコントローラーからの使い方です:

def create(conn, params) do
 case InvoiceRepo.insertassoc(params) do
  {:ok, x} ->
    json conn, Map.merge(params, %{"message" => "OK"})
  {:error, x} ->
    json conn |> put_status(400), Map.merge(params, %{"message" 
    => "Error"})
 end
end

Ectoの最新の例はそれほど多くないので、これらが初心者の質問である場合は申し訳ありません;-)。誰かアイデアがありますか?請求書の挿入をプライベート関数に入れ、ケースブロックを使用してメイントランザクションをロールバックするかどうかを決定しようとしましたが、そこから請求書IDを取得する方法もわかりませんでした。

17
Dania_es

Repo.insert/1は、挿入したばかりのモデルを実際に返します。また、検証をトランザクション処理から可能な限り切り離す必要があります。私は次のように何かを提案します:

invoice = Invoice.changeset(params["data"], :create)
items   = Enum.map(params["includes"], &InvoiceItem.changeset(&1, :create))

if invoice.valid? && Enum.all?(items, & &1.valid?) do
  Repo.transaction fn ->
    invoice = Repo.insert(invoice)
    Enum.map(items, fn item ->
      item = Ecto.Changeset.change(item, invoice_id: invoice.id)
      Repo.insert(item)
    end)
  end
else
  # handle errors
end
28
José Valim

Ecto 2.0では、次のようなことをします。

%My.Invoice{}
|> Ecto.Changeset.change
|> Ecto.Changeset.put_assoc(:invoice_items, [My.InvoiceItem.changeset(%My.InvoiceItem{}, %{description: "bleh"})])
|> My.Repo.insert!

(受け入れられた回答は2.0より前でも機能します。また、Valimは、その回答のコメントでput_assocの存在について言及しています)

10
edmz