smart_initializer 0.11.1 → 0.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +18 -0
- data/Gemfile.lock +8 -2
- data/LICENSE.txt +1 -1
- data/README.md +2 -0
- data/lib/smart_core/initializer/attribute/factory/base.rb +1 -1
- data/lib/smart_core/initializer/attribute/factory/option.rb +7 -3
- data/lib/smart_core/initializer/attribute/factory/param.rb +6 -4
- data/lib/smart_core/initializer/attribute/finalizer/abstract.rb +10 -0
- data/lib/smart_core/initializer/attribute/finalizer/anonymous_block.rb +10 -0
- data/lib/smart_core/initializer/attribute/list.rb +43 -9
- data/lib/smart_core/initializer/attribute/value/base.rb +24 -6
- data/lib/smart_core/initializer/attribute/value/option.rb +4 -1
- data/lib/smart_core/initializer/attribute/value/param.rb +1 -0
- data/lib/smart_core/initializer/constructor/definer.rb +2 -2
- data/lib/smart_core/initializer/constructor.rb +23 -12
- data/lib/smart_core/initializer/extensions/list.rb +17 -9
- data/lib/smart_core/initializer/version.rb +1 -1
- data/smart_initializer.gemspec +2 -0
- metadata +31 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: abfe1c6f3d173fe444b3ad0ba7c653bd855a76b457133c885382c9f03cf7a769
|
|
4
|
+
data.tar.gz: '0838552294d47d4af197321e88d35fc4da2af3d387748b94c3afd1a37b417951'
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 47ad9d675876569f67c2c44c4b02c2ebe96a638539a288006e2263b377e0ad4c7144e722ff914368c73c0473652a00ab9e4cf8896a31f9a0cf92257606170d60
|
|
7
|
+
data.tar.gz: 2dffffb20be16d5f868c2489c36e2e056459d31783417e53e2c57efa836b4b1890dc685db33fb307145c22cb3fc6237f7e8afe30b5356280fedc4a8f1cf51f27
|
data/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,24 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
All notable changes to this project will be documented in this file.
|
|
3
3
|
|
|
4
|
+
## [0.12.1] - 2026-07-03
|
|
5
|
+
### Changed
|
|
6
|
+
- Performance: reduced per-instantiation overhead in `SmartCore::Initializer::Constructor`:
|
|
7
|
+
- `Attribute::List` and `Extensions::List` now serve reads (`#each` and every `Enumerable`
|
|
8
|
+
method derived from it, `#size`) from an immutable frozen snapshot **lock-free**; the shared
|
|
9
|
+
`SmartCore::Engine::ReadWriteLock` is kept only for mutation (`#add`/`#concat`) and no longer
|
|
10
|
+
serializes concurrent instantiations on a threaded server;
|
|
11
|
+
- derived option-name lists are memoized (`Attribute::List#names`, `#required_option_names`)
|
|
12
|
+
instead of being recomputed and re-allocated on every instantiation inside
|
|
13
|
+
`#prevent_attribute_insufficiency`;
|
|
14
|
+
- redundant type validations per attribute are avoided in `#initialize_options`/`#initialize_parameters`:
|
|
15
|
+
the cheap `cast?` flag is checked before `type.valid?`, and the post-finalizer re-validation is
|
|
16
|
+
skipped only for the built-in identity default finalizer (which returns the already-validated value
|
|
17
|
+
untouched); custom finalizers are always re-validated, so a finalizer that mutates its argument in
|
|
18
|
+
place still raises `IncorrectTypeError` on an invalid result;
|
|
19
|
+
- ~40% faster instantiation in a single-threaded benchmark (larger under concurrency).
|
|
20
|
+
No public API or validation/casting/finalization-semantics change.
|
|
21
|
+
|
|
4
22
|
## [0.11.0] - 2022-11-25
|
|
5
23
|
### Changed
|
|
6
24
|
- Support for *Ruby@2.5* and *Ruby@2.6* has ended;
|
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
smart_initializer (0.
|
|
4
|
+
smart_initializer (0.12.1)
|
|
5
5
|
qonfig (~> 0.24)
|
|
6
6
|
smart_engine (~> 0.16)
|
|
7
7
|
smart_types (~> 0.8)
|
|
@@ -21,6 +21,7 @@ GEM
|
|
|
21
21
|
rubocop-rake (= 0.6.0)
|
|
22
22
|
rubocop-rspec (= 2.13.2)
|
|
23
23
|
ast (2.4.2)
|
|
24
|
+
bigdecimal (3.1.8)
|
|
24
25
|
coderay (1.1.3)
|
|
25
26
|
concurrent-ruby (1.1.10)
|
|
26
27
|
diff-lcs (1.5.0)
|
|
@@ -30,6 +31,7 @@ GEM
|
|
|
30
31
|
json (2.6.2)
|
|
31
32
|
method_source (1.0.0)
|
|
32
33
|
minitest (5.16.3)
|
|
34
|
+
ostruct (0.6.0)
|
|
33
35
|
parallel (1.22.1)
|
|
34
36
|
parser (3.1.2.1)
|
|
35
37
|
ast (~> 2.4.1)
|
|
@@ -94,10 +96,14 @@ GEM
|
|
|
94
96
|
|
|
95
97
|
PLATFORMS
|
|
96
98
|
arm64-darwin-21
|
|
99
|
+
arm64-darwin-23
|
|
100
|
+
x86_64-linux
|
|
97
101
|
|
|
98
102
|
DEPENDENCIES
|
|
99
103
|
armitage-rubocop (~> 1.30)
|
|
104
|
+
bigdecimal
|
|
100
105
|
bundler (~> 2.3)
|
|
106
|
+
ostruct
|
|
101
107
|
pry (~> 0.14)
|
|
102
108
|
rake (~> 13.0)
|
|
103
109
|
rspec (~> 3.11)
|
|
@@ -105,4 +111,4 @@ DEPENDENCIES
|
|
|
105
111
|
smart_initializer!
|
|
106
112
|
|
|
107
113
|
BUNDLED WITH
|
|
108
|
-
2.
|
|
114
|
+
2.5.19
|
data/LICENSE.txt
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
The MIT License (MIT)
|
|
2
2
|
|
|
3
|
-
Copyright (c) 2020-
|
|
3
|
+
Copyright (c) 2020-2024 Rustam Ibragimov
|
|
4
4
|
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
data/README.md
CHANGED
|
@@ -527,6 +527,7 @@ User.new(123, 'test', { admin: true, age: 22 })
|
|
|
527
527
|
|
|
528
528
|
## Roadmap
|
|
529
529
|
|
|
530
|
+
- an ability to re-define existing options and parameters in children classes;
|
|
530
531
|
- More semantic attribute declaration errors (more domain-related attribute error objects);
|
|
531
532
|
- incorrect `:finalize` argument type: `ArgumentError` => `FinalizeArgumentError`;
|
|
532
533
|
- incorrect `:as` argument type: `ArguemntError` => `AsArgumentError`;
|
|
@@ -535,6 +536,7 @@ User.new(123, 'test', { admin: true, age: 22 })
|
|
|
535
536
|
- Specs restructuring;
|
|
536
537
|
- Migrate from `TravisCI` to `GitHub Actions`;
|
|
537
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;
|
|
538
540
|
|
|
539
541
|
---
|
|
540
542
|
|
|
@@ -132,7 +132,7 @@ class SmartCore::Initializer::Attribute::Factory::Base
|
|
|
132
132
|
#
|
|
133
133
|
# @api private
|
|
134
134
|
# @since 0.4.0
|
|
135
|
-
def
|
|
135
|
+
def prepare_as_param(as)
|
|
136
136
|
unless as.is_a?(::NilClass) || as.is_a?(::String) || as.is_a?(::Symbol)
|
|
137
137
|
raise(SmartCore::Initializer::ArgumentError, <<~ERROR_MESSAGE)
|
|
138
138
|
Attribute alias should be a type of String or Symbol
|
|
@@ -5,6 +5,7 @@ module SmartCore::Initializer::Attribute::Factory
|
|
|
5
5
|
# @since 0.8.0
|
|
6
6
|
class Option < Base
|
|
7
7
|
class << self
|
|
8
|
+
# @param klass [Class]
|
|
8
9
|
# @param name [String, Symbol]
|
|
9
10
|
# @param type [String, Symbol, Any]
|
|
10
11
|
# @param type_system [String, Symbol]
|
|
@@ -19,7 +20,7 @@ module SmartCore::Initializer::Attribute::Factory
|
|
|
19
20
|
#
|
|
20
21
|
# @api private
|
|
21
22
|
# @since 0.8.0
|
|
22
|
-
def create(name, type, type_system, privacy, finalize, cast, mutable, as, default, optional)
|
|
23
|
+
def create(klass, name, type, type_system, privacy, finalize, cast, mutable, as, default, optional)
|
|
23
24
|
prepared_name = prepare_name_param(name)
|
|
24
25
|
prepared_privacy = prepare_privacy_param(privacy)
|
|
25
26
|
prepared_finalize = prepare_finalize_param(finalize)
|
|
@@ -27,11 +28,12 @@ module SmartCore::Initializer::Attribute::Factory
|
|
|
27
28
|
prepared_type_system = prepare_type_system_param(type_system)
|
|
28
29
|
prepared_type = prepare_type_param(type, prepared_type_system)
|
|
29
30
|
prepared_mutable = prepare_mutable_param(mutable)
|
|
30
|
-
prepared_as =
|
|
31
|
+
prepared_as = prepare_as_param(as)
|
|
31
32
|
prepared_default = prepare_default_param(default)
|
|
32
33
|
prepared_optional = prepare_optional_param(optional)
|
|
33
34
|
|
|
34
35
|
create_attribute(
|
|
36
|
+
klass,
|
|
35
37
|
prepared_name,
|
|
36
38
|
prepared_type,
|
|
37
39
|
prepared_type_system,
|
|
@@ -47,6 +49,7 @@ module SmartCore::Initializer::Attribute::Factory
|
|
|
47
49
|
|
|
48
50
|
private
|
|
49
51
|
|
|
52
|
+
# @param klass [Class]
|
|
50
53
|
# @param name [String]
|
|
51
54
|
# @param type [SmartCore::Initializer::TypeSystem::Interop]
|
|
52
55
|
# @param type_system [Class<SmartCore::Initializer::TypeSystem::Interop>]
|
|
@@ -62,6 +65,7 @@ module SmartCore::Initializer::Attribute::Factory
|
|
|
62
65
|
# @api private
|
|
63
66
|
# @since 0.8.0
|
|
64
67
|
def create_attribute(
|
|
68
|
+
klass,
|
|
65
69
|
name,
|
|
66
70
|
type,
|
|
67
71
|
type_system,
|
|
@@ -74,7 +78,7 @@ module SmartCore::Initializer::Attribute::Factory
|
|
|
74
78
|
optional
|
|
75
79
|
)
|
|
76
80
|
SmartCore::Initializer::Attribute::Value::Option.new(
|
|
77
|
-
name, type, type_system, privacy, finalize, cast, mutable, as, default, optional
|
|
81
|
+
klass, name, type, type_system, privacy, finalize, cast, mutable, as, default, optional
|
|
78
82
|
)
|
|
79
83
|
end
|
|
80
84
|
|
|
@@ -5,6 +5,7 @@ module SmartCore::Initializer::Attribute::Factory
|
|
|
5
5
|
# @since 0.8.0
|
|
6
6
|
class Param < Base
|
|
7
7
|
class << self
|
|
8
|
+
# @param klass [Class]
|
|
8
9
|
# @param name [String, Symbol]
|
|
9
10
|
# @param type [String, Symbol, Any]
|
|
10
11
|
# @param type_system [String, Symbol]
|
|
@@ -17,7 +18,7 @@ module SmartCore::Initializer::Attribute::Factory
|
|
|
17
18
|
#
|
|
18
19
|
# @api private
|
|
19
20
|
# @since 0.8.0
|
|
20
|
-
def create(name, type, type_system, privacy, finalize, cast, mutable, as)
|
|
21
|
+
def create(klass, name, type, type_system, privacy, finalize, cast, mutable, as)
|
|
21
22
|
prepared_name = prepare_name_param(name)
|
|
22
23
|
prepared_privacy = prepare_privacy_param(privacy)
|
|
23
24
|
prepared_finalize = prepare_finalize_param(finalize)
|
|
@@ -25,9 +26,10 @@ module SmartCore::Initializer::Attribute::Factory
|
|
|
25
26
|
prepared_type_system = prepare_type_system_param(type_system)
|
|
26
27
|
prepared_type = prepare_type_param(type, prepared_type_system)
|
|
27
28
|
prepared_mutable = prepare_mutable_param(mutable)
|
|
28
|
-
prepared_as =
|
|
29
|
+
prepared_as = prepare_as_param(as)
|
|
29
30
|
|
|
30
31
|
create_attribute(
|
|
32
|
+
klass,
|
|
31
33
|
prepared_name,
|
|
32
34
|
prepared_type,
|
|
33
35
|
prepared_type_system,
|
|
@@ -53,9 +55,9 @@ module SmartCore::Initializer::Attribute::Factory
|
|
|
53
55
|
#
|
|
54
56
|
# @api private
|
|
55
57
|
# @since 0.8.0
|
|
56
|
-
def create_attribute(name, type, type_system, privacy, finalize, cast, mutable, as)
|
|
58
|
+
def create_attribute(klass, name, type, type_system, privacy, finalize, cast, mutable, as)
|
|
57
59
|
SmartCore::Initializer::Attribute::Value::Param.new(
|
|
58
|
-
name, type, type_system, privacy, finalize, cast, mutable, as
|
|
60
|
+
klass, name, type, type_system, privacy, finalize, cast, mutable, as
|
|
59
61
|
)
|
|
60
62
|
end
|
|
61
63
|
end
|
|
@@ -24,6 +24,16 @@ class SmartCore::Initializer::Attribute::Finalizer::Abstract
|
|
|
24
24
|
# :nocov:
|
|
25
25
|
end
|
|
26
26
|
|
|
27
|
+
# @return [Boolean] whether this is the built-in identity finalizer
|
|
28
|
+
# (returns its argument untouched). Custom finalizers may transform or
|
|
29
|
+
# mutate the value, so callers must re-validate after invoking them.
|
|
30
|
+
#
|
|
31
|
+
# @api private
|
|
32
|
+
# @since 0.12.1
|
|
33
|
+
def default_identity?
|
|
34
|
+
false
|
|
35
|
+
end
|
|
36
|
+
|
|
27
37
|
# @return [SmartCore::Initializer::Attribute::Finalizer::Abstract]
|
|
28
38
|
def dup
|
|
29
39
|
self.class.new(finalizer)
|
|
@@ -23,6 +23,16 @@ module SmartCore::Initializer::Attribute::Finalizer
|
|
|
23
23
|
instance.instance_exec(value, &finalizer)
|
|
24
24
|
end
|
|
25
25
|
|
|
26
|
+
# @return [Boolean] true only when wrapping the shared identity default,
|
|
27
|
+
# which returns its argument untouched (so a value validated before the
|
|
28
|
+
# call is still valid after it).
|
|
29
|
+
#
|
|
30
|
+
# @api private
|
|
31
|
+
# @since 0.12.1
|
|
32
|
+
def default_identity?
|
|
33
|
+
finalizer.equal?(SmartCore::Initializer::Attribute::Value::Base::DEFAULT_FINALIZER)
|
|
34
|
+
end
|
|
35
|
+
|
|
26
36
|
private
|
|
27
37
|
|
|
28
38
|
# @return [NilClass, Any]
|
|
@@ -11,9 +11,14 @@ class SmartCore::Initializer::Attribute::List
|
|
|
11
11
|
#
|
|
12
12
|
# @api private
|
|
13
13
|
# @since 0.1.0
|
|
14
|
-
# @version 0.
|
|
14
|
+
# @version 0.12.1
|
|
15
15
|
def initialize
|
|
16
16
|
@attributes = {}
|
|
17
|
+
# NOTE: frozen snapshot of attribute values, rebuilt on each mutation so the
|
|
18
|
+
# read-heavy instantiation path (see Constructor) can iterate lock-free.
|
|
19
|
+
@snapshot = [].freeze
|
|
20
|
+
@names = [].freeze
|
|
21
|
+
@required_option_names = nil
|
|
17
22
|
@lock = SmartCore::Engine::ReadWriteLock.new
|
|
18
23
|
end
|
|
19
24
|
|
|
@@ -43,12 +48,39 @@ class SmartCore::Initializer::Attribute::List
|
|
|
43
48
|
#
|
|
44
49
|
# @api private
|
|
45
50
|
# @since 0.1.0
|
|
46
|
-
# @version 0.
|
|
51
|
+
# @version 0.12.1
|
|
47
52
|
def add(attribute)
|
|
48
|
-
@lock.write_sync
|
|
53
|
+
@lock.write_sync do
|
|
54
|
+
attributes[attribute.name] = attribute
|
|
55
|
+
@snapshot = attributes.values.freeze
|
|
56
|
+
@names = @snapshot.map(&:name).freeze
|
|
57
|
+
@required_option_names = nil
|
|
58
|
+
end
|
|
49
59
|
end
|
|
50
60
|
alias_method :<<, :add
|
|
51
61
|
|
|
62
|
+
# @return [Array<Symbol>] names of all attributes in the list
|
|
63
|
+
#
|
|
64
|
+
# @note Lock-free, memoized on mutation. Used on the instantiation path.
|
|
65
|
+
#
|
|
66
|
+
# @api private
|
|
67
|
+
# @since 0.12.1
|
|
68
|
+
def names
|
|
69
|
+
@names
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# @return [Array<Symbol>] names of options that must be provided explicitly
|
|
73
|
+
#
|
|
74
|
+
# @note Lock-free, memoized (invalidated on mutation). Only meaningful for
|
|
75
|
+
# an options list — its members respond to #has_default?/#optional?.
|
|
76
|
+
#
|
|
77
|
+
# @api private
|
|
78
|
+
# @since 0.12.1
|
|
79
|
+
def required_option_names
|
|
80
|
+
@required_option_names ||=
|
|
81
|
+
@snapshot.reject(&:has_default?).reject(&:optional?).map(&:name).freeze
|
|
82
|
+
end
|
|
83
|
+
|
|
52
84
|
# @param list [SmartCore::Initializer::Attribute::List]
|
|
53
85
|
# @return [void]
|
|
54
86
|
#
|
|
@@ -73,22 +105,24 @@ class SmartCore::Initializer::Attribute::List
|
|
|
73
105
|
# @param block [Block]
|
|
74
106
|
# @return [Enumerable]
|
|
75
107
|
#
|
|
108
|
+
# @note Lock-free: iterates the immutable snapshot maintained on mutation.
|
|
109
|
+
#
|
|
76
110
|
# @api private
|
|
77
111
|
# @since 0.1.0
|
|
78
|
-
# @version 0.
|
|
112
|
+
# @version 0.12.1
|
|
79
113
|
def each(&block)
|
|
80
|
-
@
|
|
81
|
-
block_given? ? attributes.values.each(&block) : attributes.values.each
|
|
82
|
-
end
|
|
114
|
+
block_given? ? @snapshot.each(&block) : @snapshot.each
|
|
83
115
|
end
|
|
84
116
|
|
|
85
117
|
# @return [Integer]
|
|
86
118
|
#
|
|
119
|
+
# @note Lock-free: reads the size of the immutable snapshot.
|
|
120
|
+
#
|
|
87
121
|
# @api private
|
|
88
122
|
# @since 0.1.0
|
|
89
|
-
# @version 0.
|
|
123
|
+
# @version 0.12.1
|
|
90
124
|
def size
|
|
91
|
-
@
|
|
125
|
+
@snapshot.size
|
|
92
126
|
end
|
|
93
127
|
|
|
94
128
|
# @param block [Block]
|
|
@@ -38,6 +38,12 @@ class SmartCore::Initializer::Attribute::Value::Base
|
|
|
38
38
|
# @since 0.8.0
|
|
39
39
|
DEFAULT_MUTABLE = false
|
|
40
40
|
|
|
41
|
+
# @return [Class]
|
|
42
|
+
#
|
|
43
|
+
# @api private
|
|
44
|
+
# @since 0.12.0
|
|
45
|
+
attr_reader :klass
|
|
46
|
+
|
|
41
47
|
# @return [Symbol]
|
|
42
48
|
#
|
|
43
49
|
# @api private
|
|
@@ -89,6 +95,7 @@ class SmartCore::Initializer::Attribute::Value::Base
|
|
|
89
95
|
# @since 0.8.0
|
|
90
96
|
attr_reader :as
|
|
91
97
|
|
|
98
|
+
# @param klass [Class]
|
|
92
99
|
# @param name [Symbol]
|
|
93
100
|
# @param type [SmartCore::Initializer::TypeSystem::Interop]
|
|
94
101
|
# @param type_system [Class<SmartCore::Initializer::TypeSystem::Interop>]
|
|
@@ -101,7 +108,8 @@ class SmartCore::Initializer::Attribute::Value::Base
|
|
|
101
108
|
#
|
|
102
109
|
# @api private
|
|
103
110
|
# @since 0.8.0
|
|
104
|
-
def initialize(name, type, type_system, privacy, finalizer, cast, mutable, as)
|
|
111
|
+
def initialize(klass, name, type, type_system, privacy, finalizer, cast, mutable, as)
|
|
112
|
+
@klass = klass
|
|
105
113
|
@name = name
|
|
106
114
|
@type = type
|
|
107
115
|
@type_system = type_system
|
|
@@ -121,10 +129,20 @@ class SmartCore::Initializer::Attribute::Value::Base
|
|
|
121
129
|
# @since 0.8.0
|
|
122
130
|
# @version 0.11.0
|
|
123
131
|
def validate!(value)
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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}"
|
|
129
147
|
end
|
|
130
148
|
end
|
|
@@ -23,6 +23,7 @@ module SmartCore::Initializer::Attribute::Value
|
|
|
23
23
|
attr_reader :optional
|
|
24
24
|
alias_method :optional?, :optional
|
|
25
25
|
|
|
26
|
+
# @param klass [Class]
|
|
26
27
|
# @param name [Symbol]
|
|
27
28
|
# @param type [SmartCore::Initializer::TypeSystem::Interop]
|
|
28
29
|
# @param type_system [Class<SmartCore::Initializer::TypeSystem::Interop>]
|
|
@@ -38,6 +39,7 @@ module SmartCore::Initializer::Attribute::Value
|
|
|
38
39
|
# @api private
|
|
39
40
|
# @since 0.8.0
|
|
40
41
|
def initialize(
|
|
42
|
+
klass,
|
|
41
43
|
name,
|
|
42
44
|
type,
|
|
43
45
|
type_system,
|
|
@@ -49,7 +51,7 @@ module SmartCore::Initializer::Attribute::Value
|
|
|
49
51
|
default,
|
|
50
52
|
optional
|
|
51
53
|
)
|
|
52
|
-
super(name, type, type_system, privacy, finalizer, cast, mutable, as)
|
|
54
|
+
super(klass, name, type, type_system, privacy, finalizer, cast, mutable, as)
|
|
53
55
|
@default = default
|
|
54
56
|
@optional = optional
|
|
55
57
|
end
|
|
@@ -85,6 +87,7 @@ module SmartCore::Initializer::Attribute::Value
|
|
|
85
87
|
default = @default.equal?(UNDEFINED_DEFAULT) ? @default : @default.dup
|
|
86
88
|
|
|
87
89
|
self.class.new(
|
|
90
|
+
klass,
|
|
88
91
|
name.dup,
|
|
89
92
|
type,
|
|
90
93
|
type_system,
|
|
@@ -204,7 +204,7 @@ class SmartCore::Initializer::Constructor::Definer
|
|
|
204
204
|
as
|
|
205
205
|
)
|
|
206
206
|
SmartCore::Initializer::Attribute::Factory::Param.create(
|
|
207
|
-
name, type, type_system, privacy, finalize, cast, mutable, as
|
|
207
|
+
klass, name, type, type_system, privacy, finalize, cast, mutable, as
|
|
208
208
|
)
|
|
209
209
|
end
|
|
210
210
|
|
|
@@ -236,7 +236,7 @@ class SmartCore::Initializer::Constructor::Definer
|
|
|
236
236
|
optional
|
|
237
237
|
)
|
|
238
238
|
SmartCore::Initializer::Attribute::Factory::Option.create(
|
|
239
|
-
name, type, type_system, privacy, finalize, cast, mutable, as, default, optional
|
|
239
|
+
klass, name, type, type_system, privacy, finalize, cast, mutable, as, default, optional
|
|
240
240
|
)
|
|
241
241
|
end
|
|
242
242
|
|
|
@@ -72,9 +72,10 @@ class SmartCore::Initializer::Constructor
|
|
|
72
72
|
#
|
|
73
73
|
# @api private
|
|
74
74
|
# @since 0.1.0
|
|
75
|
-
# @version 0.
|
|
75
|
+
# @version 0.12.1
|
|
76
76
|
# rubocop:disable Metrics/AbcSize
|
|
77
77
|
def prevent_attribute_insufficiency
|
|
78
|
+
options_list = klass.__options__
|
|
78
79
|
required_parameter_count = klass.__params__.size
|
|
79
80
|
|
|
80
81
|
raise(
|
|
@@ -83,16 +84,14 @@ class SmartCore::Initializer::Constructor
|
|
|
83
84
|
"(given #{parameters.size}, expected #{required_parameter_count})"
|
|
84
85
|
) unless parameters.size == required_parameter_count
|
|
85
86
|
|
|
86
|
-
|
|
87
|
-
missing_options = required_options.reject { |option_name| options.key?(option_name) }
|
|
87
|
+
missing_options = options_list.required_option_names.reject { |name| options.key?(name) }
|
|
88
88
|
|
|
89
89
|
raise(
|
|
90
90
|
SmartCore::Initializer::OptionArgumentError,
|
|
91
91
|
"Missing options: #{missing_options.join(', ')}"
|
|
92
92
|
) if missing_options.any?
|
|
93
93
|
|
|
94
|
-
|
|
95
|
-
unknown_options = options.keys - possible_options
|
|
94
|
+
unknown_options = options.keys - options_list.names
|
|
96
95
|
|
|
97
96
|
raise(
|
|
98
97
|
SmartCore::Initializer::OptionArgumentError,
|
|
@@ -114,18 +113,23 @@ class SmartCore::Initializer::Constructor
|
|
|
114
113
|
#
|
|
115
114
|
# @api private
|
|
116
115
|
# @since 0.1.0
|
|
117
|
-
# @version 0.
|
|
116
|
+
# @version 0.12.1
|
|
118
117
|
def initialize_parameters(instance)
|
|
119
118
|
parameter_definitions = klass.__params__.zip(parameters).to_h
|
|
120
119
|
|
|
121
120
|
parameter_definitions.each_pair do |attribute, parameter_value|
|
|
122
|
-
|
|
121
|
+
# NOTE: check #cast? first — it's a cheap flag, and lets us skip the type
|
|
122
|
+
# validation entirely when casting is disabled (the common case).
|
|
123
|
+
if attribute.cast? && !attribute.type.valid?(parameter_value)
|
|
123
124
|
parameter_value = attribute.type.cast(parameter_value)
|
|
124
125
|
end
|
|
125
126
|
|
|
126
127
|
attribute.validate!(parameter_value)
|
|
127
128
|
final_value = attribute.finalizer.call(parameter_value, instance)
|
|
128
|
-
|
|
129
|
+
# NOTE: skip re-validation only for the identity default finalizer, which
|
|
130
|
+
# returns the already-validated value untouched. Custom finalizers may
|
|
131
|
+
# transform or mutate it, so they are always re-validated.
|
|
132
|
+
attribute.validate!(final_value) unless attribute.finalizer.default_identity?
|
|
129
133
|
|
|
130
134
|
instance.instance_variable_set("@#{attribute.name}", final_value)
|
|
131
135
|
end
|
|
@@ -136,7 +140,7 @@ class SmartCore::Initializer::Constructor
|
|
|
136
140
|
#
|
|
137
141
|
# @api private
|
|
138
142
|
# @since 0.1.0
|
|
139
|
-
# @version 0.
|
|
143
|
+
# @version 0.12.1
|
|
140
144
|
# rubocop:disable Metrics/AbcSize
|
|
141
145
|
def initialize_options(instance)
|
|
142
146
|
klass.__options__.each do |attribute|
|
|
@@ -151,13 +155,19 @@ class SmartCore::Initializer::Constructor
|
|
|
151
155
|
# But optional attributes with defined `default` setting should be
|
|
152
156
|
# type-checked and type-casted.
|
|
153
157
|
if options.key?(attribute.name) || attribute.has_default?
|
|
154
|
-
|
|
158
|
+
# NOTE: check #cast? first — it's a cheap flag, and lets us skip the type
|
|
159
|
+
# validation entirely when casting is disabled (the common case).
|
|
160
|
+
if attribute.cast? && !attribute.type.valid?(option_value)
|
|
155
161
|
option_value = attribute.type.cast(option_value)
|
|
156
162
|
end
|
|
157
163
|
|
|
158
164
|
attribute.validate!(option_value)
|
|
159
|
-
|
|
160
|
-
|
|
165
|
+
final_value = attribute.finalizer.call(option_value, instance)
|
|
166
|
+
# NOTE: skip re-validation only for the identity default finalizer, which
|
|
167
|
+
# returns the already-validated value untouched. Custom finalizers may
|
|
168
|
+
# transform or mutate it, so they are always re-validated.
|
|
169
|
+
attribute.validate!(final_value) unless attribute.finalizer.default_identity?
|
|
170
|
+
option_value = final_value
|
|
161
171
|
end
|
|
162
172
|
|
|
163
173
|
instance.instance_variable_set("@#{attribute.name}", option_value)
|
|
@@ -179,6 +189,7 @@ class SmartCore::Initializer::Constructor
|
|
|
179
189
|
#
|
|
180
190
|
# @api private
|
|
181
191
|
# @since 0.1.0
|
|
192
|
+
# @version 0.12.1
|
|
182
193
|
def process_init_extensions(instance)
|
|
183
194
|
klass.__init_extensions__.each do |extension|
|
|
184
195
|
extension.call(instance)
|
|
@@ -11,9 +11,12 @@ class SmartCore::Initializer::Extensions::List
|
|
|
11
11
|
#
|
|
12
12
|
# @api private
|
|
13
13
|
# @since 0.1.0
|
|
14
|
-
# @version 0.
|
|
14
|
+
# @version 0.12.1
|
|
15
15
|
def initialize
|
|
16
16
|
@extensions = []
|
|
17
|
+
# NOTE: frozen snapshot rebuilt on mutation so the instantiation path can
|
|
18
|
+
# iterate (and skip when empty) lock-free.
|
|
19
|
+
@snapshot = [].freeze
|
|
17
20
|
@lock = SmartCore::Engine::ReadWriteLock.new
|
|
18
21
|
end
|
|
19
22
|
|
|
@@ -22,9 +25,12 @@ class SmartCore::Initializer::Extensions::List
|
|
|
22
25
|
#
|
|
23
26
|
# @api private
|
|
24
27
|
# @since 0.1.0
|
|
25
|
-
# @version 0.
|
|
28
|
+
# @version 0.12.1
|
|
26
29
|
def add(extension)
|
|
27
|
-
@lock.write_sync
|
|
30
|
+
@lock.write_sync do
|
|
31
|
+
extensions << extension
|
|
32
|
+
@snapshot = extensions.dup.freeze
|
|
33
|
+
end
|
|
28
34
|
end
|
|
29
35
|
alias_method :<<, :add
|
|
30
36
|
|
|
@@ -43,22 +49,24 @@ class SmartCore::Initializer::Extensions::List
|
|
|
43
49
|
# @param block [Block]
|
|
44
50
|
# @return [Enumerable]
|
|
45
51
|
#
|
|
52
|
+
# @note Lock-free: iterates the immutable snapshot maintained on mutation.
|
|
53
|
+
#
|
|
46
54
|
# @api private
|
|
47
55
|
# @since 0.1.0
|
|
48
|
-
# @version 0.
|
|
56
|
+
# @version 0.12.1
|
|
49
57
|
def each(&block)
|
|
50
|
-
@
|
|
51
|
-
block_given? ? extensions.each(&block) : extensions.each
|
|
52
|
-
end
|
|
58
|
+
block_given? ? @snapshot.each(&block) : @snapshot.each
|
|
53
59
|
end
|
|
54
60
|
|
|
55
61
|
# @return [Integer]
|
|
56
62
|
#
|
|
63
|
+
# @note Lock-free: reads the size of the immutable snapshot.
|
|
64
|
+
#
|
|
57
65
|
# @api private
|
|
58
66
|
# @since 0.1.0
|
|
59
|
-
# @version 0.
|
|
67
|
+
# @version 0.12.1
|
|
60
68
|
def size
|
|
61
|
-
@
|
|
69
|
+
@snapshot.size
|
|
62
70
|
end
|
|
63
71
|
|
|
64
72
|
private
|
data/smart_initializer.gemspec
CHANGED
|
@@ -40,4 +40,6 @@ Gem::Specification.new do |spec|
|
|
|
40
40
|
spec.add_development_dependency 'armitage-rubocop', '~> 1.30'
|
|
41
41
|
spec.add_development_dependency 'simplecov', '~> 0.21'
|
|
42
42
|
spec.add_development_dependency 'pry', '~> 0.14'
|
|
43
|
+
spec.add_development_dependency 'ostruct'
|
|
44
|
+
spec.add_development_dependency 'bigdecimal'
|
|
43
45
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: smart_initializer
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.12.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Rustam Ibragimov
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: exe
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: smart_engine
|
|
@@ -136,6 +135,34 @@ dependencies:
|
|
|
136
135
|
- - "~>"
|
|
137
136
|
- !ruby/object:Gem::Version
|
|
138
137
|
version: '0.14'
|
|
138
|
+
- !ruby/object:Gem::Dependency
|
|
139
|
+
name: ostruct
|
|
140
|
+
requirement: !ruby/object:Gem::Requirement
|
|
141
|
+
requirements:
|
|
142
|
+
- - ">="
|
|
143
|
+
- !ruby/object:Gem::Version
|
|
144
|
+
version: '0'
|
|
145
|
+
type: :development
|
|
146
|
+
prerelease: false
|
|
147
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
148
|
+
requirements:
|
|
149
|
+
- - ">="
|
|
150
|
+
- !ruby/object:Gem::Version
|
|
151
|
+
version: '0'
|
|
152
|
+
- !ruby/object:Gem::Dependency
|
|
153
|
+
name: bigdecimal
|
|
154
|
+
requirement: !ruby/object:Gem::Requirement
|
|
155
|
+
requirements:
|
|
156
|
+
- - ">="
|
|
157
|
+
- !ruby/object:Gem::Version
|
|
158
|
+
version: '0'
|
|
159
|
+
type: :development
|
|
160
|
+
prerelease: false
|
|
161
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
162
|
+
requirements:
|
|
163
|
+
- - ">="
|
|
164
|
+
- !ruby/object:Gem::Version
|
|
165
|
+
version: '0'
|
|
139
166
|
description: A simple and convenient way to declare complex constructors
|
|
140
167
|
email:
|
|
141
168
|
- iamdaiver@gmail.com
|
|
@@ -231,7 +258,6 @@ metadata:
|
|
|
231
258
|
homepage_uri: https://github.com/smart-rb/smart_initializer
|
|
232
259
|
source_code_uri: https://github.com/smart-rb/smart_initializer
|
|
233
260
|
changelog_uri: https://github.com/smart-rb/smart_initializer/blob/master/CHANGELOG.md
|
|
234
|
-
post_install_message:
|
|
235
261
|
rdoc_options: []
|
|
236
262
|
require_paths:
|
|
237
263
|
- lib
|
|
@@ -246,8 +272,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
246
272
|
- !ruby/object:Gem::Version
|
|
247
273
|
version: '0'
|
|
248
274
|
requirements: []
|
|
249
|
-
rubygems_version: 3.
|
|
250
|
-
signing_key:
|
|
275
|
+
rubygems_version: 3.6.9
|
|
251
276
|
specification_version: 4
|
|
252
277
|
summary: Initializer DSL
|
|
253
278
|
test_files: []
|