kind 1.8.0 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
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: