kind 3.1.0 → 5.2.0

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