tajihiro
tajihiro

Reputation: 2443

How to insert main and detailed data at once by ecto

I have main data and detailed data. I would like to insert both at one time.

-------
 main
-------
 id
 name
-------

---------
 detail
---------
 id
 main_id
 name
---------

I'm using Multi transaction by ecto. However I have no idea how to insert at once.

main = [name: "zaku"]
details = [%{main_id: 1, name: "hoge"}, %{main_id: 1, name: "moja"},]

Multi.new()
  |> Multi.insert(:main, Main.changeset(%Main{}, main))
  |> Multi.insert(:detail, Detail.changeset(%Detail{}, details))
  |> Repo.transaction()

The part in Detail insert does not work. How can I do for it?

Upvotes: 1

Views: 225

Answers (2)

Everett
Everett

Reputation: 9558

I think your example here is similar to the solution for your other example, you just need to close the multiple queries with a call to the transaction/1 function:

alias Ecto.Multi
alias Ecto.Repo

user = get_user_params_from_form() # <-- or where-ever you are getting data
email = get_email_params_from_form()

Multi.new()
    |> Multi.insert(:user, User.changeset(%User{}, user))
    |> Multi.insert(
      :email,
      # Capture the id from the previous operation
      fn %{
           user: %User{
             id: user_id
           }
         } ->
        Email.changeset(%Email{user_id: user_id}, email)
      end
    )
    |> Repo.transaction()

Personally, I don't find the Multi stuff very easy to work with, so sometimes I favor this other syntax, where you can pass a function to the Repo.transaction/2 callback. Loosely, that looks something like this:

Repo.transaction(fn ->
  with {:ok, thing1} <- create_thing1(attrs1) do
    create_thing2(attrs2)
  else
    {:error, e} -> Repo.rollback(e)
  end
end)


def create_thing1(attrs \\ %{}) do
  %ThingOne{}
  |> ThingOne.changeset(attrs)
  |> Repo.insert()
end

def create_thing2(attrs \\ %{}) do
  %ThingTwo{}
  |> ThingTwo.changeset(attrs)
  |> Repo.insert()
end

It should be pointed out that this pattern can be used to wrap any task in a transaction. E.g. if "thing2" is to interact with a 3rd party API, for example.

Upvotes: 0

Aleksei Matiushkin
Aleksei Matiushkin

Reputation: 120990

The insert of the child does not work because of transaction. When the insertion of Detail is attempted, the parent does not yet exist.

One usually uses Ecto.build_assoc/3 to insert dependent records, or [not recommended!] you might get rid of the transaction and insert them one by one with two queries—this would work.

Upvotes: 2

Related Questions