【问题标题】:has_many while respecting build strategy in factory_girlhas_many 同时尊重 factory_girl 中的构建策略
【发布时间】:2010-12-03 03:44:03
【问题描述】:

情况

# 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

问题

如果您运行@user = Factory(:user_with_items),则@user.items 包含这两项。问题是这些项目与数据库中的用户没有关联。如果您重新加载关联@user.items(true),那么您将返回一个空数组。我知道你可以建造它们 手动或自己创建辅助方法来构建对象图,但我想避免这种情况。

问题

那么,我的问题是如何在 factory_girl 中建立 has_many 关系,同时尊重构建策略?

【问题讨论】:

    标签: ruby-on-rails ruby unit-testing factory-bot


    【解决方案1】:

    我用继承和所有这些正确地编写了它。我的提交合并到here 和here。

    现在在 FactoryGirl 1.2.3 中,哇!

    【讨论】:

    • 链接已损坏。它弄乱了下划线。
    【解决方案2】:

    我最终修补了工厂女孩以允许 after_build 和 after_create 回调。

    实施

    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
    

    这可能是邪恶的双胞胎,也可能只是您需要的扩展。

    示例用法

    # 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
    

    希望这对将来的某人有所帮助。我可能会尝试将此提交给thoughtbot 的人,但他们的错误跟踪器中已经有几张过时的票证。

    【讨论】:

    • 不错。如果我发现任何错误,我会告诉你。仅供参考:您可以使用类似 o.items = (1..2).map{Factory.build(:item, :user => o)} 的方式清理 after_build 代码
    【解决方案3】:

    我通常喜欢将构建和创建分开,这样我仍然可以构建对象而无需进入数据库。

    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
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-03-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-02-16
      • 1970-01-01
      • 1970-01-01
      • 2012-02-08
      相关资源
      最近更新 更多