Elixir: Ecto.put_assoc V.S. Ecto.Multi.update when updating multiple child records

Minh Reigen
2 min readMar 18, 2022

Today, let’s compare updating multiple child records using put_assoc vs Ecto.Multi.update.

put_assoc is convenient if you want to do either 2 things:

1. associate a list of child records to the parent
2. add a new child record to the association.

However, it might take more work and not optimal if we want to update multiple child records.

Ecto.put_assoc

put_assoc is easy to use, but here is the catch: With put_assoc, when we want to update a few child records, we will need to provide a list of all child records modified or unmodified. Without giving a full list to Ecto.Changeset.put_assoc, what don’t appear in the list will be erased from the database when the compiler gets to Repo.update/1

With only one child record to add to the association, it is easy and convenient that we can do

put_assoc(:children, [%Child{name: “Victor Hugo”} | parent.children])

This will add the famous poet Victor Hugo to the parent’s child association.

Or the same if you want to update a child record, just give it an existing id and Ecto will do you a favor and find it and update the name for ya:

put_assoc(:children, [%Child{id: 1, name: “Lev Tolstoy”} | parent.children])

But if you want to update only one child record, it is much straight forward to do this directly on the child record (instead of updating the parent and its child association). Something like this:

change(child, %{name: “Lev Tolstoy”})
|> Repo.update!()

Ecto.Multi.update

Ecto.Multi might sound scary at first, but trust me, it’s as easy as working with Ecto.Changeset

One way to update multiple child records without having to provide a full list of them is by using Ecto.Multi.update/3. Nothing is better than a code example in a “tutorial” so here we go, a very simple example of updating multiple child records (not tested):

In this example, children_changes_paramscontains only the changes we want to make for only the child records we are changing! We don’t have to find and pass in a whole list of records and worry if we have missed any of them. Then, using Enum.reduce/3, we are composing a multi entity that consists of multiple (3) changes to be updated. This multi is then processed with the Repo.transaction/1 function and return the results.

In Ecto.Multi.update(multi, “update_#{id}", cs), we are passing in a `key` or a label composed of the id of the child record. This will be used if you want to handle errors, or further complicated operation inside the `transaction` block.

Please let me know if you have any questions and concerns.

--

--