Powerful configuration Ruby-framework with a support for many commonly used config formats with a multi-functional API, developer-friendly DSL and object-oriented behavior.
- Support for: YAML, TOML, JSON, ENV, __END__-instructions;
- Fully thread-safe;
- Object-oriented behavior (config as an object, inhertance, composition, etc), with an abilities of lazy-instantiation;
- Pluggable and extendable multi-functional API;
- Developer-friendly DSL :)
# in the past...:
Config. Defined as a class. Used as an instance. Support for inheritance and composition.
Lazy instantiation. Thread-safe. Command-style DSL. Validation layer. **Dot-notation**)
And pretty-print :) Support for **YAML**, **TOML**, **JSON**, **\_\_END\_\_**, **ENV**.
Extremely simple to define. Extremely simple to use. That's all? **Not** :)
Installation
gem 'qonfig'
$ bundle install
# --- or ---
$ gem install 'qonfig'
require 'qonfig'
Usage
- Definition
- Definition and Settings Access
- access via method
- access via index-method []
- .dig
- .slice
- .slice_value
- .subset
- Configuration
- configure via proc
- configure via settings object (by option name)
- configure via settings object (by setting key)
- instant configuration via proc
- using a hash
- using both hash and proc
- Inheritance
- Composition
- Hash representation
- Default behaviour (without options)
- With transformations
- Dot-style format
- Smart Mixin (
Qonfig::Configurable
) - Instantiation without class definition (
Qonfig::DataSet.build(&definitions)
)
- Definition and Settings Access
- Compacted config
- Definition and instantiation
- by raw initialization
- by existing Qonfig::DataSet class
- by existing Qonfig::DataSet instance
- instantiation without class definition
- validation API
- Setting readers and writers
- reading
- writing
- precitaes
- Definition and instantiation
- Interaction
- Iteration over setting keys (
#each_setting
,#deep_each_setting
) - List of config keys (
#keys
,#root_keys
) - Config reloading (reload config definitions and option values)
- Clear options (set to
nil
) - Frozen state (
.freeze_state!
,#freeze!
,#frozen?
) - Settings as Predicates
- Setting key existence (
#key?
/#option?
/#setting?
) - Run arbitrary code with temporary settings (
#with(configs = {}, &arbitrary_code)
)
- Iteration over setting keys (
- Import settings / Export settings
- Import config settings (
as instance methods
)- Import a set of setting keys (simple dot-noated key list)
- Import with custom method names (mappings)
- Prexify method name
- Import nested settings as raw Qonfig::Settings objects
- Import with pattern-matching
- Support for predicate-like methods
- Export config settings (
as singleton methods
)
- Import config settings (
- Validation
- Introduction
- Key search pattern
- Proc-based validation
- Method-based validation
- Predefined validations
- Custom predefined validators
- Validation of potential setting values
- Work with files
-
Setting keys definition
(DSL methods for dynamic setting keys definition by reading them from a file)
- Load from YAML file
- Expose YAML (
Rails
-like environment-based YAML configs) - Load from JSON file
- Expose JSON (
Rails
-like environment-based JSON configs) - Load from ENV
- Load from __END__ (aka
.load_from_self
) - Expose __END__ (aka
.expose_self
)
-
Setting values
(instance methods for loading the setting values from a file to existing config object with already defined setting keys)
- Default setting values file
- Load setting values from YAML file
- Load setting values from JSON file
- Load setting values from __END__
- Load setting values from file manually
-
Daily work
- Save to JSON file (
#save_to_json
) - Save to YAML file (
#save_to_yaml
)
- Save to JSON file (
-
Setting keys definition
- Plugins
- toml (support for
TOML
format) - pretty_print (beautified/prettified console output)
- vault (support for
Vault
store)
- toml (support for
- Roadmap
- Build
Definition
- Definition and Settings Access
- Configuration
- Inheritance
- Composition
- Hash representation
- Smart Mixin (
Qonfig::Configurable
)
Definition and Access
-
setting(name, value = nil)
- define setting with corresponding name and value; -
setting(name) { setting(name, value = nil); ... }
- define nested settings OR reopen existing nested setting and define some new nested settings; -
re_setting(name, value = nil)
,re_setting(name) { ... }
- re-define existing setting (or define new if the original does not exist); - accessing: access via method, access via index-method [], .dig, .slice, .slice_value, .subset;
# --- definition ---
class Config < Qonfig::DataSet
# nil by default
setting :project_id
# nested setting
setting :vendor_api do
setting :host, 'vendor.service.com'
end
setting :enable_graphql, false
# nested setting reopening
setting :vendor_api do
setting :user, 'simple_user'
end
# re-definition of existing setting (drop the old - make the new)
re_setting :vendor_api do
setting :domain, 'api.service.com'
setting :login, 'test_user'
end
# deep nesting
setting :credentials do
setting :user do
setting :login, 'D@iVeR'
setting :password, 'test123'
end
end
end
config = Config.new # your configuration object instance
access via method
# get option value via method
config.settings.project_id # => nil
config.settings.vendor_api.domain # => 'app.service.com'
config.settings.vendor_api.login # => 'test_user'
config.settings.enable_graphql # => false
access via index-method []
- without dot-notation:
# get option value via index (with indifferent (string / symbol / mixed) access)
config.settings[:project_id] # => nil
config.settings[:vendor_api][:domain] # => 'app.service.com'
config.settings[:vendor_api][:login] # => 'test_user'
config.settings[:enable_graphql] # => false
# get option value via index (with indifferent (string / symbol / mixed) access)
config.settings['project_id'] # => nil
config.settings['vendor_api']['domain'] # => 'app.service.com'
config.settings['vendor_api']['login'] # => 'test_user'
config.settings['enable_graphql'] # => false
# dig to value
config.settings[:vendor_api, :domain] # => 'app.service.com'
config.settings[:vendor_api, 'login'] # => 'test_user'
# get option value directly via index (with indifferent access)
config['project_id'] # => nil
config['enable_graphql'] # => false
config[:project_id] # => nil
config[:enable_graphql] # => false
- with dot-notation:
config.settings['vendor_api.domain'] # => 'app.service.com'
config.settings['vendor_api.login'] # => 'test_user'
config['vendor_api.domain'] # => 'app.service.com'
config['vendor_api.login'] # => 'test_user'
.dig
- without dot-notation:
# get option value in Hash#dig manner (and fail when the required key does not exist);
config.dig(:vendor_api, :domain) # => 'app.service.com' # (key exists)
config.dig(:vendor_api, :login) # => Qonfig::UnknownSettingError # (key does not exist)
- with dot-notation:
config.dig('vendor_api.domain') # => 'app.service.com' # (key exists)
config.dig('vendor_api.login') # => Qonfig::UnknownSettingError # (key does not exist)
.slice
- without dot-notation:
# get a hash slice of setting options (and fail when the required key does not exist);
config.slice(:vendor_api) # => { 'vendor_api' => { 'domain' => 'app_service', 'login' => 'test_user' } }
config.slice(:vendor_api, :login) # => { 'login' => 'test_user' }
config.slice(:project_api) # => Qonfig::UnknownSettingError # (key does not exist)
config.slice(:vendor_api, :port) # => Qonfig::UnknownSettingError # (key does not exist)
- with dot-notation:
config.slice('vendor_api.login') # => { 'loign' => 'test_user' }
config.slice('vendor_api.port') # => Qonfig::UnknownSettingError # (key does not exist)
.slice_value
- without dot-notaiton:
# get value from the slice of setting options using the given key set
# (and fail when the required key does not exist) (works in slice manner);
config.slice_value(:vendor_api) # => { 'domain' => 'app_service', 'login' => 'test_user' }
config.slice_value(:vendor_api, :login) # => 'test_user'
config.slice_value(:project_api) # => Qonfig::UnknownSettingError # (key does not exist)
config.slice_value(:vendor_api, :port) # => Qonfig::UnknownSettingError # (key does not exist)
- with dot-notation:
config.slice_value('vendor_api.login') # => 'test_user'
config.slice_value('vendor_api.port') # => Qonfig::UnknownSettingError # (key does not exist)
.subset
- without dot-notation:
# - get a subset (a set of sets) of config settings represented as a hash;
# - each key (or key set) represents a requirement of a certain setting key;
config.subset(:vendor_api, :enable_graphql)
# => { 'vendor_api' => { 'login' => ..., 'domain' => ... }, 'enable_graphql' => false }
config.subset(:project_id, [:vendor_api, :domain], [:credentials, :user, :login])
# => { 'project_id' => nil, 'domain' => 'app.service.com', 'login' => 'D@iVeR' }
- with dot-notation:
config.subset('project_id', 'vendor_api.domain', 'credentials.user.login')
# => { 'project_id' => nil, 'domain' => 'app.service.com', 'login' => 'D@iVeR' }
Configuration
class Config < Qonfig::DataSet
setting :testing do
setting :engine, :rspec
setting :parallel, true
end
setting :geo_api do
setting :provider, :google_maps
end
setting :enable_middlewares, false
end
config = Config.new
configure via proc
config.configure do |conf|
conf.enable_middlewares = true
conf.geo_api.provider = :yandex_maps
conf.testing.engine = :mini_test
end
configure via settings object (by option name)
config.settings.enable_middlewares = false
config.settings.geo_api.provider = :apple_maps
config.settings.testing.engine = :ultra_test
configure via settings object (by setting key)
config.settings[:enable_middlewares] = true
config.settings[:geo_api][:provider] = :rambler_maps
config.settings[:testing][:engine] = :mega_test
instant configuration via proc
config = Config.new do |conf|
conf.enable_middlewares = false
conf.geo_api.provider = :amazon_maps
conf.testing.engine = :crypto_test
end
using a hash
config = Config.new(
testing: { engine: :mini_test, parallel: false },
geo_api: { provider: :rambler_maps },
enable_middlewares: true
)
config.configure(enable_middlewares: false)
using both hash and proc (proc has higher priority)
config = Config.new(enable_middlewares: true) do |conf|
conf.testing.parallel = true
end
config.configure(geo_api: { provider: nil }) do |conf|
conf.testing.engine = :rspec
end
Inheritance
class CommonConfig < Qonfig::DataSet
setting :uploader, :fog
end
class ProjectConfig < CommonConfig
setting :auth_provider, :github
end
project_config = ProjectConfig.new
# inherited setting
project_config.settings.uploader # => :fog
# own setting
project_config.settings.auth_provider # => :github
Composition
class SharedConfig < Qonfig::DataSet
setting :logger, Logger.new
end
class ServerConfig < Qonfig::DataSet
setting :port, 12345
setting :address, '0.0.0.0'
end
class DatabaseConfig < Qonfig::DataSet
setting :user, 'test'
setting :password, 'testpaswd'
end
class ProjectConfig < Qonfig::DataSet
compose SharedConfig
setting :server do
compose ServerConfig
end
setting :db do
compose DatabaseConfig
end
end
project_config = ProjectConfig.new
# fields from SharedConfig
project_config.settings.logger # => #<Logger:0x66f57048>
# fields from ServerConfig
project_config.settings.server.port # => 12345
project_config.settings.server.address # => '0.0.0.0'
# fields from DatabaseConfig
project_config.settings.db.user # => 'test'
project_config.settings.db.password # => 'testpaswd'
Hash representation
- works via
#to_h
and#to_hash
; - supported options:
-
key_transformer:
- an optional proc that accepts setting key and makes your custom transformations; -
value_transformer:
- an optional proc that accepts setting value and makes your custom transformations; -
dot_style:
- (false
by default) represent setting keys in dot-notation (transformations are supported too);
-
class Config < Qonfig::DataSet
setting :serializers do
setting :json do
setting :engine, :ok
end
setting :hash do
setting :engine, :native
end
end
setting :adapter do
setting :default, :memory_sync
end
setting :logger, Logger.new(STDOUT)
end
Default behavior (without-options)
Config.new.to_h
# =>
{
"serializers": {
"json" => { "engine" => :ok },
"hash" => { "engine" => :native },
},
"adapter" => { "default" => :memory_sync },
"logger" => #<Logger:0x4b0d79fc>
}
With transformations
- with
key_transformer
and/orvalue_transformer
;
key_transformer = -> (key) { "#{key}!!" }
value_transformer = -> (value) { "#{value}??" }
Config.new.to_h(key_transformer: key_transformer, value_transformer: value_transformer)
# =>
{
"serializers!!": {
"json!!" => { "engine!!" => "ok??" },
"hash!!" => { "engine!!" => "native??" },
},
"adapter!!" => { "default!!" => "memory_sync??" },
"logger!!" => "#<Logger:0x00007fcde799f158>??"
}
Dot-style format
- transformations are supported too (
key_transformer
andvalue_transformer
);
Config.new.to_h(dot_style: true)
# =>
{
"serializers.json.engine" => :ok,
"serializers.hash.engine" => :native,
"adapter.default" => :memory_sync,
"logger" => #<Logger:0x4b0d79fc>,
}
transformer = -> (value) { "$$#{value}$$" }
Config.new.to_h(dot_style: true, key_transformer: transformer, value_transformer: transformer)
# => "#<Logger:0x00007fcde799f158>??"
{
"$$serializers.json.engine$$" => "$$ok$$",
"$$serializers.hash.engine$$" => "$$native$$",
"$$adapter.default$$" => "$$memory_sync$$",
"$$logger$$" => "$$#<Logger:0x00007fcde799f158>$$",
}
Smart Mixin
- class-level:
-
.configuration
- settings definitions; -
.configure
- configuration; -
.config
- config object; - settings definitions are inheritable;
-
- instance-level:
-
#configure
- configuration; -
#config
- config object; -
#shared_config
- class-level config object;
-
# --- usage ---
class Application
# make configurable
include Qonfig::Configurable
configuration do
setting :user
setting :password
end
end
app = Application.new
# class-level config
Application.config.settings.user # => nil
Application.config.settings.password # => nil
# instance-level config
app.config.settings.user # => nil
app.config.settings.password # => nil
# access to the class level config from an instance
app.shared_config.settings.user # => nil
app.shared_config.settings.password # => nil
# class-level configuration
Application.configure do |conf|
conf.user = '0exp'
conf.password = 'test123'
end
# instance-level configuration
app.configure do |conf|
conf.user = 'admin'
conf.password = '123test'
end
# class has own config object
Application.config.settings.user # => '0exp'
Application.config.settings.password # => 'test123'
# instance has own config object
app.config.settings.user # => 'admin'
app.config.settings.password # => '123test'
# access to the class level config from an instance
app.shared_config.settings.user # => '0exp'
app.shared_config.settings.password # => 'test123'
# and etc... (all Qonfig-related features)
# --- inheritance ---
class BasicApplication
# make configurable
include Qonfig::Configurable
configuration do
setting :user
setting :pswd
end
configure do |conf|
conf.user = 'admin'
conf.pswd = 'admin'
end
end
class GeneralApplication < BasicApplication
# extend inherited definitions
configuration do
setting :db do
setting :adapter
end
end
configure do |conf|
conf.user = '0exp' # .user inherited from BasicApplication
conf.pswd = '123test' # .pswd inherited from BasicApplication
conf.db.adapter = 'pg'
end
end
BasicApplication.config.to_h
{ 'user' => 'admin', 'pswd' => 'admin' }
GeneralApplication.config.to_h
{ 'user' => '0exp', 'pswd' => '123test', 'db' => { 'adapter' => 'pg' } }
# and etc... (all Qonfig-related features)
Instantiation without class definition
- without inheritance:
config = Qonfig::DataSet.build do
setting :user, 'D@iVeR'
setting :password, 'test123'
def custom_method
'custom_result'
end
end
config.is_a?(Qonfig::DataSet) # => true
config.settings.user # => 'D@iVeR'
config.settings.password # => 'test123'
config.custom_method # => 'custom_result'
- with inheritance:
class GeneralConfig < Qonfig::DataSet
setting :db_adapter, :postgresql
end
config = Qonfig::DataSet.build(GeneralConfig) do
setting :web_api, 'api.google.com'
end
config.is_a?(Qonfig::DataSet) # => true
config.settings.db_adapter # => :postgresql
config.settings.web_api # => "api.google.com"
Compacted config
- Definition and instantiation
- by raw initialization
- by existing Qonfig::DataSet class
- by existing Qonfig::DataSet instance
- instantiation without class definition
- validation API
- Setting readers and writers
- reading
- writing
- precitaes
-
Qonfig::Compacted
: represents the compacted config object with setting readers, setting writers and setting predicates only - and no any other useful instance-based functionality: - setting keys are represented as direct instace methods (
#settings
invokation does not need); - support for index-like access methods (
[]
,[]=
); - full support of
Qonfig::DataSet
definition DSL commands:-
setting
,re_setting
doc -
validate
,add_validator
doc -
load_from_self
doc,load_from_yaml
doc,load_from_json
doc,load_from_toml
doc; -
expose_self
doc,expose_yaml
doc,expose_json
doc,expose_toml
doc -
values_file
doc
-
- support for validation of potential setting values
.valid_with?
documentation; - can be instantiated by:
- by existing config object:
Qonfig::DataSet#compacted
orQonfig::Compacted.build_from(config, &configuration)
; - from existing
Qonfig::DataSet
class:Qonfig::DataSet.build_compacted
; - by direct instantiation:
Qonfig::Compacted.new(settings_values = {}, &configuration)
; - by implicit instance building without explicit class definition
Qonfig::Compacted.build(&dsl_commands)
;
- by existing config object:
- you can define your own instance methods too;
Definition and instantiation
by raw initialization
class Config < Qonfig::Compacted
setting :api, 'google.com'
setting :enabled, true
setting :queue do
setting :engine, :sidekiq
end
end
config = Config.new(api: 'yandex.ru') do |conf|
conf.enabled = false
end
config.api # => 'yandex.ru'
config.enabled # => false
config.queue.engine # => :sidekiq
by existing Qonfig::DataSet class
class Config < Qonfig::DataSet
setting :api, 'google.com'
setting :enabled, true
end
config = Config.build_compacted # builds Qonfig::Compacted instance
config.api # => 'google.com'
config.enabled # => true
by existing Qonfig::DataSet instance
Qonfig::DataSet#compacted
- (or)
Qonfig::Compacted.build_from(config)
class Config < Qonfig::DataSet
setting :api, 'google.com'
setting :enabled, true
end
config = Config.new
compacted_config = config.compacted
# --- or ---
compacted_config = Qonfig::Compacted.build_from(config)
compacted_config.api # => 'google.com'
compacted_config.enabled # => true
instantiation without class definition
config = Qonfig::Compacted.build do
setting :api, 'google.ru'
setting :enabled, true
end
config.api # => 'google.ru'
config.enabled # => true
validation API (see full documentation):
# custom validators
Qonfig::Compacted.define_validator(:version_check) do |value|
value.is_a?(Integer) && value < 100
end
class Config < Qonfig::Compacted
setting :api, 'google.ru'
setting :enabled, true
setting :version, 2
setting :queue { setting :engine, :sidekiq }
# full support of original validation api
validate :api, :string, strict: true
validate :enabled, :boolean, strict: true
validate :version, :version_check # custom validator
validate 'queue.#', :symbol
end
# potential values validation
Config.valid_with?(api: :yandex) # => false
Config.valid_with?(enabled: nil) # => false
Config.valid_with?(version: nil) # => false
Config.valid_with?(api: 'yandex.ru', enabled: false, version: 3) # => true
config = Config.new
# instance validation
config.api = :yandex # => Qonfig::ValidationError (should be a type of string)
config.version = nil # => Qonfig::ValidationError (can not be nil)
config.queue.engine = 'sneakers' # => Qonfig::ValidationError (should be a type of symbol)
Setting readers and writers
class Config < Qonfig::Compcated
setting :api, 'google.ru'
setting :enabled, true
setting :queue do
setting :engine, :sidekiq
setting :workers_count, 10
end
end
config = Config.new
reading (by setting name and index method with dot-notation support and indifferent access)
# by setting name
config.api # => 'google.ru'
config.enabled # => true
config.queue.engine # => :sidekiq
config.queue.workers_count # => 10
# by index method with dot-notation support and indiffernt access
config[:api] # => 'google.ru'
config['enabled'] # => true
config[:queue][:engine] # => :sidekiq
config['queue.workers_count'] # => 10
writing (by setting name and index method with dot-notation support and indifferent access)
# by setting name
config.api = 'yandex.ru'
config.queue.engine = :sidekiq
# and etc
# by index method with dot-notaiton support and indifferent access
config['api'] = 'yandex.ru'
config['queue.engine'] = :sidekiq
config[:queue][:workers_count] = 5
predicates (see full documentation)
class Config < Qonfig::Compcated
setting :enabled, true
setting :api, 'yandex.ru'
setting :queue do
setting :engine, :sidekiq
end
end
config = Config.new
config.enabled? # => true
config.enabled = nil
config.enabled? # => false
config.queue.engine? # => true
config.queue.engine = nil
config.queue.engine? # => false
config.queue? # => true
Interaction
- Iteration over setting keys (
#each_setting
,#deep_each_setting
) - List of config keys (
#keys
,#root_keys
) - Config reloading (reload config definitions and option values)
- Clear options (set to
nil
) - Frozen state (
.freeze_state!
,#freeze!
,#frozen?
) - Settings as Predicates
- Setting key existence (
#key?
/#option?
/#setting?
) - Run arbitrary code with temporary settings
Iteration over setting keys
-
#each_setting { |key, value| }
- iterates over the root setting keys;
-
#deep_each_setting(yield_all: false) { |key, value| }
- iterates over all setting keys (deep inside);
- key object is represented as a string of
.
-joined setting key names; -
yield_all:
means "yield all config objects" (end values and root setting objects those have nested settings) (false
by default);
class Config < Qonfig::DataSet
setting :db do
setting :creds do
setting :user, 'D@iVeR'
setting :password, 'test123',
setting :data, test: false
end
end
setting :telegraf_url, 'udp://localhost:8094'
setting :telegraf_prefix, 'test'
end
config = Config.new
.each_setting
config.each_setting { |key, value| { key => value } }
# result of each step:
{ 'db' => <Qonfig::Settings:0x00007ff8> }
{ 'telegraf_url' => 'udp://localhost:8094' }
{ 'telegraf_prefix' => 'test' }
.deep_each_setting
config.deep_each_setting { |key, value| { key => value } }
# result of each step:
{ 'db.creds.user' => 'D@iveR' }
{ 'db.creds.password' => 'test123' }
{ 'db.creds.data' => { test: false } }
{ 'telegraf_url' => 'udp://localhost:8094' }
{ 'telegraf_prefix' => 'test' }
.deep_each_setting(yield_all: true)
config.deep_each_setting(yield_all: true) { |key, value| { key => value } }
# result of each step:
{ 'db' => <Qonfig::Settings:0x00007ff8> } # (yield_all: true)
{ 'db.creds' => <Qonfig::Settings:0x00002ff1> } # (yield_all: true)
{ 'db.creds.user' => 'D@iVeR' }
{ 'db.creds.password' => 'test123' }
{ 'db.crds.data' => { test: false } }
{ 'telegraf_url' => 'udp://localhost:8094' }
{ 'telegraf_prefix' => 'test' }
List of config keys
-
#keys
- returns a list of all config keys in dot-notation format;-
all_variants:
- get all possible variants of the config's keys sequences (false
by default); -
only_root:
- get only the root config keys (false
by default);
-
-
#root_keys
- returns a list of root config keys (an alias for#keys(only_root: true)
);
# NOTE: suppose we have the following config
class Config < Qonfig::DataSet
setting :credentials do
setting :social do
setting :service, 'instagram'
setting :login, '0exp'
end
setting :admin do
setting :enabled, true
end
end
setting :server do
setting :type, 'cloud'
setting :options do
setting :os, 'CentOS'
end
end
end
config = Config.new
Default behavior
config.keys
# the result:
[
"credentials.social.service",
"credentials.social.login",
"credentials.admin.enabled",
"server.type",
"server.options.os"
]
All key variants
config.keys(all_variants: true)
# the result:
[
"credentials",
"credentials.social",
"credentials.social.service",
"credentials.social.login",
"credentials.admin",
"credentials.admin.enabled",
"server",
"server.type",
"server.options",
"server.options.os"
]
Only root keys
config.keys(only_root: true)
# the result:
['credentials', 'server']
config.root_keys
# the result:
['credentials', 'server']
Config reloading
- method signature:
#reload!(configurations = {}, &configuration)
;
# -- config example ---
class Config < Qonfig::DataSet
setting :db do
setting :adapter, 'postgresql'
end
setting :logger, Logger.new(STDOUT)
end
config = Config.new
config.settings.db.adapter # => 'postgresql'
config.settings.logger # => #<Logger:0x00007ff9>
# --- redefine some settings (or add a new one) --
config.configure { |conf| conf.logger = nil } # redefine some settings (will be reloaded)
# re-define and append settings
class Config
setting :db do
setting :adapter, 'mongoid' # re-define defaults
end
setting :enable_api, false # append new setting
end
# --- reload ---
# reload settings
config.reload!
config.settings.db.adapter # => 'mongoid'
config.settings.logger # => #<Logger:0x00007ff9> (reloaded from defaults)
config.settings.enable_api # => false (new setting)
# reload with instant configuration
config.reload!(db: { adapter: 'oracle' }) do |conf|
conf.enable_api = true # changed instantly
end
config.settings.db.adapter # => 'oracle'
config.settings.logger = # => #<Logger:0x00007ff9>
config.settings.enable_api # => true # value from instant change
Clear options
- set all config's settings to
nil
; - method signature:
#clear!
;
class Config
setting :database do
setting :user
setting :password
end
setting :web_api do
setting :endpoint
end
end
config = Config.new do |conf|
conf.database.user = '0exp'
conf.database.password = 'test123'
conf.web_api.endpoint = '/api/'
end
config.settings.database.user # => '0exp'
config.settings.database.password # => 'test123'
config.settings.web_api.endpoint # => '/api'
# clear all options
config.clear!
config.settings.database.user # => nil
config.settings.database.password # => nil
config.settings.web_api.endpoint # => nil
Frozen state
Instance-level
- method signature:
#freeze!
;
class Config < Qonfig::DataSet
setting :logger, Logger.new(STDOUT)
setting :worker, :sidekiq
setting :db do
setting :adapter, 'postgresql'
end
end
config = Config.new
config.freeze!
config.settings.logger = Logger.new(StringIO.new) # => Qonfig::FrozenSettingsError
config.settings.worker = :que # => Qonfig::FrozenSettingsError
config.settings.db.adapter = 'mongoid' # => Qonfig::FrozenSettingsError
config.reload! # => Qonfig::FrozenSettingsError
config.clear! # => Qonfig::FrozenSettingsError
Definition-level
- DSL-method signature:
freeze_state!
- indicaes that all your config instances should be frozen;
-
freeze_state!
DSL command is not inherited (your child and composed config classes will not have this declaration);
# --- base class ---
class Config < Qonfig::DataSet
setting :test, true
freeze_state!
end
config = Config.new
config.frozen? # => true
config.settings.test = false # => Qonfig::FrozenSettingsError
# --- child class ---
class InheritedConfig < Config
end
inherited_config = InheritedConfig.new
config.frozen? # => false
config.settings.test = false # ok :)
Settings as Predicates
- predicate form:
?
at the end of setting name; -
nil
andfalse
setting values indicatesfalse
; - other setting values indicates
true
; - setting roots always returns
true
;
class Config < Qonfig::DataSet
setting :database do
setting :user
setting :host, 'google.com'
setting :engine do
setting :driver, 'postgres'
end
end
end
config = Config.new
# predicates
config.settings.database.user? # => false (nil => false)
config.settings.database.host? # => true ('google.com' => true)
config.settings.database.engine.driver? # => true ('postgres' => true)
# setting roots always returns true
config.settings.database? # => true
config.settings.database.engine? # => ture
config.configure do |conf|
conf.database.user = '0exp'
conf.database.host = false
conf.database.engine.driver = true
end
# predicates
config.settings.database.user? # => true ('0exp' => true)
config.settings.database.host? # => false (false => false)
config.settings.database.engine.driver? # => true (true => true)
Setting key existence
- supports dynamic array-like format and canonical dot-notation format;
- returns
true
if the concrete key is exist; - returns
false
if the concrete key does not exist; -
dynamic array-like format:
-
#key?(*key_path)
/#option?(*key_path)
/#setting?(*key_path)
; -
*key_path
- an array of symbols and strings that represents a path to the concrete setting key; - (for example,
config.key?(:credentials, :user)
tries to check thatconfig.settings.credentials.user
is exist);
-
-
dot-notation format:
-
#key?(key)
/#option?(key)
/#setting?(key)
; -
key
- string in dot-notated format - (for example:
config.key?('credentials.user')
tries to check thatconfig.settings.crednetials.user
is exist);
-
class Config < Qonfig::DataSet
setting :credentials do
setting :user, 'D@iVeR'
setting :password, 'test123'
end
end
config = Config.new
# --- array-like format ---
config.key?('credentials', 'user') # => true
config.key?('credentials', 'token') # => false (key does not exist)
# --- dot-notation format ---
config.key?('credentials.user') # => true
config.key?('credentials.token') # => false (key does not exist)
config.key?('credentials') # => true
config.key?('que_adapter') # => false (key does not exist)
# aliases
config.setting?('credentials') # => true
config.option?(:credentials, :password) # => true
config.option?('credentials.password') # => true
Run arbitrary code with temporary settings
- provides a way to run an arbitrary code with temporarily specified settings;
- your arbitrary code can temporary change any setting too - all settings will be returned to the original state;
- (it is convenient to run code samples by this way in tests (with substitued configs));
- it is fully thread-safe
:)
;
class Config < Qonfig::DataSet
setting :queue do
setting :adapter, :sidekiq
setting :options, {}
end
end
config = Config.new
# run a block of code with temporary queue.adapter setting
config.with(queue: { adapter: 'que' }) do
# your changed settings
config.settings.queue.adapter # => 'que'
# you can temporary change settings by your code too
config.settings.queue.options = { concurrency: 10 }
# ...your another code...
end
# original settings has not changed :)
config.settings.queue.adapter # => :sidekiq
config.settings.queue.options # => {}
Import settings / Export settings
- Import config settings (
as instance methods
) - Export config settings (
as singleton methods
)
Sometimes the nesting of configs in your project is quite high, and it makes you write the rather "cumbersome" code
(config.settings.web_api.credentials.account.auth_token
for example). Frequent access to configs in this way is inconvinient - so developers wraps
such code by methods or variables. In order to make developer's life easer Qonfig
provides a special Import API simplifies the config importing
(gives you .import_settings
DSL) and gives an ability to instant config setting export from a config object (gives you #export_settings
config's method).
You can use RabbitMQ-like pattern matching in setting key names:
- if the setting key name at the current nesting level does not matter - use
*
; - if both the setting key name and nesting level does not matter - use
#
- examples:
-
db.settings.user
- matches todb.settings.user
setting; -
db.settings.*
- matches to all setting keys insidedb.settings
group of settings; -
db.*.user
- matches to alluser
setting keys at the first level ofdb
group of settings; -
#.user
- matches to alluser
setting keys; -
service.#.password
- matches to allpassword
setting keys at all levels ofservice
group of settings; -
#
- matches to ALL setting keys; -
*
- matches to all setting keys at the root level; - and etc;
-
Import config settings
-
Qonfig::Imports
- a special mixin that provides the convenient DSL to work with config import features (.import_settings
method); -
.import_settings
- DSL method for importing configuration settings (from a config instance) as instance methods of a class; - (IMPORTANT)
import_settings
imports config settings as access methods to config's settings (createsattr_reader
s for your config); - generated methods can be used as predicates (with trailing
?
symbol); - you can generate
attr_accessor
s by specifyingaccessor: true
option (be careful: you can getQonfig::AmbiguousSettingValueError
when you try to assign a value to config option which have nested settings); - signature:
.import_settings(config_object, *setting_keys, mappings: {}, prefix: '', raw: false)
-
config_object
- an instance ofQonfig::DataSet
whose config settings should be imported; -
*setting_keys
- an array of dot-notaed config's setting keys that should be imported (dot-notaed key is a key that describes each part of nested setting key as a string separated bydot
-symbol);- last part of dot-notated key will become a name of the setting access instance method;
-
mappings:
- a map of keys that describes custom method names for each imported setting; -
prefix:
- prexifies setting access method name with custom prefix; -
raw:
- use nested settings as objects or hashify them (false
by default (means "hashify nested settings")); -
accessor:
- generateattr_accessor
for imported config settigns (false
by default (means "generateattr_reader
s only"));
-
Suppose we have a config with deeply nested keys:
# NOTE: (Qonfig::DataSet.build creates a class and instantly instantiates it)
AppConfig = Qonfig::DataSet.build do
setting :web_api do
setting :credentials do
setting :account do
setting :login, 'DaiveR'
setting :auth_token, 'IAdkoa0@()1239uA'
end
end
end
setting :graphql_api, false
end
Let's see what we can to do :)
- Import a set of setting keys (simple dot-noated key list)
- Import with custom method names (mappings)
- Prexify method name
- Import nested settings as raw Qonfig::Settings objects
- Import with pattern-matching
- Support for predicate-like methods
Import a set of setting keys (simple dot-noated key list)
- last part of dot-notated key will become a name of the setting access instance method;
class ServiceObject
include Qonfig::Imports
import_settings(AppConfig,
'web_api.credentials.account.login',
'web_api.credentials.account'
)
end
service = ServiceObject.new
service.login # => "D@iVeR"
service.account # => { "login" => "D@iVeR", "auth_token" => IAdkoa0@()1239uA" }
Import with custom method names (mappings)
-
mappings:
defines a map of keys that describes custom method names for each imported setting;
class ServiceObject
include Qonfig::Imports
import_settings(AppConfig, mappings: {
account_data: 'web_api.credentials.account', # NOTE: name access method with "account_data"
secret_token: 'web_api.credentials.account.auth_token' # NOTE: name access method with "secret_token"
})
end
service = ServiceObject.new
service.account_data # => { "login" => "D@iVeR", "auth_token" => "IAdkoa0@()1239uA" }
service.auth_token # => "IAdkoa0@()1239uA"
Prexify method name
-
prefix:
- prexifies setting access method name with custom prefix;
class ServiceObject
include Qonfig::Imports
import_settings(AppConfig,
'web_api.credentials.account',
mappings: { secret_token: 'web_api.credentials.account.auth_token' },
prefix: 'config_'
)
end
service = ServiceObject.new
service.config_account # => { login" => "D@iVeR", "auth_token" => "IAdkoa0@()1239uA" }
service.config_secret_token # => "IAdkoa0@()1239uA"
Support for predicate-like methods
- generated methods can be used as predicates (with trailing
?
symbol);
class ServiceObject
include Qonfig::Imports
import_settings(AppConfig,
'web_api.credentials.account',
mappings: { secret_token: 'web_api.credentials.account.auth_token' },
)
end
service = ServiceObject.new
service.account? # => true
service.secret_token? # => true
Import nested settings as raw Qonfig::Settings objects
-
raw: false
is used by default (hashify nested settings)
# NOTE: import nested settings as raw objects (raw: true)
class ServiceObject
include Qonfig::Imports
import_settings(AppConfig, 'web_api.credentials', raw: true)
end
service = ServiceObject.new
service.credentials # => <Qonfig::Settings:0x00007ff8>
service.credentials.account.login # => "D@iVeR"
service.credentials.account.auth_token # => "IAdkoa0@()1239uA"
# NOTE: import nested settings as converted-to-hash objects (raw: false) (default behavior)
class ServiceObject
include Qonfig::Imports
import_settings(AppConfig, 'web_api.credentials', raw: false)
end
service = ServiceObject.new
service.credentials # => { "account" => { "login" => "D@iVeR", "auth_token" => "IAdkoa0@()1239uA"} }
Import with pattern-matching
- import root keys only:
import_settings(config_object, '*')
; - import all keys:
import_settings(config_object, '#')
; - import the subset of keys:
import_settings(config_object, 'group.*.group.#')
(pattern-mathcing usage);
class ServiceObject
include Qonfig::Imports
# import all settings from web_api.credentials subset
import_settings(AppConfig, 'web_api.credentials.#')
# generated instance methods:
# => service.account
# => service.login
# => service.auth_token
# import only the root keys from web_api.credentials.account subset
import_settings(AppConfig, 'web_api.credentials.account.*')
# generated instance methods:
# => service.login
# => service.auth_token
# import only the root keys
import_settings(AppConfig, '*')
# generated instance methods:
# => service.web_api
# => service.graphql_api
# import ALL keys
import_settings(AppConfig, '#')
# generated instance methods:
# => service.web_api
# => service.credentials
# => service.account
# => service.login
# => service.auth_token
# => service.graphql_api
end
Export config settings
- works in
.import_settings
manner doc (see examples and documentation above:)
) - all config objects can export their settings to an arbitrary object as singleton methods;
- (IMPORTANT)
export_settings
exports config settings as access methods to config's settings (createsattr_reader
s for your config); - generated methods can be used as predicates (with trailing
?
symbol); - you can generate
attr_accessor
s by specifyingaccessor: true
option (be careful: you can getQonfig::AmbiguousSettingValueError
when you try to assign a value to config option which have nested settings); - signature:
#export_settings(exportable_object, *setting_keys, mappings: {}, prefix: '', raw: false)
:-
exportable_object
- an arbitrary object for exporting; -
*setting_keys
- an array of dot-notaed config's setting keys that should be exported (dot-notaed key is a key that describes each part of nested setting key as a string separated bydot
-symbol);- last part of dot-notated key will become a name of the setting access instance method;
-
mappings:
- a map of keys that describes custom method names for each exported setting; -
prefix:
- prexifies setting access method name with custom prefix; -
raw:
- use nested settings as objects or hashify them (false
by default (means "hashify nested settings"));
-
-
accessor:
- generateattr_accessor
for imported config settigns (false
by default (means "generateattr_reader
s only"));
class Config < Qonfig::DataSet
setting :web_api do
setting :credentials do
setting :account do
setting :login, 'DaiveR'
setting :auth_token, 'IAdkoa0@()1239uA'
end
end
end
setting :graphql_api, false
end
class ServiceObject; end
config = Config.new
service = ServiceObject.new
service.config_account # => NoMethodError
# NOTE: export settings as access methods to config's settings
config.export_settings(service, 'web_api.credentials.account', prefix: 'config_')
service.config_account # => { "login" => "D@iVeR", "auth_token" => "IAdkoa0@()1239uA" }
# NOTE: export settings with pattern matching
config.export_settings(service, '*') # export root settings
service.web_api # => { 'credentials' => { 'account' => { ... } }, 'graphql_api' => false }
service.graphql_api # => false
# NOTE: predicates
config.export_settings(service, '*')
config.web_api? # => true
config.graphql_api? # => false
Validation
- Introduction
- Key Search Pattern
- Proc-based validation
- Method-based validation
- Predefined validations
- Custom predefined validators
- Validation of potential setting values
Introduction
Qonfig provides a lightweight DSL for defining validations and works in all cases when setting values are initialized or mutated. Settings are validated as keys (matched with a specific string pattern). You can validate both a set of keys and each key separately. If you want to check the config object completely you can define a custom validation.
Features:
- validation is invoked on any mutation of any setting:
- during dataset instantiation;
- when assigning new values;
- when calling
#reload!
; - when calling
#clear!
;
- provides
strict
andnon-strict
behavior (strict: true
andstrict: false
respectively):-
strict: false
ignores validations for settings withnil
(allowsnil
value); -
strict: true
does not ignores validations for settings withnil
; -
strict: false
is used by default;
-
- provides a special key search pattern for matching setting key names;
- you can validate potential setting values without any assignment (documentation)
- uses the key search pattern for definging what the setting key should be validated;
- you can define your own custom validation logic and validate dataset instance completely;
- validation logic should return truthy or falsy value;
- supprots two validation techniques (proc-based (documentation) and dataset-method-based (documentation)):
-
proc-based (
setting validation
) (documentation)validate('db.user', strict: true) do |value| value.is_a?(String) end
-
proc-based (
dataset validation
) (doc)validate(strict: false) do settings.user == User[1] end
-
dataset-method-based (
setting validation
) (documentation)validate 'db.user', by: :check_user, strict: true def check_user(value) value.is_a?(String) end
-
dataset-method-based (
dataset validation
) (documentation)validate by: :check_config, strict: false def check_config settings.user == User[1] end
-
proc-based (
- provides a set of standard validations (documentation):
- DSL:
validate 'key.pattern', :predefned_validator
; - supports
strict
behavior;
- DSL:
- you can define your own predefined validators (class-related and global-related) (documentation);
Key search pattern
Key search pattern works according to the following rules:
- works in
RabbitMQ
-like key pattern ruleses; - has a string format;
- nested configs are defined by a set of keys separated by
.
-symbol; - if the setting key name at the current nesting level does not matter - use
*
; - if both the setting key name and nesting level does not matter - use
#
- examples:
-
db.settings.user
- matches todb.settings.user
setting; -
db.settings.*
- matches to all setting keys insidedb.settings
group of settings; -
db.*.user
- matches to alluser
setting keys at the first level ofdb
group of settings; -
#.user
- matches to alluser
setting keys; -
service.#.password
- matches to allpassword
setting keys at all levels ofservice
group of settings; -
#
- matches to ALL setting keys; -
*
- matches to all setting keys at the root level; - and etc;
-
Proc-based validation
- your proc should return truthy value or falsy value;
-
nil
values are ignored by default; - set
strict: true
to disablenil
ignorance (strict: false
is used by default); - how to validate setting keys:
- define proc with attribute:
validate 'your.setting.path' do |value|; end
- proc will receive setting value;
- define proc with attribute:
- how to validate dataset instance:
- define proc without setting key pattern:
validate do; end
;
- define proc without setting key pattern:
class Config < Qonfig::DataSet
setting :db do
setting :user, 'D@iVeR'
setting :password, 'test123'
end
setting :service do
setting :address, 'google.ru'
setting :protocol, 'https'
setting :creds do
seting :admin, 'D@iVeR'
end
end
setting :enabled, false
setting :token, '1a2a3a', strict: true
# validates:
# - db.password
validate 'db.password' do |value|
value.is_a?(String)
end
# validates:
# - service.address
# - service.protocol
# - service.creds.user
validate 'service.#' do |value|
value.is_a?(String)
end
# validates:
# - dataset instance
validate do # NOTE: no setting key pattern
settings.enabled == false
end
# do not ignore `nil` (strict: true)
validate(:token, strict: true) do
value.is_a?(String)
end
end
config = Config.new
config.settings.db.password = 123 # => Qonfig::ValidationError (should be a string)
config.settings.service.address = 123 # => Qonfig::ValidationError (should be a string)
config.settings.service.protocol = :http # => Qonfig::ValidationError (should be a string)
config.settings.service.creds.admin = :billikota # => Qonfig::ValidationError (should be a string)
config.settings.enabled = true # => Qonfig::ValidationError (isnt `true`)
config.settings.db.password = nil # ok, nil is ignored (non-strict behavior)
config.settings.token = nil # => Qonfig::ValidationError (nil is not ignored, strict behavior) (should be a type of string)
Method-based validation
- method should return truthy value or falsy value;
-
nil
values are ignored by default; - set
strict: true
to disablenil
ignorance (strict: false
is used by default); - how to validate setting keys:
- define validation:
validate 'db.*.user', by: :your_custom_method
; - define your method with attribute:
def your_custom_method(setting_value); end
- define validation:
- how to validate config instance
- define validation:
validate by: :your_custom_method
- define your method without attributes:
def your_custom_method; end
- define validation:
class Config < Qonfig::DataSet
setting :services do
setting :counts do
setting :google, 2
setting :rambler, 3
end
setting :minimals do
setting :google, 1
setting :rambler, 0
end
end
setting :enabled, true
setting :timeout, 12345, strict: true
# validates:
# - services.counts.google
# - services.counts.rambler
# - services.minimals.google
# - services.minimals.rambler
validate 'services.#', by: :check_presence
# validates:
# - dataset instance
validate by: :check_state # NOTE: no setting key pattern
# do not ignore `nil` (strict: true)
validate :timeout, strict: true, by: :check_timeout
def check_presence(value)
value.is_a?(Numeric) && value > 0
end
def check_state
settings.enabled.is_a?(TrueClass) || settings.enabled.is_a?(FalseClass)
end
def check_timeout(value)
value.is_a?(Numeric)
end
end
config = Config.new
config.settings.counts.google = 0 # => Qonfig::ValidationError (< 0)
config.settings.minimals.google = -1 # => Qonfig::ValidationError (< 0)
config.settings.minimals.rambler = 'no' # => Qonfig::ValidationError (should be a numeric)
config.settings.counts.rambler = nil # ok, nil is ignored (default non-strict behavior)
config.settings.enabled = nil # ok, nil is ignored (default non-strict behavior)
config.settings.timeout = nil # => Qonfig::ValidationError (nil is not ignored, strict behavior) (should be a type of numeric)
Predefined validations
- DSL:
validate 'key.pattern', :predefned_validator
-
nil
values are ignored by default; - set
strict: true
to disablenil
ignorance (strict: false
is used by default); - predefined validators:
:not_nil
:integer
:float
:numeric
:big_decimal
:array
:hash
:string
:symbol
-
:text
(string
orsymbol
) :boolean
:class
:module
:proc
class Config < Qonfig::DataSet
setting :user, 'empty'
setting :password, 'empty'
setting :service do
setting :provider, :empty
setting :protocol, :empty
setting :on_fail, -> { puts 'atata!' }
end
setting :ignorance, false
validate 'user', :string
validate 'password', :string
validate 'service.provider', :text
validate 'service.protocol', :text
validate 'service.on_fail', :proc
validate 'ignorance', :not_nil
end
config = Config.new do |conf|
conf.user = 'D@iVeR'
conf.password = 'test123'
conf.service.provider = :google
conf.service.protocol = :https
end # NOTE: all right :)
config.settings.ignorance = nil # => Qonfig::ValidationError (cant be nil)
Custom predefined validators
- DSL:
.define_validator(name, &validation) { |value| ... }
- create your own predefined validator; - class-level: define validators related only to the concrete config class;
-
global-level: define validators related to all config classes (
Qonfig::DataSet.define_validator
); - you can re-define any global and inherited validator (at class level);
- you can re-define any already registered global validator on
Qonfig::DataSet
(at global-level);
Define your own class-level validator
class Config < Qonfig::DataSet
# NOTE: definition
define_validator(:user_type) { |value| value.is_a?(User) }
setting :admin # some key
# NOTE: usage
validate :admin, :user_type
end
Define new global validator
Qonfig::DataSet.define_validator(:secured_value) do |value|
value == '***'
end
class Config < Qonfig::DataSet
setting :password
validate :password, :secured_value
end
Re-definition of existing validators in child classes
class Config < Qonfig::DataSet
# NOTE: redefine existing :text validator only in Config class
define_validator(:text) { |value| value.is_a?(String) }
# NOTE: some custom validator that can be redefined in child classes
define_validator(:user) { |value| value.is_a?(User) }
end
class SubConfig < Qonfig
define_validator(:user) { |value| value.is_a?(AdminUser) } # NOTE: redefine inherited :user validator
end
Re-definition of existing global validators
# NOTE: redefine already existing :numeric validator
Qonfig::DataSet.define_validator(:numeric) do |value|
value.is_a?(Numeric) || (value.is_a?(String) && value.match?(/\A\d+\.*\d+\z/))
end
Validation of potential setting values
- (instance-level)
#valid_with?(setting_values = {}, &configuration)
- check that current config instalce will be valid with passed configurations; - (class-level)
.valid_with?(setting_values = {}, &configuration)
- check that potential config instancess will be valid with passed configurations; - makes no assignments;
#valid_with? (instance-level)
class Config < Qonfig::DataSet
setting :enabled, false
setting :queue do
setting :adapter, 'sidekiq'
end
validate :enabled, :boolean
validate 'queue.adapter', :string
end
config = Config.new
config.valid_with?(enabled: true, queue: { adapter: 'que' }) # => true
config.valid_with?(enabled: 123) # => false (should be a type of boolean)
config.valid_with?(enabled: true, queue: { adapter: Sidekiq }) # => false (queue.adapter should be a type of string)
# do-config notation is supported too
config.valid_with?(enabled: true) do |conf|
conf.queue.adapter = :sidekiq
end
# => false (queue.adapter should be a type of string)
.valid_with? (class-level)
class Config < Qonfig::DataSet
setting :enabled, false
setting :queue do
setting :adapter, 'sidekiq'
end
validate :enabled, :boolean
validate 'queue.adapter', :string
end
Config.valid_with?(enabled: true, queue: { adapter: 'que' }) # => true
Config.valid_with?(enabled: 123) # => false (should be a type of boolean)
Config.valid_with?(enabled: true, queue: { adapter: Sidekiq }) # => false (queue.adapter should be a type of string)
# do-config notation is supported too
Config.valid_with?(enabled: true) do |config|
config.queue.adapter = :sidekiq
end
# => false (queue.adapter should be a type of string)
Work with files
-
Setting keys definition
- Load from YAML file
- Expose YAML (
Rails
-like environment-based YAML configs) - Load from JSON file
- Expose JSON (
Rails
-like environment-based JSON configs) - Load from ENV
- Load from __END__ (aka
load_from_self
) - Expose __END__ (aka
expose_self
)
-
Setting values
- Default setting values file
- Load setting values from YAML file
- Load setting values from JSON file
- Load setting values from __END__
- Load setting values from file manually
-
Daily work
- Save to JSON file (
save_to_json
) - Save to YAML file (
save_to_yaml
)
- Save to JSON file (
Load from YAML file
- supports
ERB
; -
:strict
mode (fail behaviour when the required yaml file doesnt exist):-
true
(by default) - causesQonfig::FileNotFoundError
; -
false
- do nothing, ignore current command;
-
-
:replace_on_merge
- whether the setting should be replaced on the key conflict, otherwise, it will be deep merged (default);
# travis.yml
sudo: false
language: ruby
rvm:
- ruby-head
- jruby-head
# project.yml
enable_api: false
Sidekiq/Scheduler:
enable: true
# ruby_data.yml
version: <%= RUBY_VERSION %>
platform: <%= RUBY_PLATFORM %>
class Config < Qonfig::DataSet
setting :ruby do
load_from_yaml 'ruby_data.yml'
end
setting :travis do
load_from_yaml 'travis.yml'
end
load_from_yaml 'project.yml'
end
config = Config.new
config.settings.travis.sudo # => false
config.settings.travis.language # => 'ruby'
config.settings.travis.rvm # => ['ruby-head', 'jruby-head']
config.settings.enable_api # => false
config.settings['Sidekiq/Scheduler']['enable'] #=> true
config.settings.ruby.version # => '2.5.1'
config.settings.ruby.platform # => 'x86_64-darwin17'
# --- strict mode ---
class Config < Qonfig::DataSet
setting :nonexistent_yaml do
load_from_yaml 'nonexistent_yaml.yml', strict: true # true by default
end
setting :another_key
end
Config.new # => Qonfig::FileNotFoundError
# --- non-strict mode ---
class Config < Qonfig::DataSet
settings :nonexistent_yaml do
load_from_yaml 'nonexistent_yaml.yml', strict: false
end
setting :another_key
end
Config.new.to_h # => { "nonexistent_yaml" => {}, "another_key" => nil }
Expose YAML
- load configurations from YAML file in Rails-like manner (with environments);
- works in
load_from_yaml
manner; -
via:
- how an environment will be determined:-
:file_name
- load configuration from YAML file that have an
:env
part in it's name;
- load configuration from YAML file that have an
-
:env_key
- load configuration from YAML file;
- concrete configuration should be defined in the root key with
:env
name;
-
-
env:
- your environment name (must be a type ofString
,Symbol
orNumeric
); -
strict:
- requires the existence of the file and/or key with the name of the used environment:-
true
:- file should exist;
- root key with
:env
name should exist (ifvia: :env_key
is used); - raises
Qonfig::ExposeError
if file does not contain the required env key (ifvia: :env
key is used); - raises
Qonfig::FileNotFoundError
if the required file does not exist;
-
false
:- file is not required;
- root key with
:env
name is not required (ifvia: :env_key
is used);
-
-
:replace_on_merge
- whether the setting should be replaced on the key conflict, otherwise, it will be deep merged (default);
Environment is defined as a root key of YAML file
# config/project.yml
default: &default
enable_api_mode: true
google_key: 12345
window:
width: 100
height: 100
development:
<<: *default
test:
<<: *default
sidekiq_instrumentation: false
staging:
<<: *default
google_key: 777
enable_api_mode: false
production:
google_key: asd1-39sd-55aI-O92x
enable_api_mode: true
window:
width: 50
height: 150
class Config < Qonfig::DataSet
expose_yaml 'config/project.yml', via: :env_key, env: :production # load from production env
# NOTE: in rails-like application you can use this:
expose_yaml 'config/project.yml', via: :env_key, env: Rails.env
end
config = Config.new
config.settings.enable_api_mode # => true (from :production subset of keys)
config.settings.google_key # => asd1-39sd-55aI-O92x (from :production subset of keys)
config.settings.window.width # => 50 (from :production subset of keys)
config.settings.window.height # => 150 (from :production subset of keys)
Environment is defined as a part of YAML file name
# config/sidekiq.staging.yml
web:
username: staging_admin
password: staging_password
# config/sidekiq.production.yml
web:
username: urj1o2
password: u192jd0ixz0
class SidekiqConfig < Qonfig::DataSet
# NOTE: file name should be described WITHOUT environment part (in file name attribute)
expose_yaml 'config/sidekiq.yml', via: :file_name, env: :staging # load from staging env
# NOTE: in rails-like application you can use this:
expose_yaml 'config/sidekiq.yml', via: :file_name, env: Rails.env
end
config = SidekiqConfig.new
config.settings.web.username # => staging_admin (from sidekiq.staging.yml)
config.settings.web.password # => staging_password (from sidekiq.staging.yml)
Load from JSON file
- supports
ERB
; -
:strict
mode (fail behaviour when the required yaml file doesnt exist):-
true
(by default) - causesQonfig::FileNotFoundError
; -
false
- do nothing, ignore current command;
-
-
:replace_on_merge
- whether the setting should be replaced on the key conflict, otherwise, it will be deep merged (default);
// options.json
{
"user": "0exp",
"password": 12345,
"rubySettings": {
"allowedVersions": ["2.3", "2.4.2", "1.9.8"],
"gitLink": null,
"withAdditionals": false
}
}
class Config < Qonfig::DataSet
load_from_json 'options.json'
end
config = Config.new
config.settings.user # => '0exp'
config.settings.password # => 12345
config.settings.rubySettings.allowedVersions # => ['2.3', '2.4.2', '1.9.8']
config.settings.rubySettings.gitLink # => nil
config.settings.rubySettings.withAdditionals # => false
# --- strict mode ---
class Config < Qonfig::DataSet
setting :nonexistent_json do
load_from_json 'nonexistent_json.json', strict: true # true by default
end
setting :another_key
end
Config.new # => Qonfig::FileNotFoundError
# --- non-strict mode ---
class Config < Qonfig::DataSet
settings :nonexistent_json do
load_from_json 'nonexistent_json.json', strict: false
end
setting :another_key
end
Config.new.to_h # => { "nonexistent_json" => {}, "another_key" => nil }
Expose JSON
- load configurations from JSON file in Rails-like manner (with environments);
- works in
load_from_jsom
/expose_yaml
manner; -
via:
- how an environment will be determined:-
:file_name
- load configuration from JSON file that have an
:env
part in it's name;
- load configuration from JSON file that have an
-
:env_key
- load configuration from JSON file;
- concrete configuration should be defined in the root key with
:env
name;
-
-
env:
- your environment name (must be a type ofString
,Symbol
orNumeric
); -
strict:
- requires the existence of the file and/or key with the name of the used environment:-
true
:- file should exist;
- root key with
:env
name should exist (ifvia: :env_key
is used); - raises
Qonfig::ExposeError
if file does not contain the required env key (ifvia: :env
key is used); - raises
Qonfig::FileNotFoundError
if the required file does not exist;
-
false
:- file is not required;
- root key with
:env
name is not required (ifvia: :env_key
is used);
-
-
:replace_on_merge
- whether the setting should be replaced on the key conflict, otherwise, it will be deep merged (default);
Environment is defined as a root key of JSON file
// config/project.json
{
"development": {
"api_mode_enabled": true,
"logging": false,
"db_driver": "sequel",
"throttle_requests": false,
"credentials": {}
},
"test": {
"api_mode_enabled": true,
"logging": false,
"db_driver": "in_memory",
"throttle_requests": false,
"credentials": {}
},
"staging": {
"api_mode_enabled": true,
"logging": true,
"db_driver": "active_record",
"throttle_requests": true,
"credentials": {}
},
"production": {
"api_mode_enabled": true,
"logging": true,
"db_driver": "rom",
"throttle_requests": true,
"credentials": {}
}
}
class Config < Qonfig::DataSet
expose_json 'config/project.json', via: :env_key, env: :production # load from production env
# NOTE: in rails-like application you can use this:
expose_json 'config/project.json', via: :env_key, env: Rails.env
end
config = Config.new
config.settings.api_mode_enabled # => true (from :production subset of keys)
config.settings.logging # => true (from :production subset of keys)
config.settings.db_driver # => "rom" (from :production subset of keys)
config.settings.throttle_requests # => true (from :production subset of keys)
config.settings.credentials # => {} (from :production subset of keys)
Environment is defined as a part of JSON file name
// config/sidekiq.staging.json
{
"web": {
"username": "staging_admin",
"password": "staging_password"
}
}
// config/sidekiq.production.json
{
"web": {
"username": "urj1o2",
"password": "u192jd0ixz0"
}
}
class SidekiqConfig < Qonfig::DataSet
# NOTE: file name should be described WITHOUT environment part (in file name attribute)
expose_json 'config/sidekiq.json', via: :file_name, env: :staging # load from staging env
# NOTE: in rails-like application you can use this:
expose_json 'config/sidekiq.json', via: :file_name, env: Rails.env
end
config = SidekiqConfig.new
config.settings.web.username # => "staging_admin" (from sidekiq.staging.json)
config.settings.web.password # => "staging_password" (from sidekiq.staging.json)
Load from ENV
-
:convert_values
(false
by default):-
't'
,'T'
,'true'
,'TRUE'
- covnerts totrue
; -
'f'
,'F'
,'false'
,'FALSE'
- covnerts tofalse
; -
1
,23
and etc - converts toInteger
; -
1.25
,0.26
and etc - converts toFloat
; -
1, 2, test
,FALSE,Qonfig
(strings without quotes that contains at least one comma) - converts toArray
with recursively converted values; -
'"please, test"'
,"'test, please'"
(quoted strings) - converts toString
without quotes;
-
-
:prefix
- load ENV variables which names starts with a prefix:-
nil
(by default) - empty prefix; -
Regexp
- names that match the regexp pattern; -
String
- names which starts with a passed string;
-
-
:trim_prefix
(false
by default);
# some env variables
ENV['QONFIG_BOOLEAN'] = 'true'
ENV['QONFIG_INTEGER'] = '0'
ENV['QONFIG_STRING'] = 'none'
ENV['QONFIG_ARRAY'] = '1, 2.5, t, f, TEST'
ENV['QONFIG_MESSAGE'] = '"Hello, Qonfig!"'
ENV['RUN_CI'] = '1'
class Config < Qonfig::DataSet
# nested
setting :qonfig do
load_from_env convert_values: true, prefix: 'QONFIG' # or /\Aqonfig.*\z/i
end
setting :trimmed do
load_from_env convert_values: true, prefix: 'QONFIG_', trim_prefix: true # trim prefix
end
# on the root
load_from_env
end
config = Config.new
# customized
config.settings['qonfig']['QONFIG_BOOLEAN'] # => true ('true' => true)
config.settings['qonfig']['QONFIG_INTEGER'] # => 0 ('0' => 0)
config.settings['qonfig']['QONFIG_STRING'] # => 'none'
config.settings['qonfig']['QONFIG_ARRAY'] # => [1, 2.5, true, false, 'TEST']
config.settings['qonfig']['QONFIG_MESSAGE'] # => 'Hello, Qonfig!'
config.settings['qonfig']['RUN_CI'] # => Qonfig::UnknownSettingError
# trimmed (and customized)
config.settings['trimmed']['BOOLEAN'] # => true ('true' => true)
config.settings['trimmed']['INTEGER'] # => 0 ('0' => 0)
config.settings['trimmed']['STRING'] # => 'none'
config.settings['trimmed']['ARRAY'] # => [1, 2.5, true, false, 'TEST']
config.settings['trimmed']['MESSAGE'] # => 'Hello, Qonfig!'
config.settings['trimmed']['RUN_CI'] # => Qonfig::UnknownSettingError
# default
config.settings['QONFIG_BOOLEAN'] # => 'true'
config.settings['QONFIG_INTEGER'] # => '0'
config.settings['QONFIG_STRING'] # => 'none'
config.settings['QONFIG_ARRAY'] # => '1, 2.5, t, f, TEST'
config.settings['QONFIG_MESSAGE'] # => '"Hello, Qonfig!"'
config.settings['RUN_CI'] # => '1'
Load from __END__
- aka
load_from_self
-
:format
- specify the format of data placed under the__END__
instruction:-
format: :dynamic
(default) - automatic format resolvation; -
format: :yaml
- YAML format; -
format: :json
- JSON format; -
format: :toml
- TOML format (viatoml
-plugin);
-
-
:replace_on_merge
- whether the setting should be replaced on the key conflict, otherwise, it will be deep merged (default);
class Config < Qonfig::DataSet
load_from_self # on the root (:dynamic format is used by default)
setting :nested do
load_from_self, format: :yaml # with explicitly identified YAML format
end
end
config = Config.new
# on the root
config.settings.ruby_version # => '2.5.1'
config.settings.secret_key # => 'top-mega-secret'
config.settings.api_host # => 'super.puper-google.com'
config.settings.connection_timeout.seconds # => 10
config.settings.connection_timeout.enabled # => false
# nested
config.settings.nested.ruby_version # => '2.5.1'
config.settings.nested.secret_key # => 'top-mega-secret'
config.settings.nested.api_host # => 'super.puper-google.com'
config.settings.nested.connection_timeout.seconds # => 10
config.settings.nested.connection_timeout.enabled # => false
__END__
ruby_version: <%= RUBY_VERSION %>
secret_key: top-mega-secret
api_host: super.puper-google.com
connection_timeout:
seconds: 10
enabled: false
Expose __END__
- aka
expose_self
; - works in
expose_json
andexpose_yaml
manner, but with__END__
instruction of the current file; -
env:
- your environment name (must be a type ofString
,Symbol
orNumeric
); -
:format
- specify the format of data placed under the__END__
instruction:-
format: :dynamic
(default) - automatic format resolvation; -
format: :yaml
- YAML format; -
format: :json
- JSON format; -
format: :toml
- TOML format (viatoml
-plugin);
-
-
:replace_on_merge
- whether the setting should be replaced on the key conflict, otherwise, it will be deep merged (default);
class Config < Qonfig::DataSet
expose_self env: :production, format: :yaml # with explicitly identified YAML format
# NOTE: for Rails-like applications you can use this:
expose_self env: Rails.env
end
config = Config.new
config.settings.log # => true (from :production environment)
config.settings.api_enabled # => true (from :production environment)
config.settings.creds.user # => "D@iVeR" (from :production environment)
config.settings.creds.password # => "test123" (from :production environment)
__END__
default: &default
log: false
api_enabled: true
creds:
user: admin
password: 1234
development:
<<: *default
log: true
test:
<<: *default
log: false
staging:
<<: *default
production:
<<: *default
log: true
creds:
user: D@iVeR
password: test123
Default setting values file
- defines a file that should be used for setting values initialization for your config object;
-
.values_file(file_path, format: :dynamic, strict: false, expose: nil)
-
file_path
- full file path or:self
(:self
menas "load setting values from END data"); -
:format
- defines the format of file (:dynamic
means "try to automatically infer the file format") (:dynamic
by default);- supports
:yaml
,:json
,:toml
(viaQonfig.plugin(:toml)
),:dynamic
(automatic format detection);
- supports
-
:strict
- rerquires that file (or END-data) should exist (false
by default); -
:expose
- what the environment-based subset of keys should be used (nil
means "do not use any subset of keys") (nil
by default);
-
- extra keys that does not exist in your config will cause an exception
Qonfig::SettingNotFound
respectively; - initial values will be rewritten by values defined in your file;
Default behavior
# sidekiq.yml
adapter: sidekiq
options:
processes: 10
class Config < Qonfig::DataSet
values_file 'sidekiq.yml', format: :yaml
setting :adapter, 'que'
setting :options do
setting :processes, 2
setting :threads, 5
setting :protected, false
end
end
config = Config.new
config.settings.adapter # => "sidekiq" (from sidekiq.yml)
config.settings.options.processes # => 10 (from sidekiq.yml)
config.settings.options.threads # => 5 (original value)
config.settings.options.protected # => false (original value)
Load values from __END__-data
class Config < Qonfig::DataSet
values_file :self, format: :yaml
setting :user
setting :password
setting :enabled, true
end
config = Config.new
config.settings.user # => "D@iVeR" (from __END__ data)
config.settings.password # => "test123" (from __END__ data)
config.settings.enabled # => true (original value)
__END__
user: 'D@iVeR'
password: 'test123'
Setting values with environment separation
# sidekiq.yml
development:
adapter: :in_memory
options:
threads: 10
production:
adapter: :sidekiq
options:
threads: 150
class Config < Qonfig::DataSet
values_file 'sidekiq.yml', format: :yaml, expose: :development
setting :adapter
setting :options do
setting :threads
end
end
config = Config.new
config.settings.adapter # => 'in_memory' (development keys subset)
config.settings.options.threads # => 10 (development keys subset)
File does not exist
# non-strict behavior (default)
class Config < Qonfig::DataSet
values_file 'sidekiq.yml'
end
config = Config.new # no error
# strict behavior (strict: true)
class Config < Qonfig::DataSet
values_file 'sidekiq.yml', strict: true
end
config = Config.new # => Qonfig::FileNotFoundError
Load setting values from YAML file (by instance)
- prvoides an ability to load predefined setting values from a yaml file;
-
#load_from_yaml(file_path, strict: true, expose: nil, &configurations)
-
file_path
- full file path or:self
(:self
means "load setting values from END data"); -
:strict
- rerquires that file (or END-data) should exist (true
by default); -
:expose
- what the environment-based subset of keys should be used (nil
means "do not use any subset of keys") (nil
by default); -
&configurations
-do |config|
ability :)
-
Default behavior
# config.yml
domain: google.ru
creds:
auth_token: test123
class Config < Qonfig::DataSet
seting :domain, 'test.com'
setting :creds do
setting :auth_token, 'test'
end
end
config = Config.new
config.settings.domain # => "test.com"
config.settings.creds.auth_token # => "test"
# load new values
config.load_from_yaml('config.yml')
config.settings.domain # => "google.ru" (from config.yml)
config.settings.creds.auth_token # => "test123" (from config.yml)
Load from __END__
class Config < Qonfig::DataSet
seting :domain, 'test.com'
setting :creds do
setting :auth_token, 'test'
end
end
config = Config.new
config.settings.domain # => "test.com"
config.settings.creds.auth_token # => "test"
# load new values
config.load_from_yaml(:self)
config.settings.domain # => "yandex.ru" (from __END__-data)
config.settings.creds.auth_token # => "CK0sIdA" (from __END__-data)
__END__
domain: yandex.ru
creds:
auth_token: CK0sIdA
Setting values with environment separation
# config.yml
development:
domain: dev.google.ru
creds:
auth_token: kekpek
production:
domain: google.ru
creds:
auth_token: Asod1
class Config < Qonfig::DataSet
setting :domain, 'test.com'
setting :creds do
setting :auth_token
end
end
config = Config.new
# load new values (expose development settings)
config.load_from_yaml('config.yml', expose: :development)
config.settings.domain # => "dev.google.ru" (from config.yml)
config.settings.creds.auth_token # => "kek.pek" (from config.yml)
Load setting values from JSON file (by instance)
- prvoides an ability to load predefined setting values from a json file;
-
#load_from_json(file_path, strict: true, expose: nil, &configurations)
-
file_path
- full file path or:self
(:self
means "load setting values from END data"); -
:strict
- rerquires that file (or END-data) should exist (true
by default); -
:expose
- what the environment-based subset of keys should be used (nil
means "do not use any subset of keys") (nil
by default); -
&configurations
-do |config|
ability :)
-
Default behavior
// config.json
{
"domain": "google.ru",
"creds": {
"auth_token": "test123"
}
}
class Config < Qonfig::DataSet
seting :domain, 'test.com'
setting :creds do
setting :auth_token, 'test'
end
end
config = Config.new
config.settings.domain # => "test.com"
config.settings.creds.auth_token # => "test"
# load new values
config.load_from_json('config.json')
config.settings.domain # => "google.ru" (from config.json)
config.settings.creds.auth_token # => "test123" (from config.json)
Load from __END__
class Config < Qonfig::DataSet
seting :domain, 'test.com'
setting :creds do
setting :auth_token, 'test'
end
end
config = Config.new
config.settings.domain # => "test.com"
config.settings.creds.auth_token # => "test"
# load new values
config.load_from_json(:self)
config.settings.domain # => "yandex.ru" (from __END__-data)
config.settings.creds.auth_token # => "CK0sIdA" (from __END__-data)
__END__
{
"domain": "yandex.ru",
"creds": {
"auth_token": "CK0sIdA"
}
}
Setting values with environment separation
// config.json
{
"development": {
"domain": "dev.google.ru",
"creds": {
"auth_token": "kekpek"
}
},
"production": {
"domain": "google.ru",
"creds": {
"auth_token": "Asod1"
}
}
}
class Config < Qonfig::DataSet
setting :domain, 'test.com'
setting :creds do
setting :auth_token
end
end
config = Config.new
# load new values (from development subset)
config.load_from_json('config.json', expose: :development)
config.settings.domain # => "dev.google.ru" (from config.json)
config.settings.creds.auth_token # => "kek.pek" (from config.json)
Load setting values from __END__ (by instance)
- prvoides an ability to load predefined setting values from
__END__
file section; -
#load_from_self(strict: true, expose: nil, &configurations)
-
:format
- defines the format of file (:dynamic
means "try to automatically infer the file format") (:dynamic
by default);- supports
:yaml
,:json
,:toml
(viaQonfig.plugin(:toml)
),:dynamic
(automatic format detection);
- supports
-
:strict
- requires that END-data should exist (true
by default); -
:expose
- what the environment-based subset of keys should be used (nil
means "do not use any subset of keys") (nil
by default); -
&configurations
-do |config|
ability :)
-
Default behavior
class Config < Qonfig::DataSet
setting :account, 'test'
setting :options do
setting :login, '0exp'
setting :password, 'test123'
end
end
config = Config.new
config.settings.account # => "test" (original value)
config.settings.options.login # => "0exp" (original value)
config.settings.options.password # => "test123" (original value)
# load new values
config.load_from_self(format: :yaml)
# or config.load_from_self
config.settings.account # => "real" (from __END__-data)
config.settings.options.login # => "D@iVeR" (from __END__-data)
config.settings.options.password # => "azaza123" (from __END__-data)
__END__
account: real
options:
login: D@iVeR
password: azaza123
Setting values with envvironment separation
class Config < Qonfig::DataSet
setting :domain, 'test.google.ru'
setting :options do
setting :login, 'test'
setting :password, 'test123'
end
end
config = Config.new
config.settings.domain # => "test.google.ru" (original value)
config.settings.options.login # => "test" (original value)
config.settings.options.password # => "test123" (original value)
# load new values
config.load_from_self(format: :json, expose: :production)
# or config.load_from_self(expose: production)
config.settings.domain # => "prod.google.ru" (from __END__-data)
config.settings.options.login # => "prod" (from __END__-data)
config.settings.options.password # => "prod123" (from __END__-data)
__END__
{
"development": {
"domain": "dev.google.ru",
"options": {
"login": "dev",
"password": "dev123"
}
},
"production": {
"domain": "prod.google.ru",
"options": {
"login": "prod",
"password": "prod123"
}
}
}
Load setting values from file manually (by instance)
- prvoides an ability to load predefined setting values from a file;
- works in instance-based
#load_from_yaml
/#load_from_json
/#load_from_self
manner; - signature:
#load_from_file(file_path, format: :dynamic, strict: true, expose: nil, &configurations)
:-
file_path
- full file path or:self
(:self
means "load setting values from END data"); -
:format
- defines the format of file (:dynamic
means "try to automatically infer the file format") (:dynamic
by default);- supports
:yaml
,:json
,:toml
(viaQonfig.plugin(:toml)
),:dynamic
(automatic format detection);
- supports
-
:strict
- rerquires that file (or END-data) should exist (true
by default); -
:expose
- what the environment-based subset of keys should be used (nil
means "do not use any subset of keys") (nil
by default); -
&configurations
-do |config|
ability :)
-
- see examples for instance-based
#load_from_yaml
(doc) /#load_from_json
(doc) /#load_from_self
(doc);
Save to JSON file
-
#save_to_json
- represents config object as a json structure and saves it to a file:- uses native
::JSON.generate
under the hood; - writes new file (or rewrites existing file);
- attributes:
-
:path
- (required) - file path; -
:options
- (optional) - native::JSON.generate
options (from stdlib):-
:indent
-" "
by default; -
:space
-" "
by default/ -
:object_nl
-"\n"
by default;
-
-
&value_preprocessor
- (optional) - value pre-processor;
-
- uses native
Without value preprocessing (standard usage)
class AppConfig < Qonfig::DataSet
setting :server do
setting :address, 'localhost'
setting :port, 12_345
end
setting :enabled, true
end
config = AppConfig.new
# NOTE: save to json file
config.save_to_json(path: 'config.json')
{
"sentry": {
"address": "localhost",
"port": 12345
},
"enabled": true
}
With value preprocessing and custom options
class AppConfig < Qonfig::DataSet
setting :server do
setting :address, 'localhost'
setting :port, 12_345
end
setting :enabled, true
setting :dynamic, -> { 1 + 2 }
end
config = AppConfig.new
# NOTE: save to json file with custom options (no spaces / no new line / no indent; call procs)
config.save_to_json(path: 'config.json', options: { indent: '', space: '', object_nl: '' }) do |value|
value.is_a?(Proc) ? value.call : value
end
// no spaces / no new line / no indent / calculated "dynamic" setting key
{"sentry":{"address":"localhost","port":12345},"enabled":true,"dynamic":3}
Save to YAML file
-
#save_to_yaml
- represents config object as a yaml structure and saves it to a file:- uses native
::Psych.dump
under the hood; - writes new file (or rewrites existing file);
- attributes:
-
:path
- (required) - file path; -
:options
- (optional) - native::Psych.dump
options (from stdlib):-
:indentation
-2
by default; -
:line_width
--1
by default; -
:canonical
-false
by default; -
:header
-false
by default; -
:symbolize_keys
- (non-native option) -false
by default;
-
-
&value_preprocessor
- (optional) - value pre-processor;
-
- uses native
Without value preprocessing (standard usage)
class AppConfig < Qonfig::DataSet
setting :server do
setting :address, 'localhost'
setting :port, 12_345
end
setting :enabled, true
end
config = AppConfig.new
# NOTE: save to yaml file
config.save_to_yaml(path: 'config.yml')
---
server:
address: localhost
port: 12345
enabled: true
With value preprocessing and custom options
class AppConfig < Qonfig::DataSet
setting :server do
setting :address, 'localhost'
setting :port, 12_345
end
setting :enabled, true
setting :dynamic, -> { 5 + 5 }
end
config = AppConfig.new
# NOTE: save to yaml file with custom options (add yaml version header; call procs)
config.save_to_yaml(path: 'config.yml', options: { header: true }) do |value|
value.is_a?(Proc) ? value.call : value
end
# yaml version header / calculated "dynamic" setting key
%YAML 1.1
---
server:
address: localhost
port: 12345
enabled: true
dynamic: 10
Plugins
- toml (provides
load_from_toml
,save_to_toml
,expose_toml
); - pretty_print (beautified/prettified console output);
- vault (provides
load_from_vault
,expose_vault
)
Usage
- show available plugins:
Qonfig.plugins # => ["pretty_print", "toml", ..., ...]
- load specific plugin:
Qonfig.plugin(:pretty_print) # or Qonfig.plugin('pretty_print')
# -- or --
Qonfig.enable(:pretty_print) # or Qonfig.enable('pretty_print')
# -- or --
Qonfig.load(:pretty_print) # or Qonfig.load('pretty_print')
- show loaded plugins:
Qonfig.loaded_plugins # => ["pretty_print"]
# -- or --
Qonfig.enabled_plugins # => ["pretty_print"]
Plugins: toml
Qonfig.plugin(:toml)
- adds support for
toml
format (specification); - depends on
toml-rb
gem (link) (tested on>= 2.0
); - supports TOML
0.5.0
format (dependency lock) (toml-rb >= 2.0
); - provides
.load_from_toml
(works in.load_from_yaml
manner (doc)); - provides
.expose_toml
(works in.expose_yaml
manner (doc)); - provides
#save_to_toml
(works in#save_to_yaml
manner (doc)) (toml-rb
has no native options); - provides
format: :toml
for.values_file
(doc); - provides
#load_from_toml
(work in#load_from_yaml
manner (doc));
# 1) require external dependency
require 'toml-rb'
# 2) enable plugin
Qonfig.plugin(:toml)
# 3) use toml :)
Plugins: pretty_print
Qonfig.plugin(:pretty_print)
- gives you really comfortable and beautiful console output;
- represents all setting keys in dot-notation format;
Example:
class Config < Qonfig::DataSet
setting :api do
setting :domain, 'google.ru'
setting :creds do
setting :account, 'D@iVeR'
setting :password, 'test123'
end
end
setting :log_requests, true
setting :use_proxy, true
end
config = Config.new
- before:
=> #<Config:0x00007f9b6c01dab0
@__lock__=
#<Qonfig::DataSet::Lock:0x00007f9b6c01da60
@access_lock=#<Thread::Mutex:0x00007f9b6c01da38>,
@arbitary_lock=#<Thread::Mutex:0x00007f9b6c01d9e8>,
@definition_lock=#<Thread::Mutex:0x00007f9b6c01da10>>,
@settings=
#<Qonfig::Settings:0x00007f9b6c01d858
@__lock__=
#<Qonfig::Settings::Lock:0x00007f9b6c01d808
@access_lock=#<Thread::Mutex:0x00007f9b6c01d7b8>,
@definition_lock=#<Thread::Mutex:0x00007f9b6c01d7e0>,
@merge_lock=#<Thread::Mutex:0x00007f9b6c01d790>>,
@__mutation_callbacks__=
#<Qonfig::Settings::Callbacks:0x00007f9b6c01d8d0
@callbacks=[#<Proc:0x00007f9b6c01d8f8@/Users/daiver/Projects/qonfig/lib/qonfig/settings/builder.rb:39>],
@lock=#<Thread::Mutex:0x00007f9b6c01d880>>,
@__options__=
{"api"=>
#<Qonfig::Settings:0x00007f9b6c01d498
# ... and etc
- after:
=> #<Config:0x00007f9b6c01dab0
api.domain: "google.ru",
api.creds.account: "D@iVeR",
api.creds.password: "test123",
log_requests: true,
use_proxy: true>
# -- or --
=> #<Config:0x00007f9b6c01dab0 api.domain: "google.ru", api.creds.account: "D@iVeR", api.creds.password: "test123", log_requests: true, use_proxy: true>
Plugins: vault
Qonfig.plugin(:vault)
- adds support for
vault kv store
, more info - depends on
vault
gem (link) (tested on>= 0.1
); - provides
.load_from_vault
(works in.load_from_yaml
manner (doc)); - provides
.expose_vault
(works in.expose_yaml
manner (doc));
# 1) require external dependency
require 'vault'
# 2) Setup vault client
Vault.address = 'http://localhost:8200'
Vault.token = 'super-duper-token-here'
# 3) enable plugin
Qonfig.plugin(:vault)
# 3) use vault :)
Roadmap
-
General:
- documentation rework;
-
Major:
- support for Rails-like secrets;
- support for persistent data storages (we want to store configs in multiple databases and files);
- rails plugin;
- support for pattern matching;
- support for type checking (via
rbs
,typeprof
,steep
); - console utilities;
-
Minor:
- An ability to flag
Qonfig::Configurable
's config object ascompacted
(Qonfig::Compacted
); - Instance-based behavior for
Vault
plugin, also use instance ofVault
client instead ofSingleton
; - External validation class with an importing api for better custom validations;
- Setting value changement trace (in
anyway_config
manner); - Instantiation and reloading callbacks;
- File geneartors (.rb-files with a pre-filled code (and (maybe) with a pre-generated yaml/json/etc files));
- Setting value changement subscriptions and callbacks;
- CI rework (new configs, new steps, no strange dependencies);
- CI: thread-safety specs for
thruffleruby
;
- An ability to flag
Build
bin/rspec -w # test the core functionality and plugins
bin/rspec -n # test only the core functionality
Contributing
- Fork it ( https://github.com/0exp/qonfig/fork )
- Create your feature branch (
git checkout -b feature/my-new-feature
) - Commit your changes (
git commit -am '[my-new-featre] Add some feature'
) - Push to the branch (
git push origin feature/my-new-feature
) - Create new Pull Request
License
Released under MIT License.