Question

I have many models which can be authorable (have an author field) and/or tenancyable (have a tenant field). So, I wrote concerns for both of them.

The problem is in tests. I had used the shared_examples_for block to write tests for the concerns and include them into my model tests. Anyway, to do this, I have several traits and after blocks, for example:

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

trait :no_tenant do
  tenant nil
end

This piece of code should be equal in the factories of all the models that are tenancyable and authorable.

I didn't found any way to do this. Is it possible?

Was it helpful?

Solution

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top