├── .gitignore
├── .rspec
├── .rubocop.yml
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── Gemfile
├── Gemfile.lock
├── LICENSE.txt
├── README.md
├── Rakefile
├── bin
├── console
├── rspec
└── setup
├── gemfiles
├── with_external_deps.gemfile
├── with_external_deps.gemfile.lock
├── without_external_deps.gemfile
└── without_external_deps.gemfile.lock
├── lib
└── smart_core
│ ├── initializer.rb
│ └── initializer
│ ├── attribute.rb
│ ├── attribute
│ ├── factory.rb
│ ├── factory
│ │ ├── base.rb
│ │ ├── option.rb
│ │ └── param.rb
│ ├── finalizer.rb
│ ├── finalizer
│ │ ├── abstract.rb
│ │ ├── anonymous_block.rb
│ │ └── instance_method.rb
│ ├── list.rb
│ ├── value.rb
│ └── value
│ │ ├── base.rb
│ │ ├── option.rb
│ │ └── param.rb
│ ├── configuration.rb
│ ├── constructor.rb
│ ├── constructor
│ ├── dsl.rb
│ ├── dsl
│ └── inheritance.rb
│ ├── errors.rb
│ ├── extensions.rb
│ ├── extensions
│ ├── abstract.rb
│ ├── ext_init.rb
│ └── list.rb
│ ├── functionality.rb
│ ├── instance_attribute_accessing.rb
│ ├── plugins.rb
│ ├── plugins
│ ├── abstract.rb
│ ├── access_mixin.rb
│ ├── registry.rb
│ ├── registry_interface.rb
│ ├── thy_types.rb
│ └── thy_types
│ │ ├── errors.rb
│ │ ├── thy_types.rb
│ │ └── thy_types
│ │ ├── abstract_factory.rb
│ │ ├── operation.rb
│ │ └── operation
│ │ ├── base.rb
│ │ ├── cast.rb
│ │ ├── valid.rb
│ │ └── validate.rb
│ ├── settings.rb
│ ├── settings
│ ├── auto_cast.rb
│ ├── base.rb
│ ├── duplicator.rb
│ ├── strict_options.rb
│ └── type_system.rb
│ ├── type_system.rb
│ ├── type_system
│ ├── interop.rb
│ ├── interop
│ │ ├── abstract_factory.rb
│ │ ├── aliasing.rb
│ │ ├── aliasing
│ │ │ └── alias_list.rb
│ │ └── operation.rb
│ ├── registry.rb
│ ├── registry_interface.rb
│ ├── smart_types.rb
│ └── smart_types
│ │ ├── abstract_factory.rb
│ │ ├── operation.rb
│ │ └── operation
│ │ ├── base.rb
│ │ ├── cast.rb
│ │ ├── valid.rb
│ │ └── validate.rb
│ └── version.rb
├── smart_initializer.gemspec
└── spec
├── features
├── configuration_spec.rb
├── instance_attribute_list_access_spec.rb
├── integration_spec.rb
├── plugin_system
│ ├── load_spec.rb
│ └── registration_spec.rb
├── plugins
│ └── thy_types_spec.rb
└── type_system
│ ├── custom_type_system_integration_spec.rb
│ └── type_aliasing_spec.rb
├── smoke_spec.rb
├── spec_helper.rb
└── support
├── meta_scopes.rb
└── spec_support.rb
/.gitignore:
--------------------------------------------------------------------------------
1 | /.bundle/
2 | /.yardoc
3 | /_yardoc/
4 | /coverage/
5 | /doc/
6 | /pkg/
7 | /spec/reports/
8 | /tmp/
9 | .rspec_status
10 | /.idea
11 | .ruby-version
12 | *.gem
13 |
--------------------------------------------------------------------------------
/.rspec:
--------------------------------------------------------------------------------
1 | --format progress
2 | --color
3 | --require spec_helper
4 |
--------------------------------------------------------------------------------
/.rubocop.yml:
--------------------------------------------------------------------------------
1 | inherit_gem:
2 | armitage-rubocop:
3 | - lib/rubocop.general.yml
4 | - lib/rubocop.rake.yml
5 | - lib/rubocop.rspec.yml
6 |
7 | AllCops:
8 | TargetRubyVersion: 3.1
9 | NewCops: enable
10 | Include:
11 | - lib/**/*.rb
12 | - spec/**/*.rb
13 | - Gemfile
14 | - Rakefile
15 | - smart_initializer.gemspec
16 | - gemfiles/*.gemfile
17 | - bin/console
18 |
19 | # NOTE: It is not suitable for infrastracture-level frameworks
20 | Metrics/ParameterLists:
21 | Enabled: false
22 |
23 | # NOTE: It is ok to use empty blocks in specs (inside simple test cases)
24 | Lint/EmptyBlock:
25 | Exclude:
26 | - spec/**/*.rb
27 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 | All notable changes to this project will be documented in this file.
3 |
4 | ## [0.11.0] - 2022-11-25
5 | ### Changed
6 | - Support for *Ruby@2.5* and *Ruby@2.6* has ended;
7 | - Reduced object allocation count during type checking process;
8 | - Removed useless `Incorrect type` text part from `SmartCore::Initializer::IncorrectTypeError` exception message;
9 | - Updated development dependencies;
10 |
11 | ## [0.10.0] - 2022-10-04
12 | ### Changed
13 | - `SmartCore::Engine::ReadWriteLock` is used instead `SmartCore::Engine::Lock` in order to decrease the count of RubyVM's context switching and useless Mutexes usage;
14 | - reduced `KeyError`-exception flow use cases in some cases inside the framework internals (in order to reduce object allocations under the hood);
15 | - bumped development dependencies;
16 | - bumped core dependencies (in order to use `ReadWiteLock` (and actualize available inner-framework features);
17 |
18 | ## [0.9.1] - 2022-03-06
19 | ### Fixed
20 | - `finalize` now accepts lambdas with arity `-2`. For example `->(a, *b) {}` or `:freeze.to_proc`
21 | which in `Ruby >= 3` returns lambda with arity equal to `-2`.
22 |
23 | ## [0.9.0] - 2021-12-19
24 | ### Changed
25 | - `:finalize` block is not invoked on the `option` with `optional: true` flag;
26 |
27 | ## [0.8.0] - 2021-12-04
28 | ### Added
29 | - New options for option and param attributes:
30 | - Support for attribute **aliasing** (`:as` parameter);
31 | - supports: `option`, `param`;
32 | - Support for attribute **auto-casting** (`:auto_cast` parameter);
33 | - supports: `option`, `param`;
34 | - Support for **mutable** attributes (`:mutable` parameter) with type-validation inside;
35 | - supports: `option`, `options`, `param`, `params`;
36 | - Support for **optional** attributes (`:optional` parameter);
37 | - supports: `option`;
38 | - **SmartCore::Initializer::Configuration**:
39 | - Configuration setting `strict_options` now works separately "per-class" in all configs manner
40 | (each class shares the global state but has own state too)
41 | - Support for per-class/global `:auto_cast` configuration;
42 | - provides a global/per-class behavior for `:cast` option (`false` by default);
43 | - `options` declaration now supports `privacy` option;
44 | - `params` declaration now supports `privacy` option;
45 |
46 | ### Changed
47 | - Drop support of **Ruby@2.4**;
48 | - `:default` attribute parameter now duplicates the passed value during object instantiation;
49 | - `:default` attribute parameter only works with `option` attribute (`param` can't have `default` parameter now);
50 | - Attribute values generated by `:finalize` are type-validated now too;
51 | - Now lambda-based `:finalize` attributes are being checked for the signature during attribute declaration;
52 | - Some error messages have become more readable;
53 |
54 | ## Fixed
55 | - `send` method overlaping/rewriting: otions and params named as `send` breaks the internal
56 | framework-related invocations of the attribute definitioning inside the custom object constructor
57 | (this name rewrites internal Ruby's `send` method and brokes some things);
58 |
59 | ## [0.7.0] - 2021-06-23
60 | ### Added
61 | - `strict_options` config option for non-strict checking of passed options;
62 |
63 | ## [0.6.0] - 2021-06-23
64 | ### Added
65 | - Validation messages for incorrect attribute types;
66 |
67 | ## [0.5.0] - 2021-01-18
68 | ### Changed
69 | - Updated `smart_types` dependency (`~> 0.4.0`) to guarantee **Ruby@3** compatibility;
70 | - Updated development dependencies;
71 |
72 | ## [0.4.0] - 2021-01-18
73 | ### Added
74 | - Support for **Ruby 3**;
75 |
76 | ### Changed
77 | - Moved from `TravisCI` to nothing (todo: migrate to `GitHub Actions`).
78 | Temporary: we must run test cases locally.
79 | - Updated development dependencies;
80 |
81 | ## [0.3.2] - 2020-07-12
82 | ### Fixed
83 | - Deeply inherited entities lose their `__initializer_settings__` entitiy;
84 |
85 | ## [0.3.1] - 2020-07-12
86 | ### Fixed
87 | - Deeply inherited entities lose their class attribute definers;
88 |
89 | ## [0.3.0] - 2020-07-11
90 | ### Added
91 | - `extend_initialization_flow` alias method for `SmartCore::Initializer.ext_init`;
92 | - Access methods to the instance attribute lists (`#__params`, `#__options__`, `#__attributes__`);
93 |
94 | ### Changed
95 | - Updated development dependencies;
96 |
97 | ## [0.2.0] - 2020-05-16
98 | ### Changed
99 | - **Constructor**: disallow unknown option attributes;
100 |
101 | ## [0.1.0] - 2020-05-10
102 | - Release :)
103 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, gender identity and expression, level of experience,
9 | nationality, personal appearance, race, religion, or sexual identity and
10 | orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at iamdaiver@gmail.com. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at [https://contributor-covenant.org/version/1/4][version]
72 |
73 | [homepage]: https://contributor-covenant.org
74 | [version]: https://contributor-covenant.org/version/1/4/
75 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | source 'https://rubygems.org'
4 |
5 | gemspec
6 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | PATH
2 | remote: .
3 | specs:
4 | smart_initializer (0.12.0)
5 | qonfig (~> 0.24)
6 | smart_engine (~> 0.16)
7 | smart_types (~> 0.8)
8 |
9 | GEM
10 | remote: https://rubygems.org/
11 | specs:
12 | activesupport (7.0.4)
13 | concurrent-ruby (~> 1.0, >= 1.0.2)
14 | i18n (>= 1.6, < 2)
15 | minitest (>= 5.1)
16 | tzinfo (~> 2.0)
17 | armitage-rubocop (1.36.0)
18 | rubocop (= 1.36.0)
19 | rubocop-performance (= 1.15.0)
20 | rubocop-rails (= 2.16.1)
21 | rubocop-rake (= 0.6.0)
22 | rubocop-rspec (= 2.13.2)
23 | ast (2.4.2)
24 | bigdecimal (3.1.8)
25 | coderay (1.1.3)
26 | concurrent-ruby (1.1.10)
27 | diff-lcs (1.5.0)
28 | docile (1.4.0)
29 | i18n (1.12.0)
30 | concurrent-ruby (~> 1.0)
31 | json (2.6.2)
32 | method_source (1.0.0)
33 | minitest (5.16.3)
34 | ostruct (0.6.0)
35 | parallel (1.22.1)
36 | parser (3.1.2.1)
37 | ast (~> 2.4.1)
38 | pry (0.14.1)
39 | coderay (~> 1.1)
40 | method_source (~> 1.0)
41 | qonfig (0.28.0)
42 | rack (3.0.1)
43 | rainbow (3.1.1)
44 | rake (13.0.6)
45 | regexp_parser (2.6.1)
46 | rexml (3.2.5)
47 | rspec (3.12.0)
48 | rspec-core (~> 3.12.0)
49 | rspec-expectations (~> 3.12.0)
50 | rspec-mocks (~> 3.12.0)
51 | rspec-core (3.12.0)
52 | rspec-support (~> 3.12.0)
53 | rspec-expectations (3.12.0)
54 | diff-lcs (>= 1.2.0, < 2.0)
55 | rspec-support (~> 3.12.0)
56 | rspec-mocks (3.12.0)
57 | diff-lcs (>= 1.2.0, < 2.0)
58 | rspec-support (~> 3.12.0)
59 | rspec-support (3.12.0)
60 | rubocop (1.36.0)
61 | json (~> 2.3)
62 | parallel (~> 1.10)
63 | parser (>= 3.1.2.1)
64 | rainbow (>= 2.2.2, < 4.0)
65 | regexp_parser (>= 1.8, < 3.0)
66 | rexml (>= 3.2.5, < 4.0)
67 | rubocop-ast (>= 1.20.1, < 2.0)
68 | ruby-progressbar (~> 1.7)
69 | unicode-display_width (>= 1.4.0, < 3.0)
70 | rubocop-ast (1.23.0)
71 | parser (>= 3.1.1.0)
72 | rubocop-performance (1.15.0)
73 | rubocop (>= 1.7.0, < 2.0)
74 | rubocop-ast (>= 0.4.0)
75 | rubocop-rails (2.16.1)
76 | activesupport (>= 4.2.0)
77 | rack (>= 1.1)
78 | rubocop (>= 1.33.0, < 2.0)
79 | rubocop-rake (0.6.0)
80 | rubocop (~> 1.0)
81 | rubocop-rspec (2.13.2)
82 | rubocop (~> 1.33)
83 | ruby-progressbar (1.11.0)
84 | simplecov (0.21.2)
85 | docile (~> 1.1)
86 | simplecov-html (~> 0.11)
87 | simplecov_json_formatter (~> 0.1)
88 | simplecov-html (0.12.3)
89 | simplecov_json_formatter (0.1.4)
90 | smart_engine (0.17.0)
91 | smart_types (0.8.0)
92 | smart_engine (~> 0.11)
93 | tzinfo (2.0.5)
94 | concurrent-ruby (~> 1.0)
95 | unicode-display_width (2.3.0)
96 |
97 | PLATFORMS
98 | arm64-darwin-21
99 | arm64-darwin-23
100 |
101 | DEPENDENCIES
102 | armitage-rubocop (~> 1.30)
103 | bigdecimal
104 | bundler (~> 2.3)
105 | ostruct
106 | pry (~> 0.14)
107 | rake (~> 13.0)
108 | rspec (~> 3.11)
109 | simplecov (~> 0.21)
110 | smart_initializer!
111 |
112 | BUNDLED WITH
113 | 2.5.19
114 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2020-2024 Rustam Ibragimov
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SmartCore::Initializer ·
· [](https://badge.fury.io/rb/smart_initializer)
2 |
3 | A simple and convenient way to declare complex constructors with a support for various commonly used type systems.
4 | (**in active development**).
5 |
6 | ---
7 |
8 |
9 |
10 | ---
11 |
12 | ## Installation
13 |
14 | ```ruby
15 | gem 'smart_initializer'
16 | ```
17 |
18 | ```shell
19 | bundle install
20 | # --- or ---
21 | gem install smart_initializer
22 | ```
23 |
24 | ```ruby
25 | require 'smart_core/initializer'
26 | ```
27 |
28 | ---
29 |
30 | ## Table of contents
31 |
32 | - [Synopsis](#synopsis)
33 | - [Initialization flow](#initialization-flow)
34 | - [Attribute value definition flow](#attribute-value-definition-flow-during-object-allocation-and-construction)
35 | - [Constructor definition DSL](#constructor-definition-dsl)
36 | - [param](#param)
37 | - [option](#option)
38 | - [params](#params)
39 | - [options](#options)
40 | - [param and params signature](#param-and-params-signautre)
41 | - [option and options signature](#option-and-options-signature)
42 | - [Initializer integration](#initializer-integration)
43 | - [Basic Example](#basic-example)
44 | - [Access to the instance attributes](#access-to-the-instance-attributes)
45 | - [Configuration](#configuration)
46 | - [Type aliasing](#type-aliasing)
47 | - [Type casting](#type-casting)
48 | - [Initialization extension](#initialization-extension)
49 | - [Plugins](#plugins)
50 | - [thy-types](#plugin-thy-types)
51 | - [Roadmap](#roadmap)
52 | - [Build](#build)
53 |
54 | ---
55 |
56 | ## Synopsis
57 |
58 | #### Initialization flow
59 |
60 | 1. Parameter + Option definitioning and initialization (custom object allocator and constructor);
61 | 2. Original **#initialize** invokation;
62 | 3. Initialization extensions invokation;
63 |
64 | **NOTE!**: **SmarteCore::Initializer**'s constructor is invoked first
65 | in order to guarantee the validity of the SmartCore::Initializer's functionality
66 | (such as `attribute overlap chek`, `instant type checking`, `value post-processing by finalize`, etc)
67 |
68 | #### Attribute value definition flow (during object allocation and construction):
69 |
70 | 1. `original value`
71 | 2. *(if defined)*: `default value` (default value is used when `original value` is not defined)
72 | 3. *(if defined)*: `finalize`;
73 |
74 | - if `default`-object is a proc-object - this proc-object will be invoked in the `outer scope` of block definition;
75 | - if `finalize`-object is a proc-object - this proc-object will be invoked in the `isntance` context (class instance);
76 |
77 | **NOTE**: `:finalize` block are not invoked on omitted `optional: true` attributes
78 | which has no `:default` definition bock and which are not passed to the constructor. Example:
79 |
80 | ```ruby
81 | # without :default
82 |
83 | class User
84 | include SmartCore::Initializer
85 | option :age, :string, optional: true, finalize: -> (val) { "#{val}_years" }
86 | end
87 |
88 | User.new.age # => nil
89 | ```
90 |
91 | ```ruby
92 | # with :default
93 |
94 | class User
95 | include SmartCore::Initializer
96 | option :age, :string, optional: true, default: '0', finalize: -> (val) { "#{val}_years" }
97 | end
98 |
99 | User.new.age # => '0_years'
100 | ```
101 |
102 | ---
103 |
104 | ### Constructor definition DSL
105 |
106 | **NOTE**: last `Hash` argument will be treated as `kwarg`s;
107 |
108 | #### param
109 |
110 | - `param` - defines name-like attribute:
111 | - `cast` (optional) - type-cast received value if value has invalid type;
112 | - `privacy` (optional) - reader incapsulation level;
113 | - `finalize` (optional) - value post-processing (receives method name or proc) (the result value type is also validate);
114 | - `type_system` (optional) - differently chosen type system for the current attribute;
115 | - `as` (optional)- attribute alias (be careful with naming aliases that overlap the names of other attributes);
116 | - `mutable` (optional) - generate type-validated attr_writer in addition to attr_reader (`false` by default)
117 | - (**limitation**) param has no `:default` option;
118 |
119 | #### option
120 |
121 | - `option` - defines kwarg-like attribute:
122 | - `cast` (optional) - type-cast received value if value has invalid type;
123 | - `privacy` (optional) - reader incapsulation level;
124 | - `as` (optional) - attribute alias (be careful with naming aliases that overlap the names of other attributes);
125 | - `mutable` (optional) - generate type-validated attr_writer in addition to attr_reader (`false` by default)
126 | - `optional` (optional) - mark attribut as optional (you can may not initialize optional attributes,
127 | their values will be initialized with `nil` or by `default:` parameter);
128 | - `finalize` (optional) - value post-processing (receives method name or proc) (the result value type is also validate);
129 | - expects `Proc` object or `symbol`/`string` isntance method;
130 | - `default` (optional) - defalut value (if an attribute is not provided);
131 | - expects `Proc` object or a simple value of any type;
132 | - non-proc values will be `dup`licate during initialization;
133 | - `type_system` (optional) - differently chosen type system for the current attribute;
134 |
135 | #### params
136 |
137 | - `params` - defines a series of parameters;
138 | - `:mutable` (optional) - (`false` by default);
139 | - `:privacy` (optional) - (`:public` by default);
140 |
141 | #### options
142 |
143 | - `options` - defines a series of options;
144 | - `:mutable` (optional) - (`false` by default);
145 | - `:privacy` (optional) - (`:public` by default);
146 |
147 |
148 | #### `param` and `params` signautre:
149 |
150 | ```ruby
151 | param ,
152 | , # Any by default
153 | cast: false, # false by default
154 | privacy: :public, # :public by default
155 | finalize: proc { |value| value }, # no finalization by default
156 | finalize: :some_method, # use this apporiach in order to finalize by `some_method(value)` instance method
157 | as: :some_alias, # define attribute alias
158 | mutable: true, # (false by default) generate type-validated attr_writer in addition to attr_reader
159 | type_system: :smart_types # used by default
160 | ```
161 |
162 | ```ruby
163 | params , , , ...,
164 | mutable: true, # generate type-validated attr_writer in addition to attr_reader (false by default);
165 | privacy: :private # incapsulate all attributes as private
166 | ```
167 |
168 | #### `option` and `options` signature:
169 |
170 | ```ruby
171 | option ,
172 | , # Any by default
173 | cast: false, # false by default
174 | privacy: :public, # :public by default
175 | finalize: proc { |value| value }, # no finalization by default
176 | finalize: :some_method, # use this apporiach in order to finalize by `some_method(value)` instance method
177 | default: 123, # no default value by default
178 | default: proc { 123 }, # use proc/lambda object for dynamic initialization
179 | as: :some_alias, # define attribute alias
180 | mutable: true, # (false by default) generate type-validated attr_writer in addition to attr_reader
181 | optional: true # (false by default) mark attribute as optional (attribute will be defined with `nil` or by `default:` value)
182 | type_system: :smart_types # used by default
183 | ```
184 |
185 | ```ruby
186 | options , , , ...,
187 | mutable: true, # generate type-validated attr_writer in addition to attr_reader (false by default);
188 | privacy: :private # incapsulate all attributes as private
189 | ```
190 |
191 | ---
192 |
193 | ## Initializer integration
194 |
195 | - supports per-class configurations;
196 | - possible configurations:
197 | - `:type_system` - chosen type-system (`smart_types` by default);
198 | - `:strict_options` - fail extra kwarg-attributes, passed to the constructor (`true` by default);
199 | - `:auto_cast` - type-cast all values to the declared attribute type (`false` by default);
200 |
201 | ```ruby
202 | # with pre-configured type system (:smart_types, see Configuration doc)
203 |
204 | class MyStructure
205 | include SmartCore::Initializer
206 | end
207 | ```
208 |
209 | ```ruby
210 | # with manually chosen settings
211 |
212 | class MyStructure
213 | include SmartCore::Initializer(
214 | type_system: :smart_types, # use smart_types
215 | auto_cast: true, # type-cast all values by default
216 | strict_options: false # ignore extra kwargs passed to the constructor
217 | )
218 | end
219 |
220 | class AnotherStructure
221 | include SmartCore::Initializer(type_system: :thy_types) # use thy_types and global defaults
222 | end
223 | ```
224 |
225 | ---
226 |
227 | ### Basic Example:
228 |
229 |
230 | ```ruby
231 | class User
232 | include SmartCore::Initializer
233 | # --- or ---
234 | include SmartCore::Initializer(type_system: :smart_types)
235 |
236 | param :user_id, SmartCore::Types::Value::Integer, cast: false, privacy: :public
237 | param :login, :string, mutable: true
238 |
239 | option :role, default: :user, finalize: -> { |value| Role.find(name: value) }
240 |
241 | # NOTE: for method-based finalizetion use `your_method(value)` isntance method of your class;
242 | # NOTE: for dynamic default values use `proc` objects and `lambda` objects;
243 |
244 | params :name, :password
245 | options :metadata, :enabled
246 | end
247 |
248 | # with correct types (incorrect types will raise SmartCore::Initializer::IncorrectTypeError)
249 | object = User.new(1, 'kek123', 'John', 'test123', role: :admin, metadata: {}, enabled: false)
250 |
251 | # attribute accessing:
252 | object.user_id # => 1
253 | object.login # => 'kek123'
254 | object.name # => 'John'
255 | object.password # => 'test123'
256 | object.role # => :admin
257 | object.metadata # => {}
258 | object.enabled # => false
259 |
260 | # attribute mutation (only mutable attributes have a mutator):
261 | object.login = 123 # => (type vlaidation error) raises SmartCore::Initializer::IncorrectTypeError (expected String, got Integer)
262 | object.login # => 'kek123'
263 | object.login = 'pek456'
264 | object.login # => 'pek456'
265 | ```
266 |
267 | ---
268 |
269 | ## Access to the instance attributes
270 |
271 | - `#__params__` - returns a list of initialized params;
272 | - `#__options__` - returns a list of initialized options;
273 | - `#__attributes__` - returns a list of merged params and options;
274 |
275 | ```ruby
276 | class User
277 | include SmartCore::Initializer
278 |
279 | param :first_name, 'string'
280 | param :second_name, 'string'
281 | option :age, 'numeric'
282 | option :is_admin, 'boolean', default: true
283 | end
284 |
285 | user = User.new('Rustam', 'Ibragimov', age: 28)
286 |
287 | user.__params__ # => { first_name: 'Rustam', second_name: 'Ibragimov' }
288 | user.__options__ # => { age: 28, is_admin: true }
289 | user.__attributes__ # => { first_name: 'Rustam', second_name: 'Ibragimov', age: 28, is_admin: true }
290 | ```
291 |
292 | ---
293 |
294 | ## Configuration
295 |
296 | - **configuration setitngs**:
297 | - `:default_type_system` - default type system (`smart_types` by default);
298 | - `:strict_options` - fail on extra kwarg-attributes passed to the constructor (`true` by default);
299 | - `:auto_cast` - type-cast all values to the declared attribute type (`false` by default);
300 | - by default, all classes uses and inherits the Global configuration;
301 | - you can read config values via `[]` or `.config.settings` or `.config[key]`;
302 | - each class can be configured separately (in `include` invocation);
303 | - global configuration affects classes used the default global configs in run-time;
304 | - each class can be re-configured separately in run-time;
305 | - based on `Qonfig` gem;
306 |
307 | ```ruby
308 | # Global configuration:
309 |
310 | SmartCore::Initializer::Configuration.configure do |config|
311 | config.default_type_system = :smart_types # default setting value
312 | config.strict_options = true # default setting value
313 | config.auto_cast = false # default setting value
314 | end
315 | ```
316 |
317 | ```ruby
318 | # Read configs:
319 |
320 | SmartCore::Initializer::Configuration[:default_type_system]
321 | SmartCore::Initializer::Configuration.config[:default_type_system]
322 | SmartCore::Initializer::Configuration.config.settings.default_type_system
323 | ```
324 |
325 | ```ruby
326 | # per-class configuration:
327 |
328 | class Parameters
329 | include SmartCore::Initializer(auto_cast: true, strict_options: false)
330 | # 1. use globally configured `smart_types` (default value)
331 | # 2. type-cast all attributes by default (auto_cast: true)
332 | # 3. ignore extra kwarg-attributes passed to the constructor (strict_options: false)
333 | end
334 |
335 | class User
336 | include SmartCore::Initializer(type_system: :thy_types)
337 | # 1. use :thy_types isntead of pre-configured :smart_types
338 | # 2. use pre-configured auto_cast (false by default above)
339 | # 3. use pre-configured strict_options ()
340 | end
341 | ```
342 |
343 | ```ruby
344 | # debug class-related configurations:
345 |
346 | class SomeClass
347 | include SmartCore::Initializer(type_system: :thy_types)
348 | end
349 |
350 | SomeClass.__initializer_settings__[:type_system] # => :thy_types
351 | SomeClass.__initializer_settings__[:auto_cast] # => false
352 | SomeClass.__initializer_settings__[:strict_options] # => true
353 | ```
354 |
355 | ---
356 |
357 | ## Type aliasing
358 |
359 | - Usage:
360 |
361 | ```ruby
362 | # for smart_types:
363 | SmartCore::Initializer::TypeSystem::SmartTypes.type_alias('hsh', SmartCore::Types::Value::Hash)
364 |
365 | # for thy:
366 | SmartCore::Initializer::TypeSystem::ThyTypes.type_alias('int', Thy::Tyhes::Integer)
367 |
368 | class User
369 | include SmartCore::Initializer
370 |
371 | param :data, 'hsh' # use your new defined type alias
372 | option :metadata, :hsh # use your new defined type alias
373 |
374 | param :age, 'int', type_system: :thy_types
375 | end
376 | ```
377 |
378 | - Predefined aliases:
379 |
380 | ```ruby
381 | # for smart_types:
382 | SmartCore::Initializer::TypeSystem::SmartTypes.type_aliases
383 |
384 | # for thy_types:
385 | SmartCore::Initializer::TypeSystem::ThyTypes.type_aliases
386 | ```
387 |
388 | ---
389 |
390 | ## Type-casting
391 |
392 | - make param/option as type-castable:
393 |
394 | ```ruby
395 | class Order
396 | include SmartCore::Initializer
397 |
398 | param :manager, 'string' # cast: false is used by default
399 | param :amount, 'float', cast: true
400 |
401 | option :status, :symbol # cast: false is used by default
402 | option :is_processed, 'boolean', cast: true
403 | option :processed_at, 'time', cast: true
404 | end
405 |
406 | order = Order.new(
407 | 'Daiver',
408 | '123.456',
409 | status: :pending,
410 | is_processed: nil,
411 | processed_at: '2021-01-01'
412 | )
413 |
414 | order.manager # => 'Daiver'
415 | order.amount # => 123.456 (type casted)
416 | order.status # => :pending
417 | order.is_processed # => false (type casted)
418 | order.processed_at # => 2021-01-01 00:00:00 +0300 (type casted)
419 | ```
420 |
421 | - configure automatic type casting:
422 |
423 | ```ruby
424 | # per class
425 |
426 | class User
427 | include SmartCore::Initializer(auto_cast: true) # auto type cast every attribute
428 |
429 | param :x, 'string'
430 | param :y, 'numeric', cast: false # disable type-casting
431 |
432 | option :b, 'integer', cast: false # disable type-casting
433 | option :c, 'boolean'
434 | end
435 | ```
436 |
437 | ```ruby
438 | # globally
439 |
440 | SmartCore::Initializer::Configuration.configure do |config|
441 | config.auto_cast = true # false by default
442 | end
443 | ```
444 |
445 | ---
446 |
447 | ## Initialization extension
448 |
449 | - `ext_init(&block)`:
450 | - you can define as many extensions as you want;
451 | - extensions are invoked in the order they are defined;
452 | - alias method: `extend_initialization_flow`;
453 |
454 | ```ruby
455 | class User
456 | include SmartCore::Initializer
457 |
458 | option :name, :name
459 | option :age, :integer
460 |
461 | ext_init { |instance| instance.define_singleton_method(:extra) { :ext1 } }
462 | ext_init { |instance| instance.define_singleton_method(:extra2) { :ext2 } }
463 | end
464 |
465 | user = User.new(name: 'keka', age: 123)
466 | user.name # => 'keka'
467 | user.age # => 123
468 | user.extra # => :ext1
469 | user.extra2 # => :ext2
470 | ```
471 |
472 | ---
473 |
474 | ## Plugins
475 |
476 | - [thy-types](#plugin-thy-types)
477 |
478 | ---
479 |
480 | ## Plugin: thy-types
481 |
482 | Support for `Thy::Types` type system ([gem](https://github.com/akxcv/thy))
483 |
484 | - install `thy` types (`gem install thy`):
485 |
486 | ```ruby
487 | gem 'thy'
488 | ```
489 |
490 | ```shell
491 | bundle install
492 | ```
493 |
494 | - enable `thy_types` plugin:
495 |
496 | ```ruby
497 | require 'thy'
498 | SmartCore::Initializer::Configuration.plugin(:thy_types)
499 | ```
500 |
501 | - usage:
502 |
503 | ```ruby
504 | class User
505 | include SmartCore::Initializer(type_system: :thy_types)
506 |
507 | param :nickname, 'string'
508 | param :email, 'value.text', type_system: :smart_types # mixing with smart_types
509 | option :admin, Thy::Types::Boolean, default: false
510 | option :age, (Thy::Type.new { |value| value > 18 }) # custom thy type is supported too
511 | end
512 |
513 | # valid case:
514 | User.new('daiver', 'iamdaiver@gmail.com', { admin: true, age: 19 })
515 | # => new user object
516 |
517 | # invalid case (invalid age)
518 | User.new('daiver', 'iamdaiver@gmail.com', { age: 17 })
519 | # SmartCore::Initializer::ThyTypeValidationError
520 |
521 | # invaldi case (invalid nickname)
522 | User.new(123, 'test', { admin: true, age: 22 })
523 | # => SmartCore::Initializer::ThyTypeValidationError
524 | ```
525 |
526 | ---
527 |
528 | ## Roadmap
529 |
530 | - an ability to re-define existing options and parameters in children classes;
531 | - More semantic attribute declaration errors (more domain-related attribute error objects);
532 | - incorrect `:finalize` argument type: `ArgumentError` => `FinalizeArgumentError`;
533 | - incorrect `:as` argument type: `ArguemntError` => `AsArgumentError`;
534 | - etc;
535 | - Support for `RSpec` doubles and instance_doubles inside the type system integration;
536 | - Specs restructuring;
537 | - Migrate from `TravisCI` to `GitHub Actions`;
538 | - Extract `Type Interop` system to `smart_type-system`;
539 | - an ability to define nested-`option` (or `param`) for structure-like object (for object with "nested" nature like `a.b.c` or `a[:b][:c]`) with data type validaitons and with a support of (almost) full attribute DSL;
540 |
541 | ---
542 |
543 | ## Build
544 |
545 | ### Tests Running
546 |
547 | - with plugin tests:
548 |
549 | ```shell
550 | bin/rspec -w
551 | ```
552 |
553 | - without plugin tests:
554 |
555 | ```shell
556 | bin/rspec -n
557 | ```
558 |
559 | - help message:
560 |
561 | ```shell
562 | bin/rspec -h
563 | ```
564 |
565 | ### Code Style Checking
566 |
567 | - without auto-correction:
568 |
569 | ```shell
570 | bundle exec rake rubocop
571 | ```
572 |
573 | - with auto-correction:
574 |
575 | ```shell
576 | bundle exec rake rubocop -A
577 | ```
578 |
579 | ---
580 |
581 | ## Contributing
582 |
583 | - Fork it ( https://github.com/smart-rb/smart_initializer )
584 | - Create your feature branch (`git checkout -b feature/my-new-feature`)
585 | - Commit your changes (`git commit -am '[feature_context] Add some feature'`)
586 | - Push to the branch (`git push origin feature/my-new-feature`)
587 | - Create new Pull Request
588 |
589 | ## License
590 |
591 | Released under MIT License.
592 |
593 | ## Supporting
594 |
595 |
596 |
597 |
598 |
599 | ## Authors
600 |
601 | [Rustam Ibragimov](https://github.com/0exp)
602 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'bundler/gem_tasks'
4 | require 'rspec/core/rake_task'
5 | require 'rubocop'
6 | require 'rubocop/rake_task'
7 | require 'rubocop-performance'
8 | require 'rubocop-rspec'
9 | require 'rubocop-rake'
10 |
11 | RuboCop::RakeTask.new(:rubocop) do |t|
12 | config_path = File.expand_path(File.join('.rubocop.yml'), __dir__)
13 | t.options = ['--config', config_path]
14 | t.requires << 'rubocop-rspec'
15 | t.requires << 'rubocop-performance'
16 | t.requires << 'rubocop-rake'
17 | end
18 |
19 | RSpec::Core::RakeTask.new(:rspec)
20 |
21 | task default: :rspec
22 |
--------------------------------------------------------------------------------
/bin/console:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | require 'bundler/setup'
5 | require 'smart_core/initializer'
6 | require 'pry'
7 |
8 | Pry.start
9 |
--------------------------------------------------------------------------------
/bin/rspec:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | require 'pathname'
5 | require 'optparse'
6 |
7 | module SmartCoreInitializerRSpecRunner
8 | expand_gemfile_path = lambda do |gemfile|
9 | File.expand_path(File.join('..', 'gemfiles', gemfile), __dir__)
10 | end
11 |
12 | GEMFILES = {
13 | with_external_deps: expand_gemfile_path.call('with_external_deps.gemfile'),
14 | without_external_deps: expand_gemfile_path.call('without_external_deps.gemfile')
15 | }.freeze
16 |
17 | class << self
18 | def run!
19 | OptionParser.new do |opts|
20 | opts.banner = 'Usage: bin/rspec [options]'
21 |
22 | opts.on('-w', '--with-plugins', 'Run general tests and plugin tests') do
23 | run_with_plugin_tests!
24 | end
25 |
26 | opts.on('-n', '--without-plugins', 'Run general tests (without plugin tests') do
27 | run_without_plugin_tests!
28 | end
29 | end.parse!
30 | end
31 |
32 | private
33 |
34 | def run_with_plugin_tests!
35 | ENV['TEST_PLUGINS'] = 'true'
36 | ENV['FULL_TEST_COVERAGE_CHECK'] = 'true'
37 | ENV['BUNDLE_GEMFILE'] = GEMFILES[:with_external_deps]
38 | run_tests!
39 | end
40 |
41 | def run_without_plugin_tests!
42 | ENV['BUNDLE_GEMFILE'] = GEMFILES[:without_external_deps]
43 | run_tests!
44 | end
45 |
46 | def run_tests!
47 | require 'rubygems'
48 | require 'bundler/setup'
49 | load Gem.bin_path('rspec-core', 'rspec')
50 | end
51 | end
52 | end
53 |
54 | SmartCoreInitializerRSpecRunner.run!
55 |
--------------------------------------------------------------------------------
/bin/setup:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -euo pipefail
3 | IFS=$'\n\t'
4 | set -vx
5 |
6 | bundle install
7 |
8 | # Do any other automated setup that you need to do here
9 |
--------------------------------------------------------------------------------
/gemfiles/with_external_deps.gemfile:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | source 'https://rubygems.org'
4 |
5 | gem 'thy', '~> 0.1.4'
6 |
7 | gemspec path: '..'
8 |
--------------------------------------------------------------------------------
/gemfiles/with_external_deps.gemfile.lock:
--------------------------------------------------------------------------------
1 | PATH
2 | remote: ..
3 | specs:
4 | smart_initializer (0.11.1)
5 | qonfig (~> 0.24)
6 | smart_engine (~> 0.16)
7 | smart_types (~> 0.8)
8 |
9 | GEM
10 | remote: https://rubygems.org/
11 | specs:
12 | activesupport (7.0.4)
13 | concurrent-ruby (~> 1.0, >= 1.0.2)
14 | i18n (>= 1.6, < 2)
15 | minitest (>= 5.1)
16 | tzinfo (~> 2.0)
17 | armitage-rubocop (1.30.1.1)
18 | rubocop (= 1.30.1)
19 | rubocop-performance (= 1.14.2)
20 | rubocop-rails (= 2.15.0)
21 | rubocop-rake (= 0.6.0)
22 | rubocop-rspec (= 2.11.1)
23 | ast (2.4.2)
24 | coderay (1.1.3)
25 | concurrent-ruby (1.1.10)
26 | diff-lcs (1.5.0)
27 | docile (1.4.0)
28 | i18n (1.12.0)
29 | concurrent-ruby (~> 1.0)
30 | method_source (1.0.0)
31 | minitest (5.16.3)
32 | parallel (1.22.1)
33 | parser (3.1.2.1)
34 | ast (~> 2.4.1)
35 | pry (0.14.1)
36 | coderay (~> 1.1)
37 | method_source (~> 1.0)
38 | qonfig (0.28.0)
39 | rack (3.0.0)
40 | rainbow (3.1.1)
41 | rake (13.0.6)
42 | regexp_parser (2.6.0)
43 | rexml (3.2.5)
44 | rspec (3.11.0)
45 | rspec-core (~> 3.11.0)
46 | rspec-expectations (~> 3.11.0)
47 | rspec-mocks (~> 3.11.0)
48 | rspec-core (3.11.0)
49 | rspec-support (~> 3.11.0)
50 | rspec-expectations (3.11.1)
51 | diff-lcs (>= 1.2.0, < 2.0)
52 | rspec-support (~> 3.11.0)
53 | rspec-mocks (3.11.1)
54 | diff-lcs (>= 1.2.0, < 2.0)
55 | rspec-support (~> 3.11.0)
56 | rspec-support (3.11.1)
57 | rubocop (1.30.1)
58 | parallel (~> 1.10)
59 | parser (>= 3.1.0.0)
60 | rainbow (>= 2.2.2, < 4.0)
61 | regexp_parser (>= 1.8, < 3.0)
62 | rexml (>= 3.2.5, < 4.0)
63 | rubocop-ast (>= 1.18.0, < 2.0)
64 | ruby-progressbar (~> 1.7)
65 | unicode-display_width (>= 1.4.0, < 3.0)
66 | rubocop-ast (1.21.0)
67 | parser (>= 3.1.1.0)
68 | rubocop-performance (1.14.2)
69 | rubocop (>= 1.7.0, < 2.0)
70 | rubocop-ast (>= 0.4.0)
71 | rubocop-rails (2.15.0)
72 | activesupport (>= 4.2.0)
73 | rack (>= 1.1)
74 | rubocop (>= 1.7.0, < 2.0)
75 | rubocop-rake (0.6.0)
76 | rubocop (~> 1.0)
77 | rubocop-rspec (2.11.1)
78 | rubocop (~> 1.19)
79 | ruby-progressbar (1.11.0)
80 | simplecov (0.21.2)
81 | docile (~> 1.1)
82 | simplecov-html (~> 0.11)
83 | simplecov_json_formatter (~> 0.1)
84 | simplecov-html (0.12.3)
85 | simplecov_json_formatter (0.1.4)
86 | smart_engine (0.17.0)
87 | smart_types (0.8.0)
88 | smart_engine (~> 0.11)
89 | thy (0.1.4)
90 | tzinfo (2.0.5)
91 | concurrent-ruby (~> 1.0)
92 | unicode-display_width (2.3.0)
93 |
94 | PLATFORMS
95 | arm64-darwin-21
96 |
97 | DEPENDENCIES
98 | armitage-rubocop (~> 1.30)
99 | bundler (~> 2.3)
100 | pry (~> 0.14)
101 | rake (~> 13.0)
102 | rspec (~> 3.11)
103 | simplecov (~> 0.21)
104 | smart_initializer!
105 | thy (~> 0.1.4)
106 |
107 | BUNDLED WITH
108 | 2.3.17
109 |
--------------------------------------------------------------------------------
/gemfiles/without_external_deps.gemfile:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | source 'https://rubygems.org'
4 |
5 | gemspec path: '..'
6 |
--------------------------------------------------------------------------------
/gemfiles/without_external_deps.gemfile.lock:
--------------------------------------------------------------------------------
1 | PATH
2 | remote: ..
3 | specs:
4 | smart_initializer (0.11.0)
5 | qonfig (~> 0.24)
6 | smart_engine (~> 0.16)
7 | smart_types (~> 0.7)
8 |
9 | GEM
10 | remote: https://rubygems.org/
11 | specs:
12 | activesupport (7.0.4)
13 | concurrent-ruby (~> 1.0, >= 1.0.2)
14 | i18n (>= 1.6, < 2)
15 | minitest (>= 5.1)
16 | tzinfo (~> 2.0)
17 | armitage-rubocop (1.30.1.1)
18 | rubocop (= 1.30.1)
19 | rubocop-performance (= 1.14.2)
20 | rubocop-rails (= 2.15.0)
21 | rubocop-rake (= 0.6.0)
22 | rubocop-rspec (= 2.11.1)
23 | ast (2.4.2)
24 | coderay (1.1.3)
25 | concurrent-ruby (1.1.10)
26 | diff-lcs (1.5.0)
27 | docile (1.4.0)
28 | i18n (1.12.0)
29 | concurrent-ruby (~> 1.0)
30 | method_source (1.0.0)
31 | minitest (5.16.3)
32 | parallel (1.22.1)
33 | parser (3.1.2.1)
34 | ast (~> 2.4.1)
35 | pry (0.14.1)
36 | coderay (~> 1.1)
37 | method_source (~> 1.0)
38 | qonfig (0.28.0)
39 | rack (3.0.0)
40 | rainbow (3.1.1)
41 | rake (13.0.6)
42 | regexp_parser (2.6.0)
43 | rexml (3.2.5)
44 | rspec (3.11.0)
45 | rspec-core (~> 3.11.0)
46 | rspec-expectations (~> 3.11.0)
47 | rspec-mocks (~> 3.11.0)
48 | rspec-core (3.11.0)
49 | rspec-support (~> 3.11.0)
50 | rspec-expectations (3.11.1)
51 | diff-lcs (>= 1.2.0, < 2.0)
52 | rspec-support (~> 3.11.0)
53 | rspec-mocks (3.11.1)
54 | diff-lcs (>= 1.2.0, < 2.0)
55 | rspec-support (~> 3.11.0)
56 | rspec-support (3.11.1)
57 | rubocop (1.30.1)
58 | parallel (~> 1.10)
59 | parser (>= 3.1.0.0)
60 | rainbow (>= 2.2.2, < 4.0)
61 | regexp_parser (>= 1.8, < 3.0)
62 | rexml (>= 3.2.5, < 4.0)
63 | rubocop-ast (>= 1.18.0, < 2.0)
64 | ruby-progressbar (~> 1.7)
65 | unicode-display_width (>= 1.4.0, < 3.0)
66 | rubocop-ast (1.21.0)
67 | parser (>= 3.1.1.0)
68 | rubocop-performance (1.14.2)
69 | rubocop (>= 1.7.0, < 2.0)
70 | rubocop-ast (>= 0.4.0)
71 | rubocop-rails (2.15.0)
72 | activesupport (>= 4.2.0)
73 | rack (>= 1.1)
74 | rubocop (>= 1.7.0, < 2.0)
75 | rubocop-rake (0.6.0)
76 | rubocop (~> 1.0)
77 | rubocop-rspec (2.11.1)
78 | rubocop (~> 1.19)
79 | ruby-progressbar (1.11.0)
80 | simplecov (0.21.2)
81 | docile (~> 1.1)
82 | simplecov-html (~> 0.11)
83 | simplecov_json_formatter (~> 0.1)
84 | simplecov-html (0.12.3)
85 | simplecov_json_formatter (0.1.4)
86 | smart_engine (0.17.0)
87 | smart_types (0.8.0)
88 | smart_engine (~> 0.11)
89 | tzinfo (2.0.5)
90 | concurrent-ruby (~> 1.0)
91 | unicode-display_width (2.3.0)
92 |
93 | PLATFORMS
94 | arm64-darwin-21
95 |
96 | DEPENDENCIES
97 | armitage-rubocop (~> 1.30)
98 | bundler (~> 2.3)
99 | pry (~> 0.14)
100 | rake (~> 13.0)
101 | rspec (~> 3.11)
102 | simplecov (~> 0.21)
103 | smart_initializer!
104 |
105 | BUNDLED WITH
106 | 2.3.17
107 |
--------------------------------------------------------------------------------
/lib/smart_core/initializer.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'smart_core'
4 | require 'smart_core/types'
5 | require 'forwardable'
6 |
7 | # @api public
8 | # @since 0.1.0
9 | module SmartCore
10 | # @api public
11 | # @since 0.1.0
12 | module Initializer
13 | require_relative 'initializer/version'
14 | require_relative 'initializer/errors'
15 | require_relative 'initializer/plugins'
16 | require_relative 'initializer/settings'
17 | require_relative 'initializer/configuration'
18 | require_relative 'initializer/type_system'
19 | require_relative 'initializer/attribute'
20 | require_relative 'initializer/extensions'
21 | require_relative 'initializer/constructor'
22 | require_relative 'initializer/dsl'
23 | require_relative 'initializer/instance_attribute_accessing'
24 | require_relative 'initializer/functionality'
25 |
26 | class << self
27 | # @param base_klass [Class]
28 | # @return [void]
29 | #
30 | # @api private
31 | # @since 0.1.0
32 | # @version 0.3.0
33 | def included(base_klass)
34 | ::SmartCore::Initializer::Functionality.seed_to(base_klass)
35 | end
36 | end
37 |
38 | # @return [void]
39 | #
40 | # @api private
41 | # @since 0.1.0
42 | def initialize(*); end
43 | end
44 |
45 | class << self
46 | # @option type_system [String, Symbol]
47 | # @option strict_options [Boolean]
48 | # @option auto_cast [Boolean]
49 | # @return [Module]
50 | #
51 | # @api public
52 | # @since 0.1.0
53 | # @version 0.8.0
54 | # rubocop:disable Naming/MethodName
55 | def Initializer(
56 | type_system: SmartCore::Initializer::Configuration[:default_type_system],
57 | strict_options: SmartCore::Initializer::Configuration[:strict_options],
58 | auto_cast: SmartCore::Initializer::Configuration[:auto_cast]
59 | )
60 | SmartCore::Initializer::Functionality.includable_module(
61 | type_system: type_system,
62 | strict_options: strict_options,
63 | auto_cast: auto_cast
64 | )
65 | end
66 | # rubocop:enable Naming/MethodName
67 | end
68 | end
69 |
--------------------------------------------------------------------------------
/lib/smart_core/initializer/attribute.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # @api private
4 | # @since 0.1.0
5 | # @version 0.8.0
6 | module SmartCore::Initializer::Attribute
7 | require_relative 'attribute/value'
8 | require_relative 'attribute/list'
9 | require_relative 'attribute/finalizer'
10 | require_relative 'attribute/factory'
11 | end
12 |
--------------------------------------------------------------------------------
/lib/smart_core/initializer/attribute/factory.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # @api private
4 | # @since 0.1.0
5 | # @version 0.8.0
6 | module SmartCore::Initializer::Attribute::Factory
7 | require_relative 'factory/base'
8 | require_relative 'factory/option'
9 | require_relative 'factory/param'
10 | end
11 |
--------------------------------------------------------------------------------
/lib/smart_core/initializer/attribute/factory/base.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # @api private
4 | # @since 0.8.0
5 | class SmartCore::Initializer::Attribute::Factory::Base
6 | class << self
7 | # @!method create(*params)
8 | # @param params [Any] List of appropriated attributes for the corresponding factory
9 | # @return [SmartCore::Initializer::Attribute::Base]
10 |
11 | private
12 |
13 | # @param name [String, Symbol]
14 | # @return [Symbol]
15 | #
16 | # @api private
17 | # @since 0.8.0
18 | def prepare_name_param(name)
19 | unless name.is_a?(::String) || name.is_a?(::Symbol)
20 | raise(SmartCore::Initializer::ArgumentError, <<~ERROR_MESSAGE)
21 | Attribute name should be a type of String or Symbol
22 | ERROR_MESSAGE
23 | end
24 |
25 | name.to_sym
26 | end
27 |
28 | # @param type [String, Symbol, Any]
29 | # @param type_system [Class]
30 | # @return [SmartCore::Initializer::TypeSystem::Interop]
31 | #
32 | # @api private
33 | # @since 0.8.0
34 | def prepare_type_param(type, type_system)
35 | type_primitive =
36 | if type.is_a?(::String) || type.is_a?(::Symbol)
37 | type_system.type_from_alias(type)
38 | else
39 | type
40 | end
41 |
42 | type_system.create(type_primitive)
43 | end
44 |
45 | # @param cast [Boolean]
46 | # @return [Boolean]
47 | #
48 | # @api private
49 | # @since 0.8.0
50 | def prepare_cast_param(cast)
51 | unless cast.is_a?(::TrueClass) || cast.is_a?(::FalseClass)
52 | raise(SmartCore::Initializer::ArgumentError, <<~ERROR_MESSAGE)
53 | Attribute cast should be a type of boolean
54 | ERROR_MESSAGE
55 | end
56 |
57 | cast
58 | end
59 |
60 | # @param privacy [String, Symbol]
61 | # @return [Symbol]
62 | #
63 | # @api private
64 | # @since 0.8.0
65 | def prepare_privacy_param(privacy)
66 | unless privacy.is_a?(::String) || privacy.is_a?(::Symbol)
67 | raise(SmartCore::Initializer::ArgumentError, <<~ERROR_MESSAGE)
68 | Attribute privacy should be a type of String or Symbol
69 | ERROR_MESSAGE
70 | end
71 |
72 | SmartCore::Initializer::Attribute::Value::Base::PRIVACY_MODES.fetch(privacy.to_sym) do
73 | raise(SmartCore::Initializer::ArgumentError, <<~ERROR_MESSAGE)
74 | Incorrect attribute privacy identifier "#{privacy}"
75 | ERROR_MESSAGE
76 | end
77 | end
78 |
79 | # @param finalize [String, Symbol, Proc]
80 | # @return [SmartCore::Initializer::Attribute::Finalizer::AnonymousBlock/InstanceMethod]
81 | #
82 | # @api private
83 | # @since 0.8.0
84 | # @version 0.9.1
85 | def prepare_finalize_param(finalize)
86 | unless finalize.is_a?(::String) || finalize.is_a?(::Symbol) || finalize.is_a?(::Proc)
87 | raise(SmartCore::Initializer::ArgumentError, <<~ERROR_MESSAGE)
88 | Attribute finalizer should be a type of String, Symbol or Proc
89 | ERROR_MESSAGE
90 | end
91 |
92 | # rubocop:disable Style/SoleNestedConditional
93 | if finalize.is_a?(::Proc) && finalize.lambda?
94 | unless allowed_lambda_arity.bsearch { |element| finalize.arity <=> element }
95 | raise(
96 | SmartCore::Initializer::ArgumentError,
97 | "Lambda-based finalizer should have arity " \
98 | "equal to #{allowed_lambda_arity.join(', ')} " \
99 | "(your lambda object should require one attribute)"
100 | ) # TODO: show the name of attribute in error message (if the name is a valid, of course)
101 | end
102 | end
103 | # rubocop:enable Style/SoleNestedConditional
104 |
105 | SmartCore::Initializer::Attribute::Finalizer.create(finalize)
106 | end
107 |
108 | # return [Array]
109 | # @api private
110 | # @since 0.9.1
111 | def allowed_lambda_arity
112 | @allowed_lambda_arity ||= [-2, -1, 1].freeze
113 | end
114 |
115 | # @param mutable [Boolean]
116 | # @return [Boolean]
117 | #
118 | # @api private
119 | # @since 0.4.0
120 | def prepare_mutable_param(mutable)
121 | unless mutable.is_a?(::FalseClass) || mutable.is_a?(::TrueClass)
122 | raise(SmartCore::Initializer::ArgumentError, <<~ERROR_MESSAGE)
123 | :mutable attribute should be a type of boolean
124 | ERROR_MESSAGE
125 | end
126 |
127 | mutable
128 | end
129 |
130 | # @param as [String, Symbol, NilClass]
131 | # @return [String, Symbol, NilClass]
132 | #
133 | # @api private
134 | # @since 0.4.0
135 | def prepare_as_param(as)
136 | unless as.is_a?(::NilClass) || as.is_a?(::String) || as.is_a?(::Symbol)
137 | raise(SmartCore::Initializer::ArgumentError, <<~ERROR_MESSAGE)
138 | Attribute alias should be a type of String or Symbol
139 | ERROR_MESSAGE
140 | end
141 |
142 | as
143 | end
144 |
145 | # @param type_system [String, Symbol]
146 | # @return [Class]
147 | #
148 | # @api private
149 | # @since 0.8.0
150 | def prepare_type_system_param(type_system)
151 | SmartCore::Initializer::TypeSystem.resolve(type_system)
152 | end
153 | end
154 | end
155 |
--------------------------------------------------------------------------------
/lib/smart_core/initializer/attribute/factory/option.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module SmartCore::Initializer::Attribute::Factory
4 | # @api private
5 | # @since 0.8.0
6 | class Option < Base
7 | class << self
8 | # @param klass [Class]
9 | # @param name [String, Symbol]
10 | # @param type [String, Symbol, Any]
11 | # @param type_system [String, Symbol]
12 | # @param privacy [String, Symbol]
13 | # @param finalize [String, Symbol, Proc]
14 | # @param cast [Boolean]
15 | # @param mutable [Boolean]
16 | # @param as [String, Symbol, NilClass]
17 | # @param default [Proc, Any]
18 | # @param optional [Boolean]
19 | # @return [SmartCore::Initializer::Attribute::Value::Option]
20 | #
21 | # @api private
22 | # @since 0.8.0
23 | def create(klass, name, type, type_system, privacy, finalize, cast, mutable, as, default, optional)
24 | prepared_name = prepare_name_param(name)
25 | prepared_privacy = prepare_privacy_param(privacy)
26 | prepared_finalize = prepare_finalize_param(finalize)
27 | prepared_cast = prepare_cast_param(cast)
28 | prepared_type_system = prepare_type_system_param(type_system)
29 | prepared_type = prepare_type_param(type, prepared_type_system)
30 | prepared_mutable = prepare_mutable_param(mutable)
31 | prepared_as = prepare_as_param(as)
32 | prepared_default = prepare_default_param(default)
33 | prepared_optional = prepare_optional_param(optional)
34 |
35 | create_attribute(
36 | klass,
37 | prepared_name,
38 | prepared_type,
39 | prepared_type_system,
40 | prepared_privacy,
41 | prepared_finalize,
42 | prepared_cast,
43 | prepared_mutable,
44 | prepared_as,
45 | prepared_default,
46 | prepared_optional
47 | )
48 | end
49 |
50 | private
51 |
52 | # @param klass [Class]
53 | # @param name [String]
54 | # @param type [SmartCore::Initializer::TypeSystem::Interop]
55 | # @param type_system [Class]
56 | # @param privacy [Symbol]
57 | # @param finalize [SmartCore::Initializer::Attribute::Finalizer::Abstract]
58 | # @param cast [Boolean]
59 | # @param mutable [Boolean]
60 | # @param as [String, Symbol]
61 | # @param default [Proc, Any]
62 | # @param optional [Boolean]
63 | # @return [SmartCore::Initializer::Attribute::Value::Option]
64 | #
65 | # @api private
66 | # @since 0.8.0
67 | def create_attribute(
68 | klass,
69 | name,
70 | type,
71 | type_system,
72 | privacy,
73 | finalize,
74 | cast,
75 | mutable,
76 | as,
77 | default,
78 | optional
79 | )
80 | SmartCore::Initializer::Attribute::Value::Option.new(
81 | klass, name, type, type_system, privacy, finalize, cast, mutable, as, default, optional
82 | )
83 | end
84 |
85 | # @param default [Proc, Any]
86 | # @return [Proc, Any]
87 | #
88 | # @api private
89 | # @since 0.8.0
90 | def prepare_default_param(default)
91 | # NOTE: this "strange" approach is used to comply the default Factory methods approach here
92 | default
93 | end
94 |
95 | # @param optional [Boolean]
96 | # @return [Boolean]
97 | #
98 | # @api private
99 | # @since 0.8.0
100 | def prepare_optional_param(optional)
101 | unless optional.is_a?(::TrueClass) || optional.is_a?(::FalseClass)
102 | raise(SmartCore::Initializer::ArgumentError, <<~ERROR_MESSAGE)
103 | :optional attribute should be a type of boolean
104 | ERROR_MESSAGE
105 | end
106 |
107 | optional
108 | end
109 | end
110 | end
111 | end
112 |
--------------------------------------------------------------------------------
/lib/smart_core/initializer/attribute/factory/param.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module SmartCore::Initializer::Attribute::Factory
4 | # @api private
5 | # @since 0.8.0
6 | class Param < Base
7 | class << self
8 | # @param klass [Class]
9 | # @param name [String, Symbol]
10 | # @param type [String, Symbol, Any]
11 | # @param type_system [String, Symbol]
12 | # @param privacy [String, Symbol]
13 | # @param finalize [String, Symbol, Proc]
14 | # @param cast [Boolean]
15 | # @param mutable [Boolean]
16 | # @param as [String, Symbol, NilClass]
17 | # @return [SmartCore::Initializer::Attribute::Value::Param]
18 | #
19 | # @api private
20 | # @since 0.8.0
21 | def create(klass, name, type, type_system, privacy, finalize, cast, mutable, as)
22 | prepared_name = prepare_name_param(name)
23 | prepared_privacy = prepare_privacy_param(privacy)
24 | prepared_finalize = prepare_finalize_param(finalize)
25 | prepared_cast = prepare_cast_param(cast)
26 | prepared_type_system = prepare_type_system_param(type_system)
27 | prepared_type = prepare_type_param(type, prepared_type_system)
28 | prepared_mutable = prepare_mutable_param(mutable)
29 | prepared_as = prepare_as_param(as)
30 |
31 | create_attribute(
32 | klass,
33 | prepared_name,
34 | prepared_type,
35 | prepared_type_system,
36 | prepared_privacy,
37 | prepared_finalize,
38 | prepared_cast,
39 | prepared_mutable,
40 | prepared_as
41 | )
42 | end
43 |
44 | private
45 |
46 | # @param name [String]
47 | # @param type [SmartCore::Initializer::TypeSystem::Interop]
48 | # @param type_system [Class]
49 | # @param privacy [Symbol]
50 | # @param finalize [SmartCore::Initializer::Attribute::Finalizer::Abstract]
51 | # @param cast [Boolean]
52 | # @param mutable [Boolean]
53 | # @param as [String, Symbol]
54 | # @return [SmartCore::Initializer::Attribute::Value::Param]
55 | #
56 | # @api private
57 | # @since 0.8.0
58 | def create_attribute(klass, name, type, type_system, privacy, finalize, cast, mutable, as)
59 | SmartCore::Initializer::Attribute::Value::Param.new(
60 | klass, name, type, type_system, privacy, finalize, cast, mutable, as
61 | )
62 | end
63 | end
64 | end
65 | end
66 |
--------------------------------------------------------------------------------
/lib/smart_core/initializer/attribute/finalizer.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # @api private
4 | # @since 0.1.0
5 | module SmartCore::Initializer::Attribute::Finalizer
6 | require_relative 'finalizer/abstract'
7 | require_relative 'finalizer/instance_method'
8 | require_relative 'finalizer/anonymous_block'
9 |
10 | class << self
11 | # @param finalization_approach [String, Symbol, Proc]
12 | # @return [SmartCore::Initializer::Attribute::Finalizer::InstanceMethod]
13 | # @return [SmartCore::Initializer::Attribute::Finalizer::AnonymousBlock]
14 | #
15 | # @api private
16 | # @since 0.1.0
17 | # @version 0.8.0
18 | def create(finalization_approach)
19 | case finalization_approach
20 | when String, Symbol
21 | InstanceMethod.new(finalization_approach)
22 | when Proc
23 | AnonymousBlock.new(finalization_approach)
24 | else
25 | raise(
26 | SmartCore::Initializer::ArgumentError,
27 | 'Finalization approach object should be a type of Proc, Symbol or String'
28 | )
29 | end
30 | end
31 | end
32 | end
33 |
--------------------------------------------------------------------------------
/lib/smart_core/initializer/attribute/finalizer/abstract.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # @api private
4 | # @since 0.1.0
5 | class SmartCore::Initializer::Attribute::Finalizer::Abstract
6 | # @param finalizer [Any]
7 | # @return [void]
8 | #
9 | # @api private
10 | # @since 0.1.0
11 | def initialize(finalizer)
12 | @finalizer = finalizer
13 | end
14 |
15 | # @param value [Any]
16 | # @param instance [Any]
17 | # @return [Any]
18 | #
19 | # @api private
20 | # @since 0.1.0
21 | def call(value, instance)
22 | # :nocov:
23 | raise NoMethodError
24 | # :nocov:
25 | end
26 |
27 | # @return [SmartCore::Initializer::Attribute::Finalizer::Abstract]
28 | def dup
29 | self.class.new(finalizer)
30 | end
31 |
32 | private
33 |
34 | # @return [Any]
35 | #
36 | # @api private
37 | # @since 0.1.0
38 | attr_reader :finalizer
39 | end
40 |
--------------------------------------------------------------------------------
/lib/smart_core/initializer/attribute/finalizer/anonymous_block.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module SmartCore::Initializer::Attribute::Finalizer
4 | # @pai private
5 | # @since 0.1.0
6 | class AnonymousBlock < Abstract
7 | # @param finalizer [Proc]
8 | # @return [void]
9 | #
10 | # @api private
11 | # @since 0.1.0
12 | def initialize(finalizer)
13 | @finalizer = finalizer
14 | end
15 |
16 | # @param value [Any]
17 | # @param instance [Any]
18 | # @return [value]
19 | #
20 | # @pai private
21 | # @since 0.1.0
22 | def call(value, instance)
23 | instance.instance_exec(value, &finalizer)
24 | end
25 |
26 | private
27 |
28 | # @return [NilClass, Any]
29 | #
30 | # @api private
31 | # @since 0.1.0
32 | attr_reader :finalizer
33 | end
34 | end
35 |
--------------------------------------------------------------------------------
/lib/smart_core/initializer/attribute/finalizer/instance_method.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module SmartCore::Initializer::Attribute::Finalizer
4 | # @pai private
5 | # @since 0.1.0
6 | class InstanceMethod < Abstract
7 | # @param finalizer [String, Symbol]
8 | # @return [void]
9 | #
10 | # @api private
11 | # @since 0.1.0
12 | def initialize(finalizer)
13 | @finalizer = finalizer
14 | end
15 |
16 | # @param value [Any]
17 | # @param instance [Any]
18 | # @return [value]
19 | #
20 | # @pai private
21 | # @since 0.1.0
22 | def call(value, instance)
23 | instance.__send__(finalizer, value)
24 | end
25 |
26 | private
27 |
28 | # @return [NilClass, Any]
29 | #
30 | # @api private
31 | # @since 0.1.0
32 | attr_reader :finalizer
33 | end
34 | end
35 |
--------------------------------------------------------------------------------
/lib/smart_core/initializer/attribute/list.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # @api private
4 | # @since 0.1.0
5 | # @version 0.10.0
6 | class SmartCore::Initializer::Attribute::List
7 | # @since 0.1.0
8 | include Enumerable
9 |
10 | # @return [void]
11 | #
12 | # @api private
13 | # @since 0.1.0
14 | # @version 0.10.0
15 | def initialize
16 | @attributes = {}
17 | @lock = SmartCore::Engine::ReadWriteLock.new
18 | end
19 |
20 | # @param attribute_name [Symbol]
21 | # @return [SmartCore::Initializer::Atribute]
22 | #
23 | # @raise [SmartCore::Initializer::UndefinedAttributeError]
24 | #
25 | # @api private
26 | # @since 0.8.0
27 | # @version 0.10.0
28 | def fetch(attribute_name)
29 | @lock.read_sync do
30 | raise(
31 | ::SmartCore::Initializer::UndefinedAttributeError,
32 | "Attribute with `#{attribute_name}` name is not defined in your constructor. " \
33 | "Please, check your attribute definitions inside your class."
34 | ) unless attributes.key?(attribute_name)
35 |
36 | attributes[attribute_name]
37 | end
38 | end
39 | alias_method :[], :fetch
40 |
41 | # @param attribute [SmartCore::Initializer::Attribute]
42 | # @return [void]
43 | #
44 | # @api private
45 | # @since 0.1.0
46 | # @version 0.10.0
47 | def add(attribute)
48 | @lock.write_sync { attributes[attribute.name] = attribute }
49 | end
50 | alias_method :<<, :add
51 |
52 | # @param list [SmartCore::Initializer::Attribute::List]
53 | # @return [void]
54 | #
55 | # @api private
56 | # @since 0.1.0
57 | # @version 0.10.0
58 | def concat(list)
59 | @lock.write_sync do
60 | list.each { |attribute| add(attribute.dup) }
61 | end
62 | end
63 |
64 | # @param attribute [SmartCore::Initializer::Attribute]
65 | # @return [void]
66 | #
67 | # @api private
68 | # @since 0.1.0
69 | def include?(attribute)
70 | @lock.read_sync { attributes.key?(attribute.name) }
71 | end
72 |
73 | # @param block [Block]
74 | # @return [Enumerable]
75 | #
76 | # @api private
77 | # @since 0.1.0
78 | # @version 0.10.0
79 | def each(&block)
80 | @lock.read_sync do
81 | block_given? ? attributes.values.each(&block) : attributes.values.each
82 | end
83 | end
84 |
85 | # @return [Integer]
86 | #
87 | # @api private
88 | # @since 0.1.0
89 | # @version 0.10.0
90 | def size
91 | @lock.read_sync { attributes.size }
92 | end
93 |
94 | # @param block [Block]
95 | # @return [Integer]
96 | #
97 | # @api private
98 | # @since 0.1.0
99 | # @version 0.10.0
100 | def count(&block)
101 | @lock.read_sync { attributes.values.count(&block) }
102 | end
103 |
104 | private
105 |
106 | # @return [Hash]
107 | #
108 | # @api private
109 | # @since 0.1.0
110 | attr_reader :attributes
111 | end
112 |
--------------------------------------------------------------------------------
/lib/smart_core/initializer/attribute/value.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # @api private
4 | # @since 0.8.0
5 | module SmartCore::Initializer::Attribute::Value
6 | require_relative 'value/base'
7 | require_relative 'value/param'
8 | require_relative 'value/option'
9 | end
10 |
--------------------------------------------------------------------------------
/lib/smart_core/initializer/attribute/value/base.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # @api private
4 | # @since 0.8.0
5 | # @version 0.11.0
6 | class SmartCore::Initializer::Attribute::Value::Base
7 | # @return [Hash]
8 | #
9 | # @api private
10 | # @since 0.8.0
11 | PRIVACY_MODES = {
12 | public: :public,
13 | protected: :protected,
14 | private: :private
15 | }.freeze
16 |
17 | # @return [Symbol]
18 | #
19 | # @api private
20 | # @since 0.8.0
21 | DEFAULT_PRIVACY_MODE = PRIVACY_MODES[:public]
22 |
23 | # @return [Proc]
24 | #
25 | # @api private
26 | # @since 0.8.0
27 | DEFAULT_FINALIZER = proc { |value| value }.freeze
28 |
29 | # @return [NilClass]
30 | #
31 | # @api private
32 | # @since 0.8.0
33 | DEFAULT_AS = nil
34 |
35 | # @return [Boolean]
36 | #
37 | # @api private
38 | # @since 0.8.0
39 | DEFAULT_MUTABLE = false
40 |
41 | # @return [Class]
42 | #
43 | # @api private
44 | # @since 0.12.0
45 | attr_reader :klass
46 |
47 | # @return [Symbol]
48 | #
49 | # @api private
50 | # @since 0.8.0
51 | attr_reader :name
52 |
53 | # @return [SmartCore::Initializer::TypeSystem::Interop]
54 | #
55 | # @api private
56 | # @since 0.8.0
57 | attr_reader :type
58 |
59 | # @return [Class]
60 | #
61 | # @api private
62 | # @since 0.8.0
63 | attr_reader :type_system
64 |
65 | # @return [Symbol]
66 | #
67 | # @api private
68 | # @since 0.8.0
69 | attr_reader :privacy
70 |
71 | # @return [SmartCore::Initializer::Attribute::Finalizer::AnonymousBlock]
72 | # @return [SmartCore::Initializer::Attribute::Finalizer::InstanceMethod]
73 | #
74 | # @api private
75 | # @since 0.8.0
76 | attr_reader :finalizer
77 |
78 | # @return [Boolean]
79 | #
80 | # @api private
81 | # @since 0.8.0
82 | attr_reader :cast
83 | alias_method :cast?, :cast
84 |
85 | # @return [Boolean]
86 | #
87 | # @api private
88 | # @since 0.8.0
89 | attr_reader :mutable
90 | alias_method :mutable?, :mutable
91 |
92 | # @return [String, Symbol, NilClass]
93 | #
94 | # @api private
95 | # @since 0.8.0
96 | attr_reader :as
97 |
98 | # @param klass [Class]
99 | # @param name [Symbol]
100 | # @param type [SmartCore::Initializer::TypeSystem::Interop]
101 | # @param type_system [Class]
102 | # @param privacy [Symbol]
103 | # @param finalizer [SmartCore::Initializer::Attribute::AnonymousBlock/InstanceMethod]
104 | # @param cast [Boolean]
105 | # @param mutable [Boolean]
106 | # @param as [NilClass, Symbol, String]
107 | # @return [void]
108 | #
109 | # @api private
110 | # @since 0.8.0
111 | def initialize(klass, name, type, type_system, privacy, finalizer, cast, mutable, as)
112 | @klass = klass
113 | @name = name
114 | @type = type
115 | @type_system = type_system
116 | @privacy = privacy
117 | @finalizer = finalizer
118 | @cast = cast
119 | @mutable = mutable
120 | @as = as
121 | end
122 |
123 | # @param value [Any]
124 | # @return [void]
125 | #
126 | # @raise [SmartCore::Initializer::IncorrectTypeError]
127 | #
128 | # @api private
129 | # @since 0.8.0
130 | # @version 0.11.0
131 | def validate!(value)
132 | unless type.valid?(value)
133 | raise(
134 | SmartCore::Initializer::IncorrectTypeError,
135 | "Validation of attribute `#{klass}##{name}` failed. " \
136 | "Expected: #{type.identifier}, got: #{truncate(value.inspect, 100)}",
137 | )
138 | end
139 | end
140 |
141 | private
142 |
143 | def truncate(string, max_length)
144 | return string unless string.size > max_length
145 | omission = "..."
146 | "#{string[0, max_length - omission.size]}#{omission}"
147 | end
148 | end
149 |
--------------------------------------------------------------------------------
/lib/smart_core/initializer/attribute/value/option.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module SmartCore::Initializer::Attribute::Value
4 | # @api private
5 | # @since 0.8.0
6 | class Option < Base
7 | # @return [Object]
8 | #
9 | # @api private
10 | # @since 0.8.0
11 | UNDEFINED_DEFAULT = ::Object.new.tap(&:freeze)
12 |
13 | # @return [Boolean]
14 | #
15 | # @api private
16 | # @since 0.8.0
17 | DEFAULT_OPTIONAL = false
18 |
19 | # @return [Boolean]
20 | #
21 | # @api private
22 | # @since 0.8.0
23 | attr_reader :optional
24 | alias_method :optional?, :optional
25 |
26 | # @param klass [Class]
27 | # @param name [Symbol]
28 | # @param type [SmartCore::Initializer::TypeSystem::Interop]
29 | # @param type_system [Class]
30 | # @param privacy [Symbol]
31 | # @param finalizer [SmartCore::Initializer::Attribute::AnonymousBlock/InstanceMethod]
32 | # @param cast [Boolean]
33 | # @param mutable [Boolean]
34 | # @param as [NilClass, Symbol, String]
35 | # @param default [Proc, Any]
36 | # @param optional [Boolean]
37 | # @return [void]
38 | #
39 | # @api private
40 | # @since 0.8.0
41 | def initialize(
42 | klass,
43 | name,
44 | type,
45 | type_system,
46 | privacy,
47 | finalizer,
48 | cast,
49 | mutable,
50 | as,
51 | default,
52 | optional
53 | )
54 | super(klass, name, type, type_system, privacy, finalizer, cast, mutable, as)
55 | @default = default
56 | @optional = optional
57 | end
58 |
59 | # @return [Boolean]
60 | #
61 | # @api private
62 | # @since 0.8.0
63 | def has_default?
64 | !@default.equal?(UNDEFINED_DEFAULT)
65 | end
66 |
67 | # @return [Any]
68 | #
69 | # @raise [SmartCore::Initializer::NoDefaultValueError]
70 | #
71 | # @api private
72 | # @since 0.8.0
73 | def default
74 | raise(
75 | SmartCore::Initializer::NoDefaultValueError,
76 | "Attribute #{name} has no default value"
77 | ) if @default.equal?(UNDEFINED_DEFAULT)
78 |
79 | @default.is_a?(Proc) ? @default.call : @default.dup
80 | end
81 |
82 | # @return [SmartCore::Initializer::Attribute::Value::Option]
83 | #
84 | # @api private
85 | # @since 0.8.0
86 | def dup
87 | default = @default.equal?(UNDEFINED_DEFAULT) ? @default : @default.dup
88 |
89 | self.class.new(
90 | klass,
91 | name.dup,
92 | type,
93 | type_system,
94 | privacy,
95 | finalizer.dup,
96 | cast,
97 | mutable,
98 | as,
99 | default,
100 | optional
101 | )
102 | end
103 | end
104 | end
105 |
--------------------------------------------------------------------------------
/lib/smart_core/initializer/attribute/value/param.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module SmartCore::Initializer::Attribute::Value
4 | # @api private
5 | # @since 0.8.0
6 | class Param < Base
7 | # @return [SmartCore::Initializer::Attribute::Value::Param]
8 | #
9 | # @api private
10 | # @since 0.8.0
11 | def dup
12 | self.class.new(
13 | klass,
14 | name.dup,
15 | type,
16 | type_system,
17 | privacy,
18 | finalizer.dup,
19 | cast,
20 | mutable,
21 | as
22 | )
23 | end
24 | end
25 | end
26 |
--------------------------------------------------------------------------------
/lib/smart_core/initializer/configuration.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'qonfig'
4 |
5 | # @api public
6 | # @since 0.1.0
7 | module SmartCore::Initializer::Configuration
8 | # @since 0.1.0
9 | include Qonfig::Configurable
10 |
11 | # @api public
12 | # @since 0.1.0
13 | extend SmartCore::Initializer::Plugins::AccessMixin
14 |
15 | class << self
16 | # @param setting_key [String, Symbol]
17 | # @return [Qonfig::Settings, Any]
18 | #
19 | # @api private
20 | # @since 0.1.0
21 | def [](setting_key)
22 | config[setting_key]
23 | end
24 | end
25 |
26 | # @since 0.1.0
27 | configuration do
28 | # @since 0.?.0
29 | setting :default_type_system, :smart_types
30 | validate :default_type_system do |value|
31 | SmartCore::Initializer::TypeSystem.resolve(value) rescue false
32 | end
33 |
34 | # @since 0.?.0
35 | setting :strict_options, true
36 | validate :strict_options, :boolean
37 |
38 | # @since 0.8.0
39 | setting :auto_cast, false
40 | validate :auto_cast, :boolean
41 | end
42 | end
43 |
--------------------------------------------------------------------------------
/lib/smart_core/initializer/constructor.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # @api private
4 | # @since 0.1.0
5 | class SmartCore::Initializer::Constructor
6 | require_relative 'constructor/definer'
7 |
8 | # @param klass [Class]
9 | # @param arguments [Array]
10 | # @param block [Proc, NilClass]
11 | # @return [void]
12 | #
13 | # @api private
14 | # @since 0.1.0
15 | def initialize(klass, arguments, block)
16 | @klass = klass
17 | @arguments = arguments
18 | @parameters, @options = extract_attributes(arguments)
19 | @block = block
20 | end
21 |
22 | # @return [Any]
23 | #
24 | # @api private
25 | # @since 0.1.0
26 | def construct
27 | allocate_klass_instance.tap do |instance|
28 | prevent_attribute_insufficiency
29 | initialize_parameters(instance)
30 | initialize_options(instance)
31 | process_original_initializer(instance)
32 | process_init_extensions(instance)
33 | end
34 | end
35 |
36 | private
37 |
38 | # @return [Class]
39 | #
40 | # @api private
41 | # @since 0.1.0
42 | attr_reader :klass
43 |
44 | # @return [Array]
45 | #
46 | # @api private
47 | # @since 0.1.0
48 | attr_reader :arguments
49 |
50 | # @return [Array]
51 | #
52 | # @api private
53 | # @since 0.1.0
54 | attr_reader :parameters
55 |
56 | # @return [Hash]
57 | #
58 | # @api private
59 | # @since 0.1.0
60 | attr_reader :options
61 |
62 | # @return [Proc, NilClass]
63 | #
64 | # @api private
65 | # @since 0.1.0
66 | attr_reader :block
67 |
68 | # @return [void]
69 | #
70 | # @raise [SmartCore::Initializer::ParameterArgumentError]
71 | # @raise [SmartCore::Initializer::OptionArgumentError]
72 | #
73 | # @api private
74 | # @since 0.1.0
75 | # @version 0.8.0
76 | # rubocop:disable Metrics/AbcSize
77 | def prevent_attribute_insufficiency
78 | required_parameter_count = klass.__params__.size
79 |
80 | raise(
81 | SmartCore::Initializer::ParameterArgumentError,
82 | "Wrong number of parameters " \
83 | "(given #{parameters.size}, expected #{required_parameter_count})"
84 | ) unless parameters.size == required_parameter_count
85 |
86 | required_options = klass.__options__.reject(&:has_default?).reject(&:optional?).map(&:name)
87 | missing_options = required_options.reject { |option_name| options.key?(option_name) }
88 |
89 | raise(
90 | SmartCore::Initializer::OptionArgumentError,
91 | "Missing options: #{missing_options.join(', ')}"
92 | ) if missing_options.any?
93 |
94 | possible_options = klass.__options__.map(&:name)
95 | unknown_options = options.keys - possible_options
96 |
97 | raise(
98 | SmartCore::Initializer::OptionArgumentError,
99 | "Unknown options: #{unknown_options.join(', ')}"
100 | ) if klass.__initializer_settings__.strict_options && unknown_options.any?
101 | end
102 | # rubocop:enable Metrics/AbcSize
103 |
104 | # @return [Any]
105 | #
106 | # @api private
107 | # @since 0.1.0
108 | def allocate_klass_instance
109 | klass.allocate
110 | end
111 |
112 | # @param instance [Any]
113 | # @return [void]
114 | #
115 | # @api private
116 | # @since 0.1.0
117 | # @version 0.5.1
118 | def initialize_parameters(instance)
119 | parameter_definitions = klass.__params__.zip(parameters).to_h
120 |
121 | parameter_definitions.each_pair do |attribute, parameter_value|
122 | if !attribute.type.valid?(parameter_value) && attribute.cast?
123 | parameter_value = attribute.type.cast(parameter_value)
124 | end
125 |
126 | attribute.validate!(parameter_value)
127 | final_value = attribute.finalizer.call(parameter_value, instance)
128 | attribute.validate!(final_value)
129 |
130 | instance.instance_variable_set("@#{attribute.name}", final_value)
131 | end
132 | end
133 |
134 | # @param instance [Any]
135 | # @return [void]
136 | #
137 | # @api private
138 | # @since 0.1.0
139 | # @version 0.8.0
140 | # rubocop:disable Metrics/AbcSize
141 | def initialize_options(instance)
142 | klass.__options__.each do |attribute|
143 | option_value = options.fetch(attribute.name) do
144 | # NOTE: `nil` case is a case when an option is `optional`
145 | attribute.has_default? ? attribute.default : nil
146 | end
147 |
148 | # NOTE: (if-block: what if `if` receives `false`?):
149 | # For other case passed `attribute` is optional and
150 | # should not be type-checked/type-casted/etc.
151 | # But optional attributes with defined `default` setting should be
152 | # type-checked and type-casted.
153 | if options.key?(attribute.name) || attribute.has_default?
154 | if !attribute.type.valid?(option_value) && attribute.cast?
155 | option_value = attribute.type.cast(option_value)
156 | end
157 |
158 | attribute.validate!(option_value)
159 | option_value = attribute.finalizer.call(option_value, instance)
160 | attribute.validate!(option_value)
161 | end
162 |
163 | instance.instance_variable_set("@#{attribute.name}", option_value)
164 | end
165 | end
166 | # rubocop:enable Metrics/AbcSize
167 |
168 | # @param instance [Any]
169 | # @return [void]
170 | #
171 | # @api private
172 | # @since 0.1.0
173 | def process_original_initializer(instance)
174 | instance.__send__(:initialize, *arguments, &block)
175 | end
176 |
177 | # @param instance [Any]
178 | # @return [void]
179 | #
180 | # @api private
181 | # @since 0.1.0
182 | def process_init_extensions(instance)
183 | klass.__init_extensions__.each do |extension|
184 | extension.call(instance)
185 | end
186 | end
187 |
188 | # @param arguments [Array]
189 | # @return [Array,Hash>]
190 | #
191 | # @api private
192 | # @since 0.1.0
193 | def extract_attributes(arguments)
194 | extracted_parameters = []
195 | extracted_options = {}
196 |
197 | if (
198 | klass.__options__.size >= 1 &&
199 | arguments.last.is_a?(Hash) &&
200 | arguments.last.keys.all? { |key| key.is_a?(Symbol) }
201 | )
202 | extracted_parameters = arguments[0..-2]
203 | extracted_options = arguments.last
204 | else
205 | extracted_parameters = arguments
206 | end
207 |
208 | [extracted_parameters, extracted_options]
209 | end
210 | end
211 |
--------------------------------------------------------------------------------
/lib/smart_core/initializer/constructor/definer.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # @api private
4 | # @since 0.1.0
5 | # @version 0.10.0
6 | # rubocop:disable Metrics/ClassLength
7 | class SmartCore::Initializer::Constructor::Definer
8 | # @param klass [Class]
9 | # @return [void]
10 | #
11 | # @api private
12 | # @since 0.1.0
13 | # @version 0.10.0
14 | def initialize(klass)
15 | @klass = klass
16 | @lock = SmartCore::Engine::ReadWriteLock.new
17 | end
18 |
19 | # @param block [Proc]
20 | # @return [void]
21 | #
22 | # @api private
23 | # @since 0.1.0
24 | # @version 0.10.0
25 | def define_init_extension(block)
26 | @lock.write_sync do
27 | add_init_extension(build_init_extension(block))
28 | end
29 | end
30 |
31 | # @param name [String, Symbol]
32 | # @param type [String, Symbol, Any]
33 | # @param type_system [String, Symbol]
34 | # @param privacy [String, Symbol]
35 | # @param finalize [String, Symbol, Proc]
36 | # @param cast [Boolean]
37 | # @param mutable [Boolean]
38 | # @param as [String, Symbol, NilClass]
39 | # @return [void]
40 | #
41 | # @api private
42 | # @since 0.1.0
43 | # @version 0.10.0
44 | def define_parameter(
45 | name,
46 | type,
47 | type_system,
48 | privacy,
49 | finalize,
50 | cast,
51 | mutable,
52 | as
53 | )
54 | @lock.write_sync do
55 | attribute = build_param_attribute(
56 | name,
57 | type,
58 | type_system,
59 | privacy,
60 | finalize,
61 | cast,
62 | mutable,
63 | as
64 | )
65 | prevent_option_overlap(attribute)
66 | add_parameter(attribute)
67 | end
68 | end
69 |
70 | # @param names [Array]
71 | # @option mutable [Boolean]
72 | # @option privacy [String, Symbol]
73 | # @return [void]
74 | #
75 | # @api private
76 | # @since 0.1.0
77 | # @version 0.10.0
78 | def define_parameters(*names, mutable:, privacy:)
79 | @lock.write_sync do
80 | names.map do |name|
81 | build_param_attribute(
82 | name,
83 | klass.__initializer_settings__.generic_type_object,
84 | klass.__initializer_settings__.type_system,
85 | privacy,
86 | SmartCore::Initializer::Attribute::Value::Param::DEFAULT_FINALIZER,
87 | klass.__initializer_settings__.auto_cast,
88 | mutable,
89 | SmartCore::Initializer::Attribute::Value::Param::DEFAULT_AS
90 | ).tap do |attribute|
91 | prevent_option_overlap(attribute)
92 | end
93 | end.each do |attribute|
94 | add_parameter(attribute)
95 | end
96 | end
97 | end
98 |
99 | # @param name [String, Symbol]
100 | # @param type [String, Symbol, Any]
101 | # @param type_system [String, Symbol]
102 | # @param privacy [String, Symbol]
103 | # @param finalize [String, Symbol, Proc]
104 | # @param cast [Boolean]
105 | # @param mutable [Boolean]
106 | # @param as [String, Symbol, NilClass]
107 | # @param default [Proc, Any]
108 | # @param optional [Boolean]
109 | # @return [void]
110 | #
111 | # @api private
112 | # @since 0.1.0
113 | # @version 0.10.0
114 | def define_option(
115 | name,
116 | type,
117 | type_system,
118 | privacy,
119 | finalize,
120 | cast,
121 | mutable,
122 | as,
123 | default,
124 | optional
125 | )
126 | @lock.write_sync do
127 | attribute = build_option_attribute(
128 | name,
129 | type,
130 | type_system,
131 | privacy,
132 | finalize,
133 | cast,
134 | mutable,
135 | as,
136 | default,
137 | optional
138 | )
139 | prevent_parameter_overlap(attribute)
140 | add_option(attribute)
141 | end
142 | end
143 |
144 | # @param names [Array]
145 | # @option mutable [Boolean]
146 | # @option privacy [String, Symbol]
147 | # @return [void]
148 | #
149 | # @api private
150 | # @since 0.1.0
151 | # @version 0.10.0
152 | def define_options(*names, mutable:, privacy:)
153 | @lock.write_sync do
154 | names.map do |name|
155 | build_option_attribute(
156 | name,
157 | klass.__initializer_settings__.generic_type_object,
158 | klass.__initializer_settings__.type_system,
159 | privacy,
160 | SmartCore::Initializer::Attribute::Value::Option::DEFAULT_FINALIZER,
161 | klass.__initializer_settings__.auto_cast,
162 | mutable,
163 | SmartCore::Initializer::Attribute::Value::Option::DEFAULT_AS,
164 | SmartCore::Initializer::Attribute::Value::Option::UNDEFINED_DEFAULT,
165 | SmartCore::Initializer::Attribute::Value::Option::DEFAULT_OPTIONAL
166 | ).tap do |attribute|
167 | prevent_parameter_overlap(attribute)
168 | end
169 | end.each do |attribute|
170 | add_option(attribute)
171 | end
172 | end
173 | end
174 |
175 | private
176 |
177 | # @return [Class]
178 | #
179 | # @api private
180 | # @since 0.1.0
181 | attr_reader :klass
182 |
183 | # @param name [String, Symbol]
184 | # @param type [String, Symbol, Any]
185 | # @param type_system [String, Symbol]
186 | # @param privacy [String, Symbol]
187 | # @param finalize [String, Symbol, Proc]
188 | # @param cast [Boolean]
189 | # @param mutable [Boolean]
190 | # @param as [String, Symbol, NilClass]
191 | # @return [SmartCore::Initializer::Attribute::Value::Param]
192 | #
193 | # @api private
194 | # @since 0.1.0
195 | # @version 0.8.0
196 | def build_param_attribute(
197 | name,
198 | type,
199 | type_system,
200 | privacy,
201 | finalize,
202 | cast,
203 | mutable,
204 | as
205 | )
206 | SmartCore::Initializer::Attribute::Factory::Param.create(
207 | klass, name, type, type_system, privacy, finalize, cast, mutable, as
208 | )
209 | end
210 |
211 | # @param name [String, Symbol]
212 | # @param type [String, Symbol, Any]
213 | # @param type_system [String, Symbol]
214 | # @param privacy [String, Symbol]
215 | # @param finalize [String, Symbol, Proc]
216 | # @param cast [Boolean]
217 | # @param mutable [Boolean]
218 | # @param as [String, Symbol, NilClass]
219 | # @param default [Proc, Any]
220 | # @param optional [Boolean]
221 | # @return [SmartCore::Initializer::Attribute::Value::Option]
222 | #
223 | # @api private
224 | # @since 0.1.0
225 | # @version 0.8.0
226 | def build_option_attribute(
227 | name,
228 | type,
229 | type_system,
230 | privacy,
231 | finalize,
232 | cast,
233 | mutable,
234 | as,
235 | default,
236 | optional
237 | )
238 | SmartCore::Initializer::Attribute::Factory::Option.create(
239 | klass, name, type, type_system, privacy, finalize, cast, mutable, as, default, optional
240 | )
241 | end
242 |
243 | # @param block [Proc]
244 | # @return [SmartCore::Initializer::Extensions::ExtInit]
245 | #
246 | # @api private
247 | # @since 0.1.0
248 | def build_init_extension(block)
249 | SmartCore::Initializer::Extensions::ExtInit.new(block)
250 | end
251 |
252 | # @param parameter [SmartCore::Initializer::Attribute::Value::Param]
253 | # @return [void]
254 | #
255 | # @api private
256 | # @since 0.1.0
257 | # @version 0.8.0
258 | # rubocop:disable Metrics/AbcSize
259 | def add_parameter(parameter)
260 | klass.__params__ << parameter
261 | klass.__send__(:attr_reader, parameter.name)
262 | klass.__send__(parameter.privacy, parameter.name)
263 |
264 | if parameter.mutable?
265 | # NOTE:
266 | # code evaluation approach is used instead of `define_method` approach in order
267 | # to avoid the `clojure`-context binding inside the new method (this context can
268 | # access the current context or the current variable set and the way to avoid this by
269 | # ruby method is more diffcult to support and read insead of the real `code` evaluation)
270 | klass.class_eval(<<~METHOD_CODE, __FILE__, __LINE__ + 1)
271 | #{parameter.privacy} def #{parameter.name}=(new_value)
272 | self.class.__params__[:#{parameter.name}].validate!(new_value)
273 | @#{parameter.name} = new_value
274 | end
275 | METHOD_CODE
276 | end
277 |
278 | if parameter.as
279 | klass.__send__(:alias_method, parameter.as, parameter.name)
280 | klass.__send__(:alias_method, "#{parameter.as}=", "#{parameter.name}=") if parameter.mutable?
281 |
282 | klass.__send__(parameter.privacy, parameter.as)
283 | klass.__send__(parameter.privacy, "#{parameter.as}=") if parameter.mutable?
284 | end
285 | end
286 | # rubocop:enable Metrics/AbcSize
287 |
288 | # @param option [SmartCore::Initializer::Attribute::Value::Option]
289 | # @return [void]
290 | #
291 | # @api private
292 | # @since 0.1.0
293 | # @version 0.8.0
294 | # rubocop:disable Metrics/AbcSize
295 | def add_option(option)
296 | klass.__options__ << option
297 | klass.__send__(:attr_reader, option.name)
298 | klass.__send__(option.privacy, option.name)
299 |
300 | if option.mutable?
301 | # NOTE:
302 | # code evaluation approach is used instead of `define_method` approach in order
303 | # to avoid the `clojure`-context binding inside the new method (this context can
304 | # access the current context or the current variable set and the way to avoid this by
305 | # ruby method is more diffcult to support and read insead of the real `code` evaluation)
306 | klass.class_eval(<<~METHOD_CODE, __FILE__, __LINE__ + 1)
307 | #{option.privacy} def #{option.name}=(new_value)
308 | self.class.__options__[:#{option.name}].validate!(new_value)
309 | @#{option.name} = new_value
310 | end
311 | METHOD_CODE
312 | end
313 |
314 | if option.as
315 | klass.__send__(:alias_method, option.as, option.name)
316 | klass.__send__(:alias_method, "#{option.as}=", "#{option.name}=") if option.mutable?
317 |
318 | klass.__send__(option.privacy, option.as)
319 | klass.__send__(option.privacy, "#{option.as}=") if option.mutable?
320 | end
321 | end
322 | # rubocop:enable Metrics/AbcSize
323 |
324 | # @param extension [SmartCore::Initializer::Extensions::ExtInit]
325 | # @return [void]
326 | #
327 | # @api private
328 | # @since 0.1.0
329 | def add_init_extension(extension)
330 | klass.__init_extensions__ << extension
331 | end
332 |
333 | # @param parameter [SmartCore::Initializer::Attribute]
334 | # @return [void]
335 | #
336 | # @api private
337 | # @since 0.1.0
338 | def prevent_option_overlap(parameter)
339 | if klass.__options__.include?(parameter)
340 | raise(SmartCore::Initializer::OptionOverlapError, <<~ERROR_MESSAGE)
341 | You have already defined option with name :#{parameter.name}
342 | ERROR_MESSAGE
343 | end
344 | end
345 |
346 | # @param option [SmartCore::Initializer::Attribute]
347 | # @return [void]
348 | #
349 | # @api private
350 | # @since 0.1.0
351 | def prevent_parameter_overlap(option)
352 | if klass.__params__.include?(option)
353 | raise(SmartCore::Initializer::ParameterOverlapError, <<~ERROR_MESSAGE)
354 | You have already defined parameter with name :#{option.name}
355 | ERROR_MESSAGE
356 | end
357 | end
358 | end
359 | # rubocop:enable Metrics/ClassLength
360 |
--------------------------------------------------------------------------------
/lib/smart_core/initializer/dsl.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # @api private
4 | # @since 0.1.0
5 | # @version 0.10.0
6 | module SmartCore::Initializer::DSL
7 | require_relative 'dsl/inheritance'
8 |
9 | class << self
10 | # @param base_klass [Class]
11 | # @return [void]
12 | #
13 | # @api private
14 | # @since 0.1.0
15 | # @version 0.10.0
16 | def extended(base_klass)
17 | base_klass.instance_eval do
18 | instance_variable_set(:@__params__, SmartCore::Initializer::Attribute::List.new)
19 | instance_variable_set(:@__options__, SmartCore::Initializer::Attribute::List.new)
20 | instance_variable_set(:@__init_extensions__, SmartCore::Initializer::Extensions::List.new)
21 | instance_variable_set(:@__definer__, SmartCore::Initializer::Constructor::Definer.new(self))
22 | instance_variable_set(:@__initializer_settings__, SmartCore::Initializer::Settings.new)
23 | end
24 | base_klass.extend(ClassMethods)
25 | base_klass.singleton_class.prepend(ClassInheritance)
26 | end
27 | end
28 |
29 | # @api private
30 | # @since 0.1.0
31 | # @version 0.10.0
32 | module ClassInheritance
33 | # @param child_klass [Class]
34 | # @return [void]
35 | #
36 | # @api private
37 | # @since 0.1.0
38 | # @version 0.10.0
39 | def inherited(child_klass)
40 | child_klass.instance_exec(__initializer_settings__) do |init_settings|
41 | instance_variable_set(:@__params__, SmartCore::Initializer::Attribute::List.new)
42 | instance_variable_set(:@__options__, SmartCore::Initializer::Attribute::List.new)
43 | instance_variable_set(:@__init_extensions__, SmartCore::Initializer::Extensions::List.new)
44 | instance_variable_set(:@__definer__, SmartCore::Initializer::Constructor::Definer.new(self))
45 | instance_variable_set(:@__initializer_settings__, init_settings.dup)
46 | end
47 | child_klass.extend(ClassMethods)
48 | SmartCore::Initializer::DSL::Inheritance.inherit(base: self, child: child_klass)
49 | child_klass.singleton_class.prepend(ClassInheritance)
50 | super
51 | end
52 | end
53 |
54 | # @api private
55 | # @since 0.1.0
56 | module ClassMethods
57 | # @return [SmartCore::Initializer::Attribute::List]
58 | #
59 | # @api private
60 | # @since 0.1.0
61 | def __params__
62 | @__params__
63 | end
64 |
65 | # @return [SmartCore::Initializer::Attribute::List]
66 | #
67 | # @api private
68 | # @since 0.1.0
69 | def __options__
70 | @__options__
71 | end
72 |
73 | # @return [SmartCore::Initializer::Extentions::List]
74 | #
75 | # @api private
76 | # @since 0.1.0
77 | def __init_extensions__
78 | @__init_extensions__
79 | end
80 |
81 | # @return [SmartCore::Initializer::Constructor::Definer]
82 | #
83 | # @api private
84 | # @since 0.1.0
85 | def __definer__
86 | @__definer__
87 | end
88 |
89 | # @return [SmartCore::Initializer::Settings]
90 | #
91 | # @api private
92 | # @since 0.1.0
93 | def __initializer_settings__
94 | @__initializer_settings__
95 | end
96 |
97 | # @param arguments [Array]
98 | # @param block [Block]
99 | # @return [Any]
100 | #
101 | # @api public
102 | # @since 0.1.0
103 | def new(*arguments, &block)
104 | SmartCore::Initializer::Constructor.new(self, arguments, block).construct
105 | end
106 |
107 | # @param name [String, Symbol]
108 | # @param type [String, Symbol, Any]
109 | # @option cast [Boolean]
110 | # @option privacy [String, Symbol]
111 | # @option finalize [String, Symbol, Proc]
112 | # @option mutable [Boolean]
113 | # @option as [NilClass, String, Symbol]
114 | # @return [void]
115 | #
116 | # @api public
117 | # @since 0.1.0
118 | # @version 0.8.0
119 | def param(
120 | name,
121 | type = __initializer_settings__.generic_type_object,
122 | privacy: SmartCore::Initializer::Attribute::Value::Param::DEFAULT_PRIVACY_MODE,
123 | finalize: SmartCore::Initializer::Attribute::Value::Param::DEFAULT_FINALIZER,
124 | cast: __initializer_settings__.auto_cast,
125 | type_system: __initializer_settings__.type_system,
126 | mutable: SmartCore::Initializer::Attribute::Value::Param::DEFAULT_MUTABLE,
127 | as: SmartCore::Initializer::Attribute::Value::Param::DEFAULT_AS
128 | )
129 | __definer__.define_parameter(
130 | name, type, type_system, privacy, finalize, cast, mutable, as
131 | )
132 | end
133 |
134 | # @param names [Array]
135 | # @option mutable [Boolean]
136 | # @option privacy [String, Symbol]
137 | # @return [void]
138 | #
139 | # @api public
140 | # @since 0.1.0
141 | # @verison 0.8.0
142 | def params(
143 | *names,
144 | mutable: SmartCore::Initializer::Attribute::Value::Param::DEFAULT_MUTABLE,
145 | privacy: SmartCore::Initializer::Attribute::Value::Param::DEFAULT_PRIVACY_MODE
146 | )
147 | __definer__.define_parameters(*names, mutable: mutable, privacy: privacy)
148 | end
149 |
150 | # @param name [String, Symbol]
151 | # @param type [String, Symbol, Any]
152 | # @option cast [Boolean]
153 | # @option privacy [String, Symbol]
154 | # @option finalize [String, Symbol, Proc]
155 | # @option type_system [String, Symbol]
156 | # @option mutable [Boolean]
157 | # @option as [NilClass, String, Symbol]
158 | # @option default [Proc, Any]
159 | # @option optional [Boolean]
160 | # @return [void]
161 | #
162 | # @api public
163 | # @since 0.1.0
164 | # @version 0.8.0
165 | def option(
166 | name,
167 | type = __initializer_settings__.generic_type_object,
168 | privacy: SmartCore::Initializer::Attribute::Value::Option::DEFAULT_PRIVACY_MODE,
169 | finalize: SmartCore::Initializer::Attribute::Value::Option::DEFAULT_FINALIZER,
170 | cast: __initializer_settings__.auto_cast,
171 | type_system: __initializer_settings__.type_system,
172 | mutable: SmartCore::Initializer::Attribute::Value::Option::DEFAULT_MUTABLE,
173 | as: SmartCore::Initializer::Attribute::Value::Option::DEFAULT_AS,
174 | default: SmartCore::Initializer::Attribute::Value::Option::UNDEFINED_DEFAULT,
175 | optional: SmartCore::Initializer::Attribute::Value::Option::DEFAULT_OPTIONAL
176 | )
177 | __definer__.define_option(
178 | name, type, type_system, privacy, finalize, cast, mutable, as, default, optional
179 | )
180 | end
181 |
182 | # @param names [Array]
183 | # @option mutable [Boolean]
184 | # @option privacy [String, Symbol]
185 | # @return [void]
186 | #
187 | # @api public
188 | # @since 0.1.0
189 | # @version 0.8.0
190 | def options(
191 | *names,
192 | mutable: SmartCore::Initializer::Attribute::Value::Option::DEFAULT_MUTABLE,
193 | privacy: SmartCore::Initializer::Attribute::Value::Option::DEFAULT_PRIVACY_MODE
194 | )
195 | __definer__.define_options(*names, mutable: mutable, privacy: privacy)
196 | end
197 |
198 | # @param block [Block]
199 | # @return [void]
200 | #
201 | # @api public
202 | # @since 0.1.0
203 | def ext_init(&block)
204 | __definer__.define_init_extension(block)
205 | end
206 | alias_method :extend_initialization_flow, :ext_init
207 | end
208 | end
209 |
--------------------------------------------------------------------------------
/lib/smart_core/initializer/dsl/inheritance.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # @api private
4 | # @since 0.1.0
5 | module SmartCore::Initializer::DSL::Inheritance
6 | class << self
7 | # @param base [Class]
8 | # @param child [Class]
9 | # @return [void]
10 | #
11 | # @api private
12 | # @since 0.1.0
13 | def inherit(base:, child:)
14 | child.__params__.concat(base.__params__)
15 | child.__options__.concat(base.__options__)
16 | child.__init_extensions__.concat(base.__init_extensions__)
17 | end
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/lib/smart_core/initializer/errors.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module SmartCore::Initializer
4 | # @api public
5 | # @since 0.1.0
6 | Error = Class.new(SmartCore::Error)
7 |
8 | # @api public
9 | # @since 0.1.0
10 | ArgumentError = Class.new(SmartCore::ArgumentError)
11 |
12 | # @api public
13 | # @since 0.8.0
14 | AttributeError = Class.new(Error)
15 |
16 | # @api public
17 | # @since 0.8.0
18 | UndefinedAttributeError = Class.new(AttributeError)
19 |
20 | # @api public
21 | # @since 0.1.0
22 | ParameterArgumentError = Class.new(ArgumentError)
23 |
24 | # @api public
25 | # @since 0.1.0
26 | OptionArgumentError = Class.new(ArgumentError)
27 |
28 | # @api public
29 | # @since 0.8.0
30 | AliasArgumentError = Class.new(ArgumentError)
31 |
32 | # @api public
33 | # @since 0.8.0
34 | SettingArgumentError = Class.new(ArgumentError)
35 |
36 | # @api public
37 | # @since 0.1.0
38 | NoDefaultValueError = Class.new(Error)
39 |
40 | # @api public
41 | # @since 0.1.0
42 | OptionOverlapError = Class.new(ArgumentError)
43 |
44 | # @api public
45 | # @since 0.1.0
46 | ParameterOverlapError = Class.new(ArgumentError)
47 |
48 | # @api public
49 | # @since 0.1.0
50 | NoTypeAliasError = Class.new(Error)
51 |
52 | # @api public
53 | # @since 0.1.0
54 | PluginError = Class.new(Error)
55 |
56 | # @api public
57 | # @since 0.1.0
58 | UnresolvedPluginDependencyError = Class.new(PluginError)
59 |
60 | # @api public
61 | # @since 0.1.0
62 | AlreadyRegisteredPluginError = Class.new(PluginError)
63 |
64 | # @api public
65 | # @since 0.1.0
66 | UnregisteredPluginError = Class.new(PluginError)
67 |
68 | # @api public
69 | # @since 0.1.0
70 | TypeSystemError = Class.new(Error)
71 |
72 | # @api public
73 | # @since 0.5.1
74 | IncorrectTypeError = Class.new(TypeSystemError)
75 |
76 | # @api public
77 | # @since 0.1.0
78 | TypeAliasNotFoundError = Class.new(TypeSystemError)
79 |
80 | # @api public
81 | # @since 0.1.0
82 | IncorrectTypeSystemInteropError = Class.new(TypeSystemError)
83 |
84 | # @api public
85 | # @since 0.1.0
86 | IncorrectTypeObjectError = Class.new(TypeSystemError)
87 |
88 | # @api public
89 | # @since 0.1.0
90 | UnsupportedTypeSystemError = Class.new(TypeSystemError)
91 |
92 | # @api public
93 | # @since 0.1.0
94 | UnsupportedTypeOperationError = Class.new(TypeSystemError)
95 |
96 | # @api public
97 | # @since 0.1.0
98 | TypeCastingUnsupportedError = Class.new(UnsupportedTypeOperationError)
99 | end
100 |
--------------------------------------------------------------------------------
/lib/smart_core/initializer/extensions.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # @api private
4 | # @since 0.1.0
5 | module SmartCore::Initializer::Extensions
6 | require_relative 'extensions/abstract'
7 | require_relative 'extensions/ext_init'
8 | require_relative 'extensions/list'
9 | end
10 |
--------------------------------------------------------------------------------
/lib/smart_core/initializer/extensions/abstract.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # @api private
4 | # @since 0.1.0
5 | class SmartCore::Initializer::Extensions::Abstract
6 | # @!method dup
7 | # @return [SmartCore::Initializer::Extensions::Abstract]
8 | end
9 |
--------------------------------------------------------------------------------
/lib/smart_core/initializer/extensions/ext_init.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # @api private
4 | # @since 0.1.0
5 | class SmartCore::Initializer::Extensions::ExtInit < SmartCore::Initializer::Extensions::Abstract
6 | # @param extender [Proc]
7 | # @return [void]
8 | #
9 | # @api private
10 | # @since 0.1.0
11 | def initialize(extender)
12 | @extender = extender
13 | end
14 |
15 | # @param instance [Any]
16 | # @return [void]
17 | #
18 | # @api private
19 | # @since 0.1.0
20 | def call(instance)
21 | extender.call(instance)
22 | end
23 |
24 | private
25 |
26 | # @return [Proc]
27 | #
28 | # @api private
29 | # @since0 0.1.0
30 | attr_reader :extender
31 | end
32 |
--------------------------------------------------------------------------------
/lib/smart_core/initializer/extensions/list.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # @api private
4 | # @since 0.1.0
5 | # @version 0.10.0
6 | class SmartCore::Initializer::Extensions::List
7 | # @since 0.1.0
8 | include Enumerable
9 |
10 | # @return [void]
11 | #
12 | # @api private
13 | # @since 0.1.0
14 | # @version 0.10.0
15 | def initialize
16 | @extensions = []
17 | @lock = SmartCore::Engine::ReadWriteLock.new
18 | end
19 |
20 | # @param extension [SmartCore::Initializer::Extensions::Abstract]
21 | # @return [void]
22 | #
23 | # @api private
24 | # @since 0.1.0
25 | # @version 0.10.0
26 | def add(extension)
27 | @lock.write_sync { extensions << extension }
28 | end
29 | alias_method :<<, :add
30 |
31 | # @param list [SmartCore::Initializer::Extensions::List]
32 | # @return [void]
33 | #
34 | # @api private
35 | # @since 0.1.0
36 | # @version 0.10.0
37 | def concat(list)
38 | @lock.write_sync do
39 | list.each { |extension| add(extension.dup) }
40 | end
41 | end
42 |
43 | # @param block [Block]
44 | # @return [Enumerable]
45 | #
46 | # @api private
47 | # @since 0.1.0
48 | # @version 0.10.0
49 | def each(&block)
50 | @lock.read_sync do
51 | block_given? ? extensions.each(&block) : extensions.each
52 | end
53 | end
54 |
55 | # @return [Integer]
56 | #
57 | # @api private
58 | # @since 0.1.0
59 | # @version 0.10.0
60 | def size
61 | @lock.read_sync { extensions.size }
62 | end
63 |
64 | private
65 |
66 | # @return [Array]
67 | #
68 | # @api private
69 | # @since 0.1.0
70 | attr_reader :extensions
71 | end
72 |
--------------------------------------------------------------------------------
/lib/smart_core/initializer/functionality.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # @api private
4 | # @since 0.3.0
5 | module SmartCore::Initializer::Functionality
6 | class << self
7 | # @option type_system [String, Symbol]
8 | # @option strict_option [Boolean]
9 | # @option auto_cast [Boolean]
10 | # @return [Module]
11 | #
12 | # @api private
13 | # @since 0.3.0
14 | # @version 0.8.0
15 | def includable_module(type_system:, strict_options:, auto_cast:)
16 | Module.new.tap do |extension|
17 | extension.singleton_class.define_method(:included) do |base_klass|
18 | base_klass.include(::SmartCore::Initializer)
19 | base_klass.__initializer_settings__.type_system = type_system
20 | base_klass.__initializer_settings__.strict_options = strict_options
21 | base_klass.__initializer_settings__.auto_cast = auto_cast
22 | end
23 | end
24 | end
25 |
26 | # @param base_klass [Class]
27 | # @return [void]
28 | #
29 | # @api private
30 | # @since 0.3.0
31 | def seed_to(base_klass)
32 | base_klass.extend(SmartCore::Initializer::DSL)
33 | base_klass.include(SmartCore::Initializer::InstanceAttributeAccessing)
34 | end
35 | end
36 | end
37 |
--------------------------------------------------------------------------------
/lib/smart_core/initializer/instance_attribute_accessing.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # @api private
4 | # @since 0.3.0
5 | module SmartCore::Initializer::InstanceAttributeAccessing
6 | # @return [Hash]
7 | #
8 | # @api public
9 | # @since 0.3.0
10 | def __params__
11 | __collect_params__
12 | end
13 |
14 | # @return [Hash]
15 | #
16 | # @api public
17 | # @since 0.3.0
18 | def __options__
19 | __collect_options__
20 | end
21 |
22 | # @return [Hash]
23 | #
24 | # @api public
25 | # @since 0.3.0
26 | def __attributes__
27 | __collect_params__.merge(__collect_options__)
28 | end
29 |
30 | private
31 |
32 | # @return [Hash]
33 | #
34 | # @api private
35 | # @since 0.3.0
36 | def __collect_params__
37 | self.class.__params__.each_with_object({}) do |param, memo|
38 | memo[param.name] = instance_variable_get("@#{param.name}")
39 | end
40 | end
41 |
42 | # @return [Hash]
43 | #
44 | # @api private
45 | # @since 0.3.0
46 | def __collect_options__
47 | self.class.__options__.each_with_object({}) do |option, memo|
48 | memo[option.name] = instance_variable_get("@#{option.name}")
49 | end
50 | end
51 | end
52 |
--------------------------------------------------------------------------------
/lib/smart_core/initializer/plugins.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # @api private
4 | # @since 0.1.0
5 | module SmartCore::Initializer::Plugins
6 | require_relative 'plugins/abstract'
7 | require_relative 'plugins/registry'
8 | require_relative 'plugins/registry_interface'
9 | require_relative 'plugins/access_mixin'
10 | require_relative 'plugins/thy_types'
11 |
12 | # @since 0.1.0
13 | extend SmartCore::Initializer::Plugins::RegistryInterface
14 |
15 | # @since 0.1.0
16 | register_plugin('thy_types', SmartCore::Initializer::Plugins::ThyTypes)
17 | end
18 |
--------------------------------------------------------------------------------
/lib/smart_core/initializer/plugins/abstract.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # @api private
4 | # @since 0.1.0
5 | # @version 0.10.0
6 | class SmartCore::Initializer::Plugins::Abstract
7 | class << self
8 | # @param child_klass [Class]
9 | # @return [void]
10 | #
11 | # @api private
12 | # @since 0.1.0
13 | # @version 0.10.0
14 | def inherited(child_klass)
15 | child_klass.instance_variable_set(:@__loaded__, false)
16 | child_klass.instance_variable_set(:@__lock__, SmartCore::Engine::Lock.new)
17 | super
18 | end
19 |
20 | # @return [void]
21 | #
22 | # @api private
23 | # @since 0.1.0
24 | def load!
25 | __thread_safe__ do
26 | unless @__loaded__
27 | @__loaded__ = true
28 | install!
29 | end
30 | end
31 | end
32 |
33 | # @return [Boolean]
34 | #
35 | # @api private
36 | # @since 0.1.0
37 | def loaded?
38 | __thread_safe__ { @__loaded__ }
39 | end
40 |
41 | private
42 |
43 | # @return [void]
44 | #
45 | # @api private
46 | # @since 0.1.0
47 | def install!; end
48 |
49 | # @return [Any]
50 | #
51 | # @api private
52 | # @since 0.1.0
53 | def __thread_safe__(&block)
54 | @__lock__.synchronize(&block)
55 | end
56 | end
57 | end
58 |
--------------------------------------------------------------------------------
/lib/smart_core/initializer/plugins/access_mixin.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # @api private
4 | # @since 0.1.0
5 | module SmartCore::Initializer::Plugins::AccessMixin
6 | # @param plugin_name [Symbol, String]
7 | # @return [void]
8 | #
9 | # @see SmartCore::Initializer::Plugins
10 | #
11 | # @api public
12 | # @since 0.1.0
13 | def plugin(plugin_name)
14 | SmartCore::Initializer::Plugins.load(plugin_name)
15 | end
16 |
17 | # @return [Array]
18 | #
19 | # @see SmartCore::Initializer::Plugins
20 | #
21 | # @api public
22 | # @since 0.1.0
23 | def plugins
24 | SmartCore::Initializer::Plugins.names
25 | end
26 |
27 | # @return [Hash>]
28 | #
29 | # @api private
30 | # @since 0.1.0
31 | def loaded_plugins
32 | SmartCore::Initializer::Plugins.loaded_plugins
33 | end
34 | alias_method :enabled_plugins, :loaded_plugins
35 |
36 | # @param plugin_name [String, Symbol]
37 | # @param plugin_klass [Class]
38 | # @return [void]
39 | #
40 | # @see SmartCore::Initializer::Plugins
41 | #
42 | # @api public
43 | # @since 0.1.0
44 | def register_plugin(plugin_name, plugin_klass)
45 | SmartCore::Initializer::Plugins.register_plugin(plugin_name, plugin_klass)
46 | end
47 | end
48 |
--------------------------------------------------------------------------------
/lib/smart_core/initializer/plugins/registry.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # @api private
4 | # @since 0.1.0
5 | # @version 0.10.0
6 | class SmartCore::Initializer::Plugins::Registry
7 | # @since 0.1.0
8 | include Enumerable
9 |
10 | # @return [void]
11 | #
12 | # @api private
13 | # @since 0.1.0
14 | # @version 0.10.0
15 | def initialize
16 | @plugin_set = {}
17 | @access_lock = SmartCore::Engine::ReadWriteLock.new
18 | end
19 |
20 | # @param plugin_name [Symbol, String]
21 | # @return [SmartCore::Initializer::Plugins::Abstract]
22 | #
23 | # @api private
24 | # @since 0.1.0
25 | # @version 0.10.0
26 | def [](plugin_name)
27 | @access_lock.read_sync { fetch(plugin_name) }
28 | end
29 |
30 | # @param plugin_name [Symbol, String]
31 | # @param plugin_module [SmartCore::Initializer::Plugins::Abstract]
32 | # @return [void]
33 | #
34 | # @api private
35 | # @since 0.1.0
36 | # @version 0.10.0
37 | def register(plugin_name, plugin_module)
38 | @access_lock.write_sync { apply(plugin_name, plugin_module) }
39 | end
40 | alias_method :[]=, :register
41 |
42 | # @return [Array]
43 | #
44 | # @api private
45 | # @since 0.1.0
46 | # @version 0.10.0
47 | def names
48 | @access_lock.read_sync { plugin_names }
49 | end
50 |
51 | # @return [Hash>]
52 | #
53 | # @api private
54 | # @since 0.1.0
55 | # @version 0.10.0
56 | def loaded
57 | @access_lock.read_sync { loaded_plugins }
58 | end
59 |
60 | # @param block [Block]
61 | # @return [Enumerable]
62 | #
63 | # @api private
64 | # @since 0.1.0
65 | # @version 0.10.0
66 | def each(&block)
67 | @access_lock.read_sync { iterate(&block) }
68 | end
69 |
70 | private
71 |
72 | # @return [Hash]
73 | #
74 | # @api private
75 | # @since 0.1.0
76 | attr_reader :plugin_set
77 |
78 | # @return [Mutex]
79 | #
80 | # @api private
81 | # @since 0.1.0
82 | attr_reader :access_lock
83 |
84 | # @return [Array]
85 | #
86 | # @api private
87 | # @since 0.1.0
88 | def plugin_names
89 | plugin_set.keys
90 | end
91 |
92 | # @param block [Block]
93 | # @return [Enumerable]
94 | #
95 | # @api private
96 | # @since 0.1.0
97 | def iterate(&block)
98 | block_given? ? plugin_set.each_pair(&block) : plugin_set.each_pair
99 | end
100 |
101 | # @param plugin_name [String]
102 | # @return [Boolean]
103 | #
104 | # @api private
105 | # @since 0.1.0
106 | def registered?(plugin_name)
107 | plugin_set.key?(plugin_name)
108 | end
109 |
110 | # @return [Array]
111 | #
112 | # @api private
113 | # @since 0.1.0
114 | def loaded_plugins
115 | plugin_set.select { |_plugin_name, plugin_module| plugin_module.loaded? }
116 | end
117 |
118 | # @param plugin_name [Symbol, String]
119 | # @param plugin_module [SmartCore::Initializer::Plugins::Abstract]
120 | # @return [void]
121 | #
122 | # @raise [SmartCore::Initializer::AlreadyRegisteredPluginError]
123 | #
124 | # @api private
125 | # @since 0.1.0
126 | def apply(plugin_name, plugin_module)
127 | plugin_name = indifferently_accessible_plugin_name(plugin_name)
128 |
129 | if registered?(plugin_name)
130 | raise(SmartCore::Initializer::AlreadyRegisteredPluginError, <<~ERROR_MESSAGE)
131 | #{plugin_name} plugin already exists
132 | ERROR_MESSAGE
133 | end
134 |
135 | plugin_set[plugin_name] = plugin_module
136 | end
137 |
138 | # @param plugin_name [Symbol, String]
139 | # @return [SmartCore::Initializer::Plugins::Abstract]
140 | #
141 | # @raise [SmartCore::Initializer::UnregisteredPluginError]
142 | #
143 | # @api private
144 | # @since 0.1.0
145 | def fetch(plugin_name)
146 | plugin_name = indifferently_accessible_plugin_name(plugin_name)
147 |
148 | unless registered?(plugin_name)
149 | raise(SmartCore::Initializer::UnregisteredPluginError, <<~ERROR_MESSAGE)
150 | #{plugin_name} plugin is not registered
151 | ERROR_MESSAGE
152 | end
153 |
154 | plugin_set[plugin_name]
155 | end
156 |
157 | # @param key [Symbol, String]
158 | # @return [String]
159 | #
160 | # @api private
161 | # @since 0.1.0
162 | def indifferently_accessible_plugin_name(plugin_name)
163 | plugin_name.to_s
164 | end
165 | end
166 |
--------------------------------------------------------------------------------
/lib/smart_core/initializer/plugins/registry_interface.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # @api private
4 | # @since 0.1.0
5 | # @version 0.10.0
6 | module SmartCore::Initializer::Plugins::RegistryInterface
7 | class << self
8 | # @param base_module [Class, Module]
9 | # @return [void]
10 | #
11 | # @api private
12 | # @since 0.1.0
13 | # @version 0.10.0
14 | def extended(base_module)
15 | base_module.instance_variable_set(
16 | :@plugin_registry, SmartCore::Initializer::Plugins::Registry.new
17 | )
18 | base_module.instance_variable_set(
19 | :@access_lock, SmartCore::Engine::ReadWriteLock.new
20 | )
21 | end
22 | end
23 |
24 | # @param plugin_name [Symbol, String]
25 | # @return [void]
26 | #
27 | # @api public
28 | # @since 0.1.0
29 | # @version 0.10.0
30 | def load(plugin_name)
31 | @access_lock.read_sync { plugin_registry[plugin_name].load! }
32 | end
33 |
34 | # @return [Array]
35 | #
36 | # @api public
37 | # @since 0.1.0
38 | # @version 0.10.0
39 | def loaded_plugins
40 | @access_lock.read_sync { plugin_registry.loaded.keys }
41 | end
42 |
43 | # @return [Array]
44 | #
45 | # @api public
46 | # @since 0.1.0
47 | # @version 0.10.0
48 | def names
49 | @access_lock.read_sync { plugin_registry.names }
50 | end
51 |
52 | # @param plugin_name [Symbol, String]
53 | # @return [void]
54 | #
55 | # @api private
56 | # @since 0.1.0
57 | # @version 0.10.0
58 | def register_plugin(plugin_name, plugin_module)
59 | @access_lock.write_sync { plugin_registry[plugin_name] = plugin_module }
60 | end
61 |
62 | private
63 |
64 | # @return [SmartCore::Initializer::Plugins::Registry]
65 | #
66 | # @api private
67 | # @since 0.1.0
68 | attr_reader :plugin_registry
69 | end
70 |
--------------------------------------------------------------------------------
/lib/smart_core/initializer/plugins/thy_types.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # @api private
4 | # @since 0.1.0
5 | class SmartCore::Initializer::Plugins::ThyTypes < SmartCore::Initializer::Plugins::Abstract
6 | class << self
7 | # @return [void]
8 | #
9 | # @api private
10 | # @since 0.1.0
11 | def install!
12 | raise(
13 | SmartCore::Initializer::UnresolvedPluginDependencyError,
14 | '::Thy does not exist or "thy" gem is not loaded'
15 | ) unless const_defined?('::Thy')
16 |
17 | # NOTE: require necessary dependencies
18 | require 'date'
19 |
20 | # NOTE: add thy-types type system implementation
21 | require_relative 'thy_types/errors'
22 | require_relative 'thy_types/thy_types'
23 |
24 | # NOTE: register thy-types type system
25 | SmartCore::Initializer::TypeSystem.register(
26 | :thy_types, SmartCore::Initializer::TypeSystem::ThyTypes
27 | )
28 | end
29 | end
30 | end
31 |
--------------------------------------------------------------------------------
/lib/smart_core/initializer/plugins/thy_types/errors.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module SmartCore::Initializer
4 | # @api public
5 | # @since 0.1.0
6 | ThyTypesError = Class.new(SmartCore::Initializer::Error)
7 |
8 | # @api public
9 | # @since 0.1.0
10 | ThyTypeValidationError = Class.new(ThyTypesError)
11 | end
12 |
--------------------------------------------------------------------------------
/lib/smart_core/initializer/plugins/thy_types/thy_types.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module SmartCore::Initializer::TypeSystem
4 | # @api public
5 | # @since 0.1.0
6 | class ThyTypes < Interop
7 | require_relative 'thy_types/abstract_factory'
8 | require_relative 'thy_types/operation'
9 |
10 | type_alias(:any, AbstractFactory.generic_type_object)
11 | type_alias(:nil, ::Thy::Types::Nil)
12 | type_alias(:string, ::Thy::Types::String)
13 | type_alias(:symbol, ::Thy::Types::Symbol)
14 | type_alias(:integer, ::Thy::Types::Integer)
15 | type_alias(:float, ::Thy::Types::Float)
16 | type_alias(:numeric, ::Thy::Types::Numeric)
17 | type_alias(:boolean, ::Thy::Types::Boolean)
18 | type_alias(:time, ::Thy::Types::Time)
19 | type_alias(:date_time, ::Thy::Types::DateTime)
20 | type_alias(:untyped_array, ::Thy::Types::UntypedArray)
21 | type_alias(:untyped_hash, ::Thy::Types::UntypedHash)
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/lib/smart_core/initializer/plugins/thy_types/thy_types/abstract_factory.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module SmartCore::Initializer::TypeSystem
4 | # @api private
5 | # @since 0.1.0
6 | class ThyTypes::AbstractFactory < Interop::AbstractFactory
7 | # @return [Thy::Type]
8 | #
9 | # @api private
10 | # @since 0.1.0
11 | GENERIC_TYPE = ::Thy::Type.new { true }
12 |
13 | class << self
14 | # @param type [Thy::Type, #check]
15 | # @return [void]
16 | #
17 | # @raise [SmartCore::Initializer::IncorrectTypeObjectError]
18 | #
19 | # @api private
20 | # @since 0.1.0
21 | def prevent_incompatible_type!(type)
22 | unless type.respond_to?(:check) || type.is_a?(::Thy::Type)
23 | raise(
24 | SmartCore::Initializer::IncorrectTypeObjectError,
25 | 'Incorrect Thy::Type primitive ' \
26 | '(type object should respond to :check method)'
27 | )
28 | end
29 | end
30 |
31 | # @param type [Any]
32 | # @return [String]
33 | #
34 | # @api private
35 | # @since 0.5.1
36 | def build_identifier(type)
37 | type.name
38 | end
39 |
40 | # @param type [Thy::Type, #check]
41 | # @return [SmartCore::Initializer::TypeSystem::ThyTypes::Operation::Valid]
42 | #
43 | # @api private
44 | # @since 0.1.0
45 | def build_valid_operation(type)
46 | ThyTypes::Operation::Valid.new(type)
47 | end
48 |
49 | # @return [Thy::Type, #check]
50 | #
51 | # @api private
52 | # @since 0.1.0
53 | def generic_type_object
54 | GENERIC_TYPE
55 | end
56 |
57 | # @param type [Thy::Type, #check]
58 | # @return [SmartCore::Initializer::TypeSystem::ThyTypes::Operation::Validate]
59 | #
60 | # @api private
61 | # @since 0.1.0
62 | def build_validate_operation(type)
63 | ThyTypes::Operation::Validate.new(type)
64 | end
65 |
66 | # @param type [Thy::Type, #check]
67 | # @return [SmartCore::Initializer::TypeSystem::ThyTypes::Operation::Cast]
68 | #
69 | # @api private
70 | # @since 0.1.0
71 | def build_cast_operation(type)
72 | ThyTypes::Operation::Cast.new(type)
73 | end
74 |
75 | # @param identifier [String]
76 | # @param valid_op [SmartCore::Initializer::TypeSystem::ThyTypes::Operation::Valid]
77 | # @param valid_op [SmartCore::Initializer::TypeSystem::ThyTypes::Operation::Validate]
78 | # @param valid_op [SmartCore::Initializer::TypeSystem::ThyTypes::Operation::Cast]
79 | # @return [SmartCore::Initializer::TypeSystem::ThyTypes]
80 | #
81 | # @api private
82 | # @since 0.1.0
83 | # @version 0.5.1
84 | def build_interop(identifier, valid_op, validate_op, cast_op)
85 | ThyTypes.new(identifier, valid_op, validate_op, cast_op)
86 | end
87 | end
88 | end
89 | end
90 |
--------------------------------------------------------------------------------
/lib/smart_core/initializer/plugins/thy_types/thy_types/operation.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module SmartCore::Initializer::TypeSystem
4 | # @api private
5 | # @since 0.1.0
6 | module ThyTypes::Operation
7 | require_relative 'operation/base'
8 | require_relative 'operation/valid'
9 | require_relative 'operation/validate'
10 | require_relative 'operation/cast'
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/lib/smart_core/initializer/plugins/thy_types/thy_types/operation/base.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module SmartCore::Initializer::TypeSystem
4 | # @abstract
5 | # @api private
6 | # @since 0.1.0
7 | class ThyTypes::Operation::Base < Interop::Operation
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/lib/smart_core/initializer/plugins/thy_types/thy_types/operation/cast.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module SmartCore::Initializer::TypeSystem::ThyTypes::Operation
4 | # @api private
5 | # @since 0.1.0
6 | class Cast < Base
7 | # @param value [Any]
8 | # @return [void]
9 | #
10 | # @raise [SmartCore::Initializer::TypeCastingUnsupportedError]
11 | #
12 | # @api private
13 | # @since 0.1.0
14 | def call(value)
15 | raise(
16 | SmartCore::Initializer::TypeCastingUnsupportedError,
17 | 'ThyTypes type system has no support for type casting.'
18 | )
19 | end
20 | end
21 | end
22 |
--------------------------------------------------------------------------------
/lib/smart_core/initializer/plugins/thy_types/thy_types/operation/valid.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module SmartCore::Initializer::TypeSystem::ThyTypes::Operation
4 | # @api private
5 | # @since 0.1.0
6 | class Valid < Base
7 | # @param value [Any]
8 | # @return [Boolean]
9 | #
10 | # @api private
11 | # @since 0.1.0
12 | def call(value)
13 | type.check(value).success?
14 | end
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/lib/smart_core/initializer/plugins/thy_types/thy_types/operation/validate.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module SmartCore::Initializer::TypeSystem::ThyTypes::Operation
4 | # @api private
5 | # @since 0.1.0
6 | class Validate < Base
7 | # @param value [Any]
8 | # @return [void]
9 | #
10 | # @api private
11 | # @since 0.1.0
12 | def call(value)
13 | raise(
14 | SmartCore::Initializer::ThyTypeValidationError,
15 | "Thy::Types validation error: (get #{value.inspect} for type #{type.inspect}"
16 | ) unless type.check(value).success?
17 | end
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/lib/smart_core/initializer/settings.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # @api private
4 | # @since 0.1.0
5 | class SmartCore::Initializer::Settings
6 | require_relative 'settings/base'
7 | require_relative 'settings/type_system'
8 | require_relative 'settings/strict_options'
9 | require_relative 'settings/auto_cast'
10 | require_relative 'settings/duplicator'
11 |
12 | # @return [void]
13 | #
14 | # @api private
15 | # @since 0.1.0
16 | # @version 0.8.0
17 | def initialize
18 | @type_system = TypeSystem.new
19 | @strict_options = StrictOptions.new
20 | @auto_cast = AutoCast.new
21 | end
22 |
23 | # @return [Any]
24 | #
25 | # @api private
26 | # @since 0.1.0
27 | def generic_type_object
28 | @type_system.generic_type_object
29 | end
30 |
31 | # @return [Symbol]
32 | #
33 | # @api private
34 | # @since 0.1.0
35 | def type_system
36 | @type_system.resolve
37 | end
38 |
39 | # @return [Boolean]
40 | #
41 | # @api private
42 | # @since 0.8.0
43 | def strict_options
44 | @strict_options.resolve
45 | end
46 |
47 | # @return [Boolean]
48 | #
49 | # @api private
50 | # @since 0.8.0
51 | def auto_cast
52 | @auto_cast.resolve
53 | end
54 |
55 | # @param value [String, System]
56 | # @return [void]
57 | #
58 | # @api private
59 | # @since 0.1.0
60 | def type_system=(value)
61 | @type_system.assign(value)
62 | end
63 |
64 | # @param value [Boolean]
65 | # @return [void]
66 | #
67 | # @api private
68 | # @since 0.8.0
69 | def strict_options=(value)
70 | @strict_options.assign(value)
71 | end
72 |
73 | # @param value [Boolean]
74 | # @return [void]
75 | #
76 | # @api private
77 | # @since 0.8.0
78 | def auto_cast=(value)
79 | @auto_cast.assign(value)
80 | end
81 |
82 | # @return [SmartCore::Initializer::Settings]
83 | #
84 | # @api private
85 | # @since 0.1.0
86 | def dup
87 | Duplicator.duplicate(self)
88 | end
89 | end
90 |
--------------------------------------------------------------------------------
/lib/smart_core/initializer/settings/auto_cast.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # @api private
4 | # @since 0.8.0
5 | # @version 0.10.0
6 | class SmartCore::Initializer::Settings::AutoCast < SmartCore::Initializer::Settings::Base
7 | # @return [Boolean]
8 | #
9 | # @api private
10 | # @since 0.8.0
11 | # @version 0.10.0
12 | def resolve
13 | @lock.read_sync do
14 | (@value == nil) ? SmartCore::Initializer::Configuration[:auto_cast] : @value
15 | end
16 | end
17 |
18 | # @param value [Boolean]
19 | # @return [void]
20 | #
21 | # @api private
22 | # @since 0.8.0
23 | # @version 0.10.0
24 | def assign(value)
25 | @lock.write_sync do
26 | raise(
27 | SmartCore::Initializer::SettingArgumentError,
28 | ":auto_cast setting should be a type of boolean (got: `#{value.class}`)"
29 | ) unless value.is_a?(::TrueClass) || value.is_a?(::FalseClass)
30 |
31 | @value = value
32 | end
33 | end
34 | end
35 |
--------------------------------------------------------------------------------
/lib/smart_core/initializer/settings/base.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # @api private
4 | # @since 0.8.0
5 | # @version 0.10.0
6 | class SmartCore::Initializer::Settings::Base
7 | # @return [void]
8 | #
9 | # @api private
10 | # @since 0.8.0
11 | # @version 0.10.0
12 | def initialize
13 | @value = nil
14 | @lock = SmartCore::Engine::ReadWriteLock.new
15 | end
16 |
17 | # @!method resolve
18 | # @return [Any]
19 | # @api private
20 | # @since 0.8.0
21 |
22 | # @!method assign(value)
23 | # @param value [Any]
24 | # @return [void]
25 | # @raise [SmartCore::Initializer::SettingArgumentError]
26 | # @api private
27 | # @since 0.8.0
28 |
29 | # @return [SmartCore::Initializer::Settings::Base]
30 | #
31 | # @api private
32 | # @since 0.8.0
33 | # @version 0.10.0
34 | def dup
35 | @lock.write_sync do
36 | self.class.new.tap do |duplicate|
37 | duplicate.instance_variable_set(:@value, @value)
38 | end
39 | end
40 | end
41 | end
42 |
--------------------------------------------------------------------------------
/lib/smart_core/initializer/settings/duplicator.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # @api private
4 | # @since 0.1.0
5 | module SmartCore::Initializer::Settings::Duplicator
6 | class << self
7 | # @param settings [SmartCore::Initializer::Settings]
8 | # @return [SmartCore::Initializer::Settings]
9 | #
10 | # @api private
11 | # @since 0.1.0
12 | # @version 0.8.0
13 | def duplicate(settings)
14 | SmartCore::Initializer::Settings.new.tap do |new_instance|
15 | type_system = settings.instance_variable_get(:@type_system).dup
16 | strict_options = settings.instance_variable_get(:@strict_options).dup
17 | auto_cast = settings.instance_variable_get(:@auto_cast).dup
18 |
19 | new_instance.instance_variable_set(:@type_system, type_system)
20 | new_instance.instance_variable_set(:@strict_options, strict_options)
21 | new_instance.instance_variable_set(:@auto_cast, auto_cast)
22 | end
23 | end
24 | end
25 | end
26 |
--------------------------------------------------------------------------------
/lib/smart_core/initializer/settings/strict_options.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # @api private
4 | # @since 0.8.0
5 | # @version 0.10.0
6 | class SmartCore::Initializer::Settings::StrictOptions < SmartCore::Initializer::Settings::Base
7 | # @return [Boolean]
8 | #
9 | # @api private
10 | # @since 0.8.0
11 | # @version 0.10.0
12 | def resolve
13 | @lock.read_sync do
14 | (@value == nil) ? SmartCore::Initializer::Configuration[:strict_options] : @value
15 | end
16 | end
17 |
18 | # @param value [Boolean]
19 | # @return [void]
20 | #
21 | # @api private
22 | # @since 0.8.0
23 | # @version 0.10.0
24 | def assign(value)
25 | @lock.write_sync do
26 | raise(
27 | SmartCore::Initializer::SettingArgumentError,
28 | ":strict_options setting should be a type of boolean (got: `#{value.class}`)"
29 | ) unless value.is_a?(::TrueClass) || value.is_a?(::FalseClass)
30 |
31 | @value = value
32 | end
33 | end
34 | end
35 |
--------------------------------------------------------------------------------
/lib/smart_core/initializer/settings/type_system.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # @api private
4 | # @since 0.1.0
5 | # @version 0.10.0
6 | class SmartCore::Initializer::Settings::TypeSystem < SmartCore::Initializer::Settings::Base
7 | # @return [Any]
8 | #
9 | # @api private
10 | # @since 0.1.0
11 | # @version 0.10.0
12 | def generic_type_object
13 | @lock.read_sync do
14 | SmartCore::Initializer::TypeSystem.resolve(resolve).generic_type_object
15 | end
16 | end
17 |
18 | # @return [Symbol]
19 | #
20 | # @api private
21 | # @since 0.1.0
22 | # @version 0.10.0
23 | def resolve
24 | @lock.read_sync do
25 | (@value == nil) ? SmartCore::Initializer::Configuration[:default_type_system] : @value
26 | end
27 | end
28 |
29 | # @param value [String, Symbol]
30 | # @return [void]
31 | #
32 | # @api private
33 | # @since 0.1.0
34 | # @version 0.10.0
35 | def assign(value)
36 | @lock.write_sync do
37 | # NOTE: type system existence validation
38 | SmartCore::Initializer::TypeSystem.resolve(value)
39 | @value = value
40 | end
41 | end
42 | end
43 |
--------------------------------------------------------------------------------
/lib/smart_core/initializer/type_system.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # @api public
4 | # @since 0.1.0
5 | module SmartCore::Initializer::TypeSystem
6 | require_relative 'type_system/interop'
7 | require_relative 'type_system/registry'
8 | require_relative 'type_system/smart_types'
9 | require_relative 'type_system/registry_interface'
10 |
11 | # @since 0.1.0
12 | extend SmartCore::Initializer::TypeSystem::RegistryInterface
13 |
14 | # @since 0.1.0
15 | register(:smart_types, SmartCore::Initializer::TypeSystem::SmartTypes)
16 | end
17 |
--------------------------------------------------------------------------------
/lib/smart_core/initializer/type_system/interop.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # @abstract
4 | # @api private
5 | # @since 0.1.0
6 | class SmartCore::Initializer::TypeSystem::Interop
7 | require_relative 'interop/operation'
8 | require_relative 'interop/abstract_factory'
9 | require_relative 'interop/aliasing'
10 |
11 | # @since 0.1.0
12 | include SmartCore::Initializer::TypeSystem::Interop::Aliasing
13 |
14 | class << self
15 | # @param type_object [Any]
16 | # @return [SmartCore::Initializer::TypeSystem::Interop]
17 | #
18 | # @api private
19 | # @since 0.1.0
20 | def create(type_object)
21 | self::AbstractFactory.create(type_object)
22 | end
23 |
24 | # @return [SmartCore::Initialiezr::TypeSystem::Interop]
25 | #
26 | # @api private
27 | # @since 0.1.0
28 | def generic_type_object
29 | self::AbstractFactory.generic_type_object
30 | end
31 |
32 | # @param type_object [Any]
33 | # @return [void]
34 | #
35 | # @raise [SmartCore::Initializer::IncorrectTypeObjectError]
36 | #
37 | # @api private
38 | # @since 0.1.0
39 | def prevent_incompatible_type!(type_object)
40 | self::AbstractFactory.prevent_incompatible_type!(type_object)
41 | end
42 | end
43 |
44 | # @return [String]
45 | #
46 | # @api private
47 | # @since 0.5.1
48 | attr_reader :identifier
49 |
50 | # @param identifier [String]
51 | # @param valid_op [SmartCore::Initializer::TypeSystem::Interop::Operation]
52 | # @param validate_op [SmartCore::Initializer::TypeSystem::Interop::Operation]
53 | # @param cast_op [SmartCore::Initializer::TypeSystem::Interop::Operation]
54 | # @return [void]
55 | #
56 | # @api private
57 | # @since 0.1.0
58 | # @version 0.5.1
59 | def initialize(identifier, valid_op, validate_op, cast_op)
60 | @identifier = identifier
61 | @valid_op = valid_op
62 | @validate_op = validate_op
63 | @cast_op = cast_op
64 | end
65 |
66 | # @param value [Any]
67 | # @return [Boolean]
68 | #
69 | # @api private
70 | # @since 0.1.0
71 | def valid?(value)
72 | valid_op.call(value)
73 | end
74 |
75 | # @param value [Any]
76 | # @return [void]
77 | #
78 | # @api private
79 | # @since 0.1.0
80 | def validate!(value)
81 | validate_op.call(value)
82 | end
83 |
84 | # @param value [Any]
85 | # @return [Any]
86 | #
87 | # @api private
88 | # @since 0.1.0
89 | def cast(value)
90 | cast_op.call(value)
91 | end
92 |
93 | private
94 |
95 | # @return [SmartCore::Initializer::TypeSystem::Interop::Operation]
96 | #
97 | # @api private
98 | # @since 0.1.0
99 | attr_reader :valid_op
100 |
101 | # @return [SmartCore::Initializer::TypeSystem::Interop::Operation]
102 | #
103 | # @api private
104 | # @since 0.1.0
105 | attr_reader :validate_op
106 |
107 | # @return [SmartCore::Initializer::TypeSystem::Interop::Operation]
108 | #
109 | # @api private
110 | # @since 0.1.0
111 | attr_reader :cast_op
112 | end
113 |
--------------------------------------------------------------------------------
/lib/smart_core/initializer/type_system/interop/abstract_factory.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # @abstract
4 | # @api private
5 | # @since 0.1.0
6 | class SmartCore::Initializer::TypeSystem::Interop::AbstractFactory
7 | class << self
8 | # @param type [Any]
9 | # @return [SmartCore::Initializer::TypeSystem::Interop]
10 | #
11 | # @api private
12 | # @since 0.1.0
13 | def create(type)
14 | prevent_incompatible_type!(type)
15 |
16 | identifier = build_identifier(type)
17 | valid_op = build_valid_operation(type)
18 | validate_op = build_validate_operation(type)
19 | cast_op = build_cast_operation(type)
20 |
21 | build_interop(identifier, valid_op, validate_op, cast_op)
22 | end
23 |
24 | # @return [Any]
25 | #
26 | # @api private
27 | # @since 0.1.0
28 | def generic_type_object; end
29 |
30 | # @param type [Any]
31 | # @return [String]
32 | #
33 | # @api private
34 | # @since 0.5.1
35 | def build_identifier(type); end
36 |
37 | # @param type [Any]
38 | # @return [void]
39 | #
40 | # @raise [SmartCore::Initializer::IncorrectTypeObjectError]
41 | #
42 | # @api private
43 | # @since 0.1.0
44 | def prevent_incompatible_type!(type); end
45 |
46 | private
47 |
48 | # @param type [Any]
49 | # @return [SmartCore::Initializer::TypeSystem::Interop::Operation]
50 | #
51 | # @api private
52 | # @since 0.1.0
53 | def build_valid_operation(type); end
54 |
55 | # @param type [Any]
56 | # @return [SmartCore::Initializer::TypeSystem::Interop::Operation]
57 | #
58 | # @api private
59 | # @since 0.1.0
60 | def build_validate_operation(type); end
61 |
62 | # @param type [Any]
63 | # @return [SmartCore::Initializer::TypeSystem::Interop::Operation]
64 | #
65 | # @api private
66 | # @since 0.1.0
67 | def build_cast_operation(type); end
68 |
69 | # @param identifier [String]
70 | # @param valid_op [SmartCore::Initializer::TypeSystem::Interop::Operation]
71 | # @param validate_op [SmartCore::Initializer::TypeSystem::Interop::Operation]
72 | # @param cast_op [SmartCore::Initializer::TypeSystem::Interop::Operation]
73 | # @return [SmartCore::Initializer::TypeSystem::Interop]
74 | #
75 | # @api private
76 | # @since 0.1.0
77 | # @version 0.5.1
78 | def build_interop(identifier, valid_op, validate_op, cast_op); end
79 | end
80 | end
81 |
--------------------------------------------------------------------------------
/lib/smart_core/initializer/type_system/interop/aliasing.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # @api private
4 | # @since 0.1.0
5 | module SmartCore::Initializer::TypeSystem::Interop::Aliasing
6 | require_relative 'aliasing/alias_list'
7 |
8 | class << self
9 | # @param base_klass [Class]
10 | # @return [void]
11 | #
12 | # @api private
13 | # @since 0.1.0
14 | def included(base_klass)
15 | base_klass.extend(ClassMethods)
16 | base_klass.singleton_class.prepend(ClassInheritance)
17 | end
18 | end
19 |
20 | # @api private
21 | # @since 0.1.0
22 | module ClassInheritance
23 | # @param child_klass [Class]
24 | # @return [void]
25 | #
26 | # @api private
27 | # @since 0.1.0
28 | def inherited(child_klass)
29 | child_klass.instance_variable_set(:@__type_aliases__, AliasList.new(child_klass))
30 | super
31 | end
32 | end
33 |
34 | # @api private
35 | # @since 0.1.0
36 | module ClassMethods
37 | # @return [SmartCore::Initializer::TypeSystem::Interop::Aliasing::AliasList]
38 | #
39 | # @api private
40 | # @since 0.1.0
41 | def __type_aliases__
42 | @__type_aliases__
43 | end
44 |
45 | # @return [Array]
46 | #
47 | # @api public
48 | # @since 0.1.0
49 | def type_aliases
50 | __type_aliases__.keys
51 | end
52 |
53 | # @param alias_name [String, Symbol]
54 | # @param type [Any]
55 | # @return [void]
56 | #
57 | # @api public
58 | # @since 0.1.0
59 | def type_alias(alias_name, type)
60 | __type_aliases__.associate(alias_name, type)
61 | end
62 |
63 | # @param alias_name [String, Symbol]
64 | # @return [Any]
65 | #
66 | # @api public
67 | # @since 0.1.0
68 | def type_from_alias(alias_name)
69 | __type_aliases__.resolve(alias_name)
70 | end
71 | end
72 | end
73 |
--------------------------------------------------------------------------------
/lib/smart_core/initializer/type_system/interop/aliasing/alias_list.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # @api private
4 | # @since 0.1.0
5 | # @version 0.10.0
6 | class SmartCore::Initializer::TypeSystem::Interop::Aliasing::AliasList
7 | # @param interop_klass [Class]
8 | # @return [void]
9 | #
10 | # @api private
11 | # @since 0.1.0
12 | # @version 0.10.0
13 | def initialize(interop_klass)
14 | @interop_klass = interop_klass
15 | @list = {}
16 | @lock = SmartCore::Engine::ReadWriteLock.new
17 | end
18 |
19 | # @return [Array]
20 | #
21 | # @api private
22 | # @since 0.1.0
23 | # @version 0.10.0
24 | def keys
25 | @lock.read_sync { registered_aliases }
26 | end
27 |
28 | # @return [Hash]
29 | #
30 | # @api private
31 | # @since 0.1.0
32 | # @version 0.10.0
33 | def to_h
34 | @lock.read_sync { transform_to_hash }
35 | end
36 | alias_method :to_hash, :to_h
37 |
38 | # @param alias_name [String, Symbol]
39 | # @param type [Any]
40 | # @return [void]
41 | #
42 | # @api private
43 | # @since 0.1.0
44 | # @version 0.10.0
45 | def associate(alias_name, type)
46 | interop_klass.prevent_incompatible_type!(type)
47 | @lock.write_sync { set_alias(alias_name, type) }
48 | end
49 |
50 | # @param alias_name [String, Symbol]
51 | # @return [Any]
52 | #
53 | # @api private
54 | # @since 0.1.0
55 | # @version 0.10.0
56 | def resolve(alias_name)
57 | @lock.read_sync { get_alias(alias_name) }
58 | end
59 |
60 | private
61 |
62 | # @return [Hash]
63 | #
64 | # @api private
65 | # @since 0.1.0
66 | attr_reader :list
67 |
68 | # @return [Class]
69 | #
70 | # @api private
71 | # @since 0.1.0
72 | attr_reader :interop_klass
73 |
74 | # @param alias_name [String, Symbol]
75 | # @param type [Any]
76 | # @return [void]
77 | #
78 | # @api private
79 | # @since 0.1.0
80 | def set_alias(alias_name, type)
81 | alias_name = normalized_alias(alias_name)
82 |
83 | if list.key?(alias_name)
84 | ::Warning.warn(
85 | "[#{interop_klass.name}] Shadowing of the already existing \"#{alias_name}\" type alias."
86 | )
87 | end
88 |
89 | list[alias_name] = type
90 | end
91 |
92 | # @param alias_name [String, Symbol]
93 | # @return [Any]
94 | #
95 | # @api private
96 | # @since 0.1.0
97 | # @version 0.10.0
98 | def get_alias(alias_name)
99 | alias_name = normalized_alias(alias_name)
100 |
101 | raise(
102 | SmartCore::Initializer::TypeAliasNotFoundError,
103 | "Alias with name `#{alias_name}` not found."
104 | ) unless list.key?(alias_name)
105 |
106 | list.fetch(alias_name)
107 | end
108 |
109 | # @param alias_name [String, Symbol]
110 | # @return [String]
111 | #
112 | # @api private
113 | # @since 0.1.0
114 | def normalized_alias(alias_name)
115 | raise(
116 | SmartCore::Initializer::ArgumentError,
117 | 'Type alias should be a type of string or symbol'
118 | ) unless alias_name.is_a?(String) || alias_name.is_a?(Symbol)
119 |
120 | alias_name.to_s
121 | end
122 |
123 | # @return [Hash]
124 | #
125 | # @api private
126 | # @since 0.1.0
127 | def transform_to_hash
128 | list.to_h
129 | end
130 |
131 | # @return [Array]
132 | #
133 | # @api private
134 | # @since 0.1.0
135 | def registered_aliases
136 | list.keys
137 | end
138 | end
139 |
--------------------------------------------------------------------------------
/lib/smart_core/initializer/type_system/interop/operation.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # @abstract
4 | # @api private
5 | # @since 0.1.0
6 | class SmartCore::Initializer::TypeSystem::Interop::Operation
7 | # @param type [Any]
8 | # @return [void]
9 | #
10 | # @api private
11 | # @since 0.1.0
12 | def initialize(type)
13 | @type = type
14 | end
15 |
16 | # @param value [Any]
17 | # @return [Any]
18 | #
19 | # @api private
20 | # @since 0.1.0
21 | def call(value); end
22 |
23 | private
24 |
25 | # @return [Any]
26 | #
27 | # @api private
28 | # @since 0.1.0
29 | attr_reader :type
30 | end
31 |
--------------------------------------------------------------------------------
/lib/smart_core/initializer/type_system/registry.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # @api private
4 | # @since 0.1.0
5 | # @version 0.10.0
6 | class SmartCore::Initializer::TypeSystem::Registry
7 | # @since 0.1.0
8 | include Enumerable
9 |
10 | # @return [void]
11 | #
12 | # @api private
13 | # @since 0.1.0
14 | # @version 0.10.0
15 | def initialize
16 | @systems = {}
17 | @lock = SmartCore::Engine::ReadWriteLock.new
18 | end
19 |
20 | # @param system_identifier [String, Symbol]
21 | # @param interop_klass [Class]
22 | # @return [void]
23 | #
24 | # @api private
25 | # @since 0.1.0
26 | # @version 0.10.0
27 | def register(system_identifier, interop_klass)
28 | @lock.write_sync { apply(system_identifier, interop_klass) }
29 | end
30 |
31 | # @param system_identifier [String, Symbol]
32 | # @return [Class]
33 | #
34 | # @api private
35 | # @since 0.1.0
36 | # @version 0.10.0
37 | def resolve(system_identifier)
38 | @lock.read_sync { fetch(system_identifier) }
39 | end
40 |
41 | # @return [Array]
42 | #
43 | # @api private
44 | # @since 0.1.0
45 | # @version 0.10.0
46 | def names
47 | @lock.read_sync { system_names }
48 | end
49 |
50 | # @return [Array>]
51 | #
52 | # @api private
53 | # @since 0.1.0
54 | # @version 0.10.0
55 | def interops
56 | @lock.read_sync { system_interops }
57 | end
58 |
59 | # @param block [Block]
60 | # @yield [system_name, system_interop]
61 | # @yieldparam system_name [String]
62 | # @yieldparam system_interop [Class]
63 | # @return [Enumerable]
64 | #
65 | # @api private
66 | # @since 0.1.0
67 | # @version 0.10.0
68 | def each(&block)
69 | @lock.read_sync { iterate(&block) }
70 | end
71 |
72 | # @return [Hash]
73 | #
74 | # @api private
75 | # @since 0.1.0
76 | # @version 0.10.0
77 | def to_h
78 | @lock.write_sync { systems.dup }
79 | end
80 | alias_method :to_hash, :to_h
81 |
82 | private
83 |
84 | # @return [Hash]
85 | #
86 | # @api private
87 | # @since 0.1.0
88 | attr_reader :systems
89 |
90 | # @return [Array]
91 | #
92 | # @pai private
93 | # @since 0.1.0
94 | def system_names
95 | systems.keys
96 | end
97 |
98 | # @return [Array>]
99 | #
100 | # @api private
101 | # @since 0.1.0
102 | def system_interops
103 | systems.values
104 | end
105 |
106 | # @param block [Block]
107 | # @yield [system_name, system_interop]
108 | # @yieldparam system_name [String]
109 | # @yieldparam system_interop [Class]
110 | # @return [Enumerable]
111 | #
112 | # @api private
113 | # @since 0.1.0
114 | def iterate(&block)
115 | block_given? ? systems.each_pair(&block) : systems.each_pair
116 | end
117 |
118 | # @param system_identifier [String, Symbol]
119 | # @param interop_klass [Class]
120 | # @return [void]
121 | #
122 | # @api private
123 | # @since 0.1.0
124 | def apply(system_identifier, interop_klass)
125 | prevent_incorrect_system_interop!(interop_klass)
126 | identifier = indifferently_accessible_identifier(system_identifier)
127 | systems[identifier] = interop_klass
128 | end
129 |
130 | # @param system_identifier [String, Symbol]
131 | # @return [Class]
132 | #
133 | # @raise [SmartCore::Initializer::UnsupportedTypeSystemError]
134 | #
135 | # @api private
136 | # @since 0.1.0
137 | # @version 0.10.0
138 | def fetch(system_identifier)
139 | identifier = indifferently_accessible_identifier(system_identifier)
140 |
141 | raise(
142 | SmartCore::Initializer::UnsupportedTypeSystemError,
143 | "`#{identifier}` type system is not supported."
144 | ) unless systems.key?(identifier)
145 |
146 | systems.fetch(identifier)
147 | end
148 |
149 | # @param interop_klass [Class]
150 | # @return [void]
151 | #
152 | # @raise [SmartCore::Initializer::IncorrectTypeSystemInteropError]
153 | #
154 | # @api private
155 | # @since 0.1.0
156 | def prevent_incorrect_system_interop!(interop_klass)
157 | unless interop_klass.is_a?(Class) && interop_klass < SmartCore::Initializer::TypeSystem::Interop
158 | raise(
159 | SmartCore::Initializer::IncorrectTypeSystemInteropError,
160 | 'Incorrect type system interop class.'
161 | )
162 | end
163 | end
164 |
165 | # @param system_identifier [String, Symbol]
166 | # @return [String]
167 | #
168 | # @api private
169 | # @since 0.1.0
170 | def indifferently_accessible_identifier(system_identifier)
171 | system_identifier.to_s.clone.tap(&:freeze)
172 | end
173 | end
174 |
--------------------------------------------------------------------------------
/lib/smart_core/initializer/type_system/registry_interface.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # @api private
4 | # @since 0.1.0
5 | # @version 0.10.0
6 | module SmartCore::Initializer::TypeSystem::RegistryInterface
7 | class << self
8 | # @param base_module [Class, Module]
9 | # @return [void]
10 | #
11 | # @api private
12 | # @since 0.1.0
13 | # @version 0.10.0
14 | def extended(base_module)
15 | base_module.instance_variable_set(
16 | :@registry, SmartCore::Initializer::TypeSystem::Registry.new
17 | )
18 | base_module.instance_variable_set(
19 | :@access_lock, SmartCore::Engine::ReadWriteLock.new
20 | )
21 | end
22 | end
23 |
24 | # @option system_identifier [String, Symbol]
25 | # @option type [Any]
26 | # @return [SmartCore::Initializer::TypeSystem::Interop]
27 | #
28 | # @api private
29 | # @since 0.1.0
30 | # @version 0.10.0
31 | def build_interop(system: system_identifier, type: type_object)
32 | @access_lock.read_sync { registry.resolve(system_identifier).create(type_object) }
33 | end
34 |
35 | # @param identifier [String, Symbol]
36 | # @param interop_klass [Class]
37 | # @return [void]
38 | #
39 | # @api private
40 | # @since 0.1.0
41 | # @version 0.10.0
42 | def register(identifier, interop_klass)
43 | @access_lock.write_sync { registry.register(identifier, interop_klass) }
44 | end
45 |
46 | # @param identifier [String, Symbol]
47 | # @return [Class]
48 | #
49 | # @api private
50 | # @since 0.1.0
51 | # @version 0.10.0
52 | def resolve(identifier)
53 | @access_lock.read_sync { registry.resolve(identifier) }
54 | end
55 | alias_method :[], :resolve
56 |
57 | # @return [Array]
58 | #
59 | # @api public
60 | # @since 0.1.0
61 | # @version 0.10.0
62 | def names
63 | @access_lock.read_sync { registry.names }
64 | end
65 |
66 | # @return [Array>]
67 | #
68 | # @api public
69 | # @since 0.1.0
70 | # @version 0.10.0
71 | def systems
72 | @access_lock.read_sync { registry.to_h }
73 | end
74 |
75 | # @param block [Block]
76 | # @yield [system_name, system_interop]
77 | # @yieldparam system_name [String]
78 | # @yieldparam system_interop [Class]
79 | # @return [Enumerable]
80 | #
81 | # @api public
82 | # @since 0.1.0
83 | # @version 0.10.0
84 | def each(&block)
85 | @access_lock.read_sync { registry.each(&block) }
86 | end
87 |
88 | private
89 |
90 | # @return [SmartCore::Initializer::TypeSystem::Registry]
91 | #
92 | # @api private
93 | # @since 0.1.0
94 | attr_reader :registry
95 | end
96 |
--------------------------------------------------------------------------------
/lib/smart_core/initializer/type_system/smart_types.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # @api private
4 | # @since 0.1.0
5 | class SmartCore::Initializer::TypeSystem::SmartTypes < SmartCore::Initializer::TypeSystem::Interop
6 | require_relative 'smart_types/abstract_factory'
7 | require_relative 'smart_types/operation'
8 |
9 | type_alias('value.any', SmartCore::Types::Value::Any)
10 | type_alias('value.nil', SmartCore::Types::Value::Nil)
11 | type_alias('value.string', SmartCore::Types::Value::String)
12 | type_alias('value.symbol', SmartCore::Types::Value::Symbol)
13 | type_alias('value.text', SmartCore::Types::Value::Text)
14 | type_alias('value.integer', SmartCore::Types::Value::Integer)
15 | type_alias('value.float', SmartCore::Types::Value::Float)
16 | type_alias('value.numeric', SmartCore::Types::Value::Numeric)
17 | type_alias('value.big_decimal', SmartCore::Types::Value::BigDecimal)
18 | type_alias('value.boolean', SmartCore::Types::Value::Boolean)
19 | type_alias('value.array', SmartCore::Types::Value::Array)
20 | type_alias('value.hash', SmartCore::Types::Value::Hash)
21 | type_alias('value.proc', SmartCore::Types::Value::Proc)
22 | type_alias('value.class', SmartCore::Types::Value::Class)
23 | type_alias('value.module', SmartCore::Types::Value::Module)
24 | type_alias('value.time', SmartCore::Types::Value::Time)
25 | type_alias('value.date_time', SmartCore::Types::Value::DateTime)
26 | type_alias('value.date', SmartCore::Types::Value::Date)
27 | type_alias('value.time_based', SmartCore::Types::Value::TimeBased)
28 |
29 | type_alias(:any, SmartCore::Types::Value::Any)
30 | type_alias(:nil, SmartCore::Types::Value::Nil)
31 | type_alias(:string, SmartCore::Types::Value::String)
32 | type_alias(:symbol, SmartCore::Types::Value::Symbol)
33 | type_alias(:text, SmartCore::Types::Value::Text)
34 | type_alias(:integer, SmartCore::Types::Value::Integer)
35 | type_alias(:float, SmartCore::Types::Value::Float)
36 | type_alias(:numeric, SmartCore::Types::Value::Numeric)
37 | type_alias(:big_decimal, SmartCore::Types::Value::BigDecimal)
38 | type_alias(:boolean, SmartCore::Types::Value::Boolean)
39 | type_alias(:array, SmartCore::Types::Value::Array)
40 | type_alias(:hash, SmartCore::Types::Value::Hash)
41 | type_alias(:proc, SmartCore::Types::Value::Proc)
42 | type_alias(:class, SmartCore::Types::Value::Class)
43 | type_alias(:module, SmartCore::Types::Value::Module)
44 | type_alias(:time, SmartCore::Types::Value::Time)
45 | type_alias(:date_time, SmartCore::Types::Value::DateTime)
46 | type_alias(:date, SmartCore::Types::Value::Date)
47 | type_alias(:time_based, SmartCore::Types::Value::TimeBased)
48 | end
49 |
--------------------------------------------------------------------------------
/lib/smart_core/initializer/type_system/smart_types/abstract_factory.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module SmartCore::Initializer::TypeSystem
4 | # @api private
5 | # @since 0.1.0
6 | class SmartTypes::AbstractFactory < Interop::AbstractFactory
7 | class << self
8 | # @param type [SmartCore::Types::Primitive]
9 | # @return [void]
10 | #
11 | # @raise [SmartCore::Initializer::IncorrectTypeObjectError]
12 | #
13 | # @api private
14 | # @since 0.1.0
15 | def prevent_incompatible_type!(type)
16 | unless type.is_a?(SmartCore::Types::Primitive)
17 | raise(
18 | SmartCore::Initializer::IncorrectTypeObjectError,
19 | 'Incorrect SmartCore::Types primitive ' \
20 | '(type object should be a type of SmartCore::Types::Primitive)'
21 | )
22 | end
23 | end
24 |
25 | # @param type [Any]
26 | # @return [String]
27 | #
28 | # @api private
29 | # @since 0.5.1
30 | def build_identifier(type)
31 | type.name
32 | end
33 |
34 | # @param type [SmartCore::Types::Primitive]
35 | # @return [SmartCore::Initializer::TypeSystem::SmartTypes::Operation::Valid]
36 | #
37 | # @api private
38 | # @since 0.1.0
39 | def build_valid_operation(type)
40 | SmartTypes::Operation::Valid.new(type)
41 | end
42 |
43 | # @return [SmartCore::Types::Value::Any]
44 | #
45 | # @api private
46 | # @since 0.1.0
47 | def generic_type_object
48 | SmartCore::Types::Value::Any
49 | end
50 |
51 | # @param type [SmartCore::Types::Primitive]
52 | # @return [SmartCore::Initializer::TypeSystem::SmartTypes::Operation::Validate]
53 | #
54 | # @api private
55 | # @since 0.1.0
56 | def build_validate_operation(type)
57 | SmartTypes::Operation::Validate.new(type)
58 | end
59 |
60 | # @param type [SmartCore::Types::Primitive]
61 | # @return [SmartCore::Initializer::TypeSystem::SmartTypes::Operation::Cast]
62 | #
63 | # @api private
64 | # @since 0.1.0
65 | def build_cast_operation(type)
66 | SmartTypes::Operation::Cast.new(type)
67 | end
68 |
69 | # @param identifier [String]
70 | # @param valid_op [SmartCore::Initializer::TypeSystem::SmartTypes::Operation::Valid]
71 | # @param valid_op [SmartCore::Initializer::TypeSystem::SmartTypes::Operation::Validate]
72 | # @param valid_op [SmartCore::Initializer::TypeSystem::SmartTypes::Operation::Cast]
73 | # @return [SmartCore::Initializer::TypeSystem::SmartTypes]
74 | #
75 | # @api private
76 | # @since 0.1.0
77 | # @version 0.5.1
78 | def build_interop(identifier, valid_op, validate_op, cast_op)
79 | SmartTypes.new(identifier, valid_op, validate_op, cast_op)
80 | end
81 | end
82 | end
83 | end
84 |
--------------------------------------------------------------------------------
/lib/smart_core/initializer/type_system/smart_types/operation.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module SmartCore::Initializer::TypeSystem
4 | # @abstract
5 | # @api private
6 | # @since 0.1.0
7 | module SmartTypes::Operation
8 | require_relative 'operation/base'
9 | require_relative 'operation/valid'
10 | require_relative 'operation/validate'
11 | require_relative 'operation/cast'
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/lib/smart_core/initializer/type_system/smart_types/operation/base.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module SmartCore::Initializer::TypeSystem
4 | # @api private
5 | # @since 0.1.0
6 | class SmartTypes::Operation::Base < Interop::Operation
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/lib/smart_core/initializer/type_system/smart_types/operation/cast.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module SmartCore::Initializer::TypeSystem::SmartTypes::Operation
4 | # @api private
5 | # @since 0.1.0
6 | class Cast < Base
7 | # @param value [Any]
8 | # @return [Any]
9 | #
10 | # @api private
11 | # @since 0.1.0
12 | def call(value)
13 | type.cast(value)
14 | end
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/lib/smart_core/initializer/type_system/smart_types/operation/valid.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module SmartCore::Initializer::TypeSystem::SmartTypes::Operation
4 | # @api private
5 | # @since 0.1.0
6 | class Valid < Base
7 | # @param value [Any]
8 | # @return [Boolean]
9 | #
10 | # @api private
11 | # @since 0.1.0
12 | def call(value)
13 | type.valid?(value)
14 | end
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/lib/smart_core/initializer/type_system/smart_types/operation/validate.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module SmartCore::Initializer::TypeSystem::SmartTypes::Operation
4 | # @api private
5 | # @since 0.1.0
6 | class Validate < Base
7 | # @param value [Any]
8 | # @return [void]
9 | #
10 | # @api private
11 | # @since 0.1.0
12 | def call(value)
13 | type.validate!(value)
14 | end
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/lib/smart_core/initializer/version.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module SmartCore
4 | module Initializer
5 | # @return [String]
6 | #
7 | # @api public
8 | # @since 0.1.0
9 | # @version 0.11.1
10 | VERSION = '0.12.0'
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/smart_initializer.gemspec:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require_relative 'lib/smart_core/initializer/version'
4 |
5 | Gem::Specification.new do |spec|
6 | spec.required_ruby_version = Gem::Requirement.new('>= 2.7')
7 |
8 | spec.name = 'smart_initializer'
9 | spec.version = SmartCore::Initializer::VERSION
10 | spec.authors = ['Rustam Ibragimov']
11 | spec.email = ['iamdaiver@gmail.com']
12 |
13 | spec.summary = 'Initializer DSL'
14 | spec.description = 'A simple and convenient way to declare complex constructors'
15 | spec.homepage = 'https://github.com/smart-rb/smart_initializer'
16 | spec.license = 'MIT'
17 |
18 | spec.metadata['homepage_uri'] =
19 | spec.homepage
20 | spec.metadata['source_code_uri'] =
21 | 'https://github.com/smart-rb/smart_initializer'
22 | spec.metadata['changelog_uri'] =
23 | 'https://github.com/smart-rb/smart_initializer/blob/master/CHANGELOG.md'
24 |
25 | spec.files = Dir.chdir(File.expand_path(__dir__)) do
26 | `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
27 | end
28 |
29 | spec.bindir = 'exe'
30 | spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
31 | spec.require_paths = ['lib']
32 |
33 | spec.add_dependency 'smart_engine', '~> 0.16'
34 | spec.add_dependency 'smart_types', '~> 0.8'
35 | spec.add_dependency 'qonfig', '~> 0.24'
36 |
37 | spec.add_development_dependency 'bundler', '~> 2.3'
38 | spec.add_development_dependency 'rake', '~> 13.0'
39 | spec.add_development_dependency 'rspec', '~> 3.11'
40 | spec.add_development_dependency 'armitage-rubocop', '~> 1.30'
41 | spec.add_development_dependency 'simplecov', '~> 0.21'
42 | spec.add_development_dependency 'pry', '~> 0.14'
43 | spec.add_development_dependency 'ostruct'
44 | spec.add_development_dependency 'bigdecimal'
45 | end
46 |
--------------------------------------------------------------------------------
/spec/features/configuration_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | RSpec.describe 'Initializer configuration' do
4 | describe 'type system' do
5 | specify 'default type system' do
6 | expect(SmartCore::Initializer::Configuration[:default_type_system]).to eq(:smart_types)
7 | end
8 |
9 | specify 'unsupported type system - fail' do
10 | expect(SmartCore::Initializer::Configuration.config.valid_with?({
11 | default_type_system: :kek_pek
12 | })).to eq(false)
13 | end
14 |
15 | specify 'you can choose any supported type system' do
16 | expect(SmartCore::Initializer::Configuration.config.valid_with?({
17 | default_type_system: :smart_types
18 | })).to eq(true)
19 | end
20 | end
21 |
22 | describe 'strict_options' do
23 | specify 'default value' do
24 | expect(SmartCore::Initializer::Configuration[:strict_options]).to eq(true)
25 | end
26 |
27 | specify 'unsupported value => fail' do
28 | expect(SmartCore::Initializer::Configuration.config.valid_with?({
29 | strict_options: :kek_pek
30 | })).to eq(false)
31 | end
32 |
33 | specify 'you choose any supported config-switching' do
34 | expect(SmartCore::Initializer::Configuration.config.valid_with?({
35 | strict_options: true
36 | })).to eq(true)
37 |
38 | expect(SmartCore::Initializer::Configuration.config.valid_with?({
39 | strict_options: false
40 | })).to eq(true)
41 | end
42 | end
43 |
44 | describe 'auto_cast' do
45 | specify 'default value' do
46 | expect(SmartCore::Initializer::Configuration[:auto_cast]).to eq(false)
47 | end
48 |
49 | specify 'unsupported value => fail' do
50 | expect(SmartCore::Initializer::Configuration.config.valid_with?({
51 | auto_cast: :kek_pek
52 | })).to eq(false)
53 | end
54 |
55 | specify 'you choose any supported config-switching' do
56 | expect(SmartCore::Initializer::Configuration.config.valid_with?({
57 | auto_cast: true
58 | })).to eq(true)
59 |
60 | expect(SmartCore::Initializer::Configuration.config.valid_with?({
61 | auto_cast: false
62 | })).to eq(true)
63 | end
64 | end
65 | end
66 |
--------------------------------------------------------------------------------
/spec/features/instance_attribute_list_access_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | RSpec.describe 'Instance attribute list access' do
4 | specify '#__params__ / #__options__ / #__attributes__' do
5 | instance = Class.new do
6 | include SmartCore::Initializer
7 | param :first_name, 'string', as: :first
8 | param :second_name, 'string', as: :last
9 | option :age, 'numeric', as: :years
10 | option :is_admin, 'boolean', default: true
11 | end.new('Rustam', 'Ibragimov', age: 27)
12 |
13 | aggregate_failures 'attribute access (result without aliases)' do
14 | expect(instance.__params__).to eq({
15 | first_name: 'Rustam',
16 | second_name: 'Ibragimov'
17 | })
18 |
19 | expect(instance.__options__).to eq({
20 | age: 27,
21 | is_admin: true
22 | })
23 |
24 | expect(instance.__attributes__).to eq({
25 | first_name: 'Rustam',
26 | second_name: 'Ibragimov',
27 | age: 27,
28 | is_admin: true
29 | })
30 | end
31 | end
32 | end
33 |
--------------------------------------------------------------------------------
/spec/features/integration_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | RSpec.describe 'SmartCore::Initializer integration' do
4 | describe 'configurable integration' do
5 | describe 'type_system config' do
6 | specify 'default config value (:smart_types)' do
7 | custom_klass = Class.new { include SmartCore::Initializer }
8 | expect(custom_klass.__initializer_settings__.type_system).to eq(:smart_types)
9 | end
10 |
11 | specify 'custom type_system definition' do
12 | custom_klass = Class.new { include SmartCore::Initializer(type_system: :smart_types) }
13 | expect(custom_klass.__initializer_settings__.type_system).to eq(:smart_types)
14 | end
15 |
16 | specify 'fails when the chosen type system is incorrect or unsupporeted' do
17 | expect do
18 | Class.new { include SmartCore::Initializer(type_system: :kek_pek) }
19 | end.to raise_error(SmartCore::Initializer::UnsupportedTypeSystemError)
20 | end
21 | end
22 | end
23 |
24 | describe 'strict_options config' do
25 | specify 'default config value (true)' do
26 | custom_klass = Class.new { include SmartCore::Initializer }
27 | expect(custom_klass.__initializer_settings__.strict_options).to eq(true)
28 | end
29 |
30 | specify 'custom strict_options definition' do
31 | # setup local config
32 | custom_klass = Class.new { include SmartCore::Initializer(strict_options: false) }
33 | expect(custom_klass.__initializer_settings__.strict_options).to eq(false)
34 | # check that global config has not been changed
35 | expect(SmartCore::Initializer::Configuration[:strict_options]).to eq(true)
36 |
37 | # setup another local config
38 | custom_klass = Class.new { include SmartCore::Initializer(strict_options: true) }
39 | expect(custom_klass.__initializer_settings__.strict_options).to eq(true)
40 | # check that global config has not been changed
41 | expect(SmartCore::Initializer::Configuration[:strict_options]).to eq(true)
42 | end
43 |
44 | specify 'fails when the strict_options value has incorrect type' do
45 | expect do
46 | Class.new { include SmartCore::Initializer(strict_options: 123) }
47 | end.to raise_error(SmartCore::Initializer::SettingArgumentError)
48 | end
49 | end
50 |
51 | describe 'auto_cast config' do
52 | specify 'default config value (true)' do
53 | custom_klass = Class.new { include SmartCore::Initializer }
54 | expect(custom_klass.__initializer_settings__.auto_cast).to eq(false)
55 | end
56 |
57 | specify 'custom auto_cast definition' do
58 | # setup local config
59 | custom_klass = Class.new { include SmartCore::Initializer(auto_cast: true) }
60 | expect(custom_klass.__initializer_settings__.auto_cast).to eq(true)
61 | # check that global config has not been changed
62 | expect(SmartCore::Initializer::Configuration[:auto_cast]).to eq(false)
63 |
64 | # setup another local config
65 | custom_klass = Class.new { include SmartCore::Initializer(auto_cast: false) }
66 | expect(custom_klass.__initializer_settings__.auto_cast).to eq(false)
67 | # check that global config has not been changed
68 | expect(SmartCore::Initializer::Configuration[:auto_cast]).to eq(false)
69 | end
70 |
71 | specify 'fails when the auto_cast value has incorrect type' do
72 | expect do
73 | Class.new { include SmartCore::Initializer(auto_cast: 123) }
74 | end.to raise_error(SmartCore::Initializer::SettingArgumentError)
75 | end
76 | end
77 |
78 | describe 'mixed configuration' do
79 | specify 'you can configure your own configs per class with inherited globals' do
80 | expect(SmartCore::Initializer::Configuration[:auto_cast]).to eq(false)
81 | expect(SmartCore::Initializer::Configuration[:default_type_system]).to eq(:smart_types)
82 | expect(SmartCore::Initializer::Configuration[:strict_options]).to eq(true)
83 |
84 | aggregate_failures 'inheritable defaults' do
85 | klass = Class.new { include SmartCore::Initializer }
86 |
87 | expect(klass.__initializer_settings__.auto_cast).to eq(false)
88 | expect(klass.__initializer_settings__.type_system).to eq(:smart_types)
89 | expect(klass.__initializer_settings__.strict_options).to eq(true)
90 | end
91 |
92 | aggregate_failures 'local configs has higher priority over the global configs' do
93 | klass = Class.new do
94 | include SmartCore::Initializer(auto_cast: true, strict_options: false)
95 | end
96 |
97 | expect(klass.__initializer_settings__.auto_cast).to eq(true)
98 | expect(klass.__initializer_settings__.strict_options).to eq(false)
99 | expect(klass.__initializer_settings__.type_system).to eq(:smart_types)
100 | end
101 | end
102 |
103 | context 'global config affect' do
104 | specify 'global configs affects local configs' do
105 | klass = Class.new { include SmartCore::Initializer }
106 | # initial global config
107 | expect(klass.__initializer_settings__.auto_cast).to eq(false)
108 | # initial global config
109 | expect(klass.__initializer_settings__.strict_options).to eq(true)
110 |
111 | # temporary change the global config
112 | SmartCore::Initializer::Configuration.config.with({
113 | auto_cast: true,
114 | strict_options: false
115 | }) do
116 | klass = Class.new { include SmartCore::Initializer }
117 |
118 | # new global config
119 | expect(klass.__initializer_settings__.auto_cast).to eq(true)
120 | # new global config
121 | expect(klass.__initializer_settings__.strict_options).to eq(false)
122 | end
123 | end
124 | end
125 | end
126 | end
127 |
--------------------------------------------------------------------------------
/spec/features/plugin_system/load_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class SmartCore::Initializer::ALoadTest < SmartCore::Initializer::Plugins::Abstract
4 | def self.install!; end
5 | end
6 |
7 | class SmartCore::Initializer::BLoadTest < SmartCore::Initializer::Plugins::Abstract
8 | def self.install!; end
9 | end
10 |
11 | class SmartCore::Initializer::CLoadTest < SmartCore::Initializer::Plugins::Abstract
12 | def self.install!
13 | C_TEST_INTERCEPTOR.invoke
14 | end
15 | end
16 |
17 | class SmartCore::Initializer::DLoadTest < SmartCore::Initializer::Plugins::Abstract
18 | def self.install!
19 | D_TEST_INTERCEPTOR.invoke
20 | end
21 | end
22 |
23 | # rubocop:disable Layout/LineLength
24 | SmartCore::Initializer::Configuration.register_plugin(:a_load_test, SmartCore::Initializer::ALoadTest)
25 | SmartCore::Initializer::Configuration.register_plugin(:b_load_test, SmartCore::Initializer::BLoadTest)
26 | SmartCore::Initializer::Configuration.register_plugin(:c_load_test, SmartCore::Initializer::CLoadTest)
27 | SmartCore::Initializer::Configuration.register_plugin(:d_load_test, SmartCore::Initializer::DLoadTest)
28 | # rubocop:enable Layout/LineLength
29 |
30 | RSpec.describe 'SmartCore::Initializer::Plugins' do
31 | before do
32 | interceptor = Class.new { def invoke; end }
33 | stub_const('C_TEST_INTERCEPTOR', interceptor.new)
34 | stub_const('D_TEST_INTERCEPTOR', interceptor.new)
35 | end
36 |
37 | describe 'installation' do
38 | specify 'plugin loading interface' do
39 | expect(SmartCore::Initializer::ALoadTest).to receive(:load!).exactly(2).times
40 | expect(SmartCore::Initializer::BLoadTest).to receive(:load!).exactly(2).times
41 |
42 | SmartCore::Initializer::Configuration.plugin('a_load_test')
43 | SmartCore::Initializer::Configuration.plugin(:a_load_test)
44 | SmartCore::Initializer::Configuration.plugin('b_load_test')
45 | SmartCore::Initializer::Configuration.plugin(:b_load_test)
46 | end
47 |
48 | specify 'loaded plugins' do
49 | expect(SmartCore::Initializer::Configuration.loaded_plugins).not_to include(
50 | 'a_load_test', 'b_load_test'
51 | )
52 |
53 | SmartCore::Initializer::Configuration.plugin('a_load_test')
54 | expect(SmartCore::Initializer::Configuration.loaded_plugins).to include('a_load_test')
55 | expect(SmartCore::Initializer::Configuration.loaded_plugins).not_to include('b_load_test')
56 |
57 | SmartCore::Initializer::Configuration.plugin('b_load_test')
58 | expect(SmartCore::Initializer::Configuration.loaded_plugins).to include(
59 | 'a_load_test', 'b_load_test'
60 | )
61 | end
62 | end
63 |
64 | specify 'loading (loads only one time)' do
65 | expect(C_TEST_INTERCEPTOR).to receive(:invoke).exactly(1).time
66 | expect(D_TEST_INTERCEPTOR).to receive(:invoke).exactly(1).time
67 |
68 | SmartCore::Initializer::Configuration.plugin('c_load_test')
69 | SmartCore::Initializer::Configuration.plugin(:c_load_test)
70 | SmartCore::Initializer::Configuration.plugin('d_load_test')
71 | SmartCore::Initializer::Configuration.plugin(:d_load_test)
72 | end
73 | end
74 |
--------------------------------------------------------------------------------
/spec/features/plugin_system/registration_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # rubocop:disable Layout/LineLength
4 | SmartCore::Initializer::Plugins::ExistenceTest = Class.new(SmartCore::Initializer::Plugins::Abstract)
5 | SmartCore::Initializer::Configuration.register_plugin(:existence_test, SmartCore::Initializer::Plugins::ExistenceTest)
6 | # rubocop:enable Layout/LineLength
7 |
8 | SmartCore::Initializer::Plugins::ARegTest = Class.new(SmartCore::Initializer::Plugins::Abstract)
9 | SmartCore::Initializer::Plugins::BRegTest = Class.new(SmartCore::Initializer::Plugins::Abstract)
10 |
11 | RSpec.describe 'SmartCore::Initializer::Plugins' do
12 | specify 'plugin registration' do
13 | expect(SmartCore::Initializer::Plugins.names).not_to include('a_reg_test', 'b_reg_test')
14 | expect(SmartCore::Initializer::Configuration.plugins).not_to include('a_reg_test', 'b_reg_test')
15 |
16 | SmartCore::Initializer::Configuration.register_plugin(
17 | :a_reg_test, SmartCore::Initializer::Plugins::ARegTest
18 | )
19 |
20 | expect(SmartCore::Initializer::Plugins.names).to include('a_reg_test')
21 | expect(SmartCore::Initializer::Configuration.plugins).to include('a_reg_test')
22 | expect(SmartCore::Initializer::Plugins.names).not_to include('b_reg_test')
23 | expect(SmartCore::Initializer::Configuration.plugins).not_to include('b_reg_test')
24 |
25 | SmartCore::Initializer::Configuration.register_plugin(
26 | :b_reg_test, SmartCore::Initializer::Plugins::BRegTest
27 | )
28 |
29 | expect(SmartCore::Initializer::Plugins.names).to include('a_reg_test', 'b_reg_test')
30 | expect(SmartCore::Initializer::Configuration.plugins).to include('a_reg_test', 'b_reg_test')
31 | end
32 |
33 | describe 'incompatability-related failures' do
34 | specify 'plugin registration which name is in conflict with already registered plugin' do
35 | expect do
36 | SmartCore::Initializer::Plugins.register_plugin(:existence_test, Object)
37 | end.to raise_error(SmartCore::Initializer::AlreadyRegisteredPluginError)
38 | expect do
39 | SmartCore::Initializer::Configuration.register_plugin(:existence_test, Object)
40 | end.to raise_error(SmartCore::Initializer::AlreadyRegisteredPluginError)
41 | end
42 |
43 | specify 'loading of unregistered plugin' do
44 | expect do
45 | SmartCore::Initializer::Configuration.plugin(:kek_test_plugin)
46 | end.to raise_error(SmartCore::Initializer::UnregisteredPluginError)
47 | end
48 | end
49 | end
50 |
--------------------------------------------------------------------------------
/spec/features/plugins/thy_types_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | RSpec.describe 'Plugins: thy_types', plugin: :thy_types do
4 | before do
5 | require 'thy'
6 | SmartCore::Initializer::Configuration.plugin(:thy_types)
7 | end
8 |
9 | specify 'aliases' do
10 | expect(SmartCore::Initializer::TypeSystem::ThyTypes.type_aliases).to contain_exactly(
11 | 'any',
12 | 'nil',
13 | 'string',
14 | 'symbol',
15 | 'integer',
16 | 'float',
17 | 'numeric',
18 | 'boolean',
19 | 'time',
20 | 'date_time',
21 | 'untyped_array',
22 | 'untyped_hash'
23 | )
24 |
25 | expect(SmartCore::Initializer::TypeSystem::ThyTypes.type_from_alias('any')).to(
26 | respond_to(:check)
27 | )
28 | expect(SmartCore::Initializer::TypeSystem::ThyTypes.type_from_alias('nil')).to eq(
29 | Thy::Types::Nil
30 | )
31 | expect(SmartCore::Initializer::TypeSystem::ThyTypes.type_from_alias('string')).to eq(
32 | Thy::Types::String
33 | )
34 | expect(SmartCore::Initializer::TypeSystem::ThyTypes.type_from_alias('symbol')).to eq(
35 | Thy::Types::Symbol
36 | )
37 | expect(SmartCore::Initializer::TypeSystem::ThyTypes.type_from_alias('integer')).to eq(
38 | Thy::Types::Integer
39 | )
40 | expect(SmartCore::Initializer::TypeSystem::ThyTypes.type_from_alias('float')).to eq(
41 | Thy::Types::Float
42 | )
43 | expect(SmartCore::Initializer::TypeSystem::ThyTypes.type_from_alias('numeric')).to eq(
44 | Thy::Types::Numeric
45 | )
46 | expect(SmartCore::Initializer::TypeSystem::ThyTypes.type_from_alias('boolean')).to eq(
47 | Thy::Types::Boolean
48 | )
49 | expect(SmartCore::Initializer::TypeSystem::ThyTypes.type_from_alias('time')).to eq(
50 | Thy::Types::Time
51 | )
52 | expect(SmartCore::Initializer::TypeSystem::ThyTypes.type_from_alias('date_time')).to eq(
53 | Thy::Types::DateTime
54 | )
55 | expect(SmartCore::Initializer::TypeSystem::ThyTypes.type_from_alias('untyped_array')).to eq(
56 | Thy::Types::UntypedArray
57 | )
58 | expect(SmartCore::Initializer::TypeSystem::ThyTypes.type_from_alias('untyped_hash')).to eq(
59 | Thy::Types::UntypedHash
60 | )
61 | end
62 |
63 | describe 'usage' do
64 | specify 'initializer with thy-types' do
65 | data_klass = Class.new do
66 | include SmartCore::Initializer(type_system: :thy_types)
67 |
68 | param :login, Thy::Types::String
69 | param :password, 'string'
70 |
71 | option :age, 'numeric'
72 | option :as_admin, Thy::Types::Boolean
73 | end
74 |
75 | instance = data_klass.new('vasia', 'pupkin', { age: 23, as_admin: true })
76 |
77 | expect(instance.login).to eq('vasia')
78 | expect(instance.password).to eq('pupkin')
79 | expect(instance.age).to eq(23)
80 | expect(instance.as_admin).to eq(true)
81 | end
82 |
83 | specify 'mixing thy-types with smart-types' do
84 | data_klass = Class.new do
85 | include SmartCore::Initializer(type_system: :thy_types)
86 |
87 | param :nickname, SmartCore::Types::Value::String, type_system: :smart_types
88 | param :password, Thy::Types::String
89 |
90 | option :balance, 'value.float', type_system: :smart_types
91 | option :version, 'float' # thy-types
92 | end
93 |
94 | instance = data_klass.new('over', 'watch', { balance: 12.34, version: 15.707 })
95 |
96 | expect(instance.nickname).to eq('over')
97 | expect(instance.password).to eq('watch')
98 | expect(instance.balance).to eq(12.34)
99 | expect(instance.version).to eq(15.707)
100 | end
101 |
102 | specify 'no support for type cast' do
103 | data_klass = Class.new do
104 | include SmartCore::Initializer(type_system: :thy_types)
105 | param :nickname, 'string', cast: true
106 | end
107 |
108 | expect do
109 | data_klass.new(123)
110 | end.to raise_error(SmartCore::Initializer::TypeCastingUnsupportedError)
111 |
112 | expect { data_klass.new('123') }.not_to raise_error
113 | end
114 |
115 | specify 'validation' do
116 | data_klass = Class.new do
117 | include SmartCore::Initializer(type_system: :thy_types)
118 | param :nickname, 'string'
119 | option :age, 'integer'
120 | end
121 |
122 | # NOTE: invalid
123 | expect { data_klass.new('test', { age: '123' }) }.to raise_error(
124 | # NOTE: old version => SmartCore::Initializer::ThyTypeValidationError
125 | SmartCore::Initializer::IncorrectTypeError
126 | )
127 |
128 | # NOTE: invalid
129 | expect { data_klass.new(123, { age: 123 }) }.to raise_error(
130 | # NOTE: old version => SmartCore::Initializer::ThyTypeValidationError
131 | SmartCore::Initializer::IncorrectTypeError
132 | )
133 |
134 | # NOTE: valid
135 | expect { data_klass.new('123', { age: 123 }) }.not_to raise_error
136 | end
137 |
138 | specify 'validation check for smart-types mixed with thy-types' do
139 | data_klass = Class.new do
140 | include SmartCore::Initializer(type_system: :thy_types)
141 |
142 | param :nickname, 'string', type_system: :smart_types
143 | param :email, 'string'
144 | end
145 |
146 | expect { data_klass.new(123, 'iamdaiver@gmail.com') }.to raise_error(
147 | # NOTE: old version => SmartCore::Types::TypeError
148 | SmartCore::Initializer::IncorrectTypeError
149 | )
150 |
151 | expect { data_klass.new('123', :email) }.to raise_error(
152 | # NOTE: old version => SmartCore::Initializer::ThyTypeValidationError
153 | SmartCore::Initializer::IncorrectTypeError
154 | )
155 |
156 | expect { data_klass.new('123', 'iamdaiver@gmail.com') }.not_to raise_error
157 | end
158 |
159 | specify 'support for the common attribute definition functionality' do
160 | data_klass = Class.new do
161 | include SmartCore::Initializer(type_system: :thy_types)
162 |
163 | option :email, 'string', default: 'no@email.com', finalize: -> (value) { "1#{value}" }
164 | option :admin, 'boolean', default: false, finalize: -> (value) { true }
165 | end
166 |
167 | instance = data_klass.new
168 | expect(instance.email).to eq('1no@email.com')
169 | expect(instance.admin).to eq(true)
170 |
171 | instance = data_klass.new(email: 'iamdaiver@gmail.com', admin: false)
172 | expect(instance.email).to eq('1iamdaiver@gmail.com')
173 | expect(instance.admin).to eq(true)
174 | end
175 | end
176 | end
177 |
--------------------------------------------------------------------------------
/spec/features/type_system/custom_type_system_integration_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | RSpec.describe 'Type System: custom type system integration' do
4 | end
5 |
--------------------------------------------------------------------------------
/spec/features/type_system/type_aliasing_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | RSpec.describe 'Type System: type aliasing' do
4 | end
5 |
--------------------------------------------------------------------------------
/spec/smoke_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | RSpec.describe 'Smoke Test' do
4 | specify 'full definition' do
5 | class User
6 | include SmartCore::Initializer
7 | # include SmartCore::Types::System(:T)
8 | # param :test, T::Value::Integer
9 |
10 | param :user_id, SmartCore::Types::Value::Integer, cast: true, privacy: :private
11 | option :password, :integer, cast: true, default: 'test', privacy: :private
12 | option :keka, finalize: (-> (value) { value.to_s })
13 | option :send, :string, default: 'yes!' # you can use `send` as attribtue name
14 |
15 | params :creds, :nickname
16 | options :metadata, :datameta
17 |
18 | def initialize(*attrs, &block)
19 | @attrs = attrs
20 | @block = block
21 | end
22 |
23 | attr_reader :attrs
24 | attr_reader :block
25 | end
26 |
27 | random_block_result = rand(1...1000)
28 | user = User.new(
29 | 1, { admin: true }, '0exp', password: 'kek', metadata: {}, datameta: {}, keka: 123
30 | ) { random_block_result }
31 |
32 | expect(user).to be_a(User)
33 |
34 | expect { user.user_id }.to raise_error(NoMethodError)
35 | expect(user.__send__(:user_id)).to eq(1)
36 | expect(user.creds).to eq(admin: true)
37 | expect(user.nickname).to eq('0exp')
38 | expect { user.password }.to raise_error(NoMethodError)
39 | expect(user.__send__(:password)).to eq(0)
40 | expect(user.metadata).to eq({})
41 | expect(user.datameta).to eq({})
42 | expect(user.keka).to eq('123')
43 | expect(user.send).to eq('yes!')
44 |
45 | expect(user.attrs).to eq(
46 | [1, { admin: true }, '0exp',
47 | { password: 'kek', metadata: {}, datameta: {}, keka: 123 }]
48 | )
49 |
50 | expect(user.block).to be_a(Proc)
51 | expect(user.block.call).to eq(random_block_result)
52 | end
53 |
54 | specify 'incompatible param/params/option/options names should raise an error' do
55 | expect do
56 | Class.new do
57 | include SmartCore::Initializer
58 | param 123
59 | end
60 | end.to raise_error(SmartCore::Initializer::ArgumentError)
61 |
62 | expect do
63 | Class.new do
64 | include SmartCore::Initializer
65 | option 123
66 | end
67 | end.to raise_error(SmartCore::Initializer::ArgumentError)
68 |
69 | expect do
70 | Class.new do
71 | include SmartCore::Initializer
72 | params :a, :b, 123, Object.new, 'test'
73 | end
74 | end.to raise_error(SmartCore::Initializer::ArgumentError)
75 |
76 | expect do
77 | Class.new do
78 | include SmartCore::Initializer
79 | options :a, :b, 123, Object.new, 'test'
80 | end
81 | end.to raise_error(SmartCore::Initializer::ArgumentError)
82 | end
83 |
84 | specify 'type alias shadowing warn' do
85 | expect do
86 | SmartCore::Initializer::TypeSystem::SmartTypes.type_alias(
87 | :string, SmartCore::Types::Value::String
88 | )
89 | end.to output(
90 | '[SmartCore::Initializer::TypeSystem::SmartTypes] ' \
91 | 'Shadowing of the already existing "string" type alias.'
92 | ).to_stderr
93 |
94 | expect do
95 | SmartCore::Initializer::TypeSystem::SmartTypes.type_alias(
96 | :integer, SmartCore::Types::Value::Integer
97 | )
98 | end.to output(
99 | '[SmartCore::Initializer::TypeSystem::SmartTypes] ' \
100 | 'Shadowing of the already existing "integer" type alias.'
101 | ).to_stderr
102 | end
103 |
104 | specify 'invalid/unsupported type attribute' do
105 | expect do
106 | Class.new do
107 | include SmartCore::Initializer
108 | param :name, Object.new
109 | end
110 | end.to raise_error(SmartCore::Initializer::IncorrectTypeObjectError)
111 |
112 | expect do
113 | Class.new do
114 | include SmartCore::Initializer
115 | option :name, Object.new
116 | end
117 | end.to raise_error(SmartCore::Initializer::IncorrectTypeObjectError)
118 |
119 | expect do
120 | Class.new do
121 | include SmartCore::Initializer
122 | option :name, :my_custom_type_alias
123 | end
124 | end.to raise_error(SmartCore::Initializer::TypeAliasNotFoundError)
125 |
126 | expect do
127 | Class.new do
128 | include SmartCore::Initializer
129 | param :name, :my_custom_type_alias
130 | end
131 | end.to raise_error(SmartCore::Initializer::TypeAliasNotFoundError)
132 | end
133 |
134 | specify ':privacy attribute functionality' do
135 | aggregate_failures 'correct values amd fuctionality' do
136 | instance = Class.new do
137 | include SmartCore::Initializer
138 |
139 | param :a, privacy: :private
140 | param :b, privacy: :protected
141 | param :c, privacy: :public
142 |
143 | param :d, privacy: 'private'
144 | param :e, privacy: 'protected'
145 | param :g, privacy: 'public'
146 |
147 | params :a1, :a2, privacy: :private
148 | params :c1, :c2, privacy: :protected
149 | params :d1, :d2, privacy: :public
150 |
151 | params :a3, :a4, privacy: 'private'
152 | params :c3, :c4, privacy: 'protected'
153 | params :d3, :d4, privacy: 'public'
154 |
155 | option :o, privacy: :private
156 | option :p, privacy: :protected
157 | option :q, privacy: :public
158 |
159 | option :r, privacy: 'private'
160 | option :s, privacy: 'protected'
161 | option :t, privacy: 'public'
162 |
163 | options :x1, :x2, privacy: :private
164 | options :y1, :y2, privacy: :protected
165 | options :z1, :z2, privacy: :public
166 |
167 | options :x3, :x4, privacy: 'private'
168 | options :y3, :y4, privacy: 'protected'
169 | options :z3, :z4, privacy: 'public'
170 | end.new(
171 | '', '', '', '', '', '',
172 | '', '', '', '', '', '',
173 | '', '', '', '', '', '',
174 | o: 1, p: 1, q: 1, r: 1, s: 1, t: 1,
175 | x1: 1, x2: 1, x3: 1, x4: 1,
176 | y1: 1, y2: 1, y3: 1, y4: 1,
177 | z1: 1, z2: 1, z3: 1, z4: 1
178 | )
179 |
180 | expect(instance.private_methods(false)).to contain_exactly(*%i[
181 | a d a1 a2 a3 a4 o r x1 x2 x3 x4
182 | ])
183 |
184 | expect(instance.protected_methods(false)).to contain_exactly(*%i[
185 | b e c1 c2 c3 c4 p s y1 y2 y3 y4
186 | ])
187 |
188 | expect(instance.public_methods(false)).to contain_exactly(*%i[
189 | c g d1 d2 d3 d4 q t z1 z2 z3 z4
190 | ])
191 | end
192 |
193 | aggregate_failures 'incorrect values' do
194 | expect do
195 | Class.new do
196 | include SmartCore::Initializer
197 | param :name, privacy: 123
198 | end
199 | end.to raise_error(SmartCore::Initializer::ArgumentError) # TODO: PrivacyArgumentError
200 |
201 | expect do
202 | Class.new do
203 | include SmartCore::Initializer
204 | option :name, privacy: 123
205 | end
206 | end.to raise_error(SmartCore::Initializer::ArgumentError) # TODO: PrivacyArgumentError
207 | end
208 | end
209 | describe ':finalize' do
210 | specify 'finalize the result value of attribute via instance method' do
211 | klass = Class.new do
212 | include SmartCore::Initializer
213 |
214 | param :x, 'numeric', finalize: :double_up
215 | param :y, 'boolean', finalize: :switch
216 | option :a, 'integer', finalize: :change_amount
217 | option :b, 'string', finalize: :post_process
218 |
219 | def double_up(attr_value)
220 | attr_value * 2
221 | end
222 |
223 | def switch(attr_value)
224 | !attr_value
225 | end
226 |
227 | def change_amount(attr_value)
228 | attr_value + 1000
229 | end
230 |
231 | def post_process(attr_value)
232 | "#{attr_value}_post_processed!"
233 | end
234 | end
235 |
236 | instance = klass.new(5.6, true, a: 20, b: 'daiver')
237 | expect(instance.x).to eq(11.2)
238 | expect(instance.y).to eq(false)
239 | expect(instance.a).to eq(1020)
240 | expect(instance.b).to eq('daiver_post_processed!')
241 | end
242 |
243 | specify 'finalize the result value of attribute via proc/lambda object' do
244 | klass = Class.new do
245 | include SmartCore::Initializer
246 |
247 | param :x, 'numeric', finalize: -> (val) { val * val }
248 | param :y, 'boolean', finalize: proc { |val| !val }
249 | option :a, 'integer', finalize: -> (val) { val + 1 }
250 | option :b, 'string', finalize: proc { 'NOPE! :D' }
251 | end
252 |
253 | instance = klass.new(10, false, a: 1, b: 'pek')
254 |
255 | expect(instance.x).to eq(100)
256 | expect(instance.y).to eq(true)
257 | expect(instance.a).to eq(2)
258 | expect(instance.b).to eq('NOPE! :D')
259 | end
260 |
261 | specify 'expects proc/labmda/string/symbol' do
262 | expect do
263 | Class.new do
264 | include SmartCore::Initializer
265 | param :a, finalize: 123
266 | end
267 | end.to raise_error(SmartCore::Initializer::ArgumentError) # TODO: FinalizeArgumentError
268 |
269 | expect do
270 | Class.new do
271 | include SmartCore::Initializer
272 | option :a, finalize: 123
273 | end
274 | end.to raise_error(SmartCore::Initializer::ArgumentError) # TODO: FinalizeArgumentError
275 |
276 | expect do
277 | Class.new do
278 | include SmartCore::Initializer
279 | param :a, finalize: :test
280 | end
281 | end.not_to raise_error
282 |
283 | expect do
284 | Class.new do
285 | include SmartCore::Initializer
286 | option :a, finalize: 'test'
287 | end
288 | end.not_to raise_error
289 |
290 | expect do
291 | Class.new do
292 | include SmartCore::Initializer
293 | param :a, finalize: proc {}
294 | end
295 | end.not_to raise_error
296 |
297 | expect do
298 | Class.new do
299 | include SmartCore::Initializer
300 | option :a, finalize: -> (val) {}
301 | end
302 | end.not_to raise_error
303 | end
304 |
305 | specify 'lambda objects used for finalize should have an argument in their signature' do
306 | expect do
307 | Class.new do
308 | include SmartCore::Initializer
309 | option :a, finalize: -> {}
310 | end
311 | end.to raise_error(SmartCore::Initializer::ArgumentError) # TODO: FinalizeArgumentError
312 |
313 | expect do
314 | Class.new do
315 | include SmartCore::Initializer
316 | param :a, finalize: -> {}
317 | end
318 | end.to raise_error(SmartCore::Initializer::ArgumentError) # TODO: FinalizeArgumentError
319 |
320 | expect do
321 | Class.new do
322 | include SmartCore::Initializer
323 | option :a, finalize: -> (val) {}
324 | end
325 | end.not_to raise_error
326 |
327 | expect do
328 | Class.new do
329 | include SmartCore::Initializer
330 | param :a, finalize: -> (val) {}
331 | end
332 | end.not_to raise_error
333 |
334 | expect do
335 | Class.new do
336 | include SmartCore::Initializer
337 | option :a, finalize: -> (*val) {}
338 | end
339 | end.not_to raise_error
340 |
341 | expect do
342 | Class.new do
343 | include SmartCore::Initializer
344 | param :a, finalize: -> (*val) {}
345 | end
346 | end.not_to raise_error
347 |
348 | expect do
349 | Class.new do
350 | include SmartCore::Initializer
351 | param :a, finalize: -> (req_arg, *val) {}
352 | end
353 | end.not_to raise_error
354 |
355 | # NOTE: proc objects can omit attribute in their signature
356 | expect do
357 | Class.new do
358 | include SmartCore::Initializer
359 | option :a, finalize: proc {}
360 | end
361 | end.not_to raise_error
362 | end
363 |
364 | specify 'finalized return value is type-validated too' do
365 | expect do
366 | Class.new do
367 | include SmartCore::Initializer
368 | param :a, 'string', finalize: -> (val) { 1 } # returns incorrect value
369 | end.new('111')
370 | end.to raise_error(SmartCore::Initializer::IncorrectTypeError)
371 |
372 | expect do
373 | Class.new do
374 | include SmartCore::Initializer
375 | option :b, 'string', finalize: proc { 1 } # returns incorrect value
376 | end.new(b: '111')
377 | end.to raise_error(SmartCore::Initializer::IncorrectTypeError)
378 |
379 | expect do
380 | Class.new do
381 | include SmartCore::Initializer
382 | param :c, :string, finalize: :post_process
383 |
384 | def post_process(attr_value)
385 | 123
386 | end
387 | end.new('test')
388 | end.to raise_error(SmartCore::Initializer::IncorrectTypeError)
389 |
390 | expect do
391 | Class.new do
392 | include SmartCore::Initializer
393 | option :d, :string, finalize: :post_process
394 |
395 | def post_process(attr_value)
396 | 123
397 | end
398 | end.new(d: 'test')
399 | end.to raise_error(SmartCore::Initializer::IncorrectTypeError)
400 | end
401 | end
402 |
403 | specify 'inheritance' do
404 | class NanoBase
405 | include SmartCore::Initializer
406 |
407 | param :a
408 | option :b
409 |
410 | params :c, :d, :e
411 | options :f, :g
412 | end
413 |
414 | Concrete = Class.new(NanoBase)
415 | SubConcrete = Class.new(Concrete)
416 | sub_concrete = SubConcrete.new(1, 2, 3, 4, b: 6, f: 7, g: 8)
417 |
418 | expect(sub_concrete.a).to eq(1)
419 | expect(sub_concrete.b).to eq(6)
420 | expect(sub_concrete.c).to eq(2)
421 | expect(sub_concrete.d).to eq(3)
422 | expect(sub_concrete.e).to eq(4)
423 | expect(sub_concrete.f).to eq(7)
424 | expect(sub_concrete.g).to eq(8)
425 | end
426 |
427 | specify 'type aliases' do
428 | class Animal
429 | include SmartCore::Initializer
430 |
431 | param :name, :string
432 | option :age, :integer
433 | end
434 |
435 | expect { Animal.new(123, age: 123) }
436 | .to raise_error(SmartCore::Initializer::IncorrectTypeError)
437 |
438 | expect { Animal.new('test', age: 'test') }
439 | .to raise_error(SmartCore::Initializer::IncorrectTypeError)
440 |
441 | expect { Animal.new('test', age: 123) }.not_to raise_error
442 | end
443 |
444 | specify 'param and option overlapping' do
445 | aggregate_failures 'overlap failures' do
446 | expect do
447 | Class.new do
448 | include SmartCore::Initializer
449 |
450 | param :user_id
451 | option :user_id
452 | end
453 | end.to raise_error(SmartCore::Initializer::ParameterOverlapError)
454 |
455 | expect do
456 | Class.new do
457 | include SmartCore::Initializer
458 |
459 | option :user_id
460 | param :user_id
461 | end
462 | end.to raise_error(SmartCore::Initializer::OptionOverlapError)
463 | end
464 | end
465 |
466 | context 'instantiation' do
467 | describe 'strict_options option' do
468 | context "when it's true" do
469 | let(:klass) do
470 | Class.new do
471 | include SmartCore::Initializer(strict_options: true)
472 | option :user_id
473 | option :role_id, default: 123
474 | end
475 | end
476 |
477 | specify 'fails on unknown options' do
478 | expect do
479 | klass.new(user_id: 7, lol_kek: 123)
480 | end.to raise_error(SmartCore::Initializer::OptionArgumentError)
481 |
482 | expect do
483 | klass.new(user_id: 7, role_id: 55, lol_kek: 123)
484 | end.to raise_error(SmartCore::Initializer::OptionArgumentError)
485 |
486 | expect { klass.new(user_id: 7) }.not_to raise_error
487 |
488 | expect { klass.new(user_id: 7, role_id: 7) }.not_to raise_error
489 | end
490 | end
491 |
492 | context "when it's false" do
493 | let(:klass) do
494 | Class.new do
495 | include SmartCore::Initializer(strict_options: false)
496 | option :user_id
497 | option :role_id, default: 123
498 | end
499 | end
500 |
501 | specify 'skips unknown options' do
502 | expect { klass.new(user_id: 7, lol_kek: 123) }.not_to raise_error
503 | end
504 | end
505 | end
506 | end
507 |
508 | specify 'initializer behavior extension' do
509 | instance = Class.new { include SmartCore::Initializer }
510 | expect(instance).not_to respond_to(:kek)
511 | expect(instance).not_to respond_to(:lol)
512 |
513 | instance = Class.new do
514 | include SmartCore::Initializer
515 | ext_init { |object| object.define_singleton_method(:kek) { 'pek' } }
516 | ext_init { |object| object.define_singleton_method(:lol) { 'rofl' } }
517 | end.new
518 |
519 | expect(instance).to respond_to(:kek)
520 | expect(instance).to respond_to(:lol)
521 | expect(instance.kek).to eq('pek')
522 | expect(instance.lol).to eq('rofl')
523 | end
524 |
525 | describe 'attribute aliases (:as)' do
526 | specify ':as option makes an alias method for attribute' do
527 | klass = Class.new do
528 | include SmartCore::Initializer
529 |
530 | param :name, as: :first_name
531 | option :nick, as: :login
532 | end
533 |
534 | instance = klass.new('Rustam', nick: :daiver)
535 |
536 | expect(instance.name).to eq('Rustam')
537 | expect(instance.first_name).to eq('Rustam')
538 |
539 | expect(instance.nick).to eq(:daiver)
540 | expect(instance.login).to eq(:daiver)
541 | end
542 |
543 | specify 'takes into original attribute incapsulation (private/protected/public)' do
544 | instance = Class.new do
545 | include SmartCore::Initializer
546 |
547 | param :a, as: :a1, privacy: :private, mutable: true
548 | param :b, as: :b1, privacy: :protected, mutable: true
549 | param :c, as: :c1, privacy: :public, mutable: true
550 |
551 | option :x, as: :x1, privacy: :private, mutable: true
552 | option :y, as: :y1, privacy: :protected, mutable: true
553 | option :z, as: :z1, privacy: :public, mutable: true
554 | end.new(1, 2, 3, x: 4, y: 5, z: 6)
555 |
556 | # rubocop:disable Layout/LineLength
557 | expect(instance.private_methods(false)).to contain_exactly(:a, :a=, :a1, :a1=, :x, :x=, :x1, :x1=)
558 | expect(instance.protected_methods(false)).to contain_exactly(:b, :b=, :b1, :b1=, :y, :y=, :y1, :y1=)
559 | expect(instance.public_methods(false)).to contain_exactly(:c, :c=, :c1, :c1=, :z, :z=, :z1, :z1=)
560 | # rubocop:enable Layout/LineLength
561 | end
562 | specify 'aliased mutable attributes gives type-validated mutator too' do
563 | klass = Class.new do
564 | include SmartCore::Initializer
565 |
566 | param :role, :symbol, as: :access_level, mutable: true
567 | option :nick, :string, as: :login, mutable: true
568 | end
569 |
570 | instance = klass.new(:admin, nick: 'D@iVeR')
571 |
572 | aggregate_failures 'instance repsonds to aliased mutators' do
573 | expect(instance).to respond_to(:access_level=)
574 | expect(instance).to respond_to(:login=)
575 | end
576 |
577 | aggregate_failures 'aliased mutator mutates attributes correctly' do
578 | expect(instance.access_level).to eq(:admin)
579 | expect(instance.role).to eq(:admin)
580 | expect(instance.login).to eq('D@iVeR')
581 | expect(instance.nick).to eq('D@iVeR')
582 |
583 | instance.access_level = :user
584 | instance.login = 'Rustam'
585 |
586 | expect(instance.access_level).to eq(:user)
587 | expect(instance.role).to eq(:user)
588 | expect(instance.login).to eq('Rustam')
589 | expect(instance.nick).to eq('Rustam')
590 | end
591 |
592 | aggregate_failures "incopmatible types raises error and does not mutate object's state" do
593 | expect do
594 | instance.access_level = 123
595 | end.to raise_error(SmartCore::Initializer::IncorrectTypeError)
596 | expect do
597 | instance.login = Object.new
598 | end.to raise_error(SmartCore::Initializer::IncorrectTypeError)
599 |
600 | # aliases
601 | expect(instance.access_level).to eq(:user)
602 | expect(instance.login).to eq('Rustam')
603 |
604 | # originals
605 | expect(instance.role).to eq(:user)
606 | expect(instance.nick).to eq('Rustam')
607 | end
608 | end
609 |
610 | specify 'expects strings and symbols' do
611 | expect do
612 | Class.new do
613 | include SmartCore::Initializer
614 | option :a, as: 123
615 | end
616 | end.to raise_error(SmartCore::Initializer::ArgumentError) # TODO: use AliasArgumentError
617 |
618 | expect do
619 | Class.new do
620 | include SmartCore::Initializer
621 | option :a, as: Object.new
622 | end
623 | end.to raise_error(SmartCore::Initializer::ArgumentError) # TODO: use AliasArgumentError
624 |
625 | expect do
626 | Class.new do
627 | include SmartCore::Initializer
628 | option :a, as: :test
629 | end
630 | end.not_to raise_error
631 |
632 | expect do
633 | Class.new do
634 | include SmartCore::Initializer
635 | option :a, as: 'test'
636 | end
637 | end.not_to raise_error
638 | end
639 | end
640 |
641 | describe 'optional `option` attributes' do
642 | specify 'developer can omit passing optional `option` attributes' do
643 | klass = Class.new do
644 | include SmartCore::Initializer
645 |
646 | param :name
647 |
648 | option :age, 'integer'
649 | option :role, 'symbol', optional: true
650 | option :nick, 'string', optional: true
651 | option :last_login_at, 'time'
652 | end
653 |
654 | # you can omit :role and :nick options
655 | instance = klass.new('Rustam', age: 30, last_login_at: Time.parse('2020-01-01'))
656 |
657 | expect(instance.name).to eq('Rustam')
658 | expect(instance.age).to eq(30)
659 | expect(instance.last_login_at).to eq(Time.parse('2020-01-01'))
660 |
661 | expect(instance.role).to eq(nil)
662 | expect(instance.nick).to eq(nil)
663 | end
664 |
665 | specify 'omitted attributes are initialized by `nil` by default' do
666 | instance = Class.new do
667 | include SmartCore::Initializer
668 |
669 | params :x, :y
670 |
671 | option :a, 'integer'
672 | option :b, 'symbol', optional: true
673 | option :c, 'string', optional: true
674 | option :d, 'time'
675 | option :f, optional: true
676 | end.new(1, 2, a: 3, b: :kek, d: Time.parse('2021-01-01'))
677 |
678 | expect(instance.x).to eq(1)
679 | expect(instance.y).to eq(2)
680 | expect(instance.b).to eq(:kek)
681 | expect(instance.c).to eq(nil)
682 | expect(instance.d).to eq(Time.parse('2021-01-01'))
683 | expect(instance.f).to eq(nil)
684 | end
685 |
686 | specify 'omitted :optional attributes uses the declared `:default` value' do
687 | instance = Class.new do
688 | include SmartCore::Initializer
689 |
690 | params :x, :y
691 |
692 | option :a, 'numeric'
693 | option :b, 'symbol', default: -> { :test_b }, optional: true
694 | option :c, 'string', default: -> { 'test_c' }, optional: true
695 | option :d, 'string', default: 'Test_D', optional: true
696 | option :e, 'time', default: -> { Time.parse('2022-01-01') }
697 | option :f, 'time', optional: true
698 | end.new(555, 666, a: 888)
699 |
700 | expect(instance.x).to eq(555)
701 | expect(instance.y).to eq(666)
702 | expect(instance.a).to eq(888)
703 |
704 | expect(instance.b).to eq(:test_b)
705 | expect(instance.c).to eq('test_c')
706 | expect(instance.d).to eq('Test_D')
707 | expect(instance.e).to eq(Time.parse('2022-01-01'))
708 | expect(instance.f).to eq(nil)
709 | end
710 |
711 | specify 'optional attributes without :default should not be finalized' do
712 | aggregate_failures 'without :default => without finalization' do
713 | instance = Class.new do
714 | include SmartCore::Initializer
715 | option :a, :numeric, optional: true, finalize: -> (val) { 123 }
716 | end.new
717 | expect(instance.a).to eq(nil)
718 | end
719 |
720 | aggregate_failures 'with :default => with finalization' do
721 | instance = Class.new do
722 | include SmartCore::Initializer
723 | option :a, :numeric, default: 3, optional: true, finalize: -> (val) { val * 2 }
724 | end.new
725 | expect(instance.a).to eq(6)
726 | end
727 | end
728 |
729 | specify 'expects boolean value' do
730 | expect do
731 | Class.new do
732 | include SmartCore::Initializer
733 | option :a, optional: 123
734 | end
735 | end.to raise_error(SmartCore::Initializer::ArgumentError) # TODO: use OptionalArgumentError
736 |
737 | expect do
738 | Class.new do
739 | include SmartCore::Initializer
740 | option :a, optional: Object.new
741 | end
742 | end.to raise_error(SmartCore::Initializer::ArgumentError) # TODO: use OptionalArgumentError
743 |
744 | expect do
745 | Class.new do
746 | include SmartCore::Initializer
747 | option :a, optional: true
748 | end
749 | end.not_to raise_error
750 |
751 | expect do
752 | Class.new do
753 | include SmartCore::Initializer
754 | option :a, optional: false
755 | end
756 | end.not_to raise_error
757 | end
758 | end
759 |
760 | describe ':default value' do
761 | specify 'dynamic value initializetion (usage of a proc/lambda)' do
762 | klass = Class.new do
763 | include SmartCore::Initializer
764 |
765 | option :a, default: proc { '123' }
766 | option :b, default: -> { Time.parse('2011-01-01') }
767 | end
768 |
769 | aggregate_failures 'pass only one attribute (first option)' do
770 | instance = klass.new(a: 10)
771 | expect(instance.a).to eq(10)
772 | expect(instance.b).to eq(Time.parse('2011-01-01'))
773 | end
774 |
775 | aggregate_failures 'pass only one attribute (second option)' do
776 | instance = klass.new(b: Time.parse('2088-01-01'))
777 | expect(instance.a).to eq('123')
778 | expect(instance.b).to eq(Time.parse('2088-01-01'))
779 | end
780 |
781 | aggregate_failures 'use defaults' do
782 | instance = klass.new
783 | expect(instance.a).to eq('123')
784 | expect(instance.b).to eq(Time.parse('2011-01-01'))
785 | end
786 | end
787 |
788 | specify 'duplicatable values (non-dynamic values should be duplicated during initialization' do
789 | some_static_value = 'test_123'
790 |
791 | klass = Class.new do
792 | include SmartCore::Initializer
793 | option :nickname, default: 'D@iVeR' # non-dynamic value (should be duplicated)
794 | option :age, default: -> { some_static_value } # dynamic value (should be returned "as-is")
795 | end
796 |
797 | first_instance = klass.new
798 | second_instance = klass.new
799 |
800 | # non-dynamic attributes are different
801 | expect(first_instance.nickname.object_id).not_to eq(second_instance.nickname.object_id)
802 |
803 | # dynamic attribute is static in our case - should have no difference
804 | expect(first_instance.age.object_id).to eq(second_instance.age.object_id)
805 | end
806 |
807 | specify 'default value should be type validated too' do
808 | klass = Class.new do
809 | include SmartCore::Initializer
810 | option :nickname, 'string', default: -> { 123 } # default value has invalid type
811 | end
812 |
813 | expect { klass.new }.to raise_error(SmartCore::Initializer::IncorrectTypeError)
814 | end
815 | end
816 |
817 | describe 'attribute type casting' do
818 | specify 'expects boolean' do
819 | expect do
820 | Class.new do
821 | include SmartCore::Initializer
822 | param :a, :integer, cast: 123
823 | end
824 | end.to raise_error(SmartCore::Initializer::ArgumentError) # TODO: CastArgumentError
825 |
826 | expect do
827 | Class.new do
828 | include SmartCore::Initializer
829 | option :a, :integer, cast: 'kek'
830 | end
831 | end.to raise_error(SmartCore::Initializer::ArgumentError) # TODO: CastArgumentError
832 |
833 | expect do
834 | Class.new do
835 | include SmartCore::Initializer
836 | param :a, :integer, cast: true
837 | end
838 | end.not_to raise_error
839 |
840 | expect do
841 | Class.new do
842 | include SmartCore::Initializer
843 | param :a, :integer, cast: false
844 | end
845 | end.not_to raise_error
846 |
847 | expect do
848 | Class.new do
849 | include SmartCore::Initializer
850 | option :a, :integer, cast: true
851 | end
852 | end.not_to raise_error
853 |
854 | expect do
855 | Class.new do
856 | include SmartCore::Initializer
857 | option :a, :integer, cast: false
858 | end
859 | end.not_to raise_error
860 | end
861 |
862 | specify 'attribte can be marked as type-castable (non-marked by default)' do
863 | klass = Class.new do
864 | include SmartCore::Initializer(auto_cast: true)
865 |
866 | param :a, 'integer', cast: true
867 | param :b, 'boolean', cast: true
868 | param :c, 'string'
869 |
870 | option :x, 'string', cast: true
871 | option :y, 'integer', cast: true
872 | option :z, 'array'
873 | end
874 |
875 | instance = klass.new('123', 456, 'test', x: :phone, y: '123.456', z: [])
876 |
877 | expect(instance.a).to eq(123)
878 | expect(instance.b).to eq(true)
879 | expect(instance.c).to eq('test')
880 | expect(instance.x).to eq('phone')
881 | expect(instance.y).to eq(123)
882 | expect(instance.z).to eq([])
883 | end
884 |
885 | specify 'automatic type casting' do
886 | klass = Class.new do
887 | include SmartCore::Initializer(auto_cast: true) # set the automatic type casting
888 |
889 | param :a, 'integer'
890 | option :b, 'string'
891 | param :c, 'boolean', cast: false # do not cast automaticaly
892 | option :d, 'string', cast: false # do not cast automaticaly
893 | end
894 |
895 | instance = klass.new('123', true, b: 456.789, d: '12000')
896 | expect(instance.a).to eq(123) # auto-casted from string to integer
897 | expect(instance.b).to eq('456.789') # auto-casted from float to string
898 | expect(instance.c).to eq(true)
899 | expect(instance.d).to eq('12000')
900 | end
901 | end
902 |
903 | describe 'mutable attributes' do
904 | specify 'attribute definition' do
905 | klass = Class.new do
906 | include SmartCore::Initializer
907 |
908 | param :a, 'string', mutable: true, cast: true
909 | param :b, 'integer', mutable: false
910 | param :c, 'array' # mutable: false by default
911 |
912 | option :d, :string, mutable: true
913 | option :e, :integer, mutable: false
914 | option :f, :array # mutable: false by default
915 |
916 | params :z1, :z2, mutable: true
917 | params :z3, :z4, mutable: false
918 | params :z5, :z6 # mutable: false by default
919 |
920 | options :o1, :o2, mutable: true
921 | options :o3, :o4, mutable: false
922 | options :o5, :o6 # mutable: false by default
923 | end
924 |
925 | instance = klass.new(
926 | 1, 2, [],
927 | 'z1', 'z2', 'z3', 'z4', 'z5', 'z6',
928 | d: '3', e: 4, f: [],
929 | o1: 'o1', o2: 'o2', o3: 'o3', o4: 'o4', o5: 'o5', o6: 'o6'
930 | )
931 |
932 | # check param behavior
933 | aggregate_failures 'param mutator behavior' do
934 | expect { instance.a = '2' }.not_to raise_error
935 | # test that value was changed
936 | expect(instance.a).to eq('2')
937 | # test the type checking of mutable methods
938 | expect { instance.a = 2 }.to raise_error(SmartCore::Initializer::IncorrectTypeError)
939 | # NOTE: old version of error => SmartCore::Types::TypeError
940 |
941 | # non-mutable option
942 | expect { instance.b = 3 }.to raise_error(::NoMethodError)
943 | # non-mutable by default
944 | expect { instance.c = [123] }.to raise_error(::NoMethodError)
945 |
946 | # after all manipulations the state is correct
947 | expect(instance.a).to eq('2')
948 | expect(instance.b).to eq(2)
949 | expect(instance.c).to eq([])
950 | end
951 |
952 | # check option behavior
953 | aggregate_failures 'otpion mutator behavior' do
954 | expect { instance.d = '2' }.not_to raise_error
955 | # test that value was changed
956 | expect(instance.d).to eq('2')
957 | # test the type checking of mutable methods
958 | expect { instance.d = 2 }.to raise_error(SmartCore::Initializer::IncorrectTypeError)
959 | # NOTE: old version of error => SmartCore::Types::TypeError
960 |
961 | # non-mutable option
962 | expect { instance.e = 3 }.to raise_error(::NoMethodError)
963 | # non-mutable by default
964 | expect { instance.f = [123] }.to raise_error(::NoMethodError)
965 |
966 | # after all manipulations the state is correct
967 | expect(instance.d).to eq('2')
968 | expect(instance.e).to eq(4)
969 | expect(instance.f).to eq([])
970 | end
971 |
972 | # check optionS behavior
973 | aggregate_failures 'optionS mutator behavior' do
974 | # mutable options
975 | expect { instance.o1 = 'kek_o1' }.not_to raise_error
976 | expect { instance.o2 = 'kek_o2' }.not_to raise_error
977 | expect(instance.o1).to eq('kek_o1')
978 | expect(instance.o2).to eq('kek_o2')
979 |
980 | # non-mutable options
981 | expect { instance.o3 = 'kek_o1' }.to raise_error(::NoMethodError)
982 | expect { instance.o4 = 'kek_o2' }.to raise_error(::NoMethodError)
983 | expect(instance.o3).to eq('o3')
984 | expect(instance.o4).to eq('o4')
985 |
986 | # non-mutable by default
987 | expect { instance.o5 = 'kek_05' }.to raise_error(::NoMethodError)
988 | expect { instance.o6 = 'kek_06' }.to raise_error(::NoMethodError)
989 | expect(instance.o5).to eq('o5')
990 | expect(instance.o6).to eq('o6')
991 | end
992 |
993 | # check paramS behavior
994 | aggregate_failures 'paramS mutator behavior' do
995 | # mutable options
996 | expect { instance.z1 = 'lel_z1' }.not_to raise_error
997 | expect { instance.z2 = 'lel_z2' }.not_to raise_error
998 | expect(instance.z1).to eq('lel_z1')
999 | expect(instance.z2).to eq('lel_z2')
1000 |
1001 | # non-mutable options
1002 | expect { instance.z3 = 'lel_z3' }.to raise_error(::NoMethodError)
1003 | expect { instance.z4 = 'lel_z4' }.to raise_error(::NoMethodError)
1004 | expect(instance.z3).to eq('z3')
1005 | expect(instance.z4).to eq('z4')
1006 |
1007 | # non-mutable by default
1008 | expect { instance.z5 = 'lel_z5' }.to raise_error(::NoMethodError)
1009 | expect { instance.z6 = 'lel_z6' }.to raise_error(::NoMethodError)
1010 | expect(instance.z5).to eq('z5')
1011 | expect(instance.z6).to eq('z6')
1012 | end
1013 | end
1014 | specify 'expects boolean value' do
1015 | expect do
1016 | Class.new do
1017 | include SmartCore::Initializer
1018 | option :a, mutable: 123
1019 | end
1020 | end.to raise_error(SmartCore::Initializer::ArgumentError) # TODO: use MutableArgumentError
1021 |
1022 | expect do
1023 | Class.new do
1024 | include SmartCore::Initializer
1025 | option :a, mutable: Object.new
1026 | end
1027 | end.to raise_error(SmartCore::Initializer::ArgumentError) # TODO: use MutableArgumentError
1028 |
1029 | expect do
1030 | Class.new do
1031 | include SmartCore::Initializer
1032 | option :a, mutable: true
1033 | end
1034 | end.not_to raise_error
1035 |
1036 | expect do
1037 | Class.new do
1038 | include SmartCore::Initializer
1039 | option :a, mutable: false
1040 | end
1041 | end.not_to raise_error
1042 | end
1043 | end
1044 | end
1045 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'simplecov'
4 |
5 | SimpleCov.formatter = SimpleCov::Formatter::HTMLFormatter
6 | SimpleCov.minimum_coverage(100) if !!ENV['FULL_TEST_COVERAGE_CHECK']
7 | SimpleCov.enable_coverage(:branch)
8 | SimpleCov.enable_coverage(:line)
9 | SimpleCov.primary_coverage(:line)
10 | SimpleCov.add_filter('spec')
11 | SimpleCov.start
12 |
13 | require 'bundler/setup'
14 | require 'smart_core/initializer'
15 | require 'pry'
16 |
17 | require_relative 'support/spec_support'
18 | require_relative 'support/meta_scopes'
19 |
20 | RSpec.configure do |config|
21 | Kernel.srand config.seed
22 | config.disable_monkey_patching!
23 | config.filter_run_when_matching :focus
24 | config.order = :random
25 | config.shared_context_metadata_behavior = :apply_to_host_groups
26 | config.expect_with(:rspec) { |c| c.syntax = :expect }
27 | Thread.abort_on_exception = true
28 | end
29 |
--------------------------------------------------------------------------------
/spec/support/meta_scopes.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | RSpec.configure do |config|
4 | config.around(:example, :plugin) do |example|
5 | if SpecSupport.test_plugins?
6 | case example.metadata[:plugin]
7 | when :thy_types
8 | require 'thy'
9 | SmartCore::Initializer::Configuration.plugin(:thy_types)
10 | end
11 | example.call
12 | end
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/spec/support/spec_support.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module SpecSupport
4 | class << self
5 | def test_plugins?
6 | !!ENV['TEST_PLUGINS']
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------