Multiple Databases with Merb and ActiveRecord

02/26/09

Rails makes it hard, but not impossible to connect to multiple databases. Merb is ORM agnostic, and the core team loves Datamapper in part because it supports multiple database repositories. You would think that Merb might make things easier with ActiveRecord and multiple database. Not, so there are some big gotchas in the merb_activerecord plugin that make the usual Rails method fail too.

Here is the way in works in rails:


class LegacyTable < ActiveRecord::Base
establish_connection( ‘legacy_database_connection_name_in_yml_file’ )
end

Dave Thomas has written that the issue with this is that every class with this line will establish its own connection to the database, which is a lot of overhead. You can add a line change this behavior with inheritance:

class LegacyData < ActiveRecord::Base
  self.abstract_class = true
  establish_connection( 'legacy_database_connection_name_in_yml_file' )
end

class LegacyTable < LegacyData
end

In Merb when you try this technique, you get an error stating that the database is not configured correctly (ActiveRecord::AdapterNotSpecified). This is the same kind of error that you see when a database.yml file uses a tab instead of spaces, or there is a typo, or the whole database configuration is accidentally missing.

So, digging into code, it seems that merb_activerecord loads the YAML and then interns all the keys to symbols. Meanwhile, ActiveRecord::Base.establish_connection will take a string or symbol passed as an argument and convert it to a string before applying it to the loaded YAML file. What this means is that merb_activerecord for some reason, uses exactly the opposite approach of ActiveRecord to assuring key matches between symbols and string.

Since merb_activerecord is just a bridge between Merb and ActiveRecord, the question is begged ‘Why would you do something like this?" One possible reason is that symbols in Ruby are compared to one another as integers. That makes for a more efficient hash lookup since key comparison won’t involve a more expensive string comparison. That being said … ‘Huh?!". Choosing strings over symbols is ActiveRecord’s bad, and the real job of merb_activerecord is to connect the two libraries. This choice creates some annoyance when trying to do something like use multiple databases with Merb. I would just assume not to shave a few milliseconds per application startup, thank you very much.

merb_activerecord bypasses the usual ways that ActiveRecord establishes a connection, by taking advantage of the fact that you can also pass the establish_connection method a hash of configuration options. So, you can do something like this:

  ActiveRecord::Base.establish_connection(
        :adapter  => "mysql",
        :host     => "localhost",
        :username => "myuser",
        :password => "mypass",
        :database => "somedatabase"
   )

That is what merb_activerecord does. It changes all the keys to symbols in the loaded YAML configuration, sub-hashes the database_configuration based on the Merb environment, then it sends this smaller hash of database configuration details to ActiveRecord::Base.establish_connection.

So, unless you are going to move the database configuration for your database configuration into the model (ugh!), the ugly hack for Merb multiple databases involves calling the merb_activerecord method that parses the database configuration file. Here it is:

class LegacyData < ActiveRecord::Base
  self.abstract_class = true
  establish_connection( Merb::Orms::ActiveRecord.configurations[
    "database2_#{Merb.env}".to_sym
  ] )
end

This example does what the earlier Rails example simplifies out: it includes the Merb environment so you can have different secondary databases for each environment. This is good for testing … and you are testing right?