Precondition checks and atomic

I often have to check the current value of a resource object in the DB as part of precondition checks of an action (e.g. when updating a "status" column I have to make sure it has a specific value before doing it). At first I tried to use "validation" for this but that seems to be only for input arguments and input attributes. So instead I used "change before_action" hooks to load the existing record and add an error to the Changeset if needed. This works but Ash is complaining that it cannot run it as "atomic" so it needs "require_atomic? false". I find this a bit confusing as the docs on changeset hooks imply that "before_action" is run within the transaction. Am I wrong to assume that "atomic" and "transactional" are the same thing in Ash? Is "before_action" executed outside the DB transaction? I couldn't find a clear answer how to do this properly (as in idiomatic Ash and it being transactional)
5 Replies
ZachDaniel
ZachDaniel4mo ago
atomic and transactional are not the same thing in general, unless you are using serializable transaction isolation levels, which you don't want to do typically. https://hexdocs.pm/ash/update-actions.html#fully-atomic-updates
Felix
FelixOP4mo ago
That's not really what I was getting at but I was probably not clear enough. I haven't found those specific docs for some reason, thanks. Here's what I'm currently doing:
update :close do
require_atomic? false

change before_action(fn changeset, _context ->
ticket = Helpdesk.get_ticket_by_id!(changeset.data.id)

if ticket.status not in [:open, :in_progress] do
Ash.Changeset.add_error(changeset, "Ticket is not open or in progress", :status)
else
changeset
end
end)

change set_attribute(:status, :closed)
end
update :close do
require_atomic? false

change before_action(fn changeset, _context ->
ticket = Helpdesk.get_ticket_by_id!(changeset.data.id)

if ticket.status not in [:open, :in_progress] do
Ash.Changeset.add_error(changeset, "Ticket is not open or in progress", :status)
else
changeset
end
end)

change set_attribute(:status, :closed)
end
Is this idiomatic (enough)? Should I use "validation" instead for fetching and checking the current state? Does the "before_action" run it in the same transaction as the "set_attribute" part or was that just a wrong assumption on my part?
ZachDaniel
ZachDaniel4mo ago
Yep, they run in the same transaction. It is transactional, but not atomic If you ran that action in parallel, depending on the timing, each action could see a different or the same ticket status depending Well, sorry, in this example what. i mean is that they could both "do their work" under the impression that they were the ones closing the ticket Because one transaction and a concurrent transaction running, doing the same thing, will both observe old state. If you made the action fully atomic, then you would have no possibility of a race condition
Felix
FelixOP4mo ago
Thanks, I will look into atomics in more detail when I really need them
ZachDaniel
ZachDaniel4mo ago
It's totally okay to set require_atomic? false and move on with your life, just a helpful indicator you might need to do something

Did you find this page helpful?