diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index d4ce035cd7b74..c14ca30388ad1 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,8 @@ +* Accept optional transaction args to `ActiveRecord::Locking::Pessimistic#with_lock` + + `#with_lock` now accepts transaction options like `requires_new:`, + `isolation:`, and `joinable:` + * Adds support for deferrable foreign key constraints in PostgreSQL. By default, foreign key constraints in PostgreSQL are checked after each statement. This works for most use cases, diff --git a/activerecord/lib/active_record/locking/pessimistic.rb b/activerecord/lib/active_record/locking/pessimistic.rb index 0849db8a7e2f5..21f114eb6f9c5 100644 --- a/activerecord/lib/active_record/locking/pessimistic.rb +++ b/activerecord/lib/active_record/locking/pessimistic.rb @@ -81,9 +81,15 @@ def lock!(lock = true) # Wraps the passed block in a transaction, locking the object # before yielding. You can pass the SQL locking clause - # as argument (see lock!). - def with_lock(lock = true) - transaction do + # as an optional argument (see #lock!). + # + # You can also pass options like requires_new:, isolation:, + # and joinable: to the wrapping transaction (see + # ActiveRecord::ConnectionAdapters::DatabaseStatements#transaction). + def with_lock(*args) + transaction_opts = args.extract_options! + lock = args.present? ? args.first : true + transaction(**transaction_opts) do lock!(lock) yield end diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb index d523718bf4045..28720e13b3412 100644 --- a/activerecord/test/cases/locking_test.rb +++ b/activerecord/test/cases/locking_test.rb @@ -738,6 +738,19 @@ def test_with_lock_rolls_back_transaction assert_equal old, person.reload.first_name end + def test_with_lock_configures_transaction + person = Person.find 1 + 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 + current_transaction = Person.connection.transaction_manager.current_transaction + assert_not_equal outer_transaction, current_transaction + assert_equal false, current_transaction.joinable? + end + end + end + if current_adapter?(:PostgreSQLAdapter) def test_lock_sending_custom_lock_statement Person.transaction do @@ -747,6 +760,22 @@ def test_lock_sending_custom_lock_statement end end end + + def test_with_lock_sets_isolation + person = Person.find 1 + person.with_lock(isolation: :read_uncommitted) do + current_transaction = Person.connection.transaction_manager.current_transaction + assert_equal :read_uncommitted, current_transaction.isolation_level + end + end + + def test_with_lock_locks_with_no_args + person = Person.find 1 + assert_sql(/LIMIT \$?\d FOR UPDATE/i) do + person.with_lock do + end + end + end end def test_no_locks_no_wait