has_many while respecting build strategy in factory_girl

不羁的心 提交于 2019-11-27 14:45:25

问题


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?


回答1:


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!




回答2:


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.




回答3:


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


来源:https://stackoverflow.com/questions/1506556/has-many-while-respecting-build-strategy-in-factory-girl

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!