dry-initializer 3.0.4 → 3.1.0

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.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +12 -0
  3. data/LICENSE +1 -1
  4. data/README.md +4 -3
  5. data/dry-initializer.gemspec +15 -13
  6. data/lib/dry/initializer/builders/attribute.rb +92 -82
  7. data/lib/dry/initializer/builders/initializer.rb +56 -54
  8. data/lib/dry/initializer/builders/reader.rb +55 -49
  9. data/lib/dry/initializer/builders/signature.rb +29 -23
  10. data/lib/dry/initializer/builders.rb +9 -5
  11. data/lib/dry/initializer/config.rb +160 -158
  12. data/lib/dry/initializer/definition.rb +58 -54
  13. data/lib/dry/initializer/dispatchers/build_nested_type.rb +54 -40
  14. data/lib/dry/initializer/dispatchers/check_type.rb +45 -39
  15. data/lib/dry/initializer/dispatchers/prepare_default.rb +32 -25
  16. data/lib/dry/initializer/dispatchers/prepare_ivar.rb +13 -6
  17. data/lib/dry/initializer/dispatchers/prepare_optional.rb +14 -7
  18. data/lib/dry/initializer/dispatchers/prepare_reader.rb +29 -22
  19. data/lib/dry/initializer/dispatchers/prepare_source.rb +12 -5
  20. data/lib/dry/initializer/dispatchers/prepare_target.rb +44 -37
  21. data/lib/dry/initializer/dispatchers/unwrap_type.rb +21 -10
  22. data/lib/dry/initializer/dispatchers/wrap_type.rb +24 -17
  23. data/lib/dry/initializer/dispatchers.rb +48 -43
  24. data/lib/dry/initializer/dsl.rb +42 -34
  25. data/lib/dry/initializer/errors.rb +22 -0
  26. data/lib/dry/initializer/mixin/local.rb +19 -13
  27. data/lib/dry/initializer/mixin/root.rb +12 -7
  28. data/lib/dry/initializer/mixin.rb +17 -12
  29. data/lib/dry/initializer/struct.rb +34 -29
  30. data/lib/dry/initializer/undefined.rb +7 -1
  31. data/lib/dry/initializer/version.rb +3 -1
  32. data/lib/dry/initializer.rb +12 -9
  33. data/lib/dry-initializer.rb +3 -1
  34. data/lib/tasks/benchmark.rake +15 -13
  35. data/lib/tasks/profile.rake +20 -16
  36. metadata +5 -4
@@ -1,184 +1,186 @@
1
- module Dry::Initializer
2
- #
3
- # Gem-related configuration of some class
4
- #
5
- class Config
6
- # @!attribute [r] null
7
- # @return [Dry::Initializer::UNDEFINED, nil] value of unassigned variable
8
-
9
- # @!attribute [r] extended_class
10
- # @return [Class] the class whose config collected by current object
11
-
12
- # @!attribute [r] parent
13
- # @return [Dry::Initializer::Config] parent configuration
14
-
15
- # @!attribute [r] definitions
16
- # @return [Hash<Symbol, Dry::Initializer::Definition>]
17
- # hash of attribute definitions with their source names
18
-
19
- attr_reader :null, :extended_class, :parent, :definitions
20
-
21
- # @!attribute [r] mixin
22
- # @return [Module] reference to the module to be included into class
23
- def mixin
24
- @mixin ||= Module.new.tap do |mod|
25
- __dry_initializer__ = self
26
- mod.extend(Mixin::Local)
27
- mod.send :define_method, :__dry_initializer_config__ do
28
- __dry_initializer__
1
+ # frozen_string_literal: true
2
+
3
+ module Dry
4
+ module Initializer
5
+ #
6
+ # Gem-related configuration of some class
7
+ #
8
+ class Config
9
+ # @!attribute [r] null
10
+ # @return [Dry::Initializer::UNDEFINED, nil] value of unassigned variable
11
+
12
+ # @!attribute [r] extended_class
13
+ # @return [Class] the class whose config collected by current object
14
+
15
+ # @!attribute [r] parent
16
+ # @return [Dry::Initializer::Config] parent configuration
17
+
18
+ # @!attribute [r] definitions
19
+ # @return [Hash<Symbol, Dry::Initializer::Definition>]
20
+ # hash of attribute definitions with their source names
21
+
22
+ attr_reader :null, :extended_class, :parent, :definitions
23
+
24
+ # @!attribute [r] mixin
25
+ # @return [Module] reference to the module to be included into class
26
+ def mixin
27
+ @mixin ||= Module.new.tap do |mod|
28
+ initializer = self
29
+ mod.extend(Mixin::Local)
30
+ mod.define_method(:__dry_initializer_config__) do
31
+ initializer
32
+ end
33
+ mod.send :private, :__dry_initializer_config__
29
34
  end
