Traits can be registered globally, so that they can be used in any other factory without using FactoryGirl's inheritance:
FactoryGirl.define do
trait :no_author do
after(:build) { |authorable| authorable.author = nil }
end
trait :no_tenant do
tenant nil
end
factory :model do
tenant { build(:tenant) }
end
end
You can then simply build your objects like this:
FactoryGirl.build(:model, :no_tenant)
FactoryGirl.build(:model, :no_author)
after
callbacks can also be registered globally, but that would mean they are triggered for any object FactoryGirl creates, which may cause undesired side effects:
FactoryGirl.define do
after(:build) do |authorable|
authorable.author = build(:user, tenant: authorable.tenant)
end
factory :model do
tenant { build(:tenant) }
end
factory :other_model
end
FactoryGirl.build(:model) # Happiness!
FactoryGirl.build(:other_model) # undefined method `tenant'
To avoid this, you can either wrap the callback in a trait, like you did in the :no_author
trait, or you can use factory inheritance:
FactoryGirl.define do
factory :tenancyable do
trait :no_tenant do
tenant nil
end
factory :authorable do
after(:build) do |authorable|
authorable.author = build(:user, tenant: authorable.tenant)
end
trait :no_author do
after(:build) do |authorable|
authorable.author = nil
end
end
end
end
factory :model, parent: :authorable, class: 'Model' do
tenant { build(:tenant) }
end
factory :other_model
end
Note how the class for the model
factory needs to be explicitly specified here to make this work. You can now build objects:
FactoryGirl.build(:model, :no_author) # Happiness!
FactoryGirl.build(:other_model) # More Happiness!
With the second approach, the traits and callbacks are more contained. This may actually cause less unwanted surprises when you have a large codebase with many factories.