Exceptions within an ecto transaction

I’ve read in several places that try/rescue are to be generally avoided. However, I see no way around it in the following scenario (unless I’m just really missing something or discovered a bug, which I’m kinda hoping for…).

c is an invalid changeset

iex(4)> Repo.insert(c, [])
{:error, ...}
iex(5)> Repo.transaction(fn -> Repo.insert(c, []) end)
{:ok,
  {:error,
    ...}}
iex(6)> Repo.transaction(fn -> {:ok, _} = Repo.insert(c, []) end)
** (MatchError) no match of right hand side value: {:error, ...}

Ideally there will be additional things in the transaction…but they are based on the inserted records value…I don’t want to wrap everything in a transaction with a try/rescue as well, but I’m not sure how to avoid it.

Also, I’ve glanced at, but don’t quite understand, ecto multi; though I’m not sure that helps if the second db action relies on data from the first.

1 Like

It all just depends on what you’re trying to do. Because I’m not entirely sure what you’re trying to do, here are a few patterns you may find useful.

Try to insert inside a transaction, if it fails, do something else, but let the transaction succeed

Repo.transaction(fn ->
  case Repo.insert(changeset) do
    {:ok, record} -> handle_success(record)
    _ -> do_something_else()
  end
end)

Try to insert something, if it succeeds insert something else, and rollback everything if there’s a failure:

Repo.transaction(fn ->
  with {:ok, record} <- Repo.insert(foo),
    {:ok, other_record} <- Repo.insert(bar) do
      {record, other_record}
  else
    val -> Repo.rollback(val)
  end
end)
|> case do
  {:ok, {foo, bar}} ->
    IO.puts "Both foo and bar were created because the transaction succeedd"
  {:error, error} ->
    IO.puts "Neither foo nor bar were created because the transaction failed with error: #{inspect error}"
end
6 Likes

Also, if you want to use Multi, Ecto.Multi.merge can be used to access data from previous operations.

EDIT: actually, using Ecto.Multi.run might be easier too.

2 Likes

Thank you! I had tried to solve it with ‘with’, but I must have made another mistake in the process. Also, thank you @wojtekmach, I’ll look into multi.merge.

1 Like