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`)