moxley

moxley

Uncaught throw when error is returned from nested action call

I have a nested set of action calls, like this:

Action A → Action B

When the change function of Action B adds an error to its changeset, it somehow results in an uncaught throw in the call to Action B. This means that Action A can’t act on the return value from Action B when Action B returns an error.

Here’s approximately what the Action A change function might look like:

defmodule ResourceA.ActionA do
  def change(changeset, _context) do
    Ash.Changeset.before_action(changeset, fn changeset ->
      result = ResourceB.action_b()
        
      # If action_b() adds an error to its changeset,
      # the result can't be acted upon here, because there is a throw generated internally.
        
      changeset
    end)
  end
end

Here is an example repo that demonstrates the issue: hairbnb2/test/hairbnb/cart_test.exs at main · moxley/hairbnb2 · GitHub

I’ve tried using before_action, after_action, and manual, and all three techniques have the same issue.

Marked As Solved

zachdaniel

zachdaniel

Creator of Ash

Yeah, so any errors that happen inside of action hooks will incur a rollback (the throwing behavior you’re describing), and the primary reason for this is that this is actually a situation that can be forced by some of the errors that can come back from a data layer. So for example, if you violate a unique constraint in the db, the transaction has to be aborted (and so you’d want an after_transaction hook in place on the resource that started the transaction to handle/correct that.

If you are aware that an action you’re calling may fail in a hook, you can use the rollback_on_error?: false option when calling the action.


With all of that said, an argument could be made that that is not the best design/doesn’t follow the principle of least surprise. We could introduce a backwards compatibility config that will modify the default of that option to false instead relatively easily, which would make it such that only specific errors going wrong would rollback the transaction.

Also Liked

zachdaniel

zachdaniel

Creator of Ash

I get what you’re saying, the fundamental issue here is that Ash doesn’t know what the wrapping transaction is going to try to do after the error occurs. The fundamental issue here is around how after_transaction hooks work. If you didn’t start the transaction, then your after_transactions won’t be the one to compensate for errors that your action returns from within the scope of what would be its own transaction. Remember that there is no such thing as a nested transaction (there are savepoints but that’s a separate conversation).

But this is the point of the rollback_on_error? option. You should be able to specify rollback_on_error?: false and then handle whatever error occurs from your action, assuming the error was not an error directly from the data layer (in which case we always roll back).

Last Post!

zachdaniel

zachdaniel

Creator of Ash

I get what you’re saying, the fundamental issue here is that Ash doesn’t know what the wrapping transaction is going to try to do after the error occurs. The fundamental issue here is around how after_transaction hooks work. If you didn’t start the transaction, then your after_transactions won’t be the one to compensate for errors that your action returns from within the scope of what would be its own transaction. Remember that there is no such thing as a nested transaction (there are savepoints but that’s a separate conversation).

But this is the point of the rollback_on_error? option. You should be able to specify rollback_on_error?: false and then handle whatever error occurs from your action, assuming the error was not an error directly from the data layer (in which case we always roll back).

Where Next?

Popular in Questions Top

vertexbuffer
Hello, can anybody help here..? I have a list of players and I what to delete an element, but every for loop the list is reverting to ori...
New
jononomo
I am trying to figure out how Mix knows whether the environment is test, dev, or prod – where is this set? Thanks.
New
New
vonH
In asking this question I am more interested about the expressiveness of the language itself and less concerned about the availability of...
New
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
9mm
I am constructing a JSON object (map) and I need to conditionally set a field. I’m trying to write proper elixir-way code… and I’m at a l...
New
aalberti333
As the title describes, I’m trying to run Enum.map() over a list of key/value pairs, where the value is a map. My data looks like this: ...
New

Other popular topics Top

vertexbuffer
Hello, can anybody help here..? I have a list of players and I what to delete an element, but every for loop the list is reverting to ori...
New
vonH
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
New
nsuchy
Hi. I’ve noticed that Windows Powershell has it’s own IEX command and you cannot access Elixir’s IEX due to the conflict. This isn’t a cr...
New
axelson
This post is a wiki (feel free to hit the edit button near the bottom right of this post to add your own changes!) This post collects co...
239 49084 226
New
romenigld
I am trying to run a deploy with docker and I successfully runned with this command: docker build -t romenigld/blog-prod . but when I t...
New
senggen
Erlang/OTP 25 [erts-13.2.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] 15:22:35.803 [error] gen_event {lager_file_backend...
New

We're in Beta

About us Mission Statement