A simple and convenient way to declare complex constructors with a support for various commonly used type systems. (in active development).
gem'smart_initializer'bundle install
# --- or ---
gem install smart_initializerrequire'smart_core/initializer'- Synopsis
- Initializer integration
- Basic Example
- Access to the instance attributes
- Configuration
- Type aliasing
- Type casting
- Initialization extension
- Plugins
- Roadmap
- Build
- Parameter + Option definitioning and initialization (custom object allocator and constructor);
- Original #initialize invokation;
- Initialization extensions invokation;
NOTE!: SmarteCore::Initializer's constructor is invoked first
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)
original value- (if defined):
default value(default value is used whenoriginal valueis not defined) - (if defined):
finalize;
- if
default-object is a proc-object - this proc-object will be invoked in theouter scopeof block definition; - if
finalize-object is a proc-object - this proc-object will be invoked in theisntancecontext (class instance);
NOTE: :finalize block are not invoked on omitted optional: true attributes
which has no :default definition bock and which are not passed to the constructor. Example:
# without :defaultclassUserincludeSmartCore::Initializeroption:age,:string,optional: true,finalize: ->(val){"#{val}_years"}endUser.new.age# => nil# with :defaultclassUserincludeSmartCore::Initializeroption:age,:string,optional: true,default: '0',finalize: ->(val){"#{val}_years"}endUser.new.age# => '0_years'NOTE: last Hash argument will be treated as kwargs;
param- defines name-like attribute: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 (falseby default)- (limitation) param has no
:defaultoption;
option- defines 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 (falseby default)optional(optional) - mark attribut as optional (you can may not initialize optional attributes, their values will be initialized withnilor bydefault:parameter);finalize(optional) - value post-processing (receives method name or proc) (the result value type is also validate);- expects
Procobject orsymbol/stringisntance method;
- expects
default(optional) - defalut value (if an attribute is not provided);- expects
Procobject or a simple value of any type; - non-proc values will be
duplicate during initialization;
- expects
type_system(optional) - differently chosen type system for the current attribute;
params- defines a series of parameters;:mutable(optional) - (falseby default);:privacy(optional) - (:publicby default);
options- defines a series of options;:mutable(optional) - (falseby default);:privacy(optional) - (:publicby default);
param <attribute_name>,
<type=SmartCore::Types::Value::Any>,# Any by defaultcast: false,# false by defaultprivacy: :public,# :public by defaultfinalize: proc{ |value| value},# no finalization by defaultfinalize: :some_method,# use this apporiach in order to finalize by `some_method(value)` instance methodas: :some_alias,# define attribute aliasmutable: true,# (false by default) generate type-validated attr_writer in addition to attr_readertype_system: :smart_types# used by defaultparams <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 privateoption <attribute_name>,
<type=SmartCore::Types::Value::Any>,# Any by defaultcast: false,# false by defaultprivacy: :public,# :public by defaultfinalize: proc{ |value| value},# no finalization by defaultfinalize: :some_method,# use this apporiach in order to finalize by `some_method(value)` instance methoddefault: 123,# no default value by defaultdefault: proc{123},# use proc/lambda object for dynamic initializationas: :some_alias,# define attribute aliasmutable: true,# (false by default) generate type-validated attr_writer in addition to attr_readeroptional: true# (false by default) mark attribute as optional (attribute will be defined with `nil` or by `default:` value)type_system: :smart_types# used by defaultoptions <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- supports per-class configurations;
- possible configurations:
:type_system- chosen type-system (smart_typesby default);:strict_options- fail extra kwarg-attributes, passed to the constructor (trueby default);:auto_cast- type-cast all values to the declared attribute type (falseby default);
# with pre-configured type system (:smart_types, see Configuration doc)classMyStructureincludeSmartCore::Initializerend# with manually chosen settingsclassMyStructureincludeSmartCore::Initializer(type_system: :smart_types,# use smart_typesauto_cast: true,# type-cast all values by defaultstrict_options: false# ignore extra kwargs passed to the constructor)endclassAnotherStructureincludeSmartCore::Initializer(type_system: :thy_types)# use thy_types and global defaultsendclassUserincludeSmartCore::Initializer# --- or ---includeSmartCore::Initializer(type_system: :smart_types)param:user_id,SmartCore::Types::Value::Integer,cast: false,privacy: :publicparam:login,:string,mutable: trueoption: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,:passwordoptions:metadata,:enabledend# 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# => 1object.login# => 'kek123'object.name# => 'John'object.password# => 'test123'object.role# => :adminobject.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'#__params__- returns a list of initialized params;#__options__- returns a list of initialized options;#__attributes__- returns a list of merged params and options;
classUserincludeSmartCore::Initializerparam:first_name,'string'param:second_name,'string'option:age,'numeric'option:is_admin,'boolean',default: trueenduser=User.new('Rustam','Ibragimov',age: 28)user.__params__# => { first_name: 'Rustam', second_name: 'Ibragimov' }user.__options__# => { age: 28, is_admin: true }user.__attributes__# => { first_name: 'Rustam', second_name: 'Ibragimov', age: 28, is_admin: true }- configuration setitngs:
:default_type_system- default type system (smart_typesby default);:strict_options- fail on extra kwarg-attributes passed to the constructor (trueby default);:auto_cast- type-cast all values to the declared attribute type (falseby default);
- by default, all classes uses and inherits the Global configuration;
- you can read config values via
[]or.config.settingsor.config[key]; - each class can be configured separately (in
includeinvocation); - global configuration affects classes used the default global configs in run-time;
- each class can be re-configured separately in run-time;
- based on
Qonfiggem;
# Global configuration:SmartCore::Initializer::Configuration.configuredo |config|
config.default_type_system=:smart_types# default setting valueconfig.strict_options=true# default setting valueconfig.auto_cast=false# default setting valueend# Read configs:SmartCore::Initializer::Configuration[:default_type_system]SmartCore::Initializer::Configuration.config[:default_type_system]SmartCore::Initializer::Configuration.config.settings.default_type_system# per-class configuration:classParametersincludeSmartCore::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)endclassUserincludeSmartCore::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# debug class-related configurations:classSomeClassincludeSmartCore::Initializer(type_system: :thy_types)endSomeClass.__initializer_settings__[:type_system]# => :thy_typesSomeClass.__initializer_settings__[:auto_cast]# => falseSomeClass.__initializer_settings__[:strict_options]# => true- Usage:
# for smart_types:SmartCore::Initializer::TypeSystem::SmartTypes.type_alias('hsh',SmartCore::Types::Value::Hash)# for thy:SmartCore::Initializer::TypeSystem::ThyTypes.type_alias('int',Thy::Tyhes::Integer)classUserincludeSmartCore::Initializerparam:data,'hsh'# use your new defined type aliasoption:metadata,:hsh# use your new defined type aliasparam:age,'int',type_system: :thy_typesend- Predefined aliases:
# for smart_types:SmartCore::Initializer::TypeSystem::SmartTypes.type_aliases# for thy_types:SmartCore::Initializer::TypeSystem::ThyTypes.type_aliases- make param/option as type-castable:
classOrderincludeSmartCore::Initializerparam:manager,'string'# cast: false is used by defaultparam:amount,'float',cast: trueoption:status,:symbol# cast: false is used by defaultoption:is_processed,'boolean',cast: trueoption:processed_at,'time',cast: trueendorder=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# => :pendingorder.is_processed# => false (type casted)order.processed_at# => 2021-01-01 00:00:00 +0300 (type casted)- configure automatic type casting:
# per classclassUserincludeSmartCore::Initializer(auto_cast: true)# auto type cast every attributeparam:x,'string'param:y,'numeric',cast: false# disable type-castingoption:b,'integer',cast: false# disable type-castingoption:c,'boolean'end# globallySmartCore::Initializer::Configuration.configuredo |config|
config.auto_cast=true# false by defaultendext_init(&block):- you can define as many extensions as you want;
- extensions are invoked in the order they are defined;
- alias method:
extend_initialization_flow;
classUserincludeSmartCore::Initializeroption:name,:nameoption:age,:integerext_init{ |instance| instance.define_singleton_method(:extra){:ext1}}ext_init{ |instance| instance.define_singleton_method(:extra2){:ext2}}enduser=User.new(name: 'keka',age: 123)user.name# => 'keka'user.age# => 123user.extra# => :ext1user.extra2# => :ext2Support for Thy::Types type system (gem)
- install
thytypes (gem install thy):
gem'thy'bundle install- enable
thy_typesplugin:
require'thy'SmartCore::Initializer::Configuration.plugin(:thy_types)- usage:
classUserincludeSmartCore::Initializer(type_system: :thy_types)param:nickname,'string'param:email,'value.text',type_system: :smart_types# mixing with smart_typesoption:admin,Thy::Types::Boolean,default: falseoption:age,(Thy::Type.new{ |value| value > 18})# custom thy type is supported tooend# valid case:User.new('daiver','iamdaiver@gmail.com',{admin: true,age: 19})# => new user object# invalid case (invalid age)User.new('daiver','iamdaiver@gmail.com',{age: 17})# SmartCore::Initializer::ThyTypeValidationError# invaldi case (invalid nickname)User.new(123,'test',{admin: true,age: 22})# => SmartCore::Initializer::ThyTypeValidationError- an ability to re-define existing options and parameters in children classes;
- More semantic attribute declaration errors (more domain-related attribute error objects);
- incorrect
:finalizeargument type:ArgumentError=>FinalizeArgumentError; - incorrect
:asargument type:ArguemntError=>AsArgumentError; - etc;
- incorrect
- Support for
RSpecdoubles and instance_doubles inside the type system integration; - Specs restructuring;
- Migrate from
TravisCItoGitHub Actions; - Extract
Type Interopsystem tosmart_type-system; - an ability to define nested-
option(orparam) for structure-like object (for object with "nested" nature likea.b.cora[:b][:c]) with data type validaitons and with a support of (almost) full attribute DSL;
- with plugin tests:
bin/rspec -w- without plugin tests:
bin/rspec -n- help message:
bin/rspec -h- without auto-correction:
bundle exec rake rubocop- with auto-correction:
bundle exec rake rubocop -A- Fork it ( https://github.com/smart-rb/smart_initializer )
- Create your feature branch (
git checkout -b feature/my-new-feature) - Commit your changes (
git commit -am '[feature_context] Add some feature') - Push to the branch (
git push origin feature/my-new-feature) - Create new Pull Request
Released under MIT License.