Sunday, October 26, 2008

Connecting to Multiple Databases in Ruby on Rails

If you keep different tables in different databases, you've probably been looking for a way to allow Rails to connect to more than one database. You've probably found that you can add another configuration (besides development, test and production) to config/database.yml. There's a problem with that though. If you added a configuration called foo, you are now always connecting to foo, in production mode, test mode and development mode. This is especially bad in test mode, since the database would get wiped, but you probably want a different database for development and production too.

Here's how to do it. Suppose you have already set up your regular development/test/production databases in config/database.yml:

development:
  adapter: jdbc
  driver: com.mysql.jdbc.Driver
  url: jdbc:mysql://devdb.yourdomain.com:3306/mstore
  username: cart
  password: secret

# Warning: The database defined as 'test' will be erased and
# re-generated from your development database when you run 'rake'.
# Do not set this db to the same as development or production.
test:
  adapter: jdbc
  driver: com.mysql.jdbc.Driver
  url: jdbc:mysql://localhost:3306/mstore
  username: cart
  password: secret

production:
  adapter: jdbc
  driver: com.mysql.jdbc.Driver
  url: jdbc:mysql://proddb.yourdomain.com:3306/mstore
  username: cart
  password: secret

Now you decided to store all the user account information in a different database. You still want a separate development, test and production databases for your account information. You'll need to add three more entries to the database configuration then, one for each of the account databases. You'll need to name them with an appropriate suffix to specify development/test/production:

account_development:
  adapter: jdbc
  driver: com.mysql.jdbc.Driver
  url: jdbc:mysql://devdb.yourdomain.com:3306/account
  username: cart
  password: secret

# Warning: The database defined as 'test' will be erased and
# re-generated from your development database when you run 'rake'.
# Do not set this db to the same as development or production.
account_test:
  adapter: jdbc
  driver: com.mysql.jdbc.Driver
  url: jdbc:mysql://localhost:3306/account
  username: cart
  password: secret

account_production:
  adapter: jdbc
  driver: com.mysql.jdbc.Driver
  url: jdbc:mysql://accountdb.yourdomain.com:3306/account
  username: cart
  password: secret

Note that these account databases could be on the same hosts or on different hosts from the main databases. Now on to the models. You need to create a base class model for all your account tables, so that only one connection to the account database gets created. If you just call establish_connection in every model, it'll open that many database connections, one for each model. Here's a base model for the account tables:

class AccountBase < ActiveRecord::Base
  # No corresponding table in the DB.
  self.abstract_class = true

  # Open a connection to the appropriate database depending
  # on what RAILS_ENV is set to.
  establish_connection("account_#{RAILS_ENV}")
end

Now your account models need to inherit from AccountBase instead of ActiveRecord::Base, like this:

class Login < AccountBase
end

Because it inherits from AccountBase, it'll use the database connection already established by AccountBase.

Monday, October 20, 2008

/etc/hosts and Mac OS X 10.5 Leopard

Instead of using /etc/hosts on Mac OS Leopard, the proper way to add hosts is using the dscl utility. First, let's list the hosts already there:
$ dscl localhost -list /Local/Default/Hosts
$
Nothing. Let's add one. I want to be able to access localhost as me.blogger.com. Right now that doesn't work:
$ ping -q -c 1 me.blogger.com
ping: cannot resolve me.blogger.com: Unknown host
$
Create an entry for it and try again:
$ sudo dscl localhost -create /Local/Default/Hosts/me.blogger.com \
  IPAddress 127.0.0.1
$ ping -q -c 1 me.blogger.com
PING me.blogger.com (127.0.0.1): 56 data bytes

--- me.blogger.com ping statistics ---
1 packets transmitted, 1 packets received, 0% packet loss
round-trip min/avg/max/stddev = 0.077/0.077/0.077/0.000 ms
You can list the hosts you have set up, where they point, and you can delete them:
$ dscl localhost -list /Local/Default/Hosts
me.blogger.com
$
$ dscl localhost -read /Local/Default/Hosts/me.blogger.com
AppleMetaNodeLocation: /Local/Default
IPAddress: 127.0.0.1
RecordName: me.blogger.com
RecordType: dsRecTypeStandard:Hosts
$
$ sudo dscl localhost -delete /Local/Default/Hosts/me.blogger.com
$ dscl localhost -list /Local/Default/Hosts
$