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,43 +1,49 @@
1
- #
1
+ # frozen_string_literal: true
2
+
2
3
  # Checks whether an unwrapped type is valid
3
4
  #
4
- module Dry::Initializer::Dispatchers::CheckType
5
- extend self
6
-
7
- def call(source:, type: nil, wrap: 0, **options)
8
- check_if_callable! source, type
9
- check_arity! source, type, wrap
10
-
11
- { source: source, type: type, wrap: wrap, **options }
12
- end
13
-
14
- private
15
-
16
- def check_if_callable!(source, type)
17
- return if type.nil?
18
- return if type.respond_to?(:call)
19
-
20
- raise ArgumentError,
21
- "The type of the argument '#{source}' should be callable"
22
- end
23
-
24
- def check_arity!(_source, type, wrap)
25
- return if type.nil?
26
- return if wrap.zero?
27
- return if type.method(:call).arity.abs == 1
28
-
29
- raise ArgumentError, <<~MESSAGE
30
- The dry_intitializer supports wrapped types with one argument only.
31
- You cannot use array types with element coercers having several arguments.
32
-
33
- For example, this definitions are correct:
34
- option :foo, [proc(&:to_s)]
35
- option :bar, type: [[]]
36
- option :baz, ->(a, b) { [a, b] }
37
-
38
- While this is not:
39
- option :foo, [->(a, b) { [a, b] }]
40
- MESSAGE
5
+ module Dry
6
+ module Initializer
7
+ module Dispatchers
8
+ module CheckType
9
+ extend self
10
+
11
+ def call(source:, type: nil, wrap: 0, **options)
12
+ check_if_callable! source, type
13
+ check_arity! source, type, wrap
14
+
15
+ {source: source, type: type, wrap: wrap, **options}
16
+ end
17
+
18
+ private
19
+
20
+ def check_if_callable!(source, type)
21
+ return if type.nil?
22
+ return if type.respond_to?(:call)
23
+
24
+ raise ArgumentError,
25
+ "The type of the argument '#{source}' should be callable"
26
+ end
27
+
28
+ def check_arity!(_source, type, wrap)
29
+ return if type.nil?
30
+ return if wrap.zero?
31
+ return if type.method(:call).arity.abs == 1
32
+
33
+ raise ArgumentError, <<~MESSAGE
34
+ The dry_intitializer supports wrapped types with one argument only.
35
+ You cannot use array types with element coercers having several arguments.
36
+
37
+ For example, this definitions are correct:
38
+ option :foo, [proc(&:to_s)]
39
+ option :bar, type: [[]]
40
+ option :baz, ->(a, b) { [a, b] }
41
+
42
+ While this is not:
43
+ option :foo, [->(a, b) { [a, b] }]
44
+ MESSAGE
45
+ end
46
+ end
47
+ end
41
48
  end
42
- # rubocop: enable Metrics/MethodLength
43
49
  end
@@ -1,40 +1,47 @@
1
- #
1
+ # frozen_string_literal: true
2
+
2
3
  # Prepares the `:default` option
3
4
  #
4
5
  # It must respond to `.call` without arguments
5
6
  #
6
- module Dry::Initializer::Dispatchers::PrepareDefault
7
- extend self
7
+ module Dry
8
+ module Initializer
9
+ module Dispatchers
10
+ module PrepareDefault
11
+ extend self
8
12
 
9
- def call(default: nil, optional: nil, **options)
10
- default = callable! default
11
- check_arity! default
13
+ def call(default: nil, optional: nil, **options)
14
+ default = callable! default
15
+ check_arity! default
12
16
 
13
- { default: default, optional: (optional | default), **options }
14
- end
17
+ {default: default, optional: (optional | default), **options}
18
+ end
15
19
 
16
- private
20
+ private
17
21
 
