Traits + Ignored Attributes = ♥

If you don’t know about FactoryGirl’s traits, here’s a quick primer:

They allow you to specify groups of attributes and callbacks that can be applied when you create the Factory in your tests. For example:

A regular factory with a sub_referral
1
2
3
4
5
6
7
8
9
10
11
FactoryGirl.define do
  factory :referral do
    name 'A referral'
      
    trait :with_sub_referrals do
        after(:create) do |referral|
        5.times { create(:sub_referral, referral: ref) }
        end
    end
  end
end

This lets you do something like

1
FactoryGirl.create(:referral, :with_sub_referrals)

But wait, I hear you say – why not just define a new factory, like so:

A regular factory with a sub factory
1
2
3
4
5
6
7
8
9
10
11
FactoryGirl.define do
  factory :referral do
    name 'A referral'
      
    factory :referral_with_sub_referrals do
        after(:create) do |referral|
          5.times { create(:sub_referral, referral: ref) }
        end
    end
  end
end

Sure, this works, but what if I have two traits?

A regular factory with a sub_referral
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
FactoryGirl.define do
  factory :referral do
    name 'A referral'
      
    trait :with_sub_referrals do
        after(:create) do |referral|
          5.times { create(:sub_referral, referral: ref) }
        end
    end
      
    trait :with_rating do
        rating 5
    end
  end
end

Now, this lets you do

1
FactoryGirl.create(:referral, :with_sub_referrals, :with_rating)

Now you could do this by creating multiple factories, but it ends up remarkably ugly, and there’s a tonne of code repetition (ew).

However, what if I want to specify how many sent_referrals I have in the above example. Unfortunately, you can’t pass arguments into traits. Enter ignored attributes:

An ignored attribute is exactly what it sounds like – an attribute that’s ignored when the object is created.

A trait using ignored attributes
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
FactoryGirl.define do
  factory :referral do
    name 'A referral'
      
    trait :with_sub_referrals do
        ignore do
          sent_referrals_count 5
        end
      
        after(:create) do |referral, eval|
          eval.sent_referral_count.times { create(:sub_referral, referral: ref) }
        end
    end
      
    trait :with_rating do
        rating 5
    end
  end
end

So now, to set the number of sent_referrals that are created, I can do this:

1
FactoryGirl.create(:referral, :with_sent_referrals, sent_referral_count: 3)

Beautiful, isn’t it?