【问题标题】:FactoryGirl in Rails - Associations w/ Unique ConstraintsRails 中的 FactoryGirl - 具有独特约束的关联
【发布时间】:2012-03-29 12:27:49
【问题描述】:

这个问题是对这里提出的问题的扩展:

Using factory_girl in Rails with associations that have unique constraints. Getting duplicate errors

提供的答案对我来说非常有效。这是它的样子:

# 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

我目前的单例实现不支持调用FactoryGirl::Singleton.execute(:matchup, :matchupable =&gt; bm),只支持FactoryGirl::Singleton.execute(:matchup)

您如何建议修改单例工厂以支持诸如FactoryGirl::Singleton.execute(:matchup, :matchupable =&gt; bm)FactoryGirl::Singleton.execute(:matchup) 之类的调用?

因为现在,上面的代码每次在 factory :baseball_matchup 上运行钩子时都会抛出唯一性验证错误(“事件已被接受”)。最终,这是需要修复的,所以数据库中不超过一场比赛或棒球比赛。

【问题讨论】:

    标签: ruby rspec singleton factory-bot


    【解决方案1】:

    Ruby 方法可以有参数的默认值,所以用空的默认选项哈希定义你的单例方法:

      def self.execute(factory_key, options={})
    

    现在你可以同时调用它了:

      FactoryGirl::Singleton.execute(:matchup)
      FactoryGirl::Singleton.execute(:matchup, :matchupable => bm)
    

    在方法中,测试选项参数哈希,看看是否传入了任何东西:

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

    【讨论】:

      【解决方案2】:

      正如 zetetic 所提到的,您可以在执行函数上定义第二个参数,以发送在调用 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
      

      【讨论】:

      • 我认为这里的问题是,当您调用 FactoryGirl.create 并且数据库中存在现有记录(因此引发异常)时,不会返回单例,因为我们没有调用@@singletons[factory_key] 救援后。
      【解决方案3】:

      你需要做两件事来完成这项工作:

      1. 接受属性作为execute 方法的参数。
      2. 在创建单例工厂时关闭工厂名称和属性。

      请注意,第 1 步不足以解决您的问题。即使您允许execute 接受属性,对execute(:matchup, attributes) 的第一次调用也会缓存该结果并在您execute(:matchup) 的任何时候返回它,即使您尝试将不同的属性传递给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&amp;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
      

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-08-14
        • 2021-09-05
        • 1970-01-01
        • 1970-01-01
        • 2018-01-30
        • 2022-06-14
        • 2016-05-14
        • 1970-01-01
        相关资源
        最近更新 更多