What is it
HierarchicalConfig is a library that implements a strategy for configuring an application in a static, declarative, robust, and intuitive way
Principles
- You should not be able to change your config once it's loaded. That's not configuration, that's globals.
- You should be able to check in to source control a config file that holds defaults and defines requirements.
- You should be able to define defaults accross multiple environments without repeating yourself.
- You should be able to change configuration per box based on deploy that does not affect the defaults or requirements that are checked in to source control. This can be accomplished with a deploy time file or environment variables that are also highly configurable and validated.
Usage
- require 'hierarchical_config'
- MY_APP_CONFIG = HierarchicalConfig.load_config( 'config_name', 'config_directory', 'environment_name' )
How does it work
HierarchicalConfig loads a yaml file from the config directory. Each top level name in the yaml file is either a default stanza (for one or more environments,) or a specific environment.
It applies least specific rules first and proceeds to most specific. It then reads an optional overrides file and does the same. If any REQUIRED values have not been overriden by actual values, it raises an exception.
The object that it returns is a deeply nested tree of configuration that can't be modified and raises exceptions if you ask for values that it doesn't know about.
- No more having environments load without the mail server address configured.
- No more silent failures and returning nil for something you thought was configured.
- No more copy and pasting configuration accross environments.
- No more stupid YAML tricks to dry up your configuration.
- No more tricky config that changes based on runtime side effects.
- No more config that is hidden from developers and not in source control (unless you specifically need to.)
Example
config/app.yml
defaults:
root:
child_a: 1
child_b: 2
child_c:
grandchild_a: 3
grandchild_b: 4
super_secret_password: !REQUIRED
check: true
defaults[development,test]:
super_secret_password: not_that_secret
development:
root:
child_b: 8
env_vars:
super_secret_password: SUPER_SECRET
env_vars[production]:
super_secret_password: SUPER_DUPER_SECRET
config/app-overrides.yml
production:
super_secret_password: cant_trust_dev_with_this_we_symlink_this_file
Results
development
:root:
:child_a: 1
:child_b: 8
:child_c:
:grandchild_a: 3
:grandchild_b: 4
:super_secret_password: <%= ENV['SUPER_SECRET'] || 'not_that_secret' %>
:check: true
test
:root:
:child_a: 1
:child_b: 2
:child_c:
:grandchild_a: 3
:grandchild_b: 4
:super_secret_password: <%= ENV['SUPER_SECRET'] || 'not_that_secret' %>
:check: true
production
:root:
:child_a: 1
:child_b: 2
:child_c:
:grandchild_a: 3
:grandchild_b: 4
:super_secret_password: <%= ENV['SUPER_DUPER_SECRET'] || 'cant_trust_dev_with_this_we_symlink_this_file' %>
:check: true
staging
RuntimeError: ["app.super_secret_password is REQUIRED for staging"]
Code
You can access children nodes with a method call or an index operator, in any combination. Add a ?
to the name of a node to get a boolean value back.
# in development environment
AppConfig = HierarchicalConfig.load_config('app', Rails.root.join('config'), Rails.env)
AppConfig.root.child_a # => 1
AppConfig.root["child_c"].grandchild_a # => 3
AppConfig[:super_secret_password] # => 'not_that_secret'
AppConfig.check # => true
AppConfig.check? # => true
AppConfig.super_secret_password? # => true