Ruby Rails中的FactoryGirl-具有唯一约束的关联

Ruby Rails中的FactoryGirl-具有唯一约束的关联,ruby,rspec,singleton,factory-bot,Ruby,Rspec,Singleton,Factory Bot,这个问题是这里提出的问题的延伸: 所提供的答案对我非常有效。下面是它的样子: # Creates a class variable for factories that should be only created once. module FactoryGirl class Singleton @@singletons = {} def self.execute(factory_key) begin @@singletons[factor

这个问题是这里提出的问题的延伸:

所提供的答案对我非常有效。下面是它的样子:

# Creates a class variable for factories that should be only created once.

module FactoryGirl

  class Singleton
    @@singletons = {}

    def self.execute(factory_key)
      begin
        @@singletons[factory_key] = FactoryGirl.create(factory_key)
      rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotUnique
        # already in DB so return nil
      end

      @@singletons[factory_key]
    end
  end

end
我遇到的问题是,当我需要手动构建关联以支持钩子中具有唯一性约束的多态关联时。例如:

class Matchup < ActiveRecord::Base
  belongs_to :event
  belongs_to :matchupable, :polymorphic => true

  validates :event_id, :uniqueness => { :scope => [:matchupable_id, :matchupable_type] }
end

class BaseballMatchup < ActiveRecord::Base
  has_one :matchup, :as => :matchupable
end

FactoryGirl.define do
  factory :matchup do
    event { FactoryGirl::Singleton.execute(:event) }
    matchupable { FactoryGirl::Singleton.execute(:baseball_matchup) }
    home_team_record '10-5'
    away_team_record '9-6'
  end

  factory :baseball_matchup do
    home_pitcher 'Joe Bloe'
    home_pitcher_record '21-0'
    home_pitcher_era 1.92
    home_pitcher_arm 'R'
    away_pitcher 'Jack John'
    away_pitcher_record '0-21'
    away_pitcher_era 9.92
    away_pitcher_arm 'R'
    after_build do |bm|
      bm.matchup = Factory.create(:matchup, :matchupable => bm)
    end
  end
end
类匹配true
验证:事件\u id,:唯一性=>{:范围=>[:可匹配\u id,:可匹配\u类型]}
结束
类baseBallMatchip:matchupable
结束
工厂女孩
工厂:配对吗
事件{FactoryGirl::Singleton.execute(:事件)}
匹配表{FactoryGirl::Singleton.execute(:barball_matchup)}
主场球队创下10胜5负的记录
客场球队创下9胜6负的记录
结束
工厂:棒球比赛吗
本垒投手乔·布勒
本垒投手创下21-0的记录
本垒投手时代1.92
本垒投手手臂
客场投手“杰克·约翰”
客场投手记录“0-21”
客场投手时代9.92
客场投手手手手
建造完成后进行bm|
bm.matchup=Factory.create(:matchup,:matchupable=>bm)
结束
结束
结束
我当前的singleton实现不支持调用
FactoryGirl::singleton.execute(:matchup,:matchupable=>bm)
,仅
FactoryGirl::singleton.execute(:matchup)

您建议如何修改singleton工厂以支持调用,例如
FactoryGirl::singleton.execute(:matchup,:matchupable=>bm)
FactoryGirl::singleton.execute(:matchup)


因为现在,每当钩子在factory:Basketball\u Matchip上运行时,上面的代码都会抛出唯一性验证错误(“事件已被捕获”)。最终,这就是需要修复的问题,以便数据库中不会有多个匹配或Basketball\u匹配

Ruby方法可以为参数设置默认值,因此请使用空的默认选项散列定义singleton方法:

  def self.execute(factory_key, options={})
现在,您可以用两种方式来称呼它:

  FactoryGirl::Singleton.execute(:matchup)
  FactoryGirl::Singleton.execute(:matchup, :matchupable => bm)
在该方法中,测试options参数散列以查看是否传入了任何内容:

if options.empty?
  # no options specified
else
  # options were specified
end

正如Zettetic所提到的,您可以在execute函数上定义第二个参数,以发送调用FactoryGirl.create期间要使用的属性,默认值为空哈希,这样在您不使用它的情况下,它不会覆盖任何属性(在这种特殊情况下,您不需要检查属性哈希是否为空)

还请注意,在本例中,您不需要定义begin..end块,因为在解救之后没有任何事情要做,所以您可以通过将解救定义为方法定义的一部分来简化方法。在初始化良好的情况下进行赋值也将返回所分配的值,因此无需再次显式访问散列以返回该值。通过所有这些更改,代码将以如下方式结束:

# Creates a class variable for factories that should be only created once.

module FactoryGirl

  class Singleton
    @@singletons = {}

    def self.execute(factory_key, attrs = {})
      @@singletons[factory_key] = FactoryGirl.create(factory_key, attrs)
    rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotUnique
      # already in DB so return nil
    end
  end

end

您需要做两件事来实现这一目标:

  • 接受属性作为
    execute
    方法的参数
  • 创建单例工厂时,关闭工厂名称和属性
  • 请注意,步骤1不足以解决您的问题。即使您允许
    execute
    接受属性,对
    execute(:matchip,attributes)
    的第一次调用将缓存该结果,并在
    执行(:matchip)
    时返回该结果,即使您尝试将不同的属性传递给
    execute
    。这就是为什么您还需要更改用作
    @@singletons
    哈希的哈希键的内容

    下面是我测试过的一个实现:

    module FactoryGirl
      class Singleton
        @@singletons = {}
    
        def self.execute(factory_key, attributes = {})
    
          # form a unique key for this factory and set of attributes
          key = [factory_key.to_s, '?', attributes.to_query].join
    
          begin
            @@singletons[key] = FactoryGirl.create(factory_key, attributes)
          rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotUnique
            # already in DB so return nil
          end
    
          @@singletons[key]
        end
      end
    end
    
    键是一个字符串,由工厂名称和属性哈希的查询字符串表示组成(类似于
    “matchup?event=6&matchupable=2”
    )。我能够创建多个具有不同属性的不同匹配,但它尊重事件/匹配组合的唯一性

    > e = FactoryGirl.create(:event)
    > bm = FactoryGirl.create(:baseball_matchup)
    > m = FactoryGirl::Singleton.execute(:matchup, :event => e, :matchupable => bm)
    > m.id
    2
    > m = FactoryGirl::Singleton.execute(:matchup, :event => e, :matchupable => bm)
    > m.id
    2
    > f = FactoryGirl.create(:event)
    > m = FactoryGirl::Singleton.execute(:matchup, :event => f, :matchupable => bm)
    > m.id
    3
    

    如果这对您不起作用,请告诉我。

    我认为这里的问题是,当您调用FactoryGirl.create并且数据库中有一个现有记录(因此引发异常)时,将不会返回singleton,因为救援后我们没有调用@singleton[factory\u key]。