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
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
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:relationshipand: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
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
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.
Popular in Questions
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #elixirconf-us
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #metaprogramming
- #security
- #hex









