kind 1.7.0 → 2.2.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 +3 -1
- data/Gemfile +31 -6
- data/README.md +549 -55
- data/kind.gemspec +2 -2
- data/lib/kind.rb +59 -34
- data/lib/kind/active_model/kind_validator.rb +96 -0
- data/lib/kind/active_model/validation.rb +7 -0
- data/lib/kind/checker.rb +41 -3
- data/lib/kind/error.rb +12 -2
- data/lib/kind/maybe.rb +36 -11
- data/lib/kind/types.rb +14 -3
- 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 +9 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3bb8430c79ca97835ebdf6604e0c5afc4301f912db778e5f930fb9e699ae08c1
|
4
|
+
data.tar.gz: 0557c3df900513e5ec7d45fec79fdf7502f0cb06a8e1538ff935e0228ef11136
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0ad03f0d27484914f45607d9e1fd7cfdaf8000d700e31f069b274b5f977f42a8736a3d03d0cba1f3d06679f0f5a05b56d7c3b4acfd2a90cce20e524de6f9154a
|
7
|
+
data.tar.gz: 6f8e54590261939a43a52895921d80fb693a0e16361d011f53a80f1d7269aaade1e8871ae44374586ec488c026a02fced9057fdfcb300c532bd3a018498e954a
|
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,6 +1,8 @@
|
|
1
1
|
---
|
2
2
|
language: ruby
|
3
3
|
|
4
|
+
sudo: false
|
5
|
+
|
4
6
|
cache: bundler
|
5
7
|
|
6
8
|
rvm:
|
@@ -22,7 +24,7 @@ before_script:
|
|
22
24
|
- chmod +x ./cc-test-reporter
|
23
25
|
- "./cc-test-reporter before-build"
|
24
26
|
|
25
|
-
script:
|
27
|
+
script: "./.travis.sh"
|
26
28
|
|
27
29
|
after_success:
|
28
30
|
- "./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,49 @@
|
|
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
|
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[] and Kind::Maybe#then method aliases](#kindmaybe-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)
|
48
|
+
- [Kind.of.\<Type\>.as_optional](#kindoftypeas_optional)
|
49
|
+
- [Kind::Maybe#try](#kindmaybetry)
|
38
50
|
- [Kind::Empty](#kindempty)
|
51
|
+
- [Similar Projects](#similar-projects)
|
39
52
|
- [Development](#development)
|
40
53
|
- [Contributing](#contributing)
|
41
54
|
- [License](#license)
|
@@ -76,14 +89,14 @@ sum(1, 1) # 2
|
|
76
89
|
sum('1', 1) # Kind::Error ("\"1\" expected to be a kind of Numeric")
|
77
90
|
```
|
78
91
|
|
79
|
-
### Verifying the kind of some object
|
92
|
+
### Kind.of.\<Type\>() - Verifying the kind of some object
|
80
93
|
|
81
94
|
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.
|
82
95
|
|
83
96
|
```ruby
|
84
|
-
Kind.of.Hash(nil)
|
85
|
-
Kind.of.Hash('')
|
86
|
-
Kind.of.Hash(
|
97
|
+
Kind.of.Hash(nil) # **raise Kind::Error, "nil expected to be a kind of Hash"**
|
98
|
+
Kind.of.Hash('') # raise Kind::Error, "'' expected to be a kind of Hash"
|
99
|
+
Kind.of.Hash(a: 1) # {a: 1}
|
87
100
|
|
88
101
|
# ---
|
89
102
|
|
@@ -92,6 +105,15 @@ Kind.of.Boolean(true) # true
|
|
92
105
|
Kind.of.Boolean(false) # false
|
93
106
|
```
|
94
107
|
|
108
|
+
> **Note:** `Kind.of.<Type>` supports the to_proc protocol.
|
109
|
+
> And it will perform a strict validation as expected.
|
110
|
+
|
111
|
+
```ruby
|
112
|
+
collection = [ {number: 1}, 'number 2', {number: 3}, :number_4 ]
|
113
|
+
|
114
|
+
collection.map(&Kind.of.Hash) # Kind::Error ("number 2" expected to be a kind of Hash)
|
115
|
+
```
|
116
|
+
|
95
117
|
When the verified value is nil, it is possible to define a default value with the same type to be returned.
|
96
118
|
|
97
119
|
```ruby
|
@@ -104,7 +126,25 @@ Kind.of.Hash(value, or: {}) # {}
|
|
104
126
|
Kind.of.Boolean(nil, or: true) # true
|
105
127
|
```
|
106
128
|
|
107
|
-
As an alternative syntax, you can use the `Kind::Of` instead of the `Kind.of` method. e.g: `Kind::Of::Hash('')`
|
129
|
+
> **Note:** As an alternative syntax, you can use the `Kind::Of` instead of the `Kind.of` method. e.g: `Kind::Of::Hash('')`
|
130
|
+
|
131
|
+
#### Method aliases to perform a strict validation
|
132
|
+
|
133
|
+
```ruby
|
134
|
+
Kind.of.Hash[nil] # raise Kind::Error, "nil expected to be a kind of Hash"
|
135
|
+
Kind.of.Hash[''] # raise Kind::Error, "'' expected to be a kind of Hash"
|
136
|
+
Kind.of.Hash[a: 1] # {a: 1}
|
137
|
+
Kind.of.Hash['', or: {}] # {}
|
138
|
+
|
139
|
+
# or
|
140
|
+
|
141
|
+
Kind.of.Hash.instance(nil) # raise Kind::Error, "nil expected to be a kind of Hash"
|
142
|
+
Kind.of.Hash.instance('') # raise Kind::Error, "'' expected to be a kind of Hash"
|
143
|
+
Kind.of.Hash.instance(a: 1) # {a: 1}
|
144
|
+
Kind.of.Hash.instance('', or: {}) # {}
|
145
|
+
```
|
146
|
+
|
147
|
+
### Kind.of.\<Type\>.or_nil()
|
108
148
|
|
109
149
|
But if you don't need a strict type verification, use the `.or_nil` method.
|
110
150
|
|
@@ -118,36 +158,62 @@ Kind.of.Boolean.or_nil('') # nil
|
|
118
158
|
Kind.of.Boolean.or_nil(true) # true
|
119
159
|
```
|
120
160
|
|
121
|
-
|
161
|
+
### Kind.of.\<Type\>.instance?()
|
162
|
+
|
163
|
+
Use the method `.instance?` to verify if the given object has the expected type.
|
122
164
|
|
123
165
|
```ruby
|
124
|
-
Kind.of.Hash.instance?(
|
125
|
-
#
|
166
|
+
Kind.of.Hash.instance?({}) # true
|
167
|
+
Kind.of.Hash.instance?({}, HashWithIndifferentAccess.new) # true
|
168
|
+
|
169
|
+
Kind.of.Hash.instance?('') # false
|
170
|
+
Kind.of.Hash.instance?({}, '') # false
|
126
171
|
|
127
172
|
# ---
|
128
173
|
|
129
|
-
Kind.of.Boolean.instance?(
|
130
|
-
Kind.of.Boolean.instance?(true)
|
131
|
-
|
174
|
+
Kind.of.Boolean.instance?(true) # true
|
175
|
+
Kind.of.Boolean.instance?(true, false) # true
|
176
|
+
|
177
|
+
Kind.of.Boolean.instance?(nil) # false
|
178
|
+
Kind.of.Boolean.instance?(false, true, nil) # false
|
132
179
|
```
|
133
180
|
|
134
|
-
|
181
|
+
> **Note:** When `.instance?` is called without an argument,
|
182
|
+
> it will return a lambda which will perform the kind verification.
|
135
183
|
|
136
184
|
```ruby
|
137
|
-
|
138
|
-
Kind.of.Hash[''] # raise Kind::Error, "'' expected to be a kind of Hash"
|
139
|
-
Kind.of.Hash[a: 1] # {a: 1}
|
140
|
-
Kind.of.Hash['', or: {}] # {}
|
185
|
+
collection = [ {number: 1}, 'number 2', {number: 3}, :number_4 ]
|
141
186
|
|
142
|
-
|
187
|
+
collection
|
188
|
+
.select(&Kind.of.Hash.instance?) # [{:number=>1}, {:number=>3}]
|
189
|
+
```
|
143
190
|
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
191
|
+
> **Note:** You can use a different syntax to perform an instance verification.
|
192
|
+
> To do this, use Kind.of.\<Type\>?()
|
193
|
+
|
194
|
+
```ruby
|
195
|
+
Kind.of.Hash?({}) # true
|
196
|
+
Kind.of.Hash?({}, HashWithIndifferentAccess.new) # true
|
197
|
+
|
198
|
+
Kind.of.Hash?('') # false
|
199
|
+
Kind.of.Hash?({}, '') # false
|
200
|
+
|
201
|
+
# ---
|
202
|
+
|
203
|
+
Kind.of.Boolean?(true) # true
|
204
|
+
Kind.of.Boolean?(false, true) # true
|
205
|
+
|
206
|
+
Kind.of.Boolean?(nil) # false
|
207
|
+
Kind.of.Boolean?(false, true, nil) # false
|
208
|
+
|
209
|
+
# ---
|
210
|
+
|
211
|
+
collection = [ {number: 1}, 'number 2', {number: 3}, :number_4 ]
|
212
|
+
|
213
|
+
collection.select(&Kind.of.Hash?) # [{:number=>1}, {:number=>3}]
|
148
214
|
```
|
149
215
|
|
150
|
-
###
|
216
|
+
### Kind.is.\<Type\>() - Verifying if some class/module is the expected kind.
|
151
217
|
|
152
218
|
You can use `Kind.is` to verify if some class has the expected type as its ancestor.
|
153
219
|
|
@@ -167,6 +233,49 @@ Kind.of.Hash.class?(Hash) # true
|
|
167
233
|
Kind.of.Hash.class?(ActiveSupport::HashWithIndifferentAccess) # true
|
168
234
|
```
|
169
235
|
|
236
|
+
> **Note:** The `Kind.is` could check the inheritance of Classes/Modules.
|
237
|
+
|
238
|
+
```ruby
|
239
|
+
#
|
240
|
+
# Verifying if the attribute value is the class or a subclass.
|
241
|
+
#
|
242
|
+
class Human; end
|
243
|
+
class Person < Human; end
|
244
|
+
class User < Human; end
|
245
|
+
|
246
|
+
Kind.is(Human, User) # true
|
247
|
+
Kind.is(Human, Human) # true
|
248
|
+
Kind.is(Human, Person) # true
|
249
|
+
|
250
|
+
Kind.is(Human, Struct) # false
|
251
|
+
|
252
|
+
#
|
253
|
+
# Verifying if the attribute value is the module or if it is a class that includes the module
|
254
|
+
#
|
255
|
+
module Human; end
|
256
|
+
class Person; include Human; end
|
257
|
+
class User; include Human; end
|
258
|
+
|
259
|
+
Kind.is(Human, User) # true
|
260
|
+
Kind.is(Human, Human) # true
|
261
|
+
Kind.is(Human, Person) # true
|
262
|
+
|
263
|
+
Kind.is(Human, Struct) # false
|
264
|
+
|
265
|
+
#
|
266
|
+
# Verifying if the attribute value is the module or if it is a module that extends the module
|
267
|
+
#
|
268
|
+
module Human; end
|
269
|
+
module Person; extend Human; end
|
270
|
+
module User; extend Human; end
|
271
|
+
|
272
|
+
Kind.is(Human, User) # true
|
273
|
+
Kind.is(Human, Human) # true
|
274
|
+
Kind.is(Human, Person) # true
|
275
|
+
|
276
|
+
Kind.is(Human, Struct) # false
|
277
|
+
```
|
278
|
+
|
170
279
|
[⬆️ Back to Top](#table-of-contents-)
|
171
280
|
|
172
281
|
### How to create a new type checker?
|
@@ -191,6 +300,22 @@ Kind.of(User, {}) # Kind::Error ({} expected to be a kind of User)
|
|
191
300
|
Kind.of(Hash, {}) # {}
|
192
301
|
Kind.of(Hash, user) # Kind::Error (<User ...> expected to be a kind of Hash)
|
193
302
|
|
303
|
+
# ----------------------------------------- #
|
304
|
+
# Verifiyng if the value is a kind instance #
|
305
|
+
# ----------------------------------------- #
|
306
|
+
|
307
|
+
Kind.of?(Numeric, 1) # true
|
308
|
+
Kind.of?(Numeric, 1, 2.0) # true
|
309
|
+
|
310
|
+
Kind.of?(Numeric, '1') # false
|
311
|
+
Kind.of?(Numeric, 1, '2.0') # false
|
312
|
+
|
313
|
+
# Note: Kind.of?(Type) without arguments will return a
|
314
|
+
# lambda that will perform an instance verification
|
315
|
+
#
|
316
|
+
[1, '2', 3.0, '4']
|
317
|
+
.select(&Kind.of?(Numeric)) # [1, 3.0]
|
318
|
+
|
194
319
|
# ---------------------------------- #
|
195
320
|
# Creating type checkers dynamically #
|
196
321
|
# ---------------------------------- #
|
@@ -205,6 +330,15 @@ kind_of_user.instance?(User) # true
|
|
205
330
|
kind_of_user.class?(Hash) # false
|
206
331
|
kind_of_user.class?(User) # true
|
207
332
|
|
333
|
+
# ------------------------------------ #
|
334
|
+
# Using methods which returns a lambda #
|
335
|
+
# ------------------------------------ #
|
336
|
+
collection = [User.new, User.new, 0, {} nil, User.new]
|
337
|
+
|
338
|
+
collection.select(&Kind.of(User).instance?).size == 3 # true
|
339
|
+
|
340
|
+
collection.map(&Kind.of(User).as_optional).select(&:some?).size == 3 # true
|
341
|
+
|
208
342
|
# Creating type checkers dynamically is cheap
|
209
343
|
# because a singleton object is created to be available for use.
|
210
344
|
|
@@ -220,7 +354,7 @@ end
|
|
220
354
|
Kind.is(User, AdminUser) # true
|
221
355
|
```
|
222
356
|
|
223
|
-
#### Registering new (custom) type
|
357
|
+
#### Registering new (custom) type checker
|
224
358
|
|
225
359
|
Use `Kind::Types.add()`. e.g:
|
226
360
|
|
@@ -340,10 +474,162 @@ The list of types (classes and modules) available to use with `Kind.of.*` or `Ki
|
|
340
474
|
- `Kind.of.Module()`
|
341
475
|
- `Kind.of.Lambda()`
|
342
476
|
- `Kind.of.Boolean()`
|
343
|
-
- `Kind.of.Callable()`: verifies if the given value `respond_to?(:call)
|
477
|
+
- `Kind.of.Callable()`: verifies if the given value `respond_to?(:call)`.
|
344
478
|
- `Kind.of.Maybe()` or its alias `Kind.of.Optional()`
|
345
479
|
|
346
|
-
|
480
|
+
**Note:** Remember, you can use the `Kind.is.*` method to check if some given value is a class/module with all type checkers above.
|
481
|
+
|
482
|
+
[⬆️ Back to Top](#table-of-contents-)
|
483
|
+
|
484
|
+
## Kind::Validator (ActiveModel::Validations)
|
485
|
+
|
486
|
+
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
|
487
|
+
|
488
|
+
```ruby
|
489
|
+
class Person
|
490
|
+
include ActiveModel::Validations
|
491
|
+
|
492
|
+
attr_accessor :first_name, :last_name
|
493
|
+
|
494
|
+
validates :first_name, :last_name, kind: String
|
495
|
+
end
|
496
|
+
```
|
497
|
+
|
498
|
+
And to make use of it, you will need to do an explicitly require. e.g:
|
499
|
+
|
500
|
+
```ruby
|
501
|
+
# In some Gemfile
|
502
|
+
gem 'kind', require: 'kind/active_model/validation'
|
503
|
+
|
504
|
+
# In some .rb file
|
505
|
+
require 'kind/active_model/validation'
|
506
|
+
```
|
507
|
+
|
508
|
+
### Usage
|
509
|
+
|
510
|
+
**[Object#kind_of?](https://ruby-doc.org/core-2.6.4/Object.html#method-i-kind_of-3F)**
|
511
|
+
|
512
|
+
```ruby
|
513
|
+
validates :name, kind: { of: String }
|
514
|
+
|
515
|
+
# Use an array to verify if the attribute
|
516
|
+
# is an instance of one of the classes/modules.
|
517
|
+
|
518
|
+
validates :status, kind: { of: [String, Symbol]}
|
519
|
+
```
|
520
|
+
|
521
|
+
**[Kind.is](#verifying-the-kind-of-some-classmodule)**
|
522
|
+
|
523
|
+
```ruby
|
524
|
+
#
|
525
|
+
# Verifying if the attribute value is the class or a subclass.
|
526
|
+
#
|
527
|
+
class Human; end
|
528
|
+
class Person < Human; end
|
529
|
+
class User < Human; end
|
530
|
+
|
531
|
+
validates :human_kind, kind: { is: Human }
|
532
|
+
|
533
|
+
#
|
534
|
+
# Verifying if the attribute value is the module or if it is a class that includes the module
|
535
|
+
#
|
536
|
+
module Human; end
|
537
|
+
class Person; include Human; end
|
538
|
+
class User; include Human; end
|
539
|
+
|
540
|
+
validates :human_kind, kind: { is: Human }
|
541
|
+
|
542
|
+
#
|
543
|
+
# Verifying if the attribute value is the module or if it is a module that extends the module
|
544
|
+
#
|
545
|
+
module Human; end
|
546
|
+
module Person; extend Human; end
|
547
|
+
module User; extend Human; end
|
548
|
+
|
549
|
+
validates :human_kind, kind: { is: Human }
|
550
|
+
|
551
|
+
# or use an array to verify if the attribute
|
552
|
+
# is a kind of one those classes/modules.
|
553
|
+
|
554
|
+
validates :human_kind, kind: { is: [Person, User] }
|
555
|
+
```
|
556
|
+
|
557
|
+
**[Object#instance_of?](https://ruby-doc.org/core-2.6.4/Object.html#method-i-instance_of-3F)**
|
558
|
+
|
559
|
+
```ruby
|
560
|
+
validates :name, kind: { instance_of: String }
|
561
|
+
|
562
|
+
# or use an array to verify if the attribute
|
563
|
+
# is an instance of one of the classes/modules.
|
564
|
+
|
565
|
+
validates :name, kind: { instance_of: [String, Symbol] }
|
566
|
+
```
|
567
|
+
|
568
|
+
|
569
|
+
**[Object#respond_to?](https://ruby-doc.org/core-2.6.4/Object.html#method-i-respond_to-3F)**
|
570
|
+
|
571
|
+
```ruby
|
572
|
+
validates :handler, kind: { respond_to: :call }
|
573
|
+
```
|
574
|
+
|
575
|
+
**Array.new.all? { |item| item.kind_of?(Class) }**
|
576
|
+
|
577
|
+
```ruby
|
578
|
+
validates :account_types, kind: { array_of: String }
|
579
|
+
|
580
|
+
# or use an array to verify if the attribute
|
581
|
+
# is an instance of one of the classes
|
582
|
+
|
583
|
+
validates :account_types, kind: { array_of: [String, Symbol] }
|
584
|
+
```
|
585
|
+
|
586
|
+
**Array.new.all? { |item| expected_values.include?(item) }**
|
587
|
+
|
588
|
+
```ruby
|
589
|
+
# Verifies if the attribute value
|
590
|
+
# is an array with some or all the expected values.
|
591
|
+
|
592
|
+
validates :account_types, kind: { array_with: ['foo', 'bar'] }
|
593
|
+
```
|
594
|
+
|
595
|
+
#### Defining the default validation strategy
|
596
|
+
|
597
|
+
By default, you can define the attribute type directly (without a hash). e.g.
|
598
|
+
|
599
|
+
```ruby
|
600
|
+
validates :name, kind: String
|
601
|
+
# or
|
602
|
+
validates :name, kind: [String, Symbol]
|
603
|
+
```
|
604
|
+
|
605
|
+
To changes this behavior you can set another strategy to validates the attributes types:
|
606
|
+
|
607
|
+
```ruby
|
608
|
+
Kind::Validator.default_strategy = :instance_of
|
609
|
+
|
610
|
+
# Tip: Create an initializer if you are in a Rails application.
|
611
|
+
```
|
612
|
+
|
613
|
+
And these are the available options to define the default strategy:
|
614
|
+
- `kind_of` *(default)*
|
615
|
+
- `instance_of`
|
616
|
+
|
617
|
+
#### Using the `allow_nil` and `strict` options
|
618
|
+
|
619
|
+
You can use the `allow_nil` option with any of the kind validations. e.g.
|
620
|
+
|
621
|
+
```ruby
|
622
|
+
validates :name, kind: String, allow_nil: true
|
623
|
+
```
|
624
|
+
|
625
|
+
And as any active model validation, kind validations works with the `strict: true`
|
626
|
+
option and with the `validates!` method. e.g.
|
627
|
+
|
628
|
+
```ruby
|
629
|
+
validates :first_name, kind: String, strict: true
|
630
|
+
# or
|
631
|
+
validates! :last_name, kind: String
|
632
|
+
```
|
347
633
|
|
348
634
|
[⬆️ Back to Top](#table-of-contents-)
|
349
635
|
|
@@ -411,7 +697,27 @@ puts optional.value_or(1) # 1
|
|
411
697
|
puts optional.value_or { 1 } # 1
|
412
698
|
```
|
413
699
|
|
414
|
-
|
700
|
+
#### Replacing blocks by lambdas
|
701
|
+
|
702
|
+
```ruby
|
703
|
+
Add = -> params do
|
704
|
+
a, b = Kind.of.Hash(params, or: Empty::HASH).values_at(:a, :b)
|
705
|
+
|
706
|
+
a + b if Kind.of.Numeric?(a, b)
|
707
|
+
end
|
708
|
+
|
709
|
+
# --
|
710
|
+
|
711
|
+
Kind::Maybe.new(a: 1, b: 2).map(&Add).value_or(0) # 3
|
712
|
+
|
713
|
+
# --
|
714
|
+
|
715
|
+
Kind::Maybe.new([]).map(&Add).value_or(0) # 0
|
716
|
+
Kind::Maybe.new({}).map(&Add).value_or(0) # 0
|
717
|
+
Kind::Maybe.new(nil).map(&Add).value_or(0) # 0
|
718
|
+
```
|
719
|
+
|
720
|
+
### Kind::Maybe[] and Kind::Maybe#then method aliases
|
415
721
|
|
416
722
|
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.
|
417
723
|
|
@@ -425,39 +731,57 @@ result =
|
|
425
731
|
puts result # 42
|
426
732
|
```
|
427
733
|
|
428
|
-
|
429
|
-
|
430
|
-
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.
|
734
|
+
#### Replacing blocks by lambdas
|
431
735
|
|
432
736
|
```ruby
|
433
|
-
|
737
|
+
Add = -> params do
|
738
|
+
a, b = Kind.of.Hash(params, or: Empty::HASH).values_at(:a, :b)
|
434
739
|
|
435
|
-
|
740
|
+
a + b if Kind.of.Numeric.instance?(a, b)
|
741
|
+
end
|
436
742
|
|
437
|
-
|
743
|
+
# --
|
438
744
|
|
439
|
-
|
440
|
-
# Nil value #
|
441
|
-
#############
|
745
|
+
Kind::Maybe[a: 1, b: 2].then(&Add).value_or(0) # 3
|
442
746
|
|
443
|
-
|
747
|
+
# --
|
444
748
|
|
445
|
-
|
749
|
+
Kind::Maybe[1].then(&Add).value_or(0) # 0
|
750
|
+
Kind::Maybe['2'].then(&Add).value_or(0) # 0
|
751
|
+
Kind::Maybe[nil].then(&Add).value_or(0) # 0
|
752
|
+
```
|
446
753
|
|
447
|
-
|
754
|
+
### Kind::None() and Kind::Some()
|
448
755
|
|
449
|
-
|
450
|
-
|
451
|
-
#########################
|
756
|
+
If you need to ensure the return of `Kind::Maybe` results from your methods/lambdas,
|
757
|
+
you could use the methods `Kind::None` and `Kind::Some` to do this. e.g:
|
452
758
|
|
453
|
-
|
759
|
+
```ruby
|
760
|
+
Add = -> params do
|
761
|
+
a, b = Kind.of.Hash(params, or: Empty::HASH).values_at(:a, :b)
|
454
762
|
|
455
|
-
|
763
|
+
return Kind::None unless Kind.of.Numeric?(a, b)
|
456
764
|
|
457
|
-
|
458
|
-
|
765
|
+
Kind::Some(a + b)
|
766
|
+
end
|
459
767
|
|
460
|
-
|
768
|
+
# --
|
769
|
+
|
770
|
+
Add.call(1) # #<Kind::Maybe::None:0x0000... @value=nil>
|
771
|
+
Add.call({}) # #<Kind::Maybe::None:0x0000... @value=nil>
|
772
|
+
Add.call(a: 1) # #<Kind::Maybe::None:0x0000... @value=nil>
|
773
|
+
Add.call(b: 2) # #<Kind::Maybe::None:0x0000... @value=nil>
|
774
|
+
|
775
|
+
Add.call(a:1, b: 2) # #<Kind::Maybe::Some:0x0000... @value=3>
|
776
|
+
|
777
|
+
# --
|
778
|
+
|
779
|
+
Kind::Maybe[a: 1, b: 2].then(&Add).value_or(0) # 3
|
780
|
+
|
781
|
+
Kind::Maybe[1].then(&Add).value_or(0) # 0
|
782
|
+
Kind::Maybe['2'].then(&Add).value_or(0) # 0
|
783
|
+
Kind::Maybe[nil].then(&Add).value_or(0) # 0
|
784
|
+
```
|
461
785
|
|
462
786
|
### Kind.of.Maybe()
|
463
787
|
|
@@ -514,7 +838,174 @@ result2 =
|
|
514
838
|
puts result2 # 35
|
515
839
|
```
|
516
840
|
|
517
|
-
|
841
|
+
#### Replacing blocks by lambdas
|
842
|
+
|
843
|
+
```ruby
|
844
|
+
Add = -> params do
|
845
|
+
a, b = Kind.of.Hash(params, or: Empty::HASH).values_at(:a, :b)
|
846
|
+
|
847
|
+
a + b if Kind.of.Numeric.instance?(a, b)
|
848
|
+
end
|
849
|
+
|
850
|
+
# --
|
851
|
+
|
852
|
+
Kind::Optional[a: 1, b: 2].then(&Add).value_or(0) # 3
|
853
|
+
|
854
|
+
# --
|
855
|
+
|
856
|
+
Kind::Optional[1].then(&Add).value_or(0) # 0
|
857
|
+
Kind::Optional['2'].then(&Add).value_or(0) # 0
|
858
|
+
Kind::Optional[nil].then(&Add).value_or(0) # 0
|
859
|
+
```
|
860
|
+
|
861
|
+
**Note:** The `Kind.of.Optional` is available to check if some value is a `Kind::Optional`.
|
862
|
+
|
863
|
+
[⬆️ Back to Top](#table-of-contents-)
|
864
|
+
|
865
|
+
### Kind.of.\<Type\>.as_optional
|
866
|
+
|
867
|
+
It is very common the need to avoid some computing when a method receives a wrong input.
|
868
|
+
In these scenarios, you could check the given input type as optional and avoid unexpected behavior. e.g:
|
869
|
+
|
870
|
+
```ruby
|
871
|
+
def person_name(params)
|
872
|
+
Kind::Of::Hash.as_optional(params)
|
873
|
+
.map { |data| data if data.values_at(:first_name, :last_name).compact.size == 2 }
|
874
|
+
.map { |data| "#{data[:first_name]} #{data[:last_name]}" }
|
875
|
+
.value_or { 'John Doe' }
|
876
|
+
end
|
877
|
+
|
878
|
+
person_name('') # "John Doe"
|
879
|
+
person_name(nil) # "John Doe"
|
880
|
+
|
881
|
+
person_name(first_name: 'Rodrigo') # "John Doe"
|
882
|
+
person_name(last_name: 'Serradura') # "John Doe"
|
883
|
+
|
884
|
+
person_name(first_name: 'Rodrigo', last_name: 'Serradura') # "Rodrigo Serradura"
|
885
|
+
|
886
|
+
#
|
887
|
+
# See below the previous implementation without using an optional.
|
888
|
+
#
|
889
|
+
def person_name(params)
|
890
|
+
if params.kind_of?(Hash) && params.values_at(:first_name, :last_name).compact.size == 2
|
891
|
+
"#{params[:first_name]} #{params[:last_name]}"
|
892
|
+
else
|
893
|
+
'John Doe'
|
894
|
+
end
|
895
|
+
end
|
896
|
+
```
|
897
|
+
|
898
|
+
> 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).
|
899
|
+
|
900
|
+
Let's see another example using a collection and how the method `.as_optional` works when it receives no argument.
|
901
|
+
|
902
|
+
```ruby
|
903
|
+
collection = [ {number: 1}, 'number 0', {number: 2}, [0] ]
|
904
|
+
|
905
|
+
collection
|
906
|
+
.select(&Kind.of.Hash.as_optional)
|
907
|
+
.reduce(0) do |total, item|
|
908
|
+
item.try { |data| data[:number] + total } || total
|
909
|
+
end
|
910
|
+
|
911
|
+
collection
|
912
|
+
.map(&Kind.of.Hash.as_optional).select(&:some?)
|
913
|
+
.reduce(0) { |total, item| total + item.value[:number] }
|
914
|
+
|
915
|
+
# Note: All the examples above return 3 as the sum of all hashes with numbers.
|
916
|
+
```
|
917
|
+
|
918
|
+
To finish follows an example of how to use optionals to handle arguments in coupled methods.
|
919
|
+
|
920
|
+
```ruby
|
921
|
+
module PersonIntroduction
|
922
|
+
extend self
|
923
|
+
|
924
|
+
def call(params)
|
925
|
+
optional = Kind::Of::Hash.as_optional(params)
|
926
|
+
|
927
|
+
"Hi my name is #{full_name(optional)}, I'm #{age(optional)} years old."
|
928
|
+
end
|
929
|
+
|
930
|
+
private
|
931
|
+
|
932
|
+
def full_name(optional)
|
933
|
+
optional.map { |data| "#{data[:first_name]} #{data[:last_name]}" }
|
934
|
+
.value_or { 'John Doe' }
|
935
|
+
end
|
936
|
+
|
937
|
+
def age(optional)
|
938
|
+
optional.map { |data| data[:age] }.value_or(0)
|
939
|
+
end
|
940
|
+
end
|
941
|
+
|
942
|
+
#
|
943
|
+
# See below the previous implementation without using an optional.
|
944
|
+
#
|
945
|
+
module PersonIntroduction
|
946
|
+
extend self
|
947
|
+
|
948
|
+
def call(params)
|
949
|
+
"Hi my name is #{full_name(params)}, I'm #{age(params)} years old."
|
950
|
+
end
|
951
|
+
|
952
|
+
private
|
953
|
+
|
954
|
+
def full_name(params)
|
955
|
+
case params
|
956
|
+
when Hash then "#{params[:first_name]} #{params[:last_name]}"
|
957
|
+
else 'John Doe'
|
958
|
+
end
|
959
|
+
end
|
960
|
+
|
961
|
+
def age(params)
|
962
|
+
case params
|
963
|
+
when Hash then params.fetch(:age, 0)
|
964
|
+
else 0
|
965
|
+
end
|
966
|
+
end
|
967
|
+
end
|
968
|
+
```
|
969
|
+
|
970
|
+
[⬆️ Back to Top](#table-of-contents-)
|
971
|
+
|
972
|
+
### Kind::Maybe#try
|
973
|
+
|
974
|
+
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.
|
975
|
+
|
976
|
+
```ruby
|
977
|
+
object = 'foo'
|
978
|
+
|
979
|
+
Kind::Maybe[object].try(:upcase) # "FOO"
|
980
|
+
|
981
|
+
Kind::Maybe[{}].try(:fetch, :number, 0) # 0
|
982
|
+
|
983
|
+
Kind::Maybe[{number: 1}].try(:fetch, :number) # 1
|
984
|
+
|
985
|
+
Kind::Maybe[object].try { |value| value.upcase } # "FOO"
|
986
|
+
|
987
|
+
#############
|
988
|
+
# Nil value #
|
989
|
+
#############
|
990
|
+
|
991
|
+
object = nil
|
992
|
+
|
993
|
+
Kind::Maybe[object].try(:upcase) # nil
|
994
|
+
|
995
|
+
Kind::Maybe[object].try { |value| value.upcase } # nil
|
996
|
+
|
997
|
+
#########################
|
998
|
+
# Kind::Undefined value #
|
999
|
+
#########################
|
1000
|
+
|
1001
|
+
object = Kind::Undefined
|
1002
|
+
|
1003
|
+
Kind::Maybe[object].try(:upcase) # nil
|
1004
|
+
|
1005
|
+
Kind::Maybe[object].try { |value| value.upcase } # nil
|
1006
|
+
```
|
1007
|
+
|
1008
|
+
> **Note:** You can use the try method with the `Kind::Optional`.
|
518
1009
|
|
519
1010
|
[⬆️ Back to Top](#table-of-contents-)
|
520
1011
|
|
@@ -565,6 +1056,10 @@ Follows the list of constants, if the alias is available to be created:
|
|
565
1056
|
|
566
1057
|
[⬆️ Back to Top](#table-of-contents-)
|
567
1058
|
|
1059
|
+
## Similar Projects
|
1060
|
+
|
1061
|
+
- [dry-types](https://dry-rb.org/gems/dry-types)
|
1062
|
+
|
568
1063
|
## Development
|
569
1064
|
|
570
1065
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
@@ -575,7 +1070,6 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
575
1070
|
|
576
1071
|
Bug reports and pull requests are welcome on GitHub at https://github.com/serradura/kind. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/serradura/kind/blob/master/CODE_OF_CONDUCT.md).
|
577
1072
|
|
578
|
-
|
579
1073
|
## License
|
580
1074
|
|
581
1075
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|