kind 1.8.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a0a88ddbce52eca41466b1c600f745b3c212e7833bbb178cabd86514e1011601
4
- data.tar.gz: f96ab5843a3596a19863389dacda8e5865eff611163112f5092a3387ea6898a4
3
+ metadata.gz: a234e4c63dea23906a65da0718515bff19ac7934a3e919535596042cfd605bba
4
+ data.tar.gz: 5bcb0f1f669a0e87de070c5095cf8f769ddf97dde4b2169060fb21769ffbd623
5
5
  SHA512:
6
- metadata.gz: 06af14f778ea084ae699a0c00e67d6808cbdace68a838cff42782fb7150f51e3524eba060784fd0cb540d08c2ecaa4b8135f75813e7d4223b87c9d1326831c05
7
- data.tar.gz: 16e1e11bce9f67237a3f306b526f1b1f6564fe10eafcd22c2c90f2f18fa22077155a4372a9149d24dac02d266895e36c256a147a625f3592e8686bca974ab94b
6
+ metadata.gz: 232d87a65618b51c3ec8756ad9d08bce53a49d50368d4629c8e694050502dddf12cdecd878193e40e09c8fcc13139b1a35383eedaa36a1660f7cf71f7e4d9810
7
+ data.tar.gz: 4cef62a76d8981fa7409e0d598448b35b4bb01794084ad2ab264741cbf0a78e6b9142352369892e124f130750847078f90297e5d1eda955881ebea0f13e96ec7
@@ -0,0 +1,20 @@
1
+ #!/bin/bash
2
+
3
+ bundle exec rake test
4
+
5
+ ruby_v=$(ruby -v)
6
+
7
+ ACTIVEMODEL_VERSION='3.2' bundle update && bundle exec rake test
8
+ ACTIVEMODEL_VERSION='4.0' bundle update && bundle exec rake test
9
+ ACTIVEMODEL_VERSION='4.1' bundle update && bundle exec rake test
10
+ ACTIVEMODEL_VERSION='4.2' bundle update && bundle exec rake test
11
+ ACTIVEMODEL_VERSION='5.0' bundle update && bundle exec rake test
12
+ ACTIVEMODEL_VERSION='5.1' bundle update && bundle exec rake test
13
+
14
+ if [[ ! $ruby_v =~ '2.2.0' ]]; then
15
+ ACTIVEMODEL_VERSION='5.2' bundle update && bundle exec rake test
16
+ fi
17
+
18
+ if [[ $ruby_v =~ '2.5.' ]] || [[ $ruby_v =~ '2.6.' ]] || [[ $ruby_v =~ '2.7.' ]]; then
19
+ ACTIVEMODEL_VERSION='6.0' bundle update && bundle exec rake test
20
+ fi
@@ -1,7 +1,12 @@
1
1
  ---
2
2
  language: ruby
3
3
 
4
- cache: bundler
4
+ sudo: false
5
+
6
+ cache:
7
+ bundler: true
8
+ directories:
9
+ - /home/travis/.rvm/
5
10
 
6
11
  rvm:
7
12
  - 2.2.0
@@ -22,7 +27,7 @@ before_script:
22
27
  - chmod +x ./cc-test-reporter
23
28
  - "./cc-test-reporter before-build"
24
29
 
25
- script: bundle exec rake test
30
+ script: "./.travis.sh"
26
31
 
27
32
  after_success:
28
33
  - "./cc-test-reporter after-build -t simplecov"
data/Gemfile CHANGED
@@ -1,8 +1,33 @@
1
- source "https://rubygems.org"
1
+ source 'https://rubygems.org'
2
2
 
3
- # Specify your gem's dependencies in kind.gemspec
4
- gemspec
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ activemodel_version = ENV['ACTIVEMODEL_VERSION']
6
+
7
+ activemodel = case activemodel_version
8
+ when '3.2' then '3.2.22'
9
+ when '4.0' then '4.0.13'
10
+ when '4.1' then '4.1.16'
11
+ when '4.2' then '4.2.11'
12
+ when '5.0' then '5.0.7'
13
+ when '5.1' then '5.1.7'
14
+ when '5.2' then '5.2.4'
15
+ when '6.0' then '6.0.0'
16
+ end
17
+
18
+ group :test do
19
+ if activemodel_version
20
+ 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'
25
+ end
5
26
 
