kind 1.7.0 → 2.2.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: 273fcdcef40d8b61e552bb440f93a7b83adfd29621e1809201a66117babf9974
4
- data.tar.gz: 84dac931b67ccc4ee895a8c2a4c3326ccfe0faf0540df09ba9fbcbfbe5034edc
3
+ metadata.gz: 3bb8430c79ca97835ebdf6604e0c5afc4301f912db778e5f930fb9e699ae08c1
4
+ data.tar.gz: 0557c3df900513e5ec7d45fec79fdf7502f0cb06a8e1538ff935e0228ef11136
5
5
  SHA512:
6
- metadata.gz: b3d702df70066307c7dac3058c41dab688de46c606d57455194d77570823d051dab5fd43b5c87c870dee58b9cef41a9e93a7d9d4639fe39489cc4b0e2d93d4bf
7
- data.tar.gz: 4c02cd4e43a0407167f4284c6972feed925380a0d71a8c5dedcb474a24f458954175a929dac9d5b4509520cfea663e45692dba67269712bc6e8d83e53b80a8aa
6
+ metadata.gz: 0ad03f0d27484914f45607d9e1fd7cfdaf8000d700e31f069b274b5f977f42a8736a3d03d0cba1f3d06679f0f5a05b56d7c3b4acfd2a90cce20e524de6f9154a
7
+ data.tar.gz: 6f8e54590261939a43a52895921d80fb693a0e16361d011f53a80f1d7269aaade1e8871ae44374586ec488c026a02fced9057fdfcb300c532bd3a018498e954a
@@ -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,6 +1,8 @@
1
1
  ---
2
2
  language: ruby
3
3
 
4
+ sudo: false
5
+
4
6
  cache: bundler
5
7
 
6
8
  rvm:
@@ -22,7 +24,7 @@ before_script:
22
24
  - chmod +x ./cc-test-reporter
23
25
  - "./cc-test-reporter before-build"
24
26
 
25
- script: bundle exec rake test
27
+ script: "./.travis.sh"
26
28
 
27
29
  after_success:
