
Security News
New Website “Is It Really FOSS?” Tracks Transparency in Open Source Distribution Models
A new site reviews software projects to reveal if they’re truly FOSS, making complex licensing and distribution models easy to understand.
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.
# 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** :)
gem 'qonfig'
$ bundle install
# --- or ---
$ gem install 'qonfig'
require 'qonfig'
Qonfig::Configurable
)Qonfig::DataSet.build(&definitions)
)#each_setting
, #deep_each_setting
)#keys
, #root_keys
)nil
).freeze_state!
, #freeze!
, #frozen?
)#key?
/#option?
/#setting?
)#with(configs = {}, &arbitrary_code)
)as instance methods
)
as singleton methods
)(DSL methods for dynamic setting keys definition by reading them from a file)
Rails
-like environment-based YAML configs)Rails
-like environment-based JSON configs).load_from_self
).expose_self
)(instance methods for loading the setting values from a file to existing config object with already defined setting keys)
#save_to_json
)#save_to_yaml
)TOML
format)Vault
store)Qonfig::Configurable
)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);# --- 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
# 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
# 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
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'
# 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)
config.dig('vendor_api.domain') # => 'app.service.com' # (key exists)
config.dig('vendor_api.login') # => Qonfig::UnknownSettingError # (key does not exist)
# 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)
config.slice('vendor_api.login') # => { 'loign' => 'test_user' }
config.slice('vendor_api.port') # => Qonfig::UnknownSettingError # (key does not exist)
# 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)
config.slice_value('vendor_api.login') # => 'test_user'
config.slice_value('vendor_api.port') # => Qonfig::UnknownSettingError # (key does not exist)
# - 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' }
config.subset('project_id', 'vendor_api.domain', 'credentials.user.login')
# => { 'project_id' => nil, 'domain' => 'app.service.com', 'login' => 'D@iVeR' }
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
config.configure do |conf|
conf.enable_middlewares = true
conf.geo_api.provider = :yandex_maps
conf.testing.engine = :mini_test
end
config.settings.enable_middlewares = false
config.settings.geo_api.provider = :apple_maps
config.settings.testing.engine = :ultra_test
config.settings[:enable_middlewares] = true
config.settings[:geo_api][:provider] = :rambler_maps
config.settings[:testing][:engine] = :mega_test
config = Config.new do |conf|
conf.enable_middlewares = false
conf.geo_api.provider = :amazon_maps
conf.testing.engine = :crypto_test
end
config = Config.new(
testing: { engine: :mini_test, parallel: false },
geo_api: { provider: :rambler_maps },
enable_middlewares: true
)
config.configure(enable_middlewares: false)
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
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
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'
#to_h
and #to_hash
;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
Config.new.to_h
# =>
{
"serializers": {
"json" => { "engine" => :ok },
"hash" => { "engine" => :native },
},
"adapter" => { "default" => :memory_sync },
"logger" => #<Logger:0x4b0d79fc>
}
key_transformer
and/or value_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>??"
}
key_transformer
and value_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>$$",
}
.configuration
- settings definitions;.configure
- configuration;.config
- config object;#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)
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'
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"
Qonfig::Compacted
: represents the compacted config object with setting readers, setting writers and setting predicates only - and no any other useful instance-based functionality:#settings
invokation does not need);[]
,[]=
);Qonfig::DataSet
definition DSL commands:
.valid_with?
documentation;Qonfig::DataSet#compacted
or Qonfig::Compacted.build_from(config, &configuration)
;Qonfig::DataSet
class: Qonfig::DataSet.build_compacted
;Qonfig::Compacted.new(settings_values = {}, &configuration)
;Qonfig::Compacted.build(&dsl_commands)
;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
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
Qonfig::DataSet#compacted
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
config = Qonfig::Compacted.build do
setting :api, 'google.ru'
setting :enabled, true
end
config.api # => 'google.ru'
config.enabled # => true
# 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)
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
# 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
# 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
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
#each_setting
, #deep_each_setting
)#keys
, #root_keys
)nil
).freeze_state!
, #freeze!
, #frozen?
)#key?
/#option?
/#setting?
)#each_setting { |key, value| }
#deep_each_setting(yield_all: false) { |key, value| }
.
-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
config.each_setting { |key, value| { key => value } }
# result of each step:
{ 'db' => <Qonfig::Settings:0x00007ff8> }
{ 'telegraf_url' => 'udp://localhost:8094' }
{ 'telegraf_prefix' => 'test' }
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' }
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' }
#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
config.keys
# the result:
[
"credentials.social.service",
"credentials.social.login",
"credentials.admin.enabled",
"server.type",
"server.options.os"
]
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"
]
config.keys(only_root: true)
# the result:
['credentials', 'server']
config.root_keys
# the result:
['credentials', 'server']
#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
nil
;#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
#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
freeze_state!
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 :)
?
at the end of setting name;nil
and false
setting values indicates false
;true
;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)
true
if the concrete key is exist;false
if the concrete key does not exist;#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;config.key?(:credentials, :user)
tries to check that config.settings.credentials.user
is exist);#key?(key)
/ #option?(key)
/ #setting?(key)
;key
- string in dot-notated formatconfig.key?('credentials.user')
tries to check that config.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
:)
;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 # => {}
as instance methods
)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:
*
;#
db.settings.user
- matches to db.settings.user
setting;db.settings.*
- matches to all setting keys inside db.settings
group of settings;db.*.user
- matches to all user
setting keys at the first level of db
group of settings;#.user
- matches to all user
setting keys;service.#.password
- matches to all password
setting keys at all levels of service
group of settings;#
- matches to ALL setting keys;*
- matches to all setting keys at the root level;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;import_settings
imports config settings as access methods to config's settings (creates attr_reader
s for your config);?
symbol);attr_accessor
s by specifying accessor: true
option
(be careful: you can get Qonfig::AmbiguousSettingValueError
when you try to assign a value to config option which have nested settings);.import_settings(config_object, *setting_keys, mappings: {}, prefix: '', raw: false)
config_object
- an instance of Qonfig::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 by dot
-symbol);
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:
- generate attr_accessor
for imported config settigns (false
by default (means "generate attr_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 :)
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" }
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"
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"
?
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
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_settings(config_object, '*')
;import_settings(config_object, '#')
;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
.import_settings
manner doc (see examples and documentation above :)
)export_settings
exports config settings as access methods to config's settings (creates attr_reader
s for your config);?
symbol);attr_accessor
s by specifying accessor: true
option
(be careful: you can get Qonfig::AmbiguousSettingValueError
when you try to assign a value to config option which have nested settings);#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 by dot
-symbol);
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:
- generate attr_accessor
for imported config settigns (false
by default (means "generate attr_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
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:
#reload!
;#clear!
;strict
and non-strict
behavior (strict: true
and strict: false
respectively):
strict: false
ignores validations for settings with nil
(allows nil
value);strict: true
does not ignores validations for settings with nil
;strict: false
is used by default;setting validation
) (documentation)
validate('db.user', strict: true) do |value|
value.is_a?(String)
end
dataset validation
) (doc)
validate(strict: false) do
settings.user == User[1]
end
setting validation
) (documentation)
validate 'db.user', by: :check_user, strict: true
def check_user(value)
value.is_a?(String)
end
dataset validation
) (documentation)
validate by: :check_config, strict: false
def check_config
settings.user == User[1]
end
validate 'key.pattern', :predefned_validator
;strict
behavior;Key search pattern works according to the following rules:
RabbitMQ
-like key pattern ruleses;.
-symbol;*
;#
db.settings.user
- matches to db.settings.user
setting;db.settings.*
- matches to all setting keys inside db.settings
group of settings;db.*.user
- matches to all user
setting keys at the first level of db
group of settings;#.user
- matches to all user
setting keys;service.#.password
- matches to all password
setting keys at all levels of service
group of settings;#
- matches to ALL setting keys;*
- matches to all setting keys at the root level;nil
values are ignored by default;strict: true
to disable nil
ignorance (strict: false
is used by default);validate 'your.setting.path' do |value|; end
validate do; end
;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)
nil
values are ignored by default;strict: true
to disable nil
ignorance (strict: false
is used by default);validate 'db.*.user', by: :your_custom_method
;def your_custom_method(setting_value); end
validate by: :your_custom_method
def your_custom_method; end
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)
validate 'key.pattern', :predefned_validator
nil
values are ignored by default;strict: true
to disable nil
ignorance (strict: false
is used by default);:not_nil
:integer
:float
:numeric
:big_decimal
:array
:hash
:string
:symbol
:text
(string
or symbol
):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)
.define_validator(name, &validation) { |value| ... }
- create your own predefined validator;Qonfig::DataSet.define_validator
);Qonfig::DataSet
(at global-level);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
Qonfig::DataSet.define_validator(:secured_value) do |value|
value == '***'
end
class Config < Qonfig::DataSet
setting :password
validate :password, :secured_value
end
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
# 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
#valid_with?(setting_values = {}, &configuration)
- check that current config instalce will be valid with passed configurations;.valid_with?(setting_values = {}, &configuration)
- check that potential config instancess will be valid with passed configurations;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)
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)
Rails
-like environment-based YAML configs)Rails
-like environment-based JSON configs)load_from_self
)expose_self
)save_to_json
)save_to_yaml
)ERB
;:strict
mode (fail behaviour when the required yaml file doesnt exist):
true
(by default) - causes Qonfig::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 }
load_from_yaml
manner;via:
- how an environment will be determined:
:file_name
:env
part in it's name;:env_key
:env
name;env:
- your environment name (must be a type of String
, Symbol
or Numeric
);strict:
- requires the existence of the file and/or key with the name of the used environment:
true
:
:env
name should exist (if via: :env_key
is used);Qonfig::ExposeError
if file does not contain the required env key (if via: :env
key is used);Qonfig::FileNotFoundError
if the required file does not exist;false
:
:env
name is not required (if via: :env_key
is used);:replace_on_merge
- whether the setting should be replaced on the key conflict, otherwise, it will be deep merged (default);# 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)
# 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)
ERB
;:strict
mode (fail behaviour when the required yaml file doesnt exist):
true
(by default) - causes Qonfig::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 }
load_from_jsom
/expose_yaml
manner;via:
- how an environment will be determined:
:file_name
:env
part in it's name;:env_key
:env
name;env:
- your environment name (must be a type of String
, Symbol
or Numeric
);strict:
- requires the existence of the file and/or key with the name of the used environment:
true
:
:env
name should exist (if via: :env_key
is used);Qonfig::ExposeError
if file does not contain the required env key (if via: :env
key is used);Qonfig::FileNotFoundError
if the required file does not exist;false
:
:env
name is not required (if via: :env_key
is used);:replace_on_merge
- whether the setting should be replaced on the key conflict, otherwise, it will be deep merged (default);// 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)
// 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)
:convert_values
(false
by default):
't'
, 'T'
, 'true'
, 'TRUE'
- covnerts to true
;'f'
, 'F'
, 'false'
, 'FALSE'
- covnerts to false
;1
, 23
and etc - converts to Integer
;1.25
, 0.26
and etc - converts to Float
;1, 2, test
, FALSE,Qonfig
(strings without quotes that contains at least one comma) -
converts to Array
with recursively converted values;'"please, test"'
, "'test, please'"
(quoted strings) - converts to String
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_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 (via toml
-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_self
;expose_json
and expose_yaml
manner, but with __END__
instruction of the current file;env:
- your environment name (must be a type of String
, Symbol
or Numeric
);: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 (via toml
-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
.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);
:yaml
, :json
, :toml
(via Qonfig.plugin(:toml)
), :dynamic
(automatic format detection);: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);Qonfig::SettingNotFound
respectively;# 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)
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'
# 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)
# 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_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 :)# 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)
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
# 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_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 :)// 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)
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"
}
}
// 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)
__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);
:yaml
, :json
, :toml
(via Qonfig.plugin(:toml)
), :dynamic
(automatic format detection);: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 :)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
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_from_yaml
/ #load_from_json
/ #load_from_self
manner;#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);
:yaml
, :json
, :toml
(via Qonfig.plugin(:toml)
), :dynamic
(automatic format detection);: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 :)#load_from_yaml
(doc) / #load_from_json
(doc) / #load_from_self
(doc);#save_to_json
- represents config object as a json structure and saves it to a file:
::JSON.generate
under the hood;: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;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
}
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
- represents config object as a yaml structure and saves it to a file:
::Psych.dump
under the hood;: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;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
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
load_from_toml
, save_to_toml
, expose_toml
);load_from_vault
, expose_vault
)Qonfig.plugins # => ["pretty_print", "toml", ..., ...]
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')
Qonfig.loaded_plugins # => ["pretty_print"]
# -- or --
Qonfig.enabled_plugins # => ["pretty_print"]
Qonfig.plugin(:toml)
toml
format (specification);toml-rb
gem (link) (tested on >= 2.0
);0.5.0
format (dependency lock) (toml-rb >= 2.0
);.load_from_toml
(works in .load_from_yaml
manner (doc));.expose_toml
(works in .expose_yaml
manner (doc));#save_to_toml
(works in #save_to_yaml
manner (doc)) (toml-rb
has no native options);format: :toml
for .values_file
(doc);#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 :)
Qonfig.plugin(:pretty_print)
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
=> #<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
=> #<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>
Qonfig.plugin(:vault)
vault kv store
, more infovault
gem (link) (tested on >= 0.1
);.load_from_vault
(works in .load_from_yaml
manner (doc));.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 :)
rbs
, typeprof
, steep
);Qonfig::Configurable
's config object as compacted
(Qonfig::Compacted
);Vault
plugin, also use instance of Vault
client instead of Singleton
;anyway_config
manner);bin/rspec -w # test the core functionality and plugins
bin/rspec -n # test only the core functionality
git checkout -b feature/my-new-feature
)git commit -am '[my-new-featre] Add some feature'
)git push origin feature/my-new-feature
)Released under MIT License.
FAQs
Unknown package
We found that qonfig demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
A new site reviews software projects to reveal if they’re truly FOSS, making complex licensing and distribution models easy to understand.
Security News
Astral unveils pyx, a Python-native package registry in beta, designed to speed installs, enhance security, and integrate deeply with uv.
Security News
The Latio podcast explores how static and runtime reachability help teams prioritize exploitable vulnerabilities and streamline AppSec workflows.