dry-initializer 2.4.0 → 3.0.3

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 (92) hide show
  1. checksums.yaml +5 -5
  2. data/.codeclimate.yml +10 -21
  3. data/.github/ISSUE_TEMPLATE/----please-don-t-ask-for-support-via-issues.md +10 -0
  4. data/.github/ISSUE_TEMPLATE/---bug-report.md +30 -0
  5. data/.github/ISSUE_TEMPLATE/---feature-request.md +18 -0
  6. data/.github/workflows/custom_ci.yml +58 -0
  7. data/.github/workflows/docsite.yml +34 -0
  8. data/.github/workflows/sync_configs.yml +56 -0
  9. data/.gitignore +2 -0
  10. data/.rspec +1 -1
  11. data/.rubocop.yml +76 -25
  12. data/CHANGELOG.md +150 -14
  13. data/CODE_OF_CONDUCT.md +13 -0
  14. data/CONTRIBUTING.md +29 -0
  15. data/Gemfile +25 -18
  16. data/Gemfile.devtools +16 -0
  17. data/Guardfile +3 -3
  18. data/LICENSE +20 -0
  19. data/README.md +17 -79
  20. data/Rakefile +4 -4
  21. data/benchmarks/compare_several_defaults.rb +27 -27
  22. data/benchmarks/plain_options.rb +14 -14
  23. data/benchmarks/plain_params.rb +22 -22
  24. data/benchmarks/with_coercion.rb +14 -14
  25. data/benchmarks/with_defaults.rb +17 -17
  26. data/benchmarks/with_defaults_and_coercion.rb +14 -14
  27. data/bin/.gitkeep +0 -0
  28. data/docsite/source/attributes.html.md +106 -0
  29. data/docsite/source/container-version.html.md +39 -0
  30. data/docsite/source/index.html.md +43 -0
  31. data/docsite/source/inheritance.html.md +43 -0
  32. data/docsite/source/optionals-and-defaults.html.md +130 -0
  33. data/docsite/source/options-tolerance.html.md +27 -0
  34. data/docsite/source/params-and-options.html.md +74 -0
  35. data/docsite/source/rails-support.html.md +101 -0
  36. data/docsite/source/readers.html.md +43 -0
  37. data/docsite/source/skip-undefined.html.md +59 -0
  38. data/docsite/source/type-constraints.html.md +160 -0
  39. data/dry-initializer.gemspec +13 -13
  40. data/lib/dry-initializer.rb +1 -1
  41. data/lib/dry/initializer.rb +17 -16
  42. data/lib/dry/initializer/builders.rb +2 -2
  43. data/lib/dry/initializer/builders/attribute.rb +16 -11
  44. data/lib/dry/initializer/builders/initializer.rb +9 -13
  45. data/lib/dry/initializer/builders/reader.rb +4 -2
  46. data/lib/dry/initializer/builders/signature.rb +3 -3
  47. data/lib/dry/initializer/config.rb +25 -12
  48. data/lib/dry/initializer/definition.rb +20 -71
  49. data/lib/dry/initializer/dispatchers.rb +101 -33
  50. data/lib/dry/initializer/dispatchers/build_nested_type.rb +59 -0
  51. data/lib/dry/initializer/dispatchers/check_type.rb +43 -0
  52. data/lib/dry/initializer/dispatchers/prepare_default.rb +40 -0
  53. data/lib/dry/initializer/dispatchers/prepare_ivar.rb +12 -0
  54. data/lib/dry/initializer/dispatchers/prepare_optional.rb +13 -0
  55. data/lib/dry/initializer/dispatchers/prepare_reader.rb +30 -0
  56. data/lib/dry/initializer/dispatchers/prepare_source.rb +28 -0
  57. data/lib/dry/initializer/dispatchers/prepare_target.rb +44 -0
  58. data/lib/dry/initializer/dispatchers/unwrap_type.rb +22 -0
  59. data/lib/dry/initializer/dispatchers/wrap_type.rb +28 -0
  60. data/lib/dry/initializer/mixin.rb +4 -4
  61. data/lib/dry/initializer/mixin/root.rb +1 -0
  62. data/lib/dry/initializer/struct.rb +39 -0
  63. data/lib/dry/initializer/undefined.rb +2 -0
  64. data/lib/dry/initializer/version.rb +5 -0
  65. data/lib/tasks/benchmark.rake +13 -13
  66. data/lib/tasks/profile.rake +16 -16
  67. data/project.yml +2 -0
  68. data/spec/attributes_spec.rb +7 -7
  69. data/spec/coercion_of_nil_spec.rb +25 -0
  70. data/spec/custom_dispatchers_spec.rb +6 -6
  71. data/spec/custom_initializer_spec.rb +2 -2
  72. data/spec/default_values_spec.rb +9 -9
  73. data/spec/definition_spec.rb +16 -12
  74. data/spec/invalid_default_spec.rb +2 -2
  75. data/spec/list_type_spec.rb +32 -0
  76. data/spec/missed_default_spec.rb +2 -2
  77. data/spec/nested_type_spec.rb +48 -0
  78. data/spec/optional_spec.rb +16 -16
  79. data/spec/options_tolerance_spec.rb +2 -2
  80. data/spec/public_attributes_utility_spec.rb +5 -5
  81. data/spec/reader_spec.rb +13 -13
  82. data/spec/repetitive_definitions_spec.rb +9 -9
  83. data/spec/several_assignments_spec.rb +9 -9
  84. data/spec/spec_helper.rb +6 -3
  85. data/spec/subclassing_spec.rb +5 -5
  86. data/spec/support/coverage.rb +7 -0
  87. data/spec/support/warnings.rb +7 -0
  88. data/spec/type_argument_spec.rb +15 -15
  89. data/spec/type_constraint_spec.rb +46 -28
  90. data/spec/value_coercion_via_dry_types_spec.rb +8 -8
  91. metadata +51 -34
  92. data/.travis.yml +0 -24
@@ -1,20 +1,20 @@
1
+ require File.expand_path('lib/dry/initializer/version', __dir__)
2
+
1
3
  Gem::Specification.new do |gem|
2
- gem.name = "dry-initializer"
3
- gem.version = "2.4.0"
4
- gem.author = ["Vladimir Kochnev (marshall-lee)", "Andrew Kozin (nepalez)"]
5
- gem.email = "andrew.kozin@gmail.com"
6
- gem.homepage = "https://github.com/dryrb/dry-initializer"
7
- gem.summary = "DSL for declaring params and options of the initializer"
8
- gem.license = "MIT"
4
+ gem.name = 'dry-initializer'
5
+ gem.version = Dry::Initializer::VERSION
6
+ gem.author = ['Vladimir Kochnev (marshall-lee)', 'Andrew Kozin (nepalez)']
7
+ gem.email = 'andrew.kozin@gmail.com'
8
+ gem.homepage = 'https://github.com/dry-rb/dry-initializer'
9
+ gem.summary = 'DSL for declaring params and options of the initializer'
10
+ gem.license = 'MIT'
9
11
 
10
12
  gem.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
11
13
  gem.test_files = gem.files.grep(/^spec/)
12
- gem.extra_rdoc_files = Dir["README.md", "LICENSE", "CHANGELOG.md"]
14
+ gem.extra_rdoc_files = Dir['README.md', 'LICENSE', 'CHANGELOG.md']
13
15
 
14
- gem.required_ruby_version = ">= 2.3"
16
+ gem.required_ruby_version = '>= 2.3'
15
17
 
16
- gem.add_development_dependency "rspec", "~> 3.0"
17
- gem.add_development_dependency "rake", "> 10"
18
- gem.add_development_dependency "dry-types", "> 0.5.1"
19
- gem.add_development_dependency "rubocop", "~> 0.42"
18
+ gem.add_development_dependency 'rspec', '~> 3.0'
19
+ gem.add_development_dependency 'rake', '> 10'
20
20
  end
@@ -1 +1 @@
1
- require_relative "dry/initializer"
1
+ require_relative 'dry/initializer'
@@ -1,4 +1,4 @@
1
- require "set"
1
+ require 'set'
2
2
 
3
3
  # Namespace for gems in a dry-rb community
4
4
  module Dry
@@ -6,15 +6,13 @@ module Dry
6
6
  # DSL for declaring params and options of class initializers
7
7
  #
8
8
  module Initializer
9
- # Singleton for unassigned values
10
- UNDEFINED = Object.new.freeze
11
-
12
- require_relative "initializer/dsl"
13
- require_relative "initializer/definition"
14
- require_relative "initializer/builders"
15
- require_relative "initializer/config"
16
- require_relative "initializer/mixin"
17
- require_relative "initializer/dispatchers"
9
+ require_relative 'initializer/undefined'
10
+ require_relative 'initializer/dsl'
11
+ require_relative 'initializer/definition'
12
+ require_relative 'initializer/builders'
13
+ require_relative 'initializer/config'
14
+ require_relative 'initializer/mixin'
15
+ require_relative 'initializer/dispatchers'
18
16
 
19
17
  # Adds methods [.[]] and [.define]
20
18
  extend DSL
@@ -27,24 +25,25 @@ module Dry
27
25
 
28
26
  # Adds or redefines a parameter of [#dry_initializer]
29
27
  # @param [Symbol] name
30
- # @param [#call, nil] coercer (nil)
31
- # @option opts [#call] :type
28
+ # @param [#call, nil] type (nil)
32
29
  # @option opts [Proc] :default
33
30
  # @option opts [Boolean] :optional
34
31
  # @option opts [Symbol] :as
35
32
  # @option opts [true, false, :protected, :public, :private] :reader
33
+ # @yield block with nested definition
36
34
  # @return [self] itself
37
- def param(name, type = nil, **opts)
38
- dry_initializer.param(name, type, Dispatchers[opts])
35
+ def param(name, type = nil, **opts, &block)
36
+ dry_initializer.param(name, type, **opts, &block)
39
37
  self
40
38
  end
41
39
 
42
40
  # Adds or redefines an option of [#dry_initializer]
43
41
  # @param (see #param)
44
42
  # @option (see #param)
43
+ # @yield (see #param)
45
44
  # @return (see #param)
46
- def option(name, type = nil, **opts)
47
- dry_initializer.option(name, type, Dispatchers[opts])
45
+ def option(name, type = nil, **opts, &block)
46
+ dry_initializer.option(name, type, **opts, &block)
48
47
  self
49
48
  end
50
49
 
@@ -56,5 +55,7 @@ module Dry
56
55
  klass.send(:instance_variable_set, :@dry_initializer, config)
57
56
  dry_initializer.children << config
58
57
  end
58
+
59
+ require_relative 'initializer/struct'
59
60
  end
60
61
  end
@@ -1,7 +1,7 @@
1
1
  module Dry::Initializer
2
2
  # @private
3
3
  module Builders
4
- require_relative "builders/reader"
5
- require_relative "builders/initializer"
4
+ require_relative 'builders/reader'
5
+ require_relative 'builders/initializer'
6
6
  end
7
7
  end
@@ -20,17 +20,17 @@ module Dry::Initializer::Builders
20
20
  @default = definition.default
21
21
  @source = definition.source
22
22
  @ivar = definition.ivar
23
- @null = definition.null ? "Dry::Initializer::UNDEFINED" : "nil"
24
- @opts = "__dry_initializer_options__"
25
- @congif = "__dry_initializer_config__"
26
- @item = "__dry_initializer_definition__"
27
- @val = @option ? "__dry_initializer_value__" : @source
23
+ @null = definition.null ? 'Dry::Initializer::UNDEFINED' : 'nil'
24
+ @opts = '__dry_initializer_options__'
25
+ @congif = '__dry_initializer_config__'
26
+ @item = '__dry_initializer_definition__'
27
+ @val = @option ? '__dry_initializer_value__' : @source
28
28
  end
29
29
  # rubocop: enable Metrics/MethodLength
30
30
 
31
31
  def lines
32
32
  [
33
- "",
33
+ '',
34
34
  definition_line,
35
35
  reader_line,
36
36
  default_line,
@@ -41,6 +41,7 @@ module Dry::Initializer::Builders
41
41
 
42
42
  def reader_line
43
43
  return unless @option
44
+
44
45
  @optional ? optional_reader : required_reader
45
46
  end
46
47
 
@@ -55,27 +56,31 @@ module Dry::Initializer::Builders
55
56
 
56
57
  def definition_line
57
58
  return unless @type || @default
59
+
58
60
  "#{@item} = __dry_initializer_config__.definitions[:'#{@source}']"
59
61
  end
60
62
 
61
63
  def default_line
62
64
  return unless @default
63
- "#{@val} = instance_exec(&#{@item}.default) if #{@val} == #{@null}"
65
+
66
+ "#{@val} = instance_exec(&#{@item}.default) if #{@null} == #{@val}"
64
67
  end
65
68
 
66
69
  def coercion_line
67
70
  return unless @type
71
+
68
72
  arity = @type.is_a?(Proc) ? @type.arity : @type.method(:call).arity
69
- if arity.abs == 1
70
- "#{@val} = #{@item}.type.call(#{@val}) unless #{@val} == #{@null}"
73
+
74
+ if arity.abs == 1 || Dry::Types::Type.equal?(@type)
75
+ "#{@val} = #{@item}.type.call(#{@val}) unless #{@null} == #{@val}"
71
76
  else
72
- "#{@val} = #{@item}.type.call(#{@val}, self) unless #{@val} == #{@null}"
77
+ "#{@val} = #{@item}.type.call(#{@val}, self) unless #{@null} == #{@val}"
73
78
  end
74
79
  end
75
80
 
76
81
  def assignment_line
77
82
  "#{@ivar} = #{@val}" \
78
- " unless #{@val} == #{@null} && instance_variable_defined?(:#{@ivar})"
83
+ " unless #{@null} == #{@val} && instance_variable_defined?(:#{@ivar})"
79
84
  end
80
85
  end
81
86
  end
@@ -1,8 +1,8 @@
1
1
  module Dry::Initializer::Builders
2
2
  # @private
3
3
  class Initializer
4
- require_relative "signature"
5
- require_relative "attribute"
4
+ require_relative 'signature'
5
+ require_relative 'attribute'
6
6
 
7
7
  def self.[](config)
8
8
  new(config).call
@@ -30,8 +30,8 @@ module Dry::Initializer::Builders
30
30
  end
31
31
 
32
32
  def undef_line
33
- "undef :__dry_initializer_initialize__" \
34
- " if private_method_defined? :__dry_initializer_initialize__"
33
+ 'undef :__dry_initializer_initialize__' \
34
+ ' if private_method_defined? :__dry_initializer_initialize__'
35
35
  end
36
36
 
37
37
  def define_line
@@ -40,22 +40,18 @@ module Dry::Initializer::Builders
40
40
 
41
41
  def params_lines
42
42
  @definitions.reject(&:option)
43
- .flat_map { |item| Attribute[item] }
44
- .map { |line| " " << line }
43
+ .flat_map { |item| Attribute[item] }
44
+ .map { |line| ' ' << line }
45
45
  end
46
46
 
47
47
  def options_lines
48
48
  @definitions.select(&:option)
49
- .flat_map { |item| Attribute[item] }
50
- .map { |line| " " << line }
49
+ .flat_map { |item| Attribute[item] }
50
+ .map { |line| ' ' << line }
51
51
  end
52
52
 
53
53
  def end_line
54
- "end"
55
- end
56
-
57
- def private_line
58
- "private :__dry_initializer_initialize__"
54
+ 'end'
59
55
  end
60
56
  end
61
57
  end
@@ -30,16 +30,18 @@ module Dry::Initializer::Builders
30
30
 
31
31
  def attribute_line
32
32
  return unless @reader
33
+
33
34
  "attr_reader :#{@target}" unless @null
34
35
  end
35
36
 
36
37
  def method_lines
37
38
  return unless @reader
38
39
  return unless @null
40
+
39
41
  [
40
42
  "def #{@target}",
41
- " #{@ivar} unless #{@ivar} == Dry::Initializer::UNDEFINED",
42
- "end"
43
+ " #{@ivar} unless Dry::Initializer::UNDEFINED == #{@ivar}",
44
+ 'end'
43
45
  ]
44
46
  end
45
47
 
@@ -6,7 +6,7 @@ module Dry::Initializer::Builders
6
6
  end
7
7
 
8
8
  def call
9
- [*required_params, *optional_params, "*", options].compact.join(", ")
9
+ [*required_params, *optional_params, '*', options].compact.join(', ')
10
10
  end
11
11
 
12
12
  private
@@ -14,7 +14,7 @@ module Dry::Initializer::Builders
14
14
  def initialize(config)
15
15
  @config = config
16
16
  @options = config.options.any?
17
- @null = config.null ? "Dry::Initializer::UNDEFINED" : "nil"
17
+ @null = config.null ? 'Dry::Initializer::UNDEFINED' : 'nil'
18
18
  end
19
19
 
20
20
  def required_params
@@ -26,7 +26,7 @@ module Dry::Initializer::Builders
26
26
  end
27
27
 
28
28
  def options
29
- "**__dry_initializer_options__" if @options
29
+ '**__dry_initializer_options__' if @options
30
30
  end
31
31
  end
32
32
  end
@@ -51,15 +51,14 @@ module Dry::Initializer
51
51
 
52
52
  # Adds or redefines a parameter
53
53
  # @param [Symbol] name
54
- # @param [#call, nil] coercer (nil)
55
- # @option opts [#call] :type
54
+ # @param [#call, nil] type (nil)
56
55
  # @option opts [Proc] :default
57
56
  # @option opts [Boolean] :optional
58
57
  # @option opts [Symbol] :as
59
58
  # @option opts [true, false, :protected, :public, :private] :reader
60
59
  # @return [self] itself
61
- def param(name, type = nil, **opts)
62
- add_definition(false, name, type, opts)
60
+ def param(name, type = nil, **opts, &block)
61
+ add_definition(false, name, type, block, **opts)
63
62
  end
64
63
 
65
64
  # Adds or redefines an option of [#dry_initializer]
@@ -68,8 +67,8 @@ module Dry::Initializer
68
67
  # @option (see #param)
69
68
  # @return (see #param)
70
69
  #
71
- def option(name, type = nil, **opts)
72
- add_definition(true, name, type, opts)
70
+ def option(name, type = nil, **opts, &block)
71
+ add_definition(true, name, type, block, **opts)
73
72
  end
74
73
 
75
74
  # The hash of public attributes for an instance of the [#extended_class]
@@ -79,8 +78,9 @@ module Dry::Initializer
79
78
  definitions.values.each_with_object({}) do |item, obj|
80
79
  key = item.target
81
80
  next unless instance.respond_to? key
81
+
82
82
  val = instance.send(key)
83
- obj[key] = val unless val == null
83
+ obj[key] = val unless null == val
84
84
  end
85
85
  end
86
86
 
@@ -91,7 +91,7 @@ module Dry::Initializer
91
91
  definitions.values.each_with_object({}) do |item, obj|
92
92
  key = item.target
93
93
  val = instance.send(:instance_variable_get, item.ivar)
94
- obj[key] = val unless val == null
94
+ obj[key] = val unless null == val
95
95
  end
96
96
  end
97
97
 
@@ -115,7 +115,7 @@ module Dry::Initializer
115
115
  # @return [String]
116
116
  def inch
117
117
  line = Builders::Signature[self]
118
- line = line.gsub("__dry_initializer_options__", "options")
118
+ line = line.gsub('__dry_initializer_options__', 'options')
119
119
  lines = ["@!method initialize(#{line})"]
120
120
  lines += ["Initializes an instance of #{extended_class}"]
121
121
  lines += definitions.values.map(&:inch)
@@ -134,13 +134,25 @@ module Dry::Initializer
134
134
  finalize
135
135
  end
136
136
 
137
- def add_definition(option, name, type, opts)
138
- definition = Definition.new(option, null, name, type, Dispatchers[opts])
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)
139
151
  definitions[definition.source] = definition
140
152
  finalize
141
-
142
153
  mixin.class_eval definition.code
143
154
  end
155
+ # rubocop: enable Metrics/MethodLength
144
156
 
145
157
  def final_definitions
146
158
  parent_definitions = Hash(parent&.definitions&.dup)
@@ -152,6 +164,7 @@ module Dry::Initializer
152
164
  def check_type(previous, current)
153
165
  return current unless previous
154
166
  return current if previous.option == current.option
167
+
155
168
  raise SyntaxError,
156
169
  "cannot reload #{previous} of #{extended_class.superclass}" \
157
170
  " by #{current} of its subclass #{extended_class}"
@@ -14,17 +14,17 @@ module Dry::Initializer
14
14
 
15
15
  def options
16
16
  {
17
- as: target,
18
- type: type,
17
+ as: target,
18
+ type: type,
19
19
  optional: optional,
20
- default: default,
21
- reader: reader,
22
- desc: desc
20
+ default: default,
21
+ reader: reader,
22
+ desc: desc
23
23
  }.reject { |_, value| value.nil? }
24
24
  end
25
25
 
26
26
  def name
27
- @name ||= (option ? "option" : "parameter") << " '#{source}'"
27
+ @name ||= (option ? 'option' : 'parameter') << " '#{source}'"
28
28
  end
