Class: Kithe::ConfigBase

Inherits:
Object
  • Object
show all
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

Instance Method Summary collapse

Constructor Details

#initializeConfigBase

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