dry-initializer 1.4.1 → 2.0.0

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