ericgroom

ericgroom

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 OP

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 OP

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? Top

Trending in Questions Top

RSP87
I’m working on a project that simulates the bumbl example in the programming phoenix book. It acts almost like an email client. We have a...
New
kszambelanczyk
Hello! Could someone please give me a help/sample code, how to delete a file from s3 using waffle/waffle_ecto from Phoenix app. I creat...
New
RemyXRenard
I’m seeing that a list inside a Kino.DataTable will be interpreted as a charlist, even if the Kino.configure() is set to charlists: :as_l...
New
velrest
So my question is quite simple and i have found no conclusive answer on forum, google or AI. Should we use :erlang.float for Integer to ...
New
samoloth
Hi, I’ve just set up an application with ash_authentication. There is only magic link strategy for now, so there is no confirmation add o...
New
nseaSeb
Hello, I know there is an approach for handling lists that allows for optimized traversal, but I can’t recall the specific method (somet...
New
FlyingNoodle
If a change or preparation module uses Ash.Changeset.get_argument/2 or Ash.Query.get_argument/2 (or any of the other get_argument functio...
New

Other Trending Topics Top

mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Dmk
Xamal is a deployment tool for Elixir apps that deploys native releases to bare metal servers over SSH. It’s a port of GitHub - basecamp/...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge & Solve. They are GUI (Emerge) and State management (S...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews