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