dry-initializer 3.0.2 → 3.1.1

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 (89) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +260 -241
  3. data/LICENSE +1 -1
  4. data/README.md +18 -77
  5. data/dry-initializer.gemspec +34 -19
  6. data/lib/dry/initializer/builders/attribute.rb +78 -69
  7. data/lib/dry/initializer/builders/initializer.rb +56 -58
  8. data/lib/dry/initializer/builders/reader.rb +55 -47
  9. data/lib/dry/initializer/builders/signature.rb +29 -23
  10. data/lib/dry/initializer/builders.rb +9 -5
  11. data/lib/dry/initializer/config.rb +162 -158
  12. data/lib/dry/initializer/definition.rb +58 -54
  13. data/lib/dry/initializer/dispatchers/build_nested_type.rb +54 -40
  14. data/lib/dry/initializer/dispatchers/check_type.rb +45 -39
  15. data/lib/dry/initializer/dispatchers/prepare_default.rb +32 -25
  16. data/lib/dry/initializer/dispatchers/prepare_ivar.rb +13 -6
  17. data/lib/dry/initializer/dispatchers/prepare_optional.rb +14 -7
  18. data/lib/dry/initializer/dispatchers/prepare_reader.rb +29 -22
  19. data/lib/dry/initializer/dispatchers/prepare_source.rb +12 -5
  20. data/lib/dry/initializer/dispatchers/prepare_target.rb +44 -37
  21. data/lib/dry/initializer/dispatchers/unwrap_type.rb +21 -10
  22. data/lib/dry/initializer/dispatchers/wrap_type.rb +25 -17
  23. data/lib/dry/initializer/dispatchers.rb +48 -43
  24. data/lib/dry/initializer/dsl.rb +42 -34
  25. data/lib/dry/initializer/mixin/local.rb +19 -13
  26. data/lib/dry/initializer/mixin/root.rb +12 -7
  27. data/lib/dry/initializer/mixin.rb +17 -12
  28. data/lib/dry/initializer/struct.rb +34 -29
  29. data/lib/dry/initializer/undefined.rb +7 -1
  30. data/lib/dry/initializer/version.rb +7 -0
  31. data/lib/dry/initializer.rb +2 -0
  32. data/lib/dry-initializer.rb +2 -0
  33. data/lib/tasks/benchmark.rake +2 -0
  34. data/lib/tasks/profile.rake +4 -0
  35. metadata +25 -125
  36. data/.codeclimate.yml +0 -12
  37. data/.github/ISSUE_TEMPLATE/----please-don-t-ask-for-support-via-issues.md +0 -10
  38. data/.github/ISSUE_TEMPLATE/---bug-report.md +0 -34
  39. data/.github/ISSUE_TEMPLATE/---feature-request.md +0 -18
  40. data/.github/workflows/custom_ci.yml +0 -74
  41. data/.github/workflows/docsite.yml +0 -34
  42. data/.github/workflows/sync_configs.yml +0 -34
  43. data/.gitignore +0 -12
  44. data/.rspec +0 -4
  45. data/.rubocop.yml +0 -89
  46. data/CODE_OF_CONDUCT.md +0 -13
  47. data/CONTRIBUTING.md +0 -29
  48. data/Gemfile +0 -38
  49. data/Guardfile +0 -5
  50. data/LICENSE.txt +0 -21
  51. data/Rakefile +0 -8
  52. data/benchmarks/compare_several_defaults.rb +0 -82
  53. data/benchmarks/plain_options.rb +0 -63
  54. data/benchmarks/plain_params.rb +0 -84
  55. data/benchmarks/with_coercion.rb +0 -71
  56. data/benchmarks/with_defaults.rb +0 -66
  57. data/benchmarks/with_defaults_and_coercion.rb +0 -59
  58. data/docsite/source/attributes.html.md +0 -106
  59. data/docsite/source/container-version.html.md +0 -39
  60. data/docsite/source/index.html.md +0 -43
  61. data/docsite/source/inheritance.html.md +0 -43
  62. data/docsite/source/optionals-and-defaults.html.md +0 -130
  63. data/docsite/source/options-tolerance.html.md +0 -27
  64. data/docsite/source/params-and-options.html.md +0 -74
  65. data/docsite/source/rails-support.html.md +0 -101
  66. data/docsite/source/readers.html.md +0 -43
  67. data/docsite/source/skip-undefined.html.md +0 -59
  68. data/docsite/source/type-constraints.html.md +0 -160
  69. data/spec/attributes_spec.rb +0 -38
  70. data/spec/coercion_of_nil_spec.rb +0 -25
  71. data/spec/custom_dispatchers_spec.rb +0 -35
  72. data/spec/custom_initializer_spec.rb +0 -30
  73. data/spec/default_values_spec.rb +0 -83
  74. data/spec/definition_spec.rb +0 -111
  75. data/spec/invalid_default_spec.rb +0 -13
  76. data/spec/list_type_spec.rb +0 -32
  77. data/spec/missed_default_spec.rb +0 -14
  78. data/spec/nested_type_spec.rb +0 -48
  79. data/spec/optional_spec.rb +0 -71
  80. data/spec/options_tolerance_spec.rb +0 -11
  81. data/spec/public_attributes_utility_spec.rb +0 -22
  82. data/spec/reader_spec.rb +0 -87
  83. data/spec/repetitive_definitions_spec.rb +0 -69
  84. data/spec/several_assignments_spec.rb +0 -41
  85. data/spec/spec_helper.rb +0 -29
  86. data/spec/subclassing_spec.rb +0 -49
  87. data/spec/type_argument_spec.rb +0 -35
  88. data/spec/type_constraint_spec.rb +0 -78
  89. data/spec/value_coercion_via_dry_types_spec.rb +0 -29
