I'm trying to insert an invoice struct along with its associated invoice items. I'm able to insert the invoice data, and call an anonymous function to validate, cast, and insert each item. Since insert/2 does not produce a return, how can I get the invoice_id for the items while still being able to roll back the entire transaction if one item fails validation or insertion?
I've put the code in my own repo, here it is:
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
and here is how I use it from my controller:
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
There aren't many up to date examples out there with Ecto, so sorry if these are noob questions ;-). Anyone have an idea? I tried putting the invoice insert in a private function, and using a case block to determine whether the main transaction should roll back, but I couldn't figure out how to get the invoice id back from that either.
Repo.insert/1
actually returns the model you have just inserted. You also want to decouple the validation from the transaction handling as much as possible. I would suggest something as follows:
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