Search code examples
ruby-on-railsrubyrspecfactory-botdatabase-cleaner

Cannot create FactoryGirl's factory for model


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?


Solution

  • Solution without refactoring (hack)

    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
    

    Better solution with refactoring

    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.