In many Ruby applications there exists a global “configuration” object that is used to store application-wide data. For example, Capistrano uses an instance of Capistrano::Configuration to load recipe tasks and keep track of recipe variables. Rails also uses a configuration object in the ActiveSupport module that takes the form of a glorified Hash.

Using a configuration object such as these helps to keep application settings globally accessible without polluting the global namespace. They can also provide some nice syntactic sugar to mask the sometimes clumsy or verbose accessor methods of native Hash and/or Array objects.

In my work, there is a pattern that I end up using a lot for these kinds of objects. This won’t be anything new to the experienced Ruby programmer, but it should provide some insight for a beginner. The pattern looks like this:

class Config

  def initialize(data={})
    @data = {}
    update!(data)
  end

  def update!(data)
    data.each do |key, value|
      self[key] = value
    end
  end

  def [](key)
    @data[key.to_sym]
  end

  def []=(key, value)
    if value.class == Hash
      @data[key.to_sym] = Config.new(value)
    else
      @data[key.to_sym] = value
    end
  end

  def method_missing(sym, *args)
    if sym.to_s =~ /(.+)=$/
      self[$1] = args.first
    else
      self[sym]
    end
  end

end

This class is basically a wrapper for a Hash object that is kept internally as the instance variable @data. Below is some code to demonstrate how it can be used.

config = Config.new
config.database = 'database_name'
config.username = 'user'
config.db_hosts = {
  'sj'  => 'sanjose.example.com',
  'ny'  => 'newyork.example.com'
}

config.username         # "user"
config.db_hosts.ny      # "newyork.example.com"

When you use an object similar to the one above, you can completely forget about how you stored the data (was that a symbol or a string that you used for the key in your Hash?) and simply access all properties via the dot syntax. The advantage to using this class over something like OpenStruct is that you can access nested hashes the same as you would use any other variable. This makes the class especially well suited for accessing nested structures like the kinds you might find in a YAML configuration file.

yaml_data = "
---
database: mydb
auth:
  user: myuser
  pass: mypass
"

require 'yaml'
config = Config.new(YAML.load(yaml_data))

config.auth.user        # "myuser"