bcdd-result 0.5.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +30 -10
  3. data/CHANGELOG.md +49 -2
  4. data/README.md +709 -127
  5. data/lib/bcdd/result/context/expectations/mixin.rb +35 -0
  6. data/lib/bcdd/result/context/expectations.rb +41 -0
  7. data/lib/bcdd/result/context/failure.rb +9 -0
  8. data/lib/bcdd/result/context/mixin.rb +38 -0
  9. data/lib/bcdd/result/context/success.rb +15 -0
  10. data/lib/bcdd/result/context.rb +74 -0
  11. data/lib/bcdd/result/{expectations/contract → contract}/disabled.rb +2 -2
  12. data/lib/bcdd/result/{expectations → contract}/error.rb +5 -3
  13. data/lib/bcdd/result/{expectations/contract → contract}/evaluator.rb +2 -2
  14. data/lib/bcdd/result/{expectations/contract → contract}/for_types.rb +2 -2
  15. data/lib/bcdd/result/{expectations/contract → contract}/for_types_and_values.rb +10 -8
  16. data/lib/bcdd/result/contract/interface.rb +21 -0
  17. data/lib/bcdd/result/{expectations → contract}/type_checker.rb +1 -1
  18. data/lib/bcdd/result/contract.rb +43 -0
  19. data/lib/bcdd/result/error.rb +7 -9
  20. data/lib/bcdd/result/expectations/mixin.rb +42 -0
  21. data/lib/bcdd/result/expectations.rb +27 -48
  22. data/lib/bcdd/result/failure/methods.rb +21 -0
  23. data/lib/bcdd/result/failure.rb +2 -16
  24. data/lib/bcdd/result/mixin.rb +36 -4
  25. data/lib/bcdd/result/success/methods.rb +21 -0
  26. data/lib/bcdd/result/success.rb +2 -16
  27. data/lib/bcdd/result/version.rb +1 -1
  28. data/lib/bcdd/result.rb +11 -6
  29. data/sig/bcdd/result.rbs +245 -71
  30. metadata +19 -10
  31. data/lib/bcdd/result/expectations/contract/interface.rb +0 -21
  32. data/lib/bcdd/result/expectations/contract.rb +0 -25
data/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  <p align="center"><i>Empower Ruby apps with a pragmatic use of Railway Oriented Programming.</i></p>
4
4
  <p align="center">
5
5
  <img src="https://img.shields.io/badge/ruby->%3D%202.7.0-ruby.svg?colorA=99004d&colorB=cc0066" alt="Ruby">
6
- <img src="https://badge.fury.io/rb/bcdd-result.svg" alt="bcdd-result gem version" height="18">
6
+ <a href="https://rubygems.org/gems/bcdd-result"><img src="https://badge.fury.io/rb/bcdd-result.svg" alt="bcdd-result gem version" height="18"></a>
7
7
  </p>
8
8
  </p>
9
9
 
