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.