factory_bot 1.3: integrating effectively with Rails 3

Joe Ferris

factory_bot 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_bot 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 factorybot, it looked for definitions as soon as you loaded `factorybot.rb`. Unfortunately, this broke down when Rails 2.1 introduced gem dependency management. Loading factory_bot 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 factorybotrails 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_bot. It boils down to this:

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

A lesson on “easy to add” features

I originally added this feature to factorybot 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 factorybot, 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.


Disclaimer:

Looking for FactoryGirl? The library was renamed in 2017. Project name history can be found here.