smart_initializer 0.12.0 → 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 +2 -1
- 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/constructor.rb +23 -12
- data/lib/smart_core/initializer/extensions/list.rb +17 -9
- data/lib/smart_core/initializer/version.rb +1 -1
- metadata +3 -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.12.
|
|
4
|
+
smart_initializer (0.12.1)
|
|
5
5
|
qonfig (~> 0.24)
|
|
6
6
|
smart_engine (~> 0.16)
|
|
7
7
|
smart_types (~> 0.8)
|
|
@@ -97,6 +97,7 @@ GEM
|
|
|
97
97
|
PLATFORMS
|
|
98
98
|
arm64-darwin-21
|
|
99
99
|
arm64-darwin-23
|
|
100
|
+
x86_64-linux
|
|
100
101
|
|
|
101
102
|
DEPENDENCIES
|
|
102
103
|
armitage-rubocop (~> 1.30)
|
|
@@ -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]
|
|
@@ -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
|
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.12.
|
|
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
|
|
@@ -259,7 +258,6 @@ metadata:
|
|
|
259
258
|
homepage_uri: https://github.com/smart-rb/smart_initializer
|
|
260
259
|
source_code_uri: https://github.com/smart-rb/smart_initializer
|
|
261
260
|
changelog_uri: https://github.com/smart-rb/smart_initializer/blob/master/CHANGELOG.md
|
|
262
|
-
post_install_message:
|
|
263
261
|
rdoc_options: []
|
|
264
262
|
require_paths:
|
|
265
263
|
- lib
|
|
@@ -274,8 +272,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
274
272
|
- !ruby/object:Gem::Version
|
|
275
273
|
version: '0'
|
|
276
274
|
requirements: []
|
|
277
|
-
rubygems_version: 3.
|
|
278
|
-
signing_key:
|
|
275
|
+
rubygems_version: 3.6.9
|
|
279
276
|
specification_version: 4
|
|
280
277
|
summary: Initializer DSL
|
|
281
278
|
test_files: []
|