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 · [![Gem Version](https://badge.fury.io/rb/smart_initializer.svg)](https://badge.fury.io/rb/smart_initializer)
+# SmartCore::Initializer · <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> · [![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)