bcdd-result 0.6.0 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +24 -11
- data/CHANGELOG.md +28 -0
- data/README.md +585 -160
- data/lib/bcdd/result/context/expectations/mixin.rb +35 -0
- data/lib/bcdd/result/context/expectations.rb +41 -0
- data/lib/bcdd/result/context/failure.rb +9 -0
- data/lib/bcdd/result/context/mixin.rb +38 -0
- data/lib/bcdd/result/context/success.rb +15 -0
- data/lib/bcdd/result/context.rb +74 -0
- data/lib/bcdd/result/{expectations/contract → contract}/disabled.rb +2 -2
- data/lib/bcdd/result/{expectations → contract}/error.rb +5 -3
- data/lib/bcdd/result/{expectations/contract → contract}/evaluator.rb +2 -2
- data/lib/bcdd/result/{expectations/contract → contract}/for_types.rb +2 -2
- data/lib/bcdd/result/{expectations/contract → contract}/for_types_and_values.rb +10 -8
- data/lib/bcdd/result/contract/interface.rb +21 -0
- data/lib/bcdd/result/{expectations → contract}/type_checker.rb +1 -1
- data/lib/bcdd/result/contract.rb +43 -0
- data/lib/bcdd/result/error.rb +7 -9
- data/lib/bcdd/result/expectations/mixin.rb +14 -9
- data/lib/bcdd/result/expectations.rb +28 -37
- data/lib/bcdd/result/failure/methods.rb +21 -0
- data/lib/bcdd/result/failure.rb +2 -16
- data/lib/bcdd/result/mixin.rb +8 -3
- data/lib/bcdd/result/success/methods.rb +21 -0
- data/lib/bcdd/result/success.rb +2 -16
- data/lib/bcdd/result/version.rb +1 -1
- data/lib/bcdd/result.rb +6 -4
- data/sig/bcdd/result.rbs +227 -83
- metadata +18 -10
- data/lib/bcdd/result/expectations/contract/interface.rb +0 -21
- data/lib/bcdd/result/expectations/contract.rb +0 -25
data/README.md
CHANGED
@@ -35,6 +35,9 @@ Use it to enable the [Railway Oriented Programming](https://fsharpforfunandprofi
|
|
35
35
|
- [`result.value_or`](#resultvalue_or)
|
36
36
|
- [Result Data](#result-data)
|
37
37
|
- [`result.data`](#resultdata)
|
38
|
+
- [Pattern Matching](#pattern-matching)
|
39
|
+
- [`Array`/`Find` patterns](#arrayfind-patterns)
|
40
|
+
- [`Hash` patterns](#hash-patterns)
|
38
41
|
- [Railway Oriented Programming](#railway-oriented-programming)
|
39
42
|
- [`result.and_then`](#resultand_then)
|
40
43
|
- [`BCDD::Result.mixin`](#bcddresultmixin)
|
@@ -42,12 +45,8 @@ Use it to enable the [Railway Oriented Programming](https://fsharpforfunandprofi
|
|
42
45
|
- [Module example (Singleton Methods)](#module-example-singleton-methods)
|
43
46
|
- [Important Requirement](#important-requirement)
|
44
47
|
- [Dependency Injection](#dependency-injection)
|
45
|
-
- [
|
46
|
-
- [
|
47
|
-
- [`Array`/`Find` patterns](#arrayfind-patterns)
|
48
|
-
- [`Hash` patterns](#hash-patterns)
|
49
|
-
- [BCDD::Result::Expectations](#bcddresultexpectations)
|
50
|
-
- [`BCDD::Result::Expectations`](#bcddresultexpectations-1)
|
48
|
+
- [Add-ons](#add-ons)
|
49
|
+
- [`BCDD::Result::Expectations`](#bcddresultexpectations)
|
51
50
|
- [Standalone *versus* Mixin mode](#standalone-versus-mixin-mode)
|
52
51
|
- [Type checking - Result Hooks](#type-checking---result-hooks)
|
53
52
|
- [`#success?` and `#failure?`](#success-and-failure)
|
@@ -60,7 +59,16 @@ Use it to enable the [Railway Oriented Programming](https://fsharpforfunandprofi
|
|
60
59
|
- [Value checking - Result Creation](#value-checking---result-creation)
|
61
60
|
- [Success()](#success)
|
62
61
|
- [Failure()](#failure)
|
63
|
-
|
62
|
+
- [Pattern Matching Support](#pattern-matching-support)
|
63
|
+
- [`BCDD::Result::Expectations.mixin` add-ons](#bcddresultexpectationsmixin-add-ons)
|
64
|
+
- [`BCDD::Result::Context`](#bcddresultcontext)
|
65
|
+
- [Defining successes and failures](#defining-successes-and-failures)
|
66
|
+
- [`BCDD::Result::Context.mixin`](#bcddresultcontextmixin)
|
67
|
+
- [Class example (Instance Methods)](#class-example-instance-methods-1)
|
68
|
+
- [`and_expose`](#and_expose)
|
69
|
+
- [Module example (Singleton Methods)](#module-example-singleton-methods-1)
|
70
|
+
- [`BCDD::Result::Context::Expectations`](#bcddresultcontextexpectations)
|
71
|
+
- [Mixin add-ons](#mixin-add-ons)
|
64
72
|
- [About](#about)
|
65
73
|
- [Development](#development)
|
66
74
|
- [Contributing](#contributing)
|
@@ -209,9 +217,8 @@ result.failure?(:error) # false
|
|
209
217
|
|
210
218
|
### Result Hooks
|
211
219
|
|
212
|
-
Result hooks are methods that allow you to
|
213
|
-
|
214
|
-
To exemplify the them, I will implement a method that knows how to divide two numbers.
|
220
|
+
Result hooks are methods that allow you to execute a block of code based on the type of result obtained.
|
221
|
+
To demonstrate their use, I will implement a function that can divide two numbers.
|
215
222
|
|
216
223
|
```ruby
|
217
224
|
def divide(arg1, arg2)
|
@@ -228,14 +235,15 @@ end
|
|
228
235
|
|
229
236
|
#### `result.on`
|
230
237
|
|
231
|
-
`BCDD::Result#on
|
238
|
+
When you use `BCDD::Result#on`, the block will be executed only when the type matches the result type.
|
232
239
|
|
233
|
-
|
240
|
+
However, even if the block is executed, the method will always return the result itself.
|
234
241
|
|
235
|
-
The result
|
242
|
+
The value of the result will be available as the first argument of the block.
|
236
243
|
|
237
244
|
```ruby
|
238
245
|
result = divide(nil, 2)
|
246
|
+
#<BCDD::Result::Failure type=:invalid_arg data='arg1 must be numeric'>
|
239
247
|
|
240
248
|
output =
|
241
249
|
result
|
@@ -268,21 +276,40 @@ result.object_id == output.object_id # true
|
|
268
276
|
|
269
277
|
`BCDD::Result#on_type` is an alias of `BCDD::Result#on`.
|
270
278
|
|
279
|
+
```ruby
|
280
|
+
result = divide(nil, 2)
|
281
|
+
#<BCDD::Result::Failure type=:invalid_arg data='arg1 must be numeric'>
|
282
|
+
|
283
|
+
output =
|
284
|
+
result
|
285
|
+
.on_type(:invalid_arg) { |msg| puts msg }
|
286
|
+
.on_type(:division_by_zero) { |msg| puts msg }
|
287
|
+
.on_type(:division_completed) { |number| puts number }
|
288
|
+
|
289
|
+
# The code above will print 'arg1 must be numeric' and return the result itself.
|
290
|
+
|
291
|
+
result.object_id == output.object_id # true
|
292
|
+
```
|
293
|
+
|
294
|
+
*PS: The `divide()` implementation is [here](#result-hooks).*
|
295
|
+
|
296
|
+
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
297
|
+
|
271
298
|
#### `result.on_success`
|
272
299
|
|
273
|
-
`BCDD::Result#on_success` is
|
300
|
+
The `BCDD::Result#on_success` method is quite similar to the `BCDD::Result#on` hook, but with a few key differences:
|
274
301
|
|
275
|
-
1.
|
276
|
-
2. If the type is
|
302
|
+
1. It will only execute the block of code if the result is a success.
|
303
|
+
2. If the type declaration is not included, the method will execute the block for any successful result, regardless of its type.
|
277
304
|
|
278
305
|
```ruby
|
279
|
-
# It
|
306
|
+
# It executes the block and return itself.
|
280
307
|
|
281
308
|
divide(4, 2).on_success { |number| puts number }
|
282
309
|
|
283
310
|
divide(4, 2).on_success(:division_completed) { |number| puts number }
|
284
311
|
|
285
|
-
# It doesn't
|
312
|
+
# It doesn't execute the block, but return itself.
|
286
313
|
|
287
314
|
divide(4, 4).on_success(:ok) { |value| puts value }
|
288
315
|
|
@@ -295,16 +322,19 @@ divide(4, 4).on_failure { |error| puts error }
|
|
295
322
|
|
296
323
|
#### `result.on_failure`
|
297
324
|
|
298
|
-
|
325
|
+
It is the opposite of `Result#on_success`:
|
326
|
+
|
327
|
+
1. It will only execute the block of code if the result is a failure.
|
328
|
+
2. If the type declaration is not included, the method will execute the block for any failed result, regardless of its type.
|
299
329
|
|
300
330
|
```ruby
|
301
|
-
# It
|
331
|
+
# It executes the block and return itself.
|
302
332
|
|
303
333
|
divide(nil, 2).on_failure { |error| puts error }
|
304
334
|
|
305
335
|
divide(4, 0).on_failure(:invalid_arg, :division_by_zero) { |error| puts error }
|
306
336
|
|
307
|
-
# It doesn't
|
337
|
+
# It doesn't execute the block, but return itself.
|
308
338
|
|
309
339
|
divide(4, 0).on_success { |number| puts number }
|
310
340
|
|
@@ -317,11 +347,11 @@ divide(4, 0).on_failure(:invalid_arg) { |error| puts error }
|
|
317
347
|
|
318
348
|
#### `result.on_unknown`
|
319
349
|
|
320
|
-
`BCDD::Result#on_unknown` will
|
350
|
+
`BCDD::Result#on_unknown` will execute the block when no other hook (`#on`, `#on_type`, `#on_failure`, `#on_success`) has been executed.
|
321
351
|
|
322
|
-
Regardless of the block being executed, the
|
352
|
+
Regardless of the block being executed, the method will always return the result itself.
|
323
353
|
|
324
|
-
The result
|
354
|
+
The value of the result will be available as the first argument of the block.
|
325
355
|
|
326
356
|
```ruby
|
327
357
|
divide(4, 2)
|
@@ -338,7 +368,7 @@ divide(4, 2)
|
|
338
368
|
|
339
369
|
#### `result.handle`
|
340
370
|
|
341
|
-
This method
|
371
|
+
This method lets you define blocks for each hook (type, failure, or success), but instead of returning itself, it will return the output of the first match/block execution.
|
342
372
|
|
343
373
|
```ruby
|
344
374
|
divide(4, 2).handle do |result|
|
@@ -379,8 +409,8 @@ end
|
|
379
409
|
|
380
410
|
**Notes:**
|
381
411
|
* You can define multiple types to be handled by the same hook/block
|
382
|
-
* If the type is missing, it will
|
383
|
-
* The `#type` and `#[]` handlers
|
412
|
+
* If the type is missing, it will execute the block for any success or failure handler.
|
413
|
+
* The `#type` and `#[]` handlers require at least one type/argument.
|
384
414
|
|
385
415
|
*PS: The `divide()` implementation is [here](#result-hooks).*
|
386
416
|
|
@@ -388,13 +418,13 @@ end
|
|
388
418
|
|
389
419
|
### Result Value
|
390
420
|
|
391
|
-
|
421
|
+
To access the result value, you can simply call `BCDD::Result#value`.
|
392
422
|
|
393
|
-
|
423
|
+
However, there may be instances where you need to retrieve the value of a successful result or a default value if the result is a failure. In such cases, you can make use of `BCDD::Result#value_or`.
|
394
424
|
|
395
425
|
#### `result.value_or`
|
396
426
|
|
397
|
-
`BCCD::Result#value_or` returns the value when the result is a success,
|
427
|
+
`BCCD::Result#value_or` returns the value when the result is a success. However, if it is a failure, the given block will be executed, and its outcome will be returned.
|
398
428
|
|
399
429
|
```ruby
|
400
430
|
def divide(arg1, arg2)
|
@@ -462,16 +492,75 @@ print_to_hash(**success_data) # [:success, :ok, 1]
|
|
462
492
|
|
463
493
|
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
464
494
|
|
465
|
-
###
|
495
|
+
### Pattern Matching
|
466
496
|
|
467
|
-
|
497
|
+
The `BCDD::Result` also provides support to pattern matching.
|
468
498
|
|
469
|
-
|
499
|
+
In the further examples, I will use the `Divide` lambda to exemplify its usage.
|
470
500
|
|
471
|
-
|
472
|
-
|
501
|
+
```ruby
|
502
|
+
Divide = lambda do |arg1, arg2|
|
503
|
+
arg1.is_a?(::Numeric) or return BCDD::Result::Failure(:invalid_arg, 'arg1 must be numeric')
|
504
|
+
arg2.is_a?(::Numeric) or return BCDD::Result::Failure(:invalid_arg, 'arg2 must be numeric')
|
505
|
+
|
506
|
+
return BCDD::Result::Failure(:division_by_zero, 'arg2 must not be zero') if arg2.zero?
|
507
|
+
|
508
|
+
BCDD::Result::Success(:division_completed, arg1 / arg2)
|
509
|
+
end
|
510
|
+
```
|
473
511
|
|
474
|
-
|
512
|
+
#### `Array`/`Find` patterns
|
513
|
+
|
514
|
+
```ruby
|
515
|
+
case Divide.call(4, 2)
|
516
|
+
in BCDD::Result::Failure[:invalid_arg, msg] then puts msg
|
517
|
+
in BCDD::Result::Failure[:division_by_zero, msg] then puts msg
|
518
|
+
in BCDD::Result::Success[:division_completed, value] then puts value
|
519
|
+
end
|
520
|
+
|
521
|
+
# The code above will print: 2
|
522
|
+
|
523
|
+
case Divide.call(4, 0)
|
524
|
+
in BCDD::Result::Failure[:invalid_arg, msg] then puts msg
|
525
|
+
in BCDD::Result::Failure[:division_by_zero, msg] then puts msg
|
526
|
+
in BCDD::Result::Success[:division_completed, value] then puts value
|
527
|
+
end
|
528
|
+
|
529
|
+
# The code above will print: arg2 must not be zero
|
530
|
+
```
|
531
|
+
|
532
|
+
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
533
|
+
|
534
|
+
#### `Hash` patterns
|
535
|
+
|
536
|
+
```ruby
|
537
|
+
case Divide.call(10, 2)
|
538
|
+
in { failure: { invalid_arg: msg } } then puts msg
|
539
|
+
in { failure: { division_by_zero: msg } } then puts msg
|
540
|
+
in { success: { division_completed: value } } then puts value
|
541
|
+
end
|
542
|
+
|
543
|
+
# The code above will print: 5
|
544
|
+
|
545
|
+
case Divide.call('10', 2)
|
546
|
+
in { failure: { invalid_arg: msg } } then puts msg
|
547
|
+
in { failure: { division_by_zero: msg } } then puts msg
|
548
|
+
in { success: { division_completed: value } } then puts value
|
549
|
+
end
|
550
|
+
|
551
|
+
# The code above will print: arg1 must be numeric
|
552
|
+
```
|
553
|
+
|
554
|
+
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
555
|
+
|
556
|
+
### Railway Oriented Programming
|
557
|
+
|
558
|
+
["Railway Oriented Programming (ROP)"](https://fsharpforfunandprofit.com/rop/) is a programming technique that involves linking blocks together to form a sequence of operations, also known as a pipeline.
|
559
|
+
If a failure occurs in any of the blocks, the pipeline is interrupted and subsequent blocks are skipped.
|
560
|
+
|
561
|
+
The ROP technique allows you to structure your code in a way that expresses your logic as a series of operations, with the added benefit of stopping the process at the first detection of failure.
|
562
|
+
|
563
|
+
If all blocks successfully execute, the final result of the pipeline will be a success.
|
475
564
|
|
476
565
|
#### `result.and_then`
|
477
566
|
|
@@ -526,9 +615,9 @@ Divide.call(2, 2)
|
|
526
615
|
|
527
616
|
#### `BCDD::Result.mixin`
|
528
617
|
|
529
|
-
This method
|
618
|
+
This method generates a module that can be included or extended by any object. It adds two methods to the target object: `Success()` and `Failure()`. The main difference between these methods and `BCDD::Result::Success()`/`BCDD::Result::Failure()` is that the former will utilize the target object (which has received the include/extend) as the result's subject.
|
530
619
|
|
531
|
-
|
620
|
+
As a result, you can utilize the `#and_then` method to invoke methods from the result's subject.
|
532
621
|
|
533
622
|
##### Class example (Instance Methods)
|
534
623
|
|
@@ -621,11 +710,64 @@ Divide.call(4, '2') #<BCDD::Result::Failure type=:invalid_arg value="arg2 must b
|
|
621
710
|
|
622
711
|
##### Important Requirement
|
623
712
|
|
624
|
-
|
713
|
+
To use the `#and_then` method to call methods, they must use `Success()` and `Failure()` to produce the results.
|
714
|
+
|
715
|
+
If you try to use `BCDD::Result::Subject()`/`BCDD::Result::Failure()`, or results from another `BCDD::Result.mixin` instance with `#and_then`, it will raise an error because the subjects will be different.
|
716
|
+
|
717
|
+
**Note:** You can still use the block syntax, but all the results must be produced by the subject's `Success()` and `Failure()` methods.
|
718
|
+
|
719
|
+
```ruby
|
720
|
+
module ValidateNonZero
|
721
|
+
extend self, BCDD::Result.mixin
|
625
722
|
|
626
|
-
|
723
|
+
def call(numbers)
|
724
|
+
return Success(:ok, numbers) unless numbers.last.zero?
|
627
725
|
|
628
|
-
|
726
|
+
Failure(:division_by_zero, 'arg2 must not be zero')
|
727
|
+
end
|
728
|
+
end
|
729
|
+
|
730
|
+
class Divide
|
731
|
+
include BCDD::Result.mixin
|
732
|
+
|
733
|
+
attr_reader :arg1, :arg2
|
734
|
+
|
735
|
+
def initialize(arg1, arg2)
|
736
|
+
@arg1 = arg1
|
737
|
+
@arg2 = arg2
|
738
|
+
end
|
739
|
+
|
740
|
+
def call
|
741
|
+
validate_numbers
|
742
|
+
.and_then(:validate_non_zero)
|
743
|
+
.and_then(:divide)
|
744
|
+
end
|
745
|
+
|
746
|
+
private
|
747
|
+
|
748
|
+
def validate_numbers
|
749
|
+
arg1.is_a?(::Numeric) or return BCDD::Result::Failure(:invalid_arg, 'arg1 must be numeric') # This will raise an error
|
750
|
+
arg2.is_a?(::Numeric) or return Failure(:invalid_arg, 'arg2 must be numeric')
|
751
|
+
|
752
|
+
BCDD::Result::Success(:ok, [arg1, arg2]) # This will raise an error
|
753
|
+
end
|
754
|
+
|
755
|
+
def validate_non_zero(numbers)
|
756
|
+
ValidateNonZero.call(numbers) # This will raise an error
|
757
|
+
|
758
|
+
# This would work:
|
759
|
+
# In this case we are handling the other subject result and returning our own
|
760
|
+
# ValidateNonZero.call(numbers).handle do |on|
|
761
|
+
# on.success { |numbers| Success(:ok, numbers) }
|
762
|
+
# on.failure { |err| Failure(:division_by_zero, err) }
|
763
|
+
# end
|
764
|
+
end
|
765
|
+
|
766
|
+
def divide((number1, number2))
|
767
|
+
Success(:division_completed, number1 / number2)
|
768
|
+
end
|
769
|
+
end
|
770
|
+
```
|
629
771
|
|
630
772
|
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
631
773
|
|
@@ -686,7 +828,7 @@ Divide.call(4, 2, logger: Logger.new(IO::NULL))
|
|
686
828
|
|
687
829
|
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
688
830
|
|
689
|
-
#####
|
831
|
+
##### Add-ons
|
690
832
|
|
691
833
|
The `BCDD::Result.mixin` also accepts the `with:` argument. It is a hash that will be used to define the methods that will be added to the target object.
|
692
834
|
|
@@ -727,96 +869,6 @@ end
|
|
727
869
|
|
728
870
|
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
729
871
|
|
730
|
-
### Pattern Matching
|
731
|
-
|
732
|
-
The `BCDD::Result` also provides support to pattern matching.
|
733
|
-
|
734
|
-
In the further examples, I will use the `Divide` lambda to exemplify its usage.
|
735
|
-
|
736
|
-
```ruby
|
737
|
-
Divide = lambda do |arg1, arg2|
|
738
|
-
arg1.is_a?(::Numeric) or return BCDD::Result::Failure(:invalid_arg, 'arg1 must be numeric')
|
739
|
-
arg2.is_a?(::Numeric) or return BCDD::Result::Failure(:invalid_arg, 'arg2 must be numeric')
|
740
|
-
|
741
|
-
return BCDD::Result::Failure(:division_by_zero, 'arg2 must not be zero') if arg2.zero?
|
742
|
-
|
743
|
-
BCDD::Result::Success(:division_completed, arg1 / arg2)
|
744
|
-
end
|
745
|
-
```
|
746
|
-
|
747
|
-
#### `Array`/`Find` patterns
|
748
|
-
|
749
|
-
```ruby
|
750
|
-
case Divide.call(4, 2)
|
751
|
-
in BCDD::Result::Failure[:invalid_arg, msg] then puts msg
|
752
|
-
in BCDD::Result::Failure[:division_by_zero, msg] then puts msg
|
753
|
-
in BCDD::Result::Success[:division_completed, value] then puts value
|
754
|
-
end
|
755
|
-
|
756
|
-
# The code above will print: 2
|
757
|
-
|
758
|
-
case Divide.call(4, 0)
|
759
|
-
in BCDD::Result::Failure[:invalid_arg, msg] then puts msg
|
760
|
-
in BCDD::Result::Failure[:division_by_zero, msg] then puts msg
|
761
|
-
in BCDD::Result::Success[:division_completed, value] then puts value
|
762
|
-
end
|
763
|
-
|
764
|
-
# The code above will print: arg2 must not be zero
|
765
|
-
```
|
766
|
-
|
767
|
-
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
768
|
-
|
769
|
-
#### `Hash` patterns
|
770
|
-
|
771
|
-
```ruby
|
772
|
-
case Divide.call(10, 2)
|
773
|
-
in { failure: { invalid_arg: msg } } then puts msg
|
774
|
-
in { failure: { division_by_zero: msg } } then puts msg
|
775
|
-
in { success: { division_completed: value } } then puts value
|
776
|
-
end
|
777
|
-
|
778
|
-
# The code above will print: 5
|
779
|
-
|
780
|
-
case Divide.call('10', 2)
|
781
|
-
in { failure: { invalid_arg: msg } } then puts msg
|
782
|
-
in { failure: { division_by_zero: msg } } then puts msg
|
783
|
-
in { success: { division_completed: value } } then puts value
|
784
|
-
end
|
785
|
-
|
786
|
-
# The code above will print: arg1 must be numeric
|
787
|
-
```
|
788
|
-
|
789
|
-
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
790
|
-
|
791
|
-
#### BCDD::Result::Expectations
|
792
|
-
|
793
|
-
I'd like you to please read the following section to understand how to use this feature.
|
794
|
-
|
795
|
-
But if you are using Ruby >= 3.0, you can use the `in` operator to use the pattern matching to validate the result's value.
|
796
|
-
|
797
|
-
```ruby
|
798
|
-
module Divide
|
799
|
-
extend BCDD::Result::Expectations.mixin(
|
800
|
-
success: {
|
801
|
-
numbers: ->(value) { value in [Numeric, Numeric] },
|
802
|
-
division_completed: ->(value) { value in (Integer | Float) }
|
803
|
-
},
|
804
|
-
failure: { invalid_arg: String, division_by_zero: String }
|
805
|
-
)
|
806
|
-
|
807
|
-
def self.call(arg1, arg2)
|
808
|
-
arg1.is_a?(Numeric) or return Failure(:invalid_arg, 'arg1 must be numeric')
|
809
|
-
arg2.is_a?(Numeric) or return Failure(:invalid_arg, 'arg2 must be numeric')
|
810
|
-
|
811
|
-
arg2.zero? and return Failure(:division_by_zero, 'arg2 must not be zero')
|
812
|
-
|
813
|
-
Success(:division_completed, arg1 / arg2)
|
814
|
-
end
|
815
|
-
end
|
816
|
-
```
|
817
|
-
|
818
|
-
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
819
|
-
|
820
872
|
### `BCDD::Result::Expectations`
|
821
873
|
|
822
874
|
This feature lets you define contracts for your results' types and values. There are two ways to use it: the standalone (`BCDD::Result::Expectations.new`) and the mixin (`BCDD::Result::Expectations.mixin`) mode.
|
@@ -829,18 +881,18 @@ The _**standalone mode**_ creates an object that knows how to produce and valida
|
|
829
881
|
|
830
882
|
```ruby
|
831
883
|
module Divide
|
832
|
-
|
884
|
+
Result = BCDD::Result::Expectations.new(
|
833
885
|
success: %i[numbers division_completed],
|
834
886
|
failure: %i[invalid_arg division_by_zero]
|
835
887
|
)
|
836
888
|
|
837
889
|
def self.call(arg1, arg2)
|
838
|
-
arg1.is_a?(Numeric) or return
|
839
|
-
arg2.is_a?(Numeric) or return
|
890
|
+
arg1.is_a?(Numeric) or return Result::Failure(:invalid_arg, 'arg1 must be numeric')
|
891
|
+
arg2.is_a?(Numeric) or return Result::Failure(:invalid_arg, 'arg2 must be numeric')
|
840
892
|
|
841
|
-
arg2.zero? and return
|
893
|
+
arg2.zero? and return Result::Failure(:division_by_zero, 'arg2 must not be zero')
|
842
894
|
|
843
|
-
|
895
|
+
Result::Success(:division_completed, arg1 / arg2)
|
844
896
|
end
|
845
897
|
end
|
846
898
|
```
|
@@ -850,11 +902,11 @@ In the code above, we define a constant `Divide::Expected`. And because of this
|
|
850
902
|
Look what happens if you try to create a result without one of the expected types.
|
851
903
|
|
852
904
|
```ruby
|
853
|
-
Divide::
|
905
|
+
Divide::Result::Success(:ok)
|
854
906
|
# type :ok is not allowed. Allowed types: :numbers, :division_completed
|
855
907
|
# (BCDD::Result::Expectations::Error::UnexpectedType)
|
856
908
|
|
857
|
-
Divide::
|
909
|
+
Divide::Result::Failure(:err)
|
858
910
|
# type :err is not allowed. Allowed types: :invalid_arg, :division_by_zero
|
859
911
|
# (BCDD::Result::Expectations::Error::UnexpectedType)
|
860
912
|
```
|
@@ -906,11 +958,11 @@ Now that you know the two modes, let's understand how expectations can be benefi
|
|
906
958
|
|
907
959
|
#### Type checking - Result Hooks
|
908
960
|
|
909
|
-
The `BCDD::Result::Expectations` will check if the result
|
961
|
+
The `BCDD::Result::Expectations` will check if the type of the result is valid. This checking will be performed in all methods that depend on the result’s type, such as `#success?`, `#failure?`, `#on`, `#on_type`, `#on_success`, `#on_failure`, and `#handle`.
|
910
962
|
|
911
963
|
##### `#success?` and `#failure?`
|
912
964
|
|
913
|
-
When
|
965
|
+
When checking whether a result is a success or failure, `BCDD::Result::Expectations` will also verify if the result type is valid/expected. In case of an invalid type, an error will be raised.
|
914
966
|
|
915
967
|
**Success example:**
|
916
968
|
|
@@ -946,7 +998,7 @@ result.failure?(:err)
|
|
946
998
|
|
947
999
|
##### `#on` and `#on_type`
|
948
1000
|
|
949
|
-
If you use `#on` or `#on_type` to
|
1001
|
+
If you use `#on` or `#on_type` to execute a block, `BCDD::Result::Expectations` will check whether the result type is valid/expected. Otherwise, an error will be raised.
|
950
1002
|
|
951
1003
|
```ruby
|
952
1004
|
result = Divide.new.call(10, 2)
|
@@ -968,7 +1020,7 @@ result.on(:number) { |_| :this_type_does_not_exist }
|
|
968
1020
|
|
969
1021
|
##### `#on_success` and `#on_failure`
|
970
1022
|
|
971
|
-
If you use `#on_success` or `#on_failure` to
|
1023
|
+
If you use `#on_success` or `#on_failure` to execute a block, `BCDD::Result::Expectations` will check whether the result type is valid/expected. Otherwise, an error will be raised.
|
972
1024
|
|
973
1025
|
```ruby
|
974
1026
|
result = Divide.new.call(10, '2')
|
@@ -1060,15 +1112,15 @@ Divide.call(4, 2)
|
|
1060
1112
|
|
1061
1113
|
```ruby
|
1062
1114
|
module Divide
|
1063
|
-
|
1115
|
+
Result = BCDD::Result::Expectations.new(success: :ok, failure: :err)
|
1064
1116
|
|
1065
1117
|
def self.call(arg1, arg2)
|
1066
|
-
arg1.is_a?(Numeric) or return
|
1067
|
-
arg2.is_a?(Numeric) or return
|
1118
|
+
arg1.is_a?(Numeric) or return Result::Failure(:invalid_arg, 'arg1 must be numeric')
|
1119
|
+
arg2.is_a?(Numeric) or return Result::Failure(:invalid_arg, 'arg2 must be numeric')
|
1068
1120
|
|
1069
|
-
arg2.zero? and return
|
1121
|
+
arg2.zero? and return Result::Failure(:division_by_zero, 'arg2 must not be zero')
|
1070
1122
|
|
1071
|
-
|
1123
|
+
Result::Success(:division_completed, arg1 / arg2)
|
1072
1124
|
end
|
1073
1125
|
end
|
1074
1126
|
|
@@ -1119,7 +1171,7 @@ end
|
|
1119
1171
|
|
1120
1172
|
```ruby
|
1121
1173
|
module Divide
|
1122
|
-
|
1174
|
+
Result = BCDD::Result::Expectations.new(
|
1123
1175
|
success: {
|
1124
1176
|
numbers: ->(value) { value.is_a?(Array) && value.size == 2 && value.all?(Numeric) },
|
1125
1177
|
division_completed: Numeric
|
@@ -1131,12 +1183,12 @@ module Divide
|
|
1131
1183
|
)
|
1132
1184
|
|
1133
1185
|
def self.call(arg1, arg2)
|
1134
|
-
arg1.is_a?(Numeric) or return
|
1135
|
-
arg2.is_a?(Numeric) or return
|
1186
|
+
arg1.is_a?(Numeric) or return Result::Failure(:invalid_arg, 'arg1 must be numeric')
|
1187
|
+
arg2.is_a?(Numeric) or return Result::Failure(:invalid_arg, 'arg2 must be numeric')
|
1136
1188
|
|
1137
|
-
arg2.zero? and return
|
1189
|
+
arg2.zero? and return Result::Failure(:division_by_zero, 'arg2 must not be zero')
|
1138
1190
|
|
1139
|
-
|
1191
|
+
Result::Success(:division_completed, arg1 / arg2)
|
1140
1192
|
end
|
1141
1193
|
end
|
1142
1194
|
```
|
@@ -1148,32 +1200,75 @@ The value validation will only be performed through the methods `Success()` and
|
|
1148
1200
|
##### Success()
|
1149
1201
|
|
1150
1202
|
```ruby
|
1151
|
-
Divide::
|
1203
|
+
Divide::Result::Success(:ok)
|
1152
1204
|
# type :ok is not allowed. Allowed types: :numbers, :division_completed (BCDD::Result::Expectations::Error::UnexpectedType)
|
1153
1205
|
|
1154
|
-
Divide::
|
1206
|
+
Divide::Result::Success(:numbers, [1])
|
1155
1207
|
# value [1] is not allowed for :numbers type (BCDD::Result::Expectations::Error::UnexpectedValue)
|
1156
1208
|
|
1157
|
-
Divide::
|
1209
|
+
Divide::Result::Success(:division_completed, '2')
|
1158
1210
|
# value "2" is not allowed for :division_completed type (BCDD::Result::Expectations::Error::UnexpectedValue)
|
1159
1211
|
```
|
1160
1212
|
|
1161
1213
|
##### Failure()
|
1162
1214
|
|
1163
1215
|
```ruby
|
1164
|
-
Divide::
|
1216
|
+
Divide::Result::Failure(:err)
|
1165
1217
|
# type :err is not allowed. Allowed types: :invalid_arg, :division_by_zero (BCDD::Result::Expectations::Error::UnexpectedType)
|
1166
1218
|
|
1167
|
-
Divide::
|
1219
|
+
Divide::Result::Failure(:invalid_arg, :arg1_must_be_numeric)
|
1168
1220
|
# value :arg1_must_be_numeric is not allowed for :invalid_arg type (BCDD::Result::Expectations::Error::UnexpectedValue)
|
1169
1221
|
|
1170
|
-
Divide::
|
1222
|
+
Divide::Result::Failure(:division_by_zero, msg: 'arg2 must not be zero')
|
1171
1223
|
# value {:msg=>"arg2 must not be zero"} is not allowed for :division_by_zero type (BCDD::Result::Expectations::Error::UnexpectedValue)
|
1172
1224
|
```
|
1173
1225
|
|
1174
1226
|
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
1175
1227
|
|
1176
|
-
|
1228
|
+
##### Pattern Matching Support
|
1229
|
+
|
1230
|
+
The value checking has support for handling pattern-matching errors, and the cleanest way to do it is using the one-line pattern matching operators (`=>` since Ruby 3.0) and (`in` Ruby 2.7).
|
1231
|
+
|
1232
|
+
How does this operator work? They raise an error when the pattern does not match but returns nil when it matches.
|
1233
|
+
|
1234
|
+
Because of this, you will need to enable `nil` as a valid value checking. You can do it by calling the `BCDD::Result::Contract.nil_as_valid_value_checking!` method.
|
1235
|
+
|
1236
|
+
**Attention:**
|
1237
|
+
|
1238
|
+
If you decide to enable this, you will do it at the beginning of your code or in an initializer. And remember, this will affect all kinds of result expectations (`BCDD::Result::Expectations` and `BCDD::Result::Context::Expectations`). So, it is recommended to use it only when you are using pattern matching for **ALL** the result's value validations.
|
1239
|
+
|
1240
|
+
```ruby
|
1241
|
+
#
|
1242
|
+
# Put this line in an initializer or at the beginning of your code.
|
1243
|
+
# It is required if you decide to use pattern matching to validate all of your result's values.
|
1244
|
+
#
|
1245
|
+
BCDD::Result::Contract.nil_as_valid_value_checking!
|
1246
|
+
|
1247
|
+
module Divide
|
1248
|
+
extend BCDD::Result::Expectations.mixin(
|
1249
|
+
success: {
|
1250
|
+
division_completed: ->(value) { value => (Integer | Float) }
|
1251
|
+
},
|
1252
|
+
failure: { invalid_arg: String, division_by_zero: String }
|
1253
|
+
)
|
1254
|
+
|
1255
|
+
def self.call(arg1, arg2)
|
1256
|
+
arg1.is_a?(Numeric) or return Failure(:invalid_arg, 'arg1 must be numeric')
|
1257
|
+
arg2.is_a?(Numeric) or return Failure(:invalid_arg, 'arg2 must be numeric')
|
1258
|
+
|
1259
|
+
arg2.zero? and return Failure(:division_by_zero, 'arg2 must not be zero')
|
1260
|
+
|
1261
|
+
Success(:division_completed, String(arg1 / arg2))
|
1262
|
+
end
|
1263
|
+
end
|
1264
|
+
|
1265
|
+
Divide.call(10, 5)
|
1266
|
+
# value "5" is not allowed for :division_completed type ("5": Float === "5" does not return true) (BCDD::Result::Contract::Error::UnexpectedValue)
|
1267
|
+
```
|
1268
|
+
|
1269
|
+
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
1270
|
+
|
1271
|
+
#### `BCDD::Result::Expectations.mixin` add-ons
|
1177
1272
|
|
1178
1273
|
The `BCDD::Result::Expectations.mixin` also accepts the `with:` argument. It is a hash that will be used to define the methods that will be added to the target object.
|
1179
1274
|
|
@@ -1228,6 +1323,336 @@ result.success?(:ok)
|
|
1228
1323
|
|
1229
1324
|
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
1230
1325
|
|
1326
|
+
### `BCDD::Result::Context`
|
1327
|
+
|
1328
|
+
The `BCDD::Result::Context` is a `BCDD::Result`, meaning it has all the features of the `BCDD::Result`. The main difference is that it only accepts keyword arguments as a value, which applies to the `and_then`: The called methods must receive keyword arguments, and the dependency injection will be performed through keyword arguments.
|
1329
|
+
|
1330
|
+
As the input/output are hashes, the results of each `and_then` call will automatically accumulate. This is useful in operations chaining, as the result of the previous operations will be automatically available for the next one. Because of this behavior, the `BCDD::Result::Context` has the `#and_expose` method to expose only the desired keys from the accumulated result.
|
1331
|
+
|
1332
|
+
#### Defining successes and failures
|
1333
|
+
|
1334
|
+
As the `BCDD::Result`, you can declare success and failures directly from `BCDD::Result::Context`.
|
1335
|
+
|
1336
|
+
```ruby
|
1337
|
+
BCDD::Result::Context::Success(:ok, a: 1, b: 2)
|
1338
|
+
#<BCDD::Result::Context::Success type=:ok value={:a=>1, :b=>2}>
|
1339
|
+
|
1340
|
+
BCDD::Result::Context::Failure(:err, message: 'something went wrong')
|
1341
|
+
#<BCDD::Result::Context::Failure type=:err value={:message=>"something went wrong"}>
|
1342
|
+
```
|
1343
|
+
|
1344
|
+
But different from `BCDD::Result` that accepts any value, the `BCDD::Result::Context` only takes keyword arguments.
|
1345
|
+
|
1346
|
+
```ruby
|
1347
|
+
BCDD::Result::Context::Success(:ok, [1, 2])
|
1348
|
+
# wrong number of arguments (given 2, expected 1) (ArgumentError)
|
1349
|
+
|
1350
|
+
BCDD::Result::Context::Failure(:err, { message: 'something went wrong' })
|
1351
|
+
# wrong number of arguments (given 2, expected 1) (ArgumentError)
|
1352
|
+
|
1353
|
+
#
|
1354
|
+
# Use ** to convert a hash to keyword arguments
|
1355
|
+
#
|
1356
|
+
BCDD::Result::Context::Success(:ok, **{ message: 'hashes can be converted to keyword arguments' })
|
1357
|
+
#<BCDD::Result::Context::Success type=:ok value={:message=>"hashes can be converted to keyword arguments"}>
|
1358
|
+
```
|
1359
|
+
|
1360
|
+
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
1361
|
+
|
1362
|
+
#### `BCDD::Result::Context.mixin`
|
1363
|
+
|
1364
|
+
As in the `BCDD::Result`, you can use the `BCDD::Result::Context.mixin` to add the `Success()` and `Failure()` methods to your classes/modules.
|
1365
|
+
|
1366
|
+
Let's see this feature and the data accumulation in action:
|
1367
|
+
|
1368
|
+
##### Class example (Instance Methods)
|
1369
|
+
|
1370
|
+
```ruby
|
1371
|
+
class Divide
|
1372
|
+
require 'logger'
|
1373
|
+
|
1374
|
+
include BCDD::Result::Context.mixin
|
1375
|
+
|
1376
|
+
def call(arg1, arg2, logger: ::Logger.new(STDOUT))
|
1377
|
+
validate_numbers(arg1, arg2)
|
1378
|
+
.and_then(:validate_non_zero)
|
1379
|
+
.and_then(:divide, logger: logger)
|
1380
|
+
end
|
1381
|
+
|
1382
|
+
private
|
1383
|
+
|
1384
|
+
def validate_numbers(arg1, arg2)
|
1385
|
+
arg1.is_a?(::Numeric) or return Failure(:err, message: 'arg1 must be numeric')
|
1386
|
+
arg2.is_a?(::Numeric) or return Failure(:err, message: 'arg2 must be numeric')
|
1387
|
+
|
1388
|
+
Success(:ok, number1: arg1, number2: arg2)
|
1389
|
+
end
|
1390
|
+
|
1391
|
+
def validate_non_zero(number2:, **)
|
1392
|
+
return Success(:ok) if number2.nonzero?
|
1393
|
+
|
1394
|
+
Failure(:err, message: 'arg2 must not be zero')
|
1395
|
+
end
|
1396
|
+
|
1397
|
+
#
|
1398
|
+
# The logger was injected via #and_then and keyword arguments
|
1399
|
+
#
|
1400
|
+
def divide(number1:, number2:, logger:)
|
1401
|
+
result = number1 / number2
|
1402
|
+
|
1403
|
+
logger.info("The division result is #{result}")
|
1404
|
+
|
1405
|
+
Success(:ok, number: result)
|
1406
|
+
end
|
1407
|
+
end
|
1408
|
+
|
1409
|
+
Divide.new.call(10, 5)
|
1410
|
+
# I, [2023-10-27T01:51:46.905004 #76915] INFO -- : The division result is 2
|
1411
|
+
#<BCDD::Result::Context::Success type=:ok value={:number=>2}>
|
1412
|
+
|
1413
|
+
Divide.new.call('10', 5)
|
1414
|
+
#<BCDD::Result::Context::Failure type=:err value={:message=>"arg1 must be numeric"}>
|
1415
|
+
|
1416
|
+
Divide.new.call(10, '5')
|
1417
|
+
#<BCDD::Result::Context::Failure type=:err value={:message=>"arg2 must be numeric"}>
|
1418
|
+
|
1419
|
+
Divide.new.call(10, 0)
|
1420
|
+
#<BCDD::Result::Context::Failure type=:err value={:message=>"arg2 must not be zero"}>
|
1421
|
+
```
|
1422
|
+
|
1423
|
+
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
1424
|
+
|
1425
|
+
##### `and_expose`
|
1426
|
+
|
1427
|
+
This allows you to expose only the desired keys from the accumulated result. It can be used with any `BCDD::Result::Context` object.
|
1428
|
+
|
1429
|
+
Let's add it to the previous example:
|
1430
|
+
|
1431
|
+
```ruby
|
1432
|
+
class Divide
|
1433
|
+
include BCDD::Result::Context.mixin
|
1434
|
+
|
1435
|
+
def call(arg1, arg2)
|
1436
|
+
validate_numbers(arg1, arg2)
|
1437
|
+
.and_then(:validate_non_zero)
|
1438
|
+
.and_then(:divide)
|
1439
|
+
end
|
1440
|
+
|
1441
|
+
private
|
1442
|
+
|
1443
|
+
def validate_numbers(arg1, arg2)
|
1444
|
+
arg1.is_a?(::Numeric) or return Failure(:err, message: 'arg1 must be numeric')
|
1445
|
+
arg2.is_a?(::Numeric) or return Failure(:err, message: 'arg2 must be numeric')
|
1446
|
+
|
1447
|
+
Success(:ok, number1: arg1, number2: arg2)
|
1448
|
+
end
|
1449
|
+
|
1450
|
+
def validate_non_zero(number2:, **)
|
1451
|
+
return Success(:ok) if number2.nonzero?
|
1452
|
+
|
1453
|
+
Failure(:err, message: 'arg2 must not be zero')
|
1454
|
+
end
|
1455
|
+
|
1456
|
+
def divide(**input)
|
1457
|
+
Success(:ok, number: input.values.reduce(:/), **input)
|
1458
|
+
end
|
1459
|
+
end
|
1460
|
+
|
1461
|
+
Divide.new.call(10, 5)
|
1462
|
+
#<BCDD::Result::Context::Success type=:division_completed value={:number=>2}>
|
1463
|
+
```
|
1464
|
+
|
1465
|
+
As you can see, even with `divide` success exposing the division number with all the accumulated data (`**input`), the `#and_expose` could generate a new success with a new type and only with the desired keys.
|
1466
|
+
|
1467
|
+
Remove the `#and_expose` call to see the difference. This will be the outcome:
|
1468
|
+
|
1469
|
+
```ruby
|
1470
|
+
Divide.new.call(10, 5)
|
1471
|
+
#<BCDD::Result::Context::Success type=:ok value={:number=>2, :number1=>10, :number2=>5}>
|
1472
|
+
```
|
1473
|
+
|
1474
|
+
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
1475
|
+
|
1476
|
+
##### Module example (Singleton Methods)
|
1477
|
+
|
1478
|
+
Yes, the `BCDD::Result::Context.mixin` can produce singleton methods. Look for an example using a module (but it could be a class, too).
|
1479
|
+
|
1480
|
+
```ruby
|
1481
|
+
module Divide
|
1482
|
+
extend self, BCDD::Result::Context.mixin
|
1483
|
+
|
1484
|
+
def call(arg1, arg2)
|
1485
|
+
validate_numbers(arg1, arg2)
|
1486
|
+
.and_then(:validate_non_zero)
|
1487
|
+
.and_then(:divide)
|
1488
|
+
.and_expose(:division_completed, [:number])
|
1489
|
+
end
|
1490
|
+
|
1491
|
+
private
|
1492
|
+
|
1493
|
+
def validate_numbers(arg1, arg2)
|
1494
|
+
arg1.is_a?(::Numeric) or return Failure(:err, message: 'arg1 must be numeric')
|
1495
|
+
arg2.is_a?(::Numeric) or return Failure(:err, message: 'arg2 must be numeric')
|
1496
|
+
|
1497
|
+
Success(:ok, number1: arg1, number2: arg2)
|
1498
|
+
end
|
1499
|
+
|
1500
|
+
def validate_non_zero(number2:, **)
|
1501
|
+
return Success(:ok) if number2.nonzero?
|
1502
|
+
|
1503
|
+
Failure(:err, message: 'arg2 must not be zero')
|
1504
|
+
end
|
1505
|
+
|
1506
|
+
def divide(number1:, number2:)
|
1507
|
+
Success(:ok, number: number1 / number2)
|
1508
|
+
end
|
1509
|
+
end
|
1510
|
+
|
1511
|
+
Divide.call(10, 5)
|
1512
|
+
#<BCDD::Result::Context::Success type=:division_completed value={:number=>2}>
|
1513
|
+
|
1514
|
+
Divide.call('10', 5)
|
1515
|
+
#<BCDD::Result::Context::Failure type=:err value={:message=>"arg1 must be numeric"}>
|
1516
|
+
|
1517
|
+
Divide.call(10, '5')
|
1518
|
+
#<BCDD::Result::Context::Failure type=:err value={:message=>"arg2 must be numeric"}>
|
1519
|
+
|
1520
|
+
Divide.call(10, 0)
|
1521
|
+
#<BCDD::Result::Context::Failure type=:err value={:message=>"arg2 must not be zero"}>
|
1522
|
+
```
|
1523
|
+
|
1524
|
+
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
1525
|
+
|
1526
|
+
#### `BCDD::Result::Context::Expectations`
|
1527
|
+
|
1528
|
+
The `BCDD::Result::Context::Expectations` is a `BCDD::Result::Expectations` with the `BCDD::Result::Context` features.
|
1529
|
+
|
1530
|
+
This is an example using the mixin mode, but the standalone mode is also supported.
|
1531
|
+
|
1532
|
+
```ruby
|
1533
|
+
#
|
1534
|
+
# Put this line in an initializer or at the beginning of your code.
|
1535
|
+
# It is required if you decide to use pattern matching to validate all of your result's values.
|
1536
|
+
#
|
1537
|
+
BCDD::Result::Contract.nil_as_valid_value_checking!
|
1538
|
+
|
1539
|
+
class Divide
|
1540
|
+
include BCDD::Result::Context::Expectations.mixin(
|
1541
|
+
success: {
|
1542
|
+
division_completed: ->(value) { value => { number: Numeric } }
|
1543
|
+
},
|
1544
|
+
failure: {
|
1545
|
+
invalid_arg: ->(value) { value => { message: String } },
|
1546
|
+
division_by_zero: ->(value) { value => { message: String } }
|
1547
|
+
}
|
1548
|
+
)
|
1549
|
+
|
1550
|
+
def call(arg1, arg2)
|
1551
|
+
arg1.is_a?(Numeric) or return Failure(:invalid_arg, message: 'arg1 must be numeric')
|
1552
|
+
arg2.is_a?(Numeric) or return Failure(:invalid_arg, message: 'arg2 must be numeric')
|
1553
|
+
|
1554
|
+
arg2.zero? and return Failure(:division_by_zero, message: 'arg2 must not be zero')
|
1555
|
+
|
1556
|
+
Success(:division_completed, number: arg1 / arg2)
|
1557
|
+
end
|
1558
|
+
end
|
1559
|
+
|
1560
|
+
Divide.new.call(10, 5)
|
1561
|
+
#<BCDD::Result::Context::Success type=:division_completed value={:number=>2}>
|
1562
|
+
```
|
1563
|
+
|
1564
|
+
As in the `BCDD::Result::Expectations.mixin`, the `BCDD::Result::Context::Expectations.mixin` will add a Result constant in the target class. It can generate success/failure results, which ensure the mixin expectations.
|
1565
|
+
|
1566
|
+
Let's see this using previous example:
|
1567
|
+
|
1568
|
+
```ruby
|
1569
|
+
Divide::Result::Success(:division_completed, number: 2)
|
1570
|
+
#<BCDD::Result::Context::Success type=:division_completed value={:number=>2}>
|
1571
|
+
|
1572
|
+
Divide::Result::Success(:division_completed, number: '2')
|
1573
|
+
# value {:number=>"2"} is not allowed for :division_completed type ({:number=>"2"}: Numeric === "2" does not return true) (BCDD::Result::Contract::Error::UnexpectedValue)
|
1574
|
+
```
|
1575
|
+
|
1576
|
+
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
1577
|
+
|
1578
|
+
#### Mixin add-ons
|
1579
|
+
|
1580
|
+
The `BCDD::Result::Context.mixin` and `BCDD::Result::Context::Expectations.mixin` also accepts the `with:` argument. And it works the same way as the `BCDD::Result` mixins.
|
1581
|
+
|
1582
|
+
**Continue**
|
1583
|
+
|
1584
|
+
The `BCDD::Result::Context.mixin(with: :Continue)` or `BCDD::Result::Context::Expectations.mixin(with: :Continue)` adds a `Continue(**input)` that will be ignored by the expectations. This is extremely useful when you want to use `Continue()` to chain operations, but you don't want to declare N success types in the expectations.
|
1585
|
+
|
1586
|
+
Let's use a mix of `BCDD::Result::Context` features to see in action with this add-on:
|
1587
|
+
|
1588
|
+
```ruby
|
1589
|
+
#
|
1590
|
+
# Put this line in an initializer or at the beginning of your code.
|
1591
|
+
# It is required if you decide to use pattern matching to validate all of your result's values.
|
1592
|
+
#
|
1593
|
+
BCDD::Result::Contract.nil_as_valid_value_checking!
|
1594
|
+
|
1595
|
+
module Divide
|
1596
|
+
require 'logger'
|
1597
|
+
|
1598
|
+
extend self, BCDD::Result::Context::Expectations.mixin(
|
1599
|
+
with: :Continue,
|
1600
|
+
success: {
|
1601
|
+
division_completed: ->(value) { value => { number: Numeric } }
|
1602
|
+
},
|
1603
|
+
failure: {
|
1604
|
+
invalid_arg: ->(value) { value => { message: String } },
|
1605
|
+
division_by_zero: ->(value) { value => { message: String } }
|
1606
|
+
}
|
1607
|
+
)
|
1608
|
+
|
1609
|
+
def call(arg1, arg2, logger: ::Logger.new(STDOUT))
|
1610
|
+
validate_numbers(arg1, arg2)
|
1611
|
+
.and_then(:validate_non_zero)
|
1612
|
+
.and_then(:divide, logger: logger)
|
1613
|
+
.and_expose(:division_completed, [:number])
|
1614
|
+
end
|
1615
|
+
|
1616
|
+
private
|
1617
|
+
|
1618
|
+
def validate_numbers(arg1, arg2)
|
1619
|
+
arg1.is_a?(::Numeric) or return Failure(:invalid_arg, message: 'arg1 must be numeric')
|
1620
|
+
arg2.is_a?(::Numeric) or return Failure(:invalid_arg, message: 'arg2 must be numeric')
|
1621
|
+
|
1622
|
+
Continue(number1: arg1, number2: arg2)
|
1623
|
+
end
|
1624
|
+
|
1625
|
+
def validate_non_zero(number2:, **)
|
1626
|
+
return Continue() if number2.nonzero?
|
1627
|
+
|
1628
|
+
Failure(:division_by_zero, message: 'arg2 must not be zero')
|
1629
|
+
end
|
1630
|
+
|
1631
|
+
def divide(number1:, number2:, logger:)
|
1632
|
+
result = number1 / number2
|
1633
|
+
|
1634
|
+
logger.info("The division result is #{result}")
|
1635
|
+
|
1636
|
+
Continue(number: result)
|
1637
|
+
end
|
1638
|
+
end
|
1639
|
+
|
1640
|
+
Divide.call(14, 2)
|
1641
|
+
# I, [2023-10-27T02:01:05.812388 #77823] INFO -- : The division result is 7
|
1642
|
+
#<BCDD::Result::Context::Success type=:division_completed value={:number=>7}>
|
1643
|
+
|
1644
|
+
Divide.call('14', 2)
|
1645
|
+
#<BCDD::Result::Context::Failure type=:invalid_arg value={:message=>"arg1 must be numeric"}>
|
1646
|
+
|
1647
|
+
Divide.call(14, '2')
|
1648
|
+
#<BCDD::Result::Context::Failure type=:invalid_arg value={:message=>"arg2 must be numeric"}>
|
1649
|
+
|
1650
|
+
Divide.call(14, 0)
|
1651
|
+
#<BCDD::Result::Context::Failure type=:division_by_zero value={:message=>"arg2 must not be zero"}>
|
1652
|
+
```
|
1653
|
+
|
1654
|
+
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
1655
|
+
|
1231
1656
|
## About
|
1232
1657
|
|
1233
1658
|
[Rodrigo Serradura](https://github.com/serradura) created this project. He is the B/CDD process/method creator and has already made similar gems like the [u-case](https://github.com/serradura/u-case) and [kind](https://github.com/serradura/kind/blob/main/lib/kind/result.rb). This gem is a general-purpose abstraction/monad, but it also contains key features that serve as facilitators for adopting B/CDD in the code.
|