好吧,由于回报率多种多样,我有点迷失了。
我对ecto很陌生,所以去吧。
我正在尝试将我创建的帐户包装在交易中,因为它会创建许多子记录等。
到目前为止,我已经知道了:
def create_account(company_name, ...) do
Repo.transaction(fn ->
case Account.create_account(%{
# ... attributes here
}) do
????
end
# insert other model records here using the same above case pattern matching
account
end) # transaction
end
ecto模式模型上的create_account如下:
Account.ex
def create_account(attrs \\ %{}) do
%Account{}
|> Account.changeset(attrs)
|> Repo.insert()
end
所以现在有3个级别的返回值,我不确定如何一起处理所有这些值:
交易的快乐路径似乎返回了: {:ok,model}
如果account.create_account插入失败,如何将错误传递给最终返回值,以便我可以在UI中显示该错误?
如何在任何步骤中正确回滚?
答案 0 :(得分:2)
失败时,您应该使用Repo.rollback。文档说The transaction will return the value given as {:error, value}
,因此可以通过您提到的模式匹配来完成:
def create_account(company_name, ...) do
Repo.transaction(fn ->
account = case Account.create_account(%{ # ... attributes here }) do
{:ok, account} -> account
{:error, changeset} -> Repo.rollback(changeset)
end
# insert other model
{:ok, account}
end)
end
这样,您的函数将在成功时返回{:ok, account}
,并在遇到任何失败时返回{:error, changeset}
。由于您要插入多个内容,因此可能需要区分它们,就像这样:
account = case Account.create_account(%{ # ... attributes here }) do
{:ok, account} -> account
{:error, changeset} -> Repo.rollback({:account, changeset})
end
case User.create_user(account, %{ # ... attributes here }) do
{:ok, user} -> :ok
{:error, changeset} -> Repo.rollback({:user, changeset})
end
在这种情况下,如果一切正常,该函数将返回{:ok, account}
,如果帐户插入失败,则返回{:error, {:account, account_changeset}}
,如果用户插入失败,则返回{:error, {:user, user_changeset}}
。
答案 1 :(得分:2)
您的意图描述听起来像是Ecto.Multi
的完美用例。它是Ecto的一项功能,可让您定义复杂的数据处理管道。有更多示例here的详细说明,但总的来说,这个想法简单而扎实。
account = Account.changeset(%Account{}, params)
subscription = %Subscription{valid_until: ~D[2020-09-30]}
create_account =
Ecto.Multi.new()
|> Ecto.Multi.insert(:insert_account, account)
|> Ecto.Multi.run(:insert_subscription, fn repo, %{insert_account: account} ->
subscription
|> Map.put(:account_id, account.id)
|> repo.insert()
end)
Repo.transaction(create_account)
根据您的喜好随意重构它;基本思想是将每个步骤定义为诸如insert
之类的速记操作,或者定义为返回{:ok, record}
或{:error, _}
的函数-类似于Multi.run
,因为它需要引用到上一步的工件。
管道在create_account
变量中定义,然后仅在调用Repo.transaction(create_account)
时执行。这样,所有步骤都作为一个事务运行。
{:ok, %{insert_user: %User{...}, insert_subscription: %Subscription{...}}
,并提交事务。{:error, _}
),将返回一个错误元组,例如{:error, :insert_user, %Ecto.Changeset{}}
-事务将回滚。在这种情况下,失败发生在insert_user
步骤中。答案 2 :(得分:1)
使用Kernel.SpecialForms.with/1
类monad特殊格式:
def create_account(company_name, ...) do
Repo.transaction(fn ->
with {:ok, account} <- Account.create_account(...),
{:ok, _} <- AnotherModel.create_record(...),
...
{:ok, _} <- LastModel.create_record(...) do
IO.puts("All fine")
account
else
error ->
IO.inspect(error, label: "Error happened")
Repo.rollback(:error_in_transaction)
end
end) # transaction
end