How to insert parent and child via Ecto.Multi?

I have a model containing Transactions and Splits. The splits are children of transactions. I’m trying to first insert a new transaction, pick-up the freshly generated transaction.uuid, and use that value to insert into the child row to be created in the next step. I’m having trouble finding a way to reference the fresh minted uuid from the first step in the insert on child. What would be considered a good way to handle this? Thanks.

test "SPLIT inserts via Ecto.Multi style transaction." do
      multi =
        Ecto.Multi.new()
        |> Ecto.Multi.insert(:transaction, create_transaction(transaction_map()))
        |> Ecto.Multi.insert(:split, create_split(split_map(:transaction)))

      case Repo.transaction(multi) do
        {:ok, _results} ->
          IO.puts("Success")

        {:error, :transaction, changeset, _changes} ->
          IO.inspect(changeset.errors, label: "Transaction insert failed.")

        {:error, :split, changeset, _changes} ->
          IO.inspect(changeset.errors, label: "split insert failed.")
      end
end

You would be able to get it like this using Ecto.Multi.insert/4

test "SPLIT inserts via Ecto.Multi style transaction." do
  multi =
    Ecto.Multi.new()
    |> Ecto.Multi.insert(:transaction, create_transaction(transaction_map()))
    |> Ecto.Multi.insert(:split, fn %{transaction: transaction} ->
      # `transaction` is the inserted transaction a step before
      create_split(transaction.id)
    end)

  case Repo.transaction(multi) do
    {:ok, _results} ->
      IO.puts("Success")

    {:error, :transaction, changeset, _changes} ->
      IO.inspect(changeset.errors, label: "Transaction insert failed.")

    {:error, :split, changeset, _changes} ->
      IO.inspect(changeset.errors, label: "split insert failed.")
  end
end
3 Likes

Thanks! I had to come to grips with fact that multi.insert expects either struct or changeset (not a map), but once I figured that out, your tip provided the key insight. I forgot to realize that the |> operator was passing the result from the first step in in all along! Thanks for your help!

    test "SPLIT inserts via transaction." do
      multi =
        Ecto.Multi.new()
        |> Ecto.Multi.insert(
          :transaction,
          transaction_changeset()
        )
        |> Ecto.Multi.insert(:split, fn %{transaction: transaction} ->
          # pattern match on result from previous step to pull out the transaction piece.
          split_changeset(transaction)
        end)

      case Repo.transaction(multi) do
        {:ok, _results} ->
          IO.puts("Success")

        {:error, :transaction, changeset, _changes} ->
          IO.inspect(changeset.errors, label: "Transaction insert failed.")

        {:error, :split, changeset, _changes} ->
          IO.inspect(changeset.errors, label: "split insert failed.")
      end
    end