kind 3.0.0 → 5.0.0

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