moxley
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.
Trending in Questions
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
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
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
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
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
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
apply_graft/2 doesn’t rewrite an add_many sub-workflow’s deps on an add step. Grafted jobs cancel with “upstream job was deleted”
Version...
New
Other Trending Topics
I am happy to introduce the very α version of the new programming language compiled to BEAM.
Welcome Cure.
It has literally three kille...
New
Hobbes is a low-level distributed database for the Elixir programming language.
Hobbes provides a simple, safe, and scalable storage lay...
New
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
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
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
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
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #library
- #deployment
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #elixirconf
- #channels
- #exunit
- #discussion
- #code-sync
- #podcasts
- #javascript
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixirconf-us
- #blog-post
- #ai
- #elixir-ls
- #phoenix_html
- #iex
- #graphql
- #genstage
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #hex
- #security
- #metaprogramming










Showing Posts 1 to 8- Show Best Posts
- Show All (oldest first)
- Show All (newest first)
zachdaniel
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_transactionhook 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?: falseoption 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
falseinstead relatively easily, which would make it such that only specific errors going wrong would rollback the transaction.moxley
I didn’t know about
rollback_on_error?: false. Here’s the documentation, if anyone’s curious.The final problem I’m encountering is that when Action B fails, it has an after_transaction callback that writes the error to the database. When calling Action B directly, this works as expected. If instead I call Action A, which calls Action B, then the error written to the database gets rolled back, because the after_transaction is itself inside an Action A’s transaction, which gets rolled back (this is typical SQL behavior– not just Ash). I can pass
rollback_on_error?: falsein Action A’s call to Action B, but other unexpected things happen: records that should get rolled back in Action B no longer get rolled back.I think that primarily because unexpected things will happen otherwise, the
rollback_on_error?: truedefault is the right choice.The fact that my application writes an error to the database may seem strange, but it’s because the execution flow is very asynchronous (User synchronous interaction with the API + Stripe WebHook callback + User WebSocket listening for updates based on the WebHook handling). The solution might be to delegate the error write to a separate process, which will be outside of the transaction of Action A and Action B.
zachdaniel
Yeah, so whenever you have something like this:
it typically represents something you want to rethink. Most often you’d do that by handling the error in an after_transaction hook on the calling action.
moxley
Actually, this still isn’t working.
It’s fine that all the nested SQL transactions get rolled back when Action B returns an error. The problem is that I need the code execution to continue without a throw– just as if I were calling Action B directly. Simply nesting an action call inside another shouldn’t change the behavior of the error case. Should I attempt to catch the throw, so that execution can continue?
zachdaniel
The problem is that you are in an invalid transaction. If you try to do another database operation in that rolled back transaction it will fail. So when something rolls back the transaction, it goes all the way back up to the place that started the transaction, and only that error logic gets a chance to run.
moxley
Understood. I’m not trying to do another database operation– not anymore. But what I want is the behavior of Action B to be the same whether it’s called directly or called from inside the transaction of Action A. Action B needs to handle errors it encounters, and it can’t do that if there is a throw coming from somewhere inside a call it’s making– a throw that only started happening when Action B was wrapped in Action A’s transaction.
What seems to work for now is to add a try+catch around the call that Action B is making. If Action B is wrapped in Action A’s transaction, the the catch will execute, and it can handle the error. If Action B is called directly, it handle the error in a more traditional way.
moxley
Here’s the approximate solution in Action B’s change module:
zachdaniel
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_transactionhooks work. If you didn’t start the transaction, then yourafter_transactionswon’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 specifyrollback_on_error?: falseand 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).