Posted on:
December 18, 2010
by Wolfram Arnold

Producing Object Hierarchies With Factory Girl and Validation Rules or the Bane of :inverse_of

Rails Associations are powerful relationship modeling tools, factories are powerful test data generators, validations are critical to ensure data integrity. But sometimes all three of them don’t play nice together, when you want to insta-build deep object hierarchies with dependent validation rules. Read on to stop pulling out your hair and learn which combinations of features work, which ones don’t, and subtle limitations of the new :inverse_of flag. And chime in if you’ve found a better way.

Take, for example, a User model having a has_many relationship to an Address model. This is nothing out of the ordinary. Assume further that your business rules dictate that a User must have at least one address at all times and that there shall be no dangling addresses (without user reference) in the database. Also not uncommon. Presumably you would use nested attributes to create and update User and related Addresses in one go, but that’s not important here.

	class User < ActiveRecord::Base

	  has_many :addresses, :inverse_of => :user

	  validates :addresses, :length => {:minimum => 1}  # Rails 3 for validates_length_of

	  accepts_nested_attributes_for :addresses

	end


	class Address < ActiveRecord::Base

	  belongs_to :user, :inverse_of => :addresses

	  validates :user, :presence => true

	end

Lastly, we want to use Factory Girl to create an object hierarchy consisting of a User and related Address, ideally in a single line atomic statement for use in tests across the entire application.

	Factory.define :user do |f|
	  f.first_name "Joe"
	  f.last_name  "Smith"
	end

	Factory.define :user_with_address, :parent => :user do |f|
	  f.addresses { |user| [user.association(:address)] }
	end

	Factory.define :address do |f|
	  f.association(:user)   # This will lead to infinite recursion!
	  f.street "123 Main St"
	  f.city "San Francisco"
	  f.state "CA"
	  f.zip "94108"
	end

The point of this exercise is to be able to create a User with associated address

	Factory.create(:user)

or to create an Address with associated User, i.e.

	Factory.create(:address)

Unfortunately, neither works when the validation rules are in effect. Why?

  1. Factory girl uses Factory.create to generate associated objects, even when I use Factory.build. So, Factory.build(:user) would try to invoke Factory.create(:address) under the hood, which fails, because the User association isn’t set up. I tried patching Factory Girl to have it call Factory.build under the hood. This gets past this issue, and Factory.create(:user) now works. However, Factory.create(:address) still fails, because:
  2. ActiveRecord’s :inverse_of feature does not set the live object on the belongs_to side of a has_many association. This has not (yet?) changed for Rails 3.0.3. There is actually a source code comment to this effect in code file belongs_to_association.rb:
/activerecord-3.0.3/lib/active_record/associations/belongs_to_association.rb:

        # NOTE - for now, we're only supporting inverse setting from belongs_to back onto
        # has_one associations.
        def we_can_set_the_inverse_on_this?(record)
          @reflection.has_inverse? && @reflection.inverse_of.macro == :has_one
        end

The effect of this issue is this:

	irb> addr = Address.new
	irb> user = addr.build_user
	irb> user.addr
	  => []

Given that, we cannot construct the object hierarchy by calling the factory for :address. We can create the hierarchy if we start with :user, but only if we patch Factory Girl’s proxy build. Neither is satisfactory.

There is another option to work around this problem by assigning the associations in an after_build hook. This is dissatisfactory as well, however, because it removes the ability to override the association attributes with parameters passed to the factory which is often necessary.

In conclusion, it seems that Factory Girl is not well equipped to construct object hierarchies that where the objects depend on each other for validation rules. Workarounds are possible, down to writing my own methods to construct the hierarchy piece by piece. That, however, is the kind of pedestrian code that the factory was supposed to address in the first place. I hate to write an article that doesn’t come out with a new brilliant and glorious conclusion. This is more of a call for new ideas.

Please comment if you know of other tools or workaround that deal with this situation better. I’ve been a long time fan of Factory Girl but I find these problems a bit too annoying to put up with. I’ll be taking a deeper look at Factory Girl-competitor Machinist next, although it is currently in an API-breaking transition from version 1 to version 2, and version 2 doesn’t implement associations yet. Perhaps it’s not the time to switch.

blog comments powered by Disqus