factory_girl 1.3: integrating effectively with Rails 3

factory_girl 1.3: integrating effectively with Rails 3

factory_girl has always had little Rails-specific code. It only depends on Ruby, and in order to use it in your application, it asks three things of your models:

  • They can be instantiated without parameters
  • All properties have writer methods
  • They respond to #save! (this is only necessary for the “create” strategy)

However, despite this simple API, there’s one feature in factory_girl that has traditionally caused some issues with Rails: automatic factory definition loading.

The definition loader has simple rules: if it finds any of test/factories.rb, spec/factories.rb, test/factories/**/*.rb, or spec/factories/**/*.rb, it will load those files in no particular order. For most applications, this just makes things a little nicer.

Hacking around Rails dependency management

In early versions of factory_girl, it looked for definitions as soon as you loaded factory_girl.rb. Unfortunately, this broke down when Rails 2.1 introduced gem dependency management. Loading factory_girl during the application initialization phase meant that the factory definitions might reference your models before all the gems and plugins they depended on were fully loaded. Contributer technicalpickles fixed that by detecting Rails and deferring definition loading until Rails was initialized. This solution worked effectively for the entire Rails 2.x series of releases.

Bundler: effective but strict dependency management

The upcoming Rails 3 release uses a new library for managing application dependencies: bundler. Bundler’s behavior is more predictable and understandable than Rails 2’s dependency manager ever was. However, with the introduction of Bundler, two important changes were introduced that impacted many libraries that integrate silently with each other:

  • Rails is no longer initialized by the time your gem is loaded
  • Load order is no longer configurable, so you can’t depend on another gem’s constants being defined when you integrate with it

Yehuda Katz, the author of Bundler, discussed this topic in depth on his blog. In his discussion, he suggests a solution that could be added to Rubygems:

s.integrates_with "rails", "~> 3.0.0.beta2", "haml/rails" 

However, this functionality doesn’t exist yet, so we’ve decided to create a separate factory_girl_rails gem. This way, we can declare an explicit dependency on Rails, including the version, and automatic definition loading still takes place for Rails apps without any additional application code.

What this means for you

This doesn’t change much for an application that uses factory_girl. It boils down to this:

  • If you’re using Rails 2, just depend on the factory_girl gem, and Rails 2 will pick up the existing rails/init.rb and load your definitions.
  • If you’re using Rails 3, just add factory_girl_rails to your Gemfile. This gem depends on factory_girl and will set up definition loading for you.

A lesson on “easy to add” features

I originally added this feature to factory_girl because it was convenient, and seemed easy to add. However, this trivial feature has turned out to be the most difficult to maintain part of factory_girl, and is the single feature that is most likely to outright break an application. I plan on maintaining this feature because we’re used to having it and we’ve worked out most of the kinks at this point, but next time you consider adding an “easy” feature to your own library, also consider whether or not it’s essential to your library’s core mission.

Joe Ferris Developer