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