dry-initializer 3.0.2 → 3.1.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.
Files changed (89) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +260 -241
  3. data/LICENSE +1 -1
  4. data/README.md +18 -77
  5. data/dry-initializer.gemspec +34 -19
  6. data/lib/dry/initializer/builders/attribute.rb +78 -69
  7. data/lib/dry/initializer/builders/initializer.rb +56 -58
  8. data/lib/dry/initializer/builders/reader.rb +55 -47
  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 +162 -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 +25 -17
  23. data/lib/dry/initializer/dispatchers.rb +48 -43
  24. data/lib/dry/initializer/dsl.rb +42 -34
  25. data/lib/dry/initializer/mixin/local.rb +19 -13
  26. data/lib/dry/initializer/mixin/root.rb +12 -7
  27. data/lib/dry/initializer/mixin.rb +17 -12
  28. data/lib/dry/initializer/struct.rb +34 -29
  29. data/lib/dry/initializer/undefined.rb +7 -1
  30. data/lib/dry/initializer/version.rb +7 -0
  31. data/lib/dry/initializer.rb +2 -0
  32. data/lib/dry-initializer.rb +2 -0
  33. data/lib/tasks/benchmark.rake +2 -0
  34. data/lib/tasks/profile.rake +4 -0
  35. metadata +25 -125
  36. data/.codeclimate.yml +0 -12
  37. data/.github/ISSUE_TEMPLATE/----please-don-t-ask-for-support-via-issues.md +0 -10
  38. data/.github/ISSUE_TEMPLATE/---bug-report.md +0 -34
  39. data/.github/ISSUE_TEMPLATE/---feature-request.md +0 -18
  40. data/.github/workflows/custom_ci.yml +0 -74
  41. data/.github/workflows/docsite.yml +0 -34
  42. data/.github/workflows/sync_configs.yml +0 -34
  43. data/.gitignore +0 -12
  44. data/.rspec +0 -4
  45. data/.rubocop.yml +0 -89
  46. data/CODE_OF_CONDUCT.md +0 -13
  47. data/CONTRIBUTING.md +0 -29
  48. data/Gemfile +0 -38
  49. data/Guardfile +0 -5
  50. data/LICENSE.txt +0 -21
  51. data/Rakefile +0 -8
  52. data/benchmarks/compare_several_defaults.rb +0 -82
  53. data/benchmarks/plain_options.rb +0 -63
  54. data/benchmarks/plain_params.rb +0 -84
  55. data/benchmarks/with_coercion.rb +0 -71
  56. data/benchmarks/with_defaults.rb +0 -66
  57. data/benchmarks/with_defaults_and_coercion.rb +0 -59
  58. data/docsite/source/attributes.html.md +0 -106
  59. data/docsite/source/container-version.html.md +0 -39
  60. data/docsite/source/index.html.md +0 -43
  61. data/docsite/source/inheritance.html.md +0 -43
  62. data/docsite/source/optionals-and-defaults.html.md +0 -130
  63. data/docsite/source/options-tolerance.html.md +0 -27
  64. data/docsite/source/params-and-options.html.md +0 -74
  65. data/docsite/source/rails-support.html.md +0 -101
  66. data/docsite/source/readers.html.md +0 -43
  67. data/docsite/source/skip-undefined.html.md +0 -59
  68. data/docsite/source/type-constraints.html.md +0 -160
  69. data/spec/attributes_spec.rb +0 -38
  70. data/spec/coercion_of_nil_spec.rb +0 -25
  71. data/spec/custom_dispatchers_spec.rb +0 -35
  72. data/spec/custom_initializer_spec.rb +0 -30
  73. data/spec/default_values_spec.rb +0 -83
  74. data/spec/definition_spec.rb +0 -111
  75. data/spec/invalid_default_spec.rb +0 -13
  76. data/spec/list_type_spec.rb +0 -32
  77. data/spec/missed_default_spec.rb +0 -14
  78. data/spec/nested_type_spec.rb +0 -48
  79. data/spec/optional_spec.rb +0 -71
  80. data/spec/options_tolerance_spec.rb +0 -11
  81. data/spec/public_attributes_utility_spec.rb +0 -22
  82. data/spec/reader_spec.rb +0 -87
  83. data/spec/repetitive_definitions_spec.rb +0 -69
  84. data/spec/several_assignments_spec.rb +0 -41
  85. data/spec/spec_helper.rb +0 -29
  86. data/spec/subclassing_spec.rb +0 -49
  87. data/spec/type_argument_spec.rb +0 -35
  88. data/spec/type_constraint_spec.rb +0 -78
  89. data/spec/value_coercion_via_dry_types_spec.rb +0 -29
