Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
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