ericgroom

ericgroom

Confused by Ecto.Multi Error

Hi everyone, I’m running into this error I absolutely can’t make sense of while working with Ecto.Multi (I’m relatively new but feel like I have a decent grasp) and so far Google searches haven’t yielded anything.

Here is the error:

expected Ecto.Multi callback named `:relationship` to return either {:ok, value} or {:error, value}, got: {:error, :relationship, "not found", %{}}

Now I am testing a failure case, but I am expecting an error to get passed through so that the view can display an error instead of an exception. The weird thing is I have inspected the value returned from the anonymous function in Multi.run and the value is {:error, :not_found} which based on this error message I think should be fine?

Here is the function that is running into this error:

def update_relationship(%SocialUser{} = user_a, %SocialUser{} = user_b, %{} = info_attrs, with_changeset) do
    Multi.new()
    |> Multi.run(:relationship, fn _repo, _changes -> get_relationship(user_a, user_b) end)
    |> Multi.update(:update, fn %{relationship: %{info: info}} -> with_changeset.(info, info_attrs) end)
    |> Repo.transaction()
  end

Here is the code for get_relationship/2:

  def get_relationship(%SocialUser{} = user_a, %SocialUser{} = user_b) do
    Repo.get_one(from r in Relationship, where: r.user_a_id == ^user_a.id and r.user_b_id == ^user_b.id, preload: [:info])
  end

And finally Repo.get_one is a function I’ve written to make it easier to work with Repo.one in situations like this where you need a tuple:

  def get_one(query) do
     case one(query) do
       nil -> { :error, :not_found }
       row -> { :ok, row }
     end
  end

Am I doing something stupid? I can’t for the life of me figure out where this transformation of {:error, :not_found} to {:error, :relationship, "not found", %{}} is happening, as far as I can tell I’m following what the documentation says

Marked As Solved

ericgroom

ericgroom

Hi shad, thanks for the warm welcome

I am aware of the possible return values for Ecto.Multi calls, I’ve used it in a couple other places in my project. Maybe I’m incorrect but my understanding was that if one operation fails in a chain, it should not call the future operations and just return the error. It’s not that I’m failing to pattern match on the result of the Ecto.Multi call outside of this function, it’s that (according to the error message) I’m not returning a correct value from the anonymous function in :relationship, even though I am?

In fact I have a very similar usage here where an error occurring in :receiver is being passed through to the caller correctly:

  def send_request(%SocialUser{} = user_a, user_b_username) do
    Multi.new()
    |> Multi.run(:receiver, fn _repo, _changes -> get_by_username(user_b_username) end)
    |> Multi.run(:relationship, fn _repo, %{receiver: receiver} -> send_request(user_a, receiver) end)
    |> Repo.transaction()
  end

Where get_by_username/1 is defined as:

def get_by_username(username) do
    Repo.get_one from u in Status.Identity.User,
      where: u.username == ^username,
      join: s in SocialUser,
      on: u.id == s.user_id,
      select: s
  end

Also Liked

shd42

shd42

Hello and Welcome !

Ecto.Multi can return two different tuple:

  • {:ok, %{relationship: %{}, update: %{}}} where the second argument is a map of each part of your Multi, named after what name you gave them (so :relationship and :update).
  • {:error, :relationship, "not found", %{}} where the second argument is the Multi call who failed (in your case :relationship), the second would be the failed value and the last one would contain the changes that have been successfully done before the failed attempt (but reverted by the transaction if it was a database call), so empty in your case since it’s the first call that failed. That allow you to know exactly where it failed.

It’s specified in two places that i know of in the documentation:
The Repo.transaction() function → Ecto.Repo — Ecto v3.14.0
Examples on the Multi doc → Ecto.Multi — Ecto v3.14.0

JeyHey

JeyHey

Probably your function update_relationship is called from within another transaction. This causes an error because your function returns a 4-tuple instead of the expected 2-tuple.

Last Post!

ericgroom

ericgroom

I think this would be equivalent. Either way it seems the function has to know that it is being called from another Multi, either by not performing a transaction and returning the multi, or adding onto a multi. The one pitfall is if you call transaction in the parent and child function, you will run into the same error.

I’m going to try having my contexts not make database calls at all and just be prewritten queries as seemingly this is the only way to truly be able to compose Multis cleanly. I think it makes sense because already the caller knows you are using a multi due to the 4-tuple error, and it allows the caller to decide whether they want Repo.one, Repo.one!, Repo.all, etc. This does mean having Repo calls in a controller, or a very thin context on top, but I think this way is more composable than what Phoenix suggests with a generated context.

Granted I haven’t tried this yet so curious to hear what others do.

Where Next?

Popular in Questions Top

nobody
Hi! In PHP: $_SERVER[‘SERVER_ADDR’] - in Elixir? Searched the docs for ip address and the web, no good results. Thanks!
New
9mm
I am constructing a JSON object (map) and I need to conditionally set a field. I’m trying to write proper elixir-way code… and I’m at a l...
New
jerry
Good day to you all. I have been struggling to get a query involving like and ilike to work. Can anyone assist me on this, please? pro...
New
siddhant3030
Hi, I have to write a raw query for one of my project. But till now I have used ecto queries and don’t have much experience writing raw ...
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
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
svb
Hi! Currently I want to submit a form by pressing the Enter key. However, since my input field is of type “textarea” this is just adds a...
New

Other popular topics Top

KronicDeth
Elixir plugin for JetBrain’s IntelliJ Platform (including Rubymine) This is a plugin that adds support for Elixir to JetBrains IntelliJ...
289 36689 110
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
joeerl
Hello again - after a longish gap I’ve decided I really must dig into Elixir and see what’s been happening here - so I have a few questio...
New
msaraiva
Surface is an experimental library built on top of Phoenix LiveView and its new LiveComponent API that aims to provide a more declarative...
564 44167 214
New
siddhant3030
Hi, I have to write a raw query for one of my project. But till now I have used ecto queries and don’t have much experience writing raw ...
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

We're in Beta

About us Mission Statement