I’ve been cultishly carrying this snippet around for years:

login: &login
 adapter: mysql
 host: localhost
 username: myuser
 password: mypass
 encoding: utf8

For a couple of reasons around running DB migrations in differently configured environments, I wanted to factor this into bits common to all environments and bits particular to the config on the local machine. I came up with something like:

# Provide default local block
local: &local
 socket: /var/run/mysqld/mysqld.sock
 
common: &common
 adapter: mysql
 encoding: utf8
 reconnect: false
 pool: 5
 username: myuser
 password: mypass
 <<: *local

development:
 database: myproj_test
 <<: *common

This works swimmingly, but I was tired of ritually mimicking and extending the original snippet without actually understanding it. I decided to dig deeper into what was actually going on and found, unsurprisingly, that I stand on the shoulders of giants like Doug Alcorn, James Duncan Davidson, and Ben Bleything. To render proper credit, Ben’s post from June 2006 builds on a missing post from James’s blog, both of which were preceded by Doug’s observations in March 2006, and all of which draw on tricks originally found in the Typo database config. None of them seems to have detailed what’s going on with the &login/*login construct, so I thought I’d dive into that a bit for posterity.

YAML is nothing more than a human-readable way to serialize basic data structures like lists and associative arrays (aka hashes), which makes it ideal to represent the configuration values needed by Rails. It turns out that YAML provides anchors (&), references (*), and associative array merges («), all of which allow you to include by reference either by assignment or by merging a referenced array into another array. 

Here’s an example of each:

# Generate a reference
mammal: &mammal_ref
 warm_blooded: true
 lays_eggs: false

# Define via reference assignment
beaver: *mammal_ref

# Define including a hash merge
otter:
 cute: true
 <<: *mammal_ref

# Define including a hash merge, overriding a value in the reference
platypus:
 <<: *mammal_ref
 lays_eggs: true

I found that the Wikipedia entry on YAML was a good, quick overview; the official YAML spec has all that and more, but it’s not a quick read. Meanwhile, both Doug and Ben observed that bringing ERB into the mix let’s you seriously customize things. I took a bit from both of them; here’s my database.yml now:

# Provide default local block
local: &local
 username: myuser
 password: mypass
<% if File.exist? "/opt/local/var/run/mysql5/mysqld.sock" %>
 socket: /opt/local/var/run/mysql5/mysqld.sock
<% elsif File.exist? "/var/run/mysqld/mysqld.sock" %>
 socket: /var/run/mysqld/mysqld.sock
<% elsif File.exist? "/tmp/mysql.sock" %>
 socket: /tmp/mysql.sock
<% end %>
 
# Allow for local DB configuration
<%= File.read(File.join(File.dirname( __FILE__ ), 'dblocal.yml')) if File.exist?(File.join(File.dirname( __FILE__ ), 'dblocal.yml')) %>

common: &common
 adapter: mysql
 encoding: utf8
 reconnect: false
 pool: 5
 <<: *local

development:
 database: myproj_development
 <<: *common

test:
 database: myproj_test
 <<: *common

production:
 database: myproj_production
 <<: *common

Works a treat. Thanks, Doug, James, and Ben.



blog comments powered by Disqus

Published

13 August 2010

Category

ruby/rails