@@ -1,182 +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
81
- val = instance.send(key)
82
- obj[key] = val unless null == val
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
84
+
85
+ val = instance.send(key)
86
+ obj[key] = val unless null == val
87
+ end
83
88
  end
84
- end
85
89
 
86
- # The hash of assigned attributes for an instance of the [#extended_class]
87
- # @param [Dry::Initializer::Instance] instance
88
- # @return [Hash<Symbol, Object>]
89
- def attributes(instance)
90
- definitions.values.each_with_object({}) do |item, obj|
91
- key = item.target
92
- val = instance.send(:instance_variable_get, item.ivar)
93
- 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
94
99
  end
95
- end
96
100
 
97
- # Code of the `#__initialize__` method
98
- # @return [String]
99
- def code
100
- Builders::Initializer[self]
101
- end
101
+ # Code of the `#__initialize__` method
102
+ # @return [String]
103
+ def code
104
+ Builders::Initializer[self]
105
+ end
102
106
 
103
- # Finalizes config
104
- # @return [self]
105
- def finalize
106
- @definitions = final_definitions
107
- check_order_of_params
108
- mixin.class_eval(code)
109
- children.each(&:finalize)
110
- self
111
- 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
112
116
 
113
- # Human-readable representation of configured params and options
114
- # @return [String]
115
- def inch
116
- line = Builders::Signature[self]
117
- line = line.gsub("__dry_initializer_options__", "options")
118
- lines = ["@!method initialize(#{line})"]
119
- lines += ["Initializes an instance of #{extended_class}"]
120
- lines += definitions.values.map(&:inch)
121
- lines += ["@return [#{extended_class}]"]
122
- lines.join("\n")
123
- 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
124
128
 
125
- private
129
+ private
126
130
 
127
- def initialize(extended_class = nil, null: UNDEFINED)
128
- @extended_class = extended_class.tap { |klass| klass&.include mixin }
129
- sklass = extended_class&.superclass
130
- @parent = sklass.dry_initializer if sklass.is_a? Dry::Initializer
131
- @null = null || parent&.null
132
- @definitions = {}
133
- finalize
134
- 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
135
139
 
136
- # rubocop: disable Metrics/MethodLength
137
- def add_definition(option, name, type, block, **opts)
138
- opts = {
139
- parent: extended_class,
140
- option: option,
141
- null: null,
142
- source: name,
143
- type: type,
144
- block: block,
145
- **opts
146
- }
147
-
148
- options = Dispatchers.call(**opts)
149
- definition = Definition.new(**options)
150
- definitions[definition.source] = definition
151
- finalize
152
- mixin.class_eval definition.code
153
- end
154
- # 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
155
157
 
156
- def final_definitions
157
- parent_definitions = Hash(parent&.definitions&.dup)
158
- definitions.each_with_object(parent_definitions) do |(key, val), obj|
159
- 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
160
163
  end
161
- end
162
164
 
163
- def check_type(previous, current)
164
- return current unless previous
165
- return current if previous.option == current.option
166
- raise SyntaxError,
167
- "cannot reload #{previous} of #{extended_class.superclass}" \
168
- " by #{current} of its subclass #{extended_class}"
169
- end
165
+ def check_type(previous, current)
166
+ return current unless previous
167
+ return current if previous.option == current.option
168
+
169
+ raise SyntaxError,
170
+ "cannot reload #{previous} of #{extended_class.superclass}" \
171
+ " by #{current} of its subclass #{extended_class}"
172
+ end
170
173
 
171
- def check_order_of_params
172
- params.inject(nil) do |optional, current|
173
- if current.optional
174
- current
175
- elsif optional
176
- raise SyntaxError, "#{extended_class}: required #{current}" \
177
- " goes after optional #{optional}"
178
- else
179
- 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
180
184
  end
181
185
  end
182
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