kind 1.9.0 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
|