dry-initializer 0.11.0 → 1.1.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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.rubocop.yml +4 -61
  4. data/.travis.yml +18 -11
  5. data/CHANGELOG.md +108 -43
  6. data/Gemfile +1 -0
  7. data/Rakefile +6 -0
  8. data/benchmarks/options.rb +4 -4
  9. data/benchmarks/params.rb +1 -1
  10. data/benchmarks/profiler.rb +28 -0
  11. data/benchmarks/with_types.rb +5 -9
  12. data/benchmarks/with_types_and_defaults.rb +2 -4
  13. data/benchmarks/without_options.rb +3 -3
  14. data/dry-initializer.gemspec +1 -1
  15. data/lib/dry/initializer/attribute.rb +92 -0
  16. data/lib/dry/initializer/builder.rb +76 -72
  17. data/lib/dry/initializer/exceptions/default_value_error.rb +8 -0
  18. data/lib/dry/initializer/exceptions/params_order_error.rb +8 -0
  19. data/lib/dry/initializer/exceptions/type_constraint_error.rb +7 -0
  20. data/lib/dry/initializer/option.rb +62 -0
  21. data/lib/dry/initializer/param.rb +54 -0
  22. data/lib/dry/initializer.rb +77 -13
  23. data/spec/enhancement_spec.rb +18 -0
  24. data/spec/missed_default_spec.rb +2 -2
  25. data/spec/optional_spec.rb +16 -6
  26. data/spec/options_var_spec.rb +39 -0
  27. data/spec/repetitive_definitions_spec.rb +38 -18
  28. data/spec/several_assignments_spec.rb +41 -0
  29. data/spec/type_constraint_spec.rb +6 -5
  30. metadata +15 -20
  31. data/lib/dry/initializer/errors/default_value_error.rb +0 -6
  32. data/lib/dry/initializer/errors/order_error.rb +0 -7
  33. data/lib/dry/initializer/errors/plugin_error.rb +0 -6
  34. data/lib/dry/initializer/errors/redefinition_error.rb +0 -5
  35. data/lib/dry/initializer/errors/type_constraint_error.rb +0 -5
  36. data/lib/dry/initializer/errors.rb +0 -10
  37. data/lib/dry/initializer/mixin.rb +0 -77
  38. data/lib/dry/initializer/plugins/base.rb +0 -47
  39. data/lib/dry/initializer/plugins/default_proc.rb +0 -28
  40. data/lib/dry/initializer/plugins/signature.rb +0 -28
  41. data/lib/dry/initializer/plugins/type_constraint.rb +0 -21
  42. data/lib/dry/initializer/plugins/variable_setter.rb +0 -30
  43. data/lib/dry/initializer/plugins.rb +0 -10
  44. data/lib/dry/initializer/signature.rb +0 -61
  45. data/spec/plugin_registry_spec.rb +0 -45
  46. data/spec/renaming_options_spec.rb +0 -20
