omin

omin

Question about ecto transaction, multiple associations, and rollbacks

I have a transaction that updates upto 3 tables and it became long and ugly. I’d love to learn more to be able to write cleaner and more efficient code.

I have a form that takes inputs to update upto 3 tables.

a. Uses external API to cache the data if it expired or doesn’t already exist.
b. build_assoc with (a) for a new data point
c. build_assoc with (a) and (b) for a new data point

Some specific questions:

  1. Since transactions should be kept as short as possible, would you recommend fetching from the external API outside the transaction? (This is what I’ve done but it adds complexity to the code.)
  2. What would be to best practice to manage layers of associations? I found http://stackoverflow.com/questions/38033817/how-to-make-forms-and-transactions-play-well-in-phoenix-ecto/39415888#39415888 and I’m leaning toward the pattern matching example.
  3. How would you rollback multiple changesets?

Most Liked

josevalim

josevalim

Creator of Elixir

It seems Ecto.Multi may be exactly what you need: Ecto.Multi — Ecto v3.14.0

It answers questions 1 and 3 and it may as well answer question 2 too.

dimitarvp

dimitarvp

Almost two years later: yes, according to the official docs any exception raised inside the function given to Repo.transaction will result in a rollback.

Please note though, the third insert in your code is not using the bang variant of the function (namely it’s not insert!) and will thus not raise an exception so the transaction will likely still succeed with only partial success – not good.

EDIT: The above is NOT true: Ecto.Multi docs

To use Repo.transaction, do one of these:

  1. Either use bang functions everywhere in the transaction function (Repo.insert!, Repo.update! etc.), or…
  2. Use the with keyword and chain all the operations through non-bang functions and call Repo.rollback in the else clause – which would mean that the first failed operation will return {:error, reason} and the transaction will return that exact error so you can troubleshoot further afterwards. Or…
  3. Just use Ecto.Multi which will give you even more info if an operation fails. It’s really the best way of doing such composite operations ever since it was introduced. Just have one Ecto.Multi variable and append all your operations to it, then just execute it at once: Repo.transaction(your_multi).

It’s best if you don’t mix these styles. Just pick one and stick with it.

I started off using more hacky solutions and trying to be clever but nowadays I am always using Ecto.Multi and I am very pleased with the results. The code is much easier for a human to understand as well, which is a huge bonus win.

omin

omin

Thanks for the reply @jose.

Do you think we can go through an example? I couldn’t find anything online :sweat:

  transaction = Repo.transaction fn ->
    location = location || Repo.insert!(location_changeset)

    item_name = photo_params["item_name"]
    item = Repo.get_by(App.Item, [name: item_name, location_id: location.id])

    item_changeset = 
      location
      |> build_assoc(:items)
      |> App.Item.changeset(Map.put(photo_params, "name", item_name))
    item = item || Repo.insert!(item_changeset)

    photo_changeset =
      location
      |> build_assoc(:photos, item_id: item.id, user_id: current_user.id)
      |> Photo.changeset(photo_params)
    Repo.insert(photo_changeset)
  end
  
  case transaction do
    {:ok, _photo} ->
      conn
      |> redirect(to: phto_path(conn, :index))
    {:error, changeset} ->
      render(conn, "new.html", changeset: changeset)
  end

Would ecto rollback everything when the second or third Repo.insert fails? Also, is there a way to cascade a changeset with multiple schemas?

Where Next?

Popular in Questions Top

sen
Hi All, I set a environment variables in dev.exs , like below code. when i start server, how can i set the ${enable} value? thanks. d...
New
lastday4you
I wanted to check elixir version in phoenix because i found that my elixir is 1.5 but when i use Enum.chunk_by it said the function is un...
New
greenz1
I have a phoenix application from which a user can download multiple(5-6) files of size 1MB. I couldn’t find anything related to sending ...
New
tduccuong
Hi, is there any work on GUI with Elixir, that is similar to Electron/Javascript? My idea is to bundle Phoenix and BEAM into a single se...
New
shahryarjb
Hello, I get Persian date from my client and convert it to normal calendar like this: def jalali_string_to_miladi_english_number(persi...
New
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
ycv005
I have followed this StackOverflow post to install the specific version of Erlang. And When I am running mix ecto.setup then getting fol...
New
vegabook
I’m brand new to Phoenix and I have stripped one of the demo applications to the bone. I just want to get an svg up on the screen. Here i...
New
itssasanka
Hi all, Trying to get some more clarity over utc_datetime and naive_datetime for Ecto: The documentation above suggests that while ...
New
joaquinalcerro
Hi there, I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 records...
New

Other popular topics Top

Darmani72
If I have a post route which an argument: post /my_post_route/:my_param1, MyController.my_post_handler How would get the post params ...
New
senggen
Erlang/OTP 25 [erts-13.2.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] 15:22:35.803 [error] gen_event {lager_file_backend...
New
jononomo
I am trying to figure out how Mix knows whether the environment is test, dev, or prod – where is this set? Thanks.
New
minhajuddin
I have seen a lot of code which picks the first element from a list using Enum.at(0) instead of List.first. Is there a reason why people ...
New
chrismccord
This release brings a number of exciting features, including integration with the new Phoenix LiveDashboard and Phoenix LiveView. There h...
New
JorisKok
I have a server on AWS, and was running a load test using artillery. When looking at the Phoenix dashboard I see the Ports going to 100% ...
New
vonH
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
New
hariharasudhan94
I would like to know what is the best IDE for elixir development?
New
WestKeys
Currently suffering from paralysis by [HTTP client] analysis. This is rather unusual in Elixirland as there tends to be consensus on the ...
New
dogweather
I wrote this comment on r/haskell, and it’s not popular there. :wink: But I think I’m on to something… Haskell reminds me of Java, and e...
New

We're in Beta

About us Mission Statement