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