kind 2.2.0 → 4.0.0

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