As per the docs ActiveRecord::Transactions::ClassMethods, a non-new nested transaction will ignore a Rollback. From the docs:
User.transaction do
User.create(username: 'Kotori')
User.transaction do
User.create(username: 'Nemu')
raise ActiveRecord::Rollback
end
end
The raise ActiveRecord::Rollback
is ignored because it is in a child transaction (or rather, it is still within the parent transaction and not its own). I do not understand why the Rollback call would be ignored by both? I can see that since the child 'transaction' isn't really a transaction, that it would not roll back the 'Nemu' block, but why does it not trigger a rollback for the parent? Does the child transaction hide the rollback somehow?
In other words, why is it that there appears to be no way to roll back a parent transaction from within a nested child?
Actually this is exactly how Nested Transactions was designed for. I quote from oracle docs:
A nested transaction is used to provide a transactional guarantee for a subset of operations performed within the scope of a larger transaction. Doing this allows you to commit and abort the subset of operations independently of the larger transaction.
So, a child transaction in a regular nested transaction has no say regarding how him or the other children or parent (larger transaction) could behave, other than changing mutual data or failing for an exception.
But you can grant him (child transaction) a very limited voting chance on his destiny by utilizing the sub-transaction
feature as stated at rails docs by passing requires_new: true
User.transaction do
User.create(username: 'Kotori')
User.transaction(requires_new: true) do
User.create(username: 'Nemu')
raise ActiveRecord::Rollback
end
end
Which as the docs say: only creates 'Kotori'. since the powerful 'Nemu' child chose to die silently.
More details about Nested transaction rules (oracle docs)
Update:
To better understand why rails nested transactions
works this way, you need to know a bit more about how nested transactions works in DB level, I quote from rails api docs:
Most databases don’t support true nested transactions ...In order to get around this problem, #transaction will emulate the effect of nested transactions, by using savepoints: http://dev.mysql.com/doc/refman/5.0/en/savepoint.html
Ok, then the docs describes the behavior of a nested transaction
in the two mentioned cases as follows:
In case of a nested call, #transaction will behave as follows:
The block will be run without doing anything. All database statements that happen within the block are effectively appended to the already open database transaction.
However, if :requires_new is set, the block will be wrapped in a database savepoint acting as a sub-transaction.
I imagine careful, only imagine that:
option(1) (without requires_new) is there in case you used a DBMS that fully supports nested transactions
or you are happy with the "fake" behavior of nested_attributes
while option(2) is to support the savepoint
workaround if you don't.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With