Class: Kithe::ConfigBase
- Inherits:
-
Object
- Object
- Kithe::ConfigBase
- Includes:
- Singleton
- Defined in:
- lib/kithe/config_base.rb
Overview
A central place for environmental/infrastructure type configuration. There were many existing ruby/rails ‘config’ solutions, but none did quite what I wanted without extra complexity. There are now kithe dependencies on this file, this is available solely as something for an individual app to use when it is convenient.
You may also want to consider [railsconfig](github.com/railsconfig/config)
Kithe::ConfigBase:
-
uses an explicit declared list of allowable config keys, no silent typos
-
can read from a local YAML file or ENV, by default letting ENV override local YAML file values.
-
Can transform string values from ENV to some other value type
-
Lets you set defaults in code, including defaults which are based on values from other config keys.
-
Flat list of keys, you can ‘namespace’ in your key names if you want, nested hashes in my experience add too much complexity and bug potential. Kithe::ConfigBase does not use the problematic [hashie](github.com/intridea/hashie) gem.
-
Loads all values the first time any of them is asked for – should fail quickly for any bad values – on boot as long as you reference a value on boot.
# Usage
You will define a custom app subclass of Kithe::ConfigBase, and define allowable config keys in there. In the simplest case:
class Config < Kithe::ConfigBase
config_file Rails.root.join("config", "local_env.yml")
define_key :foo_bar, default: "foo bar"
end
We recommend you put your local class in ‘./lib` to avoid any oddness with Rails auto-re-loading.
This can then be looked up with:
Config.lookup("foo_bar")
If you request a key that was not defined, an ArgumentError is raised. ‘lookup` will happily return nil if no value or default were provided. Instead, for early raise (of a TypeError) on nil or `blank?`:
Config.lookup!("foo_bar")
By default this will load from:
1. a system ENV value `FOO_BAR`
2. the specified `config_file` (can specify an array of multiple, later in list take priority;
config files are run through ERB)
3. the default provided in the `define_key` definition
All values are cached after first lookup for performance and stabilty – this kind of environmental configuration should not change for life of process.
## Specifying ENV lookup
You can disable the ENV lookup:
define_key :foo_bar, env_key: false
Or specify a value to use in ENV lookup, instead of the automatic translation:
define_key :foo_bar, env_key: "unconventional_foo_bar"
Since ENV values are always strings, you can also specify a proc meant for use to transform to some other type:
define_key :foo_bar, system_env_transform: ->(str) { Integer(str) }
A built in transform is provided for keys meant to be boolean, which uses ActiveModel-compatible translation (“0”, “false” and empty string are falsey):
define_key :foo_bar, system_env_transform: Kithe::ConfigBase::BOOLEAN_TRANSFORM
## Allowable values
You can specify allowable values as an array, regex, or proc, to fail quickly if a provided value is not allowed.
define_key :foo_bar, default: "one", allows: ["one", "two", "three"]
define_key :key, allows: /one|two|three/
define_key :other, allows: ->(val) { !val.include?("foo") }
## Default value as proc
A default value can be provided as a proc. It is still only lazily executed once.
define_key :foo_bar, default: -> { "something" }
A proc default value can also use other config keys, simply by looking them up as usual:
define_key :foo_bar, default: => { "#{Config.lookup!('baz')} plus more" }
## Concurrency warning
This doesn’t use any locking for concurrent initial loads, which is technically not great, but probably shouldn’t be a problem in practice, especially in MRI. Trying to do proper locking with lazy load was too hard for me right now.
## Auto-loading
This is intentionally NOT in an auto-loaded directory, so it can be used more easily in Rails initialization without problems. github.com/rails/rails/issues/40904
Constant Summary collapse
- BOOLEAN_TRANSFORM =
lambda { |v| ! v.in?(ActiveModel::Type::Boolean::FALSE_VALUES) }
Class Method Summary collapse
- .config_file(args) ⇒ Object
- .define_key(*args) ⇒ Object
- .lookup(*args) ⇒ Object
- .lookup!(*args) ⇒ Object
Instance Method Summary collapse
- #define_key(name, env_key: nil, default: nil, system_env_transform: nil, allows: nil) ⇒ Object
-
#initialize ⇒ ConfigBase
constructor
A new instance of ConfigBase.
- #lookup(name) ⇒ Object
-
#lookup!(name) ⇒ Object
like lookup, but raises on no or blank value.
Constructor Details
#initialize ⇒ ConfigBase
Returns a new instance of ConfigBase.
115 116 117 |
# File 'lib/kithe/config_base.rb', line 115 def initialize @key_definitions = {} end |
Class Method Details
.config_file(args) ⇒ Object
137 138 139 |
# File 'lib/kithe/config_base.rb', line 137 def self.config_file(args) self.config_file_paths = (self.config_file_paths + Array(args)).freeze end |
.define_key(*args) ⇒ Object
119 120 121 |
# File 'lib/kithe/config_base.rb', line 119 def self.define_key(*args) instance.define_key(*args) end |
.lookup(*args) ⇒ Object
129 130 131 |
# File 'lib/kithe/config_base.rb', line 129 def self.lookup(*args) instance.lookup(*args) end |
.lookup!(*args) ⇒ Object
133 134 135 |
# File 'lib/kithe/config_base.rb', line 133 def self.lookup!(*args) instance.lookup!(*args) end |
Instance Method Details
#define_key(name, env_key: nil, default: nil, system_env_transform: nil, allows: nil) ⇒ Object
141 142 143 144 145 146 147 148 149 |
# File 'lib/kithe/config_base.rb', line 141 def define_key(name, env_key: nil, default: nil, system_env_transform: nil, allows: nil) @key_definitions[name.to_sym] = { name: name.to_s, env_key: env_key, default: default, system_env_transform: system_env_transform, allows: allows } end |
#lookup(name) ⇒ Object
151 152 153 154 155 156 157 158 159 160 |
# File 'lib/kithe/config_base.rb', line 151 def lookup(name) name = name.to_sym defn = @key_definitions[name] unless defn raise ArgumentError.new("No env key defined for: #{name}") end defn[:cached_result] ||= compute_lookup(name) end |
#lookup!(name) ⇒ Object
like lookup, but raises on no or blank value.
163 164 165 166 167 |
# File 'lib/kithe/config_base.rb', line 163 def lookup!(name) lookup(name).tap do |value| raise TypeError, "No value was provided for `#{name}`" if value.blank? end end |