This is a summary of what I did to support multiple databases in Rails 4.

Assume we are going to have a separate User database to store all user related tables. These are the steps:

Configurations

Add your User database configurations to config/database.yml, using the convention #{Rails.env}_user:

development_user:
  adapter: mysql2
  encoding: utf8
  reconnect: false
  database: user_db
  username: root
  
test_user:
  adapter: mysql2
  encoding: utf8
  reconnect: false
  database: user_db_test
  username: root

Database Rake Tasks

Add rake database tasks for User database.

namespace :user do
  # Load all Rails database tasks under namespace `user`.
  #
  # e.g. rake user:db:create, rake user:db:migrate
  load 'active_record/railties/databases.rake'

  # Modify the context of DatabaseTasks, so database tasks correctly use User database and migrations.
  #
  # User database schema and migrations go under `db/user/schema.rb` and `db/user/migrate/`.
  Rake::Task['user:db:load_config'].enhance do
    db_dir = Rails.root.join('db/user')
    config_dir = Rails.root.join('config')

    ActiveRecord::Tasks::DatabaseTasks.env = "#{Rails.env}_user"
    ActiveRecord::Tasks::DatabaseTasks.database_configuration = YAML.load(File.read(File.join(config_dir, 'database.yml')))
    ActiveRecord::Tasks::DatabaseTasks.db_dir = db_dir
    ActiveRecord::Tasks::DatabaseTasks.seed_loader = UserSeedLoader.new
    ActiveRecord::Tasks::DatabaseTasks.migrations_paths = [File.join(db_dir, 'migrate')]

    ActiveRecord::Base.configurations = ActiveRecord::Tasks::DatabaseTasks.database_configuration
    ActiveRecord::Base.establish_connection(ActiveRecord::Tasks::DatabaseTasks.env.to_sym)

    ActiveRecord::Migrator.migrations_paths = ActiveRecord::Tasks::DatabaseTasks.migrations_paths
  end

  class UserSeedLoader
    def load_seed
      # Load your User db seed here
      #
      # Leave this empty to stop Rails from loading main db's seed through its default loader.
    end
  end

  # Correct user:db:test tasks to use `test_user` db configuration instead of `test` db configuration.
  #
  # desc "Recreate the test database from an existent schema.rb file"
  Rake::Task['user:db:test:load_schema'].clear_actions.enhance do
    begin
      should_reconnect = ActiveRecord::Base.connection_pool.active_connection?
      ActiveRecord::Schema.verbose = false
      ActiveRecord::Tasks::DatabaseTasks.load_schema_for(
        ActiveRecord::Base.configurations['test_user'], :ruby, ENV['SCHEMA'])
    ensure
      if should_reconnect
        ActiveRecord::Base.establish_connection(
          ActiveRecord::Base.configurations[ActiveRecord::Tasks::DatabaseTasks.env])
      end
    end
  end

  # desc "Recreate the test database from an existent structure.sql file"
  Rake::Task['user:db:test:load_structure'].clear_actions.enhance do
    ActiveRecord::Tasks::DatabaseTasks.load_schema_for(
      ActiveRecord::Base.configurations['test_user'], :sql, ENV['SCHEMA'])
  end

  # desc "Empty the test database"
  Rake::Task['user:db:test:purge'].clear_actions.enhance do
    ActiveRecord::Tasks::DatabaseTasks.purge(ActiveRecord::Base.configurations['test_user'])
  end
end

ActiveRecord

Create an abstract User::Base class that connects to User database.

class User::Base < ActiveRecord::Base
  establish_connection "#{Rails.env}_user".to_sym

  self.abstract_class = true
end

class User::Account < User::Base
  # Account model in User database
end

RSpec/FactoryGirl

In FactoryGirl, specify the correct factory class is needed.

factory :user_account, class: User::Account do
  sequence(:name) { |i| "user #{i}" }
end

You have to manually delete/truncate all tables in User database after each test case.

def clear_user_db
  (User::Base.connection.tables - %w(schema_migrations)).each do |table|
    User::Base.connection.execute("DELETE FROM #{table}")
  end
end

after(:all) { clear_user_db }

References

Comments