18
- def callable!(default)
19
- return unless default
20
- return default if default.respond_to?(:call)
21
- return callable(default.to_proc) if default.respond_to?(:to_proc)
22
+ def callable!(default)
23
+ return unless default
24
+ return default if default.respond_to?(:call)
25
+ return callable(default.to_proc) if default.respond_to?(:to_proc)
22
26
 
23
- invalid!(default)
24
- end
27
+ invalid!(default)
28
+ end
25
29
 
26
- def check_arity!(default)
27
- return unless default
30
+ def check_arity!(default)
31
+ return unless default
28
32
 
29
- arity = default.method(:call).arity.to_i
30
- return unless arity.positive?
33
+ arity = default.method(:call).arity.to_i
34
+ return unless arity.positive?
31
35
 
32
- invalid!(default)
33
- end
36
+ invalid!(default)
37
+ end
34
38
 
35
- def invalid!(default)
36
- raise TypeError, "The #{default.inspect} should be" \
37
- ' either convertable to proc with no arguments,' \
38
- ' or respond to #call without arguments.'
39
+ def invalid!(default)
40
+ raise TypeError, "The #{default.inspect} should be" \
41
+ " either convertable to proc with no arguments," \
42
+ " or respond to #call without arguments."
43
+ end
44
+ end
45
+ end
39
46
  end
40
47
  end
@@ -1,12 +1,19 @@
1
- #
1
+ # frozen_string_literal: true
2
+
2
3
  # Prepares the variable name of a parameter or an option.
3
4
  #
4
- module Dry::Initializer::Dispatchers::PrepareIvar
5
- module_function
5
+ module Dry
6
+ module Initializer
7
+ module Dispatchers
8
+ module PrepareIvar
9
+ module_function
6
10
 
7
- def call(target:, **options)
8
- ivar = "@#{target}".delete('?').to_sym
11
+ def call(target:, **options)
12
+ ivar = "@#{target}".delete("?").to_sym
9
13
 
10
- { target: target, ivar: ivar, **options }
14
+ {target: target, ivar: ivar, **options}
15
+ end
16
+ end
17
+ end
11
18
  end
12
19
  end
@@ -1,13 +1,20 @@
1
- #
1
+ # frozen_string_literal: true
2
+
2
3
  # Defines whether an argument is optional
3
4
  #
4
- module Dry::Initializer::Dispatchers::PrepareOptional
5
- module_function
5
+ module Dry
6
+ module Initializer
7
+ module Dispatchers
8
+ module PrepareOptional
9
+ module_function
6
10
 
7
- def call(optional: nil, default: nil, required: nil, **options)
8
- optional ||= default
9
- optional &&= !required
11
+ def call(optional: nil, default: nil, required: nil, **options)
12
+ optional ||= default
13
+ optional &&= !required
10
14
 
11
- { optional: !!optional, default: default, **options }
15
+ {optional: !!optional, default: default, **options}
16
+ end
17
+ end
18
+ end
12
19
  end
13
20
  end
@@ -1,30 +1,37 @@
1
- #
1
+ # frozen_string_literal: true
2
+
2
3
  # Checks the reader privacy
3
4
  #
4
- module Dry::Initializer::Dispatchers::PrepareReader
5
- extend self
5
+ module Dry
6
+ module Initializer
7
+ module Dispatchers
8
+ module PrepareReader
9
+ extend self
6
10
 
7
- def call(target: nil, reader: :public, **options)
8
- reader = case reader.to_s
9
- when 'false', '' then nil
10
- when 'true' then :public
11
- when 'public', 'private', 'protected' then reader.to_sym
12
- else invalid_reader!(target, reader)
13
- end
11
+ def call(target: nil, reader: :public, **options)
12
+ reader = case reader.to_s
13
+ when "false", "" then nil
14
+ when "true" then :public
15
+ when "public", "private", "protected" then reader.to_sym
16
+ else invalid_reader!(target, reader)
17
+ end
14
18
 
15
- { target: target, reader: reader, **options }
16
- end
19
+ {target: target, reader: reader, **options}
20
+ end
17
21
 
