Why there is no `Ash.Changeset.around_transaction`?

I was wondering why there is no around_transaction function in Ash.Changeset. We already have a around_action option, but that one runs inside a transaction, meaning that I can't use it if I wan't to add something to the DB regardless if the action itself fails or not. I know that there is a before_transaction and after_transaction, but depending on what I'm doing this would not work. Just to give a more concrete example. I'm planning to port this into a Ash change:
def lock_and_transact(user, type, apply_function, update_function) do
Mutex.under(SubscribeMutex, user.uuid, fn ->
response =
case create_transaction(user, type) do
{:ok, _} ->
{:ok, response} = apply_function.()

response

{:error, _} ->
user = update_function(user)

{:error, :unfinished_transaction, handle_error!(type, user)}
end

delete_transaction!(user, type)

response
end)
end
def lock_and_transact(user, type, apply_function, update_function) do
Mutex.under(SubscribeMutex, user.uuid, fn ->
response =
case create_transaction(user, type) do
{:ok, _} ->
{:ok, response} = apply_function.()

response

{:error, _} ->
user = update_function(user)

{:error, :unfinished_transaction, handle_error!(type, user)}
end

delete_transaction!(user, type)

response
end)
end
The idea in this function is that I will first use Mutex to make sure that I always have only one code path reaching this code block at a time, after it, I create a transaction, which basically means that I have a transaction table and I store a row there for that user and type. I then run the apply_function, if that function doesn't return an error, it will call the delete_transaction! function that will remove the transaction and return the response, otherwise, it will just crash leaving the added transaction in the DB, this means that next time this code is called, it will fail to create a transaction and it will run the update_function instead. If I just wanted to create the transaction row in the DB, I would be able to create two changes, one with after_transaction(to create the row) and one with before_transaction (to delete it), but that doesn't work with the Mutex call, for the mutex I need something like around_action but for transactions.
10 Replies
ZachDaniel
ZachDaniel2y ago
We could potentially add an around_transaction hook, it just hadn’t come up before In the meantime, you can make a manual action, set transaction? false and then in the manual action implementation call the appropriate action with changeset.params
Blibs
BlibsOP2y ago
I can do that, but then I would need to create one manual action for each action that I want to use this right?
ZachDaniel
ZachDaniel2y ago
Yes, unfortunately
Blibs
BlibsOP2y ago
Would you say this is something in the roadmap? I was looking into the code to see if I could do a PR, but seems like it is not somthing that I can do without spending some time understanding the Ash.Changeset code first
ZachDaniel
ZachDaniel2y ago
Something to keep in mind with transaction hooks is that they don’t work when composed with other resource actions. It only works if the action is the “top level” action. The hooks will still fire, but if they are already in a transaction then they won’t of course be “around” the transaction. Its probably not that hard to accomplish tbh There is a function called with_hooks in changeset that you’d basically copy the implementation of the around action hooks, and call that first thing in that with hooks function
Blibs
BlibsOP2y ago
Something to keep in mind with transaction hooks is that they don’t work when composed with other resource actions. It only works if the action is the “top level” action. The hooks will still fire, but if they are already in a transaction then they won’t of course be “around” the transaction.
That's fine, the idea is to only use it in "top level" actions anyway 🙂
Blibs
BlibsOP2y ago
@Zach Daniel can you take a look into this PR? https://github.com/ash-project/ash/pull/632
GitHub
feat: Adds Ash.Changeset around_transaction by sezaru · Pull Reques...
This doesn't unit tests since I couldn't find unit tests for around_action, before_transaction or after_transaction to use as reference. Contributor checklist Bug fixes include regression...
Blibs
BlibsOP2y ago
Seems to work for me, but I'm not sure if I missed some corner case
ZachDaniel
ZachDaniel2y ago
That looks right to me. Will review more thoroughly when I get home. Well want to warn on any around transaction hooks like we do the other ones Like add this to the top of the function before running the around transaction hooks: warn_on_transaction_hooks(changeset, changeset.around_transaction, "around_transaction")
Blibs
BlibsOP2y ago
I pushed a commit with that change

Did you find this page helpful?