Skip to content

Commit 9ff844c

Browse files
authored
Execute FactoryBot.lint! in database transactions (#1726)
Prior to this change `FactoryBot.lint!` execution can trigger sequential `FactoryBot.create` calls made within the same test (either Minitest `test` or RSpec `it` blocks). This can result in linting violations for records that have database-level uniqueness constraints. This commit introduces the private `Linter#in_transaction` method to wrap the executing block within an [ActiveRecord::Base.transaction][] block that concludes with raising an [ActiveRecord::Rollback][] error. After this change, executions no longer contest with database-level uniqueness constraints. [ActiveRecord::Base.transaction]: https://edgeapi.rubyonrails.org/classes/ActiveRecord/Transactions/ClassMethods.html#method-i-transaction [ActiveRecord::Rollback]: https://edgeapi.rubyonrails.org/classes/ActiveRecord/Rollback.html
1 parent 9bf88a5 commit 9ff844c

File tree

3 files changed

+35
-2
lines changed

3 files changed

+35
-2
lines changed

NEWS.md

+4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# News
22

3+
## Unreleased
4+
5+
* Changed: execute linting tests within ActiveRecord transactions when available
6+
37
## 6.5.0 (September 6, 2024)
48

59
* fix: issue 1621 broken links in ref/factory.md by @elasticspoon in https://github.com/thoughtbot/factory_bot/pull/1623

lib/factory_bot/linter.rb

+13-2
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ def lint(factory)
7070
def lint_factory(factory)
7171
result = []
7272
begin
73-
FactoryBot.public_send(factory_strategy, factory.name)
73+
in_transaction { FactoryBot.public_send(factory_strategy, factory.name) }
7474
rescue => e
7575
result |= [FactoryError.new(e, factory)]
7676
end
@@ -80,7 +80,7 @@ def lint_factory(factory)
8080
def lint_traits(factory)
8181
result = []
8282
factory.definition.defined_traits.map(&:name).each do |trait_name|
83-
FactoryBot.public_send(factory_strategy, factory.name, trait_name)
83+
in_transaction { FactoryBot.public_send(factory_strategy, factory.name, trait_name) }
8484
rescue => e
8585
result |= [FactoryTraitError.new(e, factory, trait_name)]
8686
end
@@ -106,5 +106,16 @@ def error_message_type
106106
:message
107107
end
108108
end
109+
110+
def in_transaction
111+
if defined?(ActiveRecord::Base)
112+
ActiveRecord::Base.transaction do
113+
yield
114+
raise ActiveRecord::Rollback
115+
end
116+
else
117+
yield
118+
end
119+
end
109120
end
110121
end

spec/acceptance/lint_spec.rb

+18
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,24 @@
2626
}.to raise_error FactoryBot::InvalidFactoryError, error_message
2727
end
2828

29+
it "executes linting in an ActiveRecord::Base transaction" do
30+
define_model "User", name: :string do
31+
validates :name, uniqueness: true
32+
end
33+
34+
define_model "AlwaysValid"
35+
36+
FactoryBot.define do
37+
factory :user do
38+
factory :admin_user
39+
end
40+
41+
factory :always_valid
42+
end
43+
44+
expect { FactoryBot.lint }.to_not raise_error
45+
end
46+
2947
it "does not raise when all factories are valid" do
3048
define_model "User", name: :string do
3149
validates :name, presence: true

0 commit comments

Comments
 (0)