Allow to register transaction callbacks outside of a record - #51474
Conversation
0bf69ce to
c9ffc20
Compare
|
After discussing some more with @matthewd I added:
With the above there is now a range of ways to get callbacks around transaction lifcycles without having to resort to model callbacks. I still haven't added an equivalent of |
| ite&.each do |i| | ||
| i.rolledback!(force_restore_state: full_rollback?, should_run_callbacks: false) | ||
| run_action_on_records(ite, instances_to_run_callbacks_on) do |record, should_run_callbacks| | ||
| record.rolledback!(force_restore_state: full_rollback?, should_run_callbacks: should_run_callbacks) |
There was a problem hiding this comment.
We're not exposing full_rollback? to our new callbacks, but I'm not sure that's a generally useful piece of information... without digging in to history, this feels like it might be facilitating a historical-compatibility quirk rather than a first-principles desired function.
There was a problem hiding this comment.
Hum, I'm not even sure what it means. That the outer transaction is rolled back too?
I don't think we need it.
1a8c673 to
cd59867
Compare
Ref: rails#26103 Ref: rails#51426 A fairly common mistake with Rails is to enqueue a job from inside a transaction, and a record as argumemnt, which then lead to a RecordNotFound error when picked up by the queue. This is even one of the arguments advanced for job runners backed by the database such as `solid_queue`, `delayed_job` or `good_job`. But relying on this is undesirable iin my opinion as it makes the Active Job abstraction leaky, and if in the future you need to migrate to another backend or even just move the queue to a separate database, you may experience a lot of race conditions of the sort. But more generally, being able to defer work to after the current transaction has been a missing feature of Active Record. Right now the only way to do it is from a model callback, and this forces moving things in Active Record models that sometimes are better done elsewhere. Even as a self-proclaimed "service object skeptic", I often wanted this capability over the last decade, and I'm sure it got asked or desired by many more people. Also there's some 3rd party gems adding this capability using monkey patches. It's not a reason to upstream the capability, but it's a proof that there is demand for it. Implementation wise, this proof of concept shows that it's not really hard to implement, even with nested multi-db transactions support. Co-Authored-By: Cristian Bica <cristian.bica@gmail.com>
cd59867 to
c2df237
Compare
Ref: #26103
Ref: #51426
A fairly common mistake with Rails is to enqueue a job from inside a transaction, and a record as argumemnt, which then lead to a RecordNotFound error when picked up by the queue.
This is even one of the arguments advanced for job runners backed by the database such as
solid_queue,delayed_joborgood_job. But relying on this is undesirable iin my opinion as it makes the Active Job abstraction leaky, and if in the future you need to migrate to another backend or even just move the queue to a separate database, you may experience a lot of race conditions of the sort.But more generally, being able to defer work to after the current transaction has been a missing feature of Active Record. Right now the only way to do it is from a model callback, and this forces moving things in Active Record models that sometimes are better done elsewhere. Even as a self-proclaimed "service object skeptic", I often wanted this capability over the last decade, and I'm sure it got asked or desired by many more people.
Also there's some 3rd party gems adding this capability using monkey patches. It's not a reason to upstream the capability, but it's a proof that there is demand for it.
Implementation wise, this proof of concept shows that it's not really hard to implement, even with nested multi-db transactions support.
One open question is whether we should also expose a callback for
after_rollback(and possible after either state). On paper it makes sense, but I kinda fail to see a good use case for it.cc @matthewd