Simple Rails app configuration class
# Simple configuration system | |
# | |
# The configuration is represented as a tree of keys. Examples: | |
# | |
# AppConfig['key'] | |
# AppConfig['key','subkey'] | |
# AppConfig['key.subkey'] | |
# | |
# An optional default value can be specified: | |
# | |
# AppConfig['key.subkey', default: 'foo'] | |
# | |
# If no default is specified, an exception is raised for nonexistent | |
# values. | |
# | |
# The values are found in two places. For "static" configuration that is | |
# usable anywhere, use the file #{Rails.root}/config/config.yml and supply | |
# nested hashes, starting with a top-level hash of Rails environment names. | |
# You can use the &/<< feature of YAML to DRY up the settings. E.g.,: | |
# | |
# common: &common_settings | |
# app_name: Example Application | |
# bounce_address: Bounces <bounces@example.com> | |
# support: | |
# email: support@example.com | |
# phone: 1-800-555-1212 | |
# | |
# development: &development_settings | |
# <<: *common_settings | |
# host: localhost:3000 | |
# | |
# test: | |
# <<: *development_settings | |
# | |
# production: &production_settings | |
# <<: *common_settings | |
# host: www.example.com | |
# | |
# staging: | |
# <<: *production_settings | |
# host: staging.example.com | |
# | |
# Values can also be found in environment variables, which is useful for | |
# per-server information or sensitive data you don't want to check into source | |
# control. The environment variable's name is used as the top-level key, and | |
# the contents of the variable is treated as a base64-encoded marshaled | |
# hash. If unmarshaling fails, it's treated as a simple string value | |
# (AppConfig['X'] == ENV['x']). | |
# | |
# For convenience the make_env method will generate the environment var | |
# setting in a form suitable for shell or Heroku usage: | |
# | |
# AppConfig.make_env("FOO", {"a" => 1, "b" => 2}) | |
# => "FOO=BAh7BzoGYWkGOgZiaQc=" | |
# | |
# So doing "export FOO=BAh7BzoGYWkGOgZiaQc=" or "heroku config:add | |
# FOO=BAh7BzoGYWkGOgZiaQc=" means you can access the values like this: | |
# | |
# AppConfig['FOO.a'] => 1 | |
# | |
# Values can also be overridden at runtime. The new values are not stored | |
# permanently, but will be visible for the rest of the process lifetime. | |
# | |
# AppConfig['keys'] = "new value" | |
# | |
# In the development environment a warning will be logged if a config | |
# value is changed after it has been read. | |
# | |
# If the configuration file/environment changes, call AppConfig.reset to | |
# cause it to be re-read on the next access. | |
class AppConfig | |
class << self | |
def [](*keys_arg) | |
if keys_arg[-1].is_a?(Hash) | |
has_default = true | |
default = keys_arg.pop[:default] | |
end | |
keys = parse_keys(keys_arg) | |
track_access(keys) | |
value = find_hash(keys)[keys.last] | |
if value | |
value | |
elsif has_default | |
default | |
else | |
raise "AppConfig: #{keys.join(".")} is not in the #{Rails.env.inspect} configuration." | |
end | |
end | |
def []=(*keys_arg, value) | |
keys = parse_keys(keys_arg) | |
check_for_previous_access(keys) | |
hash = find_hash(keys) | |
raise "AppConfig['#{keys[0..-2].join(".")}'] is not a hash" unless hash.is_a?(Hash) | |
hash[keys.last] = value | |
end | |
def make_env(key, hash) | |
"#{key}=#{Base64.strict_encode64(Marshal.dump(hash.stringify_keys)).chomp}" | |
end | |
def reset | |
@union_config = nil | |
end | |
private | |
def parse_keys(keys_arg) | |
keys_arg.collect { |key| key.split('.') }.flatten | |
end | |
# Find the hash the last key would be in. Generate hashes if necessary to | |
# get that far. | |
# | |
def find_hash(keys) | |
keys[0..-2].inject(config) { |config, key| config[key] || config[key] = {} } | |
end | |
# Return a hash that combines the config.yml contents with environment | |
# variables deserialized on demand. | |
# | |
def config | |
unless @union_config | |
@yaml_config = config_from_file || {} | |
@union_config = Hash.new { |hash, key| hash[key] = (@yaml_config[key] || config_from_env_var(key)) } | |
end | |
@union_config | |
end | |
def config_from_file | |
YAML.load(File.read(Rails.root.join("config/config.yml")))[Rails.env] rescue {} | |
end | |
def config_from_env_var(key) | |
Marshal.load(Base64.decode64(ENV[key])) rescue ENV[key] | |
end | |
if Rails.env.development? | |
def track_access(keys) | |
keys_accessed[keys.join(".")] = caller(2) | |
end | |
def check_for_previous_access(keys) | |
prev_accessor = keys_accessed[keys.join(".")] | |
$stdout.puts <<END if prev_accessor | |
AppConfig['#{keys.join(".")}'] was reset at | |
\t#{caller(2).join("\n\t")} | |
but was already used at | |
\t#{prev_accessor.join("\n\t")} | |
END | |
end | |
def keys_accessed | |
@keys_accessed ||= {} | |
end | |
else | |
def track_access(keys) | |
end | |
def check_for_previous_access(keys) | |
end | |
end | |
end | |
end | |
# Author: Walter Smith http://waltersmith.us/ | |
# Copyright 2011 Informed Biometry Corporation | |
# | |
# Permission is hereby granted, free of charge, to any person obtaining a copy | |
# of this software and associated documentation files (the "Software"), to | |
# deal in the Software without restriction, including without limitation the | |
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or | |
# sell copies of the Software, and to permit persons to whom the Software is | |
# furnished to do so, subject to the following conditions: | |
# | |
# The above copyright notice and this permission notice shall be included in | |
# all copies or substantial portions of the Software. | |
# | |
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS | |
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | |
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS | |
# IN THE SOFTWARE. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment