kind 3.1.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.tool-versions +1 -0
- data/.travis.sh +37 -12
- data/.travis.yml +6 -3
- data/CHANGELOG.md +1230 -0
- data/Gemfile +10 -2
- data/README.md +817 -487
- data/lib/kind.rb +53 -283
- 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/{checker.rb → deprecations/checker.rb} +3 -2
- data/lib/kind/{checker → deprecations/checker}/factory.rb +1 -5
- data/lib/kind/{checker → deprecations/checker}/protocol.rb +3 -3
- data/lib/kind/deprecations/is.rb +35 -0
- data/lib/kind/deprecations/of.rb +258 -0
- data/lib/kind/{types.rb → deprecations/types.rb} +14 -8
- data/lib/kind/dig.rb +13 -9
- data/lib/kind/empty.rb +5 -11
- data/lib/kind/error.rb +2 -6
- data/lib/kind/maybe.rb +12 -161
- 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 +51 -9
- data/lib/kind/is.rb +0 -19
- data/lib/kind/of.rb +0 -11
data/Gemfile
CHANGED
@@ -12,9 +12,17 @@ activemodel = case activemodel_version
|
|
12
12
|
when '5.0' then '5.0.7'
|
13
13
|
when '5.1' then '5.1.7'
|
14
14
|
when '5.2' then '5.2.4'
|
15
|
-
when '6.0' then '6.0.
|
15
|
+
when '6.0' then '6.0.3.4'
|
16
|
+
when '6.1' then '6.1.2'
|
16
17
|
end
|
17
18
|
|
19
|
+
simplecov_version =
|
20
|
+
case RUBY_VERSION
|
21
|
+
when /\A2.[23]/ then '0.17.1'
|
22
|
+
when /\A2.4/ then '~> 0.18.5'
|
23
|
+
else '~> 0.19'
|
24
|
+
end
|
25
|
+
|
18
26
|
group :test do
|
19
27
|
if activemodel_version
|
20
28
|
gem 'activesupport', activemodel, require: false
|
@@ -24,7 +32,7 @@ group :test do
|
|
24
32
|
gem 'minitest', '~> 5.0'
|
25
33
|
end
|
26
34
|
|
27
|
-
gem 'simplecov', require: false
|
35
|
+
gem 'simplecov', simplecov_version, require: false
|
28
36
|
end
|
29
37
|
|
30
38
|
gem 'rake', '~> 13.0'
|
data/README.md
CHANGED
@@ -1,64 +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
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.of.\<Type\>.as_optional](#kindoftypeas_optional)
|
49
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)
|
50
91
|
- [Kind::Maybe#try](#kindmaybetry)
|
51
92
|
- [Kind::Maybe#try!](#kindmaybetry-1)
|
52
93
|
- [Kind::Maybe#dig](#kindmaybedig)
|
94
|
+
- [Kind::Maybe#check](#kindmaybecheck)
|
53
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)
|
54
100
|
- [Similar Projects](#similar-projects)
|
55
101
|
- [Development](#development)
|
56
102
|
- [Contributing](#contributing)
|
57
103
|
- [License](#license)
|
58
104
|
- [Code of Conduct](#code-of-conduct)
|
59
105
|
|
60
|
-
##
|
61
|
-
|
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).
|
62
117
|
|
63
118
|
## Installation
|
64
119
|
|
@@ -76,7 +131,7 @@ Or install it yourself as:
|
|
76
131
|
|
77
132
|
$ gem install kind
|
78
133
|
|
79
|
-
[⬆️ Back to Top](#table-of-contents-)
|
134
|
+
[⬆️ Back to Top](#table-of-contents-)
|
80
135
|
|
81
136
|
## Usage
|
82
137
|
|
@@ -84,7 +139,7 @@ With this gem you can add some kind of type checking at runtime. e.g:
|
|
84
139
|
|
85
140
|
```ruby
|
86
141
|
def sum(a, b)
|
87
|
-
Kind
|
142
|
+
Kind::Numeric[a] + Kind::Numeric[b]
|
88
143
|
end
|
89
144
|
|
90
145
|
sum(1, 1) # 2
|
@@ -92,200 +147,194 @@ sum(1, 1) # 2
|
|
92
147
|
sum('1', 1) # Kind::Error ("\"1\" expected to be a kind of Numeric")
|
93
148
|
```
|
94
149
|
|
95
|
-
|
150
|
+
[⬆️ Back to Top](#table-of-contents-)
|
96
151
|
|
97
|
-
|
152
|
+
### Kind.\<Type\>[]
|
153
|
+
|
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.
|
98
155
|
|
99
156
|
```ruby
|
100
|
-
Kind
|
101
|
-
Kind
|
102
|
-
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
|
+
```
|
103
161
|
|
104
|
-
#
|
162
|
+
[⬆️ Back to Top](#table-of-contents-)
|
105
163
|
|
106
|
-
Kind
|
107
|
-
Kind.of.Boolean(true) # true
|
108
|
-
Kind.of.Boolean(false) # false
|
109
|
-
```
|
164
|
+
### Kind::\<Type\>.===()
|
110
165
|
|
111
|
-
|
112
|
-
> And it will perform a strict validation as expected.
|
166
|
+
Use this method to verify if the given object has the expected type.
|
113
167
|
|
114
168
|
```ruby
|
115
|
-
|
116
|
-
|
117
|
-
collection.map(&Kind.of.Hash) # Kind::Error ("number 2" expected to be a kind of Hash)
|
169
|
+
Kind::Enumerable === {} # true
|
170
|
+
Kind::Enumerable === '' # false
|
118
171
|
```
|
119
172
|
|
120
|
-
|
173
|
+
[⬆️ Back to Top](#table-of-contents-)
|
121
174
|
|
122
|
-
|
123
|
-
value = nil
|
175
|
+
### Kind::\<Type\>.value?()
|
124
176
|
|
125
|
-
|
177
|
+
This method works like `.===`, but the difference is what happens when you invoke it without arguments.
|
126
178
|
|
127
|
-
|
179
|
+
```ruby
|
180
|
+
# Example of calling `.value?` with an argument:
|
128
181
|
|
129
|
-
Kind.
|
182
|
+
Kind::Enumerable.value?({}) # true
|
183
|
+
Kind::Enumerable.value?('') # false
|
130
184
|
```
|
131
185
|
|
132
|
-
|
133
|
-
|
134
|
-
#### 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.
|
135
187
|
|
136
188
|
```ruby
|
137
|
-
|
138
|
-
Kind.of.Hash[''] # raise Kind::Error, "'' expected to be a kind of Hash"
|
139
|
-
Kind.of.Hash[a: 1] # {a: 1}
|
140
|
-
Kind.of.Hash['', or: {}] # {}
|
141
|
-
|
142
|
-
# or
|
189
|
+
collection = [ {number: 1}, 'number 2', {number: 3}, :number_4, [:number, 5] ]
|
143
190
|
|
144
|
-
Kind.
|
145
|
-
Kind.of.Hash.instance('') # raise Kind::Error, "'' expected to be a kind of Hash"
|
146
|
-
Kind.of.Hash.instance(a: 1) # {a: 1}
|
147
|
-
Kind.of.Hash.instance('', or: {}) # {}
|
191
|
+
collection.select(&Kind::Enumerable.value?) # [{:number=>1}, {:number=>3}, [:number, 5]]
|
148
192
|
```
|
149
193
|
|
150
|
-
|
194
|
+
[⬆️ Back to Top](#table-of-contents-)
|
195
|
+
|
196
|
+
### Kind::\<Type\>.or_nil()
|
151
197
|
|
152
198
|
But if you don't need a strict type verification, use the `.or_nil` method.
|
153
199
|
|
154
200
|
```ruby
|
155
|
-
Kind
|
156
|
-
Kind
|
157
|
-
|
158
|
-
# ---
|
159
|
-
|
160
|
-
Kind.of.Boolean.or_nil('') # nil
|
161
|
-
Kind.of.Boolean.or_nil(true) # true
|
201
|
+
Kind::Hash.or_nil('') # nil
|
202
|
+
Kind::Hash.or_nil({a: 1}) # {a: 1}
|
162
203
|
```
|
163
204
|
|
164
|
-
|
205
|
+
[⬆️ Back to Top](#table-of-contents-)
|
165
206
|
|
166
|
-
|
207
|
+
### Kind::\<Type\>.or_undefined()
|
208
|
+
|
209
|
+
This method works like `.or_nil`, but it will return a [`Kind::Undefined`](#kindundefined) instead of `nil`.
|
167
210
|
|
168
211
|
```ruby
|
169
|
-
Kind
|
170
|
-
Kind
|
212
|
+
Kind::Hash.or_undefined('') # Kind::Undefined
|
213
|
+
Kind::Hash.or_undefined({a: 1}) # {a: 1}
|
214
|
+
```
|
171
215
|
|
172
|
-
|
173
|
-
Kind.of.Hash.instance?({}, '') # false
|
216
|
+
[⬆️ Back to Top](#table-of-contents-)
|
174
217
|
|
175
|
-
|
218
|
+
### Kind::\<Type\>.or()
|
176
219
|
|
177
|
-
|
178
|
-
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.
|
179
221
|
|
180
|
-
|
181
|
-
Kind.
|
222
|
+
```ruby
|
223
|
+
Kind::Hash.or({}, []) # {}
|
224
|
+
Kind::Hash.or(nil, []) # nil
|
225
|
+
Kind::Hash.or(nil, {a: 1}) # {a: 1}
|
182
226
|
```
|
183
227
|
|
184
|
-
|
185
|
-
> 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.
|
186
229
|
|
187
230
|
```ruby
|
188
|
-
collection = [ {number: 1}, 'number 2', {number: 3}, :number_4 ]
|
231
|
+
collection = [ {number: 1}, 'number 2', {number: 3}, :number_4, [:number, 5] ]
|
189
232
|
|
190
|
-
collection
|
191
|
-
|
233
|
+
collection.map(&Kind::Hash.or({})) # [{:number=>1}, {}, {:number=>3}, {}, {}]
|
234
|
+
collection.map(&Kind::Hash.or(nil)) # [{:number=>1}, nil, {:number=>3}, nil, nil]
|
192
235
|
```
|
193
236
|
|
194
|
-
|
195
|
-
> 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`.
|
196
238
|
|
197
239
|
```ruby
|
198
|
-
|
199
|
-
Kind.of.Hash?({}, HashWithIndifferentAccess.new) # true
|
240
|
+
collection = [ {number: 1}, 'number 2', {number: 3}, :number_4, [:number, 5] ]
|
200
241
|
|
201
|
-
Kind.
|
202
|
-
|
203
|
-
|
204
|
-
# ---
|
242
|
+
collection.map(&Kind::Hash.or(:foo)) # Kind::Error (:foo expected to be a kind of Hash)
|
243
|
+
```
|
205
244
|
|
206
|
-
|
207
|
-
Kind.of.Boolean?(false, true) # true
|
245
|
+
[⬆️ Back to Top](#table-of-contents-)
|
208
246
|
|
209
|
-
Kind
|
210
|
-
Kind.of.Boolean?(false, true, nil) # false
|
247
|
+
### Kind::\<Type\>.value()
|
211
248
|
|
212
|
-
|
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: '') # ""
|
213
252
|
|
214
|
-
|
253
|
+
Kind::String.value('1', default: '') # "1"
|
215
254
|
|
216
|
-
|
255
|
+
Kind::String.value('1', default: 1) # Kind::Error (1 expected to be a kind of String)
|
217
256
|
```
|
218
257
|
|
219
|
-
|
258
|
+
[⬆️ Back to Top](#table-of-contents-)
|
220
259
|
|
221
|
-
|
260
|
+
### Kind::\<Type\>?
|
222
261
|
|
223
|
-
|
224
|
-
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:
|
225
263
|
|
226
|
-
|
264
|
+
```ruby
|
265
|
+
# Verifying one value
|
266
|
+
Kind::Enumerable?({}) # true
|
227
267
|
|
228
|
-
|
268
|
+
# Verifying multiple values
|
269
|
+
Kind::Enumerable?({}, [], Set.new) # true
|
229
270
|
```
|
230
271
|
|
231
|
-
|
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.
|
232
273
|
|
233
274
|
```ruby
|
234
|
-
|
275
|
+
collection = [ {number: 1}, 'number 2', {number: 3}, :number_4, [:number, 5] ]
|
235
276
|
|
236
|
-
|
277
|
+
collection.select(&Kind::Enumerable?) # [{:number=>1}, {:number=>3}, [:number, 5]]
|
237
278
|
```
|
238
279
|
|
239
|
-
|
280
|
+
[⬆️ Back to Top](#table-of-contents-)
|
240
281
|
|
282
|
+
### Kind::{Array,Hash,String,Set}.value_or_empty()
|
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.
|
241
285
|
```ruby
|
242
|
-
#
|
243
|
-
#
|
244
|
-
|
245
|
-
class Human; end
|
246
|
-
class Person < Human; end
|
247
|
-
class User < Human; end
|
286
|
+
Kind::Array.value_or_empty({}) # []
|
287
|
+
Kind::Array.value_or_empty({}).frozen? # true
|
288
|
+
```
|
248
289
|
|
249
|
-
|
250
|
-
Kind.is(Human, Human) # true
|
251
|
-
Kind.is(Human, Person) # true
|
290
|
+
[⬆️ Back to Top](#table-of-contents-)
|
252
291
|
|
253
|
-
|
292
|
+
### List of all type checkers (Kind::<Type>)
|
254
293
|
|
255
|
-
|
256
|
-
# Verifying if the attribute value is the module or if it is a class that includes the module
|
257
|
-
#
|
258
|
-
module Human; end
|
259
|
-
class Person; include Human; end
|
260
|
-
class User; include Human; end
|
294
|
+
#### Core
|
261
295
|
|
262
|
-
Kind
|
263
|
-
Kind
|
264
|
-
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`
|
265
317
|
|
266
|
-
|
318
|
+
#### Stdlib
|
267
319
|
|
268
|
-
|
269
|
-
|
270
|
-
#
|
271
|
-
module Human; end
|
272
|
-
module Person; extend Human; end
|
273
|
-
module User; extend Human; end
|
320
|
+
* `Kind::OpenStruct`
|
321
|
+
* `Kind::Set`
|
274
322
|
|
275
|
-
|
276
|
-
Kind.is(Human, Human) # true
|
277
|
-
Kind.is(Human, Person) # true
|
323
|
+
#### Custom
|
278
324
|
|
279
|
-
Kind
|
280
|
-
|
325
|
+
* `Kind::Boolean`
|
326
|
+
* `Kind::Callable`
|
327
|
+
* `Kind::Lambda`
|
281
328
|
|
282
|
-
[⬆️ Back to Top](#table-of-contents-)
|
329
|
+
[⬆️ Back to Top](#table-of-contents-)
|
283
330
|
|
284
|
-
###
|
331
|
+
### Creating type checkers
|
285
332
|
|
286
|
-
There are two ways to do this, you can create type checkers dynamically or
|
333
|
+
There are two ways to do this, you can create type checkers dynamically or defining a module.
|
287
334
|
|
288
|
-
####
|
335
|
+
#### Dynamic creation
|
336
|
+
|
337
|
+
##### Using a class or a module
|
289
338
|
|
290
339
|
```ruby
|
291
340
|
class User
|
@@ -293,366 +342,476 @@ end
|
|
293
342
|
|
294
343
|
user = User.new
|
295
344
|
|
296
|
-
|
297
|
-
# Verifiyng the value kind #
|
298
|
-
# ------------------------ #
|
299
|
-
|
300
|
-
Kind.of(User, user) # <User ...>
|
301
|
-
Kind.of(User, {}) # Kind::Error ({} expected to be a kind of User)
|
345
|
+
kind_of_user = Kind::Of(User)
|
302
346
|
|
303
|
-
|
304
|
-
|
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
|
305
352
|
|
306
|
-
#
|
307
|
-
#
|
308
|
-
|
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
|
309
357
|
|
310
|
-
|
311
|
-
|
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
|
312
362
|
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
# Note: Kind.of?(Type) without arguments will return a
|
317
|
-
# lambda that will perform an instance verification
|
318
|
-
#
|
319
|
-
[1, '2', 3.0, '4']
|
320
|
-
.select(&Kind.of?(Numeric)) # [1, 3.0]
|
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.... >]
|
321
365
|
|
322
|
-
#
|
323
|
-
#
|
324
|
-
#
|
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.... >
|
325
370
|
|
326
|
-
kind_of_user
|
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.... >
|
327
375
|
|
328
|
-
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.... >
|
329
380
|
|
330
|
-
|
331
|
-
kind_of_user.
|
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.... >]
|
332
383
|
|
333
|
-
|
334
|
-
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)
|
335
386
|
|
336
|
-
#
|
337
|
-
#
|
338
|
-
#
|
339
|
-
|
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.... >
|
340
391
|
|
341
|
-
|
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...>
|
342
395
|
|
343
|
-
|
396
|
+
kind_of_user.value('1', default: User.new) # #<User:0x0000...>
|
344
397
|
|
345
|
-
#
|
346
|
-
|
398
|
+
kind_of_user.value('1', default: 1) # Kind::Error (1 expected to be a kind of User)
|
399
|
+
```
|
347
400
|
|
348
|
-
|
401
|
+
[⬆️ Back to Top](#table-of-contents-)
|
349
402
|
|
350
|
-
|
351
|
-
# Kind.is() can be used to check a class/module #
|
352
|
-
# --------------------------------------------- #
|
403
|
+
##### Using an object which responds to ===
|
353
404
|
|
354
|
-
|
355
|
-
end
|
405
|
+
Example using a lambda (an object which responds to .===) and a hash with the kind name.
|
356
406
|
|
357
|
-
|
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)
|
358
466
|
```
|
359
467
|
|
360
|
-
|
468
|
+
[⬆️ Back to Top](#table-of-contents-)
|
469
|
+
|
470
|
+
#### Kind::<Type> module
|
361
471
|
|
362
|
-
|
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:
|
363
473
|
|
364
474
|
```ruby
|
365
475
|
class User
|
366
476
|
end
|
367
477
|
|
368
|
-
|
478
|
+
module Kind
|
479
|
+
module User
|
480
|
+
extend self, TypeChecker
|
369
481
|
|
370
|
-
|
371
|
-
|
372
|
-
|
482
|
+
# Define the expected kind of this type checker.
|
483
|
+
def kind; ::User; end
|
484
|
+
end
|
373
485
|
|
374
|
-
|
375
|
-
|
486
|
+
# This how the Kind::<Type>? methods are defined.
|
487
|
+
def self.User?(*values)
|
488
|
+
KIND.of?(::User, values)
|
489
|
+
end
|
376
490
|
end
|
377
491
|
|
378
|
-
#
|
379
|
-
# Usage examples: #
|
380
|
-
# --------------- #
|
492
|
+
# Doing this you will have the same methods of a standard type checker (like: `Kind::Symbol`).
|
381
493
|
|
382
|
-
|
383
|
-
|
384
|
-
Kind.of.User({}) # Kind::Error ({} expected to be a kind of User)
|
385
|
-
|
386
|
-
Kind.of.User.or_nil({}) # nil
|
494
|
+
user = User.new
|
387
495
|
|
388
|
-
Kind
|
389
|
-
Kind
|
496
|
+
Kind::User[user] # #<User:0x0000...>
|
497
|
+
Kind::User[{}] # Kind::Error ({} expected to be a kind of User)
|
390
498
|
|
391
|
-
Kind
|
392
|
-
Kind
|
499
|
+
Kind::User?(user) # true
|
500
|
+
Kind::User?({}) # false
|
393
501
|
```
|
394
502
|
|
395
|
-
|
503
|
+
The advantages of this approach are:
|
396
504
|
|
397
|
-
|
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.
|
398
507
|
|
399
|
-
The
|
508
|
+
The disadvantage is:
|
400
509
|
|
401
|
-
|
402
|
-
module Account
|
403
|
-
class User
|
404
|
-
Kind::Types.add(self)
|
405
|
-
end
|
406
|
-
end
|
407
|
-
|
408
|
-
module Account
|
409
|
-
class User
|
410
|
-
class Membership
|
411
|
-
Kind::Types.add(self)
|
412
|
-
end
|
413
|
-
end
|
414
|
-
end
|
415
|
-
|
416
|
-
Kind.of.Account::User({}) # Kind::Error ({} expected to be a kind of Account::User)
|
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.
|
417
511
|
|
418
|
-
|
512
|
+
[⬆️ Back to Top](#table-of-contents-)
|
419
513
|
|
420
|
-
|
514
|
+
### Utility methods
|
421
515
|
|
422
|
-
Kind.
|
423
|
-
Kind.of.Account::User.instance?(Account::User.new) # true
|
516
|
+
#### Kind.of_class?()
|
424
517
|
|
425
|
-
|
426
|
-
Kind.of.Account::User.class?(Account::User) # true
|
518
|
+
This method verify if a given value is a `Class`.
|
427
519
|
|
428
|
-
|
429
|
-
|
430
|
-
Kind.
|
520
|
+
```ruby
|
521
|
+
Kind.of_class?(Hash) # true
|
522
|
+
Kind.of_class?(Enumerable) # false
|
523
|
+
Kind.of_class?(1) # false
|
524
|
+
```
|
431
525
|
|
432
|
-
|
526
|
+
[⬆️ Back to Top](#table-of-contents-)
|
433
527
|
|
434
|
-
Kind.
|
528
|
+
#### Kind.of_module?()
|
435
529
|
|
436
|
-
|
437
|
-
Kind.of.Account::User::Membership.instance?(Account::User::Membership.new) # true
|
530
|
+
This method verify if a given value is a `Module`.
|
438
531
|
|
439
|
-
|
440
|
-
Kind.
|
532
|
+
```ruby
|
533
|
+
Kind.of_module?(Hash) # false
|
534
|
+
Kind.of_module?(Enumerable) # true
|
535
|
+
Kind.of_module?(1) # false
|
441
536
|
```
|
442
537
|
|
443
|
-
[⬆️ Back to Top](#table-of-contents-)
|
538
|
+
[⬆️ Back to Top](#table-of-contents-)
|
539
|
+
|
540
|
+
#### Kind.of_module_or_class()
|
444
541
|
|
445
|
-
|
542
|
+
This method return the given value if it is a module or a class. If not, a `Kind::Error` will be raised.
|
446
543
|
|
447
|
-
|
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
|
+
```
|
448
548
|
|
449
|
-
|
549
|
+
[⬆️ Back to Top](#table-of-contents-)
|
450
550
|
|
451
|
-
|
452
|
-
- `Kind.of.Symbol`
|
453
|
-
- `Kind.of.Numeric`
|
454
|
-
- `Kind.of.Integer`
|
455
|
-
- `Kind.of.Float`
|
456
|
-
- `Kind.of.Regexp`
|
457
|
-
- `Kind.of.Time`
|
458
|
-
- `Kind.of.Array`
|
459
|
-
- `Kind.of.Range`
|
460
|
-
- `Kind.of.Hash`
|
461
|
-
- `Kind.of.Struct`
|
462
|
-
- `Kind.of.OpenStruct`
|
463
|
-
- `Kind.of.Enumerator`
|
464
|
-
- `Kind.of.Set`
|
465
|
-
- `Kind.of.Method`
|
466
|
-
- `Kind.of.Proc`
|
467
|
-
- `Kind.of.IO`
|
468
|
-
- `Kind.of.File`
|
551
|
+
#### Kind.respond_to()
|
469
552
|
|
470
|
-
|
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) # ""
|
471
557
|
|
472
|
-
|
473
|
-
|
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
|
+
```
|
474
561
|
|
475
|
-
|
562
|
+
[⬆️ Back to Top](#table-of-contents-)
|
476
563
|
|
477
|
-
|
478
|
-
- `Kind.of.Module()`
|
479
|
-
- `Kind.of.Lambda()`
|
480
|
-
- `Kind.of.Boolean()`
|
481
|
-
- `Kind.of.Callable()`: verifies if the given value `respond_to?(:call)`.
|
482
|
-
- `Kind.of.Maybe()` or its alias `Kind.of.Optional()`
|
564
|
+
#### Kind.of()
|
483
565
|
|
484
|
-
|
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
|
+
```
|
485
571
|
|
486
|
-
[⬆️ Back to Top](#table-of-contents-)
|
572
|
+
[⬆️ Back to Top](#table-of-contents-)
|
487
573
|
|
488
|
-
|
574
|
+
#### Kind.of?()
|
489
575
|
|
490
|
-
This
|
576
|
+
This method can be used to check if one or multiple values have the expected kind.
|
491
577
|
|
492
578
|
```ruby
|
493
|
-
|
494
|
-
|
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
|
+
```
|
495
588
|
|
496
|
-
|
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.
|
497
590
|
|
498
|
-
|
499
|
-
|
591
|
+
```ruby
|
592
|
+
[1, '2', 3].select(&Kind.of?(Numeric)) # [1, 3]
|
500
593
|
```
|
501
594
|
|
502
|
-
|
595
|
+
[⬆️ Back to Top](#table-of-contents-)
|
596
|
+
|
597
|
+
#### Kind.value()
|
503
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.
|
504
600
|
```ruby
|
505
|
-
|
506
|
-
gem 'kind', require: 'kind/active_model/validation'
|
601
|
+
Kind.value(String, '1', default: '') # "1"
|
507
602
|
|
508
|
-
|
509
|
-
|
603
|
+
Kind.value(String, 1, default: '') # ""
|
604
|
+
|
605
|
+
Kind.value(String, 1, default: 2) # Kind::Error (2 expected to be a kind of String)
|
510
606
|
```
|
511
607
|
|
512
|
-
|
608
|
+
[⬆️ Back to Top](#table-of-contents-)
|
513
609
|
|
514
|
-
|
610
|
+
#### Kind.is()
|
611
|
+
|
612
|
+
You can use `Kind.is` to verify if some class has the expected type as its ancestor.
|
515
613
|
|
516
614
|
```ruby
|
517
|
-
|
615
|
+
Kind.is(Hash, String) # false
|
518
616
|
|
519
|
-
#
|
520
|
-
# is an instance of one of the classes/modules.
|
617
|
+
Kind.is(Hash, Hash) # true
|
521
618
|
|
522
|
-
|
619
|
+
Kind.is(Enumerable, Hash) # true
|
523
620
|
```
|
524
621
|
|
525
|
-
|
622
|
+
The `Kind.is` also could check the inheritance of Classes/Modules.
|
526
623
|
|
527
624
|
```ruby
|
528
625
|
#
|
529
|
-
# Verifying if
|
626
|
+
# Verifying if a class is or inherits from the expected class.
|
530
627
|
#
|
531
628
|
class Human; end
|
532
629
|
class Person < Human; end
|
533
630
|
class User < Human; end
|
534
631
|
|
535
|
-
|
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
|
536
637
|
|
537
638
|
#
|
538
|
-
# Verifying if the
|
639
|
+
# Verifying if the classes included a module.
|
539
640
|
#
|
540
641
|
module Human; end
|
541
642
|
class Person; include Human; end
|
542
643
|
class User; include Human; end
|
543
644
|
|
544
|
-
|
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
|
545
650
|
|
546
651
|
#
|
547
|
-
# Verifying if
|
652
|
+
# Verifying if a class is or inherits from the expected module.
|
548
653
|
#
|
549
654
|
module Human; end
|
550
655
|
module Person; extend Human; end
|
551
656
|
module User; extend Human; end
|
552
657
|
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
# 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
|
557
661
|
|
558
|
-
|
662
|
+
Kind.is(Human, Struct) # false
|
559
663
|
```
|
560
664
|
|
561
|
-
|
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.
|
562
672
|
|
563
673
|
```ruby
|
564
|
-
|
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
|
565
678
|
|
566
|
-
#
|
567
|
-
|
679
|
+
Kind::Try.(:symbol, :strip) # nil
|
680
|
+
Kind::Try.(:symbol, :fetch, :b, 2) # nil
|
568
681
|
|
569
|
-
|
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)
|
570
684
|
```
|
571
685
|
|
572
|
-
|
573
|
-
**[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.
|
574
687
|
|
575
688
|
```ruby
|
576
|
-
|
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"]
|
577
697
|
```
|
578
698
|
|
579
|
-
|
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.
|
580
704
|
|
581
705
|
```ruby
|
582
|
-
|
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]}
|
583
709
|
|
584
|
-
#
|
585
|
-
#
|
710
|
+
Kind::Dig.(s, [:a]) # 101
|
711
|
+
Kind::Dig.(o, [:c]) # 103
|
586
712
|
|
587
|
-
|
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
|
588
725
|
```
|
589
726
|
|
590
|
-
|
727
|
+
Another difference between the `Kind::Dig` and the native Ruby dig, is that it knows how to extract values from regular objects.
|
591
728
|
|
592
729
|
```ruby
|
593
|
-
|
594
|
-
|
730
|
+
class Person
|
731
|
+
attr_reader :name
|
595
732
|
|
596
|
-
|
597
|
-
|
733
|
+
def initialize(name)
|
734
|
+
@name = name
|
735
|
+
end
|
736
|
+
end
|
598
737
|
|
599
|
-
|
738
|
+
person = Person.new('Rodrigo')
|
600
739
|
|
601
|
-
|
740
|
+
Kind::Dig.(person, [:name]) # "Rodrigo"
|
602
741
|
|
603
|
-
|
604
|
-
validates :name, kind: String
|
605
|
-
# or
|
606
|
-
validates :name, kind: [String, Symbol]
|
742
|
+
Kind::Dig.({people: [person]}, [:people, 0, :name]) # "Rodrigo"
|
607
743
|
```
|
608
744
|
|
609
|
-
|
745
|
+
This module has the method `[]` that knows how to create a lambda that will know how to perform the `dig` strategy.
|
610
746
|
|
611
747
|
```ruby
|
612
|
-
|
748
|
+
results = [
|
749
|
+
{ person: {} },
|
750
|
+
{ person: { name: 'Foo Bar'} },
|
751
|
+
{ person: { name: 'Rodrigo Serradura'} },
|
752
|
+
].map(&Kind::Dig[:person, :name])
|
613
753
|
|
614
|
-
|
754
|
+
p results # [nil, "Foo Bar", "Rodrigo Serradura"],
|
615
755
|
```
|
616
756
|
|
617
|
-
|
618
|
-
- `kind_of` *(default)*
|
619
|
-
- `instance_of`
|
757
|
+
[⬆️ Back to Top](#table-of-contents-)
|
620
758
|
|
621
|
-
####
|
759
|
+
#### Kind::Presence
|
622
760
|
|
623
|
-
|
761
|
+
The method `.call` of this module returns the given value if it's present otherwise it will return `nil`.
|
624
762
|
|
625
763
|
```ruby
|
626
|
-
|
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>
|
627
798
|
```
|
628
799
|
|
629
|
-
|
630
|
-
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:
|
631
801
|
|
632
802
|
```ruby
|
633
|
-
|
634
|
-
# or
|
635
|
-
validates! :last_name, kind: String
|
803
|
+
['', [], {}, '1', [2]].map(&Kind::Presence) # [nil, nil, nil, "1", [2]]
|
636
804
|
```
|
637
805
|
|
638
|
-
[⬆️ Back to Top](#table-of-contents-)
|
806
|
+
[⬆️ Back to Top](#table-of-contents-)
|
639
807
|
|
640
808
|
## Kind::Undefined
|
641
809
|
|
642
|
-
The [`Kind::Undefined`](https://github.com/serradura/kind/blob/
|
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`.
|
643
811
|
|
644
|
-
If you are interested, check out [the tests](https://github.com/serradura/kind/blob/
|
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.
|
645
813
|
|
646
|
-
|
647
|
-
|
648
|
-
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:
|
649
|
-
|
650
|
-
```ruby
|
651
|
-
Kind.of.String.or_undefined(nil) # Kind::Undefined
|
652
|
-
Kind.of.String.or_undefined("something") # "something"
|
653
|
-
```
|
654
|
-
|
655
|
-
[⬆️ Back to Top](#table-of-contents-)
|
814
|
+
[⬆️ Back to Top](#table-of-contents-)
|
656
815
|
|
657
816
|
## Kind::Maybe
|
658
817
|
|
@@ -701,13 +860,15 @@ puts optional.value_or(1) # 1
|
|
701
860
|
puts optional.value_or { 1 } # 1
|
702
861
|
```
|
703
862
|
|
863
|
+
[⬆️ Back to Top](#table-of-contents-)
|
864
|
+
|
704
865
|
#### Replacing blocks by lambdas
|
705
866
|
|
706
867
|
```ruby
|
707
868
|
Add = -> params do
|
708
|
-
a, b = Kind.
|
869
|
+
a, b = Kind::Hash.value_or_empty(params).values_at(:a, :b)
|
709
870
|
|
710
|
-
a + b if Kind
|
871
|
+
a + b if Kind::Numeric?(a, b)
|
711
872
|
end
|
712
873
|
|
713
874
|
# --
|
@@ -721,6 +882,8 @@ end
|
|
721
882
|
Kind::Maybe.new(nil).map(&Add).value_or(0) # 0
|
722
883
|
```
|
723
884
|
|
885
|
+
[⬆️ Back to Top](#table-of-contents-)
|
886
|
+
|
724
887
|
### Kind::Maybe[], Kind::Maybe.wrap() and Kind::Maybe#then method aliases
|
725
888
|
|
726
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.
|
@@ -748,13 +911,15 @@ result =
|
|
748
911
|
puts result # 42
|
749
912
|
```
|
750
913
|
|
914
|
+
[⬆️ Back to Top](#table-of-contents-)
|
915
|
+
|
751
916
|
#### Replacing blocks by lambdas
|
752
917
|
|
753
918
|
```ruby
|
754
919
|
Add = -> params do
|
755
|
-
a, b = Kind.
|
920
|
+
a, b = Kind::Hash.value_or_empty(params).values_at(:a, :b)
|
756
921
|
|
757
|
-
a + b if Kind
|
922
|
+
a + b if Kind::Numeric?(a, b)
|
758
923
|
end
|
759
924
|
|
760
925
|
# --
|
@@ -768,16 +933,24 @@ Kind::Maybe['2'].then(&Add).value_or(0) # 0
|
|
768
933
|
Kind::Maybe[nil].then(&Add).value_or(0) # 0
|
769
934
|
```
|
770
935
|
|
936
|
+
[⬆️ Back to Top](#table-of-contents-)
|
937
|
+
|
771
938
|
### Kind::None() and Kind::Some()
|
772
939
|
|
773
940
|
If you need to ensure the return of `Kind::Maybe` results from your methods/lambdas,
|
774
941
|
you could use the methods `Kind::None` and `Kind::Some` to do this. e.g:
|
775
942
|
|
776
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
|
+
|
777
950
|
Add = -> params do
|
778
|
-
a, b = Kind.
|
951
|
+
a, b = Kind::Hash.value_or_empty(params).values_at(:a, :b)
|
779
952
|
|
780
|
-
return Kind::None unless Kind
|
953
|
+
return Kind::None unless Kind::Numeric?(a, b)
|
781
954
|
|
782
955
|
Kind::Some(a + b)
|
783
956
|
end
|
@@ -796,40 +969,14 @@ Add.call(a:1, b: 2) # #<Kind::Maybe::Some:0x0000... @value=3>
|
|
796
969
|
Kind::Maybe[a: 1, b: 2].then(&Add).value_or(0) # 3
|
797
970
|
|
798
971
|
Kind::Maybe[1].then(&Add).value_or(0) # 0
|
799
|
-
Kind::Maybe['2'].then(&Add).value_or(0) # 0
|
800
|
-
Kind::Maybe[nil].then(&Add).value_or(0) # 0
|
801
|
-
```
|
802
|
-
|
803
|
-
### Kind.of.Maybe()
|
804
|
-
|
805
|
-
You can use the `Kind.of.Maybe()` to know if the given value is a kind of `Kind::Maybe`object. e.g:
|
806
|
-
|
807
|
-
```ruby
|
808
|
-
def double(maybe_number)
|
809
|
-
Kind.of.Maybe(maybe_number)
|
810
|
-
.map { |value| value * 2 }
|
811
|
-
.value_or(0)
|
812
|
-
end
|
813
|
-
|
814
|
-
number = Kind::Maybe[4]
|
815
|
-
|
816
|
-
puts double(number) # 8
|
817
|
-
|
818
|
-
# -------------------------------------------------------#
|
819
|
-
# All the type checker methods are available to use too. #
|
820
|
-
# -------------------------------------------------------#
|
821
|
-
|
822
|
-
Kind.of.Maybe.instance?(number) # true
|
823
|
-
|
824
|
-
Kind.of.Maybe.or_nil(number) # <Kind::Maybe::Some @value=4 ...>
|
825
972
|
|
826
|
-
|
827
|
-
Kind.of.Maybe.instance(4) # Kind::Error (4 expected to be a kind of Kind::Maybe::Result)
|
973
|
+
# --
|
828
974
|
|
829
|
-
|
830
|
-
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
|
831
976
|
```
|
832
977
|
|
978
|
+
[⬆️ Back to Top](#table-of-contents-)
|
979
|
+
|
833
980
|
### Kind::Optional
|
834
981
|
|
835
982
|
The `Kind::Optional` constant is an alias for `Kind::Maybe`. e.g:
|
@@ -855,41 +1002,80 @@ result2 =
|
|
855
1002
|
puts result2 # 35
|
856
1003
|
```
|
857
1004
|
|
1005
|
+
[⬆️ Back to Top](#table-of-contents-)
|
1006
|
+
|
858
1007
|
#### Replacing blocks by lambdas
|
859
1008
|
|
860
1009
|
```ruby
|
861
|
-
|
862
|
-
|
1010
|
+
Double = ->(arg) do
|
1011
|
+
number = Kind::Numeric.or_nil(arg)
|
863
1012
|
|
864
|
-
|
1013
|
+
Kind::Maybe[number].then { |number| number * 2 }
|
865
1014
|
end
|
866
1015
|
|
867
1016
|
# --
|
868
1017
|
|
869
|
-
Kind::Optional[
|
1018
|
+
Kind::Optional[2].then(&Double).value_or(0) # 4
|
870
1019
|
|
871
1020
|
# --
|
872
1021
|
|
873
|
-
Kind::Optional[
|
874
|
-
Kind::Optional[
|
875
|
-
|
1022
|
+
Kind::Optional['2'].then(&Double).value_or(0) # 0
|
1023
|
+
Kind::Optional[nil].then(&Double).value_or(0) # 0
|
1024
|
+
```
|
1025
|
+
|
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:
|
1031
|
+
|
1032
|
+
```ruby
|
1033
|
+
result1 =
|
1034
|
+
Kind::Maybe(Numeric)
|
1035
|
+
.wrap(5)
|
1036
|
+
.then { |value| value * 5 }
|
1037
|
+
.value_or { 0 }
|
1038
|
+
|
1039
|
+
puts result1 # 25
|
1040
|
+
|
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
|
876
1050
|
```
|
877
1051
|
|
878
|
-
|
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)
|
879
1058
|
|
880
|
-
|
1059
|
+
# ---
|
881
1060
|
|
882
|
-
|
1061
|
+
Kind::Optional(Numeric)[5]
|
1062
|
+
Kind::Optional(Numeric).new(5)
|
1063
|
+
Kind::Optional(Numeric).wrap(5)
|
1064
|
+
```
|
1065
|
+
|
1066
|
+
#### Real world examples
|
883
1067
|
|
884
|
-
It is very common the need to avoid some
|
885
|
-
In these scenarios, you could
|
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:
|
886
1070
|
|
887
1071
|
```ruby
|
888
1072
|
def person_name(params)
|
889
|
-
Kind::
|
890
|
-
.
|
891
|
-
.
|
892
|
-
.
|
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}" }
|
893
1079
|
.value_or { 'John Doe' }
|
894
1080
|
end
|
895
1081
|
|
@@ -902,56 +1088,31 @@ person_name(last_name: 'Serradura') # "John Doe"
|
|
902
1088
|
person_name(first_name: 'Rodrigo', last_name: 'Serradura') # "Rodrigo Serradura"
|
903
1089
|
|
904
1090
|
#
|
905
|
-
# See below the previous implementation without using
|
906
|
-
#
|
907
|
-
def person_name(params)
|
908
|
-
if params.kind_of?(Hash) && params.values_at(:first_name, :last_name).compact.size == 2
|
909
|
-
"#{params[:first_name]} #{params[:last_name]}"
|
910
|
-
else
|
911
|
-
'John Doe'
|
912
|
-
end
|
913
|
-
end
|
914
|
-
|
915
|
-
#
|
916
|
-
# You can also use Kind::Optional(<Type>) to achieve the same behavior
|
1091
|
+
# See below the previous implementation without using the maybe monad.
|
917
1092
|
#
|
918
1093
|
def person_name(params)
|
919
|
-
|
920
|
-
.wrap(params)
|
921
|
-
.map { |data| data if data.values_at(:first_name, :last_name).compact.size == 2 }
|
922
|
-
.map { |data| "#{data[:first_name]} #{data[:last_name]}" }
|
923
|
-
.value_or { 'John Doe' }
|
924
|
-
end
|
925
|
-
```
|
1094
|
+
default = 'John Doe'
|
926
1095
|
|
927
|
-
|
1096
|
+
return default unless params.kind_of?(Hash)
|
928
1097
|
|
929
|
-
|
1098
|
+
names = params.values_at(:first_name, :last_name).map(&Kind::Presence).tap(&:compact!)
|
930
1099
|
|
931
|
-
|
932
|
-
collection = [ {number: 1}, 'number 0', {number: 2}, [0] ]
|
1100
|
+
return default if names.size != 2
|
933
1101
|
|
934
|
-
|
935
|
-
.select(&Kind.of.Hash.as_optional)
|
936
|
-
.reduce(0) do |total, item|
|
937
|
-
item.try { |data| data[:number] + total } || total
|
938
|
-
end
|
939
|
-
|
940
|
-
collection
|
941
|
-
.map(&Kind.of.Hash.as_optional).select(&:some?)
|
942
|
-
.reduce(0) { |total, item| total + item.value[:number] }
|
1102
|
+
first_name, last_name = default
|
943
1103
|
|
944
|
-
#
|
1104
|
+
"#{first_name} #{last_name}"
|
1105
|
+
end
|
945
1106
|
```
|
946
1107
|
|
947
|
-
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.
|
948
1109
|
|
949
1110
|
```ruby
|
950
|
-
module
|
1111
|
+
module PersonIntroduction1
|
951
1112
|
extend self
|
952
1113
|
|
953
1114
|
def call(params)
|
954
|
-
optional = Kind::
|
1115
|
+
optional = Kind::Maybe(Hash).wrap(params)
|
955
1116
|
|
956
1117
|
"Hi my name is #{full_name(optional)}, I'm #{age(optional)} years old."
|
957
1118
|
end
|
@@ -959,19 +1120,20 @@ module PersonIntroduction
|
|
959
1120
|
private
|
960
1121
|
|
961
1122
|
def full_name(optional)
|
962
|
-
optional.map { |
|
1123
|
+
optional.map { |hash| "#{hash[:first_name]} #{hash[:last_name]}".strip }
|
1124
|
+
.presence
|
963
1125
|
.value_or { 'John Doe' }
|
964
1126
|
end
|
965
1127
|
|
966
1128
|
def age(optional)
|
967
|
-
optional.map { |
|
1129
|
+
optional.map { |hash| hash[:age] }.value_or(0)
|
968
1130
|
end
|
969
1131
|
end
|
970
1132
|
|
971
1133
|
#
|
972
1134
|
# See below the previous implementation without using an optional.
|
973
1135
|
#
|
974
|
-
module
|
1136
|
+
module PersonIntroduction2
|
975
1137
|
extend self
|
976
1138
|
|
977
1139
|
def call(params)
|
@@ -981,9 +1143,12 @@ module PersonIntroduction
|
|
981
1143
|
private
|
982
1144
|
|
983
1145
|
def full_name(params)
|
1146
|
+
default = 'John Doe'
|
1147
|
+
|
984
1148
|
case params
|
985
|
-
when Hash then
|
986
|
-
|
1149
|
+
when Hash then
|
1150
|
+
Kind::Presence.("#{params[:first_name]} #{params[:last_name]}".strip) || default
|
1151
|
+
else default
|
987
1152
|
end
|
988
1153
|
end
|
989
1154
|
|
@@ -996,47 +1161,49 @@ module PersonIntroduction
|
|
996
1161
|
end
|
997
1162
|
```
|
998
1163
|
|
999
|
-
[⬆️ Back to Top](#table-of-contents-)
|
1164
|
+
[⬆️ Back to Top](#table-of-contents-)
|
1000
1165
|
|
1001
|
-
###
|
1166
|
+
### Error handling
|
1167
|
+
|
1168
|
+
#### Kind::Maybe.wrap {}
|
1002
1169
|
|
1003
|
-
|
1170
|
+
The `Kind::Maybe#wrap` can receive a block, and if an exception (at `StandardError level`) happening, this will generate a None result.
|
1004
1171
|
|
1005
1172
|
```ruby
|
1006
|
-
|
1007
|
-
Kind::Maybe
|
1008
|
-
.wrap(5)
|
1009
|
-
.then { |value| value * 5 }
|
1010
|
-
.value_or { 0 }
|
1173
|
+
Kind::Maybe(Numeric)
|
1174
|
+
.wrap { 2 / 0 } # #<Kind::Maybe::None:0x0000... @value=#<ZeroDivisionError: divided by 0>>
|
1011
1175
|
|
1012
|
-
|
1176
|
+
Kind::Maybe(Numeric)
|
1177
|
+
.wrap(2) { |number| number / 0 } # #<Kind::Maybe::None:0x0000... @value=#<ZeroDivisionError: divided by 0>>
|
1178
|
+
```
|
1013
1179
|
|
1014
|
-
|
1180
|
+
#### Kind::Maybe.map! or Kind::Maybe.then!
|
1015
1181
|
|
1016
|
-
|
1017
|
-
Kind::Optional(Numeric)
|
1018
|
-
.wrap('5')
|
1019
|
-
.then { |value| value * 5 }
|
1020
|
-
.value_or { 0 }
|
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.
|
1021
1183
|
|
1022
|
-
|
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>
|
1023
1193
|
```
|
1024
1194
|
|
1025
|
-
|
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.
|
1026
1196
|
|
1027
1197
|
```ruby
|
1028
|
-
|
1029
|
-
Kind::Maybe
|
1030
|
-
Kind::Maybe(Numeric).wrap(5)
|
1031
|
-
|
1032
|
-
# ---
|
1198
|
+
# Leaking StandardError exceptions
|
1199
|
+
Kind::Maybe[2].map! { |number| number / 0 } # ZeroDivisionError (divided by 0)
|
1033
1200
|
|
1034
|
-
Kind::
|
1035
|
-
Kind::Optional(Numeric).new(5)
|
1036
|
-
Kind::Optional(Numeric).wrap(5)
|
1201
|
+
Kind::Maybe[2].then! { |number| number / 0 } # ZeroDivisionError (divided by 0)
|
1037
1202
|
```
|
1038
1203
|
|
1039
|
-
|
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-)
|
1040
1207
|
|
1041
1208
|
### Kind::Maybe#try
|
1042
1209
|
|
@@ -1076,7 +1243,7 @@ Kind::Maybe[object].try { |value| value.upcase }.value # nil
|
|
1076
1243
|
|
1077
1244
|
> **Note:** You can use the `#try` method with `Kind::Optional` objects.
|
1078
1245
|
|
1079
|
-
[⬆️ Back to Top](#table-of-contents-)
|
1246
|
+
[⬆️ Back to Top](#table-of-contents-)
|
1080
1247
|
|
1081
1248
|
### Kind::Maybe#try!
|
1082
1249
|
|
@@ -1090,7 +1257,7 @@ Kind::Maybe[{}].try!(:upcase) # => NoMethodError (undefined method `upcase' for
|
|
1090
1257
|
|
1091
1258
|
> **Note:** You can also use the `#try!` method with `Kind::Optional` objects.
|
1092
1259
|
|
1093
|
-
[⬆️ Back to Top](#table-of-contents-)
|
1260
|
+
[⬆️ Back to Top](#table-of-contents-)
|
1094
1261
|
|
1095
1262
|
### Kind::Maybe#dig
|
1096
1263
|
|
@@ -1155,7 +1322,19 @@ Kind::Maybe[b].dig(:data, 1, :f).value # nil
|
|
1155
1322
|
|
1156
1323
|
> **Note:** You can also use the `#dig` method with `Kind::Optional` objects.
|
1157
1324
|
|
1158
|
-
|
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-)
|
1159
1338
|
|
1160
1339
|
## Kind::Empty
|
1161
1340
|
|
@@ -1202,7 +1381,158 @@ Follows the list of constants, if the alias is available to be created:
|
|
1202
1381
|
- `Empty::ARRAY`
|
1203
1382
|
- `Empty::STRING`
|
1204
1383
|
|
1205
|
-
[⬆️ 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-)
|
1206
1536
|
|
1207
1537
|
## Similar Projects
|
1208
1538
|
|