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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b8e3499eadacef74b5203366275c7f68c77ad138e950ac4e5f0a02635bc118f9
4
- data.tar.gz: a227f8eb3d8beb3b1388bbe1d447055076938611929ed0a41610253feae64741
3
+ metadata.gz: 9e2aaf63cecd9545e3b19c763209b1dc4d2a2c1dec53e2050cf02e5e9c29d97a
4
+ data.tar.gz: b7464679f32b3d861bd63b31a24926a8806158bef3d2b99fc5199aa8c7f0db78
5
5
  SHA512:
6
- metadata.gz: 1c721723926e9edaf876e0e96e209fbdd33b882a9f92b2b670aeb3af26901c9c42d262a7446e811706c4fe02a3eb4bf76895747c2cac084ad20023952f480879
7
- data.tar.gz: a0dba3a720335d985feea9004a0b1ec1f8bb8625a4b6947a22acb6ab68bb0f4fb23aea38c8122570475ed332dc408330391d1572707216007f302e853e8a6e66
6
+ metadata.gz: 2003c10114e1aa03a19f21c68a1f3e3d684d2e4500cd22782c261ec935491668e56ce4916cd98ee1a9fbb64222dcf2f1061c9d6f90360bac351f0cd352c935a3
7
+ data.tar.gz: 45d7b302ba157d73ae9d5039675dc6153b8b68b0739626d3db799bd8255be8ef3a2528db6b370ca6f4fde7dec2cc597950b710b3f84bcbee3cc889d9b444f664
@@ -0,0 +1,10 @@
1
+ ---
2
+ name: "⚠️ Please don't ask for support via issues"
3
+ about: See CONTRIBUTING.md for more information
4
+ title: ''
5
+ labels: ''
6
+ assignees: ''
7
+
8
+ ---
9
+
10
+
@@ -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.
@@ -3,9 +3,9 @@ dist: trusty
3
3
  sudo: required
4
4
  bundler_args: --without benchmarks tools
5
5
  script:
6
- - bundle exec rake spec
6
+ - bundle exec rspec spec
7
7
  after_success:
8
- - '[ -d coverage ] && bundle exec codeclimate-test-reporter'
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
@@ -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
- * `valid?` and `===` behave differently, `===` works the same way `Class#===` does and `valid?` checks if the value _can be_ coerced to the struct (flash-gordon)
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
- * `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)
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
- * [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.
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
- * [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:
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
- * `Dry::Stuct#new` is now more efficient for partial updates (flash-gordon)
43
- * Ruby 2.3 is EOL and not officially supported. It may work but we don't test it.
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
- * [BREAKING] `Struct.attribute?` in the old sense is deprecated, use `has_attribute?` as a replacement
74
+ - [BREAKING] `Struct.attribute?` in the old sense is deprecated, use `has_attribute?` as a replacement
52
75
 
53
76
  ## Added
54
77
 
55
- * `Struct.attribute?` is an easy way to define omittable attributes (flash-gordon):
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
- * `Struct#to_h` recursively converts hash values to hashes, this was done to be consistent with current behavior for arrays (oeoeaio + ZimbiX)
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
- * 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)
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
- * Pretty print extension (ojab)
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
- * `constructor_type` was removed, use `transform_types` and `transform_keys` as a replacement (see below)
96
- * Default types are evaluated _only_ on missing values. Again, use `tranform_types` as a work around for `nil`s
97
- * 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 `#[]`
98
- * Ruby 2.3 is a minimal supported version
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
- * `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)
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
- * `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)
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
- * `Dry.Struct` builds a struct by a hash of attribute names and types (citizen428)
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
- * Support for `Struct.meta`, note that `.meta` returns a _new class_ (flash-gordon)
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
- * `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)
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
- * Adding a new attribute invalidates `attribute_names` (flash-gordon)
157
- * 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)
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
- * Attribute readers don't override existing instance methods (solnic)
166
- * `Struct#new` uses raw attributes instead of method calls, this makes the behavior consistent with the change above (flash-gordon)
167
- * `constructor_type` now actively rejects `:weak` and `:symbolized` values (GustavoCaso)
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
- * `Struct#new` doesn't call `.to_hash` recursively (flash-gordon)
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
- * `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)
180
- * `Struct.attribute?` and `Struct.attribute_names` for introspecting struct attributes (flash-gordon)
181
- * `Struct#__new__` is a safe-to-use-in-gems alias for `Struct#new` (flash-gordon)
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
- * `Dry::Struct#new` method to return new instance with applied changeset (Kukunin)
212
+ - `Dry::Struct#new` method to return new instance with applied changeset (Kukunin)
190
213
 
