Config. Defined as a class. Used as an instance. Support for inheritance and composition. Lazy instantiation. Thread-safe. Command-style DSL. Extremely simple to define. Extremely simple to use. That's all.
gem 'qonfig'
$ bundle install
# --- or ---
$ gem install 'qonfig'
require 'qonfig'
- Definition and Access
- Configuration
- Inheritance
- Composition
- Hash representation
- Config reloading (reload config definitions and option values)
- Clear options (set to nil)
- State freeze
- Settings as Predicates
- Load from YAML file
- Load from JSON file
- Load from ENV
- Load from __END__ (aka
load_from_self
) - Smart Mixin (
Qonfig::Configurable
) - Plugins
class Config < Qonfig::DataSet
# nil by default
setting :project_id
# nested setting
setting :vendor_api do
setting :host, 'app.service.com'
end
setting :enable_graphql, false
# nested setting reopening
setting :vendor_api do
setting :user, 'test_user'
end
end
config = Config.new
# get option value via method
config.settings.project_id # => nil
config.settings.vendor_api.host # => 'app.service.com'
config.settings.vendor_api.user # => '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][:host] # => 'app.service.com'
config.settings[:vendor_api][:user] # => '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']['host'] # => 'app.service.com'
config.settings['vendor_api']['user'] # => 'test_user'
config.settings['enable_graphql'] # => false
# 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
# get option value in Hash#dig manner (and fail when the required key does not exist)
config.dig(:vendor_api, :host) # => 'app.service.com' # (key exists)
config.dig(:vendor_api, :port) # => Qonfig::UnknownSettingError # (key does not exist)
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
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'
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>
}
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>
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 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
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
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
- 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)
:strict
mode (fail behaviour when the required yaml file doesnt exist):true
(by default) - causesQonfig::FileNotFoundError
;false
- do nothing, ignore current command;
<!-- 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 }
:strict
mode (fail behaviour when the required yaml file doesnt exist):true
(by default) - causesQonfig::FileNotFoundError
;false
- do nothing, ignore current command;
<!-- 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 }
: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'
- aka
load_from_self
class Config < Qonfig::DataSet
load_from_self # on the root
setting :nested do
load_from_self # nested
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
- 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)
# --- show names of registered plugins ---
Qonfig.plugins # => array of strings
# --- load specific plugin ---
Qonfig.plugin(:plugin_name) # or Qonfig.plugin('plugin_name')
- Fork it ( https://github.com/0exp/qonfig/fork )
- Create your feature branch (
git checkout -b feature/my-new-feature development
) - Commit your changes (
git commit -am 'Add some feature'
) - Push to the branch (
git push origin feature/my-new-feature
) - Create new Pull Request
Released under MIT License.