30
- mod.send :private, :__dry_initializer_config__
31
35
  end
32
- end
33
36
 
34
- # List of configs of all subclasses of the [#extended_class]
35
- # @return [Array<Dry::Initializer::Config>]
36
- def children
37
- @children ||= Set.new
38
- end
37
+ # List of configs of all subclasses of the [#extended_class]
38
+ # @return [Array<Dry::Initializer::Config>]
39
+ def children
40
+ @children ||= Set.new
41
+ end
39
42
 
40
- # List of definitions for initializer params
41
- # @return [Array<Dry::Initializer::Definition>]
42
- def params
43
- definitions.values.reject(&:option)
44
- end
43
+ # List of definitions for initializer params
44
+ # @return [Array<Dry::Initializer::Definition>]
45
+ def params
46
+ definitions.values.reject(&:option)
47
+ end
45
48
 
46
- # List of definitions for initializer options
47
- # @return [Array<Dry::Initializer::Definition>]
48
- def options
49
- definitions.values.select(&:option)
50
- end
49
+ # List of definitions for initializer options
50
+ # @return [Array<Dry::Initializer::Definition>]
51
+ def options
52
+ definitions.values.select(&:option)
53
+ end
51
54
 
52
- # Adds or redefines a parameter
53
- # @param [Symbol] name
54
- # @param [#call, nil] type (nil)
55
- # @option opts [Proc] :default
56
- # @option opts [Boolean] :optional
57
- # @option opts [Symbol] :as
58
- # @option opts [true, false, :protected, :public, :private] :reader
59
- # @return [self] itself
60
- def param(name, type = nil, **opts, &block)
61
- add_definition(false, name, type, block, **opts)
62
- end
55
+ # Adds or redefines a parameter
56
+ # @param [Symbol] name
57
+ # @param [#call, nil] type (nil)
58
+ # @option opts [Proc] :default
59
+ # @option opts [Boolean] :optional
60
+ # @option opts [Symbol] :as
61
+ # @option opts [true, false, :protected, :public, :private] :reader
62
+ # @return [self] itself
63
+ def param(name, type = nil, **opts, &block)
64
+ add_definition(false, name, type, block, **opts)
65
+ end
63
66
 
64
- # Adds or redefines an option of [#dry_initializer]
65
- #
66
- # @param (see #param)
67
- # @option (see #param)
68
- # @return (see #param)
69
- #
70
- def option(name, type = nil, **opts, &block)
71
- add_definition(true, name, type, block, **opts)
72
- end
67
+ # Adds or redefines an option of [#dry_initializer]
68
+ #
69
+ # @param (see #param)
70
+ # @option (see #param)
71
+ # @return (see #param)
72
+ #
73
+ def option(name, type = nil, **opts, &block)
74
+ add_definition(true, name, type, block, **opts)
75
+ end
73
76
 
74
- # The hash of public attributes for an instance of the [#extended_class]
75
- # @param [Dry::Initializer::Instance] instance
76
- # @return [Hash<Symbol, Object>]
77
- def public_attributes(instance)
78
- definitions.values.each_with_object({}) do |item, obj|
79
- key = item.target
80
- next unless instance.respond_to? key
77
+ # The hash of public attributes for an instance of the [#extended_class]
78
+ # @param [Dry::Initializer::Instance] instance
79
+ # @return [Hash<Symbol, Object>]
80
+ def public_attributes(instance)
81
+ definitions.values.each_with_object({}) do |item, obj|
82
+ key = item.target
83
+ next unless instance.respond_to? key
81
84
 
82
- val = instance.send(key)
83
- obj[key] = val unless null == val
85
+ val = instance.send(key)
86
+ obj[key] = val unless null == val
87
+ end
84
88
  end
85
- end
86
89
 
87
- # The hash of assigned attributes for an instance of the [#extended_class]
88
- # @param [Dry::Initializer::Instance] instance
89
- # @return [Hash<Symbol, Object>]
90
- def attributes(instance)
91
- definitions.values.each_with_object({}) do |item, obj|
92
- key = item.target
93
- val = instance.send(:instance_variable_get, item.ivar)
94
- obj[key] = val unless null == val
90
+ # The hash of assigned attributes for an instance of the [#extended_class]
91
+ # @param [Dry::Initializer::Instance] instance
92
+ # @return [Hash<Symbol, Object>]
93
+ def attributes(instance)
94
+ definitions.values.each_with_object({}) do |item, obj|
95
+ key = item.target
96
+ val = instance.send(:instance_variable_get, item.ivar)
97
+ obj[key] = val unless null == val
98
+ end
95
99
  end
