Elixir 如何正确编写事务并冒泡错误

Elixir 如何正确编写事务并冒泡错误,elixir,phoenix-framework,ecto,Elixir,Phoenix Framework,Ecto,好吧,因为有多个层次的回报,我有点迷路了 我对埃克托很陌生,所以我来了 我正试图将我的帐户创建包装在一个事务中,因为它会创建许多子记录等 到目前为止,我有一个: def create_account(company_name, ...) do Repo.transaction(fn -> case Account.create_account(%{ # ... attributes here }) do ???

好吧,因为有多个层次的回报,我有点迷路了

我对埃克托很陌生,所以我来了

我正试图将我的帐户创建包装在一个事务中,因为它会创建许多子记录等

到目前为止,我有一个:

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
EXTO架构模型上的create_帐户如下所示:

Account.ex

def create_account(attrs \\ %{}) do
  %Account{}
  |> Account.changeset(attrs)
  |> Repo.insert()
end
因此,现在有3个级别的返回值,我不确定如何一起处理:

  • 交易的快乐之路似乎又回来了: {:好的,模型}

  • 如果account.create\u account insert失败,如何将错误传递到最终返回值,以便在UI中显示

  • 如何在任何步骤中正确回滚

  • 使用monad-like特殊形式:

    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
    
    def创建账户(公司名称,…)做什么
    回购交易(fn->
    使用{:ok,account}时,您应该在失败时使用。文档说
    事务将返回作为{: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\u changeset}
    ,如果account插入失败,以及
    {:error,{:user,user\u changeset}
    如果用户插入失败。

    对您的意图的描述听起来像是
    exto.Multi
    的完美用例。它是exto的一个功能,允许您定义复杂的数据处理管道。这里有更多示例的详细说明,但总体上想法简单而可靠

    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,{/code>-的函数,就像在
    Multi.run
    中一样,因为它需要引用上一步的工件

    管道在
    create\u account
    变量中定义,然后仅在调用
    Repo.transaction(create\u account)
    时执行。这样,所有步骤都作为单个事务运行

    • 如果所有步骤都成功,
      {:确定,%%{insert\u用户:%user{…},insert\u订阅:%subscription{…}
      ,事务提交
    • 如果任何步骤失败(对于定义为函数的步骤,它意味着返回
      {:error,}
      ),将返回一个错误元组,例如
      {:error,:insert\u user,%exto.Changeset{}
      ,并回滚事务。在这种情况下,失败发生在
      insert\u user
      步骤中

    这非常感谢,对于如何优雅地处理此问题以及如何正确处理所有情况,我们确实感到失望。with使事情变得干净,但这可能更适合于获取而不是处理每个事务。为什么?
    Kernel.SpecialForms.with/1
    明确设计用于处理此类情况。gazil如何处理
    Repo.callback
    重复的狮子会更好吗?哦,我没有意识到这一点,让我研究一下,谢谢。还有,检查一下