@@ -0,0 +1,62 @@
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} = Dry::Initializer::UNDEFINED" if dispensable?
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}') ? #{safe_coerced} : " \
20
+ "Dry::Initializer::UNDEFINED"
21
+ end
22
+
23
+ # part of __defaults__
24
+ def default_hash
25
+ default ? { :"option_#{source}" => default } : {}
26
+ end
27
+
28
+ # part of __coercers__
29
+ def coercer_hash
30
+ return {} unless coercer
31
+ value = proc { |v| (v == Dry::Initializer::UNDEFINED) ? v : coercer.(v) }
32
+ { :"option_#{source}" => value }
33
+ end
34
+
35
+ private
36
+
37
+ def dispensable?
38
+ optional && !default
39
+ end
40
+
41
+ def maybe_optional
42
+ " if __options__.key? :'#{source}'" if dispensable?
43
+ end
44
+
45
+ def safe_coerced
46
+ return safe_default unless coercer
47
+ "__coercers__[:'option_#{source}'].call(#{safe_default})"
48
+ end
49
+
50
+ def safe_default
51
+ "__options__.fetch(:'#{source}')#{default_part}"
52
+ end
53
+
54
+ def default_part
55
+ if default
56
+ " { instance_eval(&__defaults__[:'option_#{source}']) }"
57
+ elsif !optional
58
+ " { raise ArgumentError, \"option :'#{source}' is required\" }"
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,54 @@
1
+ module Dry::Initializer
2
+ class Param < Attribute
3
+ # part of __initializer__ definition
4
+ def initializer_signature
5
+ optional ? "#{target} = Dry::Initializer::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
+ default ? { :"param_#{target}" => default } : {}
22
+ end
23
+
24
+ # part of __coercers__
25
+ def coercer_hash
26
+ return {} unless coercer
27
+ value = proc { |v| (v == Dry::Initializer::UNDEFINED) ? v : coercer.(v) }
28
+ { :"param_#{target}" => value }
29
+ end
30
+
31
+ private
32
+
33
+ def initialize(*args, **options)
34
+ fail ArgumentError.new("Do not rename params") if options.key? :as
35
+ super
36
+ end
37
+
38
+ def maybe_coerced
39
+ return maybe_default unless coercer
40
+ "__coercers__[:param_#{target}].call(#{maybe_default})"
41
+ end
42
+
43
+ def maybe_default
44
+ "#{target}#{default_part}"
45
+ end
46
+
47
+ def default_part
48
+ return unless default
49
+ " == Dry::Initializer::UNDEFINED ?" \
50
+ " instance_eval(&__defaults__[:param_#{target}]) :" \
51
+ " #{target}"
52
+ end
53
+ end
54
+ end
@@ -1,22 +1,86 @@
1
1
  module Dry
2
- # Declares arguments of the initializer (params and options)
3
- #
4
- # @api public
5
- #
6
2
  module Initializer
7
- require_relative "initializer/errors"
8
- require_relative "initializer/plugins"
9
- require_relative "initializer/signature"
3
+ require_relative "initializer/exceptions/default_value_error"
4
+ require_relative "initializer/exceptions/type_constraint_error"
5
+ require_relative "initializer/exceptions/params_order_error"
6
+
7
+ require_relative "initializer/attribute"
8
+ require_relative "initializer/param"
9
+ require_relative "initializer/option"
10
10
  require_relative "initializer/builder"
11
- require_relative "initializer/mixin"
12
11
 
13
- UNDEFINED = Object.new.freeze
12
+ # rubocop: disable Style/ConstantName
13
+ Mixin = self # for compatibility to versions below 0.12
14
+ # rubocop: enable Style/ConstantName
15
+
16
+ UNDEFINED = Object.new.tap do |obj|
17
+ obj.define_singleton_method(:inspect) { "Dry::Initializer::UNDEFINED" }
18
+ end.freeze
19
+
20
+ class << self
21
+ def extended(klass)
22
+ super
23
+ mixin = klass.send(:__initializer_mixin__)
24
+ builder = klass.send(:__initializer_builder__)
25
+
26
+ builder.call(mixin)
27
+ klass.include(mixin)
28
+ klass.send(:define_method, :initialize) do |*args|
29
+ __initialize__(*args)
30
+ end
31
+ end
32
+
33
+ def define(fn = nil, &block)
34
+ mixin = Module.new do
35
+ def initialize(*args)
36
+ __initialize__(*args)
37
+ end
38
+ end
14
39
 
15
- def self.define(proc = nil, &block)
16
- Module.new do |container|
17
- container.extend Mixin
18
- container.instance_exec(&(proc || block))
40
+ builder = Builder.new
41
+ builder.instance_exec(&(fn || block))
42
+ builder.call(mixin)
43
+ mixin
19
44
  end
