Alex Rivera | Logout

FactoryGirl in Rails - Associations w/ Unique Constraints

Asked 2012-03-13T04:26:52.007
9

This question is an extension to the one raised here:

Using factory_girl in Rails with associations that have unique constraints. Getting duplicate errors

The answer offered has worked perfectly for me. Here's what it looks like:

# Creates a class variable for factories that should be only created once.

module FactoryGirl

  class Singleton
    @@singletons = {}

    def self.execute(factory_key)
      begin
        @@singletons[factory_key] = FactoryGirl.create(factory_key)
      rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotUnique
        # already in DB so return nil
      end

      @@singletons[factory_key]
    end
  end

end

The issue that has come up for me is when I need to manually build an association to support a polymorphic association with a uniqueness constraint in a hook. For example:

class Matchup < ActiveRecord::Base
  belongs_to :event
  belongs_to :matchupable, :polymorphic => true

  validates :event_id, :uniqueness => { :scope => [:matchupable_id, :matchupable_type] }
end

class BaseballMatchup < ActiveRecord::Base
  has_one :matchup, :as => :matchupable
end

FactoryGirl.define do
  factory :matchup do
    event { FactoryGirl::Singleton.execute(:event) }
    matchupable { FactoryGirl::Singleton.execute(:baseball_matchup) }
    home_team_record '10-5'
    away_team_record '9-6'
  end

  factory :baseball_matchup do
    home_pitcher 'Joe Bloe'
    home_pitcher_record '21-0'
    home_pitcher_era 1.92
    home_pitcher_arm 'R'
    away_pitcher 'Jack John'
    away_pitcher_record '0-21'
    away_pitcher_era 9.92
    away_pitcher_arm 'R'
    after_build do |bm|
      bm.matchup = Factory.create(:matchup, :matchupable => bm)
    end
  end
end

My current singleton implementation doesn't support ca

Edit
Report

1 Answer

1

You need to do two things to make this work:

  1. Accept attributes as an argument your execute method.
  2. Key off of both the factory name and the attributes when creating the singleton factories.

Note that step 1 isn't sufficient to solve your problem. Even if you allow execute to accept attributes, the first call to execute(:matchup, attributes) will cache that result and return it any time you execute(:matchup), even if you attempt to pass different attributes to execute. That's why you also need to change what you're using as the hash key for your @@singletons hash.

Here's an implementation I tested out:

module FactoryGirl
  class Singleton
    @@singletons = {}

    def self.execute(factory_key, attributes = {})

      # form a unique key for this factory and set of attributes
      key = [factory_key.to_s, '?', attributes.to_query].join

      begin
        @@singletons[key] = FactoryGirl.create(factory_key, attributes)
      rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotUnique
        # already in DB so return nil
      end

      @@singletons[key]
    end
  end
end

The key is a string consisting of the factory name and a query string representation of the attributes hash (something like "matchup?event=6&matchupable=2"). I was able to create multiple different matchups with different attributes, but it respected the uniqueness of the event/matchupable combination.

> e = FactoryGirl.create(:event)
> bm = FactoryGirl.create(:baseball_matchup)
> m = FactoryGirl::Singleton.execute(:matchup, :event => e, :matchupable => bm)
> m.id
2
> m = FactoryGirl::Singleton.execute(:matchup, :event => e, :matchupable => bm)
> m.id
2
> f = FactoryGirl.create(:event)
> m = FactoryGirl::Singleton.execute(:matchup, :event => f, :matchupable =>
answered 2012-04-06T14:09:20.143

Your Answer