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

- old
+ new

@@ -40,10 +40,21 @@ - [Inheritance](#inheritance) - [Composition](#composition) - [Hash representation](#hash-representation) - [Smart Mixin](#smart-mixin) (`Qonfig::Configurable`) - [Instantiation without class definition](#instantiation-without-class-definition) (`Qonfig::DataSet.build(&definitions)`) +- [Compacted config](#compacted-config) + - [Definition and instantiation](#definition-and-instantiation) + - [by raw initialization](#by-raw-initialization) + - [by existing Qonfig::DataSet class](#by-existing-qonfigdataset-class) + - [by existing Qonfig::DataSet instance](#by-existing-qonfigdataset-instance) + - [instantiation without class definition](#instantiation-without-class-definition-1) + - [validation API](#validation-api-see-full-documentation) + - [Setting readers and writers](#setting-readers-and-writers) + - [reading](#reading-by-setting-name-and-index-method-with-dot-notation-support-and-indifferent-access) + - [writing](#writing-by-setting-name-and-index-method-with-dot-notation-support-and-indifferent-access) + - [precitaes](#predicates-see-full-documentation) - [Interaction](#interaction) - [Iteration over setting keys](#iteration-over-setting-keys) (`#each_setting`, `#deep_each_setting`) - [List of config keys](#list-of-config-keys) (`#keys`, `#root_keys`) - [Config reloading](#config-reloading) (reload config definitions and option values) - [Clear options](#clear-options) (set to `nil`) @@ -51,10 +62,16 @@ - [Settings as Predicates](#settings-as-predicates) - [Setting key existence](#setting-key-existence) (`#key?`/`#option?`/`#setting?`) - [Run arbitrary code with temporary settings](#run-arbitrary-code-with-temporary-settings) (`#with(configs = {}, &arbitrary_code)`) - [Import settings / Export settings](#import-settings--export-settings) - [Import config settings](#import-config-settings) (`as instance methods`) + - [Import a set of setting keys (simple dot-noated key list)](#import-a-set-of-setting-keys-simple-dot-noated-key-list) + - [Import with custom method names (mappings)](#import-with-custom-method-names-mappings) + - [Prexify method name](#prexify-method-name) + - [Import nested settings as raw Qonfig::Settings objects](#import-nested-settings-as-raw-qonfigsettings-objects) + - [Import with pattern-matching](#import-with-pattern-matching) + - [Support for predicate-like methods](#support-for-predicate-like-methods) - [Export config settings](#export-config-settings) (`as singleton methods`) - [Validation](#validation) - [Introduction](#introduction) - [Key search pattern](#key-search-pattern) - [Proc-based validation](#proc-based-validation) @@ -62,18 +79,20 @@ - [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** + - `(DSL methods for dynamic setting keys definition by reading them from a file)` - [Load from YAML file](#load-from-yaml-file) - [Expose YAML](#expose-yaml) (`Rails`-like environment-based YAML configs) - [Load from JSON file](#load-from-json-file) - [Expose JSON](#expose-json) (`Rails`-like environment-based JSON configs) - [Load from ENV](#load-from-env) - [Load from \_\_END\_\_](#load-from-__end__) (aka `.load_from_self`) - [Expose \_\_END\_\_](#expose-__end__) (aka `.expose_self`) - **Setting values** + - `(instance methods for loading setting values from a file to existing config object with already defined setting keys)` - [Default setting values file](#default-setting-values-file) - [Load setting values from YAML file](#load-setting-values-from-yaml-file-by-instance) - [Load setting values from JSON file](#load-setting-values-from-json-file-by-instance) - [Load setting values from \_\_END\_\_](#load-setting-values-from-__end__-by-instance) - [Load setting values from file manually](#load-setting-values-from-file-manually-by-instance) @@ -97,13 +116,13 @@ --- ### 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); +- `setting(name, value = nil)` - define setting with corresponding name and value; +- `setting(name) { setting(name, value = nil); ... }` - define nested settings OR reopen existing nested setting and define some new nested settings; +- `re_setting(name, value = nil)`, `re_setting(name) { ... }` - re-define existing setting (or define new if the original does not exist); - accessing: [access via method](#access-via-method), [access via index-method \[\]](#access-via-index-method-), [.dig](#dig), [.slice](#slice), [.slice_value](#slice_value), [.subset](#subset); ```ruby # --- definition --- @@ -597,10 +616,222 @@ config.settings.web_api # => "api.google.com" ``` --- +## Compacted config + +- [Definition and instantiation](#definition-and-instantiation) + - [by raw initialization](#by-raw-initialization) + - [by existing Qonfig::DataSet class](#by-existing-qonfigdataset-class) + - [by existing Qonfig::DataSet instance](#by-existing-qonfigdataset-instance) + - [instantiation without class definition](#instantiation-without-class-definition-1) + - [validation API](#validation-api-see-full-documentation) +- [Setting readers and writers](#setting-readers-and-writers) + - [reading](#reading-by-setting-name-and-index-method-with-dot-notation-support-and-indifferent-access) + - [writing](#writing-by-setting-name-and-index-method-with-dot-notation-support-and-indifferent-access) + - [precitaes](#predicates-see-full-documentation) + +--- + +- `Qonfig::Compacted`: represents the compacted config object with setting readers and setting writers; +- setting keys are represented as direct instace methods (`#settings` invokation does not need); +- no any other useful instance-based functionality - just setting readers, setting writers and setting predicates: +- support for index-like access methods (`[]`,`[]=`); +- full support of `Qonfig::DataSet` definition DSL commands: + - `setting`, `re_setting` [doc](#definition-and-access) + - `validate`, `add_validator` [doc](#validation) + - `load_from_self` [doc](#load-from-__end__), `load_from_yaml` [doc](#load-from-yaml-file), `load_from_json` [doc](#load-from-json-file), `load_from_toml` [doc](#plugins-toml); + - `expose_self` [doc](#expose-__end__), `expose_yaml` [doc](#expose-yaml), `expose_json` [doc](#expose-json), `expose_toml` [doc](#plugins-toml) + - `values_file` [doc](#default-setting-values-file) +- support for validation of potential setting values `.valid_with?` [documentation](#validation-of-potential-setting-values); +- can be instantiated by: + - by existing config object: `Qonfig::DataSet#compacted` or `Qonfig::Compacted.build_from(config, &configuration)`; + - from existing `Qonfig::DataSet` class: ``Qonfig::DataSet.build_compacted`; + - by direct instantiation: `Qonfig::Compacted.new(settings_values = {}, &configuration)`; + - by implicit instance building without explicit class definition `Qonfig::Compacted.build(&dsl_commands) # => instance of Qonfig::Compacted`; +- you can define your own instance methods too; + +--- + +### Definition and instantiation + +#### by raw initialization + +```ruby +class Config < Qonfig::Compacted + setting :api, 'google.com' + setting :enabled, true + setting :queue do + setting :engine, :sidekiq + end +end + +config = Config.new(api: 'yandex.ru') do |conf| + conf.enabled = false +end + +config.api # => 'yandex.ru' +config.enabled # => false +config.queue.engine # => :sidekiq +``` + +#### by existing Qonfig::DataSet class + +```ruby +class Config < Qonfig::DataSet + setting :api, 'google.com' + setting :enabled, true +end + +config = Config.build_compacted # builds Qonfig::Compacted instance + +config.api # => 'google.com' +config.enabled # => true +``` + +#### by existing Qonfig::DataSet instance + +- `Qonfig::DataSet#compacted` +- (or) `Qonfig::Compacted.build_from(config)` + +```ruby +class Config < Qonfig::DataSet + setting :api, 'google.com' + setting :enabled, true +end + +config = Config.new + +compacted_config = config.compacted +# --- or --- +compacted_config = Qonfig::Compacted.build_from(config) + +compacted_config.api # => 'google.com' +compacted_config.enabled # => true +``` + +#### instantiation without class definition + +```ruby +config = Qonfig::Compacted.build do + setting :api, 'google.ru' + setting :enabled, true +end + +config.api # => 'google.ru' +config.enabled # => true +``` + +#### validation API (see [full documentation](#validation)): + +```ruby +# custom validators +Qonfig::Compacted.define_validator(:version_check) do |value| + value.is_a?(Integer) && value < 100 +end + +class Config < Qonfig::Compacted + setting :api, 'google.ru' + setting :enabled, true + setting :version, 2 + setting :queue { setting :engine, :sidekiq } + + # full support of original validation api + validate :api, :string, strict: true + validate :enabled, :boolean, strict: true + validate :version, :version_check # custom validator + validate 'queue.#', :symbol +end + +# potential values validation +Config.valid_with?(api: :yandex) # => false +Config.valid_with?(enabled: nil) # => false +Config.valid_with?(version: nil) # => false +Config.valid_with?(api: 'yandex.ru', enabled: false, version: 3) # => true + +config = Config.new + +# instance validation +config.api = :yandex # => Qonfig::ValidationError (should be a type of string) +config.version = nil # => Qonfig::ValidationError (can not be nil) +config.queue.engine = 'sneakers' # => Qonfig::ValidationError (should be a type of symbol) +``` + +--- + +### Setting readers and writers + +```ruby +class Config < Qonfig::Compcated + setting :api, 'google.ru' + setting :enabled, true + setting :queue do + setting :engine, :sidekiq + setting :workers_count, 10 + end +end + +config = Config.new +``` + +#### reading (by setting name and index method with dot-notation support and indifferent access) + +```ruby +# by setting name +config.api # => 'google.ru' +config.enabled # => true +config.queue.engine # => :sidekiq +config.queue.workers_count # => 10 + +# by index method with dot-notation support and indiffernt access +config[:api] # => 'google.ru' +config['enabled'] # => true +config[:queue][:engine] # => :sidekiq +config['queue.workers_count'] # => 10 +``` + +#### writing (by setting name and index method with dot-notation support and indifferent access) + +```ruby +# by setting name +config.api = 'yandex.ru' +config.queue.engine = :sidekiq +# and etc + +# by index method with dot-notaiton support and indifferent access +config['api'] = 'yandex.ru' +config['queue.engine'] = :sidekiq +config[:queue][:workers_count] = 5 +``` + +#### predicates ([see full documentation](#settings-as-predicates)) + +```ruby +class Config < Qonfig::Compcated + setting :enabled, true + setting :api, 'yandex.ru' + setting :queue do + setting :engine, :sidekiq + end +end + +config = Config.new + +config.enabled? # => true +config.enabled = nil +config.enabled? # => false + +config.queue.engine? # => true +config.queue.engine = nil +config.queue.engine? # => false + +config.queue? # => true +``` + +--- + ## Interaction - [Iteration over setting keys](#iteration-over-setting-keys) (`#each_setting`, `#deep_each_setting`) - [List of config keys](#list-of-config-keys) (`#keys`, `#root_keys`) - [Config reloading](#config-reloading) (reload config definitions and option values) @@ -1055,19 +1286,22 @@ ### Import config settings - `Qonfig::Imports` - a special mixin that provides the convenient DSL to work with config import features (`.import_settings` method); - `.import_settings` - DSL method for importing configuration settings (from a config instance) as instance methods of a class; - (**IMPORTANT**) `import_settings` imports config settings as access methods to config's settings (creates `attr_reader`s for your config); +- generated methods can be used as predicates (with trailing `?` symbol); +- you can generate `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); - signature: `.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); - last part of dot-notated key will become a name of the setting access instance method; - `mappings:` - a map of keys that describes custom method names for each imported setting; - `prefix:` - prexifies setting access method name with custom prefix; - `raw:` - use nested settings as objects or hashify them (`false` by default (means "hashify nested settings")); - + - `accessor:` - 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: ```ruby @@ -1086,10 +1320,17 @@ end ``` Let's see what we can to do :) +- [Import a set of setting keys (simple dot-noated key list)](#import-a-set-of-setting-keys-simple-dot-noated-key-list) +- [Import with custom method names (mappings)](#import-with-custom-method-names-mappings) +- [Prexify method name](#prexify-method-name) +- [Import nested settings as raw Qonfig::Settings objects](#import-nested-settings-as-raw-qonfigsettings-objects) +- [Import with pattern-matching](#import-with-pattern-matching) +- [Support for predicate-like methods](#support-for-predicate-like-methods) + #### Import a set of setting keys (simple dot-noated key list) - last part of dot-notated key will become a name of the setting access instance method; ```ruby @@ -1143,14 +1384,34 @@ ) end service = ServiceObject.new -service.config_credentials # => { login" => "D@iVeR", "auth_token" => "IAdkoa0@()1239uA" } +service.config_account # => { login" => "D@iVeR", "auth_token" => "IAdkoa0@()1239uA" } service.config_secret_token # => "IAdkoa0@()1239uA" ``` +#### Support for predicate-like methods + +- generated methods can be used as predicates (with trailing `?` symbol); + +```ruby +class ServiceObject + include Qonfig::Imports + + import_settings(AppConfig, + 'web_api.credentials.account', + mappings: { secret_token: 'web_api.credentials.account.auth_token' }, + ) +end + +service = ServiceObject.new + +service.account? # => true +service.secret_token? # => true +``` + #### Import nested settings as raw Qonfig::Settings objects - `raw: false` is used by default (hashify nested settings) ```ruby @@ -1179,11 +1440,11 @@ service = ServiceObject.new service.credentials # => { "account" => { "login" => "D@iVeR", "auth_token" => "IAdkoa0@()1239uA"} } ``` -#### Immport with pattern-matching +#### Import with pattern-matching - import root keys only: `import_settings(config_object, '*')`; - import all keys: `import_settings(config_object, '#')`; - import the subset of keys: `import_settings(config_object, 'group.*.group.#')` (pattern-mathcing usage); @@ -1209,11 +1470,11 @@ # generated instance methods: # => service.web_api # => service.graphql_api # import ALL keys - import_Settings(AppConfig, '#') + import_settings(AppConfig, '#') # generated instance methods: # => service.web_api # => service.credentials # => service.account # => service.login @@ -1224,21 +1485,25 @@ --- ### Export config settings +- works in `.import_settings` manner [doc](#import-config-settings) (see examples and documentation above `:)`) - all config objects can export their settings to an arbitrary object as singleton methods; - (**IMPORTANT**) `export_settings` exports config settings as access methods to config's settings (creates `attr_reader`s for your config); +- generated methods can be used as predicates (with trailing `?` symbol); +- you can generate `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); - signature: `#export_settings(exportable_object, *setting_keys, mappings: {}, prefix: '', raw: false)`: - `exportable_object` - an arbitrary object for exporting; - `*setting_keys` - an array of dot-notaed config's setting keys that should be exported (dot-notaed key is a key that describes each part of nested setting key as a string separated by `dot`-symbol); - last part of dot-notated key will become a name of the setting access instance method; - `mappings:` - a map of keys that describes custom method names for each exported setting; - `prefix:` - prexifies setting access method name with custom prefix; - `raw:` - use nested settings as objects or hashify them (`false` by default (means "hashify nested settings")); -- works in `.import_settings` manner [doc](#import-config-settings) (see examples and documentation above `:)`) + - `accessor:` - generate `attr_accessor` for imported config settigns (`false` by default (means "generate `attr_reader`s only")); ```ruby class Config < Qonfig::DataSet setting :web_api do setting :credentials do @@ -1256,23 +1521,35 @@ config = Config.new service = ServiceObject.new service.config_account # => NoMethodError +``` +```ruby # 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" } +``` +```ruby # 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 ``` +```ruby +# NOTE: predicates +config.export_settings(service, '*') + +config.web_api? # => true +config.graphql_api? # => false +``` + --- ## Validation - [Introduction](#introduction) @@ -1571,15 +1848,16 @@ # NOTE: definition define_validator(:user_type) { |value| value.is_a?(User) } setting :admin # some key - validate :admin, :user_type # NOTE: useage + # NOTE: usage + validate :admin, :user_type end ``` -#### Defin new global validator +#### Define new global validator ```ruby Qonfig::DataSet.define_validator(:secured_value) do |value| value == '***' end @@ -1617,12 +1895,12 @@ --- ### 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; +- (**instance-level**) `#valid_with?(setting_values = {}, &configuration)` - check that current config instalce will be valid with passed configurations; +- (**class-level**) `.valid_with?(setting_values = {}, &configuration)` - check that potential config instancess will be valid with passed configurations; - makes no assignments; #### #valid_with? (instance-level) ```ruby @@ -1639,10 +1917,16 @@ config = Config.new config.valid_with?(enabled: true, queue: { adapter: 'que' }) # => true config.valid_with?(enabled: 123) # => false (should be a type of boolean) config.valid_with?(enabled: true, queue: { adapter: Sidekiq }) # => false (queue.adapter should be a type of string) + +# do-config notation is supported too +config.valid_with?(enabled: true) do |conf| + conf.queue.adapter = :sidekiq +end +# => false (queue.adapter should be a type of string) ``` #### .valid_with? (class-level) ```ruby @@ -1657,10 +1941,16 @@ end Config.valid_with?(enabled: true, queue: { adapter: 'que' }) # => true Config.valid_with?(enabled: 123) # => false (should be a type of boolean) Config.valid_with?(enabled: true, queue: { adapter: Sidekiq }) # => false (queue.adapter should be a type of string) + +# do-config notation is supported too +Config.valid_with?(enabled: true) do |config| + config.queue.adapter = :sidekiq +end +# => false (queue.adapter should be a type of string) ``` --- ## Work with files @@ -2115,17 +2405,18 @@ ### Load from \_\_END\_\_ - aka `load_from_self` - `:format` - specify the format of data placed under the `__END__` instruction: - - `format: :yaml` - **YAML** format (by default); + - `format: :dynamic` (default) - automatic format resolvation; + - `format: :yaml` - **YAML** format; - `format: :json` - **JSON** format; - `format: :toml` - **TOML** format (via `toml`-plugin); ```ruby class Config < Qonfig::DataSet - load_from_self # on the root (format: :yaml is used by default) + 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 @@ -2162,11 +2453,12 @@ - aka `expose_self`; - works in `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: :yaml` - **YAML** format (by default); + - `format: :dynamic` (default) - automatic format resolvation; + - `format: :yaml` - **YAML** format; - `format: :json` - **JSON** format; - `format: :toml` - **TOML** format (via `toml`-plugin); ```ruby class Config < Qonfig::DataSet @@ -2331,14 +2623,15 @@ --- ### Load setting values from YAML file (by instance) - prvoides an ability to load predefined setting values from a yaml file; -- `#load_from_yaml(file_path, strict: true, expose: nil)` +- `#load_from_yaml(file_path, strict: true, expose: nil, &configurations)` - `file_path` - full file path or `:self` (`:self` means "load setting values from __END__ data"); - `:strict` - rerquires that file (or __END__-data) should exist (`true` by default); - `:expose` - what the environment-based subset of keys should be used (`nil` means "do not use any subset of keys") (`nil` by default); + - `&configurations` - `do |config|` ability :) #### Default behavior ```yaml # config.yml @@ -2429,14 +2722,15 @@ --- ### Load setting values from JSON file (by instance) - prvoides an ability to load predefined setting values from a json file; -- `#load_from_yaml(file_path, strict: true, expose: nil)` +- `#load_from_json(file_path, strict: true, expose: nil, &configurations)` - `file_path` - full file path or `:self` (`:self` means "load setting values from __END__ data"); - `:strict` - rerquires that file (or __END__-data) should exist (`true` by default); - `:expose` - what the environment-based subset of keys should be used (`nil` means "do not use any subset of keys") (`nil` by default); + - `&configurations` - `do |config|` ability :) #### Default behavior ```json // config.json @@ -2537,15 +2831,16 @@ --- ### Load setting values from \_\_END\_\_ (by instance) - prvoides an ability to load predefined setting values from `__END__` file section; -- `#load_from_self(strict: true, expose: nil)` +- `#load_from_self(strict: true, expose: nil, &configurations)` - `:format` - defines the format of file (`:dynamic` means "try to automatically infer the file format") (`:dynamic` by default); - supports `:yaml`, `:json`, `:toml` (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 :) #### Default behavior ```ruby class Config < Qonfig::DataSet @@ -2625,16 +2920,17 @@ ### Load setting values from file manually (by instance) - prvoides an ability to load predefined setting values from a file; - works in instance-based `#load_from_yaml` / `#load_from_json` / `#load_from_self` manner; -- signature: `#load_from_file(file_path, format: :dynamic, strict: true, expose: nil)`: +- signature: `#load_from_file(file_path, format: :dynamic, strict: true, expose: nil, &configurations)`: - `file_path` - full file path or `:self` (`:self` means "load setting values from __END__ data"); - `:format` - defines the format of file (`:dynamic` means "try to automatically infer the file format") (`:dynamic` by default); - supports `:yaml`, `:json`, `:toml` (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 :) - see examples for instance-based `#load_from_yaml` ([doc](#load-setting-values-from-yaml-by-instance)) / `#load_from_json` ([doc](#load-setting-values-from-json-by-instance)) / `#load_from_self` ([doc](#load-setting-values-from-__end__-by-instance)); --- ### Save to JSON file @@ -2816,12 +3112,13 @@ --- ### Plugins: toml +- `Qonfig.plugin(:toml)` - adds support for `toml` format ([specification](https://github.com/toml-lang/toml)); -- depends on `toml-rb` gem ([link](https://github.com/emancu/toml-rb)); -- supports TOML `0.5.0` format (dependency lock); +- depends on `toml-rb` gem ([link](https://github.com/emancu/toml-rb)) (tested on `>= 2.0`); +- supports TOML `0.5.0` format (dependency lock) (`toml-rb >= 2.0`); - provides `.load_from_toml` (works in `.load_from_yaml` manner ([doc](#load-from-yaml-file))); - provides `.expose_toml` (works in `.expose_yaml` manner ([doc](#expose-yaml))); - provides `#save_to_toml` (works in `#save_to_yaml` manner ([doc](#save-to-yaml-file))) (`toml-rb` has no native options); - provides `format: :toml` for `.values_file` ([doc]()); - provides `#load_from_toml` (work in `#load_from_yaml` manner ([doc](#load-setting-values-from-yaml)));