From 5ccc564923d19695a7dca859e11cc461a8f57758 Mon Sep 17 00:00:00 2001 From: Sweta Date: Tue, 10 Sep 2019 16:24:20 -0700 Subject: [PATCH] Fix self referencing trait error (#1294) If a new trait is defined with attributes, check if attributes matches the name of the trait. Raise an error if so, to avoid a recursive call to the trait. Co-authored-by: Daniel Colsen Co-authored-by: Paras Sanghavi --- lib/factory_bot/declaration/implicit.rb | 3 +++ lib/factory_bot/errors.rb | 3 +++ lib/factory_bot/trait.rb | 6 +++-- spec/acceptance/traits_spec.rb | 35 +++++++++++++++++++++++++ 4 files changed, 45 insertions(+), 2 deletions(-) diff --git a/lib/factory_bot/declaration/implicit.rb b/lib/factory_bot/declaration/implicit.rb index 6901a64..61ff926 100644 --- a/lib/factory_bot/declaration/implicit.rb +++ b/lib/factory_bot/declaration/implicit.rb @@ -25,6 +25,9 @@ module FactoryBot [Attribute::Association.new(name, name, {})] elsif FactoryBot::Internal.sequences.registered?(name) [Attribute::Sequence.new(name, name, @ignored)] + elsif @factory.name.to_s == name.to_s + message = "Self-referencing trait '#{@name}'" + raise TraitDefinitionError, message else @factory.inherit_traits([name]) [] diff --git a/lib/factory_bot/errors.rb b/lib/factory_bot/errors.rb index bcab0ba..761ef94 100644 --- a/lib/factory_bot/errors.rb +++ b/lib/factory_bot/errors.rb @@ -2,6 +2,9 @@ module FactoryBot # Raised when a factory is defined that attempts to instantiate itself. class AssociationDefinitionError < RuntimeError; end + # Raised when a trait is defined that references itself. + class TraitDefinitionError < RuntimeError; end + # Raised when a callback is defined that has an invalid name class InvalidCallbackNameError < RuntimeError; end diff --git a/lib/factory_bot/trait.rb b/lib/factory_bot/trait.rb index 86536b3..a3d8a07 100644 --- a/lib/factory_bot/trait.rb +++ b/lib/factory_bot/trait.rb @@ -7,9 +7,11 @@ module FactoryBot @name = name.to_s @block = block @definition = Definition.new(@name) - proxy = FactoryBot::DefinitionProxy.new(@definition) - proxy.instance_eval(&@block) if block_given? + + if block_given? + proxy.instance_eval(&@block) + end end delegate :add_callback, :declare_attribute, :to_create, :define_trait, :constructor, diff --git a/spec/acceptance/traits_spec.rb b/spec/acceptance/traits_spec.rb index 561814d..fc46986 100644 --- a/spec/acceptance/traits_spec.rb +++ b/spec/acceptance/traits_spec.rb @@ -798,3 +798,38 @@ describe "traits used in associations" do expect(creator.name).to eq "Joe Creator" end end + +describe "when a self-referential trait is defined" do + it "raises a TraitDefinitionError" do + define_model("User", name: :string) + FactoryBot.define do + factory :user do + trait :admin do + admin + end + end + end + + expect { FactoryBot.build(:user, :admin) }.to raise_error( + FactoryBot::TraitDefinitionError, + "Self-referencing trait 'admin'", + ) + end + + it "raises a TraitDefinitionError" do + define_model("User", name: :string) + FactoryBot.define do + factory :user do + trait :admin do + admin + name { "name" } + end + end + end + + expect { FactoryBot.build(:user, :admin) }.to raise_error( + FactoryBot::TraitDefinitionError, + "Self-referencing trait 'admin'", + ) + end +end