Rails has a habit of lulling you into a false sense of security. The ease of use for common tasks makes you wonder why you’re wasting your time with Spring or JEE. Then you try to do something off the common path and watch Rails just shrug its shoulders and grin.

I was working on a community project recently and ran into the requirement to access some static data in another database. Multiple datasources, easy enough. Unfortunately, Rails expects you’ll be sticking with just one, and so I had to go hunting for a solution. Thankfully, I wasn’t the first to hit this and so many people had already done the hard work, leaving me with my piecing together the most elegant solution.

The first thing to do is define your datasource, presumably in database.yml.

legacy_datasource:
  adapter: mysql
  database: legacy_database
  timeout: 5000
  encoding: utf8
  host: localhost
  username: readonly
  enable_call: true
  password: readonly    

You’ll then need to introduce a base class for the models which will use this datasource. It’ll need to be abstract, to avoid declaring any columns and establish a connection to the secondary source.

class LegacyObject < ActiveRecord::Base
  establish_connection :legacy_datasource

  self.abstract_class = true

  def self.columns() @columns ||= []; end

  def self.column(name, sql_type = nil, default = nil, null = true)
    columns << ActiveRecord::ConnectionAdapters::Column.new(name.to_s, default, sql_type.to_s, null)
  end

end 

Now build the model classes you require, making sure to extend from your new base class.

class AnObject < LegacyObject

  column :object_id, :integer

  # and so on...

end 

If you just require read-only access then you’re now done. If you require migration support then a bit more work is needed.

Create a new base class for the migrations that redefines the connection.

class LegacyMigration < ActiveRecord::Migration
  def self.connection
    LegacyDatasource.connection
  end
end 

Now, extend from the new base class and write your migration as normal.

class AMigration < LegacyMigration
  def self.up   
    # ...
  end

  def self.down
    # ...
  end
end 

And you now have a complete solution. Despite the lack of official documentation, it’s surprisingly easy to do.