我在我的应用程序中有一个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
的副本并插入它,然后使用新的Bar
创建所有相关Foo
s的副本。给定一个带有许多子条的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中有合理的方法吗?
我在这里使用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)