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

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


Just FYI, you can also use the initialize_with macro inside your factory and check to see if the object already exists, then don't create it over again. The solution using a lambda (its awesome, but!) is replicating logic already present in find_or_create_by. This also works for associations where the :league is being created through an associated factory.

FactoryGirl.define do  factory :league, :aliases => [:euro_cup] do    id 1    name "European Championship"    rank 30    initialize_with { League.find_or_create_by_id(id)}  endend


I encountered the same problem and added a lambda at the top of my factories file that implements a singleton pattern, which also regenerates the model if the db has been cleared since the last round of tests/specs:

saved_single_instances = {}#Find or create the model instancesingle_instances = lambda do |factory_key|  begin    saved_single_instances[factory_key].reload  rescue NoMethodError, ActiveRecord::RecordNotFound      #was never created (is nil) or was cleared from db    saved_single_instances[factory_key] = Factory.create(factory_key)  #recreate  end  return saved_single_instances[factory_key]end

Then, using your example factories, you can use a factory_girl lazy attribute to run the lambda

Factory.define :product do |p|  p.product_type  { single_instances[:add_product_type] }  #...this block edited as per comment belowend

Voila!


EDIT:
See an even cleaner solution at the bottom of this answer.

ORIGINAL ANSWER:
This is my solution to creating FactoryGirl singleton associations:

FactoryGirl.define do  factory :platform do    name 'Foo'  end  factory :platform_version do    name 'Bar'    platform {      if Platform.find(:first).blank?        FactoryGirl.create(:platform)      else        Platform.find(:first)      end    }  endend

You call it e.g. like:

And the following platform versions exists:  | Name     |  | Master   |  | Slave    |  | Replica  |

In this way all 3 platform versions will have same platform 'Foo', i.e. singleton.

If you wanna save a db query you can instead do:

platform {  search = Platform.find(:first)  if search.blank?    FactoryGirl.create(:platform)  else    search  end}

And you can consider to make the singleton association a trait:

factory :platform_version do  name 'Bar'  platform  trait :singleton do    platform {      search = Platform.find(:first)      if search.blank?        FactoryGirl.create(:platform)      else        search      end    }  end  factory :singleton_platform_version, :traits => [:singleton]end

If you want to setup more than 1 platform, and have different sets of platform_versions, you can make different traits which are more specific, i.e.:

factory :platform_version do  name 'Bar'  platform  trait :singleton do    platform {      search = Platform.find(:first)      if search.blank?        FactoryGirl.create(:platform)      else        search      end    }  end  trait :newfoo do    platform {      search = Platform.find_by_name('NewFoo')      if search.blank?        FactoryGirl.create(:platform, :name => 'NewFoo')      else        search      end    }  end  factory :singleton_platform_version, :traits => [:singleton]  factory :newfoo_platform_version, :traits => [:newfoo]end

Hope this is useful to some out there.

EDIT:
After submitting my original solution above, I gave the code another look, and found an even cleaner way to do this: You do not define traits in the factories, instead you specify the association when you call the test step.

Make regular factories:

FactoryGirl.define do  factory :platform do    name 'Foo'  end  factory :platform_version do    name 'Bar'    platform  endend

Now you call the test step with the association specified:

And the following platform versions exists:  | Name     | Platform     |  | Master   | Name: NewFoo |  | Slave    | Name: NewFoo |  | Replica  | Name: NewFoo |

When doing it like this, the creation of platform 'NewFoo' is using 'find_or_create_by' functionality, so the first call creates the platform, the next 2 calls finds the already created platform.

In this way all 3 platform versions will have same platform 'NewFoo', and you can create as many sets of platform versions as you need.

I think this is a very clean solution, since you keep the factory clean, and you even make it visible to the reader of your test steps that those 3 platform versions all have the same platform.