45
+
46
+ def mixin(fn = nil, &block)
47
+ define(fn, &block)
48
+ end
49
+ end
50
+
51
+ def param(*args)
52
+ __initializer_builder__.param(*args).call(__initializer_mixin__)
53
+ end
54
+
55
+ def option(*args)
56
+ __initializer_builder__.option(*args).call(__initializer_mixin__)
57
+ end
58
+
59
+ private
60
+
61
+ def __initializer_mixin__
62
+ @__initializer_mixin__ ||= Module.new do
63
+ def initialize(*args)
64
+ __initialize__(*args)
65
+ end
66
+ end
67
+ end
68
+
69
+ def __initializer_builder__
70
+ @__initializer_builder__ ||= Dry::Initializer::Builder.new
71
+ end
72
+
73
+ def inherited(klass)
74
+ builder = @__initializer_builder__.dup
75
+ mixin = Module.new
76
+
77
+ klass.instance_variable_set :@__initializer_builder__, builder
78
+ klass.instance_variable_set :@__initializer_mixin__, mixin
79
+
80
+ builder.call(mixin)
81
+ klass.include mixin
82
+
83
+ super
20
84
  end
21
85
  end
22
86
  end
@@ -0,0 +1,18 @@
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
@@ -3,8 +3,8 @@ describe "missed default values" do
3
3
  class Test::Foo
4
4
  extend Dry::Initializer::Mixin
5
5
 
6
- param :foo, default: proc { :FOO }
7
- param :bar
6
+ param :foo, default: proc { :FOO }
7
+ param :bar, required: true
8
8
  end
9
9
  end
10
10
 
@@ -9,22 +9,27 @@ describe "optional value" do
9
9
  end
10
10
  end
11
11
 
12
- it "is left UNDEFINED by default" do
12
+ it "quacks like nil" do
13
13
  subject = Test::Foo.new(1)
14
14
 
15
- expect(subject.foo).to eq 1
16
- expect(subject.bar).to eq Dry::Initializer::UNDEFINED
15
+ expect(subject.bar).to eq nil
16
+ end
17
+
18
+ it "keeps info about been UNDEFINED" do
19
+ subject = Test::Foo.new(1)
20
+
21
+ expect(subject.instance_variable_get(:@bar))
22
+ .to eq Dry::Initializer::UNDEFINED
17
23
  end
18
24
 
19
25
  it "can be set explicitly" do
20
26
  subject = Test::Foo.new(1, "qux")
21
27
 
22
- expect(subject.foo).to eq 1
23
28
  expect(subject.bar).to eq "qux"
24
29
  end
25
30
  end
26
31
 
27
- context "when has default value" do
32
+ context "when has a default value" do
28
33
  before do
29
34
  class Test::Foo
30
35
  extend Dry::Initializer::Mixin
@@ -37,8 +42,13 @@ describe "optional value" do
37
42
  it "is takes default value" do
38
43
  subject = Test::Foo.new(1)
39
44
 
40
- expect(subject.foo).to eq 1
41
45
  expect(subject.bar).to eq "baz"
42
46
  end
47
+
48
+ it "can be set explicitly" do
49
+ subject = Test::Foo.new(1, "qux")
50
+
51
+ expect(subject.bar).to eq "qux"
52
+ end
43
53
  end
44
54
  end
@@ -0,0 +1,39 @@
1
+ describe "@__options__" do
2
+ context "when class has no options" do
3
+ before do
4
+ class Test::Foo
5
+ extend Dry::Initializer::Mixin
6
+ param :foo
7
+ end
8
+ end
9
+
10
+ it "is set to empty hash" do
11
+ subject = Test::Foo.new(1)
12
+
13
+ expect(subject.instance_variable_get(:@__options__)).to eq({})
14
+ end
15
+ end
16
+
17
+ context "when class has options" do
18
+ before do
19
+ class Test::Foo
20
+ extend Dry::Initializer::Mixin
21
+ param :foo
22
+ option :bar, optional: true
23
+ option :baz, optional: true
24
+ end
25
+ end
26
+
27
+ it "is set to empty hash if no options assigned" do
28
+ subject = Test::Foo.new(1)
29
+
30
+ expect(subject.instance_variable_get(:@__options__)).to eq({})
31
+ end
32
+
33
+ it "is set to hash of assigned options" do
34
+ subject = Test::Foo.new(1, baz: :QUX)
35
+
36
+ expect(subject.instance_variable_get(:@__options__)).to eq({ baz: :QUX })
37
+ end
38
+ end
39
+ end
@@ -1,49 +1,69 @@
1
1
  describe "repetitive definitions" do