96
- end
97
100
 
98
- # Code of the `#__initialize__` method
99
- # @return [String]
100
- def code
101
- Builders::Initializer[self]
102
- end
101
+ # Code of the `#__initialize__` method
102
+ # @return [String]
103
+ def code
104
+ Builders::Initializer[self]
105
+ end
103
106
 
104
- # Finalizes config
105
- # @return [self]
106
- def finalize
107
- @definitions = final_definitions
108
- check_order_of_params
109
- mixin.class_eval(code)
110
- children.each(&:finalize)
111
- self
112
- end
107
+ # Finalizes config
108
+ # @return [self]
109
+ def finalize
110
+ @definitions = final_definitions
111
+ check_order_of_params
112
+ mixin.class_eval(code)
113
+ children.each(&:finalize)
114
+ self
115
+ end
113
116
 
114
- # Human-readable representation of configured params and options
115
- # @return [String]
116
- def inch
117
- line = Builders::Signature[self]
118
- line = line.gsub('__dry_initializer_options__', 'options')
119
- lines = ["@!method initialize(#{line})"]
120
- lines += ["Initializes an instance of #{extended_class}"]
121
- lines += definitions.values.map(&:inch)
122
- lines += ["@return [#{extended_class}]"]
123
- lines.join("\n")
124
- end
117
+ # Human-readable representation of configured params and options
118
+ # @return [String]
119
+ def inch
120
+ line = Builders::Signature[self]
121
+ line = line.gsub("__dry_initializer_options__", "options")
122
+ lines = ["@!method initialize(#{line})"]
123
+ lines += ["Initializes an instance of #{extended_class}"]
124
+ lines += definitions.values.map(&:inch)
125
+ lines += ["@return [#{extended_class}]"]
126
+ lines.join("\n")
127
+ end
125
128
 
126
- private
129
+ private
127
130
 
128
- def initialize(extended_class = nil, null: UNDEFINED)
129
- @extended_class = extended_class.tap { |klass| klass&.include mixin }
130
- sklass = extended_class&.superclass
131
- @parent = sklass.dry_initializer if sklass.is_a? Dry::Initializer
132
- @null = null || parent&.null
133
- @definitions = {}
134
- finalize
135
- end
131
+ def initialize(extended_class = nil, null: UNDEFINED)
132
+ @extended_class = extended_class.tap { |klass| klass&.include mixin }
133
+ sklass = extended_class&.superclass
134
+ @parent = sklass.dry_initializer if sklass.is_a? Dry::Initializer
135
+ @null = null || parent&.null
136
+ @definitions = {}
137
+ finalize
138
+ end
136
139
 
137
- # rubocop: disable Metrics/MethodLength
138
- def add_definition(option, name, type, block, **opts)
139
- opts = {
140
- parent: extended_class,
141
- option: option,
142
- null: null,
143
- source: name,
144
- type: type,
145
- block: block,
146
- **opts
147
- }
148
-
149
- options = Dispatchers.call(**opts)
150
- definition = Definition.new(**options)
151
- definitions[definition.source] = definition
152
- finalize
153
- mixin.class_eval definition.code
154
- end
155
- # rubocop: enable Metrics/MethodLength
140
+ def add_definition(option, name, type, block, **opts)
141
+ opts = {
142
+ parent: extended_class,
143
+ option: option,
144
+ null: null,
145
+ source: name,
146
+ type: type,
147
+ block: block,
148
+ **opts
149
+ }
150
+
151
+ options = Dispatchers.call(**opts)
152
+ definition = Definition.new(**options)
153
+ definitions[definition.source] = definition
154
+ finalize
155
+ mixin.class_eval definition.code
156
+ end
156
157
 
157
- def final_definitions
158
- parent_definitions = Hash(parent&.definitions&.dup)
159
- definitions.each_with_object(parent_definitions) do |(key, val), obj|
160
- obj[key] = check_type(obj[key], val)
158
+ def final_definitions
159
+ parent_definitions = Hash(parent&.definitions&.dup)
160
+ definitions.each_with_object(parent_definitions) do |(key, val), obj|
161
+ obj[key] = check_type(obj[key], val)
162
+ end
161
163
  end
162
- end
163
164
 
164
- def check_type(previous, current)
165
- return current unless previous
166
- return current if previous.option == current.option
165
+ def check_type(previous, current)
166
+ return current unless previous
167
+ return current if previous.option == current.option
167
168
 
