Situation
# Models
class User < ActiveRecord::Base
has_many :items
end
class Items < ActiveRecord::Base
belongs_to :user
validates_presence_of :user_id
end
# Factories
Factory.define(:user) do |u|
u.name "foo"
end
Factory.define(:user_with_items, :parent => :user) do |u|
u.items {|items| [items.association(:item), items.association(:item)]}
end
Factory.define(:item) do |i|
i.color "red"
end
Factory.define(:item_with_user, :parent => :user) do |i|
i.association(:user)
end
Problem
If you run @user = Factory(:user_with_items)
then @user.items
contains the two items. The issue is that the items aren't associated with the user in the database. If you reload the association @user.items(true)
then you get back an empty array. I know you can build them
manually or create helper methods on your own to build the object graph, but I'd like to avoid that.
Question
So, my question is how can you build up a has_many relationship in factory_girl while respecting the build strategy?
I wrote it properly with inheritance and all that. My commits are merged in here and here.
It's now in FactoryGirl 1.2.3, woot!
I ended up patching factory girl to allow after_build
and after_create
callbacks.
Implementation
Factory.class_eval do
def run (proxy_class, overrides) #:nodoc:
proxy = proxy_class.new(build_class)
proxy.callbacks = @callbacks
overrides = symbolize_keys(overrides)
overrides.each {|attr, val| proxy.set(attr, val) }
passed_keys = overrides.keys.collect {|k| Factory.aliases_for(k) }.flatten
@attributes.each do |attribute|
unless passed_keys.include?(attribute.name)
attribute.add_to(proxy)
end
end
proxy.result
end
def after_create(&block)
@callbacks ||= {}
@callbacks[:after_create] = block
end
def after_build(&block)
@callbacks ||= {}
@callbacks[:after_build] = block
end
end
Factory::Proxy.class_eval do
attr_accessor :callbacks
def run_callback(name)
callbacks && callbacks[name] && callbacks[name].call(@instance)
end
end
Factory::Proxy::Build.class_eval do
def result
run_callback(:after_build)
@instance
end
end
Factory::Proxy::Create.class_eval do
def result
run_callback(:after_build)
@instance.save!
run_callback(:after_create)
@instance
end
end
This could be an evil twin or just an extension you require.
Example Usage
# Models
class User < ActiveRecord::Base
has_many :items
end
class Items < ActiveRecord::Base
belongs_to :user
validates_presence_of :user_id
end
# Factories
Factory.define(:user) do |u|
u.name "foo"
end
Factory.define(:user_with_items, :parent => :user) do |u|
u.after_build do |o|
o.items = [Factory.build(:item, :user => o), Factory.build(:item, :user => o)]
end
end
Factory.define(:item) do |i|
i.color "red"
end
Factory.define(:item_with_user, :parent => :user) do |i|
i.association(:user)
end
# Run
user = Factory(:user_with_items)
user.items(true) # Shows the two saved items
Hope this helps someone in the future. I'll probably attempt to submit this to the guys at thoughtbot, but there's a couple stale tickets in their bug tracker on the subject already.
I usually like to separate building and creating so i can still build the object without going to the database.
Factory.define(:user_with_items, :parent => :user) do |u|
u.after_build do |u|
u.items = (1..2).map {Factory.build(:item, :user => u)}
end
u.after_create do |u|
u.items.each {|i| i.save!}
end
end