It’s time for something new

After 14 years we’re hanging up our keyboards. Our team is joining the lovely people at Culture Amp, where we’ll be helping build a better world of work.

Icelab

FactoryGirl and has_many associations

By Tim Riley23 Mar 2011

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.