168
- raise SyntaxError,
169
- "cannot reload #{previous} of #{extended_class.superclass}" \
170
- " by #{current} of its subclass #{extended_class}"
171
- end
169
+ raise SyntaxError,
170
+ "cannot reload #{previous} of #{extended_class.superclass}" \
171
+ " by #{current} of its subclass #{extended_class}"
172
+ end
172
173
 
173
- def check_order_of_params
174
- params.inject(nil) do |optional, current|
175
- if current.optional
176
- current
177
- elsif optional
178
- raise SyntaxError, "#{extended_class}: required #{current}" \
179
- " goes after optional #{optional}"
180
- else
181
- optional
174
+ def check_order_of_params
175
+ params.inject(nil) do |optional, current|
176
+ if current.optional
177
+ current
178
+ elsif optional
179
+ raise SyntaxError, "#{extended_class}: required #{current}" \
180
+ " goes after optional #{optional}"
181
+ else
182
+ optional
183
+ end
182
184
  end
183
185
  end
184
186
  end
@@ -1,65 +1,69 @@
1
- module Dry::Initializer
2
- #
3
- # @private
4
- # @abstract
5
- #
6
- # Base class for parameter or option definitions
7
- # Defines methods to add corresponding reader to the class,
8
- # and build value of instance attribute.
9
- #
10
- class Definition
11
- attr_reader :option, :null, :source, :target, :ivar,
12
- :type, :optional, :default, :reader,
13
- :desc
1
+ # frozen_string_literal: true
14
2
 
15
- def options
16
- {
17
- as: target,
18
- type: type,
19
- optional: optional,
20
- default: default,
21
- reader: reader,
22
- desc: desc
23
- }.reject { |_, value| value.nil? }
24
- end
3
+ module Dry
4
+ module Initializer
5
+ #
6
+ # @private
7
+ # @abstract
8
+ #
9
+ # Base class for parameter or option definitions
10
+ # Defines methods to add corresponding reader to the class,
11
+ # and build value of instance attribute.
12
+ #
13
+ class Definition
14
+ attr_reader :option, :null, :source, :target, :ivar,
15
+ :type, :optional, :default, :reader,
16
+ :desc
25
17
 
26
- def name
27
- @name ||= (option ? 'option' : 'parameter') << " '#{source}'"
28
- end
29
- alias to_s name
30
- alias to_str name
31
- alias inspect name
18
+ def options
19
+ {
20
+ as: target,
21
+ type: type,
22
+ optional: optional,
23
+ default: default,
24
+ reader: reader,
25
+ desc: desc
26
+ }.compact
27
+ end
32
28
 
33
- def ==(other)
34
- other.instance_of?(self.class) && (other.source == source)
35
- end
29
+ def name
30
+ @name ||= "#{option ? "option" : "parameter"} '#{source}'"
31
+ end
32
+ alias_method :to_s, :name
33
+ alias_method :to_str, :name
34
+ alias_method :inspect, :name
36
35
 
37
- def code
38
- Builders::Reader[self]
39
- end
36
+ def ==(other)
37
+ other.instance_of?(self.class) && (other.source == source)
38
+ end
40
39
 
41
- def inch
42
- @inch ||= (option ? '@option' : '@param ').tap do |text|
43
- text << ' [Object]'
44
- text << (option ? " :#{source}" : " #{source}")
45
- text << (optional ? ' (optional)' : ' (required)')
46
- text << " #{desc}" if desc
40
+ def code
41
+ Builders::Reader[self]
47
42
  end
48
- end
49
43
 
50
- private
44
+ def inch
45
+ @inch ||= (option ? "@option" : "@param ").tap do |text|
46
+ text << " [Object]"
47
+ text << (option ? " :#{source}" : " #{source}")
48
+ text << (optional ? " (optional)" : " (required)")
49
+ text << " #{desc}" if desc
50
+ end
51
+ end
51
52
 
52
- def initialize(**options)
53
- @option = options[:option]
54
- @null = options[:null]
55
- @source = options[:source]
56
- @target = options[:target]
57
- @ivar = "@#{@target}"
58
- @type = options[:type]
59
- @reader = options[:reader]
60
- @default = options[:default]
61
- @optional = options[:optional]
62
- @desc = options[:desc]
53
+ private
54
+
55
+ def initialize(**options)
56
+ @option = options[:option]
57
+ @null = options[:null]
58
+ @source = options[:source]
59
+ @target = options[:target]
60
+ @ivar = "@#{@target}"
61
+ @type = options[:type]
62
+ @reader = options[:reader]
63
+ @default = options[:default]
64
+ @optional = options[:optional]
65
+ @desc = options[:desc]
66
+ end
63
67
  end
