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.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/.tool-versions +1 -0
  3. data/.travis.sh +37 -12
  4. data/.travis.yml +6 -3
  5. data/CHANGELOG.md +1230 -0
  6. data/Gemfile +10 -2
  7. data/README.md +817 -487
  8. data/lib/kind.rb +53 -283
  9. data/lib/kind/active_model/kind_validator.rb +7 -7
  10. data/lib/kind/core.rb +9 -0
  11. data/lib/kind/core/deprecation.rb +29 -0
  12. data/lib/kind/core/kind.rb +61 -0
  13. data/lib/kind/core/undefined.rb +7 -0
  14. data/lib/kind/deprecations/built_in_type_checkers.rb +23 -0
  15. data/lib/kind/{checker.rb → deprecations/checker.rb} +3 -2
  16. data/lib/kind/{checker → deprecations/checker}/factory.rb +1 -5
  17. data/lib/kind/{checker → deprecations/checker}/protocol.rb +3 -3
  18. data/lib/kind/deprecations/is.rb +35 -0
  19. data/lib/kind/deprecations/of.rb +258 -0
  20. data/lib/kind/{types.rb → deprecations/types.rb} +14 -8
  21. data/lib/kind/dig.rb +13 -9
  22. data/lib/kind/empty.rb +5 -11
  23. data/lib/kind/error.rb +2 -6
  24. data/lib/kind/maybe.rb +12 -161
  25. data/lib/kind/maybe/none.rb +57 -0
  26. data/lib/kind/maybe/result.rb +51 -0
  27. data/lib/kind/maybe/some.rb +90 -0
  28. data/lib/kind/maybe/typed.rb +29 -0
  29. data/lib/kind/maybe/wrappable.rb +35 -0
  30. data/lib/kind/presence.rb +33 -0
  31. data/lib/kind/try.rb +36 -0
  32. data/lib/kind/type_checker.rb +73 -0
  33. data/lib/kind/type_checkers.rb +30 -0
  34. data/lib/kind/type_checkers/core/array.rb +17 -0
  35. data/lib/kind/type_checkers/core/class.rb +13 -0
  36. data/lib/kind/type_checkers/core/comparable.rb +13 -0
  37. data/lib/kind/type_checkers/core/enumerable.rb +13 -0
  38. data/lib/kind/type_checkers/core/enumerator.rb +13 -0
  39. data/lib/kind/type_checkers/core/file.rb +13 -0
  40. data/lib/kind/type_checkers/core/float.rb +13 -0
  41. data/lib/kind/type_checkers/core/hash.rb +17 -0
  42. data/lib/kind/type_checkers/core/integer.rb +13 -0
  43. data/lib/kind/type_checkers/core/io.rb +13 -0
  44. data/lib/kind/type_checkers/core/method.rb +13 -0
  45. data/lib/kind/type_checkers/core/module.rb +17 -0
  46. data/lib/kind/type_checkers/core/numeric.rb +13 -0
  47. data/lib/kind/type_checkers/core/proc.rb +13 -0
  48. data/lib/kind/type_checkers/core/queue.rb +14 -0
  49. data/lib/kind/type_checkers/core/range.rb +13 -0
  50. data/lib/kind/type_checkers/core/regexp.rb +13 -0
  51. data/lib/kind/type_checkers/core/string.rb +17 -0
  52. data/lib/kind/type_checkers/core/struct.rb +13 -0
  53. data/lib/kind/type_checkers/core/symbol.rb +13 -0
  54. data/lib/kind/type_checkers/core/time.rb +13 -0
  55. data/lib/kind/type_checkers/custom/boolean.rb +19 -0
  56. data/lib/kind/type_checkers/custom/callable.rb +19 -0
  57. data/lib/kind/type_checkers/custom/lambda.rb +19 -0
  58. data/lib/kind/type_checkers/stdlib/open_struct.rb +13 -0
  59. data/lib/kind/type_checkers/stdlib/set.rb +17 -0
  60. data/lib/kind/undefined.rb +4 -2
  61. data/lib/kind/validator.rb +1 -1
  62. data/lib/kind/version.rb +1 -1
  63. data/test.sh +4 -4
  64. metadata +51 -9
  65. data/lib/kind/is.rb +0 -19
  66. 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.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
