dry-initializer 3.0.4 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
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