64
68
  end
65
69
  end
@@ -1,4 +1,5 @@
1
- #
1
+ # frozen_string_literal: true
2
+
2
3
  # Prepare nested data type from a block
3
4
  #
4
5
  # @example
@@ -7,53 +8,66 @@
7
8
  # option :qux
8
9
  # end
9
10
  #
10
- module Dry::Initializer::Dispatchers::BuildNestedType
11
- extend self
12
-
13
- # rubocop: disable Metrics/ParameterLists
14
- def call(parent:, source:, target:, type: nil, block: nil, **options)
15
- check_certainty!(source, type, block)
16
- check_name!(target, block)
17
- type ||= build_nested_type(parent, target, block)
18
- { parent: parent, source: source, target: target, type: type, **options }
19
- end
20
- # rubocop: enable Metrics/ParameterLists
11
+ module Dry
12
+ module Initializer
13
+ module Dispatchers
14
+ module BuildNestedType
15
+ extend self
21
16
 
22
- private
17
+ # rubocop: disable Metrics/ParameterLists
18
+ def call(parent:, source:, target:, type: nil, block: nil, **options)
19
+ check_certainty!(source, type, block)
20
+ check_name!(target, block)
21
+ type ||= build_nested_type(parent, target, block)
22
+ {parent: parent, source: source, target: target, type: type, **options}
23
+ end
24
+ # rubocop: enable Metrics/ParameterLists
23
25
 
24
- def check_certainty!(source, type, block)
25
- return unless block
26
- return unless type
26
+ private
27
27
 
28
- raise ArgumentError, <<~MESSAGE
29
- You should define coercer of values of argument '#{source}'
30
- either though the parameter/option, or via nested block, but not the both.
31
- MESSAGE
32
- end
28
+ def check_certainty!(source, type, block)
29
+ return unless block
30
+ return unless type
33
31
 
34
- def check_name!(name, block)
35
- return unless block
36
- return unless name[/^_|__|_$/]
32
+ raise ArgumentError, <<~MESSAGE
33
+ You should define coercer of values of argument '#{source}'
34
+ either though the parameter/option, or via nested block, but not the both.
35
+ MESSAGE
36
+ end
37
37
 
38
- raise ArgumentError, <<~MESSAGE
39
- The name of the argument '#{name}' cannot be used for nested struct.
40
- A proper name can use underscores _ to divide alphanumeric parts only.
41
- MESSAGE
42
- end
38
+ def check_name!(name, block)
39
+ return unless block
40
+ return unless name[/^_|__|_$/]
43
41
 
44
- def build_nested_type(parent, name, block)
45
- return unless block
42
+ raise ArgumentError, <<~MESSAGE
43
+ The name of the argument '#{name}' cannot be used for nested struct.
44
+ A proper name can use underscores _ to divide alphanumeric parts only.
45
+ MESSAGE
46
+ end
46
47
 
47
- klass_name = full_name(parent, name)
48
- build_struct(klass_name, block)
49
- end
48
+ def build_nested_type(parent, name, block)
49
+ return unless block
50
50
 
51
- def full_name(parent, name)
52
- "::#{parent.name}::#{name.to_s.split("_").compact.map(&:capitalize).join}"
53
- end
51
+ klass_name = full_name(parent, name)
52
+ build_struct(klass_name, block)
53
+ end
54
+
55
+ def full_name(parent, name)
56
+ "::#{parent.name}::#{name.to_s.split("_").compact.map(&:capitalize).join}"
57
+ end
54
58
 
55
- def build_struct(klass_name, block)
56
- eval "class #{klass_name} < Dry::Initializer::Struct; end"
57
- const_get(klass_name).tap { |klass| klass.class_eval(&block) }
59
+ def build_struct(klass_name, block)
60
+ # rubocop: disable Security/Eval
61
+ # rubocop: disable Style/DocumentDynamicEvalDefinition
62
+ eval <<~RUBY, TOPLEVEL_BINDING, __FILE__, __LINE__ + 1
63
+ class #{klass_name} < Dry::Initializer::Struct
64
+ end
65
+ RUBY
66
+ # rubocop: enable Style/DocumentDynamicEvalDefinition
67
+ # rubocop: enable Security/Eval
68
+ const_get(klass_name).tap { _1.class_eval(&block) }
69
+ end
70
+ end
71
+ end
58
72
  end
59
73
  end