Magic Multi-Connections
WARNING
Despite the 1.1.0 version number, this gem is not quite production ready. Various people have experienced problems using the 1.0.0 version. A solution was found to deal with this issue but it has not been fully tested, so please subscribe to the forum or RubyForge news for any updates.
What
ActiveRecord models are allowed one connection to a database at a time, per class. Ruby on Rails sets up the default connection based on your database.yml configuration to automatically select development, test or production.
But, what if you want to access two or more databases – have 2+ connections open – at the same time. ActiveRecord requires that you subclass ActiveRecord::Base
.
That prevents you doing migrations from one database to another. It prevents you using one set of model classes on two or more databases with the same schema.
Magic Multi-Connections allows you to write your models once, and use them for multiple database Rails databases at the same time. How? Using magical namespacing.
class Person < ActiveRecord::Base; end ActiveRecord::Base.establish_connection :production Person.connection # => production module ContactRepository establish_connection :contact_repo end ContactRepository::Person.connection # => contact_repo old_person = ContactRepository::Person.find_by_email(email) person = old_person.create_as(Person)
You do not have to redefine your models for the multi-connection module ContactRepository
, they are automatically picked up for you. Magically.
TODO: Example about Associations
Issues
Despite the 1.1.0 version of this gem there are still a number of issues with this gem:- Single Table Inheritance is not currently supported
- No connection pooling for alternate databases
Any help would be greatly appreciated
Installing
sudo gem install magic_multi_connections
Rails: Add the following to the bottom of your environment.rb
file
require 'magic_multi_connections'
Ruby scripts: Add the following to the top of your script
require 'rubygems' require 'magic_multi_connections'
Demonstration with Rails
A quick demonstration within Rails to provide a parallel “private” database for an application.
1. Create rails app
Using sqlite3 here, but use your preferred db:
> rails privacy -d sqlite3 > cd privacy > ruby script/generate model Person > cp config/environments/development.rb config/environments/private.rb
The last line allows us to play with our private database within the console and rake tasks.
2. Edit config/database.yml and add our private database:
Add the following to the bottom of config/database.yml
private: adapter: sqlite3 database: db/private.sqlite3
3. Create a database schema
Edit db/migrate/001_create_people.rb
class CreatePeople < ActiveRecord::Migration def self.up create_table :people do |t| t.column :name, :string end end def self.down drop_table :people end end
From the command line, migrate this to our development and private databases:
> rake db:migrate > rake db:migrate RAILS_ENV=private
4. Add some data to databases
> ruby script/console development >> Person.create(:name => 'Nic') >> Person.create(:name => 'Banjo') >> exit > ruby script/console private >> Person.create(:name => 'Super Magical Nic') >> exit
Now it should be obvious which database our app is accessing.
5. Update environment.rb
Edit config/environment.rb to include the library and create the Private module.
Add the following to the end of the file.
require "magic_multi_connections" module Private establish_connection :private end
This tells the Private module that any model class that is requested will be assigned a connection to the private database, as defined in the config/database.yml specification.
6. Setup a controller
Create a people controller with a index action
> ruby script/generate controller people index
Edit your controller app/controllers/people_controller.rb
class PeopleController < ApplicationController before_filter :check_private def index @people = @mod::Person.find(:all) end private def check_private @mod = params[:private] ? Private : Object end end
The check_private action is a hack to demonstrate one method for selecting the database. In reality, a stupid one for hiding a “private” database, but you get the point.
After check_private is called, @mod is either the Object (default) module or the Private module. The Person class is accessible through either of them.
Yes, @mod::Person
is uglier than just Person
. Sorry.
7. Setup the index.rhtml view
Edit app/views/people/index.rhtml
<h1><%= @mod::Person %></h1> <h2><%= @mod::Person.active_connection_name %></h2> <ol> <% @people.each do |person| -%> <li><%= "#{person.name} - #{person.class}" %></li> <% end -%> </ol>
8. Test our multi-connection Rails app
Launch the app
> mongrel_rails start
In your browser, go to http://localhost:3000/people and see the list of people: Nic and Banjo.
Now, to see the private database, go to http://localhost:3000/people?private=1 and see the private list. Its so private, I won’t show it here.
Note: you may need to refresh the private url to see the results. Perhaps Rails is caching the SQL even though its a different connection to a different database. If you know what’s happening here, please email me or the forum. Thanks.
9. End
There ends our example of a Rails application using one model class to access multiple databases cleanly.
Pre-existing modules
In Rails, model files are placed in the app/models folder. If you place them in a subfolder, say app/models/admin, then those model classes are access via module namespaces.
So, app/models/admin/page.rb represents the Admin::Page
class.
Magic Multi-Connections works for these model classes as well.
Admin.establish_connection :admin_dev Admin::Page.active_connection_name # => "Admin::Page"
Related articles
- Original blog article – Magic Multi-Connections: A “facility in Rails to talk to more than one database at a time
- Discussed by DHH
Dr Nic’s Blog
http://www.drnicwilliams.com/ – for future announcements and other stories and things.
Forum
Discussion about the Magic Multi-Connections is on the Magic Models forum:
http://groups.google.com/group/magicmodels
Licence
This code is free to use under the terms of the MIT licence.
Contact
Comments are welcome. Send an email to Dr Nic Williams.