@@ -1,59 +0,0 @@
1
- ---
2
- title: Skip Undefined
3
- layout: gem-single
4
- name: dry-initializer
5
- ---
6
-
7
- The initializer uses special constant `Dry::Initializer::UNDEFINED` to distinguish variables that are set to `nil` from those that are not set at all.
8
-
9
- When no value was provided, the constant is assigned to a variable, but hidden in a reader.
10
-
11
- ```ruby
12
- require 'dry-initializer'
13
-
14
- class User
15
- extend Dry::Initializer
16
- option :email, optional: true
17
- end
18
-
19
- user = User.new
20
-
21
- user.email
22
- # => nil
23
-
24
- user.instance_variable_get :@email
25
- # => Dry::Initializer::UNDEFINED
26
- ```
27
-
28
- This gives you full control of the real state of the attributes. However, all that checks cost about >30% of instantiation time, and make attribute readers 2 times slower.
29
-
30
- To avoid the overhead in cases you don't care about the differences between `nil` and undefined, you can use a light version of the module. Add `[undefined: false]` config to either `extend` or `include` line of code:
31
-
32
- ```ruby
33
- extend Dry::Initializer[undefined: false]
34
- ```
35
-
36
- ```ruby
37
- include Dry::Initializer[undefined: false] -> do
38
- # ...
39
- end
40
- ```
41
-
42
- This time you should expect `nil` every time no value was given to an optional attribute:
43
-
44
- ```ruby
45
- require 'dry-initializer'
46
-
47
- class User
48
- extend Dry::Initializer[undefined: false]
49
- option :email, optional: true
50
- end
51
-
52
- user = User.new
53
-
54
- user.email
55
- # => nil
56
-
57
- user.instance_variable_get :@email
58
- # => nil
59
- ```
@@ -1,160 +0,0 @@
1
- ---
2
- title: Type Constraints
3
- layout: gem-single
4
- name: dry-initializer
5
- ---
6
-
7
- ## Base Syntax
8
-
9
- Use `:type` key in a `param` or `option` declarations to add type coercer.
10
-
11
- ```ruby
12
- require 'dry-initializer'
13
-
14
- class User
15
- extend Dry::Initializer
16
- param :name, type: proc(&:to_s)
17
- end
18
-
19
- user = User.new :Andrew
20
- user.name # => "Andrew"
21
- ```
22
-
23
- Any object that responds to `#call` with 1 argument can be used as a type. Common examples are `proc(&:to_s)` for strings, `method(:Array)` (for arrays) or `Array.method(:wrap)` in Rails, `->(v) { !!v }` (for booleans), etc.
24
-
25
- ## Dry Types as coercers
26
-
27
- Another important example is the usage of `dry-types` as type constraints:
28
-
29
- ```ruby
30
- require 'dry-initializer'
31
- require 'dry-types'
32
-
33
- class User
34
- extend Dry::Initializer
35
- param :name, type: Dry::Types['strict.string']
36
- end
37
-
38
- user = User.new :Andrew # => #<TypeError ...>
39
- ```
40
-
41
- ## Positional Argument
42
-
43
- Instead of `:type` option you can send a constraint/coercer as the second argument:
44
-
45
- ```ruby
46
- require 'dry-initializer'
47
- require 'dry-types'
48
-
49
- class User
50
- extend Dry::Initializer
51
- param :name, Dry::Types['coercible.string']
52
- param :email, proc(&:to_s)
53
- end
54
- ```
55
-
56
- ## Array Types
57
-
58
- As mentioned above, the `:type` option takes a callable object... with one important exception.
59
-
60
- You can use arrays for values that should be wrapped to array:
61
-
62
- ```ruby
63
- class User
64
- extend Dry::Initializer
65
-
66
- option :name, proc(&:to_s)
67
- option :emails, [proc(&:to_s)]
68
- end
69
-
70
- user = User.new name: "joe", emails: :"joe@example.com"
71
- user.emails # => ["joe@example.com"]
72
-
73
- user = User.new name: "jane", emails: [:"jane@example.com", :"jane@example.org"]
74
- user.emails # => ["jane@example.com", "jane@example.org"]
75
- ```
76
-
77
- You can wrap the coercer into several arrays as well:
78
-
79
- ```ruby
80
- class User
81
- extend Dry::Initializer
82
-
83
- option :emails, [[proc(&:to_s)]]
84
- end
85
-
86
- user = User.new name: "joe", emails: "joe@example.com"
87
- user.emails # => [["joe@example.com"]]
88
- ```
89
-
90
- Eventually, you can use an empty array as a coercer. In that case we just wrap the source value(s) into array, not modifying the items:
91
-
92
- ```ruby
93
- class Article
94
- extend Dry::Initializer
95
-
96
- option :tags, []
97
- end
98
-
99
- article = Article.new(tags: 1)
100
- article.tags # => [1]
101
- ```
102
-
103
- ## Nested Options
104
-
105
- Sometimes you need to describe a structure with nested options. In this case you can use a block with `options` inside.
106
-
107
- ```ruby
108
- class User
109
- extend Dry::Initializer
110
-
111
- option :name, proc(&:to_s)
112
-
113
- option :emails, [] do
114
- option :address, proc(&:to_s)
115
- option :description, proc(&:to_s)
116
- end
117
- end
118
-
119
- user = User.new name: "joe",
120
- emails: { address: "joe@example.com", description: "Job email" }
121
-
122
- user.emails.class # => Array
123
- user.emails.first.class # => User::Emails
124
- user.emails.first.address # => "joe@example.com"
125
-
126
- user.emails.to_h # => [{ address: "joe@example.com", description: "Job email" }]
127
- ```
128
-
129
- Notice how we mixed array wrapper with a nested type.
130
-
131
- The only syntax restriction here is that you cannot use a positional `param` _inside_ the block.
132
-
133
- ## Back References
134
-
135
- Sometimes you need to refer back to the initialized instance. In this case use a second argument to explicitly give the instance to a coercer:
136
-
137
- ```ruby
138
- class Location < String
139
- attr_reader :parameter # refers back to its parameter
140
-
141
- def initialize(name, parameter)
142
- super(name)
143
- @parameter = parameter
144
- end
145
- end
146
-
147
- class Parameter
148
- extend Dry::Initializer
149
- param :name
150
- param :location, ->(value, param) { Location.new(value, param) }
151
- end
152
-
153
- offset = Parameter.new "offset", location: "query"
154
- offset.name # => "offset"
155
- offset.location # => "query"
156
- offset.location.parameter == offset # true
157
- ```
158
-
159
- [dry-types]: https://github.com/dry-rb/dry-types
160
- [dry-types-docs]: http://dry-rb.org/gems/dry-types/
@@ -1,38 +0,0 @@
1
- describe Dry::Initializer, "dry_initializer.attributes" do
2
- subject { instance.class.dry_initializer.attributes(instance) }
3
-
4
- context "when class has params" do
5
- before do
6
- class Test::Foo
7
- extend Dry::Initializer
8
- param :foo, proc(&:to_s)
9
- param :bar, default: proc { 1 }
10
- param :baz, optional: true
11
- end
12
- end
13
-
14
- let(:instance) { Test::Foo.new(:FOO) }
15
-
16
- it "collects coerced params with default values" do
17
- expect(subject).to eq({ foo: "FOO", bar: 1 })
18
- end
19
- end
20
-
21
- context "when class has options" do
22
- before do
23
- class Test::Foo
24
- extend Dry::Initializer
25
- option :foo
26
- option :bar, default: proc { 1 }
27
- option :baz, optional: true
28
- option :qux, proc(&:to_s), as: :quxx
29
- end
30
- end
31
-
32
- let(:instance) { Test::Foo.new(foo: :FOO, qux: :QUX) }
33
-
34
- it "collects coerced and renamed options with default values" do
35
- expect(subject).to eq({ foo: :FOO, bar: 1, quxx: "QUX" })
36
- end
37
- end
38
- end
@@ -1,25 +0,0 @@
1
- describe "coercion of nil" do
2
- before do
3
- class Test::Foo
4
- extend Dry::Initializer
5
- param :bar, proc(&:to_i)
6
- end
7
-
8
- class Test::Baz
9
- include Dry::Initializer.define -> do
10
- param :qux, proc(&:to_i)
11
- end
12
- end
13
- end
14
-
15
- let(:foo) { Test::Foo.new(nil) }
16
- let(:baz) { Test::Baz.new(nil) }
17
-
18
- it "works with extend syntax" do
19
- expect(foo.bar).to eq 0
20
- end
21
-
22
- it "works with include syntax" do
23
- expect(baz.qux).to eq 0
24
- end
25
- end
@@ -1,35 +0,0 @@
1
- describe "custom dispatchers" do
2
- subject { Test::Foo.new "123" }
3
-
4
- before do
5
- dispatcher = ->(op) { op[:integer] ? op.merge(type: proc(&:to_i)) : op }
6
- Dry::Initializer::Dispatchers << dispatcher
7
- end
8
-
9
- context "with extend syntax" do
10
- before do
11
- class Test::Foo
12
- extend Dry::Initializer
13
- param :id, integer: true
14
- end
15
- end
16
-
17
- it "adds syntax sugar" do
18
- expect(subject.id).to eq 123
19
- end
20
- end
21
-
22
- context "with include syntax" do
23
- before do
24
- class Test::Foo
25
- include Dry::Initializer.define -> do
26
- param :id, integer: true
27
- end
28
- end
29
- end
30
-
31
- it "adds syntax sugar" do
32
- expect(subject.id).to eq 123
33
- end
34
- end
35
- end
@@ -1,30 +0,0 @@
1
- describe "custom initializer" do
2
- before do
3
- class Test::Foo
4
- extend Dry::Initializer
5
-
6
- param :bar
7
-
8
- def initialize(*args)
9
- super
10
- @bar *= 3
11
- end
12
- end
13
-
14
- class Test::Baz < Test::Foo
15
- param :qux
16
-
17
- def initialize(*args)
18
- super
19
- @qux += 1
20
- end
21
- end
22
- end
23
-
24
- it "reloads the initializer" do
25
- baz = Test::Baz.new(5, 5)
26
-
27
- expect(baz.bar).to eq 15 # 5 * 3
28
- expect(baz.qux).to eq 6 # 5 + 1
29
- end
30
- end
@@ -1,83 +0,0 @@
1
- describe "default values" do
2
- before do
3
- class Test::Foo
4
- extend Dry::Initializer
5
-
6
- param :foo, default: proc { :FOO }
7
- param :bar, default: proc { :BAR }
8
- option :baz, default: -> { :BAZ }
9
- option :qux, default: proc { foo }
10
- option :mox, default: -> { default_mox }
11
-
12
- private
13
-
14
- def default_mox
15
- :MOX
16
- end
17
- end
18
- end
19
-
20
- it "instantiate arguments" do
21
- subject = Test::Foo.new(1, 2, baz: 3, qux: 4)
22
-
23
- expect(subject.foo).to eql 1
24
- expect(subject.bar).to eql 2
25
- expect(subject.baz).to eql 3
26
- expect(subject.qux).to eql 4
27
- end
28
-
29
- it "applies default values" do
30
- subject = Test::Foo.new
31
-
32
- expect(subject.foo).to eql :FOO
33
- expect(subject.bar).to eql :BAR
34
- expect(subject.baz).to eql :BAZ
35
- expect(subject.qux).to eql :FOO
36
- end
37
-
38
- it "applies default values partially" do
39
- subject = Test::Foo.new 1, baz: 3
40
-
41
- expect(subject.foo).to eql 1
42
- expect(subject.bar).to eql :BAR
43
- expect(subject.baz).to eql 3
44
- expect(subject.qux).to eql 1
45
- end
46
-
47
- it "applies default values from private methods" do
48
- subject = Test::Foo.new
49
- expect(subject.mox).to eql :MOX
50
- end
51
-
52
- describe "when the last param has a default and there are no options" do
53
- before do
54
- class Test::Bar
55
- extend Dry::Initializer
56
-
57
- param :foo
58
- param :bar, default: proc { {} }
59
- end
60
- end
61
-
62
- it "instantiates arguments" do
63
- subject = Test::Bar.new(1, 2)
64
-
65
- expect(subject.foo).to eql 1
66
- expect(subject.bar).to eql 2
67
- end
68
-
69
- it "applies default values" do
70
- subject = Test::Bar.new(1)
71
-
72
- expect(subject.foo).to eql 1
73
- expect(subject.bar).to eql({})
74
- end
75
-
76
- it "instantiates arguments also if the last is an hash" do
77
- subject = Test::Bar.new(1, { baz: 2, qux: 3 })
78
-
79
- expect(subject.foo).to eql 1
80
- expect(subject.bar).to eql({ baz: 2, qux: 3 })
81
- end
82
- end
83
- end
@@ -1,111 +0,0 @@
1
- describe "definition" do
2
- shared_examples :initializer do |in_context|
3
- subject { Test::Foo.new(1, bar: 2) }
4
-
5
- it "sets variables when defined via `#{in_context}`" do
6
- expect(subject.instance_variable_get(:@foo)).to eql 1
7
- expect(subject.instance_variable_get(:@bar)).to eql 2
8
- end
9
- end
10
-
11
- it_behaves_like :initializer, "extend Dry::Initializer" do
12
- before do
13
- class Test::Foo
14
- extend Dry::Initializer
15
- param :foo
16
- option :bar
17
- end
18
- end
19
-
20
- it "preservers definition params" do
21
- params = Test::Foo.dry_initializer.params.map do |definition|
22
- [definition.source, definition.options]
23
- end
24
-
25
- expect(params).to eq [
26
- [:foo, { as: :foo, reader: :public, optional: false }]
27
- ]
28
- end
29
-
30
- it "preservers definition options" do
31
- options = Test::Foo.dry_initializer.options.map do |definition|
32
- [definition.source, definition.options]
33
- end
34
-
35
- expect(options).to eq [
36
- [:bar, { as: :bar, reader: :public, optional: false }]
37
- ]
38
- end
39
- end
40
-
41
- it_behaves_like :initializer, "extend Dry::Initializer" do
42
- before do
43
- class Test::Foo
44
- extend Dry::Initializer
45
- param :foo
46
- option :bar
47
- end
48
- end
49
- end
50
-
51
- it_behaves_like :initializer, "extend Dry::Initializer[undefined: false]" do
52
- before do
53
- class Test::Foo
54
- extend Dry::Initializer[undefined: false]
55
- param :foo
56
- option :bar
57
- end
58
- end
59
- end
60
-
61
- it_behaves_like :initializer, "include Dry::Initializer with block" do
62
- before do
63
- class Test::Foo
64
- include(
65
- Dry::Initializer.define do
66
- param :foo
67
- option :bar
68
- end
69
- )
70
- end
71
- end
72
- end
73
-
74
- it_behaves_like :initializer, "include Dry::Initializer with lambda" do
75
- before do
76
- class Test::Foo
77
- include Dry::Initializer.define -> do
78
- param :foo
79
- option :bar
80
- end
81
- end
82
- end
83
- end
84
-
85
- it_behaves_like :initializer, "include Dry::Initializer[undefined: false]" do
86
- before do
87
- class Test::Foo
88
- include(
89
- Dry::Initializer[undefined: false].define do
90
- param :foo
91
- option :bar
92
- end
93
- )
94
- end
95
- end
96
- end
97
-
98
- # @deprecated
99
- it_behaves_like :initializer, "include Dry::Initializer::Mixin" do
100
- before do
101
- class Test::Foo
102
- include(
103
- Dry::Initializer::Mixin.define do
104
- param :foo
105
- option :bar
106
- end
107
- )
108
- end
109
- end
110
- end
111
- end
@@ -1,13 +0,0 @@
1
- describe "invalid default value assignment" do
2
- subject do
3
- class Test::Foo
4
- extend Dry::Initializer
5
-
6
- param :foo, default: 1
7
- end
8
- end
9
-
10
- it "raises TypeError" do
11
- expect { subject }.to raise_error TypeError
12
- end
13
- end
@@ -1,32 +0,0 @@
1
- require "dry-types"
2
-
3
- describe "list type argument" do
4
- before do
5
- class Test::Foo
6
- extend Dry::Initializer
7
- param :foo, [proc(&:to_s)]
8
- option :bar, [Dry::Types["strict.string"]]
9
- option :baz, []
10
- end
11
- end
12
-
13
- context "with single items" do
14
- subject { Test::Foo.new(1, bar: "2", baz: { qux: :QUX }) }
15
-
16
- it "coerces and wraps them to arrays" do
17
- expect(subject.foo).to eq %w[1]
18
- expect(subject.bar).to eq %w[2]
19
- expect(subject.baz).to eq [{ qux: :QUX }]
20
- end
21
- end
22
-
23
- context "with arrays" do
24
- subject { Test::Foo.new([1], bar: %w[2], baz: [{ qux: :QUX }]) }
25
-
26
- it "coerces elements" do
27
- expect(subject.foo).to eq %w[1]
28
- expect(subject.bar).to eq %w[2]
29
- expect(subject.baz).to eq [{ qux: :QUX }]
30
- end
31
- end
32
- end
@@ -1,14 +0,0 @@
1
- describe "missed default values" do
2
- subject do
3
- class Test::Foo
4
- extend Dry::Initializer
5
-
6
- param :foo, default: proc { :FOO }
7
- param :bar, required: true
8
- end
9
- end
10
-
11
- it "raises SyntaxError" do
12
- expect { subject }.to raise_error SyntaxError, /bar/
13
- end
14
- end
@@ -1,48 +0,0 @@
1
- describe "nested type argument" do
2
- subject { Test::Xyz.new("bar" => { "baz" => 42 }) }
3
-
4
- context "with nested definition only" do
5
- before do
6
- class Test::Xyz
7
- extend Dry::Initializer
8
-
9
- param :foo, as: :x do
10
- option :bar, as: :y do
11
- option :baz, proc(&:to_s), as: :z
12
- option :qux, as: :w, optional: true
13
- end
14
- end
15
- end
16
- end
17
-
18
- it "builds the type" do
19
- expect(subject.x.y.z).to eq "42"
20
- end
21
-
22
- it "converts the nested type to hash" do
23
- expect(subject.x.to_h).to eq("y" => { "z" => "42" })
24
- end
25
- end
26
-
27
- context "with nested and wrapped definitions" do
28
- before do
29
- class Test::Xyz
30
- extend Dry::Initializer
31
-
32
- param :foo, [], as: :x do
33
- option :bar, as: :y do
34
- option :baz, proc(&:to_s), as: :z
35
- option :qux, as: :w, optional: true
36
- end
37
- end
38
- end
39
- end
40
-
41
- it "builds the type" do
42
- x = subject.x
43
- expect(x).to be_instance_of Array
44
-
45
- expect(x.first.y.z).to eq "42"
46
- end
47
- end
48
- end