dry-initializer 2.4.0 → 3.0.3

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