6
- gem "rake", "~> 12.0"
7
- gem "minitest", "~> 5.0"
8
- gem "simplecov", "~> 0.17.1"
27
+ gem 'simplecov', require: false
28
+ end
29
+
30
+ gem 'rake', '~> 13.0'
31
+
32
+ # Specify your gem's dependencies in type_validator.gemspec
33
+ gemspec
data/README.md CHANGED
@@ -6,36 +6,48 @@
6
6
 
7
7
  # Kind <!-- omit in toc -->
8
8
 
9
- Basic type system for Ruby.
9
+ A simple type system (at runtime) for Ruby - free of dependencies.
10
10
 
11
11
  **Motivation:**
12
12
 
13
13
  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
14
 
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.
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).
16
16
 
17
17
  ## Table of Contents <!-- omit in toc -->
18
18
  - [Required Ruby version](#required-ruby-version)
19
19
  - [Installation](#installation)
20
20
  - [Usage](#usage)
21
- - [Verifying the kind of some object](#verifying-the-kind-of-some-object)
22
- - [Verifying the kind of some class/module](#verifying-the-kind-of-some-classmodule)
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)
23
26
  - [How to create a new type checker?](#how-to-create-a-new-type-checker)
24
27
  - [Creating/Verifiyng type checkers dynamically](#creatingverifiyng-type-checkers-dynamically)
25
- - [Registering new (custom) type checkers](#registering-new-custom-type-checkers)
28
+ - [Registering new (custom) type checker](#registering-new-custom-type-checker)
26
29
  - [What happens if a custom type checker has a namespace?](#what-happens-if-a-custom-type-checker-has-a-namespace)
27
30
  - [Type checkers](#type-checkers)
28
31
  - [Classes' type checkers](#classes-type-checkers)
29
32
  - [Modules' type checkers](#modules-type-checkers)
30
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)
31
38
  - [Kind::Undefined](#kindundefined)
32
39
  - [Kind.of.\<Type\>.or_undefined()](#kindoftypeor_undefined)
33
40
  - [Kind::Maybe](#kindmaybe)
34
- - [Kind::Maybe[] and Kind::Maybe#then](#kindmaybe-and-kindmaybethen)
35
- - [Kind::Maybe#try](#kindmaybetry)
41
+ - [Replacing blocks by lambdas](#replacing-blocks-by-lambdas)
42
+ - [Kind::Maybe[], Kind::Maybe.wrap() and Kind::Maybe#then method aliases](#kindmaybe-kindmaybewrap-and-kindmaybethen-method-aliases)
43
+ - [Replacing blocks by lambdas](#replacing-blocks-by-lambdas-1)
44
+ - [Kind::None() and Kind::Some()](#kindnone-and-kindsome)
36
45
  - [Kind.of.Maybe()](#kindofmaybe)
37
46
  - [Kind::Optional](#kindoptional)
38
- - [Kind.of.<Type>.as_optional](#kindoftypeas_optional)
47
+ - [Replacing blocks by lambdas](#replacing-blocks-by-lambdas-2)
48
+ - [Kind.of.\<Type\>.as_optional](#kindoftypeas_optional)
49
+ - [Kind::Maybe(<Type>)](#kindmaybetype)
50
+ - [Kind::Maybe#try](#kindmaybetry)
39
51
  - [Kind::Empty](#kindempty)
40
52
  - [Similar Projects](#similar-projects)
41
53
  - [Development](#development)
@@ -78,14 +90,14 @@ sum(1, 1) # 2
78
90
  sum('1', 1) # Kind::Error ("\"1\" expected to be a kind of Numeric")
79
91
  ```
80
92
 
81
- ### Verifying the kind of some object
93
+ ### Kind.of.\<Type\>() - Verifying the kind of some object
82
94
 
83
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.
84
96
 
85
97
  ```ruby
86
- Kind.of.Hash(nil) # **raise Kind::Error, "nil expected to be a kind of Hash"**
87
- Kind.of.Hash('') # raise Kind::Error, "'' expected to be a kind of Hash"
88
- Kind.of.Hash({a: 1}) # {a: 1}
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}
89
101
 
90
102
  # ---
91
103
 
@@ -94,6 +106,15 @@ Kind.of.Boolean(true) # true
94
106
  Kind.of.Boolean(false) # false
95
107
  ```
96
108
 
109
+ > **Note:** `Kind.of.<Type>` supports the to_proc protocol.
110
+ > And it will perform a strict validation as expected.
111
+
112
+ ```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)
116
+ ```
117
+
97
118
  When the verified value is nil, it is possible to define a default value with the same type to be returned.
98
119
 
99
120
  ```ruby
@@ -106,7 +127,25 @@ Kind.of.Hash(value, or: {}) # {}
106
127
  Kind.of.Boolean(nil, or: true) # true
107
128
  ```
108
129
 
109
- As an alternative syntax, you can use the `Kind::Of` instead of the `Kind.of` method. e.g: `Kind::Of::Hash('')`
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
133
+
134
+ ```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: {}] # {}
139
+
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: {}) # {}
146
+ ```
147
+
148
+ ### Kind.of.\<Type\>.or_nil()
110
149
 
111
150
  But if you don't need a strict type verification, use the `.or_nil` method.
112
151
 
@@ -120,51 +159,62 @@ Kind.of.Boolean.or_nil('') # nil
120
159
  Kind.of.Boolean.or_nil(true) # true
121
160
  ```
122
161
 
123
- And just for convenience, you can use the method `.instance?` to verify if the given object has the expected type.
162
+ ### Kind.of.\<Type\>.instance?()
163
+
164
+ Use the method `.instance?` to verify if the given object has the expected type.
124
165
 
125
166
  ```ruby
126
- Kind.of.Hash.instance?('')
127
- # false
167
+ Kind.of.Hash.instance?({}) # true
168
+ Kind.of.Hash.instance?({}, HashWithIndifferentAccess.new) # true
169
+
170
+ Kind.of.Hash.instance?('') # false
171
+ Kind.of.Hash.instance?({}, '') # false
128
172
 
129
173
  # ---
130
174
 
131
- Kind.of.Boolean.instance?('') # false
132
- Kind.of.Boolean.instance?(true) # true
133
- Kind.of.Boolean.instance?(false) # true
175
+ Kind.of.Boolean.instance?(true) # true
176
+ Kind.of.Boolean.instance?(true, false) # true
177
+
178
+ Kind.of.Boolean.instance?(nil) # false
179
+ Kind.of.Boolean.instance?(false, true, nil) # false
134
180
  ```
135
181
 
136
- **Note:** When `.instance?` is called without an argument, it will return a lambda which will perform the kind verification.
182
+ > **Note:** When `.instance?` is called without an argument,
183
+ > it will return a lambda which will perform the kind verification.
137
184
 
138
185
  ```ruby
139
- collection = [
140
- {number: 1},
141
- 'number 0',
142
- {number: 2},
143
- [0],
144
- ]
186
+ collection = [ {number: 1}, 'number 2', {number: 3}, :number_4 ]
145
187
 
146
188
  collection
147
- .select(&Kind.of.Hash.instance?)
148
- .reduce(0) { |total, item| total + item.fetch(:number, 0) } # 3
189
+ .select(&Kind.of.Hash.instance?) # [{:number=>1}, {:number=>3}]
149
190
  ```
150
191
 
151
- Also, there are aliases to perform the strict type verification. e.g:
192
+ > **Note:** You can use a different syntax to perform an instance verification.
193
+ > To do this, use Kind.of.\<Type\>?()
152
194
 
153
195
  ```ruby
154
- Kind.of.Hash[nil] # raise Kind::Error, "nil expected to be a kind of Hash"
155
- Kind.of.Hash[''] # raise Kind::Error, "'' expected to be a kind of Hash"
156
- Kind.of.Hash[a: 1] # {a: 1}
157
- Kind.of.Hash['', or: {}] # {}
196
+ Kind.of.Hash?({}) # true
197
+ Kind.of.Hash?({}, HashWithIndifferentAccess.new) # true
158
198
 
159
- # or
199
+ Kind.of.Hash?('') # false
200
+ Kind.of.Hash?({}, '') # false
160
201
 
161
- Kind.of.Hash.instance(nil) # raise Kind::Error, "nil expected to be a kind of Hash"
162
- Kind.of.Hash.instance('') # raise Kind::Error, "'' expected to be a kind of Hash"
163
- Kind.of.Hash.instance(a: 1) # {a: 1}
164
- Kind.of.Hash.instance('', or: {}) # {}
202
+ # ---
203
+
204
+ Kind.of.Boolean?(true) # true
205
+ Kind.of.Boolean?(false, true) # true
206
+
207
+ Kind.of.Boolean?(nil) # false
208
+ Kind.of.Boolean?(false, true, nil) # false
209
+
210
+ # ---
211
+
212
+ collection = [ {number: 1}, 'number 2', {number: 3}, :number_4 ]
213
+
214
+ collection.select(&Kind.of.Hash?) # [{:number=>1}, {:number=>3}]
165
215
  ```
166
216
 
167
- ### Verifying the kind of some class/module
217
+ ### Kind.is.\<Type\>() - Verifying if some class/module is the expected kind.
168
218
 
169
219
  You can use `Kind.is` to verify if some class has the expected type as its ancestor.
170
220
 
@@ -184,6 +234,49 @@ Kind.of.Hash.class?(Hash) # true
184
234
  Kind.of.Hash.class?(ActiveSupport::HashWithIndifferentAccess) # true
185
235
  ```
186
236
 
237
+ > **Note:** The `Kind.is` could check the inheritance of Classes/Modules.
238
+
239
+ ```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
246
+
247
+ Kind.is(Human, User) # true
248
+ Kind.is(Human, Human) # true
249
+ Kind.is(Human, Person) # true
250
+
251
+ Kind.is(Human, Struct) # false
252
+
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
259
+
260
+ Kind.is(Human, User) # true
261
+ Kind.is(Human, Human) # true
262
+ Kind.is(Human, Person) # true
263
+
264
+ Kind.is(Human, Struct) # false
265
+
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
272
+
273
+ Kind.is(Human, User) # true
274
+ Kind.is(Human, Human) # true
275
+ Kind.is(Human, Person) # true
276
+
277
+ Kind.is(Human, Struct) # false
278
+ ```
279
+
187
280
  [⬆️ Back to Top](#table-of-contents-)
188
281
 
189
282
  ### How to create a new type checker?
@@ -208,6 +301,22 @@ Kind.of(User, {}) # Kind::Error ({} expected to be a kind of User)
208
301
  Kind.of(Hash, {}) # {}
209
302
  Kind.of(Hash, user) # Kind::Error (<User ...> expected to be a kind of Hash)
210
303
 
304
+ # ----------------------------------------- #
305
+ # Verifiyng if the value is a kind instance #
306
+ # ----------------------------------------- #
307
+
308
+ Kind.of?(Numeric, 1) # true
309
+ Kind.of?(Numeric, 1, 2.0) # true
310
+
311
+ Kind.of?(Numeric, '1') # false
312
+ Kind.of?(Numeric, 1, '2.0') # false
313
+
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]
319
+
211
320
  # ---------------------------------- #
212
321
  # Creating type checkers dynamically #
213
322
  # ---------------------------------- #
@@ -246,7 +355,7 @@ end
246
355
  Kind.is(User, AdminUser) # true
247
356
  ```
248
357
 
249
- #### Registering new (custom) type checkers
358
+ #### Registering new (custom) type checker
250
359
 
251
360
  Use `Kind::Types.add()`. e.g:
252
361
 
@@ -366,13 +475,165 @@ The list of types (classes and modules) available to use with `Kind.of.*` or `Ki
366
475
  - `Kind.of.Module()`
367
476
  - `Kind.of.Lambda()`
368
477
  - `Kind.of.Boolean()`
369
- - `Kind.of.Callable()`: verifies if the given value `respond_to?(:call)` or if it's a class/module and if its `public_instance_methods.include?(:call)`.
478
+ - `Kind.of.Callable()`: verifies if the given value `respond_to?(:call)`.
370
479
  - `Kind.of.Maybe()` or its alias `Kind.of.Optional()`
371
480
 
372
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.
373
482
 
374
483
  [⬆️ Back to Top](#table-of-contents-)
375
484
 
485
+ ## Kind::Validator (ActiveModel::Validations)
486
+
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
488
+
489
+ ```ruby
490
+ class Person
491
+ include ActiveModel::Validations
492
+
493
+ attr_accessor :first_name, :last_name
494
+
495
+ validates :first_name, :last_name, kind: String
496
+ end
497
+ ```
498
+
499
+ And to make use of it, you will need to do an explicitly require. e.g:
500
+
501
+ ```ruby
502
+ # In some Gemfile
503
+ gem 'kind', require: 'kind/active_model/validation'
504
+
505
+ # In some .rb file
506
+ require 'kind/active_model/validation'
507
+ ```
508
+
509
+ ### Usage
510
+
511
+ **[Object#kind_of?](https://ruby-doc.org/core-2.6.4/Object.html#method-i-kind_of-3F)**
512
+
513
+ ```ruby
514
+ validates :name, kind: { of: String }
515
+
516
+ # Use an array to verify if the attribute
517
+ # is an instance of one of the classes/modules.
518
+
519
+ validates :status, kind: { of: [String, Symbol]}
520
+ ```
521
+
522
+ **[Kind.is](#verifying-the-kind-of-some-classmodule)**
523
+
524
+ ```ruby
525
+ #
526
+ # Verifying if the attribute value is the class or a subclass.
527
+ #
528
+ class Human; end
529
+ class Person < Human; end
530
+ class User < Human; end
531
+
532
+ validates :human_kind, kind: { is: Human }
533
+
534
+ #
535
+ # Verifying if the attribute value is the module or if it is a class that includes the module
536
+ #
537
+ module Human; end
538
+ class Person; include Human; end
539
+ class User; include Human; end
540
+
541
+ validates :human_kind, kind: { is: Human }
542
+
543
+ #
544
+ # Verifying if the attribute value is the module or if it is a module that extends the module
545
+ #
546
+ module Human; end
547
+ module Person; extend Human; end
548
+ module User; extend Human; end
549
+
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.
554
+
555
+ validates :human_kind, kind: { is: [Person, User] }
556
+ ```
557
+
558
+ **[Object#instance_of?](https://ruby-doc.org/core-2.6.4/Object.html#method-i-instance_of-3F)**
559
+
560
+ ```ruby
561
+ validates :name, kind: { instance_of: String }
562
+
563
+ # or use an array to verify if the attribute
564
+ # is an instance of one of the classes/modules.
565
+
566
+ validates :name, kind: { instance_of: [String, Symbol] }
567
+ ```
568
+
569
+
570
+ **[Object#respond_to?](https://ruby-doc.org/core-2.6.4/Object.html#method-i-respond_to-3F)**
571
+
572
+ ```ruby
573
+ validates :handler, kind: { respond_to: :call }
574
+ ```
575
+
576
+ **Array.new.all? { |item| item.kind_of?(Class) }**
577
+
578
+ ```ruby
579
+ validates :account_types, kind: { array_of: String }
580
+
581
+ # or use an array to verify if the attribute
582
+ # is an instance of one of the classes
583
+
584
+ validates :account_types, kind: { array_of: [String, Symbol] }
585
+ ```
586
+
587
+ **Array.new.all? { |item| expected_values.include?(item) }**
588
+
589
+ ```ruby
590
+ # Verifies if the attribute value
591
+ # is an array with some or all the expected values.
592
+
593
+ validates :account_types, kind: { array_with: ['foo', 'bar'] }
594
+ ```
595
+
596
+ #### Defining the default validation strategy
597
+
598
+ By default, you can define the attribute type directly (without a hash). e.g.
599
+
600
+ ```ruby
601
+ validates :name, kind: String
602
+ # or
603
+ validates :name, kind: [String, Symbol]
604
+ ```
605
+
606
+ To changes this behavior you can set another strategy to validates the attributes types:
607
+
608
+ ```ruby
609
+ Kind::Validator.default_strategy = :instance_of
610
+
611
+ # Tip: Create an initializer if you are in a Rails application.
612
+ ```
613
+
614
+ And these are the available options to define the default strategy:
615
+ - `kind_of` *(default)*
616
+ - `instance_of`
617
+
618
+ #### Using the `allow_nil` and `strict` options
619
+
620
+ You can use the `allow_nil` option with any of the kind validations. e.g.
621
+
622
+ ```ruby
623
+ validates :name, kind: String, allow_nil: true
624
+ ```
625
+
626
+ And as any active model validation, kind validations works with the `strict: true`
627
+ option and with the `validates!` method. e.g.
628
+
629
+ ```ruby
630
+ validates :first_name, kind: String, strict: true
631
+ # or
632
+ validates! :last_name, kind: String
633
+ ```
634
+
635
+ [⬆️ Back to Top](#table-of-contents-)
636
+
376
637
  ## Kind::Undefined
377
638
 
378
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.
@@ -437,7 +698,27 @@ puts optional.value_or(1) # 1
437
698
  puts optional.value_or { 1 } # 1
438
699
  ```
439
700
 
440
- ### Kind::Maybe[] and Kind::Maybe#then
701
+ #### Replacing blocks by lambdas
702
+
703
+ ```ruby
704
+ Add = -> params do
705
+ a, b = Kind.of.Hash(params, or: Empty::HASH).values_at(:a, :b)
706
+
707
+ a + b if Kind.of.Numeric?(a, b)
708
+ end
709
+
710
+ # --
711
+
712
+ Kind::Maybe.new(a: 1, b: 2).map(&Add).value_or(0) # 3
713
+
714
+ # --
715
+
716
+ Kind::Maybe.new([]).map(&Add).value_or(0) # 0
717
+ Kind::Maybe.new({}).map(&Add).value_or(0) # 0
718
+ Kind::Maybe.new(nil).map(&Add).value_or(0) # 0
719
+ ```
720
+
721
+ ### Kind::Maybe[], Kind::Maybe.wrap() and Kind::Maybe#then method aliases
441
722
 
442
723
  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.
443
724
 
@@ -451,43 +732,70 @@ result =
451
732
  puts result # 42
452
733
  ```
453
734
 
454
- ### Kind::Maybe#try
735
+ You can also use `Kind::Maybe.wrap()` instead of the `.new` method.
455
736
 
456
- 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.
737
+ ```ruby
738
+ result =
739
+ Kind::Maybe
740
+ .wrap(5)
741
+ .then { |value| value * 5 }
742
+ .then { |value| value + 17 }
743
+ .value_or(0)
744
+
745
+ puts result # 42
746
+ ```
747
+
748
+ #### Replacing blocks by lambdas
457
749
 
458
750
  ```ruby
459
- object = 'foo'
751
+ Add = -> params do
752
+ a, b = Kind.of.Hash(params, or: Empty::HASH).values_at(:a, :b)
460
753
 
461
- p Kind::Maybe[object].try(:upcase) # "FOO"
754
+ a + b if Kind.of.Numeric.instance?(a, b)
755
+ end
462
756
 
463
- p Kind::Maybe[{}].try(:fetch, :number, 0) # 0
757
+ # --
464
758
 
465
- p Kind::Maybe[{number: 1}].try(:fetch, :number) # 1
759
+ Kind::Maybe[a: 1, b: 2].then(&Add).value_or(0) # 3
466
760
 
467
- p Kind::Maybe[object].try { |value| value.upcase } # "FOO"
761
+ # --
468
762
 
469
- #############
470
- # Nil value #
471
- #############
763
+ Kind::Maybe[1].then(&Add).value_or(0) # 0
764
+ Kind::Maybe['2'].then(&Add).value_or(0) # 0
765
+ Kind::Maybe[nil].then(&Add).value_or(0) # 0
766
+ ```
472
767
 
473
- object = nil
768
+ ### Kind::None() and Kind::Some()
474
769
 
475
- p Kind::Maybe[object].try(:upcase) # nil
770
+ If you need to ensure the return of `Kind::Maybe` results from your methods/lambdas,
771
+ you could use the methods `Kind::None` and `Kind::Some` to do this. e.g:
476
772
 
477
- p Kind::Maybe[object].try { |value| value.upcase } # nil
773
+ ```ruby
774
+ Add = -> params do
775
+ a, b = Kind.of.Hash(params, or: Empty::HASH).values_at(:a, :b)
478
776
 
479
- #########################
480
- # Kind::Undefined value #
481
- #########################
777
+ return Kind::None unless Kind.of.Numeric?(a, b)
482
778
 
483
- object = Kind::Undefined
779
+ Kind::Some(a + b)
780
+ end
484
781
 
485
- p Kind::Maybe[object].try(:upcase) # nil
782
+ # --
486
783
 
487
- p Kind::Maybe[object].try { |value| value.upcase } # nil
488
- ```
784
+ Add.call(1) # #<Kind::Maybe::None:0x0000... @value=nil>
785
+ Add.call({}) # #<Kind::Maybe::None:0x0000... @value=nil>
786
+ Add.call(a: 1) # #<Kind::Maybe::None:0x0000... @value=nil>
787
+ Add.call(b: 2) # #<Kind::Maybe::None:0x0000... @value=nil>
489
788
 
490
- [⬆️ Back to Top](#table-of-contents-)
789
+ Add.call(a:1, b: 2) # #<Kind::Maybe::Some:0x0000... @value=3>
790
+
791
+ # --
792
+
793
+ Kind::Maybe[a: 1, b: 2].then(&Add).value_or(0) # 3
794
+
795
+ 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
+ ```
491
799
 
492
800
  ### Kind.of.Maybe()
493
801
 
@@ -544,11 +852,31 @@ result2 =
544
852
  puts result2 # 35
545
853
  ```
546
854
 
855
+ #### Replacing blocks by lambdas
856
+
857
+ ```ruby
858
+ Add = -> params do
859
+ a, b = Kind.of.Hash(params, or: Empty::HASH).values_at(:a, :b)
860
+
861
+ a + b if Kind.of.Numeric.instance?(a, b)
862
+ end
863
+
864
+ # --
865
+
866
+ Kind::Optional[a: 1, b: 2].then(&Add).value_or(0) # 3
867
+
868
+ # --
869
+
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
873
+ ```
874
+
547
875
  **Note:** The `Kind.of.Optional` is available to check if some value is a `Kind::Optional`.
548
876
 
549
877
  [⬆️ Back to Top](#table-of-contents-)
550
878
 
551
- ### Kind.of.<Type>.as_optional
879
+ ### Kind.of.\<Type\>.as_optional
552
880
 
553
881
  It is very common the need to avoid some computing when a method receives a wrong input.
554
882
  In these scenarios, you could check the given input type as optional and avoid unexpected behavior. e.g:
@@ -573,7 +901,7 @@ person_name(first_name: 'Rodrigo', last_name: 'Serradura') # "Rodrigo Serradura"
573
901
  # See below the previous implementation without using an optional.
574
902
  #
575
903
  def person_name(params)
576
- if params.is_a?(Hash) && params.values_at(:first_name, :last_name).compact.size == 2
904
+ if params.kind_of?(Hash) && params.values_at(:first_name, :last_name).compact.size == 2
577
905
  "#{params[:first_name]} #{params[:last_name]}"
578
906
  else
579
907
  'John Doe'
@@ -581,17 +909,12 @@ def person_name(params)
581
909
  end
582
910
  ```
583
911
 
584
- > 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).
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).
585
913
 
586
914
  Let's see another example using a collection and how the method `.as_optional` works when it receives no argument.
587
915
 
588
916
  ```ruby
589
- collection = [
590
- {number: 1},
591
- 'number 0',
592
- {number: 2},
593
- [0],
594
- ]
917
+ collection = [ {number: 1}, 'number 0', {number: 2}, [0] ]
595
918
 
596
919
  collection
597
920
  .select(&Kind.of.Hash.as_optional)
@@ -613,9 +936,9 @@ module PersonIntroduction
613
936
  extend self
614
937
 
615
938
  def call(params)
616
- optional_params = Kind::Of::Hash.as_optional(params)
939
+ optional = Kind::Of::Hash.as_optional(params)
617
940
 
618
- "Hi my name is #{full_name(optional_params)}, I'm #{age(optional_params)} years old."
941
+ "Hi my name is #{full_name(optional)}, I'm #{age(optional)} years old."
619
942
  end
620
943
 
621
944
  private
@@ -637,7 +960,7 @@ module PersonIntroduction
637
960
  extend self
638
961
 
639
962
  def call(params)
640
- "Hi my name is #{full_name(params)}, I'm #{age(params)}"
963
+ "Hi my name is #{full_name(params)}, I'm #{age(params)} years old."
641
964
  end
642
965
 
643
966
  private
@@ -660,6 +983,86 @@ end
660
983
 
661
984
  [⬆️ Back to Top](#table-of-contents-)
662
985
 
986
+ ### Kind::Maybe(<Type>)
987
+
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:
989
+
990
+ ```ruby
991
+ result1 =
992
+ Kind::Maybe(Numeric)
993
+ .wrap(5)
994
+ .then { |value| value * 5 }
995
+ .value_or { 0 }
996
+
997
+ puts result1 # 25
998
+
999
+ # ---
1000
+
1001
+ result2 =
1002
+ Kind::Optional(Numeric)
1003
+ .wrap('5')
1004
+ .then { |value| value * 5 }
1005
+ .value_or { 0 }
1006
+
1007
+ puts result2 # 0
1008
+ ```
1009
+
1010
+ This typed maybe has the same methods of `Kind::Maybe` class. e.g:
1011
+
1012
+ ```ruby
1013
+ Kind::Maybe(Numeric)[5]
1014
+ Kind::Maybe(Numeric).new(5)
1015
+ Kind::Maybe(Numeric).wrap(5)
1016
+
1017
+ # ---
1018
+
1019
+ Kind::Optional(Numeric)[5]
1020
+ Kind::Optional(Numeric).new(5)
1021
+ Kind::Optional(Numeric).wrap(5)
1022
+ ```
1023
+
1024
+ [⬆️ Back to Top](#table-of-contents-)
1025
+
1026
+ ### Kind::Maybe#try
1027
+
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.
1029
+
1030
+ ```ruby
1031
+ object = 'foo'
1032
+
1033
+ Kind::Maybe[object].try(:upcase) # "FOO"
1034
+
1035
+ Kind::Maybe[{}].try(:fetch, :number, 0) # 0
1036
+
1037
+ Kind::Maybe[{number: 1}].try(:fetch, :number) # 1
1038
+
1039
+ Kind::Maybe[object].try { |value| value.upcase } # "FOO"
1040
+
1041
+ #############
1042
+ # Nil value #
1043
+ #############
1044
+
1045
+ object = nil
1046
+
1047
+ Kind::Maybe[object].try(:upcase) # nil
1048
+
1049
+ Kind::Maybe[object].try { |value| value.upcase } # nil
1050
+
1051
+ #########################
1052
+ # Kind::Undefined value #
1053
+ #########################
1054
+
1055
+ object = Kind::Undefined
1056
+
1057
+ Kind::Maybe[object].try(:upcase) # nil
1058
+
1059
+ Kind::Maybe[object].try { |value| value.upcase } # nil
1060
+ ```
1061
+
1062
+ > **Note:** You can use the try method with the `Kind::Optional`.
1063
+
1064
+ [⬆️ Back to Top](#table-of-contents-)
1065
+
663
1066
  ## Kind::Empty
664
1067
 
665
1068
  When you define a method that has default arguments, for certain data types, you will always create a new object in memory. e.g: