dry-initializer 1.4.1 → 2.0.0

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 (59) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +8 -8
  3. data/.travis.yml +0 -3
  4. data/CHANGELOG.md +339 -196
  5. data/LICENSE.txt +1 -1
  6. data/README.md +3 -3
  7. data/Rakefile +2 -47
  8. data/benchmarks/{several_defaults.rb → compare_several_defaults.rb} +4 -4
  9. data/benchmarks/{without_options.rb → plain_options.rb} +20 -9
  10. data/benchmarks/{params.rb → plain_params.rb} +20 -9
  11. data/benchmarks/{with_types.rb → with_coercion.rb} +20 -9
  12. data/benchmarks/with_defaults.rb +19 -8
  13. data/benchmarks/{with_types_and_defaults.rb → with_defaults_and_coercion.rb} +21 -10
  14. data/dry-initializer.gemspec +3 -3
  15. data/lib/dry/initializer/builders/attribute.rb +76 -0
  16. data/lib/dry/initializer/builders/initializer.rb +61 -0
  17. data/lib/dry/initializer/builders/reader.rb +50 -0
  18. data/lib/dry/initializer/builders/signature.rb +32 -0
  19. data/lib/dry/initializer/builders.rb +7 -0
  20. data/lib/dry/initializer/config.rb +161 -0
  21. data/lib/dry/initializer/definition.rb +93 -0
  22. data/lib/dry/initializer/dsl.rb +43 -0
  23. data/lib/dry/initializer/mixin/local.rb +19 -0
  24. data/lib/dry/initializer/mixin/root.rb +10 -0
  25. data/lib/dry/initializer/mixin.rb +15 -0
  26. data/lib/dry/initializer.rb +45 -41
  27. data/lib/tasks/benchmark.rake +41 -0
  28. data/lib/tasks/profile.rake +78 -0
  29. data/spec/{options_var_spec.rb → attributes_spec.rb} +9 -9
  30. data/spec/custom_initializer_spec.rb +1 -1
  31. data/spec/default_values_spec.rb +6 -6
  32. data/spec/definition_spec.rb +21 -14
  33. data/spec/invalid_default_spec.rb +2 -2
  34. data/spec/missed_default_spec.rb +2 -2
  35. data/spec/optional_spec.rb +2 -2
  36. data/spec/options_tolerance_spec.rb +1 -1
  37. data/spec/public_attributes_utility_spec.rb +22 -0
  38. data/spec/reader_spec.rb +11 -11
  39. data/spec/repetitive_definitions_spec.rb +5 -5
  40. data/spec/several_assignments_spec.rb +1 -1
  41. data/spec/spec_helper.rb +5 -0
  42. data/spec/subclassing_spec.rb +7 -3
  43. data/spec/type_argument_spec.rb +1 -1
  44. data/spec/type_constraint_spec.rb +2 -2
  45. data/spec/value_coercion_via_dry_types_spec.rb +1 -1
  46. metadata +27 -27
  47. data/benchmarks/options.rb +0 -54
  48. data/benchmarks/params_vs_options.rb +0 -35
  49. data/benchmarks/profiler.rb +0 -28
  50. data/lib/dry/initializer/attribute.rb +0 -123
  51. data/lib/dry/initializer/builder.rb +0 -127
  52. data/lib/dry/initializer/class_dsl.rb +0 -37
  53. data/lib/dry/initializer/exceptions/default_value_error.rb +0 -8
  54. data/lib/dry/initializer/exceptions/params_order_error.rb +0 -8
  55. data/lib/dry/initializer/exceptions/type_constraint_error.rb +0 -7
  56. data/lib/dry/initializer/instance_dsl.rb +0 -15
  57. data/lib/dry/initializer/option.rb +0 -61
  58. data/lib/dry/initializer/param.rb +0 -52
  59. data/spec/gem_enhancement_spec.rb +0 -18
