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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.rubocop.yml +4 -61
- data/.travis.yml +18 -11
- data/CHANGELOG.md +108 -43
- data/Gemfile +1 -0
- data/Rakefile +6 -0
- data/benchmarks/options.rb +4 -4
- data/benchmarks/params.rb +1 -1
- data/benchmarks/profiler.rb +28 -0
- data/benchmarks/with_types.rb +5 -9
- data/benchmarks/with_types_and_defaults.rb +2 -4
- data/benchmarks/without_options.rb +3 -3
- data/dry-initializer.gemspec +1 -1
- data/lib/dry/initializer/attribute.rb +92 -0
- data/lib/dry/initializer/builder.rb +76 -72
- data/lib/dry/initializer/exceptions/default_value_error.rb +8 -0
- data/lib/dry/initializer/exceptions/params_order_error.rb +8 -0
- data/lib/dry/initializer/exceptions/type_constraint_error.rb +7 -0
- data/lib/dry/initializer/option.rb +62 -0
- data/lib/dry/initializer/param.rb +54 -0
- data/lib/dry/initializer.rb +77 -13
- data/spec/enhancement_spec.rb +18 -0
- data/spec/missed_default_spec.rb +2 -2
- data/spec/optional_spec.rb +16 -6
- data/spec/options_var_spec.rb +39 -0
- data/spec/repetitive_definitions_spec.rb +38 -18
- data/spec/several_assignments_spec.rb +41 -0
- data/spec/type_constraint_spec.rb +6 -5
- metadata +15 -20
- data/lib/dry/initializer/errors/default_value_error.rb +0 -6
- data/lib/dry/initializer/errors/order_error.rb +0 -7
- data/lib/dry/initializer/errors/plugin_error.rb +0 -6
- data/lib/dry/initializer/errors/redefinition_error.rb +0 -5
- data/lib/dry/initializer/errors/type_constraint_error.rb +0 -5
- data/lib/dry/initializer/errors.rb +0 -10
- data/lib/dry/initializer/mixin.rb +0 -77
- data/lib/dry/initializer/plugins/base.rb +0 -47
- data/lib/dry/initializer/plugins/default_proc.rb +0 -28
- data/lib/dry/initializer/plugins/signature.rb +0 -28
- data/lib/dry/initializer/plugins/type_constraint.rb +0 -21
- data/lib/dry/initializer/plugins/variable_setter.rb +0 -30
- data/lib/dry/initializer/plugins.rb +0 -10
- data/lib/dry/initializer/signature.rb +0 -61
- data/spec/plugin_registry_spec.rb +0 -45
- 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
|
data/lib/dry/initializer.rb
CHANGED
@@ -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/
|
8
|
-
require_relative "initializer/
|
9
|
-
require_relative "initializer/
|
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
|
-
|
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
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
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
|
data/spec/missed_default_spec.rb
CHANGED
data/spec/optional_spec.rb
CHANGED
@@ -9,22 +9,27 @@ describe "optional value" do
|
|
9
9
|
end
|
10
10
|
end
|
11
11
|
|
12
|
-
it "
|
12
|
+
it "quacks like nil" do
|
13
13
|
subject = Test::Foo.new(1)
|
14
14
|
|
15
|
-
expect(subject.
|
16
|
-
|
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
|
-
|
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 "
|
14
|
-
expect
|
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
|
-
|
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 "
|
30
|
-
expect
|
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
|
-
|
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 "
|
46
|
-
expect
|
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
|