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