使用Ecto.multi的动态插入数

时间:2017-12-30 19:48:19

标签: elixir ecto

我的应用中有一个Foo架构has_many架构Bar

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

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

我想要一个为Foo获取id的函数,创建该Foo的副本并插入它,然后使用new创建所有关联的Bar的副本Foo。给定一个带有许多子条的Foo,这个函数将一次复制那个Foo和所有这些条。

我使用Ecto.Multi来做这样的事情,但我不确定如何为可变数量的动作设置它。到目前为止,我有这个:

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

  multi =
    Multi.new
      |> Multi.run(:foo, fn %{} ->
        MyApp.Content.create_foo(%{
          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()
        end)
      end)

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

这会引发错误:

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

在Ecto.Multi中有合理的方法吗?

1 个答案:

答案 0 :(得分:1)

我在这里使用Enum.reduce_while/3。我们遍历列表,收集所有插入的栏。如果任何插入失败,我们将返回该错误,否则我们将收集的值返回到列表中。

Enum.reduce_while(bars, {:ok, []}, fn barid, {:ok, acc} ->
  bar = Repo.get(Bar, barid)
  Bar.changeset(%Bar{}, %{
    content: bar.content,
    foo_id: foo.id
  })
  |> Repo.insert()
  |> case do
    {:ok, bar} -> {:cont, {:ok, [bar | acc]}}
    {:error, error} -> {:halt, {:error, error}}
  end
end)