18
- private
22
+ private
19
23
 
20
- def invalid_reader!(target, _reader)
21
- raise ArgumentError, <<~MESSAGE
22
- Invalid setting for the ##{target} reader's privacy.
23
- Use the one of the following values for the `:reader` option:
24
- - 'public' (true) for the public reader (default)
25
- - 'private' for the private reader
26
- - 'protected' for the protected reader
27
- - nil (false) if no reader should be defined
28
- MESSAGE
24
+ def invalid_reader!(target, _reader)
25
+ raise ArgumentError, <<~MESSAGE
26
+ Invalid setting for the ##{target} reader's privacy.
27
+ Use the one of the following values for the `:reader` option:
28
+ - 'public' (true) for the public reader (default)
29
+ - 'private' for the private reader
30
+ - 'protected' for the protected reader
31
+ - nil (false) if no reader should be defined
32
+ MESSAGE
33
+ end
34
+ end
35
+ end
29
36
  end
30
37
  end
@@ -1,4 +1,5 @@
1
- #
1
+ # frozen_string_literal: true
2
+
2
3
  # The dispatcher verifies a correctness of the source name
3
4
  # of param or option, taken as a `:source` option.
4
5
  #
@@ -19,10 +20,16 @@
19
20
  # foo.second # => 666
20
21
  # ```
21
22
  #
22
- module Dry::Initializer::Dispatchers::PrepareSource
23
- module_function
23
+ module Dry
24
+ module Initializer
25
+ module Dispatchers
26
+ module PrepareSource
27
+ module_function
24
28
 
25
- def call(source:, **options)
26
- { source: source.to_s.to_sym, **options }
29
+ def call(source:, **options)
30
+ {source: source.to_s.to_sym, **options}
31
+ end
32
+ end
33
+ end
27
34
  end
28
35
  end
@@ -1,44 +1,51 @@
1
- #
1
+ # frozen_string_literal: true
2
+
2
3
  # Prepares the target name of a parameter or an option.
3
4
  #
4
5
  # Unlike source, the target must satisfy requirements for Ruby variable names.
5
6
  # It also shouldn't be in conflict with names used by the gem.
6
7
  #
7
- module Dry::Initializer::Dispatchers::PrepareTarget
8
- extend self
9
-
10
- # List of variable names reserved by the gem
11
- RESERVED = %i[
12
- __dry_initializer_options__
13
- __dry_initializer_config__
14
- __dry_initializer_value__
15
- __dry_initializer_definition__
16
- __dry_initializer_initializer__
17
- ].freeze
18
-
19
- def call(source:, target: nil, as: nil, **options)
20
- target ||= as || source
21
- target = target.to_s.to_sym.downcase
22
-
23
- check_ruby_name!(target)
24
- check_reserved_names!(target)
25
-
26
- { source: source, target: target, **options }
27
- end
28
-
29
- private
30
-
31
- def check_ruby_name!(target)
32
- return if target[/\A[[:alpha:]_][[:alnum:]_]*\??\z/u]
33
-
34
- raise ArgumentError,
35
- "The name `#{target}` is not allowed for Ruby methods"
36
- end
37
-
38
- def check_reserved_names!(target)
39
- return unless RESERVED.include?(target)
40
-
41
- raise ArgumentError,
42
- "The method name `#{target}` is reserved by the dry-initializer gem"
8
+ module Dry
9
+ module Initializer
10
+ module Dispatchers
11
+ module PrepareTarget
12
+ extend self
13
+
14
+ # List of variable names reserved by the gem
15
+ RESERVED = %i[
16
+ __dry_initializer_options__
17
+ __dry_initializer_config__
18
+ __dry_initializer_value__
19
+ __dry_initializer_definition__
20
+ __dry_initializer_initializer__
21
+ ].freeze
22
+
23
+ def call(source:, target: nil, as: nil, **options)
24
+ target ||= as || source
25
+ target = target.to_s.to_sym.downcase
26
+
27
+ check_ruby_name!(target)
28
+ check_reserved_names!(target)
29
+
30
+ {source: source, target: target, **options}
31
+ end
32
+
33
+ private
34
+
35
+ def check_ruby_name!(target)
36
+ return if target[/\A[[:alpha:]_][[:alnum:]_]*\??\z/u]
37
+
38
+ raise ArgumentError,
39
+ "The name `#{target}` is not allowed for Ruby methods"
40
+ end
41
+
42
+ def check_reserved_names!(target)
43
+ return unless RESERVED.include?(target)
44
+
45
+ raise ArgumentError,
46
+ "The method name `#{target}` is reserved by the dry-initializer gem"
47
+ end
48
+ end
49
+ end
43
50
  end
