README.md in smart_initializer-0.7.0 vs README.md in smart_initializer-0.8.0

- old
+ new

@@ -1,10 +1,16 @@ -# SmartCore::Initializer &middot; [![Gem Version](https://badge.fury.io/rb/smart_initializer.svg)](https://badge.fury.io/rb/smart_initializer) +# SmartCore::Initializer &middot; <a target="_blank" href="https://github.com/Cado-Labs"><img src="https://github.com/Cado-Labs/cado-labs-logos/raw/main/cado_labs_badge.svg" alt="Supported by Cado Labs" style="max-width: 100%; height: 20px"></a> &middot; [![Gem Version](https://badge.fury.io/rb/smart_initializer.svg)](https://badge.fury.io/rb/smart_initializer) A simple and convenient way to declare complex constructors with a support for various commonly used type systems. (**in active development**). +--- + +<img src="https://github.com/Cado-Labs/cado-labs-resources/blob/main/cado_labs_supporting_rounded.svg" alt="Supported by Cado Labs" /> + +--- + ## Installation ```ruby gem 'smart_initializer' ``` @@ -22,107 +28,214 @@ --- ## Table of contents - [Synopsis](#synopsis) + - [Initialization flow](#initialization-flow) + - [Attribute value definition flow](#attribute-value-definition-flow-during-object-allocation-and-construction) + - [Constructor definition](#constructor-definition) + - [param](#param) + - [option](#option) + - [params](#params) + - [options](#options) + - [param and params signature](#param-and-params-signature) + - [option and options signature](#option-and-options-signature) +- [Initializer integration](#initializer-integration) +- [Basic Example](#basic-example) - [Access to the instance attributes](#access-to-the-instance-attributes) - [Configuration](#configuration) - [Type aliasing](#type-aliasing) +- [Type auto-casting](#type-auto-casting) - [Initialization extension](#initialization-extension) - [Plugins](#plugins) - [thy-types](#plugin-thy-types) - [Roadmap](#roadmap) - [Build](#build) --- ## Synopsis -**Initialization flow**: +#### Initialization flow -1. Parameter + Option definitioning; -2. Original #initialize invokation; +1. Parameter + Option definitioning and initialization; +2. Original **#initialize** invokation; 3. Initialization extensions invokation; -**Constructor definition**: +**NOTE!**: original constructor is called after **SmarteCore::Initializer**'s constructor +in order to guarantee the validity of the SmartCore::Initializer's functionality +(such as `attribute overlap chek`, `instant type checking`, `value post-processing by finalize`, etc) +#### Attribute value definition flow (during object allocation and construction): + +1. `original value` +2. *(if defined)*: `default value` (default value is used when `original value` is not defined) +3. *(if defined)*: `finalize`; + +--- + +### Constructor definition DSL + +**NOTE**: last `Hash` argument will be treated as `kwarg`s; + +#### param + - `param` - defines name-like attribute: - - `cast` - type-cast received value if value has invalid type; - - `privacy` - reader incapsulation level; - - `finalize` - value post-processing (receives method name or proc); - - `type_system` - differently chosen type system for the current attribute; - - (limitation) param has no `:default` option; -- `option` - defined kwarg-like attribute: - - `cast` - type-cast received value if value has invalid type; - - `privacy` - reader incapsulation level; - - `finalize` - value post-processing (receives method name or proc); - - `default` - defalut value (if an attribute is not provided); - - `type_system` - differently chosen type system for the current attribute; -- last `Hash` argument will be treated as `kwarg`s; + - `cast` (optional) - type-cast received value if value has invalid type; + - `privacy` (optional) - reader incapsulation level; + - `finalize` (optional) - value post-processing (receives method name or proc) (the result value type is also validate); + - `type_system` (optional) - differently chosen type system for the current attribute; + - `as` (optional)- attribute alias (be careful with naming aliases that overlap the names of other attributes); + - `mutable` (optional) - generate type-validated attr_writer in addition to attr_reader (`false` by default) + - (**limitation**) param has no `:default` option; -#### initializer integration +#### option -```ruby -# with pre-configured type system (:smart_types, see Configuration doc) +- `option` - defined kwarg-like attribute: + - `cast` (optional) - type-cast received value if value has invalid type; + - `privacy` (optional) - reader incapsulation level; + - `as` (optional) - attribute alias (be careful with naming aliases that overlap the names of other attributes); + - `mutable` (optional) - generate type-validated attr_writer in addition to attr_reader (`false` by default) + - `optional` (optional) - mark attribut as optional (you can may not initialize optional attributes, + their values will be initialized with `nil` or by `default:` parameter); + - `finalize` (optional) - value post-processing (receives method name or proc) (the result value type is also validate); + - expects `Proc` object or `symbol`/`string` isntance method; + - `default` (optional) - defalut value (if an attribute is not provided); + - expects `Proc` object or a simple value of any type; + - non-proc values will be `dup`licate during initialization; + - `type_system` (optional) - differently chosen type system for the current attribute; -class MyStructure - include SmartCore::Initializer -end -``` +#### params -```ruby -# with manually chosen type system +- `params` - define a series of parameters; + - `:mutable` (optional) - (`false` by default); + - `:privacy` (optional) - (`:public` by default); -class MyStructure - include SmartCore::Initializer(type_system: :smart_types) -end +#### options -class AnotherStructure - include SmartCore::Initializer(type_system: :thy_types) -end -``` +- `options` - define a series of options; + - `:mutable` (optional) - (`false` by default); + - `:privacy` (optional) - (`:public` by default); -#### `param` signautre: +#### `param` and `params` signautre: + ```ruby param <attribute_name>, <type=SmartCore::Types::Value::Any>, # Any by default cast: false, # false by default privacy: :public, # :public by default finalize: proc { |value| value }, # no finalization by default + finalize: :some_method, # use this apporiach in order to finalize by `some_method(value)` instance method + as: :some_alias, # define attribute alias + mutable: true, # (false by default) generate type-validated attr_writer in addition to attr_reader type_system: :smart_types # used by default ``` -#### `option` signature: +```ruby +params <atribute_name1>, <attribute_name2>, <attribute_name3>, ..., + mutable: true, # generate type-validated attr_writer in addition to attr_reader (false by default); + privacy: :private # incapsulate all attributes as private +``` +#### `option` and `options` signature: + ```ruby option <attribute_name>, <type=SmartCore::Types::Value::Any>, # Any by default cast: false, # false by default privacy: :public, # :public by default finalize: proc { |value| value }, # no finalization by default + finalize: :some_method, # use this apporiach in order to finalize by `some_method(value)` instance method default: 123, # no default value by default + default: proc { 123 }, # use proc/lambda object for dynamic initialization + as: :some_alias, # define attribute alias + mutable: true, # (false by default) generate type-validated attr_writer in addition to attr_reader + optional: true # (false by default) mark attribute as optional (attribute will be defined with `nil` or by `default:` value) type_system: :smart_types # used by default ``` -Example: +```ruby +options <attribute_name1>, <attribute_name2>, <attribute_name3>, ..., + mutable: true, # generate type-validated attr_writer in addition to attr_reader (false by default); + privacy: :private # incapsulate all attributes as private +``` +--- +## Initializer integration + +- supports per-class configurations; +- possible configurations: + - `:type_system` - chosen type-system (`smart_types` by default); + - `:strict_options` - fail extra kwarg-attributes, passed to the constructor (`true` by default); + - `:auto_cast` - type-cast all values to the declared attribute type (`false` by default); + ```ruby +# with pre-configured type system (:smart_types, see Configuration doc) + +class MyStructure + include SmartCore::Initializer +end +``` + +```ruby +# with manually chosen settings + +class MyStructure + include SmartCore::Initializer( + type_system: :smart_types, # use smart_types + auto_cast: true, # type-cast all values by default + strict_options: false # ignore extra kwargs passed to the constructor + ) +end + +class AnotherStructure + include SmartCore::Initializer(type_system: :thy_types) # use thy_types and global defaults +end +``` + +--- + +### Basic Example: + + +```ruby class User include SmartCore::Initializer # --- or --- include SmartCore::Initializer(type_system: :smart_types) param :user_id, SmartCore::Types::Value::Integer, cast: false, privacy: :public + param :login, :string, mutable: true + option :role, default: :user, finalize: -> { |value| Role.find(name: value) } + # NOTE: for method-based finalizetion use `your_method(value)` isntance method of your class; + # NOTE: for dynamic default values use `proc` objects and `lambda` objects; + params :name, :password options :metadata, :enabled end -User.new(1, 'John', 'test123', role: :admin, metadata: {}, enabled: false) +# with correct types (incorrect types will raise SmartCore::Initializer::IncorrectTypeError) +object = User.new(1, 'kek123', 'John', 'test123', role: :admin, metadata: {}, enabled: false) + +# attribute accessing: +object.user_id # => 1 +object.login # => 'kek123' +object.name # => 'John' +object.password # => 'test123' +object.role # => :admin +object.metadata # => {} +object.enabled # => false + +# attribute mutation (only mutable attributes have a mutator): +object.login = 123 # => (type vlaidation error) raises SmartCore::Initializer::IncorrectTypeError (expected String, got Integer) +object.login # => 'kek123' +object.login = 'pek456' +object.login # => 'pek456' ``` --- ## Access to the instance attributes @@ -150,31 +263,69 @@ --- ## Configuration -- based on `Qonfig` gem; +- **configuration setitngs**: + - `:default_type_system` - default type system (`smart_types` by default); + - `:strict_options` - fail on extra kwarg-attributes passed to the constructor (`true` by default); + - `:auto_cast` - type-cast all values to the declared attribute type (`false` by default); +- by default, all classes uses and inherits the Global configuration; - you can read config values via `[]` or `.config.settings` or `.config[key]`; -- setitngs: - - `default_type_system` - default type system (`smart_types` by default); - - `strict_options` - raise an error when got unknown options if true (`true` by default); +- each class can be configured separately (in `include` invocation); +- global configuration affects classes used the default global configs in run-time; +- each class can be re-configured separately in run-time; +- based on `Qonfig` gem; ```ruby -# configure: +# Global configuration: + SmartCore::Initializer::Configuration.configure do |config| config.default_type_system = :smart_types # default setting value config.strict_options = true # default setting value + config.auto_cast = false # default setting value end ``` ```ruby -# read: +# Read configs: + SmartCore::Initializer::Configuration[:default_type_system] SmartCore::Initializer::Configuration.config[:default_type_system] SmartCore::Initializer::Configuration.config.settings.default_type_system ``` +```ruby +# per-class configuration: + +class Parameters + include SmartCore::Initializer(auto_cast: true, strict_options: false) + # 1. use globally configured `smart_types` (default value) + # 2. type-cast all attributes by default (auto_cast: true) + # 3. ignore extra kwarg-attributes passed to the constructor (strict_options: false) +end + +class User + include SmartCore::Initializer(type_system: :thy_types) + # 1. use :thy_types isntead of pre-configured :smart_types + # 2. use pre-configured auto_cast (false by default above) + # 3. use pre-configured strict_options () +end +``` + +```ruby +# debug class-related configurations: + +class SomeClass + include SmartCore::Initializer(type_system: :thy_types) +end + +SomeClass.__initializer_settings__[:type_system] # => :thy_types +SomeClass.__initializer_settings__[:auto_cast] # => false +SomeClass.__initializer_settings__[:strict_options] # => true +``` + --- ## Type aliasing - Usage: @@ -206,10 +357,67 @@ SmartCore::Initializer::TypeSystem::ThyTypes.type_aliases ``` --- +## Type-casting + +- make param/option as type-castable: + +```ruby +class Order + include SmartCore::Initializer + + param :manager, 'string' # cast: false is used by default + param :amount, 'float', cast: true + + option :status, :symbol # cast: false is used by default + option :is_processed, 'boolean', cast: true + option :processed_at, 'time', cast: true +end + +order = Order.new( + 'Daiver', + '123.456', + status: :pending, + is_processed: nil, + processed_at: '2021-01-01' +) + +order.manager # => 'Daiver' +order.amount # => 123.456 (type casted) +order.status # => :pending +order.is_processed # => false (type casted) +order.processed_at # => 2021-01-01 00:00:00 +0300 (type casted) +``` + +- configure automatic type casting: + +```ruby +# per class + +class User + include SmartCore::Initializer(auto_cast: true) # auto type cast every attribute + + param :x, 'string' + param :y, 'numeric', cast: false # disable type-casting + + option :b, 'integer', cast: false # disable type-casting + option :c, 'boolean' +end +``` + +```ruby +# globally + +SmartCore::Initializer::Configuration.configure do |config| + config.auto_cast = true # false by default +end +``` + +--- + ## Initialization extension - `ext_init(&block)`: - you can define as many extensions as you want; - extensions are invoked in the order they are defined; @@ -289,13 +497,13 @@ --- ## Roadmap -- (in development) Attribue Definition DSL - - Support for specifying the attribute accessor type (`read_only` parameter); - - Support for attribute aliasing (`as` parameter); +- (**thinking** / **discussing**) Finalize should be invoked on `mutable` attributes after mutation too; +- Support for `RSpec` doubles and instance_doubles inside the type system integration; +- Specs restructuring; - Migrate from `TravisCI` to `GitHub Actions`; - Extract `Type Interop` system to `smart_type-system`; --- @@ -346,9 +554,15 @@ - Create new Pull Request ## License Released under MIT License. + +## Supporting + +<a target="_blank" href="https://github.com/Cado-Labs"> + <img src="https://github.com/Cado-Labs/cado-labs-logos/raw/main/cado_labs_badge.svg" alt="Supported by Cado Labs" style="max-width: 100%;"> +</a> ## Authors [Rustam Ibragimov](https://github.com/0exp)