28
30
  - "./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,49 @@
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 using a bunch of basic abstractions. So, after reading this README and realizing that you need something more robust, I recommend you check out the [dry-types gem](https://dry-rb.org/gems/dry-types).
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[] and Kind::Maybe#then method aliases](#kindmaybe-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)
47
+ - [Replacing blocks by lambdas](#replacing-blocks-by-lambdas-2)
48
+ - [Kind.of.\<Type\>.as_optional](#kindoftypeas_optional)
49
+ - [Kind::Maybe#try](#kindmaybetry)
38
50
  - [Kind::Empty](#kindempty)
51
+ - [Similar Projects](#similar-projects)
39
52
  - [Development](#development)
40
53
  - [Contributing](#contributing)
41
54
  - [License](#license)
@@ -76,14 +89,14 @@ sum(1, 1) # 2
76
89
  sum('1', 1) # Kind::Error ("\"1\" expected to be a kind of Numeric")
77
90
  ```
78
91
 
79
- ### Verifying the kind of some object
92
+ ### Kind.of.\<Type\>() - Verifying the kind of some object
80
93
 
81
94
  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.
82
95
 
83
96
  ```ruby
84
- Kind.of.Hash(nil) # **raise Kind::Error, "nil expected to be a kind of Hash"**
85
- Kind.of.Hash('') # raise Kind::Error, "'' expected to be a kind of Hash"
86
- Kind.of.Hash({a: 1}) # {a: 1}
97
+ Kind.of.Hash(nil) # **raise Kind::Error, "nil expected to be a kind of Hash"**
98
+ Kind.of.Hash('') # raise Kind::Error, "'' expected to be a kind of Hash"
99
+ Kind.of.Hash(a: 1) # {a: 1}
87
100
 
88
101
  # ---
89
102
 
@@ -92,6 +105,15 @@ Kind.of.Boolean(true) # true
92
105
  Kind.of.Boolean(false) # false
93
106
  ```
94
107
 
108
+ > **Note:** `Kind.of.<Type>` supports the to_proc protocol.
109
+ > And it will perform a strict validation as expected.
110
+
111
+ ```ruby
112
+ collection = [ {number: 1}, 'number 2', {number: 3}, :number_4 ]
113
+
114
+ collection.map(&Kind.of.Hash) # Kind::Error ("number 2" expected to be a kind of Hash)
115
+ ```
116
+
95
117
  When the verified value is nil, it is possible to define a default value with the same type to be returned.
96
118
 
97
119
  ```ruby
@@ -104,7 +126,25 @@ Kind.of.Hash(value, or: {}) # {}
104
126
  Kind.of.Boolean(nil, or: true) # true
105
127
  ```
106
128
 
107
- As an alternative syntax, you can use the `Kind::Of` instead of the `Kind.of` method. e.g: `Kind::Of::Hash('')`
129
+ > **Note:** As an alternative syntax, you can use the `Kind::Of` instead of the `Kind.of` method. e.g: `Kind::Of::Hash('')`
130
+
131
+ #### Method aliases to perform a strict validation
132
+
133
+ ```ruby
134
+ Kind.of.Hash[nil] # raise Kind::Error, "nil expected to be a kind of Hash"
135
+ Kind.of.Hash[''] # raise Kind::Error, "'' expected to be a kind of Hash"
136
+ Kind.of.Hash[a: 1] # {a: 1}
137
+ Kind.of.Hash['', or: {}] # {}
138
+
139
+ # or
140
+
141
+ Kind.of.Hash.instance(nil) # raise Kind::Error, "nil expected to be a kind of Hash"
142
+ Kind.of.Hash.instance('') # raise Kind::Error, "'' expected to be a kind of Hash"
143
+ Kind.of.Hash.instance(a: 1) # {a: 1}
144
+ Kind.of.Hash.instance('', or: {}) # {}
145
+ ```
146
+
147
+ ### Kind.of.\<Type\>.or_nil()
108
148
 
109
149
  But if you don't need a strict type verification, use the `.or_nil` method.
110
150
 
@@ -118,36 +158,62 @@ Kind.of.Boolean.or_nil('') # nil
118
158
  Kind.of.Boolean.or_nil(true) # true
119
159
  ```
120
160
 
121
- And just for convenience, you can use the method `.instance?` to verify if the given object has the expected type.
161
+ ### Kind.of.\<Type\>.instance?()
162
+
163
+ Use the method `.instance?` to verify if the given object has the expected type.
122
164
 
123
165
  ```ruby
124
- Kind.of.Hash.instance?('')
125
- # false
166
+ Kind.of.Hash.instance?({}) # true
167
+ Kind.of.Hash.instance?({}, HashWithIndifferentAccess.new) # true
168
+
169
+ Kind.of.Hash.instance?('') # false
170
+ Kind.of.Hash.instance?({}, '') # false
126
171
 
127
172
  # ---
128
173
 
129
- Kind.of.Boolean.instance?('') # false
130
- Kind.of.Boolean.instance?(true) # true
131
- Kind.of.Boolean.instance?(false) # true
174
+ Kind.of.Boolean.instance?(true) # true
175
+ Kind.of.Boolean.instance?(true, false) # true
176
+
177
+ Kind.of.Boolean.instance?(nil) # false
178
+ Kind.of.Boolean.instance?(false, true, nil) # false
132
179
  ```
133
180
 
134
- Also, there are aliases to perform the strict type verification. e.g:
181
+ > **Note:** When `.instance?` is called without an argument,
182
+ > it will return a lambda which will perform the kind verification.
135
183
 
136
184
  ```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: {}] # {}
185
+ collection = [ {number: 1}, 'number 2', {number: 3}, :number_4 ]
141
186
 
142
- # or
187
+ collection
188
+ .select(&Kind.of.Hash.instance?) # [{:number=>1}, {:number=>3}]
189
+ ```
143
190
 
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: {}) # {}
191
+ > **Note:** You can use a different syntax to perform an instance verification.
192
+ > To do this, use Kind.of.\<Type\>?()
193
+
194
+ ```ruby
195
+ Kind.of.Hash?({}) # true
196
+ Kind.of.Hash?({}, HashWithIndifferentAccess.new) # true
197
+
198
+ Kind.of.Hash?('') # false
199
+ Kind.of.Hash?({}, '') # false
200
+
201
+ # ---
202
+
203
+ Kind.of.Boolean?(true) # true
204
+ Kind.of.Boolean?(false, true) # true
205
+
206
+ Kind.of.Boolean?(nil) # false
207
+ Kind.of.Boolean?(false, true, nil) # false
208
+
209
+ # ---
210
+
211
+ collection = [ {number: 1}, 'number 2', {number: 3}, :number_4 ]
212
+
213
+ collection.select(&Kind.of.Hash?) # [{:number=>1}, {:number=>3}]
148
214
  ```
149
215
 
150
- ### Verifying the kind of some class/module
216
+ ### Kind.is.\<Type\>() - Verifying if some class/module is the expected kind.
151
217
 
152
218
  You can use `Kind.is` to verify if some class has the expected type as its ancestor.
153
219
 
@@ -167,6 +233,49 @@ Kind.of.Hash.class?(Hash) # true
167
233
  Kind.of.Hash.class?(ActiveSupport::HashWithIndifferentAccess) # true
168
234
  ```
169
235
 
236
+ > **Note:** The `Kind.is` could check the inheritance of Classes/Modules.
237
+
238
+ ```ruby
239
+ #
240
+ # Verifying if the attribute value is the class or a subclass.
241
+ #
242
+ class Human; end
243
+ class Person < Human; end
244
+ class User < Human; end
245
+
246
+ Kind.is(Human, User) # true
247
+ Kind.is(Human, Human) # true
248
+ Kind.is(Human, Person) # true
249
+
250
+ Kind.is(Human, Struct) # false
251
+
252
+ #
253
+ # Verifying if the attribute value is the module or if it is a class that includes the module
254
+ #
255
+ module Human; end
256
+ class Person; include Human; end
257
+ class User; include Human; end
258
+
259
+ Kind.is(Human, User) # true
260
+ Kind.is(Human, Human) # true
261
+ Kind.is(Human, Person) # true
262
+
263
+ Kind.is(Human, Struct) # false
264
+
265
+ #
266
+ # Verifying if the attribute value is the module or if it is a module that extends the module
267
+ #
268
+ module Human; end
269
+ module Person; extend Human; end
270
+ module User; extend Human; end
271
+
272
+ Kind.is(Human, User) # true
273
+ Kind.is(Human, Human) # true
274
+ Kind.is(Human, Person) # true
275
+
276
+ Kind.is(Human, Struct) # false
277
+ ```
278
+
170
279
  [⬆️ Back to Top](#table-of-contents-)
171
280
 
172
281
  ### How to create a new type checker?
@@ -191,6 +300,22 @@ Kind.of(User, {}) # Kind::Error ({} expected to be a kind of User)
191
300
  Kind.of(Hash, {}) # {}
192
301
  Kind.of(Hash, user) # Kind::Error (<User ...> expected to be a kind of Hash)
193
302
 
303
+ # ----------------------------------------- #
304
+ # Verifiyng if the value is a kind instance #
305
+ # ----------------------------------------- #
306
+
307
+ Kind.of?(Numeric, 1) # true
308
+ Kind.of?(Numeric, 1, 2.0) # true
309
+
310
+ Kind.of?(Numeric, '1') # false
311
+ Kind.of?(Numeric, 1, '2.0') # false
312
+
313
+ # Note: Kind.of?(Type) without arguments will return a
314
+ # lambda that will perform an instance verification
315
+ #
316
+ [1, '2', 3.0, '4']
317
+ .select(&Kind.of?(Numeric)) # [1, 3.0]
318
+
194
319
  # ---------------------------------- #
195
320
  # Creating type checkers dynamically #
196
321
  # ---------------------------------- #
@@ -205,6 +330,15 @@ kind_of_user.instance?(User) # true
205
330
  kind_of_user.class?(Hash) # false
206
331
  kind_of_user.class?(User) # true
207
332
 
333
+ # ------------------------------------ #
334
+ # Using methods which returns a lambda #
335
+ # ------------------------------------ #
336
+ collection = [User.new, User.new, 0, {} nil, User.new]
337
+
338
+ collection.select(&Kind.of(User).instance?).size == 3 # true
339
+
340
+ collection.map(&Kind.of(User).as_optional).select(&:some?).size == 3 # true
341
+
208
342
  # Creating type checkers dynamically is cheap
209
343
  # because a singleton object is created to be available for use.
210
344
 
@@ -220,7 +354,7 @@ end
220
354
  Kind.is(User, AdminUser) # true
221
355
  ```
222
356
 
223
- #### Registering new (custom) type checkers
357
+ #### Registering new (custom) type checker
224
358
 
225
359
  Use `Kind::Types.add()`. e.g:
226
360
 
@@ -340,10 +474,162 @@ The list of types (classes and modules) available to use with `Kind.of.*` or `Ki
340
474
  - `Kind.of.Module()`
341
475
  - `Kind.of.Lambda()`
342
476
  - `Kind.of.Boolean()`
343
- - `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)`.
477
+ - `Kind.of.Callable()`: verifies if the given value `respond_to?(:call)`.
344
478
  - `Kind.of.Maybe()` or its alias `Kind.of.Optional()`
345
479
 
346
- PS: Remember, you can use the `Kind.is.*` method to check if some given value is a class/module with all type checkers above.
480
+ **Note:** Remember, you can use the `Kind.is.*` method to check if some given value is a class/module with all type checkers above.
481
+
482
+ [⬆️ Back to Top](#table-of-contents-)
483
+
484
+ ## Kind::Validator (ActiveModel::Validations)
485
+
486
+ 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
487
+
488
+ ```ruby
489
+ class Person
490
+ include ActiveModel::Validations
491
+
492
+ attr_accessor :first_name, :last_name
493
+
494
+ validates :first_name, :last_name, kind: String
495
+ end
496
+ ```
497
+
498
+ And to make use of it, you will need to do an explicitly require. e.g:
499
+
500
+ ```ruby
501
+ # In some Gemfile
502
+ gem 'kind', require: 'kind/active_model/validation'
503
+
504
+ # In some .rb file
505
+ require 'kind/active_model/validation'
506
+ ```
507
+
508
+ ### Usage
509
+
510
+ **[Object#kind_of?](https://ruby-doc.org/core-2.6.4/Object.html#method-i-kind_of-3F)**
511
+
512
+ ```ruby
513
+ validates :name, kind: { of: String }
514
+
515
+ # Use an array to verify if the attribute
516
+ # is an instance of one of the classes/modules.
517
+
518
+ validates :status, kind: { of: [String, Symbol]}
519
+ ```
520
+
521
+ **[Kind.is](#verifying-the-kind-of-some-classmodule)**
522
+
523
+ ```ruby
524
+ #
525
+ # Verifying if the attribute value is the class or a subclass.
526
+ #
527
+ class Human; end
528
+ class Person < Human; end
529
+ class User < Human; end
530
+
531
+ validates :human_kind, kind: { is: Human }
532
+
533
+ #
534
+ # Verifying if the attribute value is the module or if it is a class that includes the module
535
+ #
536
+ module Human; end
537
+ class Person; include Human; end
538
+ class User; include Human; end
539
+
540
+ validates :human_kind, kind: { is: Human }
541
+
542
+ #
543
+ # Verifying if the attribute value is the module or if it is a module that extends the module
544
+ #
545
+ module Human; end
546
+ module Person; extend Human; end
547
+ module User; extend Human; end
548
+
549
+ validates :human_kind, kind: { is: Human }
550
+
551
+ # or use an array to verify if the attribute
552
+ # is a kind of one those classes/modules.
553
+
554
+ validates :human_kind, kind: { is: [Person, User] }
555
+ ```
556
+
557
+ **[Object#instance_of?](https://ruby-doc.org/core-2.6.4/Object.html#method-i-instance_of-3F)**
558
+
559
+ ```ruby
560
+ validates :name, kind: { instance_of: String }
561
+
562
+ # or use an array to verify if the attribute
563
+ # is an instance of one of the classes/modules.
564
+
565
+ validates :name, kind: { instance_of: [String, Symbol] }
566
+ ```
567
+
568
+
569
+ **[Object#respond_to?](https://ruby-doc.org/core-2.6.4/Object.html#method-i-respond_to-3F)**
570
+
571
+ ```ruby
572
+ validates :handler, kind: { respond_to: :call }
573
+ ```
574
+
575
+ **Array.new.all? { |item| item.kind_of?(Class) }**
576
+
577
+ ```ruby
578
+ validates :account_types, kind: { array_of: String }
579
+
580
+ # or use an array to verify if the attribute
581
+ # is an instance of one of the classes
582
+
583
+ validates :account_types, kind: { array_of: [String, Symbol] }
584
+ ```
585
+
586
+ **Array.new.all? { |item| expected_values.include?(item) }**
587
+
588
+ ```ruby
589
+ # Verifies if the attribute value
590
+ # is an array with some or all the expected values.
591
+
592
+ validates :account_types, kind: { array_with: ['foo', 'bar'] }
593
+ ```
594
+
595
+ #### Defining the default validation strategy
596
+
597
+ By default, you can define the attribute type directly (without a hash). e.g.
598
+
599
+ ```ruby
600
+ validates :name, kind: String
601
+ # or
602
+ validates :name, kind: [String, Symbol]
603
+ ```
604
+
605
+ To changes this behavior you can set another strategy to validates the attributes types:
606
+
607
+ ```ruby
608
+ Kind::Validator.default_strategy = :instance_of
609
+
610
+ # Tip: Create an initializer if you are in a Rails application.
611
+ ```
612
+
613
+ And these are the available options to define the default strategy:
614
+ - `kind_of` *(default)*
615
+ - `instance_of`
616
+
617
+ #### Using the `allow_nil` and `strict` options
618
+
619
+ You can use the `allow_nil` option with any of the kind validations. e.g.
620
+
621
+ ```ruby
622
+ validates :name, kind: String, allow_nil: true
623
+ ```
624
+
625
+ And as any active model validation, kind validations works with the `strict: true`
626
+ option and with the `validates!` method. e.g.
627
+
628
+ ```ruby
629
+ validates :first_name, kind: String, strict: true
630
+ # or
631
+ validates! :last_name, kind: String
632
+ ```
347
633
 
348
634
  [⬆️ Back to Top](#table-of-contents-)
349
635
 
@@ -411,7 +697,27 @@ puts optional.value_or(1) # 1
411
697
  puts optional.value_or { 1 } # 1
412
698
  ```
413
699
 
414
- ### Kind::Maybe[] and Kind::Maybe#then
700
+ #### Replacing blocks by lambdas
701
+
702
+ ```ruby
703
+ Add = -> params do
704
+ a, b = Kind.of.Hash(params, or: Empty::HASH).values_at(:a, :b)
705
+
706
+ a + b if Kind.of.Numeric?(a, b)
707
+ end
708
+
709
+ # --
710
+
711
+ Kind::Maybe.new(a: 1, b: 2).map(&Add).value_or(0) # 3
712
+
713
+ # --
714
+
715
+ Kind::Maybe.new([]).map(&Add).value_or(0) # 0
716
+ Kind::Maybe.new({}).map(&Add).value_or(0) # 0
717
+ Kind::Maybe.new(nil).map(&Add).value_or(0) # 0
718
+ ```
719
+
720
+ ### Kind::Maybe[] and Kind::Maybe#then method aliases
415
721
 
416
722
  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.
417
723
 
@@ -425,39 +731,57 @@ result =
425
731
  puts result # 42
426
732
  ```
427
733
 
428
- ### Kind::Maybe#try
429
-
430
- 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.
734
+ #### Replacing blocks by lambdas
431
735
 
432
736
  ```ruby
433
- object = 'foo'
737
+ Add = -> params do
738
+ a, b = Kind.of.Hash(params, or: Empty::HASH).values_at(:a, :b)
434
739
 
435
- p Kind::Maybe[object].try(:upcase) # "FOO"
740
+ a + b if Kind.of.Numeric.instance?(a, b)
741
+ end
436
742
 
437
- p Kind::Maybe[object].try { |value| value.upcase } # "FOO"
743
+ # --
438
744
 
439
- #############
440
- # Nil value #
441
- #############
745
+ Kind::Maybe[a: 1, b: 2].then(&Add).value_or(0) # 3
442
746
 
443
- object = nil
747
+ # --
444
748
 
445
- p Kind::Maybe[object].try(:upcase) # nil
749
+ Kind::Maybe[1].then(&Add).value_or(0) # 0
750
+ Kind::Maybe['2'].then(&Add).value_or(0) # 0
751
+ Kind::Maybe[nil].then(&Add).value_or(0) # 0
752
+ ```
446
753
 
447
- p Kind::Maybe[object].try { |value| value.upcase } # nil
754
+ ### Kind::None() and Kind::Some()
448
755
 
449
- #########################
450
- # Kind::Undefined value #
451
- #########################
756
+ If you need to ensure the return of `Kind::Maybe` results from your methods/lambdas,
757
+ you could use the methods `Kind::None` and `Kind::Some` to do this. e.g:
452
758
 
453
- object = Kind::Undefined
759
+ ```ruby
760
+ Add = -> params do
761
+ a, b = Kind.of.Hash(params, or: Empty::HASH).values_at(:a, :b)
454
762
 
455
- p Kind::Maybe[object].try(:upcase) # nil
763
+ return Kind::None unless Kind.of.Numeric?(a, b)
456
764
 
457
- p Kind::Maybe[object].try { |value| value.upcase } # nil
458
- ```
765
+ Kind::Some(a + b)
766
+ end
459
767
 
460
- [⬆️ Back to Top](#table-of-contents-)
768
+ # --
769
+
770
+ Add.call(1) # #<Kind::Maybe::None:0x0000... @value=nil>
771
+ Add.call({}) # #<Kind::Maybe::None:0x0000... @value=nil>
772
+ Add.call(a: 1) # #<Kind::Maybe::None:0x0000... @value=nil>
773
+ Add.call(b: 2) # #<Kind::Maybe::None:0x0000... @value=nil>
774
+
775
+ Add.call(a:1, b: 2) # #<Kind::Maybe::Some:0x0000... @value=3>
776
+
777
+ # --
778
+
779
+ Kind::Maybe[a: 1, b: 2].then(&Add).value_or(0) # 3
780
+
781
+ Kind::Maybe[1].then(&Add).value_or(0) # 0
782
+ Kind::Maybe['2'].then(&Add).value_or(0) # 0
783
+ Kind::Maybe[nil].then(&Add).value_or(0) # 0
784
+ ```
461
785
 
462
786
  ### Kind.of.Maybe()
463
787
 
@@ -514,7 +838,174 @@ result2 =
514
838
  puts result2 # 35
515
839
  ```
516
840
 
517
- PS: The `Kind.of.Optional` is available to check if some value is a `Kind::Optional`.
841
+ #### Replacing blocks by lambdas
842
+
843
+ ```ruby
844
+ Add = -> params do
845
+ a, b = Kind.of.Hash(params, or: Empty::HASH).values_at(:a, :b)
846
+
847
+ a + b if Kind.of.Numeric.instance?(a, b)
848
+ end
849
+
850
+ # --
851
+
852
+ Kind::Optional[a: 1, b: 2].then(&Add).value_or(0) # 3
853
+
854
+ # --
855
+
856
+ Kind::Optional[1].then(&Add).value_or(0) # 0
857
+ Kind::Optional['2'].then(&Add).value_or(0) # 0
858
+ Kind::Optional[nil].then(&Add).value_or(0) # 0
859
+ ```
860
+
861
+ **Note:** The `Kind.of.Optional` is available to check if some value is a `Kind::Optional`.
862
+
863
+ [⬆️ Back to Top](#table-of-contents-)
864
+
865
+ ### Kind.of.\<Type\>.as_optional
866
+
867
+ It is very common the need to avoid some computing when a method receives a wrong input.
868
+ In these scenarios, you could check the given input type as optional and avoid unexpected behavior. e.g:
869
+
870
+ ```ruby
871
+ def person_name(params)
872
+ Kind::Of::Hash.as_optional(params)
873
+ .map { |data| data if data.values_at(:first_name, :last_name).compact.size == 2 }
874
+ .map { |data| "#{data[:first_name]} #{data[:last_name]}" }
875
+ .value_or { 'John Doe' }
876
+ end
877
+
878
+ person_name('') # "John Doe"
879
+ person_name(nil) # "John Doe"
880
+
881
+ person_name(first_name: 'Rodrigo') # "John Doe"
882
+ person_name(last_name: 'Serradura') # "John Doe"
883
+
884
+ person_name(first_name: 'Rodrigo', last_name: 'Serradura') # "Rodrigo Serradura"
885
+
886
+ #
887
+ # See below the previous implementation without using an optional.
888
+ #
889
+ def person_name(params)
890
+ if params.kind_of?(Hash) && params.values_at(:first_name, :last_name).compact.size == 2
891
+ "#{params[:first_name]} #{params[:last_name]}"
892
+ else
893
+ 'John Doe'
894
+ end
895
+ end
896
+ ```
897
+
898
+ > 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).
899
+
900
+ Let's see another example using a collection and how the method `.as_optional` works when it receives no argument.
901
+
902
+ ```ruby
903
+ collection = [ {number: 1}, 'number 0', {number: 2}, [0] ]
904
+
905
+ collection
906
+ .select(&Kind.of.Hash.as_optional)
907
+ .reduce(0) do |total, item|
908
+ item.try { |data| data[:number] + total } || total
909
+ end
910
+
911
+ collection
912
+ .map(&Kind.of.Hash.as_optional).select(&:some?)
913
+ .reduce(0) { |total, item| total + item.value[:number] }
914
+
915
+ # Note: All the examples above return 3 as the sum of all hashes with numbers.
916
+ ```
917
+
918
+ To finish follows an example of how to use optionals to handle arguments in coupled methods.
919
+
920
+ ```ruby
921
+ module PersonIntroduction
922
+ extend self
923
+
924
+ def call(params)
925
+ optional = Kind::Of::Hash.as_optional(params)
926
+
927
+ "Hi my name is #{full_name(optional)}, I'm #{age(optional)} years old."
928
+ end
929
+
930
+ private
931
+
932
+ def full_name(optional)
933
+ optional.map { |data| "#{data[:first_name]} #{data[:last_name]}" }
934
+ .value_or { 'John Doe' }
935
+ end
936
+
937
+ def age(optional)
938
+ optional.map { |data| data[:age] }.value_or(0)
939
+ end
940
+ end
941
+
942
+ #
943
+ # See below the previous implementation without using an optional.
944
+ #
945
+ module PersonIntroduction
946
+ extend self
947
+
948
+ def call(params)
949
+ "Hi my name is #{full_name(params)}, I'm #{age(params)} years old."
950
+ end
951
+
952
+ private
953
+
954
+ def full_name(params)
955
+ case params
956
+ when Hash then "#{params[:first_name]} #{params[:last_name]}"
957
+ else 'John Doe'
958
+ end
959
+ end
960
+
961
+ def age(params)
962
+ case params
963
+ when Hash then params.fetch(:age, 0)
964
+ else 0
965
+ end
966
+ end
967
+ end
968
+ ```
969
+
970
+ [⬆️ Back to Top](#table-of-contents-)
971
+
972
+ ### Kind::Maybe#try
973
+
974
+ 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.
975
+
976
+ ```ruby
977
+ object = 'foo'
978
+
979
+ Kind::Maybe[object].try(:upcase) # "FOO"
980
+
981
+ Kind::Maybe[{}].try(:fetch, :number, 0) # 0
982
+
983
+ Kind::Maybe[{number: 1}].try(:fetch, :number) # 1
984
+
985
+ Kind::Maybe[object].try { |value| value.upcase } # "FOO"
986
+
987
+ #############
988
+ # Nil value #
989
+ #############
990
+
991
+ object = nil
992
+
993
+ Kind::Maybe[object].try(:upcase) # nil
994
+
995
+ Kind::Maybe[object].try { |value| value.upcase } # nil
996
+
997
+ #########################
998
+ # Kind::Undefined value #
999
+ #########################
1000
+
1001
+ object = Kind::Undefined
1002
+
1003
+ Kind::Maybe[object].try(:upcase) # nil
1004
+
1005
+ Kind::Maybe[object].try { |value| value.upcase } # nil
1006
+ ```
1007
+
1008
+ > **Note:** You can use the try method with the `Kind::Optional`.
518
1009
 
519
1010
  [⬆️ Back to Top](#table-of-contents-)
520
1011
 
@@ -565,6 +1056,10 @@ Follows the list of constants, if the alias is available to be created:
565
1056
 
566
1057
  [⬆️ Back to Top](#table-of-contents-)
567
1058
 
1059
+ ## Similar Projects
1060
+
1061
+ - [dry-types](https://dry-rb.org/gems/dry-types)
1062
+
568
1063
  ## Development
569
1064
 
570
1065
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -575,7 +1070,6 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
575
1070
 
576
1071
  Bug reports and pull requests are welcome on GitHub at https://github.com/serradura/kind. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/serradura/kind/blob/master/CODE_OF_CONDUCT.md).
577
1072
 
578
-
579
1073
  ## License
580
1074
 
581
1075
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).