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
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 yourelse
block:if
create_some_object
doesn't return a struct matching {:ok, first_object} then the second_object is never created. Cool, right?You can try with
Ecto.Multi
.Here's an example:And your create function:
Or you can pattern matching to make your code nicer