kind 1.8.0 → 2.3.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.sh +20 -0
- data/.travis.yml +7 -2
- data/Gemfile +31 -6
- data/README.md +478 -75
- data/kind.gemspec +2 -2
- data/lib/kind.rb +55 -48
- data/lib/kind/active_model/kind_validator.rb +96 -0
- data/lib/kind/active_model/validation.rb +7 -0
- 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 +12 -2
- data/lib/kind/maybe.rb +60 -10
- data/lib/kind/types.rb +15 -4
- data/lib/kind/undefined.rb +1 -1
- data/lib/kind/validator.rb +40 -0
- data/lib/kind/version.rb +1 -1
- data/test.sh +11 -0
- metadata +11 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a234e4c63dea23906a65da0718515bff19ac7934a3e919535596042cfd605bba
|
4
|
+
data.tar.gz: 5bcb0f1f669a0e87de070c5095cf8f769ddf97dde4b2169060fb21769ffbd623
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 232d87a65618b51c3ec8756ad9d08bce53a49d50368d4629c8e694050502dddf12cdecd878193e40e09c8fcc13139b1a35383eedaa36a1660f7cf71f7e4d9810
|
7
|
+
data.tar.gz: 4cef62a76d8981fa7409e0d598448b35b4bb01794084ad2ab264741cbf0a78e6b9142352369892e124f130750847078f90297e5d1eda955881ebea0f13e96ec7
|
data/.travis.sh
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
|
3
|
+
bundle exec rake test
|
4
|
+
|
5
|
+
ruby_v=$(ruby -v)
|
6
|
+
|
7
|
+
ACTIVEMODEL_VERSION='3.2' bundle update && bundle exec rake test
|
8
|
+
ACTIVEMODEL_VERSION='4.0' bundle update && bundle exec rake test
|
9
|
+
ACTIVEMODEL_VERSION='4.1' bundle update && bundle exec rake test
|
10
|
+
ACTIVEMODEL_VERSION='4.2' bundle update && bundle exec rake test
|
11
|
+
ACTIVEMODEL_VERSION='5.0' bundle update && bundle exec rake test
|
12
|
+
ACTIVEMODEL_VERSION='5.1' bundle update && bundle exec rake test
|
13
|
+
|
14
|
+
if [[ ! $ruby_v =~ '2.2.0' ]]; then
|
15
|
+
ACTIVEMODEL_VERSION='5.2' bundle update && bundle exec rake test
|
16
|
+
fi
|
17
|
+
|
18
|
+
if [[ $ruby_v =~ '2.5.' ]] || [[ $ruby_v =~ '2.6.' ]] || [[ $ruby_v =~ '2.7.' ]]; then
|
19
|
+
ACTIVEMODEL_VERSION='6.0' bundle update && bundle exec rake test
|
20
|
+
fi
|
data/.travis.yml
CHANGED
@@ -1,7 +1,12 @@
|
|
1
1
|
---
|
2
2
|
language: ruby
|
3
3
|
|
4
|
-
|
4
|
+
sudo: false
|
5
|
+
|
6
|
+
cache:
|
7
|
+
bundler: true
|
8
|
+
directories:
|
9
|
+
- /home/travis/.rvm/
|
5
10
|
|
6
11
|
rvm:
|
7
12
|
- 2.2.0
|
@@ -22,7 +27,7 @@ before_script:
|
|
22
27
|
- chmod +x ./cc-test-reporter
|
23
28
|
- "./cc-test-reporter before-build"
|
24
29
|
|
25
|
-
script:
|
30
|
+
script: "./.travis.sh"
|
26
31
|
|
27
32
|
after_success:
|
28
33
|
- "./cc-test-reporter after-build -t simplecov"
|
data/Gemfile
CHANGED
@@ -1,8 +1,33 @@
|
|
1
|
-
source
|
1
|
+
source 'https://rubygems.org'
|
2
2
|
|
3
|
-
|
4
|
-
|
3
|
+
git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
|
4
|
+
|
5
|
+
activemodel_version = ENV['ACTIVEMODEL_VERSION']
|
6
|
+
|
7
|
+
activemodel = case activemodel_version
|
8
|
+
when '3.2' then '3.2.22'
|
9
|
+
when '4.0' then '4.0.13'
|
10
|
+
when '4.1' then '4.1.16'
|
11
|
+
when '4.2' then '4.2.11'
|
12
|
+
when '5.0' then '5.0.7'
|
13
|
+
when '5.1' then '5.1.7'
|
14
|
+
when '5.2' then '5.2.4'
|
15
|
+
when '6.0' then '6.0.0'
|
16
|
+
end
|
17
|
+
|
18
|
+
group :test do
|
19
|
+
if activemodel_version
|
20
|
+
gem 'activesupport', activemodel, require: false
|
21
|
+
gem 'activemodel', activemodel, require: false
|
22
|
+
gem 'minitest', activemodel_version < '4.1' ? '~> 4.2' : '~> 5.0'
|
23
|
+
else
|
24
|
+
gem 'minitest', '~> 5.0'
|
25
|
+
end
|
5
26
|
|
6
|
-
gem
|
7
|
-
|
8
|
-
|
27
|
+
gem 'simplecov', require: false
|
28
|
+
end
|
29
|
+
|
30
|
+
gem 'rake', '~> 13.0'
|
31
|
+
|
32
|
+
# Specify your gem's dependencies in type_validator.gemspec
|
33
|
+
gemspec
|
data/README.md
CHANGED
@@ -6,36 +6,48 @@
|
|
6
6
|
|
7
7
|
# Kind <!-- omit in toc -->
|
8
8
|
|
9
|
-
|
9
|
+
A simple type system (at runtime) for Ruby - free of dependencies.
|
10
10
|
|
11
11
|
**Motivation:**
|
12
12
|
|
13
13
|
As a creator of Ruby gems, I have a common need that I have to handle in many of my projects: type checking of method arguments.
|
14
14
|
|
15
|
-
One of the goals of this project is to do simple type checking like `"some string".is_a?(String)`, but, exposing useful abstractions to do it.
|
15
|
+
One of the goals of this project is to do simple type checking like `"some string".is_a?(String)`, but, exposing useful abstractions to do it. e.g: [Kind.of.\<Type\> methods](#verifying-the-kind-of-some-object), [active model validations](#kindvalidator-activemodelvalidations), [maybe monad](#kindmaybe).
|
16
16
|
|
17
17
|
## Table of Contents <!-- omit in toc -->
|
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)
|
38
|
-
|
47
|
+
- [Replacing blocks by lambdas](#replacing-blocks-by-lambdas-2)
|
48
|
+
- [Kind.of.\<Type\>.as_optional](#kindoftypeas_optional)
|
49
|
+
- [Kind::Maybe(<Type>)](#kindmaybetype)
|
50
|
+
- [Kind::Maybe#try](#kindmaybetry)
|
39
51
|
- [Kind::Empty](#kindempty)
|
40
52
|
- [Similar Projects](#similar-projects)
|
41
53
|
- [Development](#development)
|
@@ -78,14 +90,14 @@ sum(1, 1) # 2
|
|
78
90
|
sum('1', 1) # Kind::Error ("\"1\" expected to be a kind of Numeric")
|
79
91
|
```
|
80
92
|
|
81
|
-
### Verifying the kind of some object
|
93
|
+
### Kind.of.\<Type\>() - Verifying the kind of some object
|
82
94
|
|
83
95
|
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.
|
84
96
|
|
85
97
|
```ruby
|
86
|
-
Kind.of.Hash(nil)
|
87
|
-
Kind.of.Hash('')
|
88
|
-
Kind.of.Hash(
|
98
|
+
Kind.of.Hash(nil) # **raise Kind::Error, "nil expected to be a kind of Hash"**
|
99
|
+
Kind.of.Hash('') # raise Kind::Error, "'' expected to be a kind of Hash"
|
100
|
+
Kind.of.Hash(a: 1) # {a: 1}
|
89
101
|
|
90
102
|
# ---
|
91
103
|
|
@@ -94,6 +106,15 @@ Kind.of.Boolean(true) # true
|
|
94
106
|
Kind.of.Boolean(false) # false
|
95
107
|
```
|
96
108
|
|
109
|
+
> **Note:** `Kind.of.<Type>` supports the to_proc protocol.
|
110
|
+
> And it will perform a strict validation as expected.
|
111
|
+
|
112
|
+
```ruby
|
113
|
+
collection = [ {number: 1}, 'number 2', {number: 3}, :number_4 ]
|
114
|
+
|
115
|
+
collection.map(&Kind.of.Hash) # Kind::Error ("number 2" expected to be a kind of Hash)
|
116
|
+
```
|
117
|
+
|
97
118
|
When the verified value is nil, it is possible to define a default value with the same type to be returned.
|
98
119
|
|
99
120
|
```ruby
|
@@ -106,7 +127,25 @@ Kind.of.Hash(value, or: {}) # {}
|
|
106
127
|
Kind.of.Boolean(nil, or: true) # true
|
107
128
|
```
|
108
129
|
|
109
|
-
As an alternative syntax, you can use the `Kind::Of` instead of the `Kind.of` method. e.g: `Kind::Of::Hash('')`
|
130
|
+
> **Note:** As an alternative syntax, you can use the `Kind::Of` instead of the `Kind.of` method. e.g: `Kind::Of::Hash('')`
|
131
|
+
|
132
|
+
#### Method aliases to perform a strict validation
|
133
|
+
|
134
|
+
```ruby
|
135
|
+
Kind.of.Hash[nil] # raise Kind::Error, "nil expected to be a kind of Hash"
|
136
|
+
Kind.of.Hash[''] # raise Kind::Error, "'' expected to be a kind of Hash"
|
137
|
+
Kind.of.Hash[a: 1] # {a: 1}
|
138
|
+
Kind.of.Hash['', or: {}] # {}
|
139
|
+
|
140
|
+
# or
|
141
|
+
|
142
|
+
Kind.of.Hash.instance(nil) # raise Kind::Error, "nil expected to be a kind of Hash"
|
143
|
+
Kind.of.Hash.instance('') # raise Kind::Error, "'' expected to be a kind of Hash"
|
144
|
+
Kind.of.Hash.instance(a: 1) # {a: 1}
|
145
|
+
Kind.of.Hash.instance('', or: {}) # {}
|
146
|
+
```
|
147
|
+
|
148
|
+
### Kind.of.\<Type\>.or_nil()
|
110
149
|
|
111
150
|
But if you don't need a strict type verification, use the `.or_nil` method.
|
112
151
|
|
@@ -120,51 +159,62 @@ Kind.of.Boolean.or_nil('') # nil
|
|
120
159
|
Kind.of.Boolean.or_nil(true) # true
|
121
160
|
```
|
122
161
|
|
123
|
-
|
162
|
+
### Kind.of.\<Type\>.instance?()
|
163
|
+
|
164
|
+
Use the method `.instance?` to verify if the given object has the expected type.
|
124
165
|
|
125
166
|
```ruby
|
126
|
-
Kind.of.Hash.instance?(
|
127
|
-
#
|
167
|
+
Kind.of.Hash.instance?({}) # true
|
168
|
+
Kind.of.Hash.instance?({}, HashWithIndifferentAccess.new) # true
|
169
|
+
|
170
|
+
Kind.of.Hash.instance?('') # false
|
171
|
+
Kind.of.Hash.instance?({}, '') # false
|
128
172
|
|
129
173
|
# ---
|
130
174
|
|
131
|
-
Kind.of.Boolean.instance?(
|
132
|
-
Kind.of.Boolean.instance?(true)
|
133
|
-
|
175
|
+
Kind.of.Boolean.instance?(true) # true
|
176
|
+
Kind.of.Boolean.instance?(true, false) # true
|
177
|
+
|
178
|
+
Kind.of.Boolean.instance?(nil) # false
|
179
|
+
Kind.of.Boolean.instance?(false, true, nil) # false
|
134
180
|
```
|
135
181
|
|
136
|
-
**Note:** When `.instance?` is called without an argument,
|
182
|
+
> **Note:** When `.instance?` is called without an argument,
|
183
|
+
> it will return a lambda which will perform the kind verification.
|
137
184
|
|
138
185
|
```ruby
|
139
|
-
collection = [
|
140
|
-
{number: 1},
|
141
|
-
'number 0',
|
142
|
-
{number: 2},
|
143
|
-
[0],
|
144
|
-
]
|
186
|
+
collection = [ {number: 1}, 'number 2', {number: 3}, :number_4 ]
|
145
187
|
|
146
188
|
collection
|
147
|
-
.select(&Kind.of.Hash.instance?)
|
148
|
-
.reduce(0) { |total, item| total + item.fetch(:number, 0) } # 3
|
189
|
+
.select(&Kind.of.Hash.instance?) # [{:number=>1}, {:number=>3}]
|
149
190
|
```
|
150
191
|
|
151
|
-
|
192
|
+
> **Note:** You can use a different syntax to perform an instance verification.
|
193
|
+
> To do this, use Kind.of.\<Type\>?()
|
152
194
|
|
153
195
|
```ruby
|
154
|
-
Kind.of.Hash
|
155
|
-
Kind.of.Hash
|
156
|
-
Kind.of.Hash[a: 1] # {a: 1}
|
157
|
-
Kind.of.Hash['', or: {}] # {}
|
196
|
+
Kind.of.Hash?({}) # true
|
197
|
+
Kind.of.Hash?({}, HashWithIndifferentAccess.new) # true
|
158
198
|
|
159
|
-
#
|
199
|
+
Kind.of.Hash?('') # false
|
200
|
+
Kind.of.Hash?({}, '') # false
|
160
201
|
|
161
|
-
|
162
|
-
|
163
|
-
Kind.of.
|
164
|
-
Kind.of.
|
202
|
+
# ---
|
203
|
+
|
204
|
+
Kind.of.Boolean?(true) # true
|
205
|
+
Kind.of.Boolean?(false, true) # true
|
206
|
+
|
207
|
+
Kind.of.Boolean?(nil) # false
|
208
|
+
Kind.of.Boolean?(false, true, nil) # false
|
209
|
+
|
210
|
+
# ---
|
211
|
+
|
212
|
+
collection = [ {number: 1}, 'number 2', {number: 3}, :number_4 ]
|
213
|
+
|
214
|
+
collection.select(&Kind.of.Hash?) # [{:number=>1}, {:number=>3}]
|
165
215
|
```
|
166
216
|
|
167
|
-
###
|
217
|
+
### Kind.is.\<Type\>() - Verifying if some class/module is the expected kind.
|
168
218
|
|
169
219
|
You can use `Kind.is` to verify if some class has the expected type as its ancestor.
|
170
220
|
|
@@ -184,6 +234,49 @@ Kind.of.Hash.class?(Hash) # true
|
|
184
234
|
Kind.of.Hash.class?(ActiveSupport::HashWithIndifferentAccess) # true
|
185
235
|
```
|
186
236
|
|
237
|
+
> **Note:** The `Kind.is` could check the inheritance of Classes/Modules.
|
238
|
+
|
239
|
+
```ruby
|
240
|
+
#
|
241
|
+
# Verifying if the attribute value is the class or a subclass.
|
242
|
+
#
|
243
|
+
class Human; end
|
244
|
+
class Person < Human; end
|
245
|
+
class User < Human; end
|
246
|
+
|
247
|
+
Kind.is(Human, User) # true
|
248
|
+
Kind.is(Human, Human) # true
|
249
|
+
Kind.is(Human, Person) # true
|
250
|
+
|
251
|
+
Kind.is(Human, Struct) # false
|
252
|
+
|
253
|
+
#
|
254
|
+
# Verifying if the attribute value is the module or if it is a class that includes the module
|
255
|
+
#
|
256
|
+
module Human; end
|
257
|
+
class Person; include Human; end
|
258
|
+
class User; include Human; end
|
259
|
+
|
260
|
+
Kind.is(Human, User) # true
|
261
|
+
Kind.is(Human, Human) # true
|
262
|
+
Kind.is(Human, Person) # true
|
263
|
+
|
264
|
+
Kind.is(Human, Struct) # false
|
265
|
+
|
266
|
+
#
|
267
|
+
# Verifying if the attribute value is the module or if it is a module that extends the module
|
268
|
+
#
|
269
|
+
module Human; end
|
270
|
+
module Person; extend Human; end
|
271
|
+
module User; extend Human; end
|
272
|
+
|
273
|
+
Kind.is(Human, User) # true
|
274
|
+
Kind.is(Human, Human) # true
|
275
|
+
Kind.is(Human, Person) # true
|
276
|
+
|
277
|
+
Kind.is(Human, Struct) # false
|
278
|
+
```
|
279
|
+
|
187
280
|
[⬆️ Back to Top](#table-of-contents-)
|
188
281
|
|
189
282
|
### How to create a new type checker?
|
@@ -208,6 +301,22 @@ Kind.of(User, {}) # Kind::Error ({} expected to be a kind of User)
|
|
208
301
|
Kind.of(Hash, {}) # {}
|
209
302
|
Kind.of(Hash, user) # Kind::Error (<User ...> expected to be a kind of Hash)
|
210
303
|
|
304
|
+
# ----------------------------------------- #
|
305
|
+
# Verifiyng if the value is a kind instance #
|
306
|
+
# ----------------------------------------- #
|
307
|
+
|
308
|
+
Kind.of?(Numeric, 1) # true
|
309
|
+
Kind.of?(Numeric, 1, 2.0) # true
|
310
|
+
|
311
|
+
Kind.of?(Numeric, '1') # false
|
312
|
+
Kind.of?(Numeric, 1, '2.0') # false
|
313
|
+
|
314
|
+
# Note: Kind.of?(Type) without arguments will return a
|
315
|
+
# lambda that will perform an instance verification
|
316
|
+
#
|
317
|
+
[1, '2', 3.0, '4']
|
318
|
+
.select(&Kind.of?(Numeric)) # [1, 3.0]
|
319
|
+
|
211
320
|
# ---------------------------------- #
|
212
321
|
# Creating type checkers dynamically #
|
213
322
|
# ---------------------------------- #
|
@@ -246,7 +355,7 @@ end
|
|
246
355
|
Kind.is(User, AdminUser) # true
|
247
356
|
```
|
248
357
|
|
249
|
-
#### Registering new (custom) type
|
358
|
+
#### Registering new (custom) type checker
|
250
359
|
|
251
360
|
Use `Kind::Types.add()`. e.g:
|
252
361
|
|
@@ -366,13 +475,165 @@ The list of types (classes and modules) available to use with `Kind.of.*` or `Ki
|
|
366
475
|
- `Kind.of.Module()`
|
367
476
|
- `Kind.of.Lambda()`
|
368
477
|
- `Kind.of.Boolean()`
|
369
|
-
- `Kind.of.Callable()`: verifies if the given value `respond_to?(:call)
|
478
|
+
- `Kind.of.Callable()`: verifies if the given value `respond_to?(:call)`.
|
370
479
|
- `Kind.of.Maybe()` or its alias `Kind.of.Optional()`
|
371
480
|
|
372
481
|
**Note:** Remember, you can use the `Kind.is.*` method to check if some given value is a class/module with all type checkers above.
|
373
482
|
|
374
483
|
[⬆️ Back to Top](#table-of-contents-)
|
375
484
|
|
485
|
+
## Kind::Validator (ActiveModel::Validations)
|
486
|
+
|
487
|
+
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
|
488
|
+
|
489
|
+
```ruby
|
490
|
+
class Person
|
491
|
+
include ActiveModel::Validations
|
492
|
+
|
493
|
+
attr_accessor :first_name, :last_name
|
494
|
+
|
495
|
+
validates :first_name, :last_name, kind: String
|
496
|
+
end
|
497
|
+
```
|
498
|
+
|
499
|
+
And to make use of it, you will need to do an explicitly require. e.g:
|
500
|
+
|
501
|
+
```ruby
|
502
|
+
# In some Gemfile
|
503
|
+
gem 'kind', require: 'kind/active_model/validation'
|
504
|
+
|
505
|
+
# In some .rb file
|
506
|
+
require 'kind/active_model/validation'
|
507
|
+
```
|
508
|
+
|
509
|
+
### Usage
|
510
|
+
|
511
|
+
**[Object#kind_of?](https://ruby-doc.org/core-2.6.4/Object.html#method-i-kind_of-3F)**
|
512
|
+
|
513
|
+
```ruby
|
514
|
+
validates :name, kind: { of: String }
|
515
|
+
|
516
|
+
# Use an array to verify if the attribute
|
517
|
+
# is an instance of one of the classes/modules.
|
518
|
+
|
519
|
+
validates :status, kind: { of: [String, Symbol]}
|
520
|
+
```
|
521
|
+
|
522
|
+
**[Kind.is](#verifying-the-kind-of-some-classmodule)**
|
523
|
+
|
524
|
+
```ruby
|
525
|
+
#
|
526
|
+
# Verifying if the attribute value is the class or a subclass.
|
527
|
+
#
|
528
|
+
class Human; end
|
529
|
+
class Person < Human; end
|
530
|
+
class User < Human; end
|
531
|
+
|
532
|
+
validates :human_kind, kind: { is: Human }
|
533
|
+
|
534
|
+
#
|
535
|
+
# Verifying if the attribute value is the module or if it is a class that includes the module
|
536
|
+
#
|
537
|
+
module Human; end
|
538
|
+
class Person; include Human; end
|
539
|
+
class User; include Human; end
|
540
|
+
|
541
|
+
validates :human_kind, kind: { is: Human }
|
542
|
+
|
543
|
+
#
|
544
|
+
# Verifying if the attribute value is the module or if it is a module that extends the module
|
545
|
+
#
|
546
|
+
module Human; end
|
547
|
+
module Person; extend Human; end
|
548
|
+
module User; extend Human; end
|
549
|
+
|
550
|
+
validates :human_kind, kind: { is: Human }
|
551
|
+
|
552
|
+
# or use an array to verify if the attribute
|
553
|
+
# is a kind of one those classes/modules.
|
554
|
+
|
555
|
+
validates :human_kind, kind: { is: [Person, User] }
|
556
|
+
```
|
557
|
+
|
558
|
+
**[Object#instance_of?](https://ruby-doc.org/core-2.6.4/Object.html#method-i-instance_of-3F)**
|
559
|
+
|
560
|
+
```ruby
|
561
|
+
validates :name, kind: { instance_of: String }
|
562
|
+
|
563
|
+
# or use an array to verify if the attribute
|
564
|
+
# is an instance of one of the classes/modules.
|
565
|
+
|
566
|
+
validates :name, kind: { instance_of: [String, Symbol] }
|
567
|
+
```
|
568
|
+
|
569
|
+
|
570
|
+
**[Object#respond_to?](https://ruby-doc.org/core-2.6.4/Object.html#method-i-respond_to-3F)**
|
571
|
+
|
572
|
+
```ruby
|
573
|
+
validates :handler, kind: { respond_to: :call }
|
574
|
+
```
|
575
|
+
|
576
|
+
**Array.new.all? { |item| item.kind_of?(Class) }**
|
577
|
+
|
578
|
+
```ruby
|
579
|
+
validates :account_types, kind: { array_of: String }
|
580
|
+
|
581
|
+
# or use an array to verify if the attribute
|
582
|
+
# is an instance of one of the classes
|
583
|
+
|
584
|
+
validates :account_types, kind: { array_of: [String, Symbol] }
|
585
|
+
```
|
586
|
+
|
587
|
+
**Array.new.all? { |item| expected_values.include?(item) }**
|
588
|
+
|
589
|
+
```ruby
|
590
|
+
# Verifies if the attribute value
|
591
|
+
# is an array with some or all the expected values.
|
592
|
+
|
593
|
+
validates :account_types, kind: { array_with: ['foo', 'bar'] }
|
594
|
+
```
|
595
|
+
|
596
|
+
#### Defining the default validation strategy
|
597
|
+
|
598
|
+
By default, you can define the attribute type directly (without a hash). e.g.
|
599
|
+
|
600
|
+
```ruby
|
601
|
+
validates :name, kind: String
|
602
|
+
# or
|
603
|
+
validates :name, kind: [String, Symbol]
|
604
|
+
```
|
605
|
+
|
606
|
+
To changes this behavior you can set another strategy to validates the attributes types:
|
607
|
+
|
608
|
+
```ruby
|
609
|
+
Kind::Validator.default_strategy = :instance_of
|
610
|
+
|
611
|
+
# Tip: Create an initializer if you are in a Rails application.
|
612
|
+
```
|
613
|
+
|
614
|
+
And these are the available options to define the default strategy:
|
615
|
+
- `kind_of` *(default)*
|
616
|
+
- `instance_of`
|
617
|
+
|
618
|
+
#### Using the `allow_nil` and `strict` options
|
619
|
+
|
620
|
+
You can use the `allow_nil` option with any of the kind validations. e.g.
|
621
|
+
|
622
|
+
```ruby
|
623
|
+
validates :name, kind: String, allow_nil: true
|
624
|
+
```
|
625
|
+
|
626
|
+
And as any active model validation, kind validations works with the `strict: true`
|
627
|
+
option and with the `validates!` method. e.g.
|
628
|
+
|
629
|
+
```ruby
|
630
|
+
validates :first_name, kind: String, strict: true
|
631
|
+
# or
|
632
|
+
validates! :last_name, kind: String
|
633
|
+
```
|
634
|
+
|
635
|
+
[⬆️ Back to Top](#table-of-contents-)
|
636
|
+
|
376
637
|
## Kind::Undefined
|
377
638
|
|
378
639
|
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.
|
@@ -437,7 +698,27 @@ puts optional.value_or(1) # 1
|
|
437
698
|
puts optional.value_or { 1 } # 1
|
438
699
|
```
|
439
700
|
|
440
|
-
|
701
|
+
#### Replacing blocks by lambdas
|
702
|
+
|
703
|
+
```ruby
|
704
|
+
Add = -> params do
|
705
|
+
a, b = Kind.of.Hash(params, or: Empty::HASH).values_at(:a, :b)
|
706
|
+
|
707
|
+
a + b if Kind.of.Numeric?(a, b)
|
708
|
+
end
|
709
|
+
|
710
|
+
# --
|
711
|
+
|
712
|
+
Kind::Maybe.new(a: 1, b: 2).map(&Add).value_or(0) # 3
|
713
|
+
|
714
|
+
# --
|
715
|
+
|
716
|
+
Kind::Maybe.new([]).map(&Add).value_or(0) # 0
|
717
|
+
Kind::Maybe.new({}).map(&Add).value_or(0) # 0
|
718
|
+
Kind::Maybe.new(nil).map(&Add).value_or(0) # 0
|
719
|
+
```
|
720
|
+
|
721
|
+
### Kind::Maybe[], Kind::Maybe.wrap() and Kind::Maybe#then method aliases
|
441
722
|
|
442
723
|
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.
|
443
724
|
|
@@ -451,43 +732,70 @@ result =
|
|
451
732
|
puts result # 42
|
452
733
|
```
|
453
734
|
|
454
|
-
|
735
|
+
You can also use `Kind::Maybe.wrap()` instead of the `.new` method.
|
455
736
|
|
456
|
-
|
737
|
+
```ruby
|
738
|
+
result =
|
739
|
+
Kind::Maybe
|
740
|
+
.wrap(5)
|
741
|
+
.then { |value| value * 5 }
|
742
|
+
.then { |value| value + 17 }
|
743
|
+
.value_or(0)
|
744
|
+
|
745
|
+
puts result # 42
|
746
|
+
```
|
747
|
+
|
748
|
+
#### Replacing blocks by lambdas
|
457
749
|
|
458
750
|
```ruby
|
459
|
-
|
751
|
+
Add = -> params do
|
752
|
+
a, b = Kind.of.Hash(params, or: Empty::HASH).values_at(:a, :b)
|
460
753
|
|
461
|
-
|
754
|
+
a + b if Kind.of.Numeric.instance?(a, b)
|
755
|
+
end
|
462
756
|
|
463
|
-
|
757
|
+
# --
|
464
758
|
|
465
|
-
|
759
|
+
Kind::Maybe[a: 1, b: 2].then(&Add).value_or(0) # 3
|
466
760
|
|
467
|
-
|
761
|
+
# --
|
468
762
|
|
469
|
-
|
470
|
-
#
|
471
|
-
|
763
|
+
Kind::Maybe[1].then(&Add).value_or(0) # 0
|
764
|
+
Kind::Maybe['2'].then(&Add).value_or(0) # 0
|
765
|
+
Kind::Maybe[nil].then(&Add).value_or(0) # 0
|
766
|
+
```
|
472
767
|
|
473
|
-
|
768
|
+
### Kind::None() and Kind::Some()
|
474
769
|
|
475
|
-
|
770
|
+
If you need to ensure the return of `Kind::Maybe` results from your methods/lambdas,
|
771
|
+
you could use the methods `Kind::None` and `Kind::Some` to do this. e.g:
|
476
772
|
|
477
|
-
|
773
|
+
```ruby
|
774
|
+
Add = -> params do
|
775
|
+
a, b = Kind.of.Hash(params, or: Empty::HASH).values_at(:a, :b)
|
478
776
|
|
479
|
-
|
480
|
-
# Kind::Undefined value #
|
481
|
-
#########################
|
777
|
+
return Kind::None unless Kind.of.Numeric?(a, b)
|
482
778
|
|
483
|
-
|
779
|
+
Kind::Some(a + b)
|
780
|
+
end
|
484
781
|
|
485
|
-
|
782
|
+
# --
|
486
783
|
|
487
|
-
|
488
|
-
|
784
|
+
Add.call(1) # #<Kind::Maybe::None:0x0000... @value=nil>
|
785
|
+
Add.call({}) # #<Kind::Maybe::None:0x0000... @value=nil>
|
786
|
+
Add.call(a: 1) # #<Kind::Maybe::None:0x0000... @value=nil>
|
787
|
+
Add.call(b: 2) # #<Kind::Maybe::None:0x0000... @value=nil>
|
489
788
|
|
490
|
-
|
789
|
+
Add.call(a:1, b: 2) # #<Kind::Maybe::Some:0x0000... @value=3>
|
790
|
+
|
791
|
+
# --
|
792
|
+
|
793
|
+
Kind::Maybe[a: 1, b: 2].then(&Add).value_or(0) # 3
|
794
|
+
|
795
|
+
Kind::Maybe[1].then(&Add).value_or(0) # 0
|
796
|
+
Kind::Maybe['2'].then(&Add).value_or(0) # 0
|
797
|
+
Kind::Maybe[nil].then(&Add).value_or(0) # 0
|
798
|
+
```
|
491
799
|
|
492
800
|
### Kind.of.Maybe()
|
493
801
|
|
@@ -544,11 +852,31 @@ result2 =
|
|
544
852
|
puts result2 # 35
|
545
853
|
```
|
546
854
|
|
855
|
+
#### Replacing blocks by lambdas
|
856
|
+
|
857
|
+
```ruby
|
858
|
+
Add = -> params do
|
859
|
+
a, b = Kind.of.Hash(params, or: Empty::HASH).values_at(:a, :b)
|
860
|
+
|
861
|
+
a + b if Kind.of.Numeric.instance?(a, b)
|
862
|
+
end
|
863
|
+
|
864
|
+
# --
|
865
|
+
|
866
|
+
Kind::Optional[a: 1, b: 2].then(&Add).value_or(0) # 3
|
867
|
+
|
868
|
+
# --
|
869
|
+
|
870
|
+
Kind::Optional[1].then(&Add).value_or(0) # 0
|
871
|
+
Kind::Optional['2'].then(&Add).value_or(0) # 0
|
872
|
+
Kind::Optional[nil].then(&Add).value_or(0) # 0
|
873
|
+
```
|
874
|
+
|
547
875
|
**Note:** The `Kind.of.Optional` is available to check if some value is a `Kind::Optional`.
|
548
876
|
|
549
877
|
[⬆️ Back to Top](#table-of-contents-)
|
550
878
|
|
551
|
-
### Kind.of
|
879
|
+
### Kind.of.\<Type\>.as_optional
|
552
880
|
|
553
881
|
It is very common the need to avoid some computing when a method receives a wrong input.
|
554
882
|
In these scenarios, you could check the given input type as optional and avoid unexpected behavior. e.g:
|
@@ -573,7 +901,7 @@ person_name(first_name: 'Rodrigo', last_name: 'Serradura') # "Rodrigo Serradura"
|
|
573
901
|
# See below the previous implementation without using an optional.
|
574
902
|
#
|
575
903
|
def person_name(params)
|
576
|
-
if params.
|
904
|
+
if params.kind_of?(Hash) && params.values_at(:first_name, :last_name).compact.size == 2
|
577
905
|
"#{params[:first_name]} #{params[:last_name]}"
|
578
906
|
else
|
579
907
|
'John Doe'
|
@@ -581,17 +909,12 @@ def person_name(params)
|
|
581
909
|
end
|
582
910
|
```
|
583
911
|
|
584
|
-
> Note: You could use the `.as_optional` method (or it alias
|
912
|
+
> 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).
|
585
913
|
|
586
914
|
Let's see another example using a collection and how the method `.as_optional` works when it receives no argument.
|
587
915
|
|
588
916
|
```ruby
|
589
|
-
collection = [
|
590
|
-
{number: 1},
|
591
|
-
'number 0',
|
592
|
-
{number: 2},
|
593
|
-
[0],
|
594
|
-
]
|
917
|
+
collection = [ {number: 1}, 'number 0', {number: 2}, [0] ]
|
595
918
|
|
596
919
|
collection
|
597
920
|
.select(&Kind.of.Hash.as_optional)
|
@@ -613,9 +936,9 @@ module PersonIntroduction
|
|
613
936
|
extend self
|
614
937
|
|
615
938
|
def call(params)
|
616
|
-
|
939
|
+
optional = Kind::Of::Hash.as_optional(params)
|
617
940
|
|
618
|
-
"Hi my name is #{full_name(
|
941
|
+
"Hi my name is #{full_name(optional)}, I'm #{age(optional)} years old."
|
619
942
|
end
|
620
943
|
|
621
944
|
private
|
@@ -637,7 +960,7 @@ module PersonIntroduction
|
|
637
960
|
extend self
|
638
961
|
|
639
962
|
def call(params)
|
640
|
-
"Hi my name is #{full_name(params)}, I'm #{age(params)}"
|
963
|
+
"Hi my name is #{full_name(params)}, I'm #{age(params)} years old."
|
641
964
|
end
|
642
965
|
|
643
966
|
private
|
@@ -660,6 +983,86 @@ end
|
|
660
983
|
|
661
984
|
[⬆️ Back to Top](#table-of-contents-)
|
662
985
|
|
986
|
+
### Kind::Maybe(<Type>)
|
987
|
+
|
988
|
+
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:
|
989
|
+
|
990
|
+
```ruby
|
991
|
+
result1 =
|
992
|
+
Kind::Maybe(Numeric)
|
993
|
+
.wrap(5)
|
994
|
+
.then { |value| value * 5 }
|
995
|
+
.value_or { 0 }
|
996
|
+
|
997
|
+
puts result1 # 25
|
998
|
+
|
999
|
+
# ---
|
1000
|
+
|
1001
|
+
result2 =
|
1002
|
+
Kind::Optional(Numeric)
|
1003
|
+
.wrap('5')
|
1004
|
+
.then { |value| value * 5 }
|
1005
|
+
.value_or { 0 }
|
1006
|
+
|
1007
|
+
puts result2 # 0
|
1008
|
+
```
|
1009
|
+
|
1010
|
+
This typed maybe has the same methods of `Kind::Maybe` class. e.g:
|
1011
|
+
|
1012
|
+
```ruby
|
1013
|
+
Kind::Maybe(Numeric)[5]
|
1014
|
+
Kind::Maybe(Numeric).new(5)
|
1015
|
+
Kind::Maybe(Numeric).wrap(5)
|
1016
|
+
|
1017
|
+
# ---
|
1018
|
+
|
1019
|
+
Kind::Optional(Numeric)[5]
|
1020
|
+
Kind::Optional(Numeric).new(5)
|
1021
|
+
Kind::Optional(Numeric).wrap(5)
|
1022
|
+
```
|
1023
|
+
|
1024
|
+
[⬆️ Back to Top](#table-of-contents-)
|
1025
|
+
|
1026
|
+
### Kind::Maybe#try
|
1027
|
+
|
1028
|
+
If you don't want to use a map to access the value, you could use the `#try` method to access it. So, if the value wasn't `nil` or `Kind::Undefined`, it will be returned.
|
1029
|
+
|
1030
|
+
```ruby
|
1031
|
+
object = 'foo'
|
1032
|
+
|
1033
|
+
Kind::Maybe[object].try(:upcase) # "FOO"
|
1034
|
+
|
1035
|
+
Kind::Maybe[{}].try(:fetch, :number, 0) # 0
|
1036
|
+
|
1037
|
+
Kind::Maybe[{number: 1}].try(:fetch, :number) # 1
|
1038
|
+
|
1039
|
+
Kind::Maybe[object].try { |value| value.upcase } # "FOO"
|
1040
|
+
|
1041
|
+
#############
|
1042
|
+
# Nil value #
|
1043
|
+
#############
|
1044
|
+
|
1045
|
+
object = nil
|
1046
|
+
|
1047
|
+
Kind::Maybe[object].try(:upcase) # nil
|
1048
|
+
|
1049
|
+
Kind::Maybe[object].try { |value| value.upcase } # nil
|
1050
|
+
|
1051
|
+
#########################
|
1052
|
+
# Kind::Undefined value #
|
1053
|
+
#########################
|
1054
|
+
|
1055
|
+
object = Kind::Undefined
|
1056
|
+
|
1057
|
+
Kind::Maybe[object].try(:upcase) # nil
|
1058
|
+
|
1059
|
+
Kind::Maybe[object].try { |value| value.upcase } # nil
|
1060
|
+
```
|
1061
|
+
|
1062
|
+
> **Note:** You can use the try method with the `Kind::Optional`.
|
1063
|
+
|
1064
|
+
[⬆️ Back to Top](#table-of-contents-)
|
1065
|
+
|
663
1066
|
## Kind::Empty
|
664
1067
|
|
665
1068
|
When you define a method that has default arguments, for certain data types, you will always create a new object in memory. e.g:
|