diff --git a/lib/database_cleaner/active_record.rb b/lib/database_cleaner/active_record.rb index b3b6055..5fee357 100644 --- a/lib/database_cleaner/active_record.rb +++ b/lib/database_cleaner/active_record.rb @@ -4,6 +4,7 @@ ActiveSupport.on_load(:active_record) do require 'database_cleaner/active_record/base' require 'database_cleaner/active_record/transaction' + require 'database_cleaner/active_record/serializable_transaction' require 'database_cleaner/active_record/truncation' require 'database_cleaner/active_record/deletion' diff --git a/lib/database_cleaner/active_record/serializable_transaction.rb b/lib/database_cleaner/active_record/serializable_transaction.rb new file mode 100644 index 0000000..f252768 --- /dev/null +++ b/lib/database_cleaner/active_record/serializable_transaction.rb @@ -0,0 +1,13 @@ +# See: https://github.com/DatabaseCleaner/database_cleaner/pull/681 +return if Gem::Version.new(DatabaseCleaner::VERSION) < Gem::Version.new("2.0.2") + +# See: https://github.com/rails/rails/pull/55542 +return if ActiveRecord.gem_version < Gem::Version.new("8.1.0") + +module DatabaseCleaner + module ActiveRecord + class SerializableTransaction < Transaction + TRANSACTION_PARAMETERS = { isolation: :serializable }.freeze + end + end +end diff --git a/lib/database_cleaner/active_record/transaction.rb b/lib/database_cleaner/active_record/transaction.rb index c7391f0..dec7177 100644 --- a/lib/database_cleaner/active_record/transaction.rb +++ b/lib/database_cleaner/active_record/transaction.rb @@ -1,6 +1,8 @@ module DatabaseCleaner module ActiveRecord class Transaction < Base + TRANSACTION_PARAMETERS = {}.freeze + def start connection = if ::ActiveRecord.version >= Gem::Version.new("7.2") connection_class.lease_connection @@ -9,7 +11,7 @@ def start end # Hack to make sure that the connection is properly set up before cleaning - connection.transaction {} + connection.transaction(**transaction_parameters) { nil } connection.begin_transaction joinable: false end @@ -25,6 +27,12 @@ def clean connection_class.connection_pool.release_connection end + + private + + def transaction_parameters + self.class.const_get(:TRANSACTION_PARAMETERS) + end end end end diff --git a/spec/database_cleaner/active_record/serializable_transaction_spec.rb b/spec/database_cleaner/active_record/serializable_transaction_spec.rb new file mode 100644 index 0000000..c091a15 --- /dev/null +++ b/spec/database_cleaner/active_record/serializable_transaction_spec.rb @@ -0,0 +1,109 @@ +require 'support/database_helper' +require 'database_cleaner/active_record/serializable_transaction' + +return unless defined?(DatabaseCleaner::ActiveRecord::SerializableTransaction) + +RSpec.describe DatabaseCleaner::ActiveRecord::SerializableTransaction do + subject(:strategy) { described_class.new } + + DatabaseHelper.with_all_dbs do |helper| + next if helper.db == :sqlite3 + + context "using a #{helper.db} connection" do + around do |example| + helper.setup + example.run + helper.teardown + end + + describe "#clean" do + context "after an initial #start" do + before do + strategy.start + + ActiveRecord::Base.transaction(isolation: :serializable) do + 2.times { User.create! } + 2.times { Agent.create! } + end + end + + it "should clean all tables" do + expect { strategy.clean } + .to change { [User.count, Agent.count] } + .from([2,2]) + .to([0,0]) + end + end + + context "with fixtures before an initial #start" do + before do + 2.times { User.create! } + strategy.start + ActiveRecord::Base.transaction(isolation: :serializable) do + 2.times { Agent.create! } + end + end + + it "should not clean fixtures" do + expect { strategy.clean } + .to change { [User.count, Agent.count] } + .from([2,2]) + .to([2,0]) + end + end + + context "without an initial start" do + before do + ActiveRecord::Base.transaction(isolation: :serializable) do + 2.times { User.create! } + 2.times { Agent.create! } + end + end + + it "does nothing" do + expect { strategy.clean } + .to_not change { [User.count, Agent.count] } + end + end + end + + describe "#cleaning" do + context "with records" do + it "should clean all tables" do + strategy.cleaning do + ActiveRecord::Base.transaction(isolation: :serializable) do + 2.times { User.create! } + 2.times { Agent.create! } + end + expect([User.count, Agent.count]).to eq [2,2] + end + expect([User.count, Agent.count]).to eq [0,0] + end + end + + context "with fixtures" do + it "should not clean fixtures" do + 2.times { User.create! } + strategy.cleaning do + ActiveRecord::Base.transaction(isolation: :serializable) do + 2.times { Agent.create! } + end + expect([User.count, Agent.count]).to eq [2,2] + end + expect([User.count, Agent.count]).to eq [2,0] + end + end + + context "without an initial start" do + it "does nothing" do + 2.times { User.create! } + 2.times { Agent.create! } + expect { strategy.cleaning {} } + .to_not change { [User.count, Agent.count] } + .from([2,2]) + end + end + end + end + end +end