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
lanycrost
Hi everyone! I need implement if…else if…else condition from my elixir code, and anymore of this control flow structures not work proper...
New
Lily
In templates/appointment/index.html.eex: <%= for appointment <- @appointments do %> <tr> <td><%= appoi...
New
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
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
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
freewebwithme
Using vs code and installed ElixirLS: support and debugger. And I got an error popped up on start up says Failed to run ‘elixir’ comma...
New

Other popular topics Top

nobody
Hi! In PHP: $_SERVER[‘SERVER_ADDR’] - in Elixir? Searched the docs for ip address and the web, no good results. 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
openscript
Hello! Sorry for this astonishing simple question, but I’m really stuck. I try to set up the intellij-elixir plugin, but I don’t know ho...
New
alice
Hey, Just curious what are the main benefits of Elixir compared to Clojure? When is Elixir more useful than Clojure and vice versa? Th...
New
TunkShif
This post is an instruction guide to help you setup your Neovim for Elixir development from scratch. It includes general information on h...
274 42576 114
New
sergio
Kind of like when jquery came out, it was super necessary. Existing drag and drop libraries have a bunch of baggage to support old browse...
New

We're in Beta

About us Mission Statement