kind 2.3.0 → 4.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.tool-versions +1 -0
- data/.travis.sh +37 -12
- data/.travis.yml +6 -3
- data/CHANGELOG.md +1267 -0
- data/Gemfile +10 -2
- data/README.md +1005 -489
- data/lib/kind.rb +53 -281
- data/lib/kind/active_model/kind_validator.rb +20 -13
- data/lib/kind/core.rb +10 -0
- data/lib/kind/core/deprecation.rb +29 -0
- data/lib/kind/core/kind.rb +61 -0
- data/lib/kind/core/undefined.rb +7 -0
- data/lib/kind/core/wrong_number_of_args.rb +9 -0
- data/lib/kind/deprecations/built_in_type_checkers.rb +23 -0
- data/lib/kind/{checker.rb → deprecations/checker.rb} +3 -2
- data/lib/kind/{checker → deprecations/checker}/factory.rb +1 -5
- data/lib/kind/{checker → deprecations/checker}/protocol.rb +3 -3
- data/lib/kind/deprecations/is.rb +35 -0
- data/lib/kind/deprecations/of.rb +258 -0
- data/lib/kind/{types.rb → deprecations/types.rb} +14 -8
- data/lib/kind/dig.rb +40 -0
- data/lib/kind/empty.rb +5 -11
- data/lib/kind/error.rb +2 -6
- data/lib/kind/maybe.rb +14 -130
- data/lib/kind/maybe/none.rb +57 -0
- data/lib/kind/maybe/result.rb +51 -0
- data/lib/kind/maybe/some.rb +90 -0
- data/lib/kind/maybe/typed.rb +29 -0
- data/lib/kind/maybe/wrappable.rb +33 -0
- data/lib/kind/presence.rb +33 -0
- data/lib/kind/try.rb +34 -0
- data/lib/kind/type_checker.rb +87 -0
- data/lib/kind/type_checkers.rb +30 -0
- data/lib/kind/type_checkers/core/array.rb +17 -0
- data/lib/kind/type_checkers/core/class.rb +13 -0
- data/lib/kind/type_checkers/core/comparable.rb +13 -0
- data/lib/kind/type_checkers/core/enumerable.rb +13 -0
- data/lib/kind/type_checkers/core/enumerator.rb +13 -0
- data/lib/kind/type_checkers/core/file.rb +13 -0
- data/lib/kind/type_checkers/core/float.rb +13 -0
- data/lib/kind/type_checkers/core/hash.rb +17 -0
- data/lib/kind/type_checkers/core/integer.rb +13 -0
- data/lib/kind/type_checkers/core/io.rb +13 -0
- data/lib/kind/type_checkers/core/method.rb +13 -0
- data/lib/kind/type_checkers/core/module.rb +17 -0
- data/lib/kind/type_checkers/core/numeric.rb +13 -0
- data/lib/kind/type_checkers/core/proc.rb +13 -0
- data/lib/kind/type_checkers/core/queue.rb +14 -0
- data/lib/kind/type_checkers/core/range.rb +13 -0
- data/lib/kind/type_checkers/core/regexp.rb +13 -0
- data/lib/kind/type_checkers/core/string.rb +17 -0
- data/lib/kind/type_checkers/core/struct.rb +13 -0
- data/lib/kind/type_checkers/core/symbol.rb +13 -0
- data/lib/kind/type_checkers/core/time.rb +13 -0
- data/lib/kind/type_checkers/custom/boolean.rb +19 -0
- data/lib/kind/type_checkers/custom/callable.rb +19 -0
- data/lib/kind/type_checkers/custom/lambda.rb +19 -0
- data/lib/kind/type_checkers/stdlib/open_struct.rb +13 -0
- data/lib/kind/type_checkers/stdlib/set.rb +17 -0
- data/lib/kind/undefined.rb +4 -2
- data/lib/kind/validator.rb +1 -1
- data/lib/kind/version.rb +1 -1
- data/test.sh +4 -4
- metadata +53 -9
- data/lib/kind/is.rb +0 -19
- data/lib/kind/of.rb +0 -11
data/Gemfile
CHANGED
@@ -12,9 +12,17 @@ activemodel = case activemodel_version
|
|
12
12
|
when '5.0' then '5.0.7'
|
13
13
|
when '5.1' then '5.1.7'
|
14
14
|
when '5.2' then '5.2.4'
|
15
|
-
when '6.0' then '6.0.
|
15
|
+
when '6.0' then '6.0.3.4'
|
16
|
+
when '6.1' then '6.1.2'
|
16
17
|
end
|
17
18
|
|
19
|
+
simplecov_version =
|
20
|
+
case RUBY_VERSION
|
21
|
+
when /\A2.[23]/ then '0.17.1'
|
22
|
+
when /\A2.4/ then '~> 0.18.5'
|
23
|
+
else '~> 0.19'
|
24
|
+
end
|
25
|
+
|
18
26
|
group :test do
|
19
27
|
if activemodel_version
|
20
28
|
gem 'activesupport', activemodel, require: false
|
@@ -24,7 +32,7 @@ group :test do
|
|
24
32
|
gem 'minitest', '~> 5.0'
|
25
33
|
end
|
26
34
|
|
27
|
-
gem 'simplecov', require: false
|
35
|
+
gem 'simplecov', simplecov_version, require: false
|
28
36
|
end
|
29
37
|
|
30
38
|
gem 'rake', '~> 13.0'
|
data/README.md
CHANGED
@@ -1,62 +1,127 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
1
|
+
<p align="center">
|
2
|
+
<h1 align="center">🤷 kind</h1>
|
3
|
+
<p align="center"><i>A simple type system (at runtime) for Ruby - free of dependencies.</i></p>
|
4
|
+
<br>
|
5
|
+
</p>
|
6
6
|
|
7
|
-
|
7
|
+
<p align="center">
|
8
|
+
<img src="https://img.shields.io/badge/ruby->%3D%202.2.0-ruby.svg?colorA=99004d&colorB=cc0066" alt="Ruby">
|
8
9
|
|
9
|
-
|
10
|
+
<a href="https://rubygems.org/gems/kind">
|
11
|
+
<img alt="Gem" src="https://img.shields.io/gem/v/kind.svg?style=flat-square">
|
12
|
+
</a>
|
13
|
+
|
14
|
+
<a href="https://travis-ci.com/serradura/kind">
|
15
|
+
<img alt="Build Status" src="https://travis-ci.com/serradura/kind.svg?branch=master">
|
16
|
+
</a>
|
17
|
+
|
18
|
+
<a href="https://codeclimate.com/github/serradura/kind/maintainability">
|
19
|
+
<img alt="Maintainability" src="https://api.codeclimate.com/v1/badges/711329decb2806ccac95/maintainability">
|
20
|
+
</a>
|
21
|
+
|
22
|
+
<a href="https://codeclimate.com/github/serradura/kind/test_coverage">
|
23
|
+
<img alt="Test Coverage" src="https://api.codeclimate.com/v1/badges/711329decb2806ccac95/test_coverage">
|
24
|
+
</a>
|
25
|
+
</p>
|
10
26
|
|
11
27
|
**Motivation:**
|
12
28
|
|
13
29
|
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
30
|
|
15
|
-
One of the goals of this project is to do simple type checking like `"some string".is_a?(String)`, but, exposing useful abstractions
|
31
|
+
One of the goals of this project is to do simple type checking like `"some string".is_a?(String)`, but, exposing useful abstractions around this. e.g: [Kind.\<Type\> methods](#verifying-the-kind-of-some-object), [active model validations](#kindvalidator-activemodelvalidations), [maybe monad](#kindmaybe).
|
32
|
+
|
33
|
+
## Documentation <!-- omit in toc -->
|
34
|
+
|
35
|
+
Version | Documentation
|
36
|
+
---------- | -------------
|
37
|
+
unreleased | https://github.com/serradura/u-case/blob/main/README.md
|
38
|
+
4.1.0 | https://github.com/serradura/u-case/blob/v4.x/README.md
|
39
|
+
3.1.0 | https://github.com/serradura/u-case/blob/v3.x/README.md
|
40
|
+
2.3.0 | https://github.com/serradura/u-case/blob/v2.x/README.md
|
41
|
+
1.9.0 | https://github.com/serradura/u-case/blob/v1.x/README.md
|
16
42
|
|
17
43
|
## Table of Contents <!-- omit in toc -->
|
18
|
-
- [
|
44
|
+
- [Compatibility](#compatibility)
|
19
45
|
- [Installation](#installation)
|
20
46
|
- [Usage](#usage)
|
21
|
-
- [Kind
|
22
|
-
|
23
|
-
- [Kind
|
24
|
-
- [Kind
|
25
|
-
- [Kind
|
26
|
-
- [
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
- [
|
31
|
-
- [
|
32
|
-
|
33
|
-
|
34
|
-
- [
|
35
|
-
- [
|
36
|
-
- [
|
37
|
-
|
47
|
+
- [Kind.\<Type\>[]](#kindtype)
|
48
|
+
- [Kind::\<Type\>.===()](#kindtype-1)
|
49
|
+
- [Kind::\<Type\>.value?()](#kindtypevalue)
|
50
|
+
- [Kind::\<Type\>.or_nil()](#kindtypeor_nil)
|
51
|
+
- [Kind::\<Type\>.or_undefined()](#kindtypeor_undefined)
|
52
|
+
- [Kind::\<Type\>.or()](#kindtypeor)
|
53
|
+
- [Kind::\<Type\>.value()](#kindtypevalue-1)
|
54
|
+
- [Kind::\<Type\>.maybe](#kindtypemaybe)
|
55
|
+
- [Kind::\<Type\>?](#kindtype-2)
|
56
|
+
- [Kind::{Array,Hash,String,Set}.value_or_empty()](#kindarrayhashstringsetvalue_or_empty)
|
57
|
+
- [List of all type checkers](#list-of-all-type-checkers)
|
58
|
+
- [Core](#core)
|
59
|
+
- [Stdlib](#stdlib)
|
60
|
+
- [Custom](#custom)
|
61
|
+
- [Creating type checkers](#creating-type-checkers)
|
62
|
+
- [Dynamic creation](#dynamic-creation)
|
63
|
+
- [Using a class or a module](#using-a-class-or-a-module)
|
64
|
+
- [Using an object which responds to ===](#using-an-object-which-responds-to-)
|
65
|
+
- [Kind::<Type> module](#kindtype-module)
|
66
|
+
- [Utility methods](#utility-methods)
|
67
|
+
- [Kind.of_class?()](#kindof_class)
|
68
|
+
- [Kind.of_module?()](#kindof_module)
|
69
|
+
- [Kind.of_module_or_class()](#kindof_module_or_class)
|
70
|
+
- [Kind.respond_to()](#kindrespond_to)
|
71
|
+
- [Kind.of()](#kindof)
|
72
|
+
- [Kind.of?()](#kindof-1)
|
73
|
+
- [Kind.value()](#kindvalue)
|
74
|
+
- [Kind.is()](#kindis)
|
75
|
+
- [Utility modules](#utility-modules)
|
76
|
+
- [Kind::Try](#kindtry)
|
77
|
+
- [Kind::Dig](#kinddig)
|
78
|
+
- [Kind::Presence](#kindpresence)
|
38
79
|
- [Kind::Undefined](#kindundefined)
|
39
|
-
- [Kind.of.\<Type\>.or_undefined()](#kindoftypeor_undefined)
|
40
80
|
- [Kind::Maybe](#kindmaybe)
|
41
81
|
- [Replacing blocks by lambdas](#replacing-blocks-by-lambdas)
|
42
82
|
- [Kind::Maybe[], Kind::Maybe.wrap() and Kind::Maybe#then method aliases](#kindmaybe-kindmaybewrap-and-kindmaybethen-method-aliases)
|
43
83
|
- [Replacing blocks by lambdas](#replacing-blocks-by-lambdas-1)
|
44
84
|
- [Kind::None() and Kind::Some()](#kindnone-and-kindsome)
|
45
|
-
- [Kind.of.Maybe()](#kindofmaybe)
|
46
85
|
- [Kind::Optional](#kindoptional)
|
47
86
|
- [Replacing blocks by lambdas](#replacing-blocks-by-lambdas-2)
|
48
|
-
- [Kind.of.\<Type\>.as_optional](#kindoftypeas_optional)
|
49
87
|
- [Kind::Maybe(<Type>)](#kindmaybetype)
|
88
|
+
- [Real world examples](#real-world-examples)
|
89
|
+
- [Error handling](#error-handling)
|
90
|
+
- [Kind::Maybe.wrap {}](#kindmaybewrap-)
|
91
|
+
- [Kind::Maybe.map! or Kind::Maybe.then!](#kindmaybemap-or-kindmaybethen)
|
50
92
|
- [Kind::Maybe#try](#kindmaybetry)
|
93
|
+
- [Kind::Maybe#try!](#kindmaybetry-1)
|
94
|
+
- [Kind::Maybe#dig](#kindmaybedig)
|
95
|
+
- [Kind::Maybe#check](#kindmaybecheck)
|
96
|
+
- [Kind::Maybe#presence](#kindmaybepresence)
|
51
97
|
- [Kind::Empty](#kindempty)
|
98
|
+
- [Kind::Validator (ActiveModel::Validations)](#kindvalidator-activemodelvalidations)
|
99
|
+
- [Usage](#usage-1)
|
100
|
+
- [Object#===](#object)
|
101
|
+
- [Kind.is](#kindis-1)
|
102
|
+
- [Object#instance_of?](#objectinstance_of)
|
103
|
+
- [Object#respond_to?](#objectrespond_to)
|
104
|
+
- [Array.new.all? { |item| item.kind_of?(Class) }](#arraynewall--item-itemkind_ofclass-)
|
105
|
+
- [Array.new.all? { |item| expected_values.include?(item) }](#arraynewall--item-expected_valuesincludeitem-)
|
106
|
+
- [Defining the default validation strategy](#defining-the-default-validation-strategy)
|
107
|
+
- [Using the `allow_nil` and `strict` options](#using-the-allow_nil-and-strict-options)
|
52
108
|
- [Similar Projects](#similar-projects)
|
53
109
|
- [Development](#development)
|
54
110
|
- [Contributing](#contributing)
|
55
111
|
- [License](#license)
|
56
112
|
- [Code of Conduct](#code-of-conduct)
|
57
113
|
|
58
|
-
##
|
59
|
-
|
114
|
+
## Compatibility
|
115
|
+
|
116
|
+
| u-case | branch | ruby | activemodel |
|
117
|
+
| -------------- | ------- | -------- | -------------- |
|
118
|
+
| unreleased | main | >= 2.2.0 | >= 3.2, <= 6.1 |
|
119
|
+
| 4.1.0 | v4.x | >= 2.2.0 | >= 3.2, <= 6.1 |
|
120
|
+
| 3.1.0 | v3.x | >= 2.2.0 | >= 3.2, <= 6.1 |
|
121
|
+
| 2.3.0 | v2.x | >= 2.2.0 | >= 3.2, <= 6.0 |
|
122
|
+
| 1.9.0 | v1.x | >= 2.2.0 | >= 3.2, <= 6.0 |
|
123
|
+
|
124
|
+
> Note: The activemodel is an optional dependency, it is related with the [Kind::Validator](#kindvalidator-activemodelvalidations).
|
60
125
|
|
61
126
|
## Installation
|
62
127
|
|
@@ -74,7 +139,7 @@ Or install it yourself as:
|
|
74
139
|
|
75
140
|
$ gem install kind
|
76
141
|
|
77
|
-
[⬆️ Back to Top](#table-of-contents-)
|
142
|
+
[⬆️ Back to Top](#table-of-contents-)
|
78
143
|
|
79
144
|
## Usage
|
80
145
|
|
@@ -82,7 +147,7 @@ With this gem you can add some kind of type checking at runtime. e.g:
|
|
82
147
|
|
83
148
|
```ruby
|
84
149
|
def sum(a, b)
|
85
|
-
Kind
|
150
|
+
Kind::Numeric[a] + Kind::Numeric[b]
|
86
151
|
end
|
87
152
|
|
88
153
|
sum(1, 1) # 2
|
@@ -90,200 +155,227 @@ sum(1, 1) # 2
|
|
90
155
|
sum('1', 1) # Kind::Error ("\"1\" expected to be a kind of Numeric")
|
91
156
|
```
|
92
157
|
|
93
|
-
|
158
|
+
[⬆️ Back to Top](#table-of-contents-)
|
159
|
+
|
160
|
+
### Kind.\<Type\>[]
|
94
161
|
|
95
|
-
By default, basic verifications are strict. So, when you perform `Kind
|
162
|
+
By default, basic verifications are strict. So, when you perform `Kind::Hash[value]` the given value will be returned if it was a Hash, but if not, an error will be raised.
|
96
163
|
|
97
164
|
```ruby
|
98
|
-
Kind
|
99
|
-
Kind
|
100
|
-
Kind
|
165
|
+
Kind::Hash[nil] # Kind::Error (nil expected to be a kind of Hash)
|
166
|
+
Kind::Hash[''] # Kind::Error ("" expected to be a kind of Hash)
|
167
|
+
Kind::Hash[a: 1] # {a: 1}
|
168
|
+
```
|
101
169
|
|
102
|
-
#
|
170
|
+
[⬆️ Back to Top](#table-of-contents-)
|
103
171
|
|
104
|
-
Kind
|
105
|
-
Kind.of.Boolean(true) # true
|
106
|
-
Kind.of.Boolean(false) # false
|
107
|
-
```
|
172
|
+
### Kind::\<Type\>.===()
|
108
173
|
|
109
|
-
|
110
|
-
> And it will perform a strict validation as expected.
|
174
|
+
Use this method to verify if the given object has the expected type.
|
111
175
|
|
112
176
|
```ruby
|
113
|
-
|
114
|
-
|
115
|
-
collection.map(&Kind.of.Hash) # Kind::Error ("number 2" expected to be a kind of Hash)
|
177
|
+
Kind::Enumerable === {} # true
|
178
|
+
Kind::Enumerable === '' # false
|
116
179
|
```
|
117
180
|
|
118
|
-
|
181
|
+
[⬆️ Back to Top](#table-of-contents-)
|
119
182
|
|
120
|
-
|
121
|
-
value = nil
|
183
|
+
### Kind::\<Type\>.value?()
|
122
184
|
|
123
|
-
|
185
|
+
This method works like `.===`, but the difference is what happens when you invoke it without arguments.
|
124
186
|
|
125
|
-
|
187
|
+
```ruby
|
188
|
+
# Example of calling `.value?` with an argument:
|
126
189
|
|
127
|
-
Kind.
|
190
|
+
Kind::Enumerable.value?({}) # true
|
191
|
+
Kind::Enumerable.value?('') # false
|
128
192
|
```
|
129
193
|
|
130
|
-
|
131
|
-
|
132
|
-
#### Method aliases to perform a strict validation
|
194
|
+
When `.value?` is called without an argument, it will return a lambda which will know how to perform the kind verification.
|
133
195
|
|
134
196
|
```ruby
|
135
|
-
|
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: {}] # {}
|
197
|
+
collection = [ {number: 1}, 'number 2', {number: 3}, :number_4, [:number, 5] ]
|
139
198
|
|
140
|
-
#
|
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: {}) # {}
|
199
|
+
collection.select(&Kind::Enumerable.value?) # [{:number=>1}, {:number=>3}, [:number, 5]]
|
146
200
|
```
|
147
201
|
|
148
|
-
|
202
|
+
[⬆️ Back to Top](#table-of-contents-)
|
203
|
+
|
204
|
+
### Kind::\<Type\>.or_nil()
|
149
205
|
|
150
206
|
But if you don't need a strict type verification, use the `.or_nil` method.
|
151
207
|
|
152
208
|
```ruby
|
153
|
-
Kind
|
154
|
-
Kind
|
209
|
+
Kind::Hash.or_nil('') # nil
|
210
|
+
Kind::Hash.or_nil({a: 1}) # {a: 1}
|
211
|
+
```
|
155
212
|
|
156
|
-
#
|
213
|
+
[⬆️ Back to Top](#table-of-contents-)
|
214
|
+
|
215
|
+
### Kind::\<Type\>.or_undefined()
|
157
216
|
|
158
|
-
Kind
|
159
|
-
|
217
|
+
This method works like `.or_nil`, but it will return a [`Kind::Undefined`](#kindundefined) instead of `nil`.
|
218
|
+
|
219
|
+
```ruby
|
220
|
+
Kind::Hash.or_undefined('') # Kind::Undefined
|
221
|
+
Kind::Hash.or_undefined({a: 1}) # {a: 1}
|
160
222
|
```
|
161
223
|
|
162
|
-
|
224
|
+
[⬆️ Back to Top](#table-of-contents-)
|
163
225
|
|
164
|
-
|
226
|
+
### Kind::\<Type\>.or()
|
165
227
|
|
166
|
-
|
167
|
-
Kind.of.Hash.instance?({}) # true
|
168
|
-
Kind.of.Hash.instance?({}, HashWithIndifferentAccess.new) # true
|
228
|
+
This method can return a fallback if the given value isn't an instance of the expected kind.
|
169
229
|
|
170
|
-
|
171
|
-
Kind
|
230
|
+
```ruby
|
231
|
+
Kind::Hash.or({}, []) # {}
|
232
|
+
Kind::Hash.or(nil, []) # nil
|
233
|
+
Kind::Hash.or(nil, {a: 1}) # {a: 1}
|
234
|
+
```
|
172
235
|
|
173
|
-
|
236
|
+
If it doesn't receive a second argument (the value), it will return a callable that knows how to expose an instance of the expected type or a fallback if the given value is wrong.
|
174
237
|
|
175
|
-
|
176
|
-
|
238
|
+
```ruby
|
239
|
+
collection = [ {number: 1}, 'number 2', {number: 3}, :number_4, [:number, 5] ]
|
177
240
|
|
178
|
-
Kind.
|
179
|
-
Kind.
|
241
|
+
collection.map(&Kind::Hash.or({})) # [{:number=>1}, {}, {:number=>3}, {}, {}]
|
242
|
+
collection.map(&Kind::Hash.or(nil)) # [{:number=>1}, nil, {:number=>3}, nil, nil]
|
180
243
|
```
|
181
244
|
|
182
|
-
|
183
|
-
> it will return a lambda which will perform the kind verification.
|
245
|
+
An error will be raised if the fallback didn't have the expected kind or if not `nil` / `Kind::Undefined`.
|
184
246
|
|
185
247
|
```ruby
|
186
|
-
collection = [ {number: 1}, 'number 2', {number: 3}, :number_4 ]
|
248
|
+
collection = [ {number: 1}, 'number 2', {number: 3}, :number_4, [:number, 5] ]
|
187
249
|
|
188
|
-
collection
|
189
|
-
.select(&Kind.of.Hash.instance?) # [{:number=>1}, {:number=>3}]
|
250
|
+
collection.map(&Kind::Hash.or(:foo)) # Kind::Error (:foo expected to be a kind of Hash)
|
190
251
|
```
|
191
252
|
|
192
|
-
|
193
|
-
|
253
|
+
[⬆️ Back to Top](#table-of-contents-)
|
254
|
+
|
255
|
+
### Kind::\<Type\>.value()
|
194
256
|
|
257
|
+
This method ensures that you will have a value of the expected kind. But, in the case of the given value be invalid, this method will require a default value (with the expected kind) to be returned.
|
195
258
|
```ruby
|
196
|
-
Kind.
|
197
|
-
Kind.of.Hash?({}, HashWithIndifferentAccess.new) # true
|
259
|
+
Kind::String.value(1, default: '') # ""
|
198
260
|
|
199
|
-
Kind.
|
200
|
-
Kind.of.Hash?({}, '') # false
|
261
|
+
Kind::String.value('1', default: '') # "1"
|
201
262
|
|
202
|
-
#
|
263
|
+
Kind::String.value('1', default: 1) # Kind::Error (1 expected to be a kind of String)
|
264
|
+
```
|
203
265
|
|
204
|
-
|
205
|
-
Kind.of.Boolean?(false, true) # true
|
266
|
+
[⬆️ Back to Top](#table-of-contents-)
|
206
267
|
|
207
|
-
Kind
|
208
|
-
Kind.of.Boolean?(false, true, nil) # false
|
268
|
+
### Kind::\<Type\>.maybe
|
209
269
|
|
210
|
-
#
|
270
|
+
This method exposes a [typed `Kind::Maybe`](#kindmaybetype) and using it will be possible to apply a sequence of operations in the case of the wrapped value has the expected kind.
|
211
271
|
|
212
|
-
|
272
|
+
```ruby
|
273
|
+
Double = ->(value) do
|
274
|
+
Kind::Numeric.maybe(value)
|
275
|
+
.then { |number| number * 2 }
|
276
|
+
.value_or(0)
|
277
|
+
end
|
213
278
|
|
214
|
-
|
279
|
+
Double.('2') # 0
|
280
|
+
Double.(2) # 4
|
215
281
|
```
|
216
282
|
|
217
|
-
|
218
|
-
|
219
|
-
You can use `Kind.is` to verify if some class has the expected type as its ancestor.
|
283
|
+
If it is invoked without arguments, it returns the typed Maybe. But, if it receives arguments, it will behave like the `Kind::Maybe.wrap` method. e.g.
|
220
284
|
|
221
285
|
```ruby
|
222
|
-
Kind.
|
286
|
+
Kind::Integer.maybe #<Kind::Maybe::Typed:0x0000... @kind=Kind::Integer>
|
223
287
|
|
224
|
-
Kind.
|
288
|
+
Kind::Integer.maybe(0).some? # true
|
289
|
+
Kind::Integer.maybe { 1 }.some? # true
|
290
|
+
Kind::Integer.maybe(2) { |n| n * 2 }.some? # true
|
225
291
|
|
226
|
-
Kind.
|
292
|
+
Kind::Integer.maybe { 2 / 0 }.none? # true
|
293
|
+
Kind::Integer.maybe(2) { |n| n / 0 }.none? # true
|
294
|
+
Kind::Integer.maybe('2') { |n| n * n }.none? # true
|
227
295
|
```
|
228
296
|
|
229
|
-
|
297
|
+
> **Note:** You can use `Kind::\<Type\>.optional` as an alias for `Kind::\<Type\>.maybe`.
|
298
|
+
|
299
|
+
[⬆️ Back to Top](#table-of-contents-)
|
300
|
+
|
301
|
+
### Kind::\<Type\>?
|
302
|
+
|
303
|
+
There is a second way to do a type verification and know if one or multiple values has the expected type. You can use the predicate kind methods (`Kind::Hash?`). e.g:
|
230
304
|
|
231
305
|
```ruby
|
232
|
-
|
306
|
+
# Verifying one value
|
307
|
+
Kind::Enumerable?({}) # true
|
233
308
|
|
234
|
-
|
309
|
+
# Verifying multiple values
|
310
|
+
Kind::Enumerable?({}, [], Set.new) # true
|
235
311
|
```
|
236
312
|
|
237
|
-
|
313
|
+
Like the `Kind::<Type>.value?` method, if the `Kind::<Type>?` doesn't receive an argument, it will return a lambda which will know how to perform the kind verification.
|
238
314
|
|
239
315
|
```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
|
316
|
+
collection = [ {number: 1}, 'number 2', {number: 3}, :number_4, [:number, 5] ]
|
246
317
|
|
247
|
-
|
248
|
-
|
249
|
-
Kind.is(Human, Person) # true
|
318
|
+
collection.select(&Kind::Enumerable?) # [{:number=>1}, {:number=>3}, [:number, 5]]
|
319
|
+
```
|
250
320
|
|
251
|
-
|
321
|
+
[⬆️ Back to Top](#table-of-contents-)
|
252
322
|
|
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
|
323
|
+
### Kind::{Array,Hash,String,Set}.value_or_empty()
|
259
324
|
|
260
|
-
|
261
|
-
|
262
|
-
Kind.
|
325
|
+
This method is available for some type checkers (`Kind::Array`, `Kind::Hash`, `Kind::String`, `Kind::Set`), and it will return an empty frozen value if the given one hasn't the expected kind.
|
326
|
+
```ruby
|
327
|
+
Kind::Array.value_or_empty({}) # []
|
328
|
+
Kind::Array.value_or_empty({}).frozen? # true
|
329
|
+
```
|
263
330
|
|
264
|
-
|
331
|
+
[⬆️ Back to Top](#table-of-contents-)
|
265
332
|
|
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
|
333
|
+
### List of all type checkers
|
272
334
|
|
273
|
-
|
274
|
-
Kind.is(Human, Human) # true
|
275
|
-
Kind.is(Human, Person) # true
|
335
|
+
#### Core
|
276
336
|
|
277
|
-
Kind
|
278
|
-
|
337
|
+
* `Kind::Array`
|
338
|
+
* `Kind::Class`
|
339
|
+
* `Kind::Comparable`
|
340
|
+
* `Kind::Enumerable`
|
341
|
+
* `Kind::Enumerator`
|
342
|
+
* `Kind::File`
|
343
|
+
* `Kind::Float`
|
344
|
+
* `Kind::Hash`
|
345
|
+
* `Kind::Integer`
|
346
|
+
* `Kind::IO`
|
347
|
+
* `Kind::Method`
|
348
|
+
* `Kind::Module`
|
349
|
+
* `Kind::Numeric`
|
350
|
+
* `Kind::Proc`
|
351
|
+
* `Kind::Queue`
|
352
|
+
* `Kind::Range`
|
353
|
+
* `Kind::Regexp`
|
354
|
+
* `Kind::String`
|
355
|
+
* `Kind::Struct`
|
356
|
+
* `Kind::Symbol`
|
357
|
+
* `Kind::Time`
|
358
|
+
|
359
|
+
#### Stdlib
|
360
|
+
|
361
|
+
* `Kind::OpenStruct`
|
362
|
+
* `Kind::Set`
|
279
363
|
|
280
|
-
|
364
|
+
#### Custom
|
281
365
|
|
282
|
-
|
366
|
+
* `Kind::Boolean`
|
367
|
+
* `Kind::Callable`
|
368
|
+
* `Kind::Lambda`
|
283
369
|
|
284
|
-
|
370
|
+
[⬆️ Back to Top](#table-of-contents-)
|
285
371
|
|
286
|
-
|
372
|
+
### Creating type checkers
|
373
|
+
|
374
|
+
There are two ways to do this, you can create type checkers dynamically or defining a module.
|
375
|
+
|
376
|
+
#### Dynamic creation
|
377
|
+
|
378
|
+
##### Using a class or a module
|
287
379
|
|
288
380
|
```ruby
|
289
381
|
class User
|
@@ -291,365 +383,486 @@ end
|
|
291
383
|
|
292
384
|
user = User.new
|
293
385
|
|
294
|
-
|
295
|
-
# Verifiyng the value kind #
|
296
|
-
# ------------------------ #
|
297
|
-
|
298
|
-
Kind.of(User, user) # <User ...>
|
299
|
-
Kind.of(User, {}) # Kind::Error ({} expected to be a kind of User)
|
386
|
+
kind_of_user = Kind::Of(User)
|
300
387
|
|
301
|
-
|
302
|
-
|
388
|
+
# kind_of_user.name
|
389
|
+
# kind_of_user.kind
|
390
|
+
# The type checker can return its kind and its name
|
391
|
+
kind_of_user.name # "User"
|
392
|
+
kind_of_user.kind # User
|
303
393
|
|
304
|
-
#
|
305
|
-
#
|
306
|
-
|
394
|
+
# kind_of_user.===
|
395
|
+
# Can check if a given value is an instance of its kind.
|
396
|
+
kind_of_user === 0 # false
|
397
|
+
kind_of_user === User.new # true
|
307
398
|
|
308
|
-
|
309
|
-
|
399
|
+
# kind_of_user.value?(value)
|
400
|
+
# Can check if a given value is an instance of its kind.
|
401
|
+
kind_of_user.value?('') # false
|
402
|
+
kind_of_user.value?(User.new) # true
|
310
403
|
|
311
|
-
|
312
|
-
|
404
|
+
# If it doesn't receive an argument, a lambda will be returned and it will know how to do the type verification.
|
405
|
+
[0, User.new].select?(&kind_of_user.value?) # [#<User:0x0000.... >]
|
313
406
|
|
314
|
-
#
|
315
|
-
#
|
316
|
-
#
|
317
|
-
|
318
|
-
.select(&Kind.of?(Numeric)) # [1, 3.0]
|
407
|
+
# kind_of_user.or_nil(value)
|
408
|
+
# Can return nil if the given value isn't an instance of its kind
|
409
|
+
kind_of_user.or_nil({}) # nil
|
410
|
+
kind_of_user.or_nil(User.new) # #<User:0x0000.... >
|
319
411
|
|
320
|
-
#
|
321
|
-
#
|
322
|
-
#
|
412
|
+
# kind_of_user.or_undefined(value)
|
413
|
+
# Can return Kind::Undefined if the given value isn't an instance of its kind
|
414
|
+
kind_of_user.or_undefined([]) # Kind::Undefined
|
415
|
+
kind_of_user.or_undefined(User.new) # #<User:0x0000.... >
|
323
416
|
|
324
|
-
kind_of_user
|
417
|
+
# kind_of_user.or(fallback, value)
|
418
|
+
# Can return a fallback if the given value isn't an instance of its kind
|
419
|
+
kind_of_user.or(nil, 0) # nil
|
420
|
+
kind_of_user.or(nil, User.new) # #<User:0x0000.... >
|
325
421
|
|
326
|
-
|
422
|
+
# If it doesn't receive a second argument (the value), it will return a callable that knows how to expose an instance of the expected type or a fallback if the given value was wrong.
|
423
|
+
[1, User.new].map(&kind_of_user.or(nil)) # [nil, #<User:0x0000.... >]
|
327
424
|
|
328
|
-
|
329
|
-
kind_of_user.
|
425
|
+
# An error will be raised if the fallback didn't have the expected kind or if not nil / Kind::Undefined.
|
426
|
+
[0, User.new].map(&kind_of_user.or(:foo)) # Kind::Error (:foo expected to be a kind of User)
|
330
427
|
|
331
|
-
|
332
|
-
|
428
|
+
# kind_of_user[value]
|
429
|
+
# Will raise Kind::Error if the given value isn't an instance of the expected kind
|
430
|
+
kind_of_user[:foo] # Kind::Error (:foo expected to be a kind of User)
|
431
|
+
kind_of_user[User.new] # #<User:0x0000.... >
|
333
432
|
|
334
|
-
#
|
335
|
-
#
|
336
|
-
|
337
|
-
collection = [User.new, User.new, 0, {} nil, User.new]
|
433
|
+
# kind_of_user.value(arg, default:)
|
434
|
+
# This method ensures that you will have a value of the expected kind. But, in the case of the given value be invalid, this method will require a default value (with the expected kind) to be returned.
|
435
|
+
kind_of_user.value(User.new, default: User.new) # #<User:0x0000...>
|
338
436
|
|
339
|
-
|
437
|
+
kind_of_user.value('1', default: User.new) # #<User:0x0000...>
|
340
438
|
|
341
|
-
|
439
|
+
kind_of_user.value('1', default: 1) # Kind::Error (1 expected to be a kind of User)
|
342
440
|
|
343
|
-
#
|
344
|
-
#
|
441
|
+
# kind_of_user.maybe
|
442
|
+
# This method returns a typed Kind::Maybe.
|
443
|
+
kind_of_user.maybe('1').value_or(User.new) # #<User:0x0000...>
|
444
|
+
```
|
345
445
|
|
346
|
-
|
446
|
+
[⬆️ Back to Top](#table-of-contents-)
|
347
447
|
|
348
|
-
|
349
|
-
# Kind.is() can be used to check a class/module #
|
350
|
-
# --------------------------------------------- #
|
448
|
+
##### Using an object which responds to ===
|
351
449
|
|
352
|
-
|
353
|
-
end
|
450
|
+
Example using a lambda (an object which responds to .===) and a hash with the kind name.
|
354
451
|
|
355
|
-
|
452
|
+
```ruby
|
453
|
+
PositiveInteger = Kind::Of(
|
454
|
+
-> value { value.kind_of?(Integer) && value > 0 },
|
455
|
+
name: 'PositiveInteger'
|
456
|
+
)
|
457
|
+
|
458
|
+
# PositiveInteger.name
|
459
|
+
# PositiveInteger.kind
|
460
|
+
# The type checker can return its kind and its name
|
461
|
+
PositiveInteger.name # "PositiveInteger"
|
462
|
+
PositiveInteger.kind # #<Proc:0x0000.... >
|
463
|
+
|
464
|
+
# PositiveInteger.===
|
465
|
+
# Can check if a given value is an instance of its kind.
|
466
|
+
PositiveInteger === 1 # true
|
467
|
+
PositiveInteger === 0 # false
|
468
|
+
|
469
|
+
# PositiveInteger.value?(value)
|
470
|
+
# Can check if a given value is an instance of its kind.
|
471
|
+
PositiveInteger.value?(1) # true
|
472
|
+
PositiveInteger.value?(-1) # false
|
473
|
+
|
474
|
+
# If it doesn't receive an argument, a lambda will be returned and it will know how to do the type verification.
|
475
|
+
[1, 2, 0, 3, -1].select?(&PositiveInteger.value?) # [1, 2, 3]
|
476
|
+
|
477
|
+
# PositiveInteger.or_nil(value)
|
478
|
+
# Can return nil if the given value isn't an instance of its kind
|
479
|
+
PositiveInteger.or_nil(1) # 1
|
480
|
+
PositiveInteger.or_nil(0) # nil
|
481
|
+
|
482
|
+
# PositiveInteger.or_undefined(value)
|
483
|
+
# Can return Kind::Undefined if the given value isn't an instance of its kind
|
484
|
+
PositiveInteger.or_undefined(2) # 2
|
485
|
+
PositiveInteger.or_undefined(-1) # Kind::Undefined
|
486
|
+
|
487
|
+
# PositiveInteger.or(fallback, value)
|
488
|
+
# Can return a fallback if the given value isn't an instance of its kind
|
489
|
+
PositiveInteger.or(nil, 1) # 1
|
490
|
+
PositiveInteger.or(nil, 0) # nil
|
491
|
+
|
492
|
+
# If it doesn't receive a second argument (the value), it will return a callable that knows how to expose an instance of the expected type or a fallback if the given value was wrong.
|
493
|
+
[1, 2, 0, 3, -1].map(&PositiveInteger.or(1)) # [1, 2, 1, 3, 1]
|
494
|
+
[1, 2, 0, 3, -1].map(&PositiveInteger.or(nil)) # [1, 2, nil, 3, nil]
|
495
|
+
|
496
|
+
# An error will be raised if the fallback didn't have the expected kind or if not nil / Kind::Undefined.
|
497
|
+
[1, 2, 0, 3, -1].map(&PositiveInteger.or(:foo)) # Kind::Error (:foo expected to be a kind of PositiveInteger)
|
498
|
+
|
499
|
+
# PositiveInteger[value]
|
500
|
+
# Will raise Kind::Error if the given value isn't an instance of the expected kind
|
501
|
+
PositiveInteger[1] # 1
|
502
|
+
PositiveInteger[:foo] # Kind::Error (:foo expected to be a kind of PositiveInteger)
|
503
|
+
|
504
|
+
# PositiveInteger.value(arg, default:)
|
505
|
+
# This method ensures that you will have a value of the expected kind. But, in the case of the given value be invalid, this method will require a default value (with the expected kind) to be returned.
|
506
|
+
PositiveInteger.value(2, default: 1) # 2
|
507
|
+
|
508
|
+
PositiveInteger.value('1', default: 1) # 1
|
509
|
+
|
510
|
+
PositiveInteger.value('1', default: 0) # Kind::Error (0 expected to be a kind of PositiveInteger)
|
511
|
+
|
512
|
+
# PositiveInteger.maybe
|
513
|
+
# This method returns a typed Kind::Maybe.
|
514
|
+
PositiveInteger.maybe(0).value_or(1) # 1
|
515
|
+
|
516
|
+
PositiveInteger.maybe(2).value_or(1) # 2
|
356
517
|
```
|
357
518
|
|
358
|
-
|
519
|
+
[⬆️ Back to Top](#table-of-contents-)
|
359
520
|
|
360
|
-
|
521
|
+
#### Kind::<Type> module
|
522
|
+
|
523
|
+
The idea here is to create a type checker inside of the `Kind` namespace, so to do this, you will only need to use pure Ruby. e.g:
|
361
524
|
|
362
525
|
```ruby
|
363
526
|
class User
|
364
527
|
end
|
365
528
|
|
366
|
-
|
367
|
-
|
368
|
-
|
529
|
+
module Kind
|
530
|
+
module User
|
531
|
+
extend self, TypeChecker
|
369
532
|
|
370
|
-
#
|
533
|
+
# Define the expected kind of this type checker.
|
534
|
+
def kind; ::User; end
|
535
|
+
end
|
371
536
|
|
372
|
-
|
373
|
-
|
537
|
+
# This how the Kind::<Type>? methods are defined.
|
538
|
+
def self.User?(*values)
|
539
|
+
KIND.of?(::User, values)
|
540
|
+
end
|
374
541
|
end
|
375
542
|
|
376
|
-
#
|
377
|
-
# Usage examples: #
|
378
|
-
# --------------- #
|
379
|
-
|
380
|
-
Kind.of.User(User.new) # #<User:0x0000...>
|
543
|
+
# Doing this you will have the same methods of a standard type checker (like: `Kind::Symbol`).
|
381
544
|
|
382
|
-
|
383
|
-
|
384
|
-
Kind.of.User.or_nil({}) # nil
|
545
|
+
user = User.new
|
385
546
|
|
386
|
-
Kind
|
387
|
-
Kind
|
547
|
+
Kind::User[user] # #<User:0x0000...>
|
548
|
+
Kind::User[{}] # Kind::Error ({} expected to be a kind of User)
|
388
549
|
|
389
|
-
Kind
|
390
|
-
Kind
|
550
|
+
Kind::User?(user) # true
|
551
|
+
Kind::User?({}) # false
|
391
552
|
```
|
392
553
|
|
393
|
-
|
554
|
+
The advantages of this approach are:
|
394
555
|
|
395
|
-
|
556
|
+
1. You will have a singleton (a unique instance) to be used, so the garbage collector will work less.
|
557
|
+
2. You can define additional methods to be used with this kind.
|
396
558
|
|
397
|
-
The
|
559
|
+
The disadvantage is:
|
398
560
|
|
399
|
-
|
400
|
-
module Account
|
401
|
-
class User
|
402
|
-
Kind::Types.add(self)
|
403
|
-
end
|
404
|
-
end
|
405
|
-
|
406
|
-
module Account
|
407
|
-
class User
|
408
|
-
class Membership
|
409
|
-
Kind::Types.add(self)
|
410
|
-
end
|
411
|
-
end
|
412
|
-
end
|
413
|
-
|
414
|
-
Kind.of.Account::User({}) # Kind::Error ({} expected to be a kind of Account::User)
|
561
|
+
1. You could overwrite some standard type checker or constant. I believe that this will be hard to happen, but must be your concern if you decide to use this kind of approach.
|
415
562
|
|
416
|
-
|
563
|
+
[⬆️ Back to Top](#table-of-contents-)
|
417
564
|
|
418
|
-
|
565
|
+
### Utility methods
|
419
566
|
|
420
|
-
Kind.
|
421
|
-
Kind.of.Account::User.instance?(Account::User.new) # true
|
567
|
+
#### Kind.of_class?()
|
422
568
|
|
423
|
-
|
424
|
-
Kind.of.Account::User.class?(Account::User) # true
|
569
|
+
This method verify if a given value is a `Class`.
|
425
570
|
|
426
|
-
|
427
|
-
|
428
|
-
Kind.
|
571
|
+
```ruby
|
572
|
+
Kind.of_class?(Hash) # true
|
573
|
+
Kind.of_class?(Enumerable) # false
|
574
|
+
Kind.of_class?(1) # false
|
575
|
+
```
|
429
576
|
|
430
|
-
|
577
|
+
[⬆️ Back to Top](#table-of-contents-)
|
431
578
|
|
432
|
-
Kind.
|
579
|
+
#### Kind.of_module?()
|
433
580
|
|
434
|
-
|
435
|
-
Kind.of.Account::User::Membership.instance?(Account::User::Membership.new) # true
|
581
|
+
This method verify if a given value is a `Module`.
|
436
582
|
|
437
|
-
|
438
|
-
Kind.
|
583
|
+
```ruby
|
584
|
+
Kind.of_module?(Hash) # false
|
585
|
+
Kind.of_module?(Enumerable) # true
|
586
|
+
Kind.of_module?(1) # false
|
439
587
|
```
|
440
588
|
|
441
|
-
[⬆️ Back to Top](#table-of-contents-)
|
589
|
+
[⬆️ Back to Top](#table-of-contents-)
|
590
|
+
|
591
|
+
#### Kind.of_module_or_class()
|
442
592
|
|
443
|
-
|
593
|
+
This method return the given value if it is a module or a class. If not, a `Kind::Error` will be raised.
|
444
594
|
|
445
|
-
|
595
|
+
```ruby
|
596
|
+
Kind.of_module_or_class(String) # String
|
597
|
+
Kind.of_module_or_class(1) # Kind::Error (1 expected to be a kind of Module/Class)
|
598
|
+
```
|
446
599
|
|
447
|
-
|
600
|
+
[⬆️ Back to Top](#table-of-contents-)
|
448
601
|
|
449
|
-
|
450
|
-
- `Kind.of.Symbol`
|
451
|
-
- `Kind.of.Numeric`
|
452
|
-
- `Kind.of.Integer`
|
453
|
-
- `Kind.of.Float`
|
454
|
-
- `Kind.of.Regexp`
|
455
|
-
- `Kind.of.Time`
|
456
|
-
- `Kind.of.Array`
|
457
|
-
- `Kind.of.Range`
|
458
|
-
- `Kind.of.Hash`
|
459
|
-
- `Kind.of.Struct`
|
460
|
-
- `Kind.of.Enumerator`
|
461
|
-
- `Kind.of.Set`
|
462
|
-
- `Kind.of.Method`
|
463
|
-
- `Kind.of.Proc`
|
464
|
-
- `Kind.of.IO`
|
465
|
-
- `Kind.of.File`
|
602
|
+
#### Kind.respond_to()
|
466
603
|
|
467
|
-
|
604
|
+
this method returns the given object if it responds to all of the method names. But if the object does not respond to some of the expected methods, an error will be raised.
|
605
|
+
```ruby
|
606
|
+
Kind.respond_to('', :upcase) # ""
|
607
|
+
Kind.respond_to('', :upcase, :strip) # ""
|
468
608
|
|
469
|
-
|
470
|
-
|
609
|
+
Kind.respond_to(1, :upcase) # expected 1 to respond to :upcase
|
610
|
+
Kind.respond_to(2, :to_s, :upcase) # expected 2 to respond to :upcase
|
611
|
+
```
|
471
612
|
|
472
|
-
|
613
|
+
[⬆️ Back to Top](#table-of-contents-)
|
473
614
|
|
474
|
-
|
475
|
-
- `Kind.of.Module()`
|
476
|
-
- `Kind.of.Lambda()`
|
477
|
-
- `Kind.of.Boolean()`
|
478
|
-
- `Kind.of.Callable()`: verifies if the given value `respond_to?(:call)`.
|
479
|
-
- `Kind.of.Maybe()` or its alias `Kind.of.Optional()`
|
615
|
+
#### Kind.of()
|
480
616
|
|
481
|
-
|
617
|
+
There is a second way to do a strict type verification, you can use the `Kind.of()` method to do this. It receives the kind as the first argument and the value to be checked as the second one.
|
618
|
+
```ruby
|
619
|
+
Kind.of(Hash, {}) # {}
|
620
|
+
Kind.of(Hash, []) # Kind::Error ([] expected to be a kind of Hash)
|
621
|
+
```
|
482
622
|
|
483
|
-
[⬆️ Back to Top](#table-of-contents-)
|
623
|
+
[⬆️ Back to Top](#table-of-contents-)
|
484
624
|
|
485
|
-
|
625
|
+
#### Kind.of?()
|
486
626
|
|
487
|
-
This
|
627
|
+
This method can be used to check if one or multiple values have the expected kind.
|
488
628
|
|
489
629
|
```ruby
|
490
|
-
|
491
|
-
|
630
|
+
# Checking one value
|
631
|
+
Kind.of?(Array, []) # true
|
632
|
+
Kind.of?(Array, {}) # false
|
633
|
+
|
634
|
+
# Checking multiple values
|
635
|
+
Kind.of?(Enumerable, [], {}) # true
|
636
|
+
Kind.of?(Hash, {}, {}) # true
|
637
|
+
Kind.of?(Array, [], {}) # false
|
638
|
+
```
|
492
639
|
|
493
|
-
|
640
|
+
If the method receives only the first argument (the kind) a lambda will be returned and it will know how to do the type verification.
|
494
641
|
|
495
|
-
|
496
|
-
|
642
|
+
```ruby
|
643
|
+
[1, '2', 3].select(&Kind.of?(Numeric)) # [1, 3]
|
497
644
|
```
|
498
645
|
|
499
|
-
|
646
|
+
[⬆️ Back to Top](#table-of-contents-)
|
500
647
|
|
648
|
+
#### Kind.value()
|
649
|
+
|
650
|
+
This method ensures that you will have a value of the expected kind. But, in the case of the given value be invalid, this method will require a default value (with the expected kind) to be returned.
|
501
651
|
```ruby
|
502
|
-
|
503
|
-
gem 'kind', require: 'kind/active_model/validation'
|
652
|
+
Kind.value(String, '1', default: '') # "1"
|
504
653
|
|
505
|
-
|
506
|
-
|
654
|
+
Kind.value(String, 1, default: '') # ""
|
655
|
+
|
656
|
+
Kind.value(String, 1, default: 2) # Kind::Error (2 expected to be a kind of String)
|
507
657
|
```
|
508
658
|
|
509
|
-
|
659
|
+
[⬆️ Back to Top](#table-of-contents-)
|
660
|
+
|
661
|
+
#### Kind.is()
|
510
662
|
|
511
|
-
|
663
|
+
You can use `Kind.is` to verify if some class has the expected type as its ancestor.
|
512
664
|
|
513
665
|
```ruby
|
514
|
-
|
666
|
+
Kind.is(Hash, String) # false
|
515
667
|
|
516
|
-
#
|
517
|
-
# is an instance of one of the classes/modules.
|
668
|
+
Kind.is(Hash, Hash) # true
|
518
669
|
|
519
|
-
|
670
|
+
Kind.is(Enumerable, Hash) # true
|
520
671
|
```
|
521
672
|
|
522
|
-
|
673
|
+
The `Kind.is` also could check the inheritance of Classes/Modules.
|
523
674
|
|
524
675
|
```ruby
|
525
676
|
#
|
526
|
-
# Verifying if
|
677
|
+
# Verifying if a class is or inherits from the expected class.
|
527
678
|
#
|
528
679
|
class Human; end
|
529
680
|
class Person < Human; end
|
530
681
|
class User < Human; end
|
531
682
|
|
532
|
-
|
683
|
+
Kind.is(Human, User) # true
|
684
|
+
Kind.is(Human, Human) # true
|
685
|
+
Kind.is(Human, Person) # true
|
686
|
+
|
687
|
+
Kind.is(Human, Struct) # false
|
533
688
|
|
534
689
|
#
|
535
|
-
# Verifying if the
|
690
|
+
# Verifying if the classes included a module.
|
536
691
|
#
|
537
692
|
module Human; end
|
538
693
|
class Person; include Human; end
|
539
694
|
class User; include Human; end
|
540
695
|
|
541
|
-
|
696
|
+
Kind.is(Human, User) # true
|
697
|
+
Kind.is(Human, Human) # true
|
698
|
+
Kind.is(Human, Person) # true
|
699
|
+
|
700
|
+
Kind.is(Human, Struct) # false
|
542
701
|
|
543
702
|
#
|
544
|
-
# Verifying if
|
703
|
+
# Verifying if a class is or inherits from the expected module.
|
545
704
|
#
|
546
705
|
module Human; end
|
547
706
|
module Person; extend Human; end
|
548
707
|
module User; extend Human; end
|
549
708
|
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
# is a kind of one those classes/modules.
|
709
|
+
Kind.is(Human, User) # true
|
710
|
+
Kind.is(Human, Human) # true
|
711
|
+
Kind.is(Human, Person) # true
|
554
712
|
|
555
|
-
|
713
|
+
Kind.is(Human, Struct) # false
|
556
714
|
```
|
557
715
|
|
558
|
-
|
716
|
+
[⬆️ Back to Top](#table-of-contents-)
|
717
|
+
|
718
|
+
### Utility modules
|
719
|
+
|
720
|
+
#### Kind::Try
|
721
|
+
|
722
|
+
The method `.call` of this module invokes a public method with or without arguments like `public_send` does, except that if the receiver does not respond to it the call returns `nil` rather than raising an exception.
|
559
723
|
|
560
724
|
```ruby
|
561
|
-
|
725
|
+
Kind::Try.(' foo ', :strip) # "foo"
|
726
|
+
Kind::Try.({a: 1}, :[], :a) # 1
|
727
|
+
Kind::Try.({a: 1}, :[], :b) # nil
|
728
|
+
Kind::Try.({a: 1}, :fetch, :b, 2) # 2
|
562
729
|
|
563
|
-
#
|
564
|
-
|
730
|
+
Kind::Try.(:symbol, :strip) # nil
|
731
|
+
Kind::Try.(:symbol, :fetch, :b, 2) # nil
|
565
732
|
|
566
|
-
|
733
|
+
# It raises an exception if the method name isn't a string or a symbol
|
734
|
+
Kind::Try.({a: 1}, 1, :a) # TypeError (1 is not a symbol nor a string)
|
567
735
|
```
|
568
736
|
|
569
|
-
|
570
|
-
**[Object#respond_to?](https://ruby-doc.org/core-2.6.4/Object.html#method-i-respond_to-3F)**
|
737
|
+
This module has the method `[]` that knows how to create a lambda that will know how to perform the `try` strategy.
|
571
738
|
|
572
739
|
```ruby
|
573
|
-
|
740
|
+
results =
|
741
|
+
[
|
742
|
+
{},
|
743
|
+
{name: 'Foo Bar'},
|
744
|
+
{name: 'Rodrigo Serradura'},
|
745
|
+
].map(&Kind::Try[:fetch, :name, 'John Doe'])
|
746
|
+
|
747
|
+
p results # ["John Doe", "Foo Bar", "Rodrigo Serradura"]
|
574
748
|
```
|
575
749
|
|
576
|
-
|
750
|
+
[⬆️ Back to Top](#table-of-contents-)
|
751
|
+
|
752
|
+
#### Kind::Dig
|
753
|
+
|
754
|
+
The method `.call` of this module has the same behavior of Ruby dig methods ([Hash](https://ruby-doc.org/core-2.3.0/Hash.html#method-i-dig), [Array](https://ruby-doc.org/core-2.3.0/Array.html#method-i-dig), [Struct](https://ruby-doc.org/core-2.3.0/Struct.html#method-i-dig), [OpenStruct](https://ruby-doc.org/stdlib-2.3.0/libdoc/ostruct/rdoc/OpenStruct.html#method-i-dig)), but it will not raise an error if some step can't be digged.
|
577
755
|
|
578
756
|
```ruby
|
579
|
-
|
757
|
+
s = Struct.new(:a, :b).new(101, 102)
|
758
|
+
o = OpenStruct.new(c: 103, d: 104)
|
759
|
+
d = { struct: s, ostruct: o, data: [s, o]}
|
580
760
|
|
581
|
-
#
|
582
|
-
#
|
761
|
+
Kind::Dig.(s, [:a]) # 101
|
762
|
+
Kind::Dig.(o, [:c]) # 103
|
583
763
|
|
584
|
-
|
764
|
+
Kind::Dig.(d, [:struct, :b]) # 102
|
765
|
+
Kind::Dig.(d, [:data, 0, :b]) # 102
|
766
|
+
Kind::Dig.(d, [:data, 0, 'b']) # 102
|
767
|
+
|
768
|
+
Kind::Dig.(d, [:ostruct, :d]) # 104
|
769
|
+
Kind::Dig.(d, [:data, 1, :d]) # 104
|
770
|
+
Kind::Dig.(d, [:data, 1, 'd']) # 104
|
771
|
+
|
772
|
+
Kind::Dig.(d, [:struct, :f]) # nil
|
773
|
+
Kind::Dig.(d, [:ostruct, :f]) # nil
|
774
|
+
Kind::Dig.(d, [:data, 0, :f]) # nil
|
775
|
+
Kind::Dig.(d, [:data, 1, :f]) # nil
|
585
776
|
```
|
586
777
|
|
587
|
-
|
778
|
+
Another difference between the `Kind::Dig` and the native Ruby dig, is that it knows how to extract values from regular objects.
|
588
779
|
|
589
780
|
```ruby
|
590
|
-
|
591
|
-
|
781
|
+
class Person
|
782
|
+
attr_reader :name
|
592
783
|
|
593
|
-
|
594
|
-
|
784
|
+
def initialize(name)
|
785
|
+
@name = name
|
786
|
+
end
|
787
|
+
end
|
595
788
|
|
596
|
-
|
789
|
+
person = Person.new('Rodrigo')
|
597
790
|
|
598
|
-
|
791
|
+
Kind::Dig.(person, [:name]) # "Rodrigo"
|
599
792
|
|
600
|
-
|
601
|
-
validates :name, kind: String
|
602
|
-
# or
|
603
|
-
validates :name, kind: [String, Symbol]
|
793
|
+
Kind::Dig.({people: [person]}, [:people, 0, :name]) # "Rodrigo"
|
604
794
|
```
|
605
795
|
|
606
|
-
|
796
|
+
This module has the method `[]` that knows how to create a lambda that will know how to perform the `dig` strategy.
|
607
797
|
|
608
798
|
```ruby
|
609
|
-
|
799
|
+
results = [
|
800
|
+
{ person: {} },
|
801
|
+
{ person: { name: 'Foo Bar'} },
|
802
|
+
{ person: { name: 'Rodrigo Serradura'} },
|
803
|
+
].map(&Kind::Dig[:person, :name])
|
610
804
|
|
611
|
-
|
805
|
+
p results # [nil, "Foo Bar", "Rodrigo Serradura"],
|
612
806
|
```
|
613
807
|
|
614
|
-
|
615
|
-
- `kind_of` *(default)*
|
616
|
-
- `instance_of`
|
808
|
+
[⬆️ Back to Top](#table-of-contents-)
|
617
809
|
|
618
|
-
####
|
810
|
+
#### Kind::Presence
|
619
811
|
|
620
|
-
|
812
|
+
The method `.call` of this module returns the given value if it's present otherwise it will return `nil`.
|
621
813
|
|
622
814
|
```ruby
|
623
|
-
|
815
|
+
Kind::Presence.(true) # true
|
816
|
+
Kind::Presence.('foo') # "foo"
|
817
|
+
Kind::Presence.([1, 2]) # [1, 2]
|
818
|
+
Kind::Presence.({a: 3}) # {a: 3}
|
819
|
+
Kind::Presence.(Set.new([4])) # #<Set: {4}>
|
820
|
+
|
821
|
+
Kind::Presence.('') # nil
|
822
|
+
Kind::Presence.(' ') # nil
|
823
|
+
Kind::Presence.("\t\n\r") # nil
|
824
|
+
Kind::Presence.("\u00a0") # nil
|
825
|
+
|
826
|
+
Kind::Presence.([]) # nil
|
827
|
+
Kind::Presence.({}) # nil
|
828
|
+
Kind::Presence.(Set.new) # nil
|
829
|
+
|
830
|
+
Kind::Presence.(nil) # nil
|
831
|
+
Kind::Presence.(false) # nil
|
832
|
+
|
833
|
+
# nil will be returned if the given object responds to the method blank? and this method result is true.
|
834
|
+
MyObject = Struct.new(:is_blank) do
|
835
|
+
def blank?
|
836
|
+
is_blank
|
837
|
+
end
|
838
|
+
end
|
839
|
+
|
840
|
+
my_object = MyObject.new
|
841
|
+
|
842
|
+
my_object.is_blank = true
|
843
|
+
|
844
|
+
Kind::Presence.(my_object) # nil
|
845
|
+
|
846
|
+
my_object.is_blank = false
|
847
|
+
|
848
|
+
Kind::Presence.(my_object) # #<struct MyObject is_blank=false>
|
624
849
|
```
|
625
850
|
|
626
|
-
|
627
|
-
option and with the `validates!` method. e.g.
|
851
|
+
This module also has the method `to_proc`, because of this you can make use of the `Kind::Presence` in methods that receive a block as an argument. e.g:
|
628
852
|
|
629
853
|
```ruby
|
630
|
-
|
631
|
-
# or
|
632
|
-
validates! :last_name, kind: String
|
854
|
+
['', [], {}, '1', [2]].map(&Kind::Presence) # [nil, nil, nil, "1", [2]]
|
633
855
|
```
|
634
856
|
|
635
|
-
[⬆️ Back to Top](#table-of-contents-)
|
857
|
+
[⬆️ Back to Top](#table-of-contents-)
|
636
858
|
|
637
859
|
## Kind::Undefined
|
638
860
|
|
639
|
-
The [`Kind::Undefined`](https://github.com/serradura/kind/blob/
|
640
|
-
|
641
|
-
If you are interested, check out [the tests](https://github.com/serradura/kind/blob/834f6b8ebdc737de8e5628986585f30c1a5aa41b/test/kind/undefined_test.rb) to understand its methods.
|
642
|
-
|
643
|
-
### Kind.of.\<Type\>.or_undefined()
|
644
|
-
|
645
|
-
If you interested in use `Kind::Undefined` you can use the method `.or_undefined` with any of the [available type checkers](#type-checkers). e.g:
|
861
|
+
The [`Kind::Undefined`](https://github.com/serradura/kind/blob/1674bab/lib/kind/undefined.rb) constant can be used to distinguish the usage of `nil`.
|
646
862
|
|
647
|
-
|
648
|
-
Kind.of.String.or_undefined(nil) # Kind::Undefined
|
649
|
-
Kind.of.String.or_undefined("something") # "something"
|
650
|
-
```
|
863
|
+
If you are interested, check out [the tests](https://github.com/serradura/kind/blob/main/test/kind/undefined_test.rb) to understand its methods.
|
651
864
|
|
652
|
-
[⬆️ Back to Top](#table-of-contents-)
|
865
|
+
[⬆️ Back to Top](#table-of-contents-)
|
653
866
|
|
654
867
|
## Kind::Maybe
|
655
868
|
|
@@ -698,13 +911,15 @@ puts optional.value_or(1) # 1
|
|
698
911
|
puts optional.value_or { 1 } # 1
|
699
912
|
```
|
700
913
|
|
914
|
+
[⬆️ Back to Top](#table-of-contents-)
|
915
|
+
|
701
916
|
#### Replacing blocks by lambdas
|
702
917
|
|
703
918
|
```ruby
|
704
919
|
Add = -> params do
|
705
|
-
a, b = Kind.
|
920
|
+
a, b = Kind::Hash.value_or_empty(params).values_at(:a, :b)
|
706
921
|
|
707
|
-
a + b if Kind
|
922
|
+
a + b if Kind::Numeric?(a, b)
|
708
923
|
end
|
709
924
|
|
710
925
|
# --
|
@@ -718,6 +933,8 @@ end
|
|
718
933
|
Kind::Maybe.new(nil).map(&Add).value_or(0) # 0
|
719
934
|
```
|
720
935
|
|
936
|
+
[⬆️ Back to Top](#table-of-contents-)
|
937
|
+
|
721
938
|
### Kind::Maybe[], Kind::Maybe.wrap() and Kind::Maybe#then method aliases
|
722
939
|
|
723
940
|
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.
|
@@ -745,13 +962,15 @@ result =
|
|
745
962
|
puts result # 42
|
746
963
|
```
|
747
964
|
|
965
|
+
[⬆️ Back to Top](#table-of-contents-)
|
966
|
+
|
748
967
|
#### Replacing blocks by lambdas
|
749
968
|
|
750
969
|
```ruby
|
751
970
|
Add = -> params do
|
752
|
-
a, b = Kind.
|
971
|
+
a, b = Kind::Hash.value_or_empty(params).values_at(:a, :b)
|
753
972
|
|
754
|
-
a + b if Kind
|
973
|
+
a + b if Kind::Numeric?(a, b)
|
755
974
|
end
|
756
975
|
|
757
976
|
# --
|
@@ -765,16 +984,24 @@ Kind::Maybe['2'].then(&Add).value_or(0) # 0
|
|
765
984
|
Kind::Maybe[nil].then(&Add).value_or(0) # 0
|
766
985
|
```
|
767
986
|
|
987
|
+
[⬆️ Back to Top](#table-of-contents-)
|
988
|
+
|
768
989
|
### Kind::None() and Kind::Some()
|
769
990
|
|
770
991
|
If you need to ensure the return of `Kind::Maybe` results from your methods/lambdas,
|
771
992
|
you could use the methods `Kind::None` and `Kind::Some` to do this. e.g:
|
772
993
|
|
773
994
|
```ruby
|
995
|
+
Double = ->(arg) do
|
996
|
+
number = Kind::Numeric.or_nil(arg)
|
997
|
+
|
998
|
+
Kind::Maybe[number].then { |number| number * 2 }
|
999
|
+
end
|
1000
|
+
|
774
1001
|
Add = -> params do
|
775
|
-
a, b = Kind.
|
1002
|
+
a, b = Kind::Hash.value_or_empty(params).values_at(:a, :b)
|
776
1003
|
|
777
|
-
return Kind::None unless Kind
|
1004
|
+
return Kind::None unless Kind::Numeric?(a, b)
|
778
1005
|
|
779
1006
|
Kind::Some(a + b)
|
780
1007
|
end
|
@@ -793,40 +1020,14 @@ Add.call(a:1, b: 2) # #<Kind::Maybe::Some:0x0000... @value=3>
|
|
793
1020
|
Kind::Maybe[a: 1, b: 2].then(&Add).value_or(0) # 3
|
794
1021
|
|
795
1022
|
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
|
-
```
|
799
1023
|
|
800
|
-
|
801
|
-
|
802
|
-
You can use the `Kind.of.Maybe()` to know if the given value is a kind of `Kind::Maybe`object. e.g:
|
803
|
-
|
804
|
-
```ruby
|
805
|
-
def double(maybe_number)
|
806
|
-
Kind.of.Maybe(maybe_number)
|
807
|
-
.map { |value| value * 2 }
|
808
|
-
.value_or(0)
|
809
|
-
end
|
810
|
-
|
811
|
-
number = Kind::Maybe[4]
|
812
|
-
|
813
|
-
puts double(number) # 8
|
814
|
-
|
815
|
-
# -------------------------------------------------------#
|
816
|
-
# All the type checker methods are available to use too. #
|
817
|
-
# -------------------------------------------------------#
|
818
|
-
|
819
|
-
Kind.of.Maybe.instance?(number) # true
|
820
|
-
|
821
|
-
Kind.of.Maybe.or_nil(number) # <Kind::Maybe::Some @value=4 ...>
|
822
|
-
|
823
|
-
Kind.of.Maybe.instance(number) # <Kind::Maybe::Some @value=4 ...>
|
824
|
-
Kind.of.Maybe.instance(4) # Kind::Error (4 expected to be a kind of Kind::Maybe::Result)
|
1024
|
+
# --
|
825
1025
|
|
826
|
-
|
827
|
-
Kind.of.Maybe[4] # Kind::Error (4 expected to be a kind of Kind::Maybe::Result)
|
1026
|
+
Add.(a: 2, b: 2).then(&Double).value # 8
|
828
1027
|
```
|
829
1028
|
|
1029
|
+
[⬆️ Back to Top](#table-of-contents-)
|
1030
|
+
|
830
1031
|
### Kind::Optional
|
831
1032
|
|
832
1033
|
The `Kind::Optional` constant is an alias for `Kind::Maybe`. e.g:
|
@@ -852,91 +1053,117 @@ result2 =
|
|
852
1053
|
puts result2 # 35
|
853
1054
|
```
|
854
1055
|
|
1056
|
+
[⬆️ Back to Top](#table-of-contents-)
|
1057
|
+
|
855
1058
|
#### Replacing blocks by lambdas
|
856
1059
|
|
857
1060
|
```ruby
|
858
|
-
|
859
|
-
|
1061
|
+
Double = ->(arg) do
|
1062
|
+
number = Kind::Numeric.or_nil(arg)
|
860
1063
|
|
861
|
-
|
1064
|
+
Kind::Maybe[number].then { |number| number * 2 }
|
862
1065
|
end
|
863
1066
|
|
864
1067
|
# --
|
865
1068
|
|
866
|
-
Kind::Optional[
|
1069
|
+
Kind::Optional[2].then(&Double).value_or(0) # 4
|
867
1070
|
|
868
1071
|
# --
|
869
1072
|
|
870
|
-
Kind::Optional[
|
871
|
-
Kind::Optional[
|
872
|
-
Kind::Optional[nil].then(&Add).value_or(0) # 0
|
1073
|
+
Kind::Optional['2'].then(&Double).value_or(0) # 0
|
1074
|
+
Kind::Optional[nil].then(&Double).value_or(0) # 0
|
873
1075
|
```
|
874
1076
|
|
875
|
-
|
876
|
-
|
877
|
-
[⬆️ Back to Top](#table-of-contents-)
|
1077
|
+
[⬆️ Back to Top](#table-of-contents-)
|
878
1078
|
|
879
|
-
### Kind
|
1079
|
+
### Kind::Maybe(<Type>)
|
880
1080
|
|
881
|
-
|
882
|
-
In these scenarios, you could check the given input type as optional and avoid unexpected behavior. e.g:
|
1081
|
+
You can use `Kind::Maybe(<Type>)` or `Kind::Optional(<Type>)` to create a maybe monad which will return None if the given input hasn't the expected type. e.g:
|
883
1082
|
|
884
1083
|
```ruby
|
885
|
-
|
886
|
-
Kind::
|
887
|
-
|
888
|
-
|
889
|
-
|
890
|
-
end
|
891
|
-
|
892
|
-
person_name('') # "John Doe"
|
893
|
-
person_name(nil) # "John Doe"
|
1084
|
+
result1 =
|
1085
|
+
Kind::Maybe(Numeric)
|
1086
|
+
.wrap(5)
|
1087
|
+
.then { |value| value * 5 }
|
1088
|
+
.value_or { 0 }
|
894
1089
|
|
895
|
-
|
896
|
-
person_name(last_name: 'Serradura') # "John Doe"
|
1090
|
+
puts result1 # 25
|
897
1091
|
|
898
|
-
|
1092
|
+
# ---
|
899
1093
|
|
900
|
-
|
901
|
-
|
902
|
-
|
903
|
-
|
904
|
-
|
905
|
-
|
906
|
-
|
907
|
-
'John Doe'
|
908
|
-
end
|
909
|
-
end
|
1094
|
+
result2 =
|
1095
|
+
Kind::Optional(Numeric)
|
1096
|
+
.wrap('5')
|
1097
|
+
.then { |value| value * 5 }
|
1098
|
+
.value_or { 0 }
|
1099
|
+
|
1100
|
+
puts result2 # 0
|
910
1101
|
```
|
911
1102
|
|
912
|
-
|
1103
|
+
This typed maybe has the same methods of `Kind::Maybe` class. e.g:
|
913
1104
|
|
914
|
-
|
1105
|
+
```ruby
|
1106
|
+
Kind::Maybe(Numeric)[5]
|
1107
|
+
Kind::Maybe(Numeric).new(5)
|
1108
|
+
Kind::Maybe(Numeric).wrap(5)
|
1109
|
+
|
1110
|
+
# ---
|
1111
|
+
|
1112
|
+
Kind::Optional(Numeric)[5]
|
1113
|
+
Kind::Optional(Numeric).new(5)
|
1114
|
+
Kind::Optional(Numeric).wrap(5)
|
1115
|
+
```
|
1116
|
+
|
1117
|
+
#### Real world examples
|
1118
|
+
|
1119
|
+
It is very common the need to avoid some operation when a method receives the wrong input.
|
1120
|
+
In these scenarios, you could create a maybe monad that will return None if the given input hasn't the expected type. e.g:
|
915
1121
|
|
916
1122
|
```ruby
|
917
|
-
|
1123
|
+
def person_name(params)
|
1124
|
+
Kind::Maybe(Hash)
|
1125
|
+
.wrap(params)
|
1126
|
+
.then { |hash| hash.values_at(:first_name, :last_name) }
|
1127
|
+
.then { |names| names.map(&Kind::Presence).tap(&:compact!) }
|
1128
|
+
.check { |names| names.size == 2 }
|
1129
|
+
.then { |(first_name, last_name)| "#{first_name} #{last_name}" }
|
1130
|
+
.value_or { 'John Doe' }
|
1131
|
+
end
|
918
1132
|
|
919
|
-
|
920
|
-
|
921
|
-
.reduce(0) do |total, item|
|
922
|
-
item.try { |data| data[:number] + total } || total
|
923
|
-
end
|
1133
|
+
person_name('') # "John Doe"
|
1134
|
+
person_name(nil) # "John Doe"
|
924
1135
|
|
925
|
-
|
926
|
-
|
927
|
-
.reduce(0) { |total, item| total + item.value[:number] }
|
1136
|
+
person_name(first_name: 'Rodrigo') # "John Doe"
|
1137
|
+
person_name(last_name: 'Serradura') # "John Doe"
|
928
1138
|
|
929
|
-
|
1139
|
+
person_name(first_name: 'Rodrigo', last_name: 'Serradura') # "Rodrigo Serradura"
|
1140
|
+
|
1141
|
+
#
|
1142
|
+
# See below the previous implementation without using the maybe monad.
|
1143
|
+
#
|
1144
|
+
def person_name(params)
|
1145
|
+
default = 'John Doe'
|
1146
|
+
|
1147
|
+
return default unless params.kind_of?(Hash)
|
1148
|
+
|
1149
|
+
names = params.values_at(:first_name, :last_name).map(&Kind::Presence).tap(&:compact!)
|
1150
|
+
|
1151
|
+
return default if names.size != 2
|
1152
|
+
|
1153
|
+
first_name, last_name = names
|
1154
|
+
|
1155
|
+
"#{first_name} #{last_name}"
|
1156
|
+
end
|
930
1157
|
```
|
931
1158
|
|
932
|
-
To finish follows an example of how to use
|
1159
|
+
To finish follows an example of how to use the Maybe monad to handle arguments in coupled methods.
|
933
1160
|
|
934
1161
|
```ruby
|
935
|
-
module
|
1162
|
+
module PersonIntroduction1
|
936
1163
|
extend self
|
937
1164
|
|
938
1165
|
def call(params)
|
939
|
-
optional = Kind::
|
1166
|
+
optional = Kind::Maybe(Hash).wrap(params)
|
940
1167
|
|
941
1168
|
"Hi my name is #{full_name(optional)}, I'm #{age(optional)} years old."
|
942
1169
|
end
|
@@ -944,19 +1171,20 @@ module PersonIntroduction
|
|
944
1171
|
private
|
945
1172
|
|
946
1173
|
def full_name(optional)
|
947
|
-
optional.map { |
|
1174
|
+
optional.map { |hash| "#{hash[:first_name]} #{hash[:last_name]}".strip }
|
1175
|
+
.presence
|
948
1176
|
.value_or { 'John Doe' }
|
949
1177
|
end
|
950
1178
|
|
951
1179
|
def age(optional)
|
952
|
-
optional.
|
1180
|
+
optional.dig(:age).value_or(0)
|
953
1181
|
end
|
954
1182
|
end
|
955
1183
|
|
956
1184
|
#
|
957
1185
|
# See below the previous implementation without using an optional.
|
958
1186
|
#
|
959
|
-
module
|
1187
|
+
module PersonIntroduction2
|
960
1188
|
extend self
|
961
1189
|
|
962
1190
|
def call(params)
|
@@ -966,9 +1194,12 @@ module PersonIntroduction
|
|
966
1194
|
private
|
967
1195
|
|
968
1196
|
def full_name(params)
|
1197
|
+
default = 'John Doe'
|
1198
|
+
|
969
1199
|
case params
|
970
|
-
when Hash then
|
971
|
-
|
1200
|
+
when Hash then
|
1201
|
+
Kind::Presence.("#{params[:first_name]} #{params[:last_name]}".strip) || default
|
1202
|
+
else default
|
972
1203
|
end
|
973
1204
|
end
|
974
1205
|
|
@@ -981,62 +1212,64 @@ module PersonIntroduction
|
|
981
1212
|
end
|
982
1213
|
```
|
983
1214
|
|
984
|
-
[⬆️ Back to Top](#table-of-contents-)
|
1215
|
+
[⬆️ Back to Top](#table-of-contents-)
|
985
1216
|
|
986
|
-
###
|
1217
|
+
### Error handling
|
1218
|
+
|
1219
|
+
#### Kind::Maybe.wrap {}
|
987
1220
|
|
988
|
-
|
1221
|
+
The `Kind::Maybe#wrap` can receive a block, and if an exception (at `StandardError level`) happening, this will generate a None result.
|
989
1222
|
|
990
1223
|
```ruby
|
991
|
-
|
992
|
-
Kind::Maybe
|
993
|
-
.wrap(5)
|
994
|
-
.then { |value| value * 5 }
|
995
|
-
.value_or { 0 }
|
1224
|
+
Kind::Maybe(Numeric)
|
1225
|
+
.wrap { 2 / 0 } # #<Kind::Maybe::None:0x0000... @value=#<ZeroDivisionError: divided by 0>>
|
996
1226
|
|
997
|
-
|
1227
|
+
Kind::Maybe(Numeric)
|
1228
|
+
.wrap(2) { |number| number / 0 } # #<Kind::Maybe::None:0x0000... @value=#<ZeroDivisionError: divided by 0>>
|
1229
|
+
```
|
998
1230
|
|
999
|
-
|
1231
|
+
#### Kind::Maybe.map! or Kind::Maybe.then!
|
1000
1232
|
|
1001
|
-
|
1002
|
-
Kind::Optional(Numeric)
|
1003
|
-
.wrap('5')
|
1004
|
-
.then { |value| value * 5 }
|
1005
|
-
.value_or { 0 }
|
1233
|
+
By default the `Kind::Maybe#map` and `Kind::Maybe#then` intercept exceptions at the `StandardError` level. So if an exception was intercepted a None will be returned.
|
1006
1234
|
|
1007
|
-
|
1235
|
+
```ruby
|
1236
|
+
# Handling StandardError exceptions
|
1237
|
+
result1 = Kind::Maybe[2].map { |number| number / 0 }
|
1238
|
+
result1.none? # true
|
1239
|
+
result1.value # #<ZeroDivisionError: divided by 0>
|
1240
|
+
|
1241
|
+
result2 = Kind::Maybe[3].then { |number| number / 0 }
|
1242
|
+
result2.none? # true
|
1243
|
+
result2.value # #<ZeroDivisionError: divided by 0>
|
1008
1244
|
```
|
1009
1245
|
|
1010
|
-
|
1246
|
+
But there are versions of these methods (`Kind::Maybe#map!` and `Kind::Maybe#then!`) that allow the exception leak, so, the user must handle the exception by himself or use this method when he wants to see the error be raised.
|
1011
1247
|
|
1012
1248
|
```ruby
|
1013
|
-
|
1014
|
-
Kind::Maybe
|
1015
|
-
Kind::Maybe(Numeric).wrap(5)
|
1016
|
-
|
1017
|
-
# ---
|
1249
|
+
# Leaking StandardError exceptions
|
1250
|
+
Kind::Maybe[2].map! { |number| number / 0 } # ZeroDivisionError (divided by 0)
|
1018
1251
|
|
1019
|
-
Kind::
|
1020
|
-
Kind::Optional(Numeric).new(5)
|
1021
|
-
Kind::Optional(Numeric).wrap(5)
|
1252
|
+
Kind::Maybe[2].then! { |number| number / 0 } # ZeroDivisionError (divided by 0)
|
1022
1253
|
```
|
1023
1254
|
|
1024
|
-
|
1255
|
+
> **Note:** If an exception (at StandardError level) is returned by the methods `#then`, `#map` it will be resolved as None.
|
1256
|
+
|
1257
|
+
[⬆️ Back to Top](#table-of-contents-)
|
1025
1258
|
|
1026
1259
|
### Kind::Maybe#try
|
1027
1260
|
|
1028
|
-
If you don't want to use
|
1261
|
+
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.
|
1029
1262
|
|
1030
1263
|
```ruby
|
1031
1264
|
object = 'foo'
|
1032
1265
|
|
1033
|
-
Kind::Maybe[object].try(:upcase) # "FOO"
|
1266
|
+
Kind::Maybe[object].try(:upcase).value # "FOO"
|
1034
1267
|
|
1035
|
-
Kind::Maybe[{}].try(:fetch, :number, 0) # 0
|
1268
|
+
Kind::Maybe[{}].try(:fetch, :number, 0).value # 0
|
1036
1269
|
|
1037
|
-
Kind::Maybe[{number: 1}].try(:fetch, :number) # 1
|
1270
|
+
Kind::Maybe[{number: 1}].try(:fetch, :number).value # 1
|
1038
1271
|
|
1039
|
-
Kind::Maybe[object].try { |value| value.upcase } # "FOO"
|
1272
|
+
Kind::Maybe[object].try { |value| value.upcase }.value # "FOO"
|
1040
1273
|
|
1041
1274
|
#############
|
1042
1275
|
# Nil value #
|
@@ -1044,9 +1277,9 @@ Kind::Maybe[object].try { |value| value.upcase } # "FOO"
|
|
1044
1277
|
|
1045
1278
|
object = nil
|
1046
1279
|
|
1047
|
-
Kind::Maybe[object].try(:upcase) # nil
|
1280
|
+
Kind::Maybe[object].try(:upcase).value # nil
|
1048
1281
|
|
1049
|
-
Kind::Maybe[object].try { |value| value.upcase } # nil
|
1282
|
+
Kind::Maybe[object].try { |value| value.upcase }.value # nil
|
1050
1283
|
|
1051
1284
|
#########################
|
1052
1285
|
# Kind::Undefined value #
|
@@ -1054,14 +1287,117 @@ Kind::Maybe[object].try { |value| value.upcase } # nil
|
|
1054
1287
|
|
1055
1288
|
object = Kind::Undefined
|
1056
1289
|
|
1057
|
-
Kind::Maybe[object].try(:upcase) # nil
|
1290
|
+
Kind::Maybe[object].try(:upcase).value # nil
|
1291
|
+
|
1292
|
+
Kind::Maybe[object].try { |value| value.upcase }.value # nil
|
1293
|
+
```
|
1294
|
+
|
1295
|
+
> **Note:** You can use the `#try` method with `Kind::Optional` objects.
|
1296
|
+
|
1297
|
+
[⬆️ Back to Top](#table-of-contents-)
|
1298
|
+
|
1299
|
+
### Kind::Maybe#try!
|
1058
1300
|
|
1059
|
-
|
1301
|
+
Has the same behavior of its `#try`, but it will raise an error if the value doesn't respond to the expected method.
|
1302
|
+
|
1303
|
+
```ruby
|
1304
|
+
Kind::Maybe[{}].try(:upcase) # => #<Kind::Maybe::None:0x0000... @value=nil>
|
1305
|
+
|
1306
|
+
Kind::Maybe[{}].try!(:upcase) # => NoMethodError (undefined method `upcase' for {}:Hash)
|
1060
1307
|
```
|
1061
1308
|
|
1062
|
-
> **Note:** You can use the try method with
|
1309
|
+
> **Note:** You can also use the `#try!` method with `Kind::Optional` objects.
|
1310
|
+
|
1311
|
+
[⬆️ Back to Top](#table-of-contents-)
|
1312
|
+
|
1313
|
+
### Kind::Maybe#dig
|
1063
1314
|
|
1064
|
-
|
1315
|
+
Has the same behavior of Ruby dig methods ([Hash](https://ruby-doc.org/core-2.3.0/Hash.html#method-i-dig), [Array](https://ruby-doc.org/core-2.3.0/Array.html#method-i-dig), [Struct](https://ruby-doc.org/core-2.3.0/Struct.html#method-i-dig), [OpenStruct](https://ruby-doc.org/stdlib-2.3.0/libdoc/ostruct/rdoc/OpenStruct.html#method-i-dig)), but it will not raise an error if some value can't be digged.
|
1316
|
+
|
1317
|
+
```ruby
|
1318
|
+
[nil, 1, '', /x/].each do |value|
|
1319
|
+
p Kind::Maybe[value].dig(:foo).value # nil
|
1320
|
+
end
|
1321
|
+
|
1322
|
+
# --
|
1323
|
+
|
1324
|
+
a = [1, 2, 3]
|
1325
|
+
|
1326
|
+
Kind::Maybe[a].dig(0).value # 1
|
1327
|
+
|
1328
|
+
Kind::Maybe[a].dig(3).value # nil
|
1329
|
+
|
1330
|
+
# --
|
1331
|
+
|
1332
|
+
h = { foo: {bar: {baz: 1}}}
|
1333
|
+
|
1334
|
+
Kind::Maybe[h].dig(:foo).value # {bar: {baz: 1}}
|
1335
|
+
Kind::Maybe[h].dig(:foo, :bar).value # {baz: 1}
|
1336
|
+
Kind::Maybe[h].dig(:foo, :bar, :baz).value # 1
|
1337
|
+
|
1338
|
+
Kind::Maybe[h].dig(:foo, :bar, 'baz').value # nil
|
1339
|
+
|
1340
|
+
# --
|
1341
|
+
|
1342
|
+
i = { foo: [{'bar' => [1, 2]}, {baz: [3, 4]}] }
|
1343
|
+
|
1344
|
+
Kind::Maybe[i].dig(:foo, 0, 'bar', 0).value # 1
|
1345
|
+
Kind::Maybe[i].dig(:foo, 0, 'bar', 1).value # 2
|
1346
|
+
Kind::Maybe[i].dig(:foo, 0, 'bar', -1).value # 2
|
1347
|
+
|
1348
|
+
Kind::Maybe[i].dig(:foo, 0, 'bar', 2).value # nil
|
1349
|
+
|
1350
|
+
# --
|
1351
|
+
|
1352
|
+
s = Struct.new(:a, :b).new(101, 102)
|
1353
|
+
o = OpenStruct.new(c: 103, d: 104)
|
1354
|
+
b = { struct: s, ostruct: o, data: [s, o]}
|
1355
|
+
|
1356
|
+
Kind::Maybe[s].dig(:a).value # 101
|
1357
|
+
Kind::Maybe[b].dig(:struct, :b).value # 102
|
1358
|
+
Kind::Maybe[b].dig(:data, 0, :b).value # 102
|
1359
|
+
Kind::Maybe[b].dig(:data, 0, 'b').value # 102
|
1360
|
+
|
1361
|
+
Kind::Maybe[o].dig(:c).value # 103
|
1362
|
+
Kind::Maybe[b].dig(:ostruct, :d).value # 104
|
1363
|
+
Kind::Maybe[b].dig(:data, 1, :d).value # 104
|
1364
|
+
Kind::Maybe[b].dig(:data, 1, 'd').value # 104
|
1365
|
+
|
1366
|
+
Kind::Maybe[s].dig(:f).value # nil
|
1367
|
+
Kind::Maybe[o].dig(:f).value # nil
|
1368
|
+
Kind::Maybe[b].dig(:struct, :f).value # nil
|
1369
|
+
Kind::Maybe[b].dig(:ostruct, :f).value # nil
|
1370
|
+
Kind::Maybe[b].dig(:data, 0, :f).value # nil
|
1371
|
+
Kind::Maybe[b].dig(:data, 1, :f).value # nil
|
1372
|
+
```
|
1373
|
+
|
1374
|
+
> **Note:** You can also use the `#dig` method with `Kind::Optional` objects.
|
1375
|
+
|
1376
|
+
### Kind::Maybe#check
|
1377
|
+
|
1378
|
+
This method will return the current Some after verify if the block output is truthy. e.g:
|
1379
|
+
|
1380
|
+
```ruby
|
1381
|
+
Kind::Maybe(Array)
|
1382
|
+
.wrap(['Rodrigo', 'Serradura'])
|
1383
|
+
.then { |names| names.map(&Kind::Presence).tap(&:compact!) }
|
1384
|
+
.check { |names| names.size == 2 }
|
1385
|
+
.value # ["Rodrigo", "Serradura"]
|
1386
|
+
```
|
1387
|
+
|
1388
|
+
[⬆️ Back to Top](#table-of-contents-)
|
1389
|
+
|
1390
|
+
### Kind::Maybe#presence
|
1391
|
+
|
1392
|
+
This method will return None if the wrapped value wasn't present.
|
1393
|
+
|
1394
|
+
```ruby
|
1395
|
+
result = Kind::Maybe(Hash).wrap(foo: '').dig(:foo).presence
|
1396
|
+
result.none? # true
|
1397
|
+
result.value # nil
|
1398
|
+
```
|
1399
|
+
|
1400
|
+
[⬆️ Back to Top](#table-of-contents-)
|
1065
1401
|
|
1066
1402
|
## Kind::Empty
|
1067
1403
|
|
@@ -1108,7 +1444,187 @@ Follows the list of constants, if the alias is available to be created:
|
|
1108
1444
|
- `Empty::ARRAY`
|
1109
1445
|
- `Empty::STRING`
|
1110
1446
|
|
1111
|
-
[⬆️ Back to Top](#table-of-contents-)
|
1447
|
+
[⬆️ Back to Top](#table-of-contents-)
|
1448
|
+
|
1449
|
+
## Kind::Validator (ActiveModel::Validations)
|
1450
|
+
|
1451
|
+
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
|
1452
|
+
|
1453
|
+
```ruby
|
1454
|
+
class Person
|
1455
|
+
include ActiveModel::Validations
|
1456
|
+
|
1457
|
+
attr_accessor :first_name, :last_name
|
1458
|
+
|
1459
|
+
validates :first_name, :last_name, kind: String
|
1460
|
+
end
|
1461
|
+
```
|
1462
|
+
|
1463
|
+
And to make use of it, you will need to do an explicitly require. e.g:
|
1464
|
+
|
1465
|
+
```ruby
|
1466
|
+
# In some Gemfile
|
1467
|
+
gem 'kind', require: 'kind/active_model/validation'
|
1468
|
+
|
1469
|
+
# In some .rb file
|
1470
|
+
require 'kind/active_model/validation'
|
1471
|
+
```
|
1472
|
+
|
1473
|
+
[⬆️ Back to Top](#table-of-contents-)
|
1474
|
+
|
1475
|
+
### Usage
|
1476
|
+
|
1477
|
+
#### [Object#===](https://ruby-doc.org/core-3.0.0/Object.html#method-i-3D-3D-3D)
|
1478
|
+
|
1479
|
+
```ruby
|
1480
|
+
validates :name, kind: { of: String }
|
1481
|
+
```
|
1482
|
+
|
1483
|
+
Use an array to verify if the attribute is an instance of one of the classes/modules.
|
1484
|
+
|
1485
|
+
```ruby
|
1486
|
+
validates :status, kind: { of: [String, Symbol]}
|
1487
|
+
```
|
1488
|
+
|
1489
|
+
Because of kind verification be made via `===` you can use type checkers as the expected kinds.
|
1490
|
+
|
1491
|
+
```ruby
|
1492
|
+
validates :alive, kind: Kind::Boolean
|
1493
|
+
```
|
1494
|
+
|
1495
|
+
[⬆️ Back to Top](#table-of-contents-)
|
1496
|
+
|
1497
|
+
#### [Kind.is](#verifying-the-kind-of-some-classmodule)
|
1498
|
+
|
1499
|
+
```ruby
|
1500
|
+
#
|
1501
|
+
# Verifying if the attribute value is the class or a subclass.
|
1502
|
+
#
|
1503
|
+
class Human; end
|
1504
|
+
class Person < Human; end
|
1505
|
+
class User < Human; end
|
1506
|
+
|
1507
|
+
validates :human_kind, kind: { is: Human }
|
1508
|
+
|
1509
|
+
#
|
1510
|
+
# Verifying if the attribute value is the module or if it is a class that includes the module
|
1511
|
+
#
|
1512
|
+
module Human; end
|
1513
|
+
class Person; include Human; end
|
1514
|
+
class User; include Human; end
|
1515
|
+
|
1516
|
+
validates :human_kind, kind: { is: Human }
|
1517
|
+
|
1518
|
+
#
|
1519
|
+
# Verifying if the attribute value is the module or if it is a module that extends the module
|
1520
|
+
#
|
1521
|
+
module Human; end
|
1522
|
+
module Person; extend Human; end
|
1523
|
+
module User; extend Human; end
|
1524
|
+
|
1525
|
+
validates :human_kind, kind: { is: Human }
|
1526
|
+
|
1527
|
+
# or use an array to verify if the attribute
|
1528
|
+
# is a kind of one those classes/modules.
|
1529
|
+
|
1530
|
+
validates :human_kind, kind: { is: [Person, User] }
|
1531
|
+
```
|
1532
|
+
|
1533
|
+
[⬆️ Back to Top](#table-of-contents-)
|
1534
|
+
|
1535
|
+
#### [Object#instance_of?](https://ruby-doc.org/core-3.0.0/Object.html#method-i-instance_of-3F)
|
1536
|
+
|
1537
|
+
```ruby
|
1538
|
+
validates :name, kind: { instance_of: String }
|
1539
|
+
|
1540
|
+
# or use an array to verify if the attribute
|
1541
|
+
# is an instance of one of the classes/modules.
|
1542
|
+
|
1543
|
+
validates :name, kind: { instance_of: [String, Symbol] }
|
1544
|
+
```
|
1545
|
+
|
1546
|
+
[⬆️ Back to Top](#table-of-contents-)
|
1547
|
+
|
1548
|
+
#### [Object#respond_to?](https://ruby-doc.org/core-3.0.0/Object.html#method-i-respond_to-3F)
|
1549
|
+
|
1550
|
+
```ruby
|
1551
|
+
validates :handler, kind: { respond_to: :call }
|
1552
|
+
```
|
1553
|
+
|
1554
|
+
This validation can verify one or multiple methods.
|
1555
|
+
|
1556
|
+
```ruby
|
1557
|
+
validates :params, kind: { respond_to: [:[], :values_at] }
|
1558
|
+
```
|
1559
|
+
|
1560
|
+
[⬆️ Back to Top](#table-of-contents-)
|
1561
|
+
|
1562
|
+
#### Array.new.all? { |item| item.kind_of?(Class) }
|
1563
|
+
|
1564
|
+
```ruby
|
1565
|
+
validates :account_types, kind: { array_of: String }
|
1566
|
+
|
1567
|
+
# or use an array to verify if the attribute
|
1568
|
+
# is an instance of one of the classes
|
1569
|
+
|
1570
|
+
validates :account_types, kind: { array_of: [String, Symbol] }
|
1571
|
+
```
|
1572
|
+
|
1573
|
+
[⬆️ Back to Top](#table-of-contents-)
|
1574
|
+
|
1575
|
+
#### Array.new.all? { |item| expected_values.include?(item) }
|
1576
|
+
|
1577
|
+
```ruby
|
1578
|
+
# Verifies if the attribute value
|
1579
|
+
# is an array with some or all the expected values.
|
1580
|
+
|
1581
|
+
validates :account_types, kind: { array_with: ['foo', 'bar'] }
|
1582
|
+
```
|
1583
|
+
|
1584
|
+
[⬆️ Back to Top](#table-of-contents-)
|
1585
|
+
|
1586
|
+
### Defining the default validation strategy
|
1587
|
+
|
1588
|
+
By default, you can define the attribute type directly (without a hash). e.g.
|
1589
|
+
|
1590
|
+
```ruby
|
1591
|
+
validates :name, kind: String
|
1592
|
+
# or
|
1593
|
+
validates :name, kind: [String, Symbol]
|
1594
|
+
```
|
1595
|
+
|
1596
|
+
To changes this behavior you can set another strategy to validates the attributes types:
|
1597
|
+
|
1598
|
+
```ruby
|
1599
|
+
Kind::Validator.default_strategy = :instance_of
|
1600
|
+
|
1601
|
+
# Tip: Create an initializer if you are in a Rails application.
|
1602
|
+
```
|
1603
|
+
|
1604
|
+
And these are the available options to define the default strategy:
|
1605
|
+
- `kind_of` *(default)*
|
1606
|
+
- `instance_of`
|
1607
|
+
|
1608
|
+
[⬆️ Back to Top](#table-of-contents-)
|
1609
|
+
|
1610
|
+
### Using the `allow_nil` and `strict` options
|
1611
|
+
|
1612
|
+
You can use the `allow_nil` option with any of the kind validations. e.g.
|
1613
|
+
|
1614
|
+
```ruby
|
1615
|
+
validates :name, kind: String, allow_nil: true
|
1616
|
+
```
|
1617
|
+
|
1618
|
+
And as any active model validation, kind validations works with the `strict: true`
|
1619
|
+
option and with the `validates!` method. e.g.
|
1620
|
+
|
1621
|
+
```ruby
|
1622
|
+
validates :first_name, kind: String, strict: true
|
1623
|
+
# or
|
1624
|
+
validates! :last_name, kind: String
|
1625
|
+
```
|
1626
|
+
|
1627
|
+
[⬆️ Back to Top](#table-of-contents-)
|
1112
1628
|
|
1113
1629
|
## Similar Projects
|
1114
1630
|
|