- ![Ruby](https://img.shields.io/badge/ruby-2.2+-ruby.svg?colorA=99004d&colorB=cc0066)
2
- [![Gem](https://img.shields.io/gem/v/kind.svg?style=flat-square)](https://rubygems.org/gems/kind)
3
- [![Build Status](https://travis-ci.com/serradura/kind.svg?branch=master)](https://travis-ci.com/serradura/kind)
4
- [![Maintainability](https://api.codeclimate.com/v1/badges/711329decb2806ccac95/maintainability)](https://codeclimate.com/github/serradura/kind/maintainability)
5
- [![Test Coverage](https://api.codeclimate.com/v1/badges/711329decb2806ccac95/test_coverage)](https://codeclimate.com/github/serradura/kind/test_coverage)
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
- # Kind <!-- omit in toc -->
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
- A simple type system (at runtime) for Ruby - free of dependencies.
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 to do it. e.g: [Kind.of.\<Type\> methods](#verifying-the-kind-of-some-object), [active model validations](#kindvalidator-activemodelvalidations), [maybe monad](#kindmaybe).
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
- - [Required Ruby version](#required-ruby-version)
44
+ - [Compatibility](#compatibility)
19
45
  - [Installation](#installation)
20
46
  - [Usage](#usage)
21
- - [Kind.of.\<Type\>() - Verifying the kind of some object](#kindoftype---verifying-the-kind-of-some-object)
22
- - [Method aliases to perform a strict validation](#method-aliases-to-perform-a-strict-validation)
23
- - [Kind.of.\<Type\>.or_nil()](#kindoftypeor_nil)
24
- - [Kind.of.\<Type\>.instance?()](#kindoftypeinstance)
25
- - [Kind.is.\<Type\>() - Verifying if some class/module is the expected kind.](#kindistype---verifying-if-some-classmodule-is-the-expected-kind)
26
- - [How to create a new type checker?](#how-to-create-a-new-type-checker)
27
- - [Creating/Verifiyng type checkers dynamically](#creatingverifiyng-type-checkers-dynamically)
28
- - [Registering new (custom) type checker](#registering-new-custom-type-checker)
29
- - [What happens if a custom type checker has a namespace?](#what-happens-if-a-custom-type-checker-has-a-namespace)
30
- - [Type checkers](#type-checkers)
31
- - [Classes' type checkers](#classes-type-checkers)
32
- - [Modules' type checkers](#modules-type-checkers)
33
- - [Specials' type checkers](#specials-type-checkers)
34
- - [Kind::Validator (ActiveModel::Validations)](#kindvalidator-activemodelvalidations)
35
- - [Usage](#usage-1)
36
- - [Defining the default validation strategy](#defining-the-default-validation-strategy)
37
- - [Using the `allow_nil` and `strict` options](#using-the-allow_nil-and-strict-options)
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
- ## Required Ruby version
61
- > \>= 2.2.0
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
+ [⬆️ &nbsp;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.of.Numeric(a) + Kind.of.Numeric(b)
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
- ### Kind.of.\<Type\>() - Verifying the kind of some object
150
+ [⬆️ &nbsp;Back to Top](#table-of-contents-)
96
151
 
97
- By default, basic verifications are strict. So, when you perform `Kind.of.Hash(value)`, if the given value was a Hash, the value itself will be returned, but if it isn't the right type, an error will be raised.
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.of.Hash(nil) # **raise Kind::Error, "nil expected to be a kind of Hash"**
101
- Kind.of.Hash('') # raise Kind::Error, "'' expected to be a kind of Hash"
102
- Kind.of.Hash(a: 1) # {a: 1}
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
+ [⬆️ &nbsp;Back to Top](#table-of-contents-)
105
163
 
106
- Kind.of.Boolean(nil) # raise Kind::Error, "nil expected to be a kind of Boolean"
107
- Kind.of.Boolean(true) # true
108
- Kind.of.Boolean(false) # false
109
- ```
164
+ ### Kind::\<Type\>.===()
110
165
 
111
- > **Note:** `Kind.of.<Type>` supports the to_proc protocol.
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
- collection = [ {number: 1}, 'number 2', {number: 3}, :number_4 ]
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
- When the verified value is nil, it is possible to define a default value with the same type to be returned.
173
+ [⬆️ &nbsp;Back to Top](#table-of-contents-)
121
174
 
122
- ```ruby
123
- value = nil
175
+ ### Kind::\<Type\>.value?()
124
176
 
125
- Kind.of.Hash(value, or: {}) # {}
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.of.Boolean(nil, or: true) # true
182
+ Kind::Enumerable.value?({}) # true
183
+ Kind::Enumerable.value?('') # false
130
184
  ```
131
185
 
132
- > **Note:** As an alternative syntax, you can use the `Kind::Of` instead of the `Kind.of` method. e.g: `Kind::Of::Hash('')`
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
- Kind.of.Hash[nil] # raise Kind::Error, "nil expected to be a kind of Hash"
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.of.Hash.instance(nil) # raise Kind::Error, "nil expected to be a kind of Hash"
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
- ### Kind.of.\<Type\>.or_nil()
194
+ [⬆️ &nbsp;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.of.Hash.or_nil('') # nil
156
- Kind.of.Hash.or_nil({a: 1}) # {a: 1}
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
- ### Kind.of.\<Type\>.instance?()
205
+ [⬆️ &nbsp;Back to Top](#table-of-contents-)
165
206
 
166
- Use the method `.instance?` to verify if the given object has the expected type.
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.of.Hash.instance?({}) # true
170
- Kind.of.Hash.instance?({}, HashWithIndifferentAccess.new) # true
212
+ Kind::Hash.or_undefined('') # Kind::Undefined
213
+ Kind::Hash.or_undefined({a: 1}) # {a: 1}
214
+ ```
171
215
 
172
- Kind.of.Hash.instance?('') # false
173
- Kind.of.Hash.instance?({}, '') # false
216
+ [⬆️ &nbsp;Back to Top](#table-of-contents-)
174
217
 
175
- # ---
218
+ ### Kind::\<Type\>.or()
176
219
 
177
- Kind.of.Boolean.instance?(true) # true
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
- Kind.of.Boolean.instance?(nil) # false
181
- Kind.of.Boolean.instance?(false, true, nil) # false
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
- > **Note:** When `.instance?` is called without an argument,
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
- .select(&Kind.of.Hash.instance?) # [{:number=>1}, {:number=>3}]
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
- > **Note:** You can use a different syntax to perform an instance verification.
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
- Kind.of.Hash?({}) # true
199
- Kind.of.Hash?({}, HashWithIndifferentAccess.new) # true
240
+ collection = [ {number: 1}, 'number 2', {number: 3}, :number_4, [:number, 5] ]
200
241
 
201
- Kind.of.Hash?('') # false
202
- Kind.of.Hash?({}, '') # false
203
-
204
- # ---
242
+ collection.map(&Kind::Hash.or(:foo)) # Kind::Error (:foo expected to be a kind of Hash)
243
+ ```
205
244
 
206
- Kind.of.Boolean?(true) # true
207
- Kind.of.Boolean?(false, true) # true
245
+ [⬆️ &nbsp;Back to Top](#table-of-contents-)
208
246
 
209
- Kind.of.Boolean?(nil) # false
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
- collection = [ {number: 1}, 'number 2', {number: 3}, :number_4 ]
253
+ Kind::String.value('1', default: '') # "1"
215
254
 
216
- collection.select(&Kind.of.Hash?) # [{:number=>1}, {:number=>3}]
255
+ Kind::String.value('1', default: 1) # Kind::Error (1 expected to be a kind of String)
217
256
  ```
218
257
 
219
- ### Kind.is.\<Type\>() - Verifying if some class/module is the expected kind.
258
+ [⬆️ &nbsp;Back to Top](#table-of-contents-)
220
259
 
221
- You can use `Kind.is` to verify if some class has the expected type as its ancestor.
260
+ ### Kind::\<Type\>?
222
261
 
223
- ```ruby
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
- Kind.is.Hash(Hash) # true
264
+ ```ruby
265
+ # Verifying one value
266
+ Kind::Enumerable?({}) # true
227
267
 
228
- Kind.is.Hash(ActiveSupport::HashWithIndifferentAccess) # true
268
+ # Verifying multiple values
269
+ Kind::Enumerable?({}, [], Set.new) # true
229
270
  ```
230
271
 
231
- And just for convenience, you can use the method `Kind.of.*.class?` to verify if the given class has the expected type as its ancestor.
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
- Kind.of.Hash.class?(Hash) # true
275
+ collection = [ {number: 1}, 'number 2', {number: 3}, :number_4, [:number, 5] ]
235
276
 
236
- Kind.of.Hash.class?(ActiveSupport::HashWithIndifferentAccess) # true
277
+ collection.select(&Kind::Enumerable?) # [{:number=>1}, {:number=>3}, [:number, 5]]
237
278
  ```
238
279
 
239
- > **Note:** The `Kind.is` could check the inheritance of Classes/Modules.
280
+ [⬆️ &nbsp;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
- # Verifying if the attribute value is the class or a subclass.
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
- Kind.is(Human, User) # true
250
- Kind.is(Human, Human) # true
251
- Kind.is(Human, Person) # true
290
+ [⬆️ &nbsp;Back to Top](#table-of-contents-)
252
291
 
253
- Kind.is(Human, Struct) # false
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.is(Human, User) # true
263
- Kind.is(Human, Human) # true
264
- Kind.is(Human, Person) # true
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
- Kind.is(Human, Struct) # false
318
+ #### Stdlib
267
319
 
268
- #
269
- # Verifying if the attribute value is the module or if it is a module that extends the module
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
- Kind.is(Human, User) # true
276
- Kind.is(Human, Human) # true
277
- Kind.is(Human, Person) # true
323
+ #### Custom
278
324
 
279
- Kind.is(Human, Struct) # false
280
- ```
325
+ * `Kind::Boolean`
326
+ * `Kind::Callable`
327
+ * `Kind::Lambda`
281
328
 
282
- [⬆️ Back to Top](#table-of-contents-)
329
+ [⬆️ &nbsp;Back to Top](#table-of-contents-)
283
330
 
284
- ### How to create a new type checker?
331
+ ### Creating type checkers
285
332
 
286
- There are two ways to do this, you can create type checkers dynamically or register new ones.
333
+ There are two ways to do this, you can create type checkers dynamically or defining a module.
287
334
 
288
- #### Creating/Verifiyng type checkers dynamically
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
- Kind.of(Hash, {}) # {}
304
- Kind.of(Hash, user) # Kind::Error (<User ...> expected to be a kind of Hash)
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
- # Verifiyng if the value is a kind instance #
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
- Kind.of?(Numeric, 1) # true
311
- Kind.of?(Numeric, 1, 2.0) # true
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
- Kind.of?(Numeric, '1') # false
314
- Kind.of?(Numeric, 1, '2.0') # false
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
- # Creating type checkers dynamically #
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 = 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.or_nil({}) # nil
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
- kind_of_user.instance?({}) # false
331
- kind_of_user.instance?(User) # true
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
- kind_of_user.class?(Hash) # false
334
- kind_of_user.class?(User) # true
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
- # Using methods which returns a lambda #
338
- # ------------------------------------ #
339
- collection = [User.new, User.new, 0, {} nil, User.new]
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
- collection.select(&Kind.of(User).instance?).size == 3 # true
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
- collection.map(&Kind.of(User).as_optional).select(&:some?).size == 3 # true
396
+ kind_of_user.value('1', default: User.new) # #<User:0x0000...>
344
397
 
345
- # Creating type checkers dynamically is cheap
346
- # 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
+ ```
347
400
 
348
- kind_of_user.object_id == Kind.of(User).object_id # true
401
+ [⬆️ &nbsp;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
- class AdminUser < User
355
- end
405
+ Example using a lambda (an object which responds to .===) and a hash with the kind name.
356
406
 
357
- Kind.is(User, AdminUser) # true
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
- #### Registering new (custom) type checker
468
+ [⬆️ &nbsp;Back to Top](#table-of-contents-)
469
+
470
+ #### Kind::<Type> module
361
471
 
362
- Use `Kind::Types.add()`. e.g:
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
- # You can define it at the end of the file class/module where class/module was declared.
478
+ module Kind
479
+ module User
480
+ extend self, TypeChecker
369
481
 
370
- Kind::Types.add(User)
371
-
372
- # Or, you can add the type checker within the class definition.
482
+ # Define the expected kind of this type checker.
483
+ def kind; ::User; end
484
+ end
373
485
 
374
- class User
375
- Kind::Types.add(self)
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
- Kind.of.User(User.new) # #<User:0x0000...>
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.of.User.instance?({}) # false
389
- Kind.of.User.instance?(User) # true
496
+ Kind::User[user] # #<User:0x0000...>
497
+ Kind::User[{}] # Kind::Error ({} expected to be a kind of User)
390
498
 
391
- Kind.of.User.class?(Hash) # false
392
- Kind.of.User.class?(User) # true
499
+ Kind::User?(user) # true
500
+ Kind::User?({}) # false
393
501
  ```
394
502
 
395
- [⬆️ Back to Top](#table-of-contents-)
503
+ The advantages of this approach are:
396
504
 
397
- ##### What happens if a custom type checker has a namespace?
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 type checker will preserve the namespace. ;)
508
+ The disadvantage is:
400
509
 
401
- ```ruby
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
- Kind.of.Account::User(Account::User.new) # #<Account::User:0x0000...>
512
+ [⬆️ &nbsp;Back to Top](#table-of-contents-)
419
513
 
420
- Kind.of.Account::User.or_nil({}) # nil
514
+ ### Utility methods
421
515
 
422
- Kind.of.Account::User.instance?({}) # false
423
- Kind.of.Account::User.instance?(Account::User.new) # true
516
+ #### Kind.of_class?()
424
517
 
425
- Kind.of.Account::User.class?(Hash) # false
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.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
+ ```
431
525
 
432
- Kind.of.Account::User::Membership(Account::User::Membership.new) # #<Account::User::Membership:0x0000...>
526
+ [⬆️ &nbsp;Back to Top](#table-of-contents-)
433
527
 
434
- Kind.of.Account::User::Membership.or_nil({}) # nil
528
+ #### Kind.of_module?()
435
529
 
436
- Kind.of.Account::User::Membership.instance?({}) # false
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
- Kind.of.Account::User::Membership.class?(Hash) # false
440
- Kind.of.Account::User::Membership.class?(Account::User::Membership) # true
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
+ [⬆️ &nbsp;Back to Top](#table-of-contents-)
539
+
540
+ #### Kind.of_module_or_class()
444
541
 
445
- ## Type checkers
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
- The list of types (classes and modules) available to use with `Kind.of.*` or `Kind.is.*` are:
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
- ### Classes' type checkers
549
+ [⬆️ &nbsp;Back to Top](#table-of-contents-)
450
550
 
451
- - `Kind.of.String`
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
- ### Modules' type checkers
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
- - `Kind.of.Enumerable`
473
- - `Kind.of.Comparable`
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
- ### Specials' type checkers
562
+ [⬆️ &nbsp;Back to Top](#table-of-contents-)
476
563
 
477
- - `Kind.of.Class()`
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
- **Note:** Remember, you can use the `Kind.is.*` method to check if some given value is a class/module with all type checkers above.
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
+ [⬆️ &nbsp;Back to Top](#table-of-contents-)
487
573
 
488
- ## Kind::Validator (ActiveModel::Validations)
574
+ #### Kind.of?()
489
575
 
490
- 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
576
+ This method can be used to check if one or multiple values have the expected kind.
491
577
 
492
578
  ```ruby
493
- class Person
494
- include ActiveModel::Validations
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
- attr_accessor :first_name, :last_name
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
- validates :first_name, :last_name, kind: String
499
- end
591
+ ```ruby
592
+ [1, '2', 3].select(&Kind.of?(Numeric)) # [1, 3]
500
593
  ```
501
594
 
502
- And to make use of it, you will need to do an explicitly require. e.g:
595
+ [⬆️ &nbsp;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
- # In some Gemfile
506
- gem 'kind', require: 'kind/active_model/validation'
601
+ Kind.value(String, '1', default: '') # "1"
507
602
 
508
- # In some .rb file
509
- require 'kind/active_model/validation'
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
- ### Usage
608
+ [⬆️ &nbsp;Back to Top](#table-of-contents-)
513
609
 
514
- **[Object#kind_of?](https://ruby-doc.org/core-2.6.4/Object.html#method-i-kind_of-3F)**
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
- validates :name, kind: { of: String }
615
+ Kind.is(Hash, String) # false
518
616
 
519
- # Use an array to verify if the attribute
520
- # is an instance of one of the classes/modules.
617
+ Kind.is(Hash, Hash) # true
521
618
 
522
- validates :status, kind: { of: [String, Symbol]}
619
+ Kind.is(Enumerable, Hash) # true
523
620
  ```
524
621
 
525
- **[Kind.is](#verifying-the-kind-of-some-classmodule)**
622
+ The `Kind.is` also could check the inheritance of Classes/Modules.
526
623
 
527
624
  ```ruby
528
625
  #
529
- # Verifying if the attribute value is the class or a subclass.
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
- validates :human_kind, kind: { is: Human }
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 attribute value is the module or if it is a class that includes the module
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
- validates :human_kind, kind: { is: Human }
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 the attribute value is the module or if it is a module that extends the module
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
- validates :human_kind, kind: { is: Human }
554
-
555
- # or use an array to verify if the attribute
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
- validates :human_kind, kind: { is: [Person, User] }
662
+ Kind.is(Human, Struct) # false
559
663
  ```
560
664
 
561
- **[Object#instance_of?](https://ruby-doc.org/core-2.6.4/Object.html#method-i-instance_of-3F)**
665
+ [⬆️ &nbsp;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
- validates :name, kind: { instance_of: String }
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
- # or use an array to verify if the attribute
567
- # is an instance of one of the classes/modules.
679
+ Kind::Try.(:symbol, :strip) # nil
680
+ Kind::Try.(:symbol, :fetch, :b, 2) # nil
568
681
 
569
- validates :name, kind: { instance_of: [String, Symbol] }
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
- validates :handler, kind: { respond_to: :call }
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
- **Array.new.all? { |item| item.kind_of?(Class) }**
699
+ [⬆️ &nbsp;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
- validates :account_types, kind: { array_of: String }
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
- # or use an array to verify if the attribute
585
- # is an instance of one of the classes
710
+ Kind::Dig.(s, [:a]) # 101
711
+ Kind::Dig.(o, [:c]) # 103
586
712
 
587
- validates :account_types, kind: { array_of: [String, Symbol] }
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
- **Array.new.all? { |item| expected_values.include?(item) }**
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
- # Verifies if the attribute value
594
- # is an array with some or all the expected values.
730
+ class Person
731
+ attr_reader :name
595
732
 
596
- validates :account_types, kind: { array_with: ['foo', 'bar'] }
597
- ```
733
+ def initialize(name)
734
+ @name = name
735
+ end
736
+ end
598
737
 
599
- #### Defining the default validation strategy
738
+ person = Person.new('Rodrigo')
600
739
 
601
- By default, you can define the attribute type directly (without a hash). e.g.
740
+ Kind::Dig.(person, [:name]) # "Rodrigo"
602
741
 
603
- ```ruby
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
- To changes this behavior you can set another strategy to validates the attributes types:
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
- Kind::Validator.default_strategy = :instance_of
748
+ results = [
749
+ { person: {} },
750
+ { person: { name: 'Foo Bar'} },
751
+ { person: { name: 'Rodrigo Serradura'} },
752
+ ].map(&Kind::Dig[:person, :name])
613
753
 
614
- # Tip: Create an initializer if you are in a Rails application.
754
+ p results # [nil, "Foo Bar", "Rodrigo Serradura"],
615
755
  ```
616
756
 
617
- And these are the available options to define the default strategy:
618
- - `kind_of` *(default)*
619
- - `instance_of`
757
+ [⬆️ &nbsp;Back to Top](#table-of-contents-)
620
758
 
621
- #### Using the `allow_nil` and `strict` options
759
+ #### Kind::Presence
622
760
 
623
- You can use the `allow_nil` option with any of the kind validations. e.g.
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
- validates :name, kind: String, allow_nil: true
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
- And as any active model validation, kind validations works with the `strict: true`
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
- validates :first_name, kind: String, strict: true
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
+ [⬆️ &nbsp;Back to Top](#table-of-contents-)
639
807
 
640
808
  ## Kind::Undefined
641
809
 
642
- The [`Kind::Undefined`](https://github.com/serradura/kind/blob/834f6b8ebdc737de8e5628986585f30c1a5aa41b/lib/kind/undefined.rb) constant is used as the default argument of type checkers. This is necessary [to know if no arguments were passed to the type check methods](https://github.com/serradura/kind/blob/834f6b8ebdc737de8e5628986585f30c1a5aa41b/lib/kind.rb#L45-L48). But, you can use it in your codebase too, especially if you need to distinguish the usage of `nil` as a method argument.
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/834f6b8ebdc737de8e5628986585f30c1a5aa41b/test/kind/undefined_test.rb) to understand its methods.
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
- ### Kind.of.\<Type\>.or_undefined()
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
+ [⬆️ &nbsp;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
+ [⬆️ &nbsp;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.of.Hash(params, or: Empty::HASH).values_at(:a, :b)
869
+ a, b = Kind::Hash.value_or_empty(params).values_at(:a, :b)
709
870
 
710
- a + b if Kind.of.Numeric?(a, b)
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
+ [⬆️ &nbsp;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
+ [⬆️ &nbsp;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.of.Hash(params, or: Empty::HASH).values_at(:a, :b)
920
+ a, b = Kind::Hash.value_or_empty(params).values_at(:a, :b)
756
921
 
757
- a + b if Kind.of.Numeric.instance?(a, b)
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
+ [⬆️ &nbsp;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.of.Hash(params, or: Empty::HASH).values_at(:a, :b)
951
+ a, b = Kind::Hash.value_or_empty(params).values_at(:a, :b)
779
952
 
780
- return Kind::None unless Kind.of.Numeric?(a, b)
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
- Kind.of.Maybe.instance(number) # <Kind::Maybe::Some @value=4 ...>
827
- Kind.of.Maybe.instance(4) # Kind::Error (4 expected to be a kind of Kind::Maybe::Result)
973
+ # --
828
974
 
829
- Kind.of.Maybe[number] # <Kind::Maybe::Some @value=4 ...>
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
+ [⬆️ &nbsp;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
+ [⬆️ &nbsp;Back to Top](#table-of-contents-)
1006
+
858
1007
  #### Replacing blocks by lambdas
859
1008
 
860
1009
  ```ruby
861
- Add = -> params do
862
- a, b = Kind.of.Hash(params, or: Empty::HASH).values_at(:a, :b)
1010
+ Double = ->(arg) do
1011
+ number = Kind::Numeric.or_nil(arg)
863
1012
 
864
- a + b if Kind.of.Numeric.instance?(a, b)
1013
+ Kind::Maybe[number].then { |number| number * 2 }
865
1014
  end
866
1015
 
867
1016
  # --
868
1017
 
869
- Kind::Optional[a: 1, b: 2].then(&Add).value_or(0) # 3
1018
+ Kind::Optional[2].then(&Double).value_or(0) # 4
870
1019
 
871
1020
  # --
872
1021
 
873
- Kind::Optional[1].then(&Add).value_or(0) # 0
874
- Kind::Optional['2'].then(&Add).value_or(0) # 0
875
- 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
1024
+ ```
1025
+
1026
+ [⬆️ &nbsp;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
- **Note:** The `Kind.of.Optional` is available to check if some value is a `Kind::Optional`.
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
- [⬆️ Back to Top](#table-of-contents-)
1059
+ # ---
881
1060
 
882
- ### Kind.of.\<Type\>.as_optional
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 computing when a method receives a wrong input.
885
- In these scenarios, you could check the given input type as optional and avoid unexpected behavior. e.g:
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::Of::Hash
890
- .as_optional(params)
891
- .map { |data| data if data.values_at(:first_name, :last_name).compact.size == 2 }
892
- .map { |data| "#{data[:first_name]} #{data[:last_name]}" }
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 an optional.
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
- Kind::Optional(Hash)
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
- > 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).
1096
+ return default unless params.kind_of?(Hash)
928
1097
 
929
- Let's see another example using a collection and how the method `.as_optional` works when it receives no argument.
1098
+ names = params.values_at(:first_name, :last_name).map(&Kind::Presence).tap(&:compact!)
930
1099
 
931
- ```ruby
932
- collection = [ {number: 1}, 'number 0', {number: 2}, [0] ]
1100
+ return default if names.size != 2
933
1101
 
934
- collection
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
- # Note: All the examples above return 3 as the sum of all hashes with numbers.
1104
+ "#{first_name} #{last_name}"
1105
+ end
945
1106
  ```
946
1107
 
947
- To finish follows an example of how to use optionals to handle arguments in coupled methods.
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 PersonIntroduction
1111
+ module PersonIntroduction1
951
1112
  extend self
952
1113
 
953
1114
  def call(params)
954
- optional = Kind::Of::Hash.as_optional(params)
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 { |data| "#{data[:first_name]} #{data[:last_name]}" }
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 { |data| data[:age] }.value_or(0)
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 PersonIntroduction
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 "#{params[:first_name]} #{params[:last_name]}"
986
- else 'John Doe'
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
+ [⬆️ &nbsp;Back to Top](#table-of-contents-)
1000
1165
 
1001
- ### Kind::Maybe(<Type>)
1166
+ ### Error handling
1167
+
1168
+ #### Kind::Maybe.wrap {}
1002
1169
 
1003
- There is an alternative to `Kind.of.\<Type\>.as_optional`, you can use `Kind::Optional(<Type>)` to create a maybe monad which will return None if the given input hasn't the expected type. e.g:
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
- result1 =
1007
- Kind::Maybe(Numeric)
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
- puts result1 # 25
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
- result2 =
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
- puts result2 # 0
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
- This typed maybe has the same methods of `Kind::Maybe` class. e.g:
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
- Kind::Maybe(Numeric)[5]
1029
- Kind::Maybe(Numeric).new(5)
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::Optional(Numeric)[5]
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
- [⬆️ Back to Top](#table-of-contents-)
1204
+ > **Note:** If an exception (at StandardError level) is returned by the methods `#then`, `#map` it will be resolved as None.
1205
+
1206
+ [⬆️ &nbsp;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
+ [⬆️ &nbsp;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
+ [⬆️ &nbsp;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
- [⬆️ Back to Top](#table-of-contents-)
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
+ [⬆️ &nbsp;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
+ [⬆️ &nbsp;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
+ [⬆️ &nbsp;Back to Top](#table-of-contents-)
1206
1536
 
1207
1537
  ## Similar Projects
1208
1538