Two Headed Cat! Using Secondbase to provide some level of sanity in a two-database Rails application.

Rails has never played nice with using multiple databases. While it is very easy to go and set up a new connection via ActiveRecord.establish_connection, the niceties that rails provides just don't exist for your second database. Fixtures don't work without explicitly calling set_fixture_class and you do not have access to any of the database tasks or generators that are provided by rails. To help alleviate this pain point for us at CustomInk, Karle Durante wrote SecondBase five years ago to provide database task support and migration generation to Rails applications. Between then and now the internals of Rails changed significantly. DatabaseTasks took over much of the responsibility for the rake tasks we come to depend on for managing databases. While this refactor is a nice addition. It required changing the internals of Secondbase to support newer versions of Rails. To this end, Ken Collins and I set out to upgrade and modernize SecondBase. The solution we settled on is to wrap and enhance the existing Rails database tasks and subclass the migration generator. This provides us with almost all of the tools that Rails gives the original database in a somewhat future proof fashion and does what Ken alluded to in his Multi-Database practices article.

Using SecondBase

In your Gemfile add the SecondBase gem:

gem 'secondbase'

Then in your config/database.yml add your SecondBase configuration:

secondbase:
  development:
    adapter: mysql
    database: myapp_development
  test:
    adapter: mysql
    database: myapp_test

This configuration block supports any options that your regular database supports including url.

Now any model that inherits from SecondBase::Base will connect to and use the database like a regular ActiveRecord model. You can also use associations between your primary database's and secondary database's models.

Tasks and Generators

SecondBase enhances key rake tasks to make having two databases as seamless as possible. All create, drop, purge and test tasks will run across both databases by invoking the original. The only time you will need to call a db:second_base namespace task is to run a migration on the secondary database exclusively.

Rails' migration generator is subclassed to provide the same features as the original. Simply invoke second_base:migration and a new migration will be created targeting your secondary database.

Extra Helpers

We provide a SecondBase::Forced helper to allow you to force a class that inherits from ActiveRecord::Base to use your secondary database. For example, ActiveRecord::SessionStore::Session.extend SecondBase::Forced will make SessionStore use your secondary database.

We also provide a Secondbase.on_base helper for situations where you need to temporarily force ActiveRecord::Base to be your secondary database. The helper is used internally by SecondBase to help deal with the global mutable state issues the migrator and database task objects suffer from. For instance, you can combo this with DatabaseCleaner's truncation strategy to clean out your database for each test.

Closing Thoughts

I've provided a brief overview of SecondBase here. For a longer explanation of the features provided check out the readme. If you would like to see a demo of this gem in action, check out this demo project. To get a better understanding of multi-database rails applications, I would recommend you check out this post by Ken Collins. Finally, if your still running into issues with any of this, please feel free to open an issue on the SecondBase repo.

by Hunter Madison