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

electic
Hi, I am new to Elixir. I am trying to use the DateTime component to insert a date into MySQL however the there seems to be no way to fo...
New
New
lessless
I believe there are people here who are dealing with CSV files import on the daily basis, and since Excel is a really popular tool there ...
New
ashish173
I am using Ecto timestamps with postgres, I can see the timestamps() use the :naive_dateime but for my use case I wanted to store the ti...
New
dokuzbir
I want to highlight html closing tags when i click a html tag. That works in .html files but doesnt work for html.eex templates. How can...
New
fireproofsocks
Forgive me if this is obvious, but how does one delete a database record WITHOUT selecting it first? Ecto.Repo — Ecto v3.14.0 has exampl...
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

JeremM34
Hello, how can I check the Phoenix version ? Thanks !
New
JakeBecker
TL;DR: I’ve just released an implementation of Microsoft’s IDE-independent Language Server Protocol for Elixir. It adds language support ...
1144 54996 245
New
sergio_101
I am VERY much an elixir newbie. I have taken one elixir course and one phoenix course on Udemy. During that course, I saw the instructor...
New
albydarned
Hello all! I am typing this post from my new MacBook Pro with the M1 chip. I’m loving it so far, and will probably use it as my daily dr...
New
Patoshizzle
After calling mix ecto.create I get this error: 17:00:32.162 [error] GenServer #PID<0.412.0> terminating ** (Postgrex.Error) FATAL...
New
AstonJ
Posting this to see if we can make things easier for people to get into Neovim. If you use Neovim and have a favourite distro please let ...
New

We're in Beta

About us Mission Statement