-
Notifications
You must be signed in to change notification settings - Fork 21.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Accept optional transaction args to #with_lock #43224
Accept optional transaction args to #with_lock #43224
Conversation
7d7eb04
to
00f8d34
Compare
@@ -738,6 +738,29 @@ def test_with_lock_rolls_back_transaction | |||
assert_equal old, person.reload.first_name | |||
end | |||
|
|||
def test_with_lock_forwards_transaction_options | |||
person = Person.find 1 | |||
assert_called_with(person, :transaction, [requires_new: true, isolation: "serializable", joinable: false]) do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is testing the implementation details. We avoid to do that in Rails. Can you change the test to really test if the transaction was open with the right options?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good. I updated this test to introspect the resulting transaction.
Person.transaction do | ||
outer_transaction = Person.connection.transaction_manager.current_transaction | ||
assert_equal true, outer_transaction.joinable? | ||
person.with_lock(requires_new: true, joinable: false) do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The updated test no longer tests I am now testing isolation in the adapter-specific tests below since I needed to limit testing to an engine that supports transactions anyway.isolation:
. Testing isolation is database-specific because they each support different isolation levels (and sqlite3 doesn't even like to have isolation specified at all since it only supports one isolation level). We forward all opts to transaction
anyway, so if any work, all should work. I experimented with a test that assert_raises ActiveRecord::TransactionIsolationError
when provided a nonexistent isolation type, which would work consistently across DB engines, but it felt weird to test that behavior from here.
end | ||
end | ||
|
||
if current_adapter?(:PostgreSQLAdapter) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a pattern I saw in other tests which feels appropriate here. In order to test transaction behavior I need to do these tests on an engine that supports transactions, but it doesn't feel important to test all database engines' specific behavior at this level, so I stuck with postgresql.
Thanks, @rafaelfranca! This should be ready for another look. I've got meaningful end to end tests of all the transaction and locking behavior now. Happy to squash commits and rebase the changelog, but want to make sure I'm on the right track here. |
a66dd45
to
d66fc65
Compare
d66fc65
to
3782cb0
Compare
Make #with_lock as expressive as calling #transaction and #lock! individually to enable behavior like so: person.with_lock("FOR UPDATE NOWAIT", requires_new: true) do ... end Helps teams who prefer #with_lock over #lock! to ensure the lock is taken out within a transaction, even when advanced transaction control is required without requiring redundant transaction blocks.
3782cb0
to
b5e670a
Compare
Awesome! Thank you for working on this |
Summary
Make #with_lock as expressive as calling
#transaction
and#lock!
individually to enable behavior like so:
Helps teams who prefer
#with_lock
over#lock!
to ensure the lock istaken out within a transaction, even when advanced transaction control
is required without requiring redundant transaction blocks.
Other Information
This is a nice-to-have in a pretty stable part of ActiveRecord, but it would be helpful for teams who use linters to ensure there's a wrapping transaction before attempting to take out a lock, while still giving them the flexibility to, e.g. require a new nested transaction without double nesting.
Could buy the counterargument that the performance cost of the following is minimal given the default behavior of nested transactions
But I wanted to run it up the flagpole anyway since it feels like a coherent addition to the
#with_lock
method contract, is fully backward compatible, and provides a succinct way to express transaction configuration while advantaging the "safer" block syntax.Retrospective h/t to @fractaledmind's PR #43077 which I just found as I was about to submit this PR (great minds think alike!). Stephen's PR is patching the exact same issue in maybe a more expressive way. The approach I'm submitting avoids coupling to the options that
#transaction
takes and passes them through blind, but it does so via some old school options munging as opposed to named arguments; I'm honestly not sure which I prefer, but if his approach was preferred, the testing approach I took would probably work well in his PR.