kind 1.9.0 → 3.0.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/.travis.yml +4 -1
- data/README.md +445 -154
- data/lib/kind.rb +55 -48
- data/lib/kind/active_model/kind_validator.rb +36 -16
- data/lib/kind/checker.rb +3 -63
- data/lib/kind/checker/factory.rb +35 -0
- data/lib/kind/checker/protocol.rb +73 -0
- data/lib/kind/error.rb +1 -1
- data/lib/kind/maybe.rb +90 -17
- data/lib/kind/types.rb +15 -4
- data/lib/kind/undefined.rb +1 -1
- data/lib/kind/validator.rb +2 -2
- data/lib/kind/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a0a947ffbd4a82b5ca7a74006ae8383c725393e1e8640364dc9e73874aac0eb0
|
4
|
+
data.tar.gz: d49f7382d4786e079c42ee7f45609ca8ca292ccb04e4d8ac299cdfa4cbf2e1c8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 77195516f80bae446150221c3c95a621ecacb70335f4594242b3d7f60e9de7ed5be32624c02183faf1ab5cc4717b0c229c82c52ec70aad6122d1fe51a71dfc92
|
7
|
+
data.tar.gz: 1f7ab4c70b8548dda0707d31ba9d500946c5314d8d9462ecdcd5ca0b23f1a197691d0eb6586e4a1628426794f1495d0303d2b1b7c4b32a1bcc73fdc88ea1123b
|
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -18,28 +18,37 @@ One of the goals of this project is to do simple type checking like `"some strin
|
|
18
18
|
- [Required Ruby version](#required-ruby-version)
|
19
19
|
- [Installation](#installation)
|
20
20
|
- [Usage](#usage)
|
21
|
-
- [Verifying the kind of some object](#verifying-the-kind-of-some-object)
|
22
|
-
|
21
|
+
- [Kind.of.\<Type\>() - Verifying the kind of some object](#kindoftype---verifying-the-kind-of-some-object)
|
22
|
+
- [Method aliases to perform a strict validation](#method-aliases-to-perform-a-strict-validation)
|
23
|
+
- [Kind.of.\<Type\>.or_nil()](#kindoftypeor_nil)
|
24
|
+
- [Kind.of.\<Type\>.instance?()](#kindoftypeinstance)
|
25
|
+
- [Kind.is.\<Type\>() - Verifying if some class/module is the expected kind.](#kindistype---verifying-if-some-classmodule-is-the-expected-kind)
|
23
26
|
- [How to create a new type checker?](#how-to-create-a-new-type-checker)
|
24
27
|
- [Creating/Verifiyng type checkers dynamically](#creatingverifiyng-type-checkers-dynamically)
|
25
|
-
- [Registering new (custom) type
|
28
|
+
- [Registering new (custom) type checker](#registering-new-custom-type-checker)
|
26
29
|
- [What happens if a custom type checker has a namespace?](#what-happens-if-a-custom-type-checker-has-a-namespace)
|
27
30
|
- [Type checkers](#type-checkers)
|
28
31
|
- [Classes' type checkers](#classes-type-checkers)
|
29
32
|
- [Modules' type checkers](#modules-type-checkers)
|
30
33
|
- [Specials' type checkers](#specials-type-checkers)
|
34
|
+
- [Kind::Validator (ActiveModel::Validations)](#kindvalidator-activemodelvalidations)
|
35
|
+
- [Usage](#usage-1)
|
36
|
+
- [Defining the default validation strategy](#defining-the-default-validation-strategy)
|
37
|
+
- [Using the `allow_nil` and `strict` options](#using-the-allow_nil-and-strict-options)
|
31
38
|
- [Kind::Undefined](#kindundefined)
|
32
39
|
- [Kind.of.\<Type\>.or_undefined()](#kindoftypeor_undefined)
|
33
40
|
- [Kind::Maybe](#kindmaybe)
|
34
|
-
|
35
|
-
- [Kind::Maybe#
|
41
|
+
- [Replacing blocks by lambdas](#replacing-blocks-by-lambdas)
|
42
|
+
- [Kind::Maybe[], Kind::Maybe.wrap() and Kind::Maybe#then method aliases](#kindmaybe-kindmaybewrap-and-kindmaybethen-method-aliases)
|
43
|
+
- [Replacing blocks by lambdas](#replacing-blocks-by-lambdas-1)
|
44
|
+
- [Kind::None() and Kind::Some()](#kindnone-and-kindsome)
|
36
45
|
- [Kind.of.Maybe()](#kindofmaybe)
|
37
46
|
- [Kind::Optional](#kindoptional)
|
47
|
+
- [Replacing blocks by lambdas](#replacing-blocks-by-lambdas-2)
|
38
48
|
- [Kind.of.\<Type\>.as_optional](#kindoftypeas_optional)
|
39
|
-
- [Kind::
|
40
|
-
- [
|
41
|
-
|
42
|
-
- [Using the `allow_nil` and `strict` options](#using-the-allow_nil-and-strict-options)
|
49
|
+
- [Kind::Maybe(<Type>)](#kindmaybetype)
|
50
|
+
- [Kind::Maybe#try](#kindmaybetry)
|
51
|
+
- [Kind::Maybe#try!](#kindmaybetry-1)
|
43
52
|
- [Kind::Empty](#kindempty)
|
44
53
|
- [Similar Projects](#similar-projects)
|
45
54
|
- [Development](#development)
|
@@ -82,14 +91,14 @@ sum(1, 1) # 2
|
|
82
91
|
sum('1', 1) # Kind::Error ("\"1\" expected to be a kind of Numeric")
|
83
92
|
```
|
84
93
|
|
85
|
-
### Verifying the kind of some object
|
94
|
+
### Kind.of.\<Type\>() - Verifying the kind of some object
|
86
95
|
|
87
96
|
By default, basic verifications are strict. So, when you perform `Kind.of.Hash(value)`, if the given value was a Hash, the value itself will be returned, but if it isn't the right type, an error will be raised.
|
88
97
|
|
89
98
|
```ruby
|
90
|
-
Kind.of.Hash(nil)
|
91
|
-
Kind.of.Hash('')
|
92
|
-
Kind.of.Hash(
|
99
|
+
Kind.of.Hash(nil) # **raise Kind::Error, "nil expected to be a kind of Hash"**
|
100
|
+
Kind.of.Hash('') # raise Kind::Error, "'' expected to be a kind of Hash"
|
101
|
+
Kind.of.Hash(a: 1) # {a: 1}
|
93
102
|
|
94
103
|
# ---
|
95
104
|
|
@@ -99,14 +108,12 @@ Kind.of.Boolean(false) # false
|
|
99
108
|
```
|
100
109
|
|
101
110
|
> **Note:** `Kind.of.<Type>` supports the to_proc protocol.
|
102
|
-
>
|
103
|
-
> when the value has the desired kind and false if it hasn't.
|
111
|
+
> And it will perform a strict validation as expected.
|
104
112
|
|
105
113
|
```ruby
|
106
114
|
collection = [ {number: 1}, 'number 2', {number: 3}, :number_4 ]
|
107
115
|
|
108
|
-
collection
|
109
|
-
.select(&Kind.of.Hash) # [{number: 1}, {number: 3}]
|
116
|
+
collection.map(&Kind.of.Hash) # Kind::Error ("number 2" expected to be a kind of Hash)
|
110
117
|
```
|
111
118
|
|
112
119
|
When the verified value is nil, it is possible to define a default value with the same type to be returned.
|
@@ -123,6 +130,24 @@ Kind.of.Boolean(nil, or: true) # true
|
|
123
130
|
|
124
131
|
> **Note:** As an alternative syntax, you can use the `Kind::Of` instead of the `Kind.of` method. e.g: `Kind::Of::Hash('')`
|
125
132
|
|
133
|
+
#### Method aliases to perform a strict validation
|
134
|
+
|
135
|
+
```ruby
|
136
|
+
Kind.of.Hash[nil] # raise Kind::Error, "nil expected to be a kind of Hash"
|
137
|
+
Kind.of.Hash[''] # raise Kind::Error, "'' expected to be a kind of Hash"
|
138
|
+
Kind.of.Hash[a: 1] # {a: 1}
|
139
|
+
Kind.of.Hash['', or: {}] # {}
|
140
|
+
|
141
|
+
# or
|
142
|
+
|
143
|
+
Kind.of.Hash.instance(nil) # raise Kind::Error, "nil expected to be a kind of Hash"
|
144
|
+
Kind.of.Hash.instance('') # raise Kind::Error, "'' expected to be a kind of Hash"
|
145
|
+
Kind.of.Hash.instance(a: 1) # {a: 1}
|
146
|
+
Kind.of.Hash.instance('', or: {}) # {}
|
147
|
+
```
|
148
|
+
|
149
|
+
### Kind.of.\<Type\>.or_nil()
|
150
|
+
|
126
151
|
But if you don't need a strict type verification, use the `.or_nil` method.
|
127
152
|
|
128
153
|
```ruby
|
@@ -135,17 +160,24 @@ Kind.of.Boolean.or_nil('') # nil
|
|
135
160
|
Kind.of.Boolean.or_nil(true) # true
|
136
161
|
```
|
137
162
|
|
138
|
-
|
163
|
+
### Kind.of.\<Type\>.instance?()
|
164
|
+
|
165
|
+
Use the method `.instance?` to verify if the given object has the expected type.
|
139
166
|
|
140
167
|
```ruby
|
141
|
-
Kind.of.Hash.instance?(
|
142
|
-
#
|
168
|
+
Kind.of.Hash.instance?({}) # true
|
169
|
+
Kind.of.Hash.instance?({}, HashWithIndifferentAccess.new) # true
|
170
|
+
|
171
|
+
Kind.of.Hash.instance?('') # false
|
172
|
+
Kind.of.Hash.instance?({}, '') # false
|
143
173
|
|
144
174
|
# ---
|
145
175
|
|
146
|
-
Kind.of.Boolean.instance?(
|
147
|
-
Kind.of.Boolean.instance?(true)
|
148
|
-
|
176
|
+
Kind.of.Boolean.instance?(true) # true
|
177
|
+
Kind.of.Boolean.instance?(true, false) # true
|
178
|
+
|
179
|
+
Kind.of.Boolean.instance?(nil) # false
|
180
|
+
Kind.of.Boolean.instance?(false, true, nil) # false
|
149
181
|
```
|
150
182
|
|
151
183
|
> **Note:** When `.instance?` is called without an argument,
|
@@ -155,27 +187,35 @@ Kind.of.Boolean.instance?(false) # true
|
|
155
187
|
collection = [ {number: 1}, 'number 2', {number: 3}, :number_4 ]
|
156
188
|
|
157
189
|
collection
|
158
|
-
.select(&Kind.of.Hash.instance?)
|
159
|
-
.reduce(0) { |total, item| total + item.fetch(:number, 0) } # 4
|
190
|
+
.select(&Kind.of.Hash.instance?) # [{:number=>1}, {:number=>3}]
|
160
191
|
```
|
161
192
|
|
162
|
-
|
193
|
+
> **Note:** You can use a different syntax to perform an instance verification.
|
194
|
+
> To do this, use Kind.of.\<Type\>?()
|
163
195
|
|
164
196
|
```ruby
|
165
|
-
Kind.of.Hash
|
166
|
-
Kind.of.Hash
|
167
|
-
Kind.of.Hash[a: 1] # {a: 1}
|
168
|
-
Kind.of.Hash['', or: {}] # {}
|
197
|
+
Kind.of.Hash?({}) # true
|
198
|
+
Kind.of.Hash?({}, HashWithIndifferentAccess.new) # true
|
169
199
|
|
170
|
-
#
|
200
|
+
Kind.of.Hash?('') # false
|
201
|
+
Kind.of.Hash?({}, '') # false
|
171
202
|
|
172
|
-
|
173
|
-
|
174
|
-
Kind.of.
|
175
|
-
Kind.of.
|
203
|
+
# ---
|
204
|
+
|
205
|
+
Kind.of.Boolean?(true) # true
|
206
|
+
Kind.of.Boolean?(false, true) # true
|
207
|
+
|
208
|
+
Kind.of.Boolean?(nil) # false
|
209
|
+
Kind.of.Boolean?(false, true, nil) # false
|
210
|
+
|
211
|
+
# ---
|
212
|
+
|
213
|
+
collection = [ {number: 1}, 'number 2', {number: 3}, :number_4 ]
|
214
|
+
|
215
|
+
collection.select(&Kind.of.Hash?) # [{:number=>1}, {:number=>3}]
|
176
216
|
```
|
177
217
|
|
178
|
-
###
|
218
|
+
### Kind.is.\<Type\>() - Verifying if some class/module is the expected kind.
|
179
219
|
|
180
220
|
You can use `Kind.is` to verify if some class has the expected type as its ancestor.
|
181
221
|
|
@@ -195,6 +235,49 @@ Kind.of.Hash.class?(Hash) # true
|
|
195
235
|
Kind.of.Hash.class?(ActiveSupport::HashWithIndifferentAccess) # true
|
196
236
|
```
|
197
237
|
|
238
|
+
> **Note:** The `Kind.is` could check the inheritance of Classes/Modules.
|
239
|
+
|
240
|
+
```ruby
|
241
|
+
#
|
242
|
+
# Verifying if the attribute value is the class or a subclass.
|
243
|
+
#
|
244
|
+
class Human; end
|
245
|
+
class Person < Human; end
|
246
|
+
class User < Human; end
|
247
|
+
|
248
|
+
Kind.is(Human, User) # true
|
249
|
+
Kind.is(Human, Human) # true
|
250
|
+
Kind.is(Human, Person) # true
|
251
|
+
|
252
|
+
Kind.is(Human, Struct) # false
|
253
|
+
|
254
|
+
#
|
255
|
+
# Verifying if the attribute value is the module or if it is a class that includes the module
|
256
|
+
#
|
257
|
+
module Human; end
|
258
|
+
class Person; include Human; end
|
259
|
+
class User; include Human; end
|
260
|
+
|
261
|
+
Kind.is(Human, User) # true
|
262
|
+
Kind.is(Human, Human) # true
|
263
|
+
Kind.is(Human, Person) # true
|
264
|
+
|
265
|
+
Kind.is(Human, Struct) # false
|
266
|
+
|
267
|
+
#
|
268
|
+
# Verifying if the attribute value is the module or if it is a module that extends the module
|
269
|
+
#
|
270
|
+
module Human; end
|
271
|
+
module Person; extend Human; end
|
272
|
+
module User; extend Human; end
|
273
|
+
|
274
|
+
Kind.is(Human, User) # true
|
275
|
+
Kind.is(Human, Human) # true
|
276
|
+
Kind.is(Human, Person) # true
|
277
|
+
|
278
|
+
Kind.is(Human, Struct) # false
|
279
|
+
```
|
280
|
+
|
198
281
|
[⬆️ Back to Top](#table-of-contents-)
|
199
282
|
|
200
283
|
### How to create a new type checker?
|
@@ -219,6 +302,22 @@ Kind.of(User, {}) # Kind::Error ({} expected to be a kind of User)
|
|
219
302
|
Kind.of(Hash, {}) # {}
|
220
303
|
Kind.of(Hash, user) # Kind::Error (<User ...> expected to be a kind of Hash)
|
221
304
|
|
305
|
+
# ----------------------------------------- #
|
306
|
+
# Verifiyng if the value is a kind instance #
|
307
|
+
# ----------------------------------------- #
|
308
|
+
|
309
|
+
Kind.of?(Numeric, 1) # true
|
310
|
+
Kind.of?(Numeric, 1, 2.0) # true
|
311
|
+
|
312
|
+
Kind.of?(Numeric, '1') # false
|
313
|
+
Kind.of?(Numeric, 1, '2.0') # false
|
314
|
+
|
315
|
+
# Note: Kind.of?(Type) without arguments will return a
|
316
|
+
# lambda that will perform an instance verification
|
317
|
+
#
|
318
|
+
[1, '2', 3.0, '4']
|
319
|
+
.select(&Kind.of?(Numeric)) # [1, 3.0]
|
320
|
+
|
222
321
|
# ---------------------------------- #
|
223
322
|
# Creating type checkers dynamically #
|
224
323
|
# ---------------------------------- #
|
@@ -257,7 +356,7 @@ end
|
|
257
356
|
Kind.is(User, AdminUser) # true
|
258
357
|
```
|
259
358
|
|
260
|
-
#### Registering new (custom) type
|
359
|
+
#### Registering new (custom) type checker
|
261
360
|
|
262
361
|
Use `Kind::Types.add()`. e.g:
|
263
362
|
|
@@ -377,13 +476,165 @@ The list of types (classes and modules) available to use with `Kind.of.*` or `Ki
|
|
377
476
|
- `Kind.of.Module()`
|
378
477
|
- `Kind.of.Lambda()`
|
379
478
|
- `Kind.of.Boolean()`
|
380
|
-
- `Kind.of.Callable()`: verifies if the given value `respond_to?(:call)
|
479
|
+
- `Kind.of.Callable()`: verifies if the given value `respond_to?(:call)`.
|
381
480
|
- `Kind.of.Maybe()` or its alias `Kind.of.Optional()`
|
382
481
|
|
383
482
|
**Note:** Remember, you can use the `Kind.is.*` method to check if some given value is a class/module with all type checkers above.
|
384
483
|
|
385
484
|
[⬆️ Back to Top](#table-of-contents-)
|
386
485
|
|
486
|
+
## Kind::Validator (ActiveModel::Validations)
|
487
|
+
|
488
|
+
This module enables the capability to validate types via [`ActiveModel::Validations >= 3.2, < 6.1.0`](https://api.rubyonrails.org/classes/ActiveModel/Validations.html). e.g
|
489
|
+
|
490
|
+
```ruby
|
491
|
+
class Person
|
492
|
+
include ActiveModel::Validations
|
493
|
+
|
494
|
+
attr_accessor :first_name, :last_name
|
495
|
+
|
496
|
+
validates :first_name, :last_name, kind: String
|
497
|
+
end
|
498
|
+
```
|
499
|
+
|
500
|
+
And to make use of it, you will need to do an explicitly require. e.g:
|
501
|
+
|
502
|
+
```ruby
|
503
|
+
# In some Gemfile
|
504
|
+
gem 'kind', require: 'kind/active_model/validation'
|
505
|
+
|
506
|
+
# In some .rb file
|
507
|
+
require 'kind/active_model/validation'
|
508
|
+
```
|
509
|
+
|
510
|
+
### Usage
|
511
|
+
|
512
|
+
**[Object#kind_of?](https://ruby-doc.org/core-2.6.4/Object.html#method-i-kind_of-3F)**
|
513
|
+
|
514
|
+
```ruby
|
515
|
+
validates :name, kind: { of: String }
|
516
|
+
|
517
|
+
# Use an array to verify if the attribute
|
518
|
+
# is an instance of one of the classes/modules.
|
519
|
+
|
520
|
+
validates :status, kind: { of: [String, Symbol]}
|
521
|
+
```
|
522
|
+
|
523
|
+
**[Kind.is](#verifying-the-kind-of-some-classmodule)**
|
524
|
+
|
525
|
+
```ruby
|
526
|
+
#
|
527
|
+
# Verifying if the attribute value is the class or a subclass.
|
528
|
+
#
|
529
|
+
class Human; end
|
530
|
+
class Person < Human; end
|
531
|
+
class User < Human; end
|
532
|
+
|
533
|
+
validates :human_kind, kind: { is: Human }
|
534
|
+
|
535
|
+
#
|
536
|
+
# Verifying if the attribute value is the module or if it is a class that includes the module
|
537
|
+
#
|
538
|
+
module Human; end
|
539
|
+
class Person; include Human; end
|
540
|
+
class User; include Human; end
|
541
|
+
|
542
|
+
validates :human_kind, kind: { is: Human }
|
543
|
+
|
544
|
+
#
|
545
|
+
# Verifying if the attribute value is the module or if it is a module that extends the module
|
546
|
+
#
|
547
|
+
module Human; end
|
548
|
+
module Person; extend Human; end
|
549
|
+
module User; extend Human; end
|
550
|
+
|
551
|
+
validates :human_kind, kind: { is: Human }
|
552
|
+
|
553
|
+
# or use an array to verify if the attribute
|
554
|
+
# is a kind of one those classes/modules.
|
555
|
+
|
556
|
+
validates :human_kind, kind: { is: [Person, User] }
|
557
|
+
```
|
558
|
+
|
559
|
+
**[Object#instance_of?](https://ruby-doc.org/core-2.6.4/Object.html#method-i-instance_of-3F)**
|
560
|
+
|
561
|
+
```ruby
|
562
|
+
validates :name, kind: { instance_of: String }
|
563
|
+
|
564
|
+
# or use an array to verify if the attribute
|
565
|
+
# is an instance of one of the classes/modules.
|
566
|
+
|
567
|
+
validates :name, kind: { instance_of: [String, Symbol] }
|
568
|
+
```
|
569
|
+
|
570
|
+
|
571
|
+
**[Object#respond_to?](https://ruby-doc.org/core-2.6.4/Object.html#method-i-respond_to-3F)**
|
572
|
+
|
573
|
+
```ruby
|
574
|
+
validates :handler, kind: { respond_to: :call }
|
575
|
+
```
|
576
|
+
|
577
|
+
**Array.new.all? { |item| item.kind_of?(Class) }**
|
578
|
+
|
579
|
+
```ruby
|
580
|
+
validates :account_types, kind: { array_of: String }
|
581
|
+
|
582
|
+
# or use an array to verify if the attribute
|
583
|
+
# is an instance of one of the classes
|
584
|
+
|
585
|
+
validates :account_types, kind: { array_of: [String, Symbol] }
|
586
|
+
```
|
587
|
+
|
588
|
+
**Array.new.all? { |item| expected_values.include?(item) }**
|
589
|
+
|
590
|
+
```ruby
|
591
|
+
# Verifies if the attribute value
|
592
|
+
# is an array with some or all the expected values.
|
593
|
+
|
594
|
+
validates :account_types, kind: { array_with: ['foo', 'bar'] }
|
595
|
+
```
|
596
|
+
|
597
|
+
#### Defining the default validation strategy
|
598
|
+
|
599
|
+
By default, you can define the attribute type directly (without a hash). e.g.
|
600
|
+
|
601
|
+
```ruby
|
602
|
+
validates :name, kind: String
|
603
|
+
# or
|
604
|
+
validates :name, kind: [String, Symbol]
|
605
|
+
```
|
606
|
+
|
607
|
+
To changes this behavior you can set another strategy to validates the attributes types:
|
608
|
+
|
609
|
+
```ruby
|
610
|
+
Kind::Validator.default_strategy = :instance_of
|
611
|
+
|
612
|
+
# Tip: Create an initializer if you are in a Rails application.
|
613
|
+
```
|
614
|
+
|
615
|
+
And these are the available options to define the default strategy:
|
616
|
+
- `kind_of` *(default)*
|
617
|
+
- `instance_of`
|
618
|
+
|
619
|
+
#### Using the `allow_nil` and `strict` options
|
620
|
+
|
621
|
+
You can use the `allow_nil` option with any of the kind validations. e.g.
|
622
|
+
|
623
|
+
```ruby
|
624
|
+
validates :name, kind: String, allow_nil: true
|
625
|
+
```
|
626
|
+
|
627
|
+
And as any active model validation, kind validations works with the `strict: true`
|
628
|
+
option and with the `validates!` method. e.g.
|
629
|
+
|
630
|
+
```ruby
|
631
|
+
validates :first_name, kind: String, strict: true
|
632
|
+
# or
|
633
|
+
validates! :last_name, kind: String
|
634
|
+
```
|
635
|
+
|
636
|
+
[⬆️ Back to Top](#table-of-contents-)
|
637
|
+
|
387
638
|
## Kind::Undefined
|
388
639
|
|
389
640
|
The [`Kind::Undefined`](https://github.com/serradura/kind/blob/834f6b8ebdc737de8e5628986585f30c1a5aa41b/lib/kind/undefined.rb) constant is used as the default argument of type checkers. This is necessary [to know if no arguments were passed to the type check methods](https://github.com/serradura/kind/blob/834f6b8ebdc737de8e5628986585f30c1a5aa41b/lib/kind.rb#L45-L48). But, you can use it in your codebase too, especially if you need to distinguish the usage of `nil` as a method argument.
|
@@ -448,7 +699,27 @@ puts optional.value_or(1) # 1
|
|
448
699
|
puts optional.value_or { 1 } # 1
|
449
700
|
```
|
450
701
|
|
451
|
-
|
702
|
+
#### Replacing blocks by lambdas
|
703
|
+
|
704
|
+
```ruby
|
705
|
+
Add = -> params do
|
706
|
+
a, b = Kind.of.Hash(params, or: Empty::HASH).values_at(:a, :b)
|
707
|
+
|
708
|
+
a + b if Kind.of.Numeric?(a, b)
|
709
|
+
end
|
710
|
+
|
711
|
+
# --
|
712
|
+
|
713
|
+
Kind::Maybe.new(a: 1, b: 2).map(&Add).value_or(0) # 3
|
714
|
+
|
715
|
+
# --
|
716
|
+
|
717
|
+
Kind::Maybe.new([]).map(&Add).value_or(0) # 0
|
718
|
+
Kind::Maybe.new({}).map(&Add).value_or(0) # 0
|
719
|
+
Kind::Maybe.new(nil).map(&Add).value_or(0) # 0
|
720
|
+
```
|
721
|
+
|
722
|
+
### Kind::Maybe[], Kind::Maybe.wrap() and Kind::Maybe#then method aliases
|
452
723
|
|
453
724
|
You can use `Kind::Maybe[]` (brackets) instead of the `.new` to transform values in a `Kind::Maybe`. Another alias is `.then` to the `.map` method.
|
454
725
|
|
@@ -462,43 +733,70 @@ result =
|
|
462
733
|
puts result # 42
|
463
734
|
```
|
464
735
|
|
465
|
-
|
736
|
+
You can also use `Kind::Maybe.wrap()` instead of the `.new` method.
|
466
737
|
|
467
|
-
|
738
|
+
```ruby
|
739
|
+
result =
|
740
|
+
Kind::Maybe
|
741
|
+
.wrap(5)
|
742
|
+
.then { |value| value * 5 }
|
743
|
+
.then { |value| value + 17 }
|
744
|
+
.value_or(0)
|
745
|
+
|
746
|
+
puts result # 42
|
747
|
+
```
|
748
|
+
|
749
|
+
#### Replacing blocks by lambdas
|
468
750
|
|
469
751
|
```ruby
|
470
|
-
|
752
|
+
Add = -> params do
|
753
|
+
a, b = Kind.of.Hash(params, or: Empty::HASH).values_at(:a, :b)
|
471
754
|
|
472
|
-
|
755
|
+
a + b if Kind.of.Numeric.instance?(a, b)
|
756
|
+
end
|
473
757
|
|
474
|
-
|
758
|
+
# --
|
475
759
|
|
476
|
-
|
760
|
+
Kind::Maybe[a: 1, b: 2].then(&Add).value_or(0) # 3
|
477
761
|
|
478
|
-
|
762
|
+
# --
|
479
763
|
|
480
|
-
|
481
|
-
#
|
482
|
-
|
764
|
+
Kind::Maybe[1].then(&Add).value_or(0) # 0
|
765
|
+
Kind::Maybe['2'].then(&Add).value_or(0) # 0
|
766
|
+
Kind::Maybe[nil].then(&Add).value_or(0) # 0
|
767
|
+
```
|
483
768
|
|
484
|
-
|
769
|
+
### Kind::None() and Kind::Some()
|
485
770
|
|
486
|
-
|
771
|
+
If you need to ensure the return of `Kind::Maybe` results from your methods/lambdas,
|
772
|
+
you could use the methods `Kind::None` and `Kind::Some` to do this. e.g:
|
487
773
|
|
488
|
-
|
774
|
+
```ruby
|
775
|
+
Add = -> params do
|
776
|
+
a, b = Kind.of.Hash(params, or: Empty::HASH).values_at(:a, :b)
|
489
777
|
|
490
|
-
|
491
|
-
# Kind::Undefined value #
|
492
|
-
#########################
|
778
|
+
return Kind::None unless Kind.of.Numeric?(a, b)
|
493
779
|
|
494
|
-
|
780
|
+
Kind::Some(a + b)
|
781
|
+
end
|
495
782
|
|
496
|
-
|
783
|
+
# --
|
497
784
|
|
498
|
-
|
499
|
-
|
785
|
+
Add.call(1) # #<Kind::Maybe::None:0x0000... @value=nil>
|
786
|
+
Add.call({}) # #<Kind::Maybe::None:0x0000... @value=nil>
|
787
|
+
Add.call(a: 1) # #<Kind::Maybe::None:0x0000... @value=nil>
|
788
|
+
Add.call(b: 2) # #<Kind::Maybe::None:0x0000... @value=nil>
|
500
789
|
|
501
|
-
|
790
|
+
Add.call(a:1, b: 2) # #<Kind::Maybe::Some:0x0000... @value=3>
|
791
|
+
|
792
|
+
# --
|
793
|
+
|
794
|
+
Kind::Maybe[a: 1, b: 2].then(&Add).value_or(0) # 3
|
795
|
+
|
796
|
+
Kind::Maybe[1].then(&Add).value_or(0) # 0
|
797
|
+
Kind::Maybe['2'].then(&Add).value_or(0) # 0
|
798
|
+
Kind::Maybe[nil].then(&Add).value_or(0) # 0
|
799
|
+
```
|
502
800
|
|
503
801
|
### Kind.of.Maybe()
|
504
802
|
|
@@ -555,6 +853,26 @@ result2 =
|
|
555
853
|
puts result2 # 35
|
556
854
|
```
|
557
855
|
|
856
|
+
#### Replacing blocks by lambdas
|
857
|
+
|
858
|
+
```ruby
|
859
|
+
Add = -> params do
|
860
|
+
a, b = Kind.of.Hash(params, or: Empty::HASH).values_at(:a, :b)
|
861
|
+
|
862
|
+
a + b if Kind.of.Numeric.instance?(a, b)
|
863
|
+
end
|
864
|
+
|
865
|
+
# --
|
866
|
+
|
867
|
+
Kind::Optional[a: 1, b: 2].then(&Add).value_or(0) # 3
|
868
|
+
|
869
|
+
# --
|
870
|
+
|
871
|
+
Kind::Optional[1].then(&Add).value_or(0) # 0
|
872
|
+
Kind::Optional['2'].then(&Add).value_or(0) # 0
|
873
|
+
Kind::Optional[nil].then(&Add).value_or(0) # 0
|
874
|
+
```
|
875
|
+
|
558
876
|
**Note:** The `Kind.of.Optional` is available to check if some value is a `Kind::Optional`.
|
559
877
|
|
560
878
|
[⬆️ Back to Top](#table-of-contents-)
|
@@ -566,10 +884,11 @@ In these scenarios, you could check the given input type as optional and avoid u
|
|
566
884
|
|
567
885
|
```ruby
|
568
886
|
def person_name(params)
|
569
|
-
Kind::Of::Hash
|
570
|
-
|
571
|
-
|
572
|
-
|
887
|
+
Kind::Of::Hash
|
888
|
+
.as_optional(params)
|
889
|
+
.map { |data| data if data.values_at(:first_name, :last_name).compact.size == 2 }
|
890
|
+
.map { |data| "#{data[:first_name]} #{data[:last_name]}" }
|
891
|
+
.value_or { 'John Doe' }
|
573
892
|
end
|
574
893
|
|
575
894
|
person_name('') # "John Doe"
|
@@ -590,9 +909,20 @@ def person_name(params)
|
|
590
909
|
'John Doe'
|
591
910
|
end
|
592
911
|
end
|
912
|
+
|
913
|
+
#
|
914
|
+
# You can also use Kind::Optional(<Type>) to achieve the same behavior
|
915
|
+
#
|
916
|
+
def person_name(params)
|
917
|
+
Kind::Optional(Hash)
|
918
|
+
.wrap(params)
|
919
|
+
.map { |data| data if data.values_at(:first_name, :last_name).compact.size == 2 }
|
920
|
+
.map { |data| "#{data[:first_name]} #{data[:last_name]}" }
|
921
|
+
.value_or { 'John Doe' }
|
922
|
+
end
|
593
923
|
```
|
594
924
|
|
595
|
-
> Note: You could use the `.as_optional` method (or it alias
|
925
|
+
> Note: You could use the `.as_optional` method (or it alias `.as_maybe`) with any [type checker](https://github.com/serradura/kind/blob/b177fed9cc2b3347d63963a2a2fd99f989c51a9a/README.md#type-checkers).
|
596
926
|
|
597
927
|
Let's see another example using a collection and how the method `.as_optional` works when it receives no argument.
|
598
928
|
|
@@ -666,136 +996,97 @@ end
|
|
666
996
|
|
667
997
|
[⬆️ Back to Top](#table-of-contents-)
|
668
998
|
|
669
|
-
|
999
|
+
### Kind::Maybe(<Type>)
|
670
1000
|
|
671
|
-
|
1001
|
+
There is an alternative to `Kind.of.\<Type\>.as_optional`, you can use `Kind::Optional(<Type>)` to create a maybe monad which will return None if the given input hasn't the expected type. e.g:
|
672
1002
|
|
673
1003
|
```ruby
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
|
1004
|
+
result1 =
|
1005
|
+
Kind::Maybe(Numeric)
|
1006
|
+
.wrap(5)
|
1007
|
+
.then { |value| value * 5 }
|
1008
|
+
.value_or { 0 }
|
678
1009
|
|
679
|
-
|
680
|
-
end
|
681
|
-
```
|
1010
|
+
puts result1 # 25
|
682
1011
|
|
683
|
-
|
1012
|
+
# ---
|
684
1013
|
|
685
|
-
|
686
|
-
|
687
|
-
|
1014
|
+
result2 =
|
1015
|
+
Kind::Optional(Numeric)
|
1016
|
+
.wrap('5')
|
1017
|
+
.then { |value| value * 5 }
|
1018
|
+
.value_or { 0 }
|
688
1019
|
|
689
|
-
|
690
|
-
require 'kind/active_model/validation'
|
1020
|
+
puts result2 # 0
|
691
1021
|
```
|
692
1022
|
|
693
|
-
|
694
|
-
|
695
|
-
**[Object#kind_of?](https://ruby-doc.org/core-2.6.4/Object.html#method-i-kind_of-3F)**
|
1023
|
+
This typed maybe has the same methods of `Kind::Maybe` class. e.g:
|
696
1024
|
|
697
1025
|
```ruby
|
698
|
-
|
699
|
-
|
700
|
-
|
1026
|
+
Kind::Maybe(Numeric)[5]
|
1027
|
+
Kind::Maybe(Numeric).new(5)
|
1028
|
+
Kind::Maybe(Numeric).wrap(5)
|
701
1029
|
|
702
|
-
#
|
703
|
-
# is an instance of one of the classes/modules.
|
1030
|
+
# ---
|
704
1031
|
|
705
|
-
|
706
|
-
|
707
|
-
|
1032
|
+
Kind::Optional(Numeric)[5]
|
1033
|
+
Kind::Optional(Numeric).new(5)
|
1034
|
+
Kind::Optional(Numeric).wrap(5)
|
708
1035
|
```
|
709
1036
|
|
710
|
-
|
711
|
-
|
712
|
-
```ruby
|
713
|
-
validates :name, kind: { instance_of: String }
|
714
|
-
|
715
|
-
# or use an array to verify if the attribute
|
716
|
-
# is an instance of one of the classes/modules.
|
717
|
-
|
718
|
-
validates :name, kind: { instance_of: [String, Symbol] }
|
719
|
-
```
|
1037
|
+
[⬆️ Back to Top](#table-of-contents-)
|
720
1038
|
|
1039
|
+
### Kind::Maybe#try
|
721
1040
|
|
722
|
-
|
1041
|
+
If you don't want to use `#map/#then` to access the value, you could use the `#try` method to access it. So, if the value wasn't `nil` or `Kind::Undefined`, the some monad will be returned.
|
723
1042
|
|
724
1043
|
```ruby
|
725
|
-
|
726
|
-
```
|
1044
|
+
object = 'foo'
|
727
1045
|
|
728
|
-
|
1046
|
+
Kind::Maybe[object].try(:upcase).value # "FOO"
|
729
1047
|
|
730
|
-
|
731
|
-
# Verifies if the attribute value is the class or a subclass.
|
1048
|
+
Kind::Maybe[{}].try(:fetch, :number, 0).value # 0
|
732
1049
|
|
733
|
-
|
1050
|
+
Kind::Maybe[{number: 1}].try(:fetch, :number).value # 1
|
734
1051
|
|
735
|
-
|
1052
|
+
Kind::Maybe[object].try { |value| value.upcase }.value # "FOO"
|
736
1053
|
|
737
|
-
|
738
|
-
|
1054
|
+
#############
|
1055
|
+
# Nil value #
|
1056
|
+
#############
|
739
1057
|
|
740
|
-
|
1058
|
+
object = nil
|
741
1059
|
|
742
|
-
|
743
|
-
validates :account_types, kind: { array_of: String }
|
1060
|
+
Kind::Maybe[object].try(:upcase).value # nil
|
744
1061
|
|
745
|
-
|
746
|
-
# is an instance of one of the classes
|
1062
|
+
Kind::Maybe[object].try { |value| value.upcase }.value # nil
|
747
1063
|
|
748
|
-
|
749
|
-
|
1064
|
+
#########################
|
1065
|
+
# Kind::Undefined value #
|
1066
|
+
#########################
|
750
1067
|
|
751
|
-
|
1068
|
+
object = Kind::Undefined
|
752
1069
|
|
753
|
-
|
754
|
-
# Verifies if the attribute value
|
755
|
-
# is an array with some or all the expected values.
|
1070
|
+
Kind::Maybe[object].try(:upcase).value # nil
|
756
1071
|
|
757
|
-
|
1072
|
+
Kind::Maybe[object].try { |value| value.upcase }.value # nil
|
758
1073
|
```
|
759
1074
|
|
760
|
-
|
1075
|
+
> **Note:** You can use the `#try` method with `Kind::Optional` objects.
|
761
1076
|
|
762
|
-
|
1077
|
+
[⬆️ Back to Top](#table-of-contents-)
|
763
1078
|
|
764
|
-
|
765
|
-
validates :name, kind: String
|
766
|
-
# or
|
767
|
-
validates :name, kind: [String, Symbol]
|
768
|
-
```
|
1079
|
+
### Kind::Maybe#try!
|
769
1080
|
|
770
|
-
|
1081
|
+
Has the same behavior of its `#try`, but it will raise an error if the value doesn't respond to the expected method.
|
771
1082
|
|
772
1083
|
```ruby
|
773
|
-
Kind::
|
774
|
-
|
775
|
-
# Tip: Create an initializer if you are in a Rails application.
|
776
|
-
```
|
777
|
-
|
778
|
-
And these are the available options to define the default strategy:
|
779
|
-
- `is_a`
|
780
|
-
- `kind_of` *(default)*
|
781
|
-
- `instance_of`
|
782
|
-
|
783
|
-
#### Using the `allow_nil` and `strict` options
|
784
|
-
|
785
|
-
You can use the `allow_nil` option with any of the kind validations. e.g.
|
1084
|
+
Kind::Maybe[{}].try(:upcase) # => #<Kind::Maybe::None:0x0000... @value=nil>
|
786
1085
|
|
787
|
-
|
788
|
-
validates :name, kind: String, allow_nil: true
|
1086
|
+
Kind::Maybe[{}].try!(:upcase) # => NoMethodError (undefined method `upcase' for {}:Hash)
|
789
1087
|
```
|
790
1088
|
|
791
|
-
|
792
|
-
option and with the `validates!` method. e.g.
|
793
|
-
|
794
|
-
```ruby
|
795
|
-
validates :first_name, kind: String, strict: true
|
796
|
-
# or
|
797
|
-
validates! :last_name, kind: String
|
798
|
-
```
|
1089
|
+
> **Note:** You can also use the `#try!` method with `Kind::Optional` objects.
|
799
1090
|
|
800
1091
|
[⬆️ Back to Top](#table-of-contents-)
|
801
1092
|
|