Skip to content

Commit d69f305

Browse files
authored
BUGFIX: Parent/Child implicit trait results depend on build order. (#1717)
- When a parent with an implicit trait is built at the same time as a child that inherits, and calls, that trait, the result changes based on which is built first. - This commit ensures all implicit traits are run within the correct context.
1 parent 9ff844c commit d69f305

File tree

4 files changed

+44
-2
lines changed

4 files changed

+44
-2
lines changed

lib/factory_bot/definition.rb

+4
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,10 @@ def define_trait(trait)
9595
@defined_traits.add(trait)
9696
end
9797

98+
def defined_traits_names
99+
@defined_traits.map(&:name)
100+
end
101+
98102
def register_enum(enum)
99103
@registered_enums << enum
100104
end

lib/factory_bot/factory.rb

+10-2
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ def initialize(name, options = {})
1717
end
1818

1919
delegate :add_callback, :declare_attribute, :to_create, :define_trait, :constructor,
20-
:defined_traits, :inherit_traits, :append_traits, to: :@definition
20+
:defined_traits, :defined_traits_names, :inherit_traits, :append_traits,
21+
to: :@definition
2122

2223
def build_class
2324
@build_class ||= if class_name.is_a? Class
@@ -85,7 +86,7 @@ def names
8586
def compile
8687
unless @compiled
8788
parent.compile
88-
parent.defined_traits.each { |trait| define_trait(trait) }
89+
inherit_parent_traits
8990
@definition.compile(build_class)
9091
build_hierarchy
9192
@compiled = true
@@ -153,6 +154,13 @@ def parent
153154
end
154155
end
155156

157+
def inherit_parent_traits
158+
parent.defined_traits.each do |trait|
159+
next if defined_traits_names.include?(trait.name)
160+
define_trait(trait.clone)
161+
end
162+
end
163+
156164
def initialize_copy(source)
157165
super
158166
@definition = @definition.clone

lib/factory_bot/trait.rb

+4
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ def initialize(name, &block)
1414
end
1515
end
1616

17+
def clone
18+
Trait.new(name, &block)
19+
end
20+
1721
delegate :add_callback, :declare_attribute, :to_create, :define_trait, :constructor,
1822
:callbacks, :attributes, :klass, :klass=, to: :@definition
1923

spec/acceptance/traits_spec.rb

+26
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@
3838
gender { "Female" }
3939
end
4040

41+
trait :make_it_great do
42+
great
43+
end
44+
4145
factory :great_user do
4246
great
4347
end
@@ -50,6 +54,12 @@
5054
end
5155
end
5256

57+
factory :greatest_user do
58+
trait :great do
59+
great { "GREATEST EVER!!!" }
60+
end
61+
end
62+
5363
factory :admin, traits: [:admin]
5464

5565
factory :male_user do
@@ -177,6 +187,22 @@
177187
end
178188
end
179189

190+
context "factory with implicit traits called by child" do
191+
it "calls the correct trait when parent built first" do
192+
user = FactoryBot.create(:user, :make_it_great)
193+
194+
expect(user.great).to eq "GREAT!!!"
195+
end
196+
197+
it "calls the correct trait when child built first" do
198+
greatest = FactoryBot.create(:greatest_user, :make_it_great)
199+
user = FactoryBot.create(:user, :make_it_great)
200+
201+
expect(user.great).to eq "GREAT!!!"
202+
expect(greatest.great).to eq "GREATEST EVER!!!"
203+
end
204+
end
205+
180206
context "child factory created where trait attributes are inherited" do
181207
subject { FactoryBot.create(:child_male_user) }
182208
its(:gender) { should eq "Male" }

0 commit comments

Comments
 (0)