44
51
  end
@@ -1,22 +1,33 @@
1
- #
1
+ # frozen_string_literal: true
2
+
2
3
  # Looks at the `:type` option and counts how many nested arrays
3
4
  # it contains around either nil or a callable value.
4
5
  #
5
6
  # The counted number is preserved in the `:wrap` virtual option
6
7
  # used by the [WrapType] dispatcher.
7
8
  #
8
- module Dry::Initializer::Dispatchers::UnwrapType
9
- extend self
9
+ module Dry
10
+ module Initializer
11
+ module Dispatchers
12
+ module UnwrapType
13
+ extend self
10
14
 
11
- def call(type: nil, wrap: 0, **options)
12
- type, wrap = unwrap(type, 0)
15
+ def call(type: nil, wrap: 0, **options)
16
+ type, count = unwrap(type, wrap)
13
17
 
14
- { type: type, wrap: wrap, **options }
15
- end
18
+ {type: type, wrap: count, **options}
19
+ end
16
20
 
17
- private
21
+ private
18
22
 
19
- def unwrap(type, count)
20
- type.is_a?(Array) ? unwrap(type.first, count + 1) : [type, count]
23
+ def unwrap(type, count)
24
+ if type.is_a?(::Array)
25
+ unwrap(type.first, count + 1)
26
+ else
27
+ [type, count]
28
+ end
29
+ end
30
+ end
31
+ end
21
32
  end
22
33
  end
@@ -1,28 +1,35 @@
1
- #
1
+ # frozen_string_literal: true
2
+
2
3
  # Takes `:type` and `:wrap` to construct the final value coercer
3
4
  #
4
- module Dry::Initializer::Dispatchers::WrapType
5
- extend self
5
+ module Dry
6
+ module Initializer
7
+ module Dispatchers
8
+ module WrapType
9
+ extend self
6
10
 
7
- def call(type: nil, wrap: 0, **options)
8
- { type: wrapped_type(type, wrap), **options }
9
- end
11
+ def call(type: nil, wrap: 0, **options)
12
+ {type: wrapped_type(type, wrap), **options}
13
+ end
10
14
 
11
- private
15
+ private
12
16
 
13
- def wrapped_type(type, count)
14
- return type if count.zero?
17
+ def wrapped_type(type, count)
18
+ return type if count.zero?
15
19
 
16
- ->(value) { wrap_value(value, count, type) }
17
- end
20
+ ->(value) { wrap_value(value, count, type) }
21
+ end
18
22
 
19
- def wrap_value(value, count, type)
20
- if count.zero?
21
- type ? type.call(value) : value
22
- else
23
- return [wrap_value(value, count - 1, type)] unless value.is_a?(Array)
23
+ def wrap_value(value, count, type)
24
+ if count.zero?
25
+ type ? type.call(value) : value
26
+ else
27
+ return [wrap_value(value, count - 1, type)] unless value.is_a?(Array)
24
28
 
25
- value.map { |item| wrap_value(item, count - 1, type) }
29
+ value.map { |item| wrap_value(item, count - 1, type) }
30
+ end
31
+ end
32
+ end
26
33
  end
27
34
  end
28
35
  end
