bcdd-result 0.6.0 → 0.7.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 +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.
|