无法为模型创建 FactoryGirl 的工厂

Cannot create FactoryGirl's factory for model

我在遗留项目中遇到过这种情况。假设我有 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-模型,但首先我需要工厂。我创建了这个工厂:

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 不变的情况下解决这个问题?

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

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

在生产环境中,此代码可以正常工作,但是当您编写测试时,您的数据库通常会在每个测试用例后进行清理 运行(rspec 的 it) .所以解决方案是在空数据库中的每个测试用例 运行 之前创建 'main entity':

# 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-查询。