Dynamic number of inserts within Ecto Multi?

I have a Foo schema in my app that has_many of the schema Bar.

  schema "foos" do
    field :content, :string 
    has_many :bars, MyApp.Content.Bar, foreign_key: :foo_id
  schema "bars" do
    field :content, :string 
    belongs_to :foo, MyApp.Content.Foo, foreign_key: :foo_id

I want a function that takes an id for a Foo, creates a copy of that Foo and inserts it, and then creates copies of all the associated Bars with the new Foo. Given a Foo with many child Bars, this function will copy that Foo and all those Bars in one go.

I use Ecto.Multi for things like this, but I’m not sure how to set it up for a variable number of actions. So far I have this:

resolve fn (%{foo_id: foo_id}, _info) ->
  oldfoo = Repo.get!(Foo, foo_id)

  multi =
      |> Multi.run(:foo, fn %{} ->
          content: oldfoo.content
        }) end)
      |> Multi.run(:bars, fn %{foo: foo} ->

        query =
          from b in Bar,
            where: b.foo_id == ^foo.id,
            select: b.id

        bars = Repo.all(query)  # returns a list of id/ints like [2,3,6,7,11...]

        Enum.map(bars, fn barid ->
          bar = Repo.get(Bar, barid)
          Bar.changeset(%Bar{}, %{
            content: bar.content,
            foo_id: foo.id
            |> Repo.insert()

  case Repo.transaction(multi) do
    {:ok, %{foo: foo}} ->
      {:ok, foo}
    {:error, _} ->
      {:error, "Error"}

This throws an error:

** (exit) an exception was raised:
    ** (CaseClauseError) no case clause matching: [ok: %MyApp.Content.Foo...

Is there a reasonable way to do this within Ecto.Multi?

1 Like

Ecto.Multi.run does expect {:ok, any()} | {:error, any()} to be the returned value of the function. You’re returning [{:ok, …}, {:ok, …}]. I’d rather use Ecto.Multi.merge and return a second Ecto.Multi for all the bars to be created. For that keep in mind that you can name the Ecto.Multi steps not only with atoms. {:bar, index} can be just as valid, as long as names are unique.