@@ -1,4 +1,5 @@
1
- #
1
+ # frozen_string_literal: true
2
+
2
3
  # The module is responsible for __normalizing__ arguments
3
4
  # of `.param` and `.option`.
4
5
  #
@@ -60,53 +61,57 @@
60
61
  # param :id, integer: true
61
62
  # end
62
63
  #
63
- module Dry::Initializer::Dispatchers
64
- extend self
64
+ module Dry
65
+ module Initializer
66
+ module Dispatchers
67
+ extend self
65
68
 
66
- # @!attribute [rw] null Defines a value to be set to unassigned attributes
67
- # @return [Object]
68
- attr_accessor :null
69
+ # @!attribute [rw] null Defines a value to be set to unassigned attributes
70
+ # @return [Object]
71
+ attr_accessor :null
69
72
 
70
- #
71
- # Registers a new dispatcher
72
- #
73
- # @param [#call] dispatcher
74
- # @return [self] itself
75
- #
76
- def <<(dispatcher)
77
- @pipeline = [dispatcher] + pipeline
78
- self
79
- end
73
+ #
74
+ # Registers a new dispatcher
75
+ #
76
+ # @param [#call] dispatcher
77
+ # @return [self] itself
78
+ #
79
+ def <<(dispatcher)
80
+ @pipeline = [dispatcher] + pipeline
81
+ self
82
+ end
80
83
 
81
- #
82
- # Normalizes the source set of options
83
- #
84
- # @param [Hash<Symbol, Object>] options
85
- # @return [Hash<Symbol, Objct>] normalized set of options
86
- #
87
- def call(**options)
88
- options = { null: null, **options }
89
- pipeline.reduce(options) { |opts, dispatcher| dispatcher.call(**opts) }
90
- end
84
+ #
85
+ # Normalizes the source set of options
86
+ #
87
+ # @param [Hash<Symbol, Object>] options
88
+ # @return [Hash<Symbol, Objct>] normalized set of options
89
+ #
90
+ def call(**options)
91
+ options = {null: null, **options}
92
+ pipeline.reduce(options) { |opts, dispatcher| dispatcher.call(**opts) }
93
+ end
91
94
 
92
- private
95
+ private
93
96
 
94
- require_relative 'dispatchers/build_nested_type'
95
- require_relative 'dispatchers/check_type'
96
- require_relative 'dispatchers/prepare_default'
97
- require_relative 'dispatchers/prepare_ivar'
98
- require_relative 'dispatchers/prepare_optional'
99
- require_relative 'dispatchers/prepare_reader'
100
- require_relative 'dispatchers/prepare_source'
101
- require_relative 'dispatchers/prepare_target'
102
- require_relative 'dispatchers/unwrap_type'
103
- require_relative 'dispatchers/wrap_type'
97
+ require_relative "dispatchers/build_nested_type"
98
+ require_relative "dispatchers/check_type"
99
+ require_relative "dispatchers/prepare_default"
100
+ require_relative "dispatchers/prepare_ivar"
101
+ require_relative "dispatchers/prepare_optional"
102
+ require_relative "dispatchers/prepare_reader"
103
+ require_relative "dispatchers/prepare_source"
104
+ require_relative "dispatchers/prepare_target"
105
+ require_relative "dispatchers/unwrap_type"
106
+ require_relative "dispatchers/wrap_type"
104
107
 
105
- def pipeline
106
- @pipeline ||= [
107
- PrepareSource, PrepareTarget, PrepareIvar, PrepareReader,
108
- PrepareDefault, PrepareOptional,
109
- UnwrapType, CheckType, BuildNestedType, WrapType
110
- ]
108
+ def pipeline
109
+ @pipeline ||= [
110
+ PrepareSource, PrepareTarget, PrepareIvar, PrepareReader,
111
+ PrepareDefault, PrepareOptional,
112
+ UnwrapType, CheckType, BuildNestedType, WrapType
113
+ ]
114
+ end
115
+ end
111
116
  end
112
117
  end