dry-initializer 2.4.0 → 3.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.codeclimate.yml +10 -21
- data/.github/ISSUE_TEMPLATE/----please-don-t-ask-for-support-via-issues.md +10 -0
- data/.github/ISSUE_TEMPLATE/---bug-report.md +30 -0
- data/.github/ISSUE_TEMPLATE/---feature-request.md +18 -0
- data/.github/workflows/custom_ci.yml +58 -0
- data/.github/workflows/docsite.yml +34 -0
- data/.github/workflows/sync_configs.yml +56 -0
- data/.gitignore +2 -0
- data/.rspec +1 -1
- data/.rubocop.yml +76 -25
- data/CHANGELOG.md +150 -14
- data/CODE_OF_CONDUCT.md +13 -0
- data/CONTRIBUTING.md +29 -0
- data/Gemfile +25 -18
- data/Gemfile.devtools +16 -0
- data/Guardfile +3 -3
- data/LICENSE +20 -0
- data/README.md +17 -79
- data/Rakefile +4 -4
- data/benchmarks/compare_several_defaults.rb +27 -27
- data/benchmarks/plain_options.rb +14 -14
- data/benchmarks/plain_params.rb +22 -22
- data/benchmarks/with_coercion.rb +14 -14
- data/benchmarks/with_defaults.rb +17 -17
- data/benchmarks/with_defaults_and_coercion.rb +14 -14
- data/bin/.gitkeep +0 -0
- data/docsite/source/attributes.html.md +106 -0
- data/docsite/source/container-version.html.md +39 -0
- data/docsite/source/index.html.md +43 -0
- data/docsite/source/inheritance.html.md +43 -0
- data/docsite/source/optionals-and-defaults.html.md +130 -0
- data/docsite/source/options-tolerance.html.md +27 -0
- data/docsite/source/params-and-options.html.md +74 -0
- data/docsite/source/rails-support.html.md +101 -0
- data/docsite/source/readers.html.md +43 -0
- data/docsite/source/skip-undefined.html.md +59 -0
- data/docsite/source/type-constraints.html.md +160 -0
- data/dry-initializer.gemspec +13 -13
- data/lib/dry-initializer.rb +1 -1
- data/lib/dry/initializer.rb +17 -16
- data/lib/dry/initializer/builders.rb +2 -2
- data/lib/dry/initializer/builders/attribute.rb +16 -11
- data/lib/dry/initializer/builders/initializer.rb +9 -13
- data/lib/dry/initializer/builders/reader.rb +4 -2
- data/lib/dry/initializer/builders/signature.rb +3 -3
- data/lib/dry/initializer/config.rb +25 -12
- data/lib/dry/initializer/definition.rb +20 -71
- data/lib/dry/initializer/dispatchers.rb +101 -33
- data/lib/dry/initializer/dispatchers/build_nested_type.rb +59 -0
- data/lib/dry/initializer/dispatchers/check_type.rb +43 -0
- data/lib/dry/initializer/dispatchers/prepare_default.rb +40 -0
- data/lib/dry/initializer/dispatchers/prepare_ivar.rb +12 -0
- data/lib/dry/initializer/dispatchers/prepare_optional.rb +13 -0
- data/lib/dry/initializer/dispatchers/prepare_reader.rb +30 -0
- data/lib/dry/initializer/dispatchers/prepare_source.rb +28 -0
- data/lib/dry/initializer/dispatchers/prepare_target.rb +44 -0
- data/lib/dry/initializer/dispatchers/unwrap_type.rb +22 -0
- data/lib/dry/initializer/dispatchers/wrap_type.rb +28 -0
- data/lib/dry/initializer/mixin.rb +4 -4
- data/lib/dry/initializer/mixin/root.rb +1 -0
- data/lib/dry/initializer/struct.rb +39 -0
- data/lib/dry/initializer/undefined.rb +2 -0
- data/lib/dry/initializer/version.rb +5 -0
- data/lib/tasks/benchmark.rake +13 -13
- data/lib/tasks/profile.rake +16 -16
- data/project.yml +2 -0
- data/spec/attributes_spec.rb +7 -7
- data/spec/coercion_of_nil_spec.rb +25 -0
- data/spec/custom_dispatchers_spec.rb +6 -6
- data/spec/custom_initializer_spec.rb +2 -2
- data/spec/default_values_spec.rb +9 -9
- data/spec/definition_spec.rb +16 -12
- data/spec/invalid_default_spec.rb +2 -2
- data/spec/list_type_spec.rb +32 -0
- data/spec/missed_default_spec.rb +2 -2
- data/spec/nested_type_spec.rb +48 -0
- data/spec/optional_spec.rb +16 -16
- data/spec/options_tolerance_spec.rb +2 -2
- data/spec/public_attributes_utility_spec.rb +5 -5
- data/spec/reader_spec.rb +13 -13
- data/spec/repetitive_definitions_spec.rb +9 -9
- data/spec/several_assignments_spec.rb +9 -9
- data/spec/spec_helper.rb +6 -3
- data/spec/subclassing_spec.rb +5 -5
- data/spec/support/coverage.rb +7 -0
- data/spec/support/warnings.rb +7 -0
- data/spec/type_argument_spec.rb +15 -15
- data/spec/type_constraint_spec.rb +46 -28
- data/spec/value_coercion_via_dry_types_spec.rb +8 -8
- metadata +51 -34
- data/.travis.yml +0 -24
@@ -0,0 +1,130 @@
|
|
1
|
+
---
|
2
|
+
title: Optional Attributes and Default Values
|
3
|
+
layout: gem-single
|
4
|
+
name: dry-initializer
|
5
|
+
---
|
6
|
+
|
7
|
+
By default both params and options are mandatory. Use `:default` key to make them optional:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
require 'dry-initializer'
|
11
|
+
|
12
|
+
class User
|
13
|
+
extend Dry::Initializer
|
14
|
+
|
15
|
+
param :name, default: proc { 'Unknown user' }
|
16
|
+
option :email, default: proc { 'unknown@example.com' }
|
17
|
+
option :phone, optional: true
|
18
|
+
end
|
19
|
+
|
20
|
+
user = User.new
|
21
|
+
user.name # => 'Unknown user'
|
22
|
+
user.email # => 'unknown@example.com'
|
23
|
+
user.phone # => Dry::Initializer::UNDEFINED
|
24
|
+
|
25
|
+
user = User.new 'Vladimir', email: 'vladimir@example.com', phone: '71234567788'
|
26
|
+
user.name # => 'Vladimir'
|
27
|
+
user.email # => 'vladimir@example.com'
|
28
|
+
user.phone # => '71234567788'
|
29
|
+
```
|
30
|
+
|
31
|
+
You cannot define required **parameter** after optional one. The following example raises `SyntaxError` exception:
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
require 'dry-initializer'
|
35
|
+
|
36
|
+
class User
|
37
|
+
extend Dry::Initializer
|
38
|
+
|
39
|
+
param :name, default: proc { 'Unknown name' }
|
40
|
+
param :email # => #<SyntaxError ...>
|
41
|
+
end
|
42
|
+
```
|
43
|
+
|
44
|
+
You should assign `nil` value explicitly. Otherwise an instance variable it will be left undefined. In both cases attribute reader method will return `nil`.
|
45
|
+
|
46
|
+
```ruby
|
47
|
+
require 'dry-initializer'
|
48
|
+
|
49
|
+
class User
|
50
|
+
extend Dry::Initializer
|
51
|
+
|
52
|
+
param :name
|
53
|
+
option :email, optional: true
|
54
|
+
end
|
55
|
+
|
56
|
+
user = User.new 'Andrew'
|
57
|
+
user.email # => nil
|
58
|
+
user.instance_variable_get :@email
|
59
|
+
# => Dry::Initializer::UNDEFINED
|
60
|
+
|
61
|
+
user = User.new 'Andrew', email: nil
|
62
|
+
user.email # => nil
|
63
|
+
user.instance_variable_get :@email
|
64
|
+
# => nil
|
65
|
+
```
|
66
|
+
|
67
|
+
You can also set `nil` as a default value:
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
require 'dry-initializer'
|
71
|
+
|
72
|
+
class User
|
73
|
+
extend Dry::Initializer
|
74
|
+
|
75
|
+
param :name
|
76
|
+
option :email, default: proc { nil }
|
77
|
+
end
|
78
|
+
|
79
|
+
user = User.new 'Andrew'
|
80
|
+
user.email # => nil
|
81
|
+
user.instance_variable_get :@email
|
82
|
+
# => nil
|
83
|
+
```
|
84
|
+
|
85
|
+
You **must** wrap default values into procs.
|
86
|
+
|
87
|
+
If you need to **assign** proc as a default value, wrap it to another one:
|
88
|
+
|
89
|
+
```ruby
|
90
|
+
require 'dry-initializer'
|
91
|
+
|
92
|
+
class User
|
93
|
+
extend Dry::Initializer
|
94
|
+
|
95
|
+
param :name_proc, default: proc { proc { 'Unknown user' } }
|
96
|
+
end
|
97
|
+
|
98
|
+
user = User.new
|
99
|
+
user.name_proc.call # => 'Unknown user'
|
100
|
+
```
|
101
|
+
|
102
|
+
Proc will be executed in a scope of new instance. You can refer to other arguments:
|
103
|
+
|
104
|
+
```ruby
|
105
|
+
require 'dry-initializer'
|
106
|
+
|
107
|
+
class User
|
108
|
+
extend Dry::Initializer
|
109
|
+
|
110
|
+
param :name
|
111
|
+
param :email, default: proc { "#{name.downcase}@example.com" }
|
112
|
+
end
|
113
|
+
|
114
|
+
user = User.new 'Andrew'
|
115
|
+
user.email # => 'andrew@example.com'
|
116
|
+
```
|
117
|
+
|
118
|
+
**Warning**: when using lambdas instead of procs, don't forget an argument, required by [instance_eval][instance_eval] (you can skip in in a proc).
|
119
|
+
|
120
|
+
```ruby
|
121
|
+
require 'dry-initializer'
|
122
|
+
|
123
|
+
class User
|
124
|
+
extend Dry::Initializer
|
125
|
+
|
126
|
+
param :name, default: -> (obj) { 'Dude' }
|
127
|
+
end
|
128
|
+
```
|
129
|
+
|
130
|
+
[instance_eval]: http://ruby-doc.org/core-2.2.0/BasicObject.html#method-i-instance_eval
|
@@ -0,0 +1,27 @@
|
|
1
|
+
---
|
2
|
+
title: Tolerance to Unknown Options
|
3
|
+
layout: gem-single
|
4
|
+
name: dry-initializer
|
5
|
+
---
|
6
|
+
|
7
|
+
By default the initializer is strict for params (positional arguments), expecting them to be defined explicitly.
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
require 'dry-initializer'
|
11
|
+
|
12
|
+
class User
|
13
|
+
extend Dry::Initializer
|
14
|
+
end
|
15
|
+
|
16
|
+
user = User.new 'Joe' # raises ArgumentError
|
17
|
+
```
|
18
|
+
|
19
|
+
At the same time it is tolerant to unknown options. All unknown options are accepted, but ignored:
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
# It accepts undefined options...
|
23
|
+
user = User.new name: 'Joe'
|
24
|
+
|
25
|
+
# ...but ignores them
|
26
|
+
user.respond_to? :name # => false
|
27
|
+
```
|
@@ -0,0 +1,74 @@
|
|
1
|
+
---
|
2
|
+
title: Params and Options
|
3
|
+
layout: gem-single
|
4
|
+
name: dry-initializer
|
5
|
+
---
|
6
|
+
|
7
|
+
Use `param` to define plain argument:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
require 'dry-initializer'
|
11
|
+
|
12
|
+
class User
|
13
|
+
extend Dry::Initializer
|
14
|
+
|
15
|
+
param :name
|
16
|
+
param :email
|
17
|
+
end
|
18
|
+
|
19
|
+
user = User.new 'Andrew', 'andrew@email.com'
|
20
|
+
user.name # => 'Andrew'
|
21
|
+
user.email # => 'andrew@email.com'
|
22
|
+
```
|
23
|
+
|
24
|
+
Use `option` to define named (hash) argument:
|
25
|
+
|
26
|
+
```ruby
|
27
|
+
require 'dry-initializer'
|
28
|
+
|
29
|
+
class User
|
30
|
+
extend Dry::Initializer
|
31
|
+
|
32
|
+
option :name
|
33
|
+
option :email
|
34
|
+
end
|
35
|
+
|
36
|
+
user = User.new email: 'andrew@email.com', name: 'Andrew'
|
37
|
+
user.name # => 'Andrew'
|
38
|
+
user.email # => 'andrew@email.com'
|
39
|
+
```
|
40
|
+
|
41
|
+
Options can be renamed using `:as` key:
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
require 'dry-initializer'
|
45
|
+
|
46
|
+
class User
|
47
|
+
extend Dry::Initializer
|
48
|
+
|
49
|
+
option :name, as: :username
|
50
|
+
end
|
51
|
+
|
52
|
+
user = User.new name: "Joe"
|
53
|
+
user.username # => "Joe"
|
54
|
+
user.instance_variable_get :@username # => "Joe"
|
55
|
+
user.instance_variable_get :@name # => nil
|
56
|
+
user.respond_to? :name # => false
|
57
|
+
```
|
58
|
+
|
59
|
+
You can also define several ways of initializing the same argument via different options:
|
60
|
+
|
61
|
+
```ruby
|
62
|
+
require 'dry-initializer'
|
63
|
+
|
64
|
+
class User
|
65
|
+
extend Dry::Initializer
|
66
|
+
|
67
|
+
option :phone
|
68
|
+
option :telephone, as: :phone
|
69
|
+
option :name, optional: true
|
70
|
+
end
|
71
|
+
|
72
|
+
User.new(phone: '1234567890').phone # => '1234567890'
|
73
|
+
User.new(telephone: '1234567890').phone # => '1234567890'
|
74
|
+
```
|
@@ -0,0 +1,101 @@
|
|
1
|
+
---
|
2
|
+
title: Rails Support
|
3
|
+
layout: gem-single
|
4
|
+
name: dry-initializer
|
5
|
+
---
|
6
|
+
|
7
|
+
Rails plugin is implemented in a separate [dry-initializer-rails](https://github.com/nepalez/dry-initializer-rails) gem.
|
8
|
+
|
9
|
+
It provides coercion of assigned values to corresponding ActiveRecord instances.
|
10
|
+
|
11
|
+
### Base Example
|
12
|
+
|
13
|
+
Add the `:model` setting to `param` or `option`:
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
require 'dry-initializer-rails'
|
17
|
+
|
18
|
+
class CreateOrder
|
19
|
+
extend Dry::Initializer
|
20
|
+
|
21
|
+
# Params and options
|
22
|
+
param :customer, model: 'Customer' # use either a name
|
23
|
+
option :product, model: Product # or a class
|
24
|
+
|
25
|
+
def call
|
26
|
+
Order.create customer: customer, product: product
|
27
|
+
end
|
28
|
+
end
|
29
|
+
```
|
30
|
+
|
31
|
+
Now you can assign values as pre-initialized model instances:
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
customer = Customer.find(1)
|
35
|
+
product = Product.find(2)
|
36
|
+
|
37
|
+
order = CreateOrder.new(customer, product: product).call
|
38
|
+
order.customer # => <Customer @id=1 ...>
|
39
|
+
order.product # => <Product @id=2 ...>
|
40
|
+
```
|
41
|
+
|
42
|
+
...or their ids:
|
43
|
+
|
44
|
+
```ruby
|
45
|
+
order = CreateOrder.new(1, product: 2).call
|
46
|
+
order.customer # => <Customer @id=1 ...>
|
47
|
+
order.product # => <Product @id=2 ...>
|
48
|
+
```
|
49
|
+
|
50
|
+
The instance is envoked using method `find_by(id: ...)`.
|
51
|
+
With wrong ids `nil` values are assigned to corresponding params and options:
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
order = CreateOrder.new(0, product: 0).call
|
55
|
+
order.customer # => nil
|
56
|
+
order.product # => nil
|
57
|
+
```
|
58
|
+
|
59
|
+
### Custom Keys
|
60
|
+
|
61
|
+
You can specify custom `key` for searching model instance:
|
62
|
+
|
63
|
+
```ruby
|
64
|
+
require 'dry-initializer-rails'
|
65
|
+
|
66
|
+
class CreateOrder
|
67
|
+
extend Dry::Initializer
|
68
|
+
|
69
|
+
param :customer, model: 'User', find_by: 'name'
|
70
|
+
option :product, model: Item, find_by: :name
|
71
|
+
end
|
72
|
+
```
|
73
|
+
|
74
|
+
This time you can send names (not ids) to the initializer:
|
75
|
+
|
76
|
+
```ruby
|
77
|
+
order = CreateOrder.new('Andrew', product: 'the_thing_no_123').call
|
78
|
+
|
79
|
+
order.customer # => <User @name='Andrew' ...>
|
80
|
+
order.product # => <Item @name='the_thing_no_123' ...>
|
81
|
+
```
|
82
|
+
|
83
|
+
### Container Syntax
|
84
|
+
|
85
|
+
If you prefer [container syntax](docs::container-version), extend plugin inside the block:
|
86
|
+
|
87
|
+
```ruby
|
88
|
+
require 'dry-initializer-rails'
|
89
|
+
|
90
|
+
class CreateOrder
|
91
|
+
include Dry::Initializer.define -> do
|
92
|
+
# ... params/options declarations
|
93
|
+
end
|
94
|
+
end
|
95
|
+
```
|
96
|
+
|
97
|
+
### Types vs Models
|
98
|
+
|
99
|
+
[Type constraints](docs::type-constraints) are checked before the coercion.
|
100
|
+
|
101
|
+
When mixing `:type` and `:model` settings for the same param/option, you should use [sum types](/gems/dry-types/1.2/sum) that accept both model instances and their attributes.
|
@@ -0,0 +1,43 @@
|
|
1
|
+
---
|
2
|
+
title: Readers
|
3
|
+
layout: gem-single
|
4
|
+
name: dry-initializer
|
5
|
+
---
|
6
|
+
|
7
|
+
By default public attribute reader is defined for every param and option.
|
8
|
+
|
9
|
+
You can define private or protected reader instead:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
require 'dry-initializer'
|
13
|
+
|
14
|
+
class User
|
15
|
+
extend Dry::Initializer
|
16
|
+
|
17
|
+
param :name, reader: :private # the same as adding `private :name`
|
18
|
+
param :email, reader: :protected # the same as adding `protected :email`
|
19
|
+
end
|
20
|
+
```
|
21
|
+
|
22
|
+
To skip any reader, use `reader: false`:
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
require 'dry-initializer'
|
26
|
+
|
27
|
+
class User
|
28
|
+
extend Dry::Initializer
|
29
|
+
|
30
|
+
param :name
|
31
|
+
param :email, reader: false
|
32
|
+
end
|
33
|
+
|
34
|
+
user = User.new 'Luke', 'luke@example.com'
|
35
|
+
user.name # => 'Luke'
|
36
|
+
|
37
|
+
user.email # => #<NoMethodError ...>
|
38
|
+
user.instance_variable_get :@email # => 'luke@example.com'
|
39
|
+
```
|
40
|
+
|
41
|
+
Notice that any other value except for `false`, `:protected` and `:private` provides a public reader.
|
42
|
+
|
43
|
+
No writers are defined. Define them using pure ruby `attr_writer` when necessary.
|
@@ -0,0 +1,59 @@
|
|
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
|
+
```
|
@@ -0,0 +1,160 @@
|
|
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/
|