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