Skip to content

bdurand/config_object

Repository files navigation

ConfigObject

The purpose of this gem is to have a standard way of configuring objects that is useable by other Ruby gems.

Features

There are plenty of other gems that provide a configuration facility. This gem exists to provide a unique set of special features.

  • Support for complex configuration objects that can contain their own logic

  • Support for multiple configuration objects of the same class

  • Support for different settings for different environments

  • Configuration can be done from Ruby code or YAML files

  • DRY up your configuration with defaults

  • Configured values are frozen so they can’t be accidentally modified

  • Configuration can be reloaded at any time and notify observing objects

Examples

For the examples, we’ll suppose we have some city data which is pretty static and doesn’t change much.

To use this library, you just need to declare a class that includes ConfigObject. Including this module will add an id attribute and an initialize method that sets attributes from a hash. For each key in the hash, the initializer will look for a setter method and call it with the value. If there is no setter method, it will simply set an instance variable with the same name.

class City
  include ConfigObject
  attr_reader :name, :county, :state, :population, :census_year, :hostname

  # Set the county information based on a hash. This feature can be used to create
  # complex objects from simple configuration hashes.
  def county= (attributes)
    if attributes
      @county = County.new(attributes)
    else
      @county = nil
    end
  end

  # You can use the objects as more than simple data stores because you can add whatever
  # logic you like to the class
  def size
    if population > 1000000
      :big
    elsif population > 300000
      :medium
    else
      :small
    end
  end
end

# The initializer behavior of setting attributes from a hash is available in the module Attributes.
# If we just want that behavior, we can include it in any module.
class County
  include ConfigObject::Attributes
  attr_reader :name, :population
end

You can use a YAML file to load multiple cities like so:

chicago:
  name: Chicago
  state: IL
  population: 2896016
  census_year: 2000
  county:
    name: Cook
    population: 5294664

st_louis:
  name: St. Louis
  state: MO
  population: 348189
  census_year: 2000
  county:
    name: St. Louis
    population: 1016315

milwaukee:
  name: Milwaukee
  state: WI
  population: 604477
  census_year: 2008
  county:
    name: Milwaukee
    population: 953328

Multiple Objects

The above configuration will give us three cities with ids of “chicago”, “st_louis”, and “milwaukee”. These object can be reference from the class:

City[:chicago]

or

City['chicago']

If we want all of them, we can call

City.all

If we want to find by something other than the id using a filter hash:

City.find(:name => "Chicago")
City.all(:state => "IL")

You can match values using a regular expression:

City.find(:name => /^St/)
City.all(:state => /(IL)|(WI)/)

You can use a dot syntax to specify a chain of attributes to call:

City.all("county.name" => 'Milwaukee')

If an attribute in the filter takes a single argument, it will be called with the match value:

City.all("county.population.>", 2000000)

The result of finding by a hash are cached for future lookups so their is no performance penalty for calling them multiple times.

Configuring

As show above, you can configure a class with a YAML file. You can also specify multiple YAML files, a directory containing YAML files, or specify your configuration from Ruby code. When you specify multiple files, the setting in the later files will be merged into the settings in the earlier files in the list.

One YAML file

City.configuration_files = 'config/cities.yml'

Multiple YAML files

City.configuration_files = 'config/cities.yml', 'config/production_cities.yml'

Configure from code

City.configure({
  :chicago => {:name => "Chicago", :population => 3000000},
  :st_louis => {:name => "St. Louis", :population => 1000000}
})

Multiple Environments

Often, your configuration will require different settings in different environments. There are a couple of ways to handle that.

First, if you use multiple objects, like in our city example, you can specify multiple configuration files or blocks depending on the environment.

For example, in a Rails application you could put your environment specific settings in separate files and call:

City.configuration_files = "config/cities.yml", "config/#{Rails.env}_cities.yml"

development_cities.yml

chicago:
  host_name: chicago.local

st_louis:
  host_name: st-louis.local

milwaukee:
  host_name: milwaukee.local

production_cities.yml

chicago:
  host_name: chicago.example.com

st_louis:
  host_name: st-louis.example.com

milwaukee:
  host_name: milwaukee.example.com

The other way to handle environment specific settings is if you only need one configuration object, use the environment name as the configuration id. For example, suppose we have a configuration class HostConfig in a Rails application configured with this file:

development:
  host: localhost
  port: 5700
  username: example
  password: abc123

production:
  host: example.com
  port: 5700
  username: example
  password: $SEddd1

To get the correct settings you could then reference:

HostConfig[Rails.env]

Default Values

If you have some attributes that are mostly the same across all configuration objects, you can specify default values to DRY up you configuration. For instance we could rewrite our sample HostConfig file as:

defaults:
  port: 5700
  username: example

development:
  host: localhost
  password: abc123

production:
  host: example.com
  password: $SEddd1

Defaults can only be specified for root level hash. You can also specify defaults with the set_defaults method.

Stable Values

The attributes set on the configuration objects will be automatically frozen. This is to protect you from accidentally calling destructive methods on them (ie. << or gsub! on String) and changing the original values. These sorts of bugs can be awfully hard to find especially in a Rails application where they may only appear when the code gets to production.

Reloading

You can reload the configuration objects at any time with the reload method.

Objects can register themselves with the configuration class to be notified whenever the configuration is reloaded by calling add_observer and specifying a callback. The callback method or block will be called whenever the configuration is changed so that persistent objects that use the configuration can reinitialize themselves with the new values.

About

Feature packed configuration library for Ruby projects.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages