无法为模型创建 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.create
或Entity.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-查询。
我在遗留项目中遇到过这种情况。假设我有 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.create
或Entity.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-查询。