How to make forms and transactions play well in phoenix + ecto?

怎甘沉沦 提交于 2019-12-12 09:11:35

问题


I'm playing with Phoenix + Ecto and I stumbled upon something that does not feel idiomatic to me.

I have a form that represents an Invitation. When creating an Invitation we also need to create a User and obviously I want both to happen in a transaction so I keep data consistency. In my form I ask for name and email.

Since I want the Invitation changeset in my view to represent the errors correctly I ended up with this code... but does not look great.

Do you know a better way to do this in Phoenix + Ecto?

def create(params) do
  Repo.transaction(fn ->
    case Repo.insert(User.email_changeset(%User{}, params)) do
      {:ok, user} ->
        changeset = Invitation.changeset(%Invitation{}, params)
        case Repo.insert(Ecto.Changeset.change(changeset, user_id: user.id)) do
          {:ok, user} ->
            user
          {:error, changeset} ->
            Repo.rollback(changeset)
        end
      {:error, _changeset} ->
        Repo.rollback(%{Ecto.Changeset.add_error(changeset, :email, "Wrong email") | action: :insert})
    end
  end)
end

回答1:


You are looking for the with operator. The beauty of this syntax is that if, at any point, you don't get what you're expecting, it stops the chain of commands and fires your else block:

Repo.transaction(fn ->
  with {:ok, first_object} <- create_some_object,
       {:ok, second_object} <- create_another(first_object.something)  do
       second_object
  else
    {:error, error_key} ->
      Repo.rollback(error_key)
  end
end)

if create_some_object doesn't return a struct matching {:ok, first_object} then the second_object is never created. Cool, right?




回答2:


You can try with Ecto.Multi.Here's an example:

defmodule Service do
  alias Ecto.Multi
  import Ecto

  def insert_changeset(params) do
    Multi.new
    |> Multi.insert(:user, User.email_changeset(%User{}, params))
    |> Multi.insert(:invitation, Invitation.changeset(%Invitation{}, params))
  end
end

And your create function:

def create(params) do
  Service.insert_changeset(params)
  |> Repo.transaction
end

Or you can pattern matching to make your code nicer

  def create(params) do
    Repo.transaction(fn ->
      changeset = User.email_changeset(%User{}, params)
      changeset
      |> Repo.insert
      |> invitation_insert(params)
    end)
  end

  defp invitation_insert({:error, changeset}, _params), do: Repo.rollback(changeset)
  defp invitation_insert({:ok, _}, params) do
    Invitation.changeset(%Invitation{}, params)
    |> Repo.insert
    |> do_invitation_insert
  end

  defp do_invitation_insert({:ok, user}), do: user
  defp do_invitation_insert({:error, changeset}), do: Repo.rollback(changeset)


来源:https://stackoverflow.com/questions/38033817/how-to-make-forms-and-transactions-play-well-in-phoenix-ecto

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!