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
Trending in Questions
Other Trending Topics
Latest Phoenix Threads
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
- #elixirconf
- #channels
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #blog-post
- #ai
- #phoenix_html
- #elixirconf-us
- #iex
- #graphql
- #genstage
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #hex
- #security
- #metaprogramming










Showing Posts 1 to 10- Show Best Posts
- Show All (oldest first)
- Show All (newest first)
shd42
Hello and Welcome !
Ecto.Multican 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.0Examples on the Multi doc → Ecto.Multi — Ecto v3.14.0
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
:receiveris being passed through to the caller correctly:Where
get_by_username/1is defined as:shd42
Oh ! Completely misread the original post, sorry about that.
A
), you’re basically getting out of the transaction, and calling a database query outside of it. Could you try to use the actual repo being provided and see if that fixes the issue ?
Multi.runprovides you in the first argument with the currentRepo, which i’m pretty sure is “tagged” with the current transaction. But in the underlying function, you are using the default one (probably through an alias made in the module). So if i understand correctly (someone will correct me if i’m notericgroom
No worries.
That’s a good idea, but doesn’t seem to be the issue in this case (although I do wonder if by not using that there are other bugs that can occur, something to look into).
Curiously, when I change
update_relationship/4to the following:I get this error:
Notice how it’s saying that the “Ecto.Multi callback named
:relationship” even though I changed the name of the operation!I totally forgot that this whole thing was a part of another Ecto.Multi transaction higher up the stack (and it had the same
:relationshipname, thanks past me), and the stacktrace isn’t very helpful for Ecto as I think everything is obscured by macros.So it makes total sense, the inner transaction was failing and then producing a normal Ecto.Multi/Repo.transaction error, but the parent Multi expects just
{:error value}There is probably a better way to do this, but the minimal way to fix this is to not use a transaction in the child multi and just return the multi, and then call Multi.merge in the parent:
shd42
Well, for sure that make better sense.
If you don’t use the provided repo, the side-effect you could get would be that an inner query failing would not trigger the proper rollback on the previous queries, since the other inner queries would have done their jobs successfully, but inside their own different transactions. That would be problematic for your data integrity (assuming your making a query that changes something).
Can you try by both having a different name on each operations and using the provided repo instead of the one you import ? I’m using a lot of
Ecto.Multiin my apps, and following that pattern never failed me, so i’m curious if that’s really the issue, or if something else is happening.ericgroom
It does seem to be wrapped in a transaction even without passing the repo; as in
get_relationship/2if I doIO.inspect Repo.in_transaction?it printstrue.if I modify
get_relationshipto take a repo like this:The resulting logs are the same, although I haven’t tried this yet with an operation that actually modifies the DB.
That said, I’m sure it passes the Repo for a reason and that I should be using it, since it sounds like you have used this in the past, do you have any good patterns for not littering every function with keyword list arguments? Maybe just having a function that returns a query and it’s up to the caller to actually run it inside of a Repo?
al2o3cr
The value passed in the first argument of an
Ecto.Multi.runcallback is the name of the repo - if you want your multi to be 100% reusable you’d use it as:The transaction is bound to the process, the value passed in
repois a plain ol’ atom.shd42
Good to know for the transaction. The documentation isn’t really clear about this then, but should have gone to the code directly to make sure. Thanks
I don’t fully understand @ericgroom issue then. I’ve been using stuff similar to him without issue. Must be missing something. For avoiding littering my functions when i need to reuse them, i do what you said eric. I have a function who builds the query and returns it (usually, i go further and split it in multiple part to allow filtering, sorting…conditionally), and another one who’s job is to execute it, thus allowing to reuse it when needed.
JeyHey
Probably your function
update_relationshipis called from within another transaction. This causes an error because your function returns a 4-tuple instead of the expected 2-tuple.shd42
Well if the transaction is bound to the current process has @al2o3cr said, that shouldn’t impact and the transaction should be the same, unless spawning a different process to do the other Multi. Or i am misunderstanding something ?