by Vikram Ramakrishnan Recently, we worked on a client project that required sending over a number of fields to the server during user registration. Some of these fields (email, password, etc.) were part of the schema and others with other schema. Since these other schema depend on , we would have to nest conditional transactions in our , which would provide potential for multiple points of failure. Rather than nest these conditional transactions, we wanted to be able to easily sequence our transactions and match on errors and failures. The following is an explanation of how we used to make this easy. user user RegistrationController Ecto.Multi Consider the following example. You have two schema: and . A and an a . During the user registration process, you want the user to submit their user details along with their mailing address details. For simplicity sake, let’s assume we’re validating on all of the fields, so in the event of any fields not being sent over to the server, the entire transaction fails. Here’s an example of good request params being sent over to the server: user address user has_many addresses address belongs_to user {"user": {"email": "vikram@quantlayer.com","password": "password1","phone_number": "6176176176"},"address": {"city": "Cambridge","country": "US","postal_code": "02139","state_province": "MA","street_line1": "5 QuantLayer Ave."}} Since a mailing address belongs to a user, we have to create a user to associate with the address before the address can be created. Keeping that all in mind, the logic might look something like this: 1. Try creating a user2. If user creation fails, return an error3. If user creation succeeds, try creating an address4. If address creation fails, delete the user and return an error5. If address creation succeeds, return the user and jwt Here’s an example of what this looks like in the controller: user_changeset = User.changeset(%User{}, user_params) case Repo.insert(user_changeset) do {:ok, user} ->address_changeset =%Address{user_id: user.id}|> Address.changeset(address_params) case Repo.insert(address_changeset) do{:ok, _address} ->{:ok, jwt, _full_claims} =Guardian.encode_and_sign(user, :token) conn |> put\_status(:created) |> render(MyApp.SessionView, "create.json", jwt: jwt, user: user) {:error, changeset} -> Repo.delete(user) conn |> put\_status(:unprocessable\_entity) |> render(MyApp.RegView, "error.json", changeset: changeset) end {:error, changeset} -> conn|> put_status(:unprocessable_entity)|> render(MyApp.RegView, "error.json", changeset: changeset)end There are a few things I don’t like about this. First of all, the nested statements make it difficult to follow. Secondly, we’re deleting the newly created user on an address failure, which increases the number of database transactions. And finally, we aren’t handling errors based on bad inputs for both and params. This approach is really flimsy. Imagine adding another step, like required credit card details. Nesting further statements along with tracking multiple points of error become a hassle. I would rather be able to rollback the entire transaction if any part of it fails. case user address case Enter Ecto.Multi lets us handle multiple, dependent Repo transactions. Ecto.Multi The docs ( ) describe it as follows: https://hexdocs.pm/ecto/Ecto.Multi.html “Ecto.Multi makes it possible to pack operations that should be performed together (in a single database transaction) and gives a way to introspect the queued operations without actually performing them. Each operation is given a name that is unique and will identify its result or will help to identify the place of failure in case it occurs.” So, let’s rewrite the example above with : Ecto.Multi user_changeset = User.changeset(%User{}, user_params) multi =Multi.new|> Multi.insert(:user, user_changeset)|> Multi.run(:address, fn %{user: user} ->address_changeset =%Address{user_id: user.id}|> Address.changeset(address_params)Repo.insert(address_changeset)end) case Repo.transaction(multi) do{:ok, result} ->{:ok, jwt, _full_claims} =Guardian.encode_and_sign(result.user, :token) conn |> put\_status(:created) |> render(MyApp.SessionView, "create.json", jwt: jwt, user: result.user) {:error, :user, changeset, %{}} ->conn|> put_status(:unprocessable_entity)|> render(MyApp.RegView, "error.json", changeset: changeset) {:error, :address, changeset, %{}} ->conn|> put_status(:unprocessable_entity)|> render(MyApp.RegView, "error.json", changeset: changeset)end Here, we assign an transaction to . accepts changesets through functions like . Note that the and are the unique names we assign to the operations in and , which is why we can pass to . The changesets are checked, and if there are errors, the transaction doesn’t start and returns the errors. We then use to pass an arbitrary function, which is dependent on the user in the line prior. When we execute the transaction with , we can pattern match on all the possible outcomes, which makes adding more requirements later on easier. Ecto.Multi.new multi Multi insert :user :address Multi.insert/2 Multi.run/2 user Multi.run/2 Multi.run Repo.transaction(multi) More good perspective on the purpose of the library is contained here in the original PR: Ecto.Multi https://github.com/elixir-ecto/ecto/issues/1114 Interested in discussing custom software needs more broadly? Drop me a line at vikram@quantlayer.com — I would love to chat with you. Follow us on Twitter at https://twitter.com/@QuantLayer is how hackers start their afternoons. We’re a part of the family. We are now and happy to opportunities. Hacker Noon @AMI accepting submissions discuss advertising &sponsorship To learn more, , , or simply, read our about page like/message us on Facebook tweet/DM @HackerNoon. If you enjoyed this story, we recommend reading our and . Until next time, don’t take the realities of the world for granted! latest tech stories trending tech stories