29
29
  alias to_s name
30
30
  alias to_str name
@@ -39,78 +39,27 @@ module Dry::Initializer
39
39
  end
40
40
 
41
41
  def inch
42
- @inch ||= (option ? "@option" : "@param ").tap do |text|
43
- text << " [Object]"
42
+ @inch ||= (option ? '@option' : '@param ').tap do |text|
43
+ text << ' [Object]'
44
44
  text << (option ? " :#{source}" : " #{source}")
45
- text << (optional ? " (optional)" : " (required)")
45
+ text << (optional ? ' (optional)' : ' (required)')
46
46
  text << " #{desc}" if desc
47
47
  end
48
48
  end
49
49
 
50
50
  private
51
51
 
52
- def initialize(option, null, source, coercer = nil, **options)
53
- @option = !!option
54
- @null = null
55
- @source = source.to_sym
56
- @target = check_target options.fetch(:as, source).to_sym
57
- @ivar = :"@#{target}"
58
- @type = check_type(coercer || options[:type])
59
- @reader = prepare_reader options.fetch(:reader, true)
60
- @default = check_default options[:default]
61
- @optional = options.fetch(:optional, @default)
62
- @desc = options[:desc]&.to_s&.capitalize
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]
63
63
  end
64
-
65
- def check_source(value)
66
- if RESERVED.include? value
67
- raise ArgumentError, "Name #{value} is reserved by dry-initializer gem"
68
- end
69
-
70
- unless option || value[ATTRIBUTE]
71
- raise ArgumentError, "Invalid parameter name :'#{value}'"
72
- end
73
-
74
- value
75
- end
76
-
77
- def check_target(value)
78
- return value if value[ATTRIBUTE]
79
- raise ArgumentError, "Invalid variable name :'#{value}'"
80
- end
81
-
82
- def check_type(value)
83
- return if value.nil?
84
- arity = value.arity if value.is_a? Proc
85
- arity ||= value.method(:call).arity if value.respond_to? :call
86
- return value if [1, 2].include? arity.to_i.abs
87
- raise TypeError,
88
- "type of #{inspect} should respond to #call with 1..2 arguments"
89
- end
90
-
91
- def check_default(value)
92
- return if value.nil?
93
- return value if value.is_a?(Proc) && value.arity < 1
94
- raise TypeError,
95
- "default value of #{inspect} should be a proc without params"
96
- end
97
-
98
- def prepare_reader(value)
99
- case value.to_s
100
- when "", "false" then false
101
- when "private" then :private
102
- when "protected" then :protected
103
- else :public
104
- end
105
- end
106
-
107
- ATTRIBUTE = /\A\w+\z/
108
- RESERVED = %i[
109
- __dry_initializer_options__
110
- __dry_initializer_config__
111
- __dry_initializer_value__
112
- __dry_initializer_definition__
113
- __dry_initializer_initializer__
114
- ].freeze
115
64
  end
116
65
  end