@@ -1,123 +0,0 @@
1
- module Dry::Initializer
2
- # Contains definitions for a single attribute, and builds its parts of mixin
3
- class Attribute
4
- class << self
5
- # Collection of additional dispatchers for method options
6
- #
7
- # @example Enhance the gem by adding :coercer alias for type
8
- # Dry::Initializer::Attribute.dispatchers << -> (string: nil, **op) do
9
- # op[:type] = proc(&:to_s) if string
10
- # op
11
- # end
12
- #
13
- # class User
14
- # extend Dry::Initializer
15
- # param :name, string: true # same as `type: proc(&:to_s)`
16
- # end
17
- #
18
- def dispatchers
19
- @@dispatchers ||= []
20
- end
21
-
22
- def new(source, coercer = nil, **options)
23
- options[:source] = source
24
- options[:target] = options.delete(:as) || source
25
- options[:type] ||= coercer
26
- params = dispatchers.inject(options) { |h, m| m.call(h) }
27
-
28
- super(params)
29
- end
30
-
31
- def param(*args)
32
- Param.new(*args)
33
- end
34
-
35
- def option(*args)
36
- Option.new(*args)
37
- end
38
- end
39
-
40
- attr_reader :source, :target, :coercer, :default, :optional, :reader
41
-
42
- def initialize(options)
43
- @source = options[:source]
44
- @target = options[:target]
45
- @coercer = options[:type]
46
- @default = options[:default]
47
- @optional = !!(options[:optional] || @default)
48
- @reader = options.fetch(:reader, :public)
49
- @undefined = options.fetch(:undefined, true)
50
- validate
51
- end
52
-
53
- def ==(other)
54
- source == other.source
55
- end
56
-
57
- def postsetter
58
- "@__options__[:#{target}] = @#{target}" \
59
- " unless @#{target} == #{undefined}"
60
- end
61
-
62
- def getter
63
- return unless reader
64
- command = %w(private protected).include?(reader.to_s) ? reader : :public
65
-
66
- <<-RUBY
67
- undef_method :#{target} if method_defined?(:#{target}) ||
68
- protected_method_defined?(:#{target}) ||
69
- private_method_defined?(:#{target})
70
- #{reader_definition}
71
- #{command} :#{target}
72
- RUBY
73
- end
74
-
75
- private
76
-
77
- def validate
78
- validate_target
79
- validate_default
80
- validate_coercer
81
- end
82
-
83
- def undefined
84
- @undefined ? "Dry::Initializer::UNDEFINED" : "nil"
85
- end
86
-
87
- def reader_definition
88
- if @undefined
89
- "def #{target}; @#{target} unless @#{target} == #{undefined}; end"
90
- else
91
- "attr_reader :#{target}"
92
- end
93
- end
94
-
95
- def validate_target
96
- return if target =~ /\A\w+\Z/
97
- fail ArgumentError.new("Invalid name '#{target}' for the target variable")
98
- end
99
-
100
- def validate_default
101
- return if default.nil? || default.is_a?(Proc)
102
- fail DefaultValueError.new(source, default)
103
- end
104
-
105
- def validate_coercer
106
- return if coercer.nil? || coercer.respond_to?(:call)
107
- fail TypeConstraintError.new(source, coercer)
108
- end
109
-
110
- def default_hash(type)
111
- default ? { :"#{type}_#{source}" => default } : {}
112
- end
113
-
114
- def coercer_hash(type)
115
- return {} unless coercer
116
-
117
- value = coercer unless @undefined
118
- value ||= proc { |v| v == Dry::Initializer::UNDEFINED ? v : coercer.(v) }
119
-
120
- { :"#{type}_#{source}" => value }
121
- end
122
- end
123
- end
@@ -1,127 +0,0 @@
1
- module Dry::Initializer
2
- class Builder
3
- def param(*args, **opts)
4
- @params = insert @params, Attribute.param(*args, **@config.merge(opts))
5
- validate_collections
6
- end
7
-
8
- def option(*args, **opts)
9
- @options = insert @options, Attribute.option(*args, **@config.merge(opts))
10
- validate_collections
11
- end
12
-
13
- def call(mixin)
14
- clear_method(mixin, :__defaults__)
15
- clear_method(mixin, :__coercers__)
16
- clear_method(mixin, :__initialize__)
17
-
18
- defaults = send(:defaults)
19
- coercers = send(:coercers)
20
-
21
- mixin.send(:define_method, :__defaults__) { defaults }
22
- mixin.send(:define_method, :__coercers__) { coercers }
23
- mixin.class_eval(code)
24
- end
25
-
26
- private
27
-
28
- def initialize(**config)
29
- @config = config
30
- @params = []
31
- @options = []
32
- end
33
-
34
- def insert(collection, new_item)
35
- index = collection.index(new_item) || collection.count
36
- collection.dup.tap { |list| list[index] = new_item }
37
- end
38
-
39
- def clear_method(mixin, name)
40
- mixin.send(:undef_method, name) if mixin.private_method_defined? name
41
- end
42
-
43
- def code
44
- <<-RUBY
45
- def __initialize__(#{initializer_signatures})
46
- @__options__ = {}
47
- #{initializer_presetters}
48
- #{initializer_setters}
49
- #{initializer_postsetters}
50
- end
51
- private :__initialize__
52
- private :__defaults__
53
- private :__coercers__
54
-
55
- #{getters}
56
- RUBY
57
- end
58
-
59
- def attributes
60
- @params + @options
61
- end
62
-
63
- def duplications
64
- attributes.group_by(&:target)
65
- .reject { |_, val| val.count == 1 }
66
- .keys
67
- end
68
-
69
- def initializer_signatures
70
- sig = @params.map(&:initializer_signature).compact.uniq
71
- sig << (sig.any? && @options.any? ? "**__options__" : "__options__ = {}")
72
- sig.join(", ")
73
- end
74
-
75
- def initializer_presetters
76
- dups = duplications
77
- attributes.map { |a| " #{a.presetter}" if dups.include? a.target }
78
- .compact.uniq.join("\n")
79
- end
80
-
81
- def initializer_setters
82
- dups = duplications
83
- attributes.map do |a|
84
- dups.include?(a.target) ? " #{a.safe_setter}" : " #{a.fast_setter}"
85
- end.compact.uniq.join("\n")
86
- end
87
-
88
- def initializer_postsetters
89
- attributes.map { |a| " #{a.postsetter}" }.compact.uniq.join("\n")
90
- end
91
-
92
- def defined_options
93
- if @options.any?
94
- keys = @options.map(&:target).join(" ")
95
- "__options__.select { |key| %i(#{keys}).include? key }"
96
- else
97
- "{}"
98
- end
99
- end
100
-
101
- def getters
102
- attributes.map(&:getter).compact.uniq.join("\n")
103
- end
104
-
105
- def defaults
106
- attributes.map(&:default_hash).reduce({}, :merge)
107
- end
108
-
109
- def coercers
110
- attributes.map(&:coercer_hash).reduce({}, :merge)
111
- end
112
-
113
- def validate_collections
114
- optional_param = nil
115
-
116
- @params.each do |param|
117
- if param.optional
118
- optional_param = param.source if param.optional
119
- elsif optional_param
120
- fail ParamsOrderError.new(param.source, optional_param)
121
- end
122
- end
123
-
124
- self
125
- end
126
- end
127
- end
@@ -1,37 +0,0 @@
1
- module Dry::Initializer
2
- module ClassDSL
3
- attr_reader :config
4
-
5
- def [](**settings)
6
- Module.new do
7
- extend Dry::Initializer::ClassDSL
8
- include Dry::Initializer
9
- @config = settings
10
- end
11
- end
12
-
13
- def define(fn = nil, &block)
14
- mixin = Module.new { include InstanceDSL }
15
- builder = Builder.new Hash(config)
16
- builder.instance_exec(&(fn || block))
17
- builder.call(mixin)
18
- mixin
19
- end
20
-
21
- private
22
-
23
- def extended(klass)
24
- super
25
- mixin = klass.send(:__initializer_mixin__)
26
- builder = klass.send(:__initializer_builder__, Hash(config))
27
- builder.call(mixin)
28
-
29
- klass.include(InstanceDSL) # defines #initialize
30
- klass.include(mixin) # defines #__initialize__ (to be redefined)
31
- end
32
-
33
- def mixin(fn = nil, &block)
34
- define(fn, &block)
35
- end
36
- end
37
- end
@@ -1,8 +0,0 @@
1
- module Dry::Initializer
2
- class DefaultValueError < TypeError
3
- def initialize(name, value)
4
- super "Cannot set #{value.inspect} directly as a default value" \
5
- " of the argument '#{name}'. Wrap it to either proc or lambda."
6
- end
7
- end
8
- end
@@ -1,8 +0,0 @@
1
- module Dry::Initializer
2
- class ParamsOrderError < RuntimeError
3
- def initialize(required, optional)
4
- super "Optional param '#{optional}'" \
5
- " should not preceed required '#{required}'"
6
- end
7
- end
8
- end
@@ -1,7 +0,0 @@
1
- module Dry::Initializer
2
- class TypeConstraintError < TypeError
3
- def initialize(name, type)
4
- super "#{type} constraint for argument '#{name}' doesn't respond to #call"
5
- end
6
- end
7
- end
@@ -1,15 +0,0 @@
1
- module Dry::Initializer
2
- module InstanceDSL
3
- private
4
-
5
- # The method is reloaded explicitly
6
- # in a class that extend [Dry::Initializer], or in its subclasses.
7
- def initialize(*args)
8
- __initialize__(*args)
9
- end
10
-
11
- # The method is redefined implicitly every time
12
- # a `param` or `option` is invoked.
13
- def __initialize__(*); end
14
- end
15
- end
@@ -1,61 +0,0 @@
1
- module Dry::Initializer
2
- class Option < Attribute
3
- # part of __initializer__ definition
4
- def initializer_signature
5
- "**__options__"
6
- end
7
-
8
- # parts of __initalizer__
9
- def presetter
10
- "@#{target} = #{undefined}" if dispensable? && @undefined
11
- end
12
-
13
- def safe_setter
14
- "@#{target} = #{safe_coerced}#{maybe_optional}"
15
- end
16
-
17
- def fast_setter
18
- return safe_setter unless dispensable?
19
- "@#{target} = __options__.key?(:'#{source}')" \
20
- " ? #{safe_coerced}" \
21
- " : #{undefined}"
22
- end
23
-
24
- # part of __defaults__
25
- def default_hash
26
- super :option
27
- end
28
-
29
- # part of __coercers__
30
- def coercer_hash
31
- super :option
32
- end
33
-
34
- private
35
-
36
- def dispensable?
37
- optional && !default
38
- end
39
-
40
- def maybe_optional
41
- " if __options__.key? :'#{source}'" if dispensable?
42
- end
43
-
44
- def safe_coerced
45
- return safe_default unless coercer
46
- "__coercers__[:'option_#{source}'].call(#{safe_default})"
47
- end
48
-
49
- def safe_default
50
- "__options__.fetch(:'#{source}')#{default_part}"
51
- end
52
-
53
- def default_part
54
- if default
55
- " { instance_exec(&__defaults__[:'option_#{source}']) }"
56
- elsif !optional
57
- " { raise ArgumentError, \"option :'#{source}' is required\" }"
58
- end
59
- end
60
- end
61
- end
@@ -1,52 +0,0 @@
1
- module Dry::Initializer
2
- class Param < Attribute
3
- # part of __initializer__ definition
4
- def initializer_signature
5
- optional ? "#{target} = #{undefined}" : target
6
- end
7
-
8
- # parts of __initalizer__
9
- def presetter; end
10
-
11
- def safe_setter
12
- "@#{target} = #{maybe_coerced}"
13
- end
14
-
15
- def fast_setter
16
- safe_setter
17
- end
18
-
19
- # part of __defaults__
20
- def default_hash
21
- super :param
22
- end
23
-
24
- # part of __coercers__
25
- def coercer_hash
26
- super :param
27
- end
28
-
29
- private
30
-
31
- def initialize(*args, **options)
32
- fail ArgumentError.new("Do not rename params") if options.key? :as
33
- super
34
- end
35
-
36
- def maybe_coerced
37
- return maybe_default unless coercer
38
- "__coercers__[:param_#{target}].call(#{maybe_default})"
39
- end
40
-
41
- def maybe_default
42
- "#{target}#{default_part}"
43
- end
44
-
45
- def default_part
46
- return unless default
47
- " == #{undefined} ?" \
48
- " instance_exec(&__defaults__[:param_#{target}]) :" \
49
- " #{target}"
50
- end
51
- end
52
- end
@@ -1,18 +0,0 @@
1
- describe "gem enhancement" do
2
- before do
3
- Dry::Initializer::Attribute.dispatchers << ->(string: false, **op) do
4
- op[:type] = proc(&:to_s) if string
5
- op
6
- end
7
-
8
- class Test::Foo
9
- extend Dry::Initializer
10
- param :bar, string: true
11
- end
12
- end
13
-
14
- it "works" do
15
- foo = Test::Foo.new(:BAZ)
16
- expect(foo.bar).to eq "BAZ"
17
- end
18
- end