dry-struct 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/ISSUE_TEMPLATE/----please-don-t-ask-for-support-via-issues.md +10 -0
- data/.github/ISSUE_TEMPLATE/---bug-report.md +34 -0
- data/.github/ISSUE_TEMPLATE/---feature-request.md +18 -0
- data/.travis.yml +4 -8
- data/CHANGELOG.md +63 -40
- data/Gemfile +1 -0
- data/bin/console +1 -1
- data/docsite/source/index.html.md +101 -0
- data/docsite/source/nested-structs.html.md +49 -0
- data/docsite/source/recipes.html.md +143 -0
- data/lib/dry/struct.rb +20 -11
- data/lib/dry/struct/class_interface.rb +14 -14
- data/lib/dry/struct/hashify.rb +1 -5
- data/lib/dry/struct/value.rb +2 -2
- data/lib/dry/struct/version.rb +1 -1
- metadata +9 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9e2aaf63cecd9545e3b19c763209b1dc4d2a2c1dec53e2050cf02e5e9c29d97a
|
4
|
+
data.tar.gz: b7464679f32b3d861bd63b31a24926a8806158bef3d2b99fc5199aa8c7f0db78
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2003c10114e1aa03a19f21c68a1f3e3d684d2e4500cd22782c261ec935491668e56ce4916cd98ee1a9fbb64222dcf2f1061c9d6f90360bac351f0cd352c935a3
|
7
|
+
data.tar.gz: 45d7b302ba157d73ae9d5039675dc6153b8b68b0739626d3db799bd8255be8ef3a2528db6b370ca6f4fde7dec2cc597950b710b3f84bcbee3cc889d9b444f664
|
@@ -0,0 +1,34 @@
|
|
1
|
+
---
|
2
|
+
name: "\U0001F41B Bug report"
|
3
|
+
about: See CONTRIBUTING.md for more information
|
4
|
+
title: ''
|
5
|
+
labels: bug
|
6
|
+
assignees: ''
|
7
|
+
|
8
|
+
---
|
9
|
+
|
10
|
+
**Before you submit this: WE ONLY ACCEPT BUG REPORTS AND FEATURE REQUESTS**
|
11
|
+
|
12
|
+
For more information see [our contribution guidelines](https://github.com/rom-rb/rom/blob/master/CONTRIBUTING.md)
|
13
|
+
|
14
|
+
**Before you report**
|
15
|
+
|
16
|
+
:warning: If you have a problem related to a schema, please **report it under [dry-schema issues](https://github.com/dry-rb/dry-schema/issues/new?assignees=&labels=bug&template=---bug-report.md&title=)** instead.
|
17
|
+
|
18
|
+
**Describe the bug**
|
19
|
+
|
20
|
+
A clear and concise description of what the bug is.
|
21
|
+
|
22
|
+
**To Reproduce**
|
23
|
+
|
24
|
+
Provide detailed steps to reproduce, an executable script would be best.
|
25
|
+
|
26
|
+
**Expected behavior**
|
27
|
+
|
28
|
+
A clear and concise description of what you expected to happen.
|
29
|
+
|
30
|
+
**Your environment**
|
31
|
+
|
32
|
+
- Affects my production application: **YES/NO**
|
33
|
+
- Ruby version: ...
|
34
|
+
- OS: ...
|
@@ -0,0 +1,18 @@
|
|
1
|
+
---
|
2
|
+
name: "\U0001F6E0 Feature request"
|
3
|
+
about: See CONTRIBUTING.md for more information
|
4
|
+
title: ''
|
5
|
+
labels: feature
|
6
|
+
assignees: ''
|
7
|
+
|
8
|
+
---
|
9
|
+
|
10
|
+
Summary of what the feature is supposed to do.
|
11
|
+
|
12
|
+
## Examples
|
13
|
+
|
14
|
+
Code examples showing how the feature could be used.
|
15
|
+
|
16
|
+
## Resources
|
17
|
+
|
18
|
+
Additional information, like a link to the discussion forum thread where the feature was discussed etc.
|
data/.travis.yml
CHANGED
@@ -3,9 +3,9 @@ dist: trusty
|
|
3
3
|
sudo: required
|
4
4
|
bundler_args: --without benchmarks tools
|
5
5
|
script:
|
6
|
-
- bundle exec
|
6
|
+
- bundle exec rspec spec
|
7
7
|
after_success:
|
8
|
-
-
|
8
|
+
- "[ -d coverage ] && bundle exec codeclimate-test-reporter"
|
9
9
|
rvm:
|
10
10
|
- 2.4.5
|
11
11
|
- 2.5.5
|
@@ -17,13 +17,9 @@ env:
|
|
17
17
|
- COVERAGE=true
|
18
18
|
- JRUBY_OPTS='--dev -J-Xmx1024M'
|
19
19
|
matrix:
|
20
|
+
include:
|
21
|
+
- rvm: 2.7
|
20
22
|
allow_failures:
|
21
23
|
- rvm: truffleruby
|
22
24
|
notifications:
|
23
25
|
email: false
|
24
|
-
webhooks:
|
25
|
-
urls:
|
26
|
-
- https://webhooks.gitter.im/e/19098b4253a72c9796db
|
27
|
-
on_success: change # options: [always|never|change] default: always
|
28
|
-
on_failure: always # options: [always|never|change] default: always
|
29
|
-
on_start: false # default: false
|
data/CHANGELOG.md
CHANGED
@@ -1,12 +1,35 @@
|
|
1
|
+
# 1.1.0 2019-10-07
|
2
|
+
|
3
|
+
## Added
|
4
|
+
|
5
|
+
- Experimental support for pattern matching :tada: (flash-gordon)
|
6
|
+
|
7
|
+
```ruby
|
8
|
+
User = Dry.Struct(name: 'string', email: 'string')
|
9
|
+
|
10
|
+
user = User.new(name: 'John Doe', email: 'john@acme.org')
|
11
|
+
|
12
|
+
case user
|
13
|
+
in User({ name: 'John Doe', email: })
|
14
|
+
puts email
|
15
|
+
else
|
16
|
+
puts 'Not John'
|
17
|
+
end
|
18
|
+
```
|
19
|
+
|
20
|
+
See more examples in the [specs](https://github.com/dry-rb/dry-struct/blob/956fff208296731c40f1fea04b36106ea01b56d0/spec/dry/struct/pattern_matching_spec.rb).
|
21
|
+
|
22
|
+
[Compare v1.0.0...v1.1.0](https://github.com/dry-rb/dry-struct/compare/v1.0.0...v1.1.0)
|
23
|
+
|
1
24
|
# 1.0.0 2019-04-23
|
2
25
|
|
3
26
|
## Changed
|
4
27
|
|
5
|
-
|
28
|
+
- `valid?` and `===` behave differently, `===` works the same way `Class#===` does and `valid?` checks if the value _can be_ coerced to the struct (flash-gordon)
|
6
29
|
|
7
30
|
## Added
|
8
31
|
|
9
|
-
|
32
|
+
- `Struct.call` now accepts an optional block that will be called on failed coercion. This behavior is consistent with dry-types 1.0. Note that `.new` doesn't take a block (flash-gordon)
|
10
33
|
```ruby
|
11
34
|
User = Dry::Struct(name: 'string')
|
12
35
|
User.(1) { :oh_no }
|
@@ -19,7 +42,7 @@
|
|
19
42
|
|
20
43
|
## Changed
|
21
44
|
|
22
|
-
|
45
|
+
- [BREAKING] `Struct.input` was renamed `Struct.schema`, hence `Struct.schema` returns an instance of `Dry::Types::Hash::Schema` rather than a `Hash`. Schemas are also implementing `Enumerable` but they iterate over key types.
|
23
46
|
New API:
|
24
47
|
```ruby
|
25
48
|
User.schema.each do |key|
|
@@ -31,7 +54,7 @@
|
|
31
54
|
```ruby
|
32
55
|
User.schema.key(:id) # => #<Dry::Types::Hash::Key ...>
|
33
56
|
```
|
34
|
-
|
57
|
+
- [BREAKING] `transform_types` now passes one argument to the block, an instance of the `Key` type. Combined with the new API from dry-types it simplifies declaring omittable keys:
|
35
58
|
```ruby
|
36
59
|
class StructWithOptionalKeys < Dry::Struct
|
37
60
|
transform_types { |key| key.required(false) }
|
@@ -39,8 +62,8 @@
|
|
39
62
|
transform_types(&:omittable)
|
40
63
|
end
|
41
64
|
```
|
42
|
-
|
43
|
-
|
65
|
+
- `Dry::Stuct#new` is now more efficient for partial updates (flash-gordon)
|
66
|
+
- Ruby 2.3 is EOL and not officially supported. It may work but we don't test it.
|
44
67
|
|
45
68
|
[Compare v0.6.0...v0.7.0](https://github.com/dry-rb/dry-struct/compare/v0.6.0...v0.7.0)
|
46
69
|
|
@@ -48,11 +71,11 @@
|
|
48
71
|
|
49
72
|
## Changed
|
50
73
|
|
51
|
-
|
74
|
+
- [BREAKING] `Struct.attribute?` in the old sense is deprecated, use `has_attribute?` as a replacement
|
52
75
|
|
53
76
|
## Added
|
54
77
|
|
55
|
-
|
78
|
+
- `Struct.attribute?` is an easy way to define omittable attributes (flash-gordon):
|
56
79
|
|
57
80
|
```ruby
|
58
81
|
class User < Dry::Struct
|
@@ -64,7 +87,7 @@
|
|
64
87
|
|
65
88
|
## Fixed
|
66
89
|
|
67
|
-
|
90
|
+
- `Struct#to_h` recursively converts hash values to hashes, this was done to be consistent with current behavior for arrays (oeoeaio + ZimbiX)
|
68
91
|
|
69
92
|
[Compare v0.5.1...v0.6.0](https://github.com/dry-rb/dry-struct/compare/v0.5.1...v0.6.0)
|
70
93
|
|
@@ -72,11 +95,11 @@
|
|
72
95
|
|
73
96
|
## Fixed
|
74
97
|
|
75
|
-
|
98
|
+
- Constant resolution is now restricted to the current module when structs are automatically defined using the block syntax. This shouldn't break any existing code (piktur)
|
76
99
|
|
77
100
|
## Added
|
78
101
|
|
79
|
-
|
102
|
+
- Pretty print extension (ojab)
|
80
103
|
```ruby
|
81
104
|
Dry::Struct.load_extensions(:pretty_print)
|
82
105
|
PP.pp(user)
|
@@ -92,14 +115,14 @@
|
|
92
115
|
|
93
116
|
## BREAKING CHANGES
|
94
117
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
118
|
+
- `constructor_type` was removed, use `transform_types` and `transform_keys` as a replacement (see below)
|
119
|
+
- Default types are evaluated _only_ on missing values. Again, use `tranform_types` as a work around for `nil`s
|
120
|
+
- Values are now stored within a single instance variable names `@attributes`, this sped up struct creation and improved support for reserved attribute names such as `hash`, they don't get a getter but still can be read via `#[]`
|
121
|
+
- Ruby 2.3 is a minimal supported version
|
99
122
|
|
100
123
|
## Added
|
101
124
|
|
102
|
-
|
125
|
+
- `Dry::Struct.transform_types` accepts a block which is yielded on every type to add. Since types are `dry-types`' objects that come with a robust DSL it's rather simple to restore the behavior of `constructor_type`. See https://github.com/dry-rb/dry-struct/pull/64 for details (flash-gordon)
|
103
126
|
|
104
127
|
Example: evaluate defaults on `nil` values
|
105
128
|
|
@@ -111,9 +134,9 @@
|
|
111
134
|
end
|
112
135
|
```
|
113
136
|
|
114
|
-
|
137
|
+
- `Data::Struct.transform_keys` accepts a block/proc that transforms keys of input hashes. The most obvious usage is simbolization but arbitrary transformations are allowed (flash-gordon)
|
115
138
|
|
116
|
-
|
139
|
+
- `Dry.Struct` builds a struct by a hash of attribute names and types (citizen428)
|
117
140
|
|
118
141
|
```ruby
|
119
142
|
User = Dry::Struct(name: 'strict.string') do
|
@@ -121,7 +144,7 @@
|
|
121
144
|
end
|
122
145
|
```
|
123
146
|
|
124
|
-
|
147
|
+
- Support for `Struct.meta`, note that `.meta` returns a _new class_ (flash-gordon)
|
125
148
|
|
126
149
|
```ruby
|
127
150
|
class User < Dry::Struct
|
@@ -133,7 +156,7 @@
|
|
133
156
|
User.new(name: 'Jade').class == UserWithMeta.new(name: 'Jade').class # => false
|
134
157
|
```
|
135
158
|
|
136
|
-
|
159
|
+
- `Struct.attribute` yields a block with definition for nested structs. It defines a nested constant for the new struct and supports arrays (AMHOL + flash-gordon)
|
137
160
|
|
138
161
|
```ruby
|
139
162
|
class User < Dry::Struct
|
@@ -153,8 +176,8 @@
|
|
153
176
|
|
154
177
|
## Fixed
|
155
178
|
|
156
|
-
|
157
|
-
|
179
|
+
- Adding a new attribute invalidates `attribute_names` (flash-gordon)
|
180
|
+
- Struct classes track subclasses and define attributes in them, now it doesn't matter whether you define attributes first and _then_ subclass or vice versa. Note this can lead to memory leaks in Rails environment when struct classes are reloaded (flash-gordon)
|
158
181
|
|
159
182
|
[Compare v0.4.0...v0.5.0](https://github.com/dry-rb/dry-struct/compare/v0.4.0...v0.5.0)
|
160
183
|
|
@@ -162,13 +185,13 @@
|
|
162
185
|
|
163
186
|
## Changed
|
164
187
|
|
165
|
-
|
166
|
-
|
167
|
-
|
188
|
+
- Attribute readers don't override existing instance methods (solnic)
|
189
|
+
- `Struct#new` uses raw attributes instead of method calls, this makes the behavior consistent with the change above (flash-gordon)
|
190
|
+
- `constructor_type` now actively rejects `:weak` and `:symbolized` values (GustavoCaso)
|
168
191
|
|
169
192
|
## Fixed
|
170
193
|
|
171
|
-
|
194
|
+
- `Struct#new` doesn't call `.to_hash` recursively (flash-gordon)
|
172
195
|
|
173
196
|
[Compare v0.3.1...v0.4.0](https://github.com/dry-rb/dry-struct/compare/v0.3.1...v0.4.0)
|
174
197
|
|
@@ -176,9 +199,9 @@
|
|
176
199
|
|
177
200
|
## Added
|
178
201
|
|
179
|
-
|
180
|
-
|
181
|
-
|
202
|
+
- `Struct.constructor` that makes dry-struct more aligned with dry-types; now you can have a struct with a custom constructor that will be called _before_ calling the `new` method (v-kolesnikov)
|
203
|
+
- `Struct.attribute?` and `Struct.attribute_names` for introspecting struct attributes (flash-gordon)
|
204
|
+
- `Struct#__new__` is a safe-to-use-in-gems alias for `Struct#new` (flash-gordon)
|
182
205
|
|
183
206
|
[Compare v0.3.0...v0.3.1](https://github.com/dry-rb/dry-struct/compare/v0.3.0...v0.3.1)
|
184
207
|
|
@@ -186,16 +209,16 @@
|
|
186
209
|
|
187
210
|
## Added
|
188
211
|
|
189
|
-
|
212
|
+
- `Dry::Struct#new` method to return new instance with applied changeset (Kukunin)
|
190
213
|
|
191
214
|
## Fixed
|
192
215
|
|
193
|
-
|
194
|
-
|
216
|
+
- `.[]` and `.call` does not coerce subclass to superclass anymore (Kukunin)
|
217
|
+
- Raise ArgumentError when attribute type is a string and no value provided is for `new` (GustavoCaso)
|
195
218
|
|
196
219
|
## Changed
|
197
220
|
|
198
|
-
|
221
|
+
- `.new` without arguments doesn't use nil as an input for non-default types anymore (flash-gordon)
|
199
222
|
|
200
223
|
[Compare v0.2.1...v0.3.0](https://github.com/dry-rb/dry-struct/compare/v0.2.1...v0.3.0)
|
201
224
|
|
@@ -203,7 +226,7 @@
|
|
203
226
|
|
204
227
|
## Fixed
|
205
228
|
|
206
|
-
|
229
|
+
- Fixed `Dry::Struct::Value` which appeared to be broken in the last release (flash-gordon)
|
207
230
|
|
208
231
|
[Compare v0.2.0...v0.2.1](https://github.com/dry-rb/dry-struct/compare/v0.2.0...v0.2.1)
|
209
232
|
|
@@ -211,7 +234,7 @@
|
|
211
234
|
|
212
235
|
## Changed
|
213
236
|
|
214
|
-
|
237
|
+
- Struct attributes can be overridden in a subclass (flash-gordon)
|
215
238
|
|
216
239
|
[Compare v0.1.1...v0.2.0](https://github.com/dry-rb/dry-struct/compare/v0.1.1...v0.2.0)
|
217
240
|
|
@@ -219,7 +242,7 @@
|
|
219
242
|
|
220
243
|
## Fixed
|
221
244
|
|
222
|
-
|
245
|
+
- Make `Dry::Struct` act as a constrained type. This fixes the behavior of sum types containing structs (flash-gordon)
|
223
246
|
|
224
247
|
[Compare v0.1.0...v0.1.1](https://github.com/dry-rb/dry-struct/compare/v0.1.0...v0.1.1)
|
225
248
|
|
@@ -227,13 +250,13 @@
|
|
227
250
|
|
228
251
|
## Added
|
229
252
|
|
230
|
-
|
253
|
+
- `:strict_with_defaults` constructor type (backus)
|
231
254
|
|
232
255
|
## Changed
|
233
256
|
|
234
|
-
|
235
|
-
|
236
|
-
|
257
|
+
- [BREAKING] `:strict` was renamed to `:permissive` as it ignores missing keys (backus)
|
258
|
+
- [BREAKING] `:strict` now raises on unexpected keys (backus)
|
259
|
+
- Structs no longer auto-register themselves in the types container as they implement `Type` interface and we don't have to wrap them in `Type::Definition` (flash-gordon)
|
237
260
|
|
238
261
|
[Compare v0.0.1...v0.1.0](https://github.com/dry-rb/dry-struct/compare/v0.0.1...v0.1.0)
|
239
262
|
|
data/Gemfile
CHANGED
data/bin/console
CHANGED
@@ -0,0 +1,101 @@
|
|
1
|
+
---
|
2
|
+
title: Introduction
|
3
|
+
layout: gem-single
|
4
|
+
type: gem
|
5
|
+
name: dry-struct
|
6
|
+
sections:
|
7
|
+
- nested-structs
|
8
|
+
- recipes
|
9
|
+
---
|
10
|
+
|
11
|
+
`dry-struct` is a gem built on top of `dry-types` which provides virtus-like DSL for defining typed struct classes.
|
12
|
+
|
13
|
+
### Basic Usage
|
14
|
+
|
15
|
+
You can define struct objects which will have readers for specified attributes using a simple dsl:
|
16
|
+
|
17
|
+
``` ruby
|
18
|
+
require 'dry-struct'
|
19
|
+
|
20
|
+
module Types
|
21
|
+
include Dry.Types()
|
22
|
+
end
|
23
|
+
|
24
|
+
class User < Dry::Struct
|
25
|
+
attribute :name, Types::String.optional
|
26
|
+
attribute :age, Types::Coercible::Integer
|
27
|
+
end
|
28
|
+
|
29
|
+
user = User.new(name: nil, age: '21')
|
30
|
+
|
31
|
+
user.name # nil
|
32
|
+
user.age # 21
|
33
|
+
|
34
|
+
user = User.new(name: 'Jane', age: '21')
|
35
|
+
|
36
|
+
user.name # => "Jane"
|
37
|
+
user.age # => 21
|
38
|
+
```
|
39
|
+
|
40
|
+
### Value
|
41
|
+
|
42
|
+
You can define value objects which will behave like structs but will be *deeply frozen*:
|
43
|
+
|
44
|
+
``` ruby
|
45
|
+
class Location < Dry::Struct::Value
|
46
|
+
attribute :lat, Types::Float
|
47
|
+
attribute :lng, Types::Float
|
48
|
+
end
|
49
|
+
|
50
|
+
loc1 = Location.new(lat: 1.23, lng: 4.56)
|
51
|
+
loc2 = Location.new(lat: 1.23, lng: 4.56)
|
52
|
+
|
53
|
+
loc1.frozen? # true
|
54
|
+
loc2.frozen? # true
|
55
|
+
|
56
|
+
loc1 == loc2
|
57
|
+
# true
|
58
|
+
```
|
59
|
+
|
60
|
+
### Hash Schemas
|
61
|
+
|
62
|
+
`Dry::Struct` out of the box uses [hash schemas](/gems/dry-types/hash-schemas) from `dry-types` for processing input hashes. `with_type_transform` and `with_key_transform` are exposed as `transform_types` and `transform_keys`:
|
63
|
+
|
64
|
+
```ruby
|
65
|
+
class User < Dry::Struct
|
66
|
+
transform_keys(&:to_sym)
|
67
|
+
|
68
|
+
attribute :name, Types::String.optional
|
69
|
+
attribute :age, Types::Coercible::Integer
|
70
|
+
end
|
71
|
+
|
72
|
+
User.new('name' => 'Jane', 'age' => '21')
|
73
|
+
# => #<User name="Jane" age=21>
|
74
|
+
```
|
75
|
+
|
76
|
+
This plays nicely with inheritance, you can define a base struct for symbolizing input and then reuse it:
|
77
|
+
|
78
|
+
```ruby
|
79
|
+
class SymbolizeStruct < Dry::Struct
|
80
|
+
transform_keys(&:to_sym)
|
81
|
+
end
|
82
|
+
|
83
|
+
class User < SymbolizeStruct
|
84
|
+
attribute :name, Types::String.optional
|
85
|
+
attribute :age, Types::Coercible::Integer
|
86
|
+
end
|
87
|
+
```
|
88
|
+
|
89
|
+
### Validating data with dry-struct
|
90
|
+
|
91
|
+
Please don't. Structs are meant to work with valid input, it cannot generate error messages good enough for displaying them for a user etc. Use [`dry-validation`](/gems/dry-validation) for validating incoming data and then pass its output to structs.
|
92
|
+
|
93
|
+
### Differences between dry-struct and virtus
|
94
|
+
|
95
|
+
`dry-struct` look somewhat similar to Virtus but there are few significant differences:
|
96
|
+
|
97
|
+
* Structs don't provide attribute writers and are meant to be used as "data objects" exclusively
|
98
|
+
* Handling of attribute values is provided by standalone type objects from `dry-types`, which gives you way more powerful features
|
99
|
+
* Handling of attribute hashes is provided by standalone hash schemas from `dry-types`, which means there are different types of constructors in `dry-struct`
|
100
|
+
* Structs are not designed as swiss-army knifes, specific constructor types are used depending on the use case
|
101
|
+
* Struct classes quack like `dry-types`, which means you can use them in hash schemas, as array members or sum them
|
@@ -0,0 +1,49 @@
|
|
1
|
+
---
|
2
|
+
title: Nested Structs
|
3
|
+
layout: gem-single
|
4
|
+
name: dry-struct
|
5
|
+
---
|
6
|
+
|
7
|
+
The DSL allows to define nested structs by passing a block to `attribute`:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
class User < Dry::Struct
|
11
|
+
attribute :name, Types::String
|
12
|
+
attribute :address do
|
13
|
+
attribute :city, Types::String
|
14
|
+
attribute :street, Types::String
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
User.new(name: 'Jane', address: { city: 'London', street: 'Oxford' })
|
19
|
+
# => #<User name="Jane" address=#<User::Address city="London" street="Oxford">>
|
20
|
+
|
21
|
+
# constants for nested structs are automatically defined
|
22
|
+
User::Address
|
23
|
+
# => User::Address
|
24
|
+
```
|
25
|
+
|
26
|
+
By default, new struct classes uses `Dry::Struct` as a base class (`Dry::Struct::Value` for values). You can explicitly pass a different class:
|
27
|
+
|
28
|
+
```ruby
|
29
|
+
class User < Dry::Struct
|
30
|
+
attribute :address, MyStruct do
|
31
|
+
# ...
|
32
|
+
end
|
33
|
+
end
|
34
|
+
```
|
35
|
+
|
36
|
+
It is even possible to define an array of struct:
|
37
|
+
|
38
|
+
```ruby
|
39
|
+
class User < Dry::Struct
|
40
|
+
attribute :addresses, Types::Array do
|
41
|
+
attribute :city, Types::String
|
42
|
+
attribute :street, Types::String
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# constants are still there!
|
47
|
+
User::Address
|
48
|
+
# => User::Address
|
49
|
+
```
|
@@ -0,0 +1,143 @@
|
|
1
|
+
---
|
2
|
+
title: Recipes
|
3
|
+
layout: gem-single
|
4
|
+
name: dry-struct
|
5
|
+
---
|
6
|
+
|
7
|
+
### Symbolize input keys
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
require 'dry-struct'
|
11
|
+
|
12
|
+
module Types
|
13
|
+
include Dry.Types()
|
14
|
+
end
|
15
|
+
|
16
|
+
class User < Dry::Struct
|
17
|
+
transform_keys(&:to_sym)
|
18
|
+
|
19
|
+
attribute :name, Types::String
|
20
|
+
end
|
21
|
+
|
22
|
+
User.new('name' => 'Jane')
|
23
|
+
# => #<User name="Jane">
|
24
|
+
```
|
25
|
+
|
26
|
+
### Tolerance to extra keys
|
27
|
+
|
28
|
+
Structs ignore extra keys by default. This can be changed by replacing the constructor.
|
29
|
+
|
30
|
+
```ruby
|
31
|
+
class User < Dry::Struct
|
32
|
+
# This does the trick
|
33
|
+
schema schema.strict
|
34
|
+
|
35
|
+
attribute :name, Types::String
|
36
|
+
end
|
37
|
+
|
38
|
+
User.new(name: 'Jane', age: 21)
|
39
|
+
# => Dry::Struct::Error ([User.new] unexpected keys [:age] in Hash input)
|
40
|
+
```
|
41
|
+
|
42
|
+
### Tolerance to missing keys
|
43
|
+
|
44
|
+
You can mark certain keys as optional by calling `attribute?`.
|
45
|
+
|
46
|
+
```ruby
|
47
|
+
class User < Dry::Struct
|
48
|
+
attribute :name, Types::String
|
49
|
+
attribute? :age, Types::Integer
|
50
|
+
end
|
51
|
+
|
52
|
+
user = User.new(name: 'Jane')
|
53
|
+
# => #<User name="Jane" age=nil>
|
54
|
+
user.age
|
55
|
+
# => nil
|
56
|
+
```
|
57
|
+
|
58
|
+
In the example above `nil` violates the type constraint so be careful with `attribute?`.
|
59
|
+
|
60
|
+
### Default values
|
61
|
+
|
62
|
+
Instead of violating constraints you can assign default values to attributes:
|
63
|
+
|
64
|
+
```ruby
|
65
|
+
class User < Dry::Struct
|
66
|
+
attribute :name, Types::String
|
67
|
+
attribute :age, Types::Integer.default(18)
|
68
|
+
end
|
69
|
+
|
70
|
+
User.new(name: 'Jane')
|
71
|
+
# => #<User name="Jane" age=18>
|
72
|
+
```
|
73
|
+
|
74
|
+
### Resolving default values on `nil`
|
75
|
+
|
76
|
+
`nil` as a value isn't replaced with a default value for default types. You may use `transform_types` to turn all types into constructors which map `nil` to `Dry::Types::Undefined` which in order triggers default values.
|
77
|
+
|
78
|
+
```ruby
|
79
|
+
class User < Dry::Struct
|
80
|
+
transform_types do |type|
|
81
|
+
if type.default?
|
82
|
+
type.constructor do |value|
|
83
|
+
value.nil? ? Dry::Types::Undefined : value
|
84
|
+
end
|
85
|
+
else
|
86
|
+
type
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
attribute :name, Types::String
|
91
|
+
attribute :age, Types::Integer.default(18)
|
92
|
+
end
|
93
|
+
|
94
|
+
User.new(name: 'Jane')
|
95
|
+
# => #<User name="Jane" age=18>
|
96
|
+
User.new(name: 'Jane', age: nil)
|
97
|
+
# => #<User name="Jane" age=18>
|
98
|
+
```
|
99
|
+
|
100
|
+
### Creating a custom struct class
|
101
|
+
|
102
|
+
You can combine examples from this page to create a custom-purposed base struct class and the reuse it your application or gem
|
103
|
+
|
104
|
+
```ruby
|
105
|
+
class MyStruct < Dry::Struct
|
106
|
+
# throw an error when unknown keys provided
|
107
|
+
schema schema.strict
|
108
|
+
|
109
|
+
# convert string keys to symbols
|
110
|
+
transform_keys(&:to_sym)
|
111
|
+
|
112
|
+
# resolve default types on nil
|
113
|
+
transform_types do |type|
|
114
|
+
if type.default?
|
115
|
+
type.constructor do |value|
|
116
|
+
value.nil? ? Dry::Types::Undefined : value
|
117
|
+
end
|
118
|
+
else
|
119
|
+
type
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
```
|
124
|
+
|
125
|
+
### Set default value for a nested hash
|
126
|
+
|
127
|
+
```ruby
|
128
|
+
class Foo < Dry::Struct
|
129
|
+
attribute :bar do
|
130
|
+
attribute :nested, Types::Integer
|
131
|
+
end
|
132
|
+
end
|
133
|
+
```
|
134
|
+
|
135
|
+
```ruby
|
136
|
+
class Foo < Dry::Struct
|
137
|
+
class Bar < Dry::Struct
|
138
|
+
attribute :nested, Types::Integer
|
139
|
+
end
|
140
|
+
|
141
|
+
attribute :bar, Bar.default { Bar.new(nested: 1) }
|
142
|
+
end
|
143
|
+
```
|
data/lib/dry/struct.rb
CHANGED
@@ -16,21 +16,21 @@ module Dry
|
|
16
16
|
# require 'dry-struct'
|
17
17
|
#
|
18
18
|
# module Types
|
19
|
-
# include Dry.Types
|
19
|
+
# include Dry.Types()
|
20
20
|
# end
|
21
21
|
#
|
22
|
-
# Person = Dry.Struct(name: Types::
|
22
|
+
# Person = Dry.Struct(name: Types::String, age: Types::Integer)
|
23
23
|
# matz = Person.new(name: "Matz", age: 52)
|
24
24
|
# matz.name #=> "Matz"
|
25
25
|
# matz.age #=> 52
|
26
26
|
#
|
27
|
-
# Test = Dry.Struct(expected: Types::
|
27
|
+
# Test = Dry.Struct(expected: Types::String) { schema(schema.strict) }
|
28
28
|
# Test[expected: "foo", unexpected: "bar"]
|
29
29
|
# #=> Dry::Struct::Error: [Test.new] unexpected keys [:unexpected] in Hash input
|
30
30
|
def self.Struct(attributes = Dry::Core::Constants::EMPTY_HASH, &block)
|
31
31
|
Class.new(Dry::Struct) do
|
32
32
|
attributes.each { |a, type| attribute a, type }
|
33
|
-
|
33
|
+
module_eval(&block) if block
|
34
34
|
end
|
35
35
|
end
|
36
36
|
|
@@ -60,7 +60,7 @@ module Dry
|
|
60
60
|
# require 'dry-struct'
|
61
61
|
#
|
62
62
|
# module Types
|
63
|
-
# include Dry.Types
|
63
|
+
# include Dry.Types()
|
64
64
|
# end
|
65
65
|
#
|
66
66
|
# class Book < Dry::Struct
|
@@ -111,8 +111,8 @@ module Dry
|
|
111
111
|
#
|
112
112
|
# @example
|
113
113
|
# class Book < Dry::Struct
|
114
|
-
# attribute :title, Types::
|
115
|
-
# attribute :subtitle, Types::
|
114
|
+
# attribute :title, Types::String
|
115
|
+
# attribute :subtitle, Types::String.optional
|
116
116
|
# end
|
117
117
|
#
|
118
118
|
# rom_n_roda = Book.new(
|
@@ -132,8 +132,8 @@ module Dry
|
|
132
132
|
#
|
133
133
|
# @example
|
134
134
|
# class Book < Dry::Struct
|
135
|
-
# attribute :title, Types::
|
136
|
-
# attribute :subtitle, Types::
|
135
|
+
# attribute :title, Types::String
|
136
|
+
# attribute :subtitle, Types::String.optional
|
137
137
|
# end
|
138
138
|
#
|
139
139
|
# rom_n_roda = Book.new(
|
@@ -157,8 +157,8 @@ module Dry
|
|
157
157
|
#
|
158
158
|
# @example
|
159
159
|
# class Book < Dry::Struct
|
160
|
-
# attribute :title, Types::
|
161
|
-
# attribute :subtitle, Types::
|
160
|
+
# attribute :title, Types::String
|
161
|
+
# attribute :subtitle, Types::String.optional
|
162
162
|
# end
|
163
163
|
#
|
164
164
|
# rom_n_roda = Book.new(
|
@@ -183,6 +183,15 @@ module Dry
|
|
183
183
|
attrs = klass.attribute_names.map { |key| " #{key}=#{@attributes[key].inspect}" }.join
|
184
184
|
"#<#{ klass.name || klass.inspect }#{ attrs }>"
|
185
185
|
end
|
186
|
+
|
187
|
+
if RUBY_VERSION >= '2.7'
|
188
|
+
# Pattern matching support
|
189
|
+
#
|
190
|
+
# @api private
|
191
|
+
def deconstruct
|
192
|
+
[attributes]
|
193
|
+
end
|
194
|
+
end
|
186
195
|
end
|
187
196
|
end
|
188
197
|
|
@@ -53,7 +53,7 @@ module Dry
|
|
53
53
|
# end
|
54
54
|
#
|
55
55
|
# Language.schema
|
56
|
-
# # => #<Dry::Types[Constructor<Schema<keys={name: Nominal<String> details: Language::Details}> fn=Kernel.Hash>]>
|
56
|
+
# # => #<Dry::Types[Constructor<Schema<keys={name: Constrained<Nominal<String> rule=[type?(String)]> details: Language::Details}> fn=Kernel.Hash>]>
|
57
57
|
#
|
58
58
|
# ruby = Language.new(name: 'Ruby', details: { type: 'OO' })
|
59
59
|
# ruby.name #=> 'Ruby'
|
@@ -72,9 +72,9 @@ module Dry
|
|
72
72
|
#
|
73
73
|
# Language.schema
|
74
74
|
# => #<Dry::Types[Constructor<Schema<keys={
|
75
|
-
# name: Nominal<String>
|
76
|
-
# versions: Array<Nominal<String>>
|
77
|
-
# celebrities: Array<Language::Celebrity>
|
75
|
+
# name: Constrained<Nominal<String> rule=[type?(String)]>
|
76
|
+
# versions: Constrained<Array<Constrained<Nominal<String> rule=[type?(String)]>> rule=[type?(Array)]>
|
77
|
+
# celebrities: Constrained<Array<Language::Celebrity> rule=[type?(Array)]>
|
78
78
|
# }> fn=Kernel.Hash>]>
|
79
79
|
#
|
80
80
|
# ruby = Language.new(
|
@@ -104,11 +104,11 @@ module Dry
|
|
104
104
|
#
|
105
105
|
# @example
|
106
106
|
# class User < Dry::Struct
|
107
|
-
# attribute :name, Types::
|
108
|
-
# attribute? :email, Types::
|
107
|
+
# attribute :name, Types::String
|
108
|
+
# attribute? :email, Types::String
|
109
109
|
# end
|
110
110
|
#
|
111
|
-
# User.new(name: 'John') # => #<User name="John">
|
111
|
+
# User.new(name: 'John') # => #<User name="John" email=nil>
|
112
112
|
#
|
113
113
|
# @param [Symbol] name name of the defined attribute
|
114
114
|
# @param [Dry::Types::Type, nil] type or superclass of nested type
|
@@ -145,8 +145,8 @@ module Dry
|
|
145
145
|
#
|
146
146
|
# Book.schema
|
147
147
|
# # => #<Dry::Types[Constructor<Schema<keys={
|
148
|
-
# # title: Nominal<String>
|
149
|
-
# # author: Nominal<String>
|
148
|
+
# # title: Constrained<Nominal<String> rule=[type?(String)]>
|
149
|
+
# # author: Constrained<Nominal<String> rule=[type?(String)]>
|
150
150
|
# # }> fn=Kernel.Hash>]>
|
151
151
|
def attributes(new_schema)
|
152
152
|
keys = new_schema.keys.map { |k| k.to_s.chomp('?').to_sym }
|
@@ -182,7 +182,7 @@ module Dry
|
|
182
182
|
# class Book < Dry::Struct
|
183
183
|
# transform_types { |t| t.meta(struct: :Book) }
|
184
184
|
#
|
185
|
-
# attribute :title, Types::
|
185
|
+
# attribute :title, Types::String
|
186
186
|
# end
|
187
187
|
#
|
188
188
|
# Book.schema.key(:title).meta # => { struct: :Book }
|
@@ -199,7 +199,7 @@ module Dry
|
|
199
199
|
# class Book < Dry::Struct
|
200
200
|
# transform_keys(&:to_sym)
|
201
201
|
#
|
202
|
-
# attribute :title, Types::
|
202
|
+
# attribute :title, Types::String
|
203
203
|
# end
|
204
204
|
#
|
205
205
|
# Book.new('title' => "The Old Man and the Sea")
|
@@ -272,10 +272,10 @@ module Dry
|
|
272
272
|
# @yieldreturn [Dry::Types::ResultResult]
|
273
273
|
# @return [Dry::Types::Result]
|
274
274
|
def try(input)
|
275
|
-
|
275
|
+
success(self[input])
|
276
276
|
rescue Struct::Error => e
|
277
|
-
|
278
|
-
block_given? ? yield(
|
277
|
+
failure_result = failure(input, e.message)
|
278
|
+
block_given? ? yield(failure_result) : failure_result
|
279
279
|
end
|
280
280
|
|
281
281
|
# @param [Hash{Symbol => Object},Dry::Struct] input
|
data/lib/dry/struct/hashify.rb
CHANGED
@@ -7,11 +7,7 @@ module Dry
|
|
7
7
|
# @return [Hash, Array]
|
8
8
|
def self.[](value)
|
9
9
|
if value.respond_to?(:to_hash)
|
10
|
-
|
11
|
-
value.to_hash.transform_values { |v| self[v] }
|
12
|
-
else
|
13
|
-
value.to_hash.each_with_object({}) { |(k, v), h| h[k] = self[v] }
|
14
|
-
end
|
10
|
+
value.to_hash.transform_values { |current| self[current] }
|
15
11
|
elsif value.respond_to?(:to_ary)
|
16
12
|
value.to_ary.map { |item| self[item] }
|
17
13
|
else
|
data/lib/dry/struct/value.rb
CHANGED
@@ -7,8 +7,8 @@ module Dry
|
|
7
7
|
#
|
8
8
|
# @example
|
9
9
|
# class Location < Dry::Struct::Value
|
10
|
-
# attribute :lat, Types::
|
11
|
-
# attribute :lng, Types::
|
10
|
+
# attribute :lat, Types::Float
|
11
|
+
# attribute :lng, Types::Float
|
12
12
|
# end
|
13
13
|
#
|
14
14
|
# loc1 = Location.new(lat: 1.23, lng: 4.56)
|
data/lib/dry/struct/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dry-struct
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Piotr Solnica
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-
|
11
|
+
date: 2019-10-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: dry-equalizer
|
@@ -135,6 +135,9 @@ executables: []
|
|
135
135
|
extensions: []
|
136
136
|
extra_rdoc_files: []
|
137
137
|
files:
|
138
|
+
- ".github/ISSUE_TEMPLATE/----please-don-t-ask-for-support-via-issues.md"
|
139
|
+
- ".github/ISSUE_TEMPLATE/---bug-report.md"
|
140
|
+
- ".github/ISSUE_TEMPLATE/---feature-request.md"
|
138
141
|
- ".gitignore"
|
139
142
|
- ".rspec"
|
140
143
|
- ".travis.yml"
|
@@ -151,6 +154,9 @@ files:
|
|
151
154
|
- benchmarks/setup.rb
|
152
155
|
- bin/console
|
153
156
|
- bin/setup
|
157
|
+
- docsite/source/index.html.md
|
158
|
+
- docsite/source/nested-structs.html.md
|
159
|
+
- docsite/source/recipes.html.md
|
154
160
|
- dry-struct.gemspec
|
155
161
|
- lib/dry-struct.rb
|
156
162
|
- lib/dry/struct.rb
|
@@ -188,7 +194,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
188
194
|
- !ruby/object:Gem::Version
|
189
195
|
version: '0'
|
190
196
|
requirements: []
|
191
|
-
rubygems_version: 3.0.
|
197
|
+
rubygems_version: 3.0.6
|
192
198
|
signing_key:
|
193
199
|
specification_version: 4
|
194
200
|
summary: Typed structs and value objects.
|