README.md in qonfig-0.19.1 vs README.md in qonfig-0.20.0

- old
+ new

@@ -58,10 +58,11 @@ - [Introduction](#introduction) - [Key search pattern](#key-search-pattern) - [Proc-based validation](#proc-based-validation) - [Method-based validation](#method-based-validation) - [Predefined validations](#predefined-validations) + - [Custom predefined validators](#custom-predefined-validators) - [Validation of potential setting values](#validation-of-potential-setting-values) - [Work with files](#work-with-files) - **Setting keys definition** - [Load from YAML file](#load-from-yaml-file) - [Expose YAML](#expose-yaml) (`Rails`-like environment-based YAML configs) @@ -96,28 +97,40 @@ --- ### Definition and Access +- `setting(name, value)` - define setting with corresponding name and value; +- `setting(name) { setting(name, value); ... }` - define nested settings OR reopen existing nested setting and define some new nested settings; +- `re_setting(name, value)`, `re_setting(name) { ... }` - re-define existing setting (or define new if the original does not exist); +- accessing: [access via method](#access-via-method), [access via index-method \[\]](#access-via-index-method-), + [.dig](#dig), [.slice](#slice), [.slice_value](#slice_value), [.subset](#subset); + ```ruby # --- definition --- class Config < Qonfig::DataSet # nil by default setting :project_id # nested setting setting :vendor_api do - setting :host, 'app.service.com' + setting :host, 'vendor.service.com' end setting :enable_graphql, false # nested setting reopening setting :vendor_api do - setting :user, 'test_user' + 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' @@ -131,30 +144,30 @@ #### access via method ```ruby # 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.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: ```ruby # 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[: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']['host'] # => 'app.service.com' -config.settings['vendor_api']['user'] # => 'test_user' +config.settings['vendor_api']['domain'] # => 'app.service.com' +config.settings['vendor_api']['login'] # => 'test_user' config.settings['enable_graphql'] # => false # get option value directly via index (with indifferent access) config['project_id'] # => nil config['enable_graphql'] # => false @@ -163,50 +176,50 @@ ``` - with dot-notation: ```ruby -config.settings['vendor_api.host'] # => 'app.service.com' -config.settings['vendor_api.user'] # => 'test_user' +config.settings['vendor_api.domain'] # => 'app.service.com' +config.settings['vendor_api.login'] # => 'test_user' -config['vendor_api.host'] # => 'app.service.com' -config['vendor_api.user'] # => 'test_user' +config['vendor_api.domain'] # => 'app.service.com' +config['vendor_api.login'] # => 'test_user' ``` #### .dig - without dot-notation: ```ruby # 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) +config.dig(:vendor_api, :domain) # => 'app.service.com' # (key exists) +config.dig(:vendor_api, :login) # => Qonfig::UnknownSettingError # (key does not exist) ``` - with dot-notation: ```ruby -config.dig('vendor_api.host') # => 'app.service.com' # (key exists) -config.dig('vendor_api.port') # => 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) ``` #### .slice - without dot-notation: ```ruby # get a hash slice of setting options (and fail when the required key does not exist); -config.slice(:vendor_api) # => { 'vendor_api' => { 'host' => 'app_service', 'user' => 'test_user' } } -config.slice(:vendor_api, :user) # => { 'user' => 'test_user' } +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: ```ruby -config.slice('vendor_api.user') # => { 'user' => 'test_user' } +config.slice('vendor_api.login') # => { 'loign' => 'test_user' } config.slice('vendor_api.port') # => Qonfig::UnknownSettingError # (key does not exist) ``` #### .slice_value @@ -215,20 +228,20 @@ ```ruby # 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) # => { 'host' => 'app_service', 'user' => 'test_user' } -config.slice_value(:vendor_api, :user) # => 'test_user' +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: ```ruby -config.slice_value('vendor_api.user') # => 'test_user' +config.slice_value('vendor_api.login') # => 'test_user' config.slice_value('vendor_api.port') # => Qonfig::UnknownSettingError # (key does not exist) ``` #### .subset @@ -237,21 +250,21 @@ ```ruby # - 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.subet(:vendor_api, :enable_graphql) -# => { 'vendor_api' => { 'user' => ..., 'host' => ... }, 'enable_graphql' => false } +# => { 'vendor_api' => { 'login' => ..., 'domain' => ... }, 'enable_graphql' => false } -config.subset(:project_id, [:vendor_api, :host], [:credentials, :user, :login]) -# => { 'project_id' => nil, 'host' => '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' } ``` - with dot-notation: ```ruby -config.subset('project_id', 'vendor_api.host', 'credentials.user.login') -# => { 'project_id' => nil, 'host' => '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' } ``` --- ### Configuration @@ -1022,10 +1035,23 @@ 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 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; + - and etc; + --- ### Import config settings - `Qonfig::Imports` - a special mixin that provides the convenient DSL to work with config import features (`.import_settings` method); @@ -1053,10 +1079,12 @@ setting :login, 'DaiveR' setting :auth_token, 'IAdkoa0@()1239uA' end end end + + setting :graphql_api, false end ``` Let's see what we can to do :) @@ -1151,10 +1179,51 @@ service = ServiceObject.new service.credentials # => { "account" => { "login" => "D@iVeR", "auth_token" => "IAdkoa0@()1239uA"} } ``` +#### Immport 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); + +```ruby +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 - all config objects can export their settings to an arbitrary object as singleton methods; @@ -1177,10 +1246,12 @@ setting :login, 'DaiveR' setting :auth_token, 'IAdkoa0@()1239uA' end end end + + setting :graphql_api, false end class ServiceObject; end config = Config.new @@ -1190,10 +1261,16 @@ # 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 ``` --- ## Validation @@ -1201,10 +1278,11 @@ - [Introduction](#introduction) - [Key Search Pattern](#key-search-pattern) - [Proc-based validation](#proc-based-validation) - [Method-based validation](#method-based-validation) - [Predefined validations](#predefined-validations) +- [Custom predefined validators](#custom-predefined-validators) - [Validation of potential setting values](#validation-of-potential-setting-values) --- ### Introduction @@ -1222,11 +1300,11 @@ - when calling `#clear!`; - provides `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; -- provides special [key search pattern](#key-search-pattern) for matching setting key names; +- provides a special [key search pattern](#key-search-pattern) for matching setting key names; - you can validate potential setting values without any assignment ([documentation](#validation-of-potential-setting-values)) - uses the [key search pattern](#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](#proc-based-validation)) and **dataset-method-based** ([documentation](#method-based-validation))): @@ -1259,10 +1337,11 @@ end ``` - provides a **set of standard validations** ([documentation](#predefined-validations)): - DSL: `validate 'key.pattern', :predefned_validator`; - supports `strict` behavior; +- you can define your own predefined validators (class-related and global-related) ([documentation](#custom-predefined-validators)); --- ### Key search pattern @@ -1475,10 +1554,71 @@ 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 + +```ruby +class Config < Qonfig::DataSet + # NOTE: definition + define_validator(:user_type) { |value| value.is_a?(User) } + + setting :admin # some key + + validate :admin, :user_type # NOTE: useage +end +``` + +#### Defin new global validator + +```ruby +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 + +```ruby +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 + +```ruby +# 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?(configurations = {})` - check that current config instalce will be valid with passed configurations; - (**class-level**) `.valid_with?(configurations = {})` - check that potential config instancess will be valid with passed configurations; - makes no assignments; @@ -2774,10 +2914,9 @@ - cli toolchain; - support for Rails-like secrets; - support for persistent data storages (we want to store configs in multiple databases and files); - Rails reload plugin; - **Minor**: - - custom global (and class-level) validators (with a special Validator Definition DSL); ## Contributing - Fork it ( https://github.com/0exp/qonfig/fork ) - Create your feature branch (`git checkout -b feature/my-new-feature`)