2
+ subject { Test::Foo.new }
3
+
2
4
  context "of params" do
3
- subject do
5
+ before do
4
6
  class Test::Foo
5
7
  extend Dry::Initializer::Mixin
6
8
 
7
- param :foo
8
- param :bar
9
- param :foo
9
+ param :foo, default: proc { 0 }
10
+ param :bar, default: proc { 1 }
11
+ param :foo, default: proc { 2 }
10
12
  end
11
13
  end
12
14
 
13
- it "raise SyntaxError" do
14
- expect { subject }.to raise_error SyntaxError, /foo/
15
+ it "reloads the attribute" do
16
+ expect(subject.foo).to eq 2
15
17
  end
16
18
  end
17
19
 
18
20
  context "of options" do
19
- subject do
21
+ before do
20
22
  class Test::Foo
21
23
  extend Dry::Initializer::Mixin
22
24
 
23
- option :foo
24
- option :bar
25
- option :foo
25
+ option :foo, default: proc { 0 }
26
+ option :bar, default: proc { 1 }
27
+ option :foo, default: proc { 2 }
26
28
  end
27
29
  end
28
30
 
29
- it "raise SyntaxError" do
30
- expect { subject }.to raise_error SyntaxError, /foo/
31
+ it "reloads the attribute" do
32
+ expect(subject.foo).to eq 2
31
33
  end
32
34
  end
33
35
 
34
36
  context "of param and option" do
35
- subject do
37
+ before do
38
+ class Test::Foo
39
+ extend Dry::Initializer::Mixin
40
+
41
+ param :foo, default: proc { 0 }
42
+ option :bar, default: proc { 1 }
43
+ option :foo, default: proc { 2 }
44
+ end
45
+ end
46
+
47
+ it "reloads the attribute" do
48
+ expect(subject.foo).to eq 2
49
+ end
50
+ end
51
+
52
+ context "of optional param and option" do
53
+ before do
36
54
  class Test::Foo
37
55
  extend Dry::Initializer::Mixin
38
56
 
39
- param :foo
40
- option :bar
41
- option :foo
57
+ param :foo, optional: true
58
+ option :bar, optional: true
59
+ option :foo, optional: true
42
60
  end
43
61
  end
44
62
 
45
- it "raise SyntaxError" do
46
- expect { subject }.to raise_error SyntaxError, /foo/
63
+ it "allows various assignments" do
64
+ expect(Test::Foo.new(1).foo).to eq 1
65
+ expect(Test::Foo.new(foo: 2).foo).to eq 2
66
+ expect(Test::Foo.new(1, foo: 2).foo).to eq 2
47
67
  end
48
68
  end
49
69
  end
@@ -0,0 +1,41 @@
1
+ describe "attribute with several assignments" do
2
+ before do
3
+ class Test::Foo
4
+ extend Dry::Initializer::Mixin
5
+
6
+ option :bar, proc(&:to_s), optional: true
7
+ option :"some foo", as: :bar, optional: true
8
+ end
9
+ end
10
+
11
+ context "when not defined" do
12
+ subject { Test::Foo.new }
13
+
14
+ it "is left undefined" do
15
+ expect(subject.bar).to be_nil
16
+ expect(subject.instance_variable_get :@bar)
17
+ .to eq Dry::Initializer::UNDEFINED
18
+ end
19
+ end
20
+
21
+ context "when set directly" do
22
+ subject { Test::Foo.new bar: :BAZ }
23
+
24
+ it "sets the attribute" do
25
+ expect(subject.bar).to eq "BAZ"
26
+ end
27
+ end
28
+
29
+ context "when renamed" do
30
+ subject { Test::Foo.new "some foo": :BAZ }
31
+
32
+ it "renames the attribute" do
33
+ expect(subject.bar).to eq :BAZ
34
+ expect(subject).not_to respond_to :foo
35
+ end
36
+
37
+ it "renames the variable" do
38
+ expect(subject.instance_variable_get(:@bar)).to eq :BAZ
39
+ end
40
+ end
41
+ end