kind 1.7.0 → 2.2.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: 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).