无法为模型

时间:2017-12-02 12:56:01

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

我对遗留项目有这种情况。假设我有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不变的情况下解决这个问题?

1 个答案:

答案 0 :(得分:3)

没有重构的解决方案(黑客)

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

在生产的情况下,此代码正常运行,但是当您编写测试时,您的数据库通常会在每个测试用例运行后清理(rspec&#39; s it)。所以解决方案是创建主要实体&#39;在每个测试用例在空数据库中运行之前:

# 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 - 工厂:

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 ||= ...使用了memorization,以避免在每个Entity.main_entity调用上执行相同的SELECT查询。