Why there is no `Ash.Changeset.around_transaction`?
Eduardo B. Alexandre:
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
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.
zachdaniel:
We could potentially add an
around_transaction
hook, it just hadn’t come up before
zachdaniel:
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
Eduardo B. Alexandre:
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:
Yes, unfortunately
Eduardo B. Alexandre:
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:
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.
zachdaniel:
Its probably not that hard to accomplish tbh
zachdaniel:
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
Eduardo B. Alexandre:
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 🙂
Eduardo B. Alexandre:
<@197905764424089601> can you take a look into this PR? https://github.com/ash-project/ash/pull/632
Eduardo B. Alexandre:
Seems to work for me, but I’m not sure if I missed some corner case
zachdaniel:
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
zachdaniel:
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”)
Eduardo B. Alexandre:
I pushed a commit with that change