dry-struct 1.0.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/.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.
|