【问题标题】:Cannot create FactoryGirl's factory for model无法为模型创建 FactoryGirl 的工厂
【发布时间】:2017-12-02 12:56:00
【问题描述】:

我在遗留项目中遇到这种情况。假设我有 rails 模型:

rails g model entity name:string

在该模型中有MAIN_ENTITY 常量:

class Entity < ActiveRecord::Base  
  MAIN_ENTITY_ID = 1
  MAIN_ENTITY = Entity.find(MAIN_ENTITY_ID)
end

现在我想用rspec 测试覆盖Entity-model,但首先我需要工厂。我创建了这个工厂:

FactoryGirl.define do
  factory :entity do
    name { FFaker::Company.name }
  end
end

当我在 rspec 测试中运行 create(:entity) 时,我收到了这个错误:

Failure/Error: MAIN_ENTITY = Entity.find(MAIN_ENTITY_ID)

ActiveRecord::RecordNotFound:
  Couldn't find Entity with 'id'=1

如何在不重构代码并保持MAIN_ENTITY 不变的情况下解决这个问题?

【问题讨论】:

    标签: ruby-on-rails ruby rspec factory-bot database-cleaner


    【解决方案1】:

    无需重构的解决方案(hack)

    在访问类的静态上下文之前初始化 Ruby 常量。因此,当您调用 Entity 类(手动或使用 FactoryGirl)时,后台会出现 SQL 查询,它正在尝试使用 id == 1 查找记录。

    在生产的情况下,这段代码可以工作,但是当您编写测试时,您的数据库通常会在每次测试用例运行后清理(rspec 的it)。所以解决方案是在每个测试用例在空数据库中运行之前创建“主实体”:

    # Your DatabaseCleaner initializer file
    RSpec.configure do |config|
      # ...
      config.around(:each) do |example|
        DatabaseCleaner.cleaning do
          FactoryGirl.create_main_entity
          example.run
        end
      end
    end
    

    create_main_entity方法中我们这里有限制:我们不能使用Entity.createEntity.new方法,所以我们应该使用更底层的解决方案:我们需要将INSERT直接查询到数据库中。我会像这样重写你的entities.rb-factory:

    FactoryGirl.define do
      factory :entity do
        name { FFaker::Company.name }
      end
    end
    
    
    module FactoryGirl
      def self.create_main_entity
        connection = ActiveRecord::Base.connection
    
        values = {
          id: 1,
          name: 'MAIN ENTITY',
          created_at: Time.now.to_s(:db),
          updated_at: Time.now.to_s(:db)
        }
    
        sql = <<-SQL
          INSERT INTO entities (#{values.keys.map {|k| k.to_s}.join(', ')})
          VALUES(#{values.values.map{"'%s'"}.join(', ')})
        SQL
    
        connection.insert(sql % values.values)
      end
    end
    

    更好的重构解决方案

    我上面的代码是临时解决方案。我建议你重构你的Entity 模型。您可以使用静态方法,而不是通过查找记录来初始化常量:

    class Entity < ActiveRecord::Base
      MAIN_ENTITY_ID = 1
    
      def self.main_entity
        @@main_entity ||= Entity.find(MAIN_ENTITY_ID)
      end
    end
    

    我在这里使用了记忆 @@main_entity ||= ... 以避免在每个 Entity.main_entity 调用上执行相同的 SELECT 查询。

    【讨论】:

    • 并且最好不要让代码依赖于Entityid。如果可能,请使用其他一些独特的属性,例如slugtitle 等任何全球唯一的。
    • 同意你的观点,@JagdeepSingh,在某些情况下,id 可以更改。例如,我们有两个结构相同的数据库(同一公司的两个分支机构),有一天我们需要将这两个数据库合并为一个数据库。所以当db2.Entity合并到db1.Entity时,db1.Entityid-s会发生变化。还有一些其他类似的情况,例如将部分数据库转储到 json/xml,以便将这些数据导入其他地方。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-09-01
    • 2021-10-13
    • 2020-07-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多