FactoryGirl and has_many associations
If you’re writing acceptance or integration tests for your Rails app using tools like Cucumber or RSpec and Capybara, then you’ll likely be familiar with generating seed data for your tests using factories. FactoryGirl is one such factory tool, and it’s currently-in-beta syntax for defining factories is rather nice:
FactoryGirl.define do
factory :venue do
name 'The Batcave'
address '5 Smith Street'
end
factory :gig do
name 'The Who'
venue
end
end
Here we have a factory for a Gig model that belongs to a Venue, and now the corresponding models:
class Venue < ActiveRecord::Base
has_many :gigs
validates_presence_of :name, :address
end
class Gig < ActiveRecord::Base
belongs_to :venue
validates_presence_of :name
validates_presence_of :venue
end
Now we can create a gig in our tests:
FactoryGirl.create(:gig)
Since the factory for the gig includes the venue
association, this is also created, which ensures that the gig object is valid (since it requres the venue to be present).
This approach works nicely for models with a belongs_to
association, but what about has_many
? FactoryGirl provides callbacks that you can use to build the members of a has_many
relationship:
FactoryGirl.define do
factory :venue_with_gigs, :parent => :venue do
after_create do |venue|
FactoryGirl.create(:gig, :venue => venue)
end
end
end
Since having child gigs isn’t a requirement for venues, we’ve created another factory that extends the basic venue factory with and creates a child gig in the after_create
callback. But what happens if our venues actually require at least one child gig?
class Venue < ActiveRecord::Base
has_many :gigs
validates_presence_of :gigs
end
Using after_create
callbacks in the factory won’t work here because the initial creation of the venue will fail due to the empty set of gigs. Instead, we need to move the creation of the gigs into the basic venue factory, and use a different technique, this time in an after_build
callback:
FactoryGirl.define do
factory :venue do
name 'The Batcave'
address '5 Smith Street'
after_build do |venue|
venue.gigs << FactoryGirl.build(:gig, :venue => venue)
end
end
end
The after_build
callback is useful here because it is called both when you run FactoryGirl.build
and also FactoryGirl.create
. When you do the latter, the new gig records are assigned to the gigs association via FactoryGirl.build(:gig, :venue => venue)
.[1] For example:
>> venue = FactoryGirl.create :venue
=> #<Venue id: 1, name: "The Batcave", created_at: "2011-03-23 06:01:37", updated_at: "2011-03-23 06:01:37", address: "5 Smith Street">
>> venue.gigs
=> [#<Gig id: 1, name: "The Who": venue_id: 1, created_at: "2011-03-23 06:02:18", updated_at: "2011-03-23 06:02:18">]
Now you are equipped to build factories that satisfy all kinds of ActiveRecord associations, and you can get back to testing!
[1] You might think that the :venue => venue
association is unnecessary, since the association with the venue is implied when the gig is pushed onto the venue’s gigs
object. However, it is necessary to do this within the context of these factories, since a gig built without an explicitly set :venue
will create a new venue, which in turn creates a new child gig, which in turn creates another venue, and before long you’re in “stack level too deep” country. That’s dangerous country.