I have this situation with legacy project. Suppose I have rails model:
rails g model entity name:string
In that model there is MAIN_ENTITY
constant:
class Entity < ActiveRecord::Base
MAIN_ENTITY_ID = 1
MAIN_ENTITY = Entity.find(MAIN_ENTITY_ID)
end
Now I want to cover Entity
-model with rspec tests, but first I need factory. I created this factory:
FactoryGirl.define do
factory :entity do
name { FFaker::Company.name }
end
end
And when I running create(:entity)
inside rspec test, I am getting this error:
Failure/Error: MAIN_ENTITY = Entity.find(MAIN_ENTITY_ID)
ActiveRecord::RecordNotFound:
Couldn't find Entity with 'id'=1
How to fix that without refactoring code and keeping MAIN_ENTITY
constant?
Ruby constants initializing before you access the static context of class. So, when you calling Entity
class (manually or by using FactoryGirl), in background there is SQL query, which is trying to find record with id == 1
.
In case of production, this code is working, but when you writing tests, your database usually is cleaning up after each test case run (rspec's it
). So solution would be to create 'main entity' before each test case run in empty database:
# 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
In create_main_entity
method we have limitation here: we cannot use Entity.create
or Entity.new
methods, so we should use more low level solution: we need to make INSERT
query directly into database. I would rewrite your entities.rb
-factory like this:
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
My code above is temporary solution. I would recommend you to refactor your Entity
model. Instead of initializing constant by finding record, you can use static method:
class Entity < ActiveRecord::Base
MAIN_ENTITY_ID = 1
def self.main_entity
@@main_entity ||= Entity.find(MAIN_ENTITY_ID)
end
end
I used memorization here @@main_entity ||= ...
to avoid executing same SELECT-query on each Entity.main_entity
call.