Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

I'm working with a Rails 2.2 project working to update it. I'm replacing existing fixtures with factories (using factory_girl) and have had some issues. The problem is with models that represent tables with lookup data. When I create a Cart with two products that have the same product type, each created product is re-creating the same product type. This errors from a unique validation on the ProductType model.

Problem Demonstration

This is from a unit test where I create a Cart and put it together in pieces. I had to do this to get around the problem. This still demonstrates the problem though. I'll explain.

cart = Factory(:cart) cart.cart_items = [Factory(:cart_item,                             :cart => cart,                             :product => Factory(:added_users_product)),                    Factory(:cart_item,                             :cart => cart,                             :product => Factory(:added_profiles_product))] 

The two products being added are of the same type and when each product is created it is re-creating the product type and creating duplicates.

The error that gets generated is: "ActiveRecord::RecordInvalid: Validation failed: Name has already been taken, Code has already been taken"

Workaround

The workaround for this example is to override the product type being used and pass in a specific instance so only one instance is used. The "add_product_type" is fetched early and passed in for each cart item.

cart = Factory(:cart) prod_type = Factory(:add_product_type)   #New cart.cart_items = [Factory(:cart_item,                            :cart => cart,                            :product => Factory(:added_users_product,                                                :product_type => prod_type)), #New                    Factory(:cart_item,                            :cart => cart,                            :product => Factory(:added_profiles_product,                                                :product_type => prod_type))] #New 

Question

What is the best way to use factory_girl with "pick-list" types of associations?

I'd like for the factory definition to contain everything instead of having to assemble it in the test, although I can live with it.

Background and Extra Details

factories/product.rb

# Declare ProductTypes  Factory.define :product_type do |t|   t.name "None"   t.code "none" end  Factory.define :sub_product_type, :parent => :product_type do |t|   t.name "Subscription"   t.code "sub" end  Factory.define :add_product_type, :parent => :product_type do |t|   t.name "Additions"   t.code "add" end  # Declare Products  Factory.define :product do |p|   p.association :product_type, :factory => :add_product_type   #... end  Factory.define :added_profiles_product, :parent => :product do |p|   p.association :product_type, :factory => :add_product_type   #... end  Factory.define :added_users_product, :parent => :product do |p|   p.association :product_type, :factory => :add_product_type   #... end 

The purpose of ProductType's "code" is so the application can give special meaning to them. The ProductType model looks something like this:

class ProductType < ActiveRecord::Base   has_many :products    validates_presence_of :name, :code   validates_uniqueness_of :name, :code   #... end 

factories/cart.rb

# Define Cart Items  Factory.define :cart_item do |i|   i.association :cart   i.association :product, :factory => :test_product   i.quantity 1 end  Factory.define :cart_item_sub, :parent => :cart_item do |i|   i.association :product, :factory => :year_sub_product end  Factory.define :cart_item_add_profiles, :parent => :cart_item do |i|   i.association :product, :factory => :add_profiles_product end  # Define Carts  # Define a basic cart class. No cart_items as it creates dups with lookup types. Factory.define :cart do |c|   c.association :account, :factory => :trial_account end  Factory.define :cart_with_two_different_items, :parent => :cart do |o|   o.after_build do |cart|     cart.cart_items = [Factory(:cart_item,                                 :cart => cart,                                 :product => Factory(:year_sub_product)),                        Factory(:cart_item,                                 :cart => cart,                                 :product => Factory(:added_profiles_product))]   end end 

When I try to define the cart with two items of the same product type, I get the same error described above.

Factory.define :cart_with_two_add_items, :parent => :cart do |o|   o.after_build do |cart|     cart.cart_items = [Factory(:cart_item,                                :cart => cart,                                :product => Factory(:added_users_product)),                        Factory(:cart_item,                                :cart => cart,                                :product => Factory(:added_profiles_product))]   end end 
like image 678
Mark Eric Avatar asked Jan 06 '10 19:01

Mark Eric


1 Answers

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)}   end end 
like image 119
CubaLibre Avatar answered Sep 29 '22 11:09

CubaLibre