2 min read

Multiple Database in the same Rails app with Octopus

The framework Ruby on Rails isn't designed to manage multiple databases at the same time, anyway a wonderful gem Octopus allows to manage multiple dbs in a transparent and clean way

Simple use case: Data DB and Admin DB

Many web applications have accounts, permissions, logging and some data to display. These entities usually belongs to the same db, but if the we need to change often the data structures there is a risk to lose some data during the schema migration or evolution, so a safe and indipendent db perfectly fit for this need.

So let's see how to tweak the Rails MVC to manage different DBs (sharding) at the model level.

# Gemfile # add octopus dependency and *setup your DBs*
gem 'ar-octopus', :require => "octopus", :git => 'https://github.com/tchandy/octopus.git'

Then You need a way to separate the Admin DB migrations from the Data DB migrations.
By default all the migrations will belong to the master DB which is the Data DB and the Admin DB migrations will specify the different DB in this way:

# db/migrate/2012222_users_on_admin_db.rb
class CreateUsersDevise < ActiveRecord::Migration
using(:admin)

def change
create_table(:users) do |t|
..

The db :admin is the one specified in the shards.yml file

# config/shards.yml
octopus:
environments:
- development
- production
development:
shards:
admin:
adapter: sqlite3
database: db/admin_development.sqlite3
pool: 5
timeout: 5000
...

Now the models who don't use the master db have to specify a different connection and a clean way to do it is to extend the ActiveRecord::Base class to hijack the default connection.

# app/models/admin_record.rb # it's generic
require 'active_support/concern'
module AdminRecord
extend ActiveSupport::Concern
include Octopus::Model
included do
class_eval do
octopus_establish_connection "admin_#{Rails.env}"
connection_proxy.current_shard = :admin # :master otherwise
end
end
end
# app/models/user.rb
class User < ActiveRecord::Base
include AdminRecord
end

if everything works as expected open a rails console and try to print

Post.connection_config
User.connection_config

The output rapresent the model connection and it should be different. I've found this approch very clean but I had to disable the Rails class caching feature in production and I had to add also the octopus connection to the normal models .

# post.rb
class Post < ActiveRecord::Base
octopus_establish_connection Rails.env
end

Let me know what You think and if you have better ways to do db sharding with ActiveRecord. I can say it is perfect but it works if the database use the same adapter driver, otherwise You'll encounter this bug.