@@ -35,17 +35,18 @@ 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
- - [`BCDD::Result::Mixin`](#bcddresultmixin)
43
+ - [`BCDD::Result.mixin`](#bcddresultmixin)
41
44
  - [Class example (Instance Methods)](#class-example-instance-methods)
42
45
  - [Module example (Singleton Methods)](#module-example-singleton-methods)
43
- - [Restrictions](#restrictions)
44
- - [Pattern Matching](#pattern-matching)
45
- - [`Array`/`Find` patterns](#arrayfind-patterns)
46
- - [`Hash` patterns](#hash-patterns)
47
- - [BCDD::Result::Expectations](#bcddresultexpectations)
48
- - [`BCDD::Result::Expectations`](#bcddresultexpectations-1)
46
+ - [Important Requirement](#important-requirement)
47
+ - [Dependency Injection](#dependency-injection)
48
+ - [Add-ons](#add-ons)
49
+ - [`BCDD::Result::Expectations`](#bcddresultexpectations)
49
50
  - [Standalone *versus* Mixin mode](#standalone-versus-mixin-mode)
50
51
  - [Type checking - Result Hooks](#type-checking---result-hooks)
51
52
  - [`#success?` and `#failure?`](#success-and-failure)
@@ -58,6 +59,16 @@ Use it to enable the [Railway Oriented Programming](https://fsharpforfunandprofi
58
59
  - [Value checking - Result Creation](#value-checking---result-creation)
59
60
  - [Success()](#success)
60
61
  - [Failure()](#failure)
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)
61
72
  - [About](#about)
62
73
  - [Development](#development)
63
74
  - [Contributing](#contributing)
@@ -206,9 +217,8 @@ result.failure?(:error) # false
206
217
 
207
218
  ### Result Hooks
208
219
 
209
- Result hooks are methods that allow you to perform a block of code depending on the result type.
210
-
211
- 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.
212
222
 
213
223
  ```ruby
214
224
  def divide(arg1, arg2)
@@ -225,14 +235,15 @@ end
225
235
 
226
236
  #### `result.on`
227
237
 
228
- `BCDD::Result#on` will perform the block when the type matches the result type.
238
+ When you use `BCDD::Result#on`, the block will be executed only when the type matches the result type.
229
239
 
230
- Regardless of the block being executed, the return of the method will always be the result itself.
240
+ However, even if the block is executed, the method will always return the result itself.
231
241
 
232
- The result value will be exposed as the first argument of the block.
242
+ The value of the result will be available as the first argument of the block.
233
243
 
234
244
  ```ruby
235
245
  result = divide(nil, 2)
246
+ #<BCDD::Result::Failure type=:invalid_arg data='arg1 must be numeric'>
236
247
 
237
248
  output =
238
249
  result
@@ -265,21 +276,40 @@ result.object_id == output.object_id # true
265
276
 
266
277
  `BCDD::Result#on_type` is an alias of `BCDD::Result#on`.
267
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">⬆️ &nbsp;back to top</a></p>
297
+
268
298
  #### `result.on_success`
269
299
 
270
- `BCDD::Result#on_success` is very similar to the `BCDD::Result#on` hook. The main differences are:
300
+ The `BCDD::Result#on_success` method is quite similar to the `BCDD::Result#on` hook, but with a few key differences:
271
301
 
272
- 1. Only perform the block when the result is a success.
273
- 2. If the type is missing it will perform the block for any success.
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.
274
304
 
275
305
  ```ruby
276
- # It performs the block and return itself.
306
+ # It executes the block and return itself.
277
307
 
278
308
  divide(4, 2).on_success { |number| puts number }
279
309
 
280
310
  divide(4, 2).on_success(:division_completed) { |number| puts number }
281
311
 
282
- # It doesn't perform the block, but return itself.
312
+ # It doesn't execute the block, but return itself.
283
313
 
284
314
  divide(4, 4).on_success(:ok) { |value| puts value }
285
315
 
@@ -292,16 +322,19 @@ divide(4, 4).on_failure { |error| puts error }
292
322
 
293
323
  #### `result.on_failure`
294
324
 
295
- Is the opposite of `Result#on_success`.
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.
296
329
 
297
330
  ```ruby
298
- # It performs the block and return itself.
331
+ # It executes the block and return itself.
299
332
 
300
333
  divide(nil, 2).on_failure { |error| puts error }
301
334
 
302
335
  divide(4, 0).on_failure(:invalid_arg, :division_by_zero) { |error| puts error }
303
336
 
304
- # It doesn't perform the block, but return itself.
337
+ # It doesn't execute the block, but return itself.
305
338
 
306
339
  divide(4, 0).on_success { |number| puts number }
307
340
 
@@ -314,11 +347,11 @@ divide(4, 0).on_failure(:invalid_arg) { |error| puts error }
314
347
 
315
348
  #### `result.on_unknown`
316
349
 
317
- `BCDD::Result#on_unknown` will perform the block when no other hook (`#on`, `#on_type`, `#on_failure`, `#on_success`) has been executed.
350
+ `BCDD::Result#on_unknown` will execute the block when no other hook (`#on`, `#on_type`, `#on_failure`, `#on_success`) has been executed.
318
351
 
319
- Regardless of the block being executed, the return of the method will always be the result itself.
352
+ Regardless of the block being executed, the method will always return the result itself.
320
353
 
321
- The result value will be exposed as the first argument of the block.
354
+ The value of the result will be available as the first argument of the block.
322
355
 
323
356
  ```ruby
324
357
  divide(4, 2)
@@ -335,7 +368,7 @@ divide(4, 2)
335
368
 
336
369
  #### `result.handle`
337
370
 
338
- This method will allow you to define blocks for each hook (type, failure, success), but instead of returning itself, it will return the output of the first match/block execution.
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.
339
372
 
340
373
  ```ruby
341
374
  divide(4, 2).handle do |result|
@@ -376,8 +409,8 @@ end
376
409
 
377
410
  **Notes:**
378
411
  * You can define multiple types to be handled by the same hook/block
379
- * If the type is missing, it will perform the block for any success or failure handler.
380
- * The `#type` and `#[]` handlers will require at least one type/argument.
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.
381
414
 
382
415
  *PS: The `divide()` implementation is [here](#result-hooks).*
383
416
 
@@ -385,13 +418,13 @@ end
385
418
 
386
419
  ### Result Value
387
420
 
388
- The most simple way to get the result value is by calling `BCDD::Result#value`.
421
+ To access the result value, you can simply call `BCDD::Result#value`.
389
422
 
390
- But sometimes you need to get the value of a successful result or a default value if it is a failure. In this case, you can use `BCDD::Result#value_or`.
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`.
391
424
 
392
425
  #### `result.value_or`
393
426
 
394
- `BCCD::Result#value_or` returns the value when the result is a success, but if is a failure the block will be performed, and its outcome will be the output.
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.
395
428
 
396
429
  ```ruby
397
430
  def divide(arg1, arg2)
@@ -459,16 +492,75 @@ print_to_hash(**success_data) # [:success, :ok, 1]
459
492
 
460
493
  <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
461
494
 
462
- ### Railway Oriented Programming
495
+ ### Pattern Matching
496
+
497
+ The `BCDD::Result` also provides support to pattern matching.
498
+
499
+ In the further examples, I will use the `Divide` lambda to exemplify its usage.
500
+
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
+ ```
511
+
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
463
528
 
464
- This feature/pattern is also known as ["Railway Oriented Programming"](https://fsharpforfunandprofit.com/rop/).
529
+ # The code above will print: arg2 must not be zero
530
+ ```
465
531
 
466
- The idea is to chain blocks and creates a pipeline of operations that can be interrupted by a failure.
532
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
467
533
 
468
- In other words, the block will be executed only if the result is a success.
469
- So, if some block returns a failure, the following blocks will be skipped.
534
+ #### `Hash` patterns
470
535
 
471
- Due to this characteristic, you can use this feature to express some logic as a sequence of operations. And have the guarantee that the process will stop by the first failure detection, and if everything is ok, the final result will be a success.
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">⬆️ &nbsp;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.
472
564
 
473
565
  #### `result.and_then`
474
566
 
@@ -521,17 +613,17 @@ Divide.call(2, 2)
521
613
 
522
614
  <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
523
615
 
524
- #### `BCDD::Result::Mixin`
616
+ #### `BCDD::Result.mixin`
525
617
 
526
- It is a module that can be included/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 first ones will use the target object (who received the include/extend) as the result's subject.
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.
527
619
 
528
- And because of this, you can use the `#and_then` method to call methods from the result's subject.
620
+ As a result, you can utilize the `#and_then` method to invoke methods from the result's subject.
529
621
 
530
622
  ##### Class example (Instance Methods)
531
623
 
532
624
  ```ruby
533
625
  class Divide
534
- include BCDD::Result::Mixin
626
+ include BCDD::Result.mixin
535
627
 
536
628
  attr_reader :arg1, :arg2
537
629
 
@@ -579,8 +671,7 @@ Divide.new(4, '2').call #<BCDD::Result::Failure type=:invalid_arg value="arg2 mu
579
671
 
580
672
  ```ruby
581
673
  module Divide
582
- extend BCDD::Result::Mixin
583
- extend self
674
+ extend self, BCDD::Result.mixin
584
675
 
585
676
  def call(arg1, arg2)
586
677
  validate_numbers(arg1, arg2)
@@ -615,100 +706,163 @@ Divide.call('4', 2) #<BCDD::Result::Failure type=:invalid_arg value="arg1 must b
615
706
  Divide.call(4, '2') #<BCDD::Result::Failure type=:invalid_arg value="arg2 must be numeric">
616
707
  ```
617
708
 
618
- ##### Restrictions
709
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
619
710
 
620
- The unique condition for using the `#and_then` to call methods is that they must use the `Success()` and `Failure()` to produce their results.
711
+ ##### Important Requirement
621
712
 
622
- If you use `BCDD::Result::Subject()`/`BCDD::Result::Failure()`, or use result from another `BCDD::Result::Mixin` instance, the `#and_then` will raise an error because the subjects will be different.
713
+ To use the `#and_then` method to call methods, they must use `Success()` and `Failure()` to produce the results.
623
714
 
624
- > **Note**: You still can use the block syntax, but all the results must be produced by the subject's `Success()` and `Failure()` methods.
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.
625
716
 
626
- <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
717
+ **Note:** You can still use the block syntax, but all the results must be produced by the subject's `Success()` and `Failure()` methods.
627
718
 
628
- ### Pattern Matching
719
+ ```ruby
720
+ module ValidateNonZero
721
+ extend self, BCDD::Result.mixin
629
722
 
630
- The `BCDD::Result` also provides support to pattern matching.
723
+ def call(numbers)
724
+ return Success(:ok, numbers) unless numbers.last.zero?
631
725
 
632
- In the further examples, I will use the `Divide` lambda to exemplify its usage.
726
+ Failure(:division_by_zero, 'arg2 must not be zero')
727
+ end
728
+ end
633
729
 
634
- ```ruby
635
- Divide = lambda do |arg1, arg2|
636
- arg1.is_a?(::Numeric) or return BCDD::Result::Failure(:invalid_arg, 'arg1 must be numeric')
637
- arg2.is_a?(::Numeric) or return BCDD::Result::Failure(:invalid_arg, 'arg2 must be numeric')
730
+ class Divide
731
+ include BCDD::Result.mixin
638
732
 
639
- return BCDD::Result::Failure(:division_by_zero, 'arg2 must not be zero') if arg2.zero?
733
+ attr_reader :arg1, :arg2
640
734
 
641
- BCDD::Result::Success(:division_completed, arg1 / arg2)
642
- end
643
- ```
735
+ def initialize(arg1, arg2)
736
+ @arg1 = arg1
737
+ @arg2 = arg2
738
+ end
644
739
 
645
- #### `Array`/`Find` patterns
740
+ def call
741
+ validate_numbers
742
+ .and_then(:validate_non_zero)
743
+ .and_then(:divide)
744
+ end
646
745
 
647
- ```ruby
648
- case Divide.call(4, 2)
649
- in BCDD::Result::Failure[:invalid_arg, msg] then puts msg
650
- in BCDD::Result::Failure[:division_by_zero, msg] then puts msg
651
- in BCDD::Result::Success[:division_completed, value] then puts value
652
- end
746
+ private
653
747
 
654
- # The code above will print: 2
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')
655
751
 
656
- case Divide.call(4, 0)
657
- in BCDD::Result::Failure[:invalid_arg, msg] then puts msg
658
- in BCDD::Result::Failure[:division_by_zero, msg] then puts msg
659
- in BCDD::Result::Success[:division_completed, value] then puts value
660
- end
752
+ BCDD::Result::Success(:ok, [arg1, arg2]) # This will raise an error
753
+ end
661
754
 
662
- # The code above will print: arg2 must not be zero
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
663
770
  ```
664
771
 
665
772
  <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
666
773
 
667
- #### `Hash` patterns
774
+ ##### Dependency Injection
775
+
776
+ The `BCDD::Result#and_then` accepts a second argument that will be used to share a value with the subject's method. To receive this argument, the subject's method must have an arity of two, where the first argument will be the result value and the second will be the shared value.
668
777
 
669
778
  ```ruby
670
- case Divide.call(10, 2)
671
- in { failure: { invalid_arg: msg } } then puts msg
672
- in { failure: { division_by_zero: msg } } then puts msg
673
- in { success: { division_completed: value } } then puts value
674
- end
779
+ require 'logger'
675
780
 
676
- # The code above will print: 5
781
+ module Divide
782
+ extend self, BCDD::Result.mixin
677
783
 
678
- case Divide.call('10', 2)
679
- in { failure: { invalid_arg: msg } } then puts msg
680
- in { failure: { division_by_zero: msg } } then puts msg
681
- in { success: { division_completed: value } } then puts value
784
+ def call(arg1, arg2, logger: ::Logger.new(STDOUT))
785
+ validate_numbers(arg1, arg2)
786
+ .and_then(:validate_non_zero, logger)
787
+ .and_then(:divide, logger)
788
+ end
789
+
790
+ private
791
+
792
+ def validate_numbers(arg1, arg2)
793
+ arg1.is_a?(::Numeric) or return Failure(:invalid_arg, 'arg1 must be numeric')
794
+ arg2.is_a?(::Numeric) or return Failure(:invalid_arg, 'arg2 must be numeric')
795
+
796
+ Success(:ok, [arg1, arg2])
797
+ end
798
+
799
+ def validate_non_zero(numbers, logger)
800
+ if numbers.last.zero?
801
+ logger.error('arg2 must not be zero')
802
+
803
+ Failure(:division_by_zero, 'arg2 must not be zero')
804
+ else
805
+ logger.info('The numbers are valid')
806
+
807
+ Success(:ok, numbers)
808
+ end
809
+ end
810
+
811
+ def divide((number1, number2), logger)
812
+ division = number1 / number2
813
+
814
+ logger.info("The division result is #{division}")
815
+
816
+ Success(:division_completed, division)
817
+ end
682
818
  end
683
819
 
684
- # The code above will print: arg1 must be numeric
820
+ Divide.call(4, 2)
821
+ # I, [2023-10-11T00:08:05.546237 #18139] INFO -- : The numbers are valid
822
+ # I, [2023-10-11T00:08:05.546337 #18139] INFO -- : The division result is 2
823
+ #=> #<BCDD::Result::Success type=:division_completed value=2>
824
+
825
+ Divide.call(4, 2, logger: Logger.new(IO::NULL))
826
+ #=> #<BCDD::Result::Success type=:division_completed value=2>
685
827
  ```
686
828
 
687
829
  <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
688
830
 
689
- #### BCDD::Result::Expectations
831
+ ##### Add-ons
690
832
 
691
- I'd like you to please read the following section to understand how to use this feature.
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
 
693
- 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.
835
+ **Continue**
836
+
837
+ This addon will create the `Continue(value)` method, which will know how to produce a `Success(:continued, value)`. It is useful when you want to perform a sequence of operations but want to avoid returning a specific result for each step.
694
838
 
695
839
  ```ruby
696
840
  module Divide
697
- extend BCDD::Result::Expectations.mixin(
698
- success: {
699
- numbers: ->(value) { value in [Numeric, Numeric] },
700
- division_completed: ->(value) { value in (Integer | Float) }
701
- },
702
- failure: { invalid_arg: String, division_by_zero: String }
703
- )
841
+ extend self, BCDD::Result.mixin(with: :Continue)
704
842
 
705
- def self.call(arg1, arg2)
706
- arg1.is_a?(Numeric) or return Failure(:invalid_arg, 'arg1 must be numeric')
707
- arg2.is_a?(Numeric) or return Failure(:invalid_arg, 'arg2 must be numeric')
843
+ def call(arg1, arg2)
844
+ validate_numbers(arg1, arg2)
845
+ .and_then(:validate_non_zero)
846
+ .and_then(:divide)
847
+ end
708
848
 
709
- arg2.zero? and return Failure(:division_by_zero, 'arg2 must not be zero')
849
+ private
710
850
 
711
- Success(:division_completed, arg1 / arg2)
851
+ def validate_numbers(arg1, arg2)
852
+ arg1.is_a?(::Numeric) or return Failure(:invalid_arg, 'arg1 must be numeric')
853
+ arg2.is_a?(::Numeric) or return Failure(:invalid_arg, 'arg2 must be numeric')
854
+
855
+ Continue([arg1, arg2])
856
+ end
857
+
858
+ def validate_non_zero(numbers)
859
+ return Continue(numbers) unless numbers.last.zero?
860
+
861
+ Failure(:division_by_zero, 'arg2 must not be zero')
862
+ end
863
+
864
+ def divide((number1, number2))
865
+ Success(:division_completed, number1 / number2)
712
866
  end
713
867
  end
714
868
  ```
@@ -727,18 +881,18 @@ The _**standalone mode**_ creates an object that knows how to produce and valida
727
881
 
728
882
  ```ruby
729
883
  module Divide
730
- Expected = BCDD::Result::Expectations.new(
884
+ Result = BCDD::Result::Expectations.new(
731
885
  success: %i[numbers division_completed],
732
886
  failure: %i[invalid_arg division_by_zero]
733
887
  )
734
888
 
735
889
  def self.call(arg1, arg2)
736
- arg1.is_a?(Numeric) or return Expected::Failure(:invalid_arg, 'arg1 must be numeric')
737
- arg2.is_a?(Numeric) or return Expected::Failure(:invalid_arg, 'arg2 must be numeric')
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')
738
892
 
739
- arg2.zero? and return Expected::Failure(:division_by_zero, 'arg2 must not be zero')
893
+ arg2.zero? and return Result::Failure(:division_by_zero, 'arg2 must not be zero')
740
894
 
741
- Expected::Success(:division_completed, arg1 / arg2)
895
+ Result::Success(:division_completed, arg1 / arg2)
742
896
  end
743
897
  end
744
898
  ```
@@ -748,11 +902,11 @@ In the code above, we define a constant `Divide::Expected`. And because of this
748
902
  Look what happens if you try to create a result without one of the expected types.
749
903
 
750
904
  ```ruby
751
- Divide::Expected::Success(:ok)
905
+ Divide::Result::Success(:ok)
752
906
  # type :ok is not allowed. Allowed types: :numbers, :division_completed
753
907
  # (BCDD::Result::Expectations::Error::UnexpectedType)
754
908
 
755
- Divide::Expected::Failure(:err)
909
+ Divide::Result::Failure(:err)
756
910
  # type :err is not allowed. Allowed types: :invalid_arg, :division_by_zero
757
911
  # (BCDD::Result::Expectations::Error::UnexpectedType)
758
912
  ```
@@ -804,11 +958,11 @@ Now that you know the two modes, let's understand how expectations can be benefi
804
958
 
805
959
  #### Type checking - Result Hooks
806
960
 
807
- The `BCDD::Result::Expectations` will check if the result's type is valid. This checking will be performed in all the methods that rely on the result's type, like `#success?`, `#failure?`, `#on`, `#on_type`, `#on_success`, `#on_failure`, `#handle`.
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 results type, such as `#success?`, `#failure?`, `#on`, `#on_type`, `#on_success`, `#on_failure`, and `#handle`.
808
962
 
809
963
  ##### `#success?` and `#failure?`
810
964
 
811
- When you check whether a result is a success or failure, `BCDD::Result::Expectations` will check whether the result type is valid/expected. Otherwise, an error will be raised.
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.
812
966
 
813
967
  **Success example:**
814
968
 
@@ -844,7 +998,7 @@ result.failure?(:err)
844
998
 
845
999
  ##### `#on` and `#on_type`
846
1000
 
847
- If you use `#on` or `#on_type` to perform a block, `BCDD::Result::Expectations` will check whether the result type is valid/expected. Otherwise, an error will be raised.
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.
848
1002
 
849
1003
  ```ruby
850
1004
  result = Divide.new.call(10, 2)
@@ -866,7 +1020,7 @@ result.on(:number) { |_| :this_type_does_not_exist }
866
1020
 
867
1021
  ##### `#on_success` and `#on_failure`
868
1022
 
869
- If you use `#on_success` or `#on_failure` to perform a block, `BCDD::Result::Expectations` will check whether the result type is valid/expected. Otherwise, an error will be raised.
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.
870
1024
 
871
1025
  ```ruby
872
1026
  result = Divide.new.call(10, '2')
@@ -958,15 +1112,15 @@ Divide.call(4, 2)
958
1112
 
959
1113
  ```ruby
960
1114
  module Divide
961
- Expected = BCDD::Result::Expectations.new(success: :ok, failure: :err)
1115
+ Result = BCDD::Result::Expectations.new(success: :ok, failure: :err)
962
1116
 
963
1117
  def self.call(arg1, arg2)
964
- arg1.is_a?(Numeric) or return Expected::Failure(:invalid_arg, 'arg1 must be numeric')
965
- arg2.is_a?(Numeric) or return Expected::Failure(:invalid_arg, 'arg2 must be numeric')
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')
966
1120
 
967
- arg2.zero? and return Expected::Failure(:division_by_zero, 'arg2 must not be zero')
1121
+ arg2.zero? and return Result::Failure(:division_by_zero, 'arg2 must not be zero')
968
1122
 
969
- Expected::Success(:division_completed, arg1 / arg2)
1123
+ Result::Success(:division_completed, arg1 / arg2)
970
1124
  end
971
1125
  end
972
1126
 
@@ -1017,7 +1171,7 @@ end
1017
1171
 
1018
1172
  ```ruby
1019
1173
  module Divide
1020
- Expected = BCDD::Result::Expectations.new(
1174
+ Result = BCDD::Result::Expectations.new(
1021
1175
  success: {
1022
1176
  numbers: ->(value) { value.is_a?(Array) && value.size == 2 && value.all?(Numeric) },
1023
1177
  division_completed: Numeric
@@ -1029,12 +1183,12 @@ module Divide
1029
1183
  )
1030
1184
 
1031
1185
  def self.call(arg1, arg2)
1032
- arg1.is_a?(Numeric) or return Expected::Failure(:invalid_arg, 'arg1 must be numeric')
1033
- arg2.is_a?(Numeric) or return Expected::Failure(:invalid_arg, 'arg2 must be numeric')
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')
1034
1188
 
1035
- arg2.zero? and return Expected::Failure(:division_by_zero, 'arg2 must not be zero')
1189
+ arg2.zero? and return Result::Failure(:division_by_zero, 'arg2 must not be zero')
1036
1190
 
1037
- Expected::Success(:division_completed, arg1 / arg2)
1191
+ Result::Success(:division_completed, arg1 / arg2)
1038
1192
  end
1039
1193
  end
1040
1194
  ```
@@ -1046,31 +1200,459 @@ The value validation will only be performed through the methods `Success()` and
1046
1200
  ##### Success()
1047
1201
 
1048
1202
  ```ruby
1049
- Divide::Expected::Success(:ok)
1203
+ Divide::Result::Success(:ok)
1050
1204
  # type :ok is not allowed. Allowed types: :numbers, :division_completed (BCDD::Result::Expectations::Error::UnexpectedType)
1051
1205
 
1052
- Divide::Expected::Success(:numbers, [1])
1206
+ Divide::Result::Success(:numbers, [1])
1053
1207
  # value [1] is not allowed for :numbers type (BCDD::Result::Expectations::Error::UnexpectedValue)
1054
1208
 
1055
- Divide::Expected::Success(:division_completed, '2')
1209
+ Divide::Result::Success(:division_completed, '2')
1056
1210
  # value "2" is not allowed for :division_completed type (BCDD::Result::Expectations::Error::UnexpectedValue)
1057
1211
  ```
1058
1212
 
1059
1213
  ##### Failure()
1060
1214
 
1061
1215
  ```ruby
1062
- Divide::Expected::Failure(:err)
1216
+ Divide::Result::Failure(:err)
1063
1217
  # type :err is not allowed. Allowed types: :invalid_arg, :division_by_zero (BCDD::Result::Expectations::Error::UnexpectedType)
1064
1218
 
1065
- Divide::Expected::Failure(:invalid_arg, :arg1_must_be_numeric)
1219
+ Divide::Result::Failure(:invalid_arg, :arg1_must_be_numeric)
1066
1220
  # value :arg1_must_be_numeric is not allowed for :invalid_arg type (BCDD::Result::Expectations::Error::UnexpectedValue)
1067
1221
 
1068
- Divide::Expected::Failure(:division_by_zero, msg: 'arg2 must not be zero')
1222
+ Divide::Result::Failure(:division_by_zero, msg: 'arg2 must not be zero')
1069
1223
  # value {:msg=>"arg2 must not be zero"} is not allowed for :division_by_zero type (BCDD::Result::Expectations::Error::UnexpectedValue)
1070
1224
  ```
1071
1225
 
1072
1226
  <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
1073
1227
 
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">⬆️ &nbsp;back to top</a></p>
1270
+
1271
+ #### `BCDD::Result::Expectations.mixin` add-ons
1272
+
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.
1274
+
1275
+ **Continue**
1276
+
1277
+ It is similar to `BCDD::Result.mixin(with: :Continue)`, the key difference is that the `Continue(value)` will be ignored by the expectations. This is extremely useful when you want to use `Continue(value)` to chain operations, but you don't want to declare N success types in the expectations.
1278
+
1279
+ ```ruby
1280
+ class Divide
1281
+ include BCDD::Result::Expectations.mixin(
1282
+ with: :Continue,
1283
+ success: :division_completed,
1284
+ failure: %i[invalid_arg division_by_zero]
1285
+ )
1286
+
1287
+ def call(arg1, arg2)
1288
+ validate_numbers(arg1, arg2)
1289
+ .and_then(:validate_non_zero)
1290
+ .and_then(:divide)
1291
+ end
1292
+
1293
+ private
1294
+
1295
+ def validate_numbers(arg1, arg2)
1296
+ arg1.is_a?(::Numeric) or return Failure(:invalid_arg, 'arg1 must be numeric')
1297
+ arg2.is_a?(::Numeric) or return Failure(:invalid_arg, 'arg2 must be numeric')
1298
+
1299
+ Continue([arg1, arg2])
1300
+ end
1301
+
1302
+ def validate_non_zero(numbers)
1303
+ return Continue(numbers) unless numbers.last.zero?
1304
+
1305
+ Failure(:division_by_zero, 'arg2 must not be zero')
1306
+ end
1307
+
1308
+ def divide((number1, number2))
1309
+ Success(:division_completed, number1 / number2)
1310
+ end
1311
+ end
1312
+
1313
+ result = Divide.new.call(4,2)
1314
+ # => #<BCDD::Result::Success type=:division_completed value=2>
1315
+
1316
+ # The example below shows an error because the :ok type is not allowed.
1317
+ # But look at the allowed types have only one type (:division_completed).
1318
+ # This is because the :continued type is ignored by the expectations.
1319
+ #
1320
+ result.success?(:ok)
1321
+ # type :ok is not allowed. Allowed types: :division_completed (BCDD::Result::Expectations::Error::UnexpectedType)
1322
+ ```
1323
+
1324
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
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">⬆️ &nbsp;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">⬆️ &nbsp;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">⬆️ &nbsp;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">⬆️ &nbsp;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">⬆️ &nbsp;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">⬆️ &nbsp;back to top</a></p>
1655
+
1074
1656
  ## About
1075
1657
 
1076
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.