191
214
  ## Fixed
192
215
 
193
- * `.[]` and `.call` does not coerce subclass to superclass anymore (Kukunin)
194
- * Raise ArgumentError when attribute type is a string and no value provided is for `new` (GustavoCaso)
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
- * `.new` without arguments doesn't use nil as an input for non-default types anymore (flash-gordon)
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
- * Fixed `Dry::Struct::Value` which appeared to be broken in the last release (flash-gordon)
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
- * Struct attributes can be overridden in a subclass (flash-gordon)
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
- * Make `Dry::Struct` act as a constrained type. This fixes the behavior of sum types containing structs (flash-gordon)
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
- * `:strict_with_defaults` constructor type (backus)
253
+ - `:strict_with_defaults` constructor type (backus)
231
254
 
232
255
  ## Changed
233
256
 
234
- * [BREAKING] `:strict` was renamed to `:permissive` as it ignores missing keys (backus)
235
- * [BREAKING] `:strict` now raises on unexpected keys (backus)
236
- * 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)
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
@@ -6,6 +6,7 @@ gemspec
6
6
 
7
7
  group :test do
8
8
  gem 'codeclimate-test-reporter', platform: :mri, require: false
9
+ gem 'dry-monads'
9
10
  gem 'simplecov', require: false
10
11
  gem 'warning'
11
12
  end
@@ -6,7 +6,7 @@ require 'dry/struct'
6
6
  require 'irb'
7
7
 
8
8
  module Types
9
- include Dry.Types
9
+ include Dry.Types()
10
10
  end
11
11
 
12
12
  binding.irb
@@ -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
+ ```
@@ -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::Strict::String, age: Types::Strict::Int)
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::Strict::String) { input(input.strict) }
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
- instance_eval(&block) if block
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::Strict::String
115
- # attribute :subtitle, Types::Strict::String.optional
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::Strict::String
136
- # attribute :subtitle, Types::Strict::String.optional
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::Strict::String
161
- # attribute :subtitle, Types::Strict::String.optional
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::Strict::String
108
- # attribute? :email, Types::Strict::String
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::Strict::String
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::Strict::String
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
- Types::Result::Success.new(self[input])
275
+ success(self[input])
276
276
  rescue Struct::Error => e
277
- failure = Types::Result::Failure.new(input, e.message)
278
- block_given? ? yield(failure) : failure
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
@@ -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
- if RUBY_VERSION >= '2.4'
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
@@ -7,8 +7,8 @@ module Dry
7
7
  #
8
8
  # @example
9
9
  # class Location < Dry::Struct::Value
10
- # attribute :lat, Types::Strict::Float
11
- # attribute :lng, Types::Strict::Float
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)
@@ -1,6 +1,6 @@
1
1
  module Dry
2
2
  class Struct
3
3
  # @private
4
- VERSION = '1.0.0'.freeze
4
+ VERSION = '1.1.0'.freeze
5
5
  end
6
6
  end
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.0.0
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-04-23 00:00:00.000000000 Z
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.3
197
+ rubygems_version: 3.0.6
192
198
  signing_key:
193
199
  specification_version: 4
194
200
  summary: Typed structs and value objects.