FactoryGirl associations without callbacks

Kelly Stannard
The Casper Tech Blog: Z++
3 min readMay 31, 2017

FactoryGirl is a way to generate objects via a common interface and language. More specifically, it is for generating ActiveRecord objects and saving them to the DB. By using FactoryGirl you can easily abstract the creation of large records behind a short method call and even set up ActiveRecord associations with minimal fuss. In this article we are going to look at a different way to set up those associations.

The current way

Given a post factory defined as such:

factory :post do
user
text "hello"
end

If you wanted to have an associated user factory, the FactoryGirl guide will have you do this:

factory :user do
name "bob"

after(:create) do |user, evaluator|
create(:post, user: user, text: "#{user.name} is cool")
end
end

Problems with the current way

This is alright, but leads to issues. For example, if you merely build a user, then that user will not have any posts. This is unfortunate because you could gain significant speed increases in your business logic tests by avoiding the database with build.

>> FactoryGirl.build(:user).posts.size
=> 0
>> FactoryGirl.create(:user).posts.size
=> 2

Also, in a more complex association structure it can be hard to set up the entire structure.

factory :oeuvre do
after(:create) do |oeuvre, evaluator|
user = build :user
posts = create_list(:posts, 2, user: user)
replies = create_list(:replies, 2, user: user)
oeuvre.works.push *posts
oeuvre.works.push *replies
end
end

A better way

An under-documented fact about attribute blocks is that self is the evaluator object from the after create block. This allows you to call any transient, attribute, or association on the factory.

factory :user do
name "bob"
posts { [build(:post, user: nil, text: "#{name} is cool")] }
replies { [build(:reply, user: nil, text: "#{name} is cool")] }
end

factory :oeuvre do
user { build :user }
works { user.posts + user.replies }
end

As you can see above, the oeuvre factory is now much simpler and accesses user attributes directly within build time code. In addition the build method now gives the correct sizes.

>> FactoryGirl.build(:user).posts.size
=> 2
>> FactoryGirl.create(:user).posts.size
=> 2

Last but not least, creating objects with association blocks as opposed to after create blocks is significantly faster:

factory :user_a, class: 'User' do
after(:create) {|u,e| create_list(:post, 2, user: u) }
end

factory :user_b do
posts { build_list(:post, 2, user: nil) }
end

factory :post do
user
end

Benchmark.ips {|r|
r.report("after create") { FactoryGirl.create :user_a }
r.report("assn create") { FactoryGirl.create :user_b }
}
Warming up --------------------------------------
after create 3.000 i/100ms
assn create 8.000 i/100ms
Calculating -------------------------------------
after create 35.724 (±22.4%) i/s - 171.000 in 5.040311s
assn create 91.368 (±17.5%) i/s - 448.000 in 5.049313s

A better better way

This is not a panacea, though, and this works mostly for fairly simple factories.

You might have looked above and noticed that I am passing nil to certain build calls. The reason for this is because if you pass nothing you will get an infinite loop and if you try to pass self you will get an error. The only way around this currently is passing nil.

You will also eventually wind up with a situation where you want to use data from the post factory within the user factory and you will have to again resort to callbacks.

To solve those issues, I have opened a pull request on FactoryGirl and I would recommend you check it out.

If you’re interested in learning more about the types of problems we address, check out our jobs board — we’re always looking for great engineers to join the team.

--

--