bcdd-result 0.4.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
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
 
@@ -37,13 +37,30 @@ Use it to enable the [Railway Oriented Programming](https://fsharpforfunandprofi
37
37
  - [`result.data`](#resultdata)
38
38
  - [Railway Oriented Programming](#railway-oriented-programming)
39
39
  - [`result.and_then`](#resultand_then)
40
- - [`BCDD::Result::Mixin`](#bcddresultmixin)
41
- - [Class example (instance methods)](#class-example-instance-methods)
42
- - [Module example (singleton methods)](#module-example-singleton-methods)
43
- - [Restrictions](#restrictions)
40
+ - [`BCDD::Result.mixin`](#bcddresultmixin)
41
+ - [Class example (Instance Methods)](#class-example-instance-methods)
42
+ - [Module example (Singleton Methods)](#module-example-singleton-methods)
43
+ - [Important Requirement](#important-requirement)
44
+ - [Dependency Injection](#dependency-injection)
45
+ - [Addons](#addons)
44
46
  - [Pattern Matching](#pattern-matching)
45
47
  - [`Array`/`Find` patterns](#arrayfind-patterns)
46
48
  - [`Hash` patterns](#hash-patterns)
49
+ - [BCDD::Result::Expectations](#bcddresultexpectations)
50
+ - [`BCDD::Result::Expectations`](#bcddresultexpectations-1)
51
+ - [Standalone *versus* Mixin mode](#standalone-versus-mixin-mode)
52
+ - [Type checking - Result Hooks](#type-checking---result-hooks)
53
+ - [`#success?` and `#failure?`](#success-and-failure)
54
+ - [`#on` and `#on_type`](#on-and-on_type)
55
+ - [`#on_success` and `#on_failure`](#on_success-and-on_failure)
56
+ - [`#handle`](#handle)
57
+ - [Type checking - Result Creation](#type-checking---result-creation)
58
+ - [Mixin mode](#mixin-mode)
59
+ - [Standalone mode](#standalone-mode)
60
+ - [Value checking - Result Creation](#value-checking---result-creation)
61
+ - [Success()](#success)
62
+ - [Failure()](#failure)
63
+ - [`BCDD::Result::Expectations.mixin` Addons](#bcddresultexpectationsmixin-addons)
47
64
  - [About](#about)
48
65
  - [Development](#development)
49
66
  - [Contributing](#contributing)
@@ -371,9 +388,9 @@ end
371
388
 
372
389
  ### Result Value
373
390
 
374
- The most simple way to get the result value is by calling `BCDD::Result#value` or `BCDD::Result#data`.
391
+ The most simple way to get the result value is by calling `BCDD::Result#value`.
375
392
 
376
- 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` or `BCDD::Result#data_or`.
393
+ 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`.
377
394
 
378
395
  #### `result.value_or`
379
396
 
@@ -507,19 +524,17 @@ Divide.call(2, 2)
507
524
 
508
525
  <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
509
526
 
510
- #### `BCDD::Result::Mixin`
527
+ #### `BCDD::Result.mixin`
511
528
 
512
- It is a module that can be included/extended by any object. It adds two methods to the target object: `Success()` and `Failure()`.
529
+ This method produces 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.
513
530
 
514
- The main difference between these methods and `BCDD::Result::Success()`/`BCDD::Result::Failure()` is that the first ones will use the target object as the result's subject.
531
+ And because of this, you can use the `#and_then` method to call methods from the result's subject.
515
532
 
516
- And because of this, you can use the `#and_then` method to call methods from the target object (result's subject).
517
-
518
- ##### Class example (instance methods)
533
+ ##### Class example (Instance Methods)
519
534
 
520
535
  ```ruby
521
536
  class Divide
522
- include BCDD::Result::Mixin
537
+ include BCDD::Result.mixin
523
538
 
524
539
  attr_reader :arg1, :arg2
525
540
 
@@ -563,12 +578,11 @@ Divide.new('4', 2).call #<BCDD::Result::Failure type=:invalid_arg value="arg1 mu
563
578
  Divide.new(4, '2').call #<BCDD::Result::Failure type=:invalid_arg value="arg2 must be numeric">
564
579
  ```
565
580
 
566
- ##### Module example (singleton methods)
581
+ ##### Module example (Singleton Methods)
567
582
 
568
583
  ```ruby
569
584
  module Divide
570
- extend BCDD::Result::Mixin
571
- extend self
585
+ extend self, BCDD::Result.mixin
572
586
 
573
587
  def call(arg1, arg2)
574
588
  validate_numbers(arg1, arg2)
@@ -603,7 +617,9 @@ Divide.call('4', 2) #<BCDD::Result::Failure type=:invalid_arg value="arg1 must b
603
617
  Divide.call(4, '2') #<BCDD::Result::Failure type=:invalid_arg value="arg2 must be numeric">
604
618
  ```
605
619
 
606
- ##### Restrictions
620
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
621
+
622
+ ##### Important Requirement
607
623
 
608
624
  The unique condition for using the `#and_then` to call methods is that they must use the `Success()` and `Failure()` to produce their results.
609
625
 
@@ -613,6 +629,104 @@ If you use `BCDD::Result::Subject()`/`BCDD::Result::Failure()`, or use result fr
613
629
 
614
630
  <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
615
631
 
632
+ ##### Dependency Injection
633
+
634
+ 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.
635
+
636
+ ```ruby
637
+ require 'logger'
638
+
639
+ module Divide
640
+ extend self, BCDD::Result.mixin
641
+
642
+ def call(arg1, arg2, logger: ::Logger.new(STDOUT))
643
+ validate_numbers(arg1, arg2)
644
+ .and_then(:validate_non_zero, logger)
645
+ .and_then(:divide, logger)
646
+ end
647
+
648
+ private
649
+
650
+ def validate_numbers(arg1, arg2)
651
+ arg1.is_a?(::Numeric) or return Failure(:invalid_arg, 'arg1 must be numeric')
652
+ arg2.is_a?(::Numeric) or return Failure(:invalid_arg, 'arg2 must be numeric')
653
+
654
+ Success(:ok, [arg1, arg2])
655
+ end
656
+
657
+ def validate_non_zero(numbers, logger)
658
+ if numbers.last.zero?
659
+ logger.error('arg2 must not be zero')
660
+
661
+ Failure(:division_by_zero, 'arg2 must not be zero')
662
+ else
663
+ logger.info('The numbers are valid')
664
+
665
+ Success(:ok, numbers)
666
+ end
667
+ end
668
+
669
+ def divide((number1, number2), logger)
670
+ division = number1 / number2
671
+
672
+ logger.info("The division result is #{division}")
673
+
674
+ Success(:division_completed, division)
675
+ end
676
+ end
677
+
678
+ Divide.call(4, 2)
679
+ # I, [2023-10-11T00:08:05.546237 #18139] INFO -- : The numbers are valid
680
+ # I, [2023-10-11T00:08:05.546337 #18139] INFO -- : The division result is 2
681
+ #=> #<BCDD::Result::Success type=:division_completed value=2>
682
+
683
+ Divide.call(4, 2, logger: Logger.new(IO::NULL))
684
+ #=> #<BCDD::Result::Success type=:division_completed value=2>
685
+ ```
686
+
687
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
688
+
689
+ ##### Addons
690
+
691
+ 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
+
693
+ **Continue**
694
+
695
+ 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.
696
+
697
+ ```ruby
698
+ module Divide
699
+ extend self, BCDD::Result.mixin(with: :Continue)
700
+
701
+ def call(arg1, arg2)
702
+ validate_numbers(arg1, arg2)
703
+ .and_then(:validate_non_zero)
704
+ .and_then(:divide)
705
+ end
706
+
707
+ private
708
+
709
+ def validate_numbers(arg1, arg2)
710
+ arg1.is_a?(::Numeric) or return Failure(:invalid_arg, 'arg1 must be numeric')
711
+ arg2.is_a?(::Numeric) or return Failure(:invalid_arg, 'arg2 must be numeric')
712
+
713
+ Continue([arg1, arg2])
714
+ end
715
+
716
+ def validate_non_zero(numbers)
717
+ return Continue(numbers) unless numbers.last.zero?
718
+
719
+ Failure(:division_by_zero, 'arg2 must not be zero')
720
+ end
721
+
722
+ def divide((number1, number2))
723
+ Success(:division_completed, number1 / number2)
724
+ end
725
+ end
726
+ ```
727
+
728
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
729
+
616
730
  ### Pattern Matching
617
731
 
618
732
  The `BCDD::Result` also provides support to pattern matching.
@@ -674,6 +788,446 @@ end
674
788
 
675
789
  <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
676
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">⬆️ &nbsp;back to top</a></p>
819
+
820
+ ### `BCDD::Result::Expectations`
821
+
822
+ 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.
823
+
824
+ It was designed to ensure all the aspects of the result's type and value. So, an error will be raised if you try to create or handle a result with an unexpected type or value.
825
+
826
+ #### Standalone *versus* Mixin mode
827
+
828
+ The _**standalone mode**_ creates an object that knows how to produce and validate results based on the defined expectations. Look at the example below:
829
+
830
+ ```ruby
831
+ module Divide
832
+ Expected = BCDD::Result::Expectations.new(
833
+ success: %i[numbers division_completed],
834
+ failure: %i[invalid_arg division_by_zero]
835
+ )
836
+
837
+ def self.call(arg1, arg2)
838
+ arg1.is_a?(Numeric) or return Expected::Failure(:invalid_arg, 'arg1 must be numeric')
839
+ arg2.is_a?(Numeric) or return Expected::Failure(:invalid_arg, 'arg2 must be numeric')
840
+
841
+ arg2.zero? and return Expected::Failure(:division_by_zero, 'arg2 must not be zero')
842
+
843
+ Expected::Success(:division_completed, arg1 / arg2)
844
+ end
845
+ end
846
+ ```
847
+
848
+ In the code above, we define a constant `Divide::Expected`. And because of this (it is a constant), we can use it inside and outside the module.
849
+
850
+ Look what happens if you try to create a result without one of the expected types.
851
+
852
+ ```ruby
853
+ Divide::Expected::Success(:ok)
854
+ # type :ok is not allowed. Allowed types: :numbers, :division_completed
855
+ # (BCDD::Result::Expectations::Error::UnexpectedType)
856
+
857
+ Divide::Expected::Failure(:err)
858
+ # type :err is not allowed. Allowed types: :invalid_arg, :division_by_zero
859
+ # (BCDD::Result::Expectations::Error::UnexpectedType)
860
+ ```
861
+
862
+ The _**mixin mode**_ is similar to `BCDD::Result::Mixin`, but it also defines the expectations for the result's types and values.
863
+
864
+ ```ruby
865
+ class Divide
866
+ include BCDD::Result::Expectations.mixin(
867
+ success: %i[numbers division_completed],
868
+ failure: %i[invalid_arg division_by_zero]
869
+ )
870
+
871
+ def call(arg1, arg2)
872
+ validate_numbers(arg1, arg2)
873
+ .and_then(:validate_non_zero)
874
+ .and_then(:divide)
875
+ end
876
+
877
+ private
878
+
879
+ def validate_numbers(arg1, arg2)
880
+ arg1.is_a?(Numeric) or return Failure(:invalid_arg, 'arg1 must be numeric')
881
+ arg2.is_a?(Numeric) or return Failure(:invalid_arg, 'arg2 must be numeric')
882
+
883
+ Success(:numbers, [arg1, arg2])
884
+ end
885
+
886
+ def validate_non_zero(numbers)
887
+ return Success(:numbers, numbers) unless numbers.last.zero?
888
+
889
+ Failure(:division_by_zero, 'arg2 must not be zero')
890
+ end
891
+
892
+ def divide((number1, number2))
893
+ Success(:division_completed, number1 / number2)
894
+ end
895
+ end
896
+ ```
897
+
898
+ This mode also defines an `Expected` constant to be used inside and outside the module.
899
+
900
+ > **PROTIP:**
901
+ > You can use the `Expected` constant to mock the result's type and value in your tests. As they will have the exact expectations, your tests will check if the result clients are handling the result correctly.
902
+
903
+ Now that you know the two modes, let's understand how expectations can be beneficial and powerful for defining contracts.
904
+
905
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
906
+
907
+ #### Type checking - Result Hooks
908
+
909
+ 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`.
910
+
911
+ ##### `#success?` and `#failure?`
912
+
913
+ 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.
914
+
915
+ **Success example:**
916
+
917
+ ```ruby
918
+ result = Divide.new.call(10, 2)
919
+
920
+ result.success? # true
921
+ result.success?(:numbers) # false
922
+ result.success?(:division_completed) # true
923
+
924
+ result.success?(:ok)
925
+ # type :ok is not allowed. Allowed types: :numbers, :division_completed
926
+ # (BCDD::Result::Expectations::Error::UnexpectedType)
927
+ ```
928
+
929
+ **Failure example:**
930
+
931
+ ```ruby
932
+ result = Divide.new.call(10, '2')
933
+
934
+ result.failure? # true
935
+ result.failure?(:invalid_arg) # true
936
+ result.failure?(:division_by_zero) # false
937
+
938
+ result.failure?(:err)
939
+ # type :err is not allowed. Allowed types: :invalid_arg, :division_by_zero
940
+ # (BCDD::Result::Expectations::Error::UnexpectedType)
941
+ ```
942
+
943
+ *PS: The `Divide` implementation is [here](#standalone-versus-mixin-mode).*
944
+
945
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
946
+
947
+ ##### `#on` and `#on_type`
948
+
949
+ 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.
950
+
951
+ ```ruby
952
+ result = Divide.new.call(10, 2)
953
+
954
+ result
955
+ .on(:invalid_arg, :division_by_zero) { |msg| puts msg }
956
+ .on(:division_completed) { |number| puts "The result is #{number}" }
957
+
958
+ # The code above will print 'The result is 5'
959
+
960
+ result.on(:number) { |_| :this_type_does_not_exist }
961
+ # type :number is not allowed. Allowed types: :numbers, :division_completed, :invalid_arg, :division_by_zero
962
+ # (BCDD::Result::Expectations::Error::UnexpectedType)
963
+ ```
964
+
965
+ *PS: The `Divide` implementation is [here](#standalone-versus-mixin-mode).*
966
+
967
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
968
+
969
+ ##### `#on_success` and `#on_failure`
970
+
971
+ 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.
972
+
973
+ ```ruby
974
+ result = Divide.new.call(10, '2')
975
+
976
+ result
977
+ .on_failure(:invalid_arg, :division_by_zero) { |msg| puts msg }
978
+ .on_success(:division_completed) { |number| puts "The result is #{number}" }
979
+
980
+ result
981
+ .on_success { |number| puts "The result is #{number}" }
982
+ .on_failure { |msg| puts msg }
983
+
984
+ # Both codes above will print 'arg2 must be numeric'
985
+
986
+ result.on_success(:ok) { |_| :this_type_does_not_exist }
987
+ # type :ok is not allowed. Allowed types: :numbers, :division_completed
988
+ # (BCDD::Result::Expectations::Error::UnexpectedType)
989
+
990
+ result.on_failure(:err) { |_| :this_type_does_not_exist }
991
+ # type :err is not allowed. Allowed types: :invalid_arg, :division_by_zero
992
+ # (BCDD::Result::Expectations::Error::UnexpectedType)
993
+ ```
994
+
995
+ *PS: The `Divide` implementation is [here](#standalone-versus-mixin-mode).*
996
+
997
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
998
+
999
+ ##### `#handle`
1000
+
1001
+ The `BCDD::Result::Expectations` will also be applied on all the handlers defined by the `#handle` method/block.
1002
+
1003
+ ```ruby
1004
+ result = Divide.call(10, 2)
1005
+
1006
+ result.handle do |on|
1007
+ on.type(:ok) { |_| :this_type_does_not_exist }
1008
+ end
1009
+ # type :ok is not allowed. Allowed types: :numbers, :division_completed, :invalid_arg, :division_by_zero (BCDD::Result::Expectations::Error::UnexpectedType)
1010
+
1011
+ result.handle do |on|
1012
+ on.success(:ok) { |_| :this_type_does_not_exist }
1013
+ end
1014
+ # type :ok is not allowed. Allowed types: :numbers, :division_completed (BCDD::Result::Expectations::Error::UnexpectedType)
1015
+
1016
+ result.handle do |on|
1017
+ on.failure(:err) { |_| :this_type_does_not_exist }
1018
+ end
1019
+ # type :err is not allowed. Allowed types: :numbers, :division_completed (BCDD::Result::Expectations::Error::UnexpectedType)
1020
+ ```
1021
+
1022
+ *PS: The `Divide` implementation is [here](#standalone-versus-mixin-mode).*
1023
+
1024
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
1025
+
1026
+ #### Type checking - Result Creation
1027
+
1028
+ The `BCDD::Result::Expectations` will be used on the result creation `Success()` and `Failure()` methods. So, when the result type is valid/expected, the result will be created. Otherwise, an error will be raised.
1029
+
1030
+ This works for both modes (standalone and mixin).
1031
+
1032
+ ##### Mixin mode
1033
+
1034
+ ```ruby
1035
+ module Divide
1036
+ extend BCDD::Result::Expectations.mixin(success: :ok, failure: :err)
1037
+
1038
+ def self.call(arg1, arg2)
1039
+ arg1.is_a?(Numeric) or return Failure(:invalid_arg, 'arg1 must be numeric')
1040
+ arg2.is_a?(Numeric) or return Failure(:invalid_arg, 'arg2 must be numeric')
1041
+
1042
+ arg2.zero? and return Failure(:division_by_zero, 'arg2 must not be zero')
1043
+
1044
+ Success(:division_completed, arg1 / arg2)
1045
+ end
1046
+ end
1047
+
1048
+ Divide.call('4', 2)
1049
+ # type :invalid_arg is not allowed. Allowed types: :err
1050
+ # (BCDD::Result::Expectations::Error::UnexpectedType)
1051
+
1052
+ Divide.call(4, 2)
1053
+ # type :division_completed is not allowed. Allowed types: :ok
1054
+ # (BCDD::Result::Expectations::Error::UnexpectedType)
1055
+ ```
1056
+
1057
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
1058
+
1059
+ ##### Standalone mode
1060
+
1061
+ ```ruby
1062
+ module Divide
1063
+ Expected = BCDD::Result::Expectations.new(success: :ok, failure: :err)
1064
+
1065
+ def self.call(arg1, arg2)
1066
+ arg1.is_a?(Numeric) or return Expected::Failure(:invalid_arg, 'arg1 must be numeric')
1067
+ arg2.is_a?(Numeric) or return Expected::Failure(:invalid_arg, 'arg2 must be numeric')
1068
+
1069
+ arg2.zero? and return Expected::Failure(:division_by_zero, 'arg2 must not be zero')
1070
+
1071
+ Expected::Success(:division_completed, arg1 / arg2)
1072
+ end
1073
+ end
1074
+
1075
+ Divide.call('4', 2)
1076
+ # type :invalid_arg is not allowed. Allowed types: :err
1077
+ # (BCDD::Result::Expectations::Error::UnexpectedType)
1078
+
1079
+ Divide.call(4, 2)
1080
+ # type :division_completed is not allowed. Allowed types: :ok
1081
+ # (BCDD::Result::Expectations::Error::UnexpectedType)
1082
+ ```
1083
+
1084
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
1085
+
1086
+ #### Value checking - Result Creation
1087
+
1088
+ The `Result::Expectations` supports types of validations. The first is the type checking only, and the second is the type and value checking.
1089
+
1090
+ To define expectations for your result's values, you must declare a Hash with the type as the key and the value as the value. A value validator is any object that responds to `#===` (case equality operator).
1091
+
1092
+ **Mixin mode:**
1093
+
1094
+ ```ruby
1095
+ module Divide
1096
+ extend BCDD::Result::Expectations.mixin(
1097
+ success: {
1098
+ numbers: ->(value) { value.is_a?(Array) && value.size == 2 && value.all?(Numeric) },
1099
+ division_completed: Numeric
1100
+ },
1101
+ failure: {
1102
+ invalid_arg: String,
1103
+ division_by_zero: String
1104
+ }
1105
+ )
1106
+
1107
+ def self.call(arg1, arg2)
1108
+ arg1.is_a?(Numeric) or return Failure(:invalid_arg, 'arg1 must be numeric')
1109
+ arg2.is_a?(Numeric) or return Failure(:invalid_arg, 'arg2 must be numeric')
1110
+
1111
+ arg2.zero? and return Failure(:division_by_zero, 'arg2 must not be zero')
1112
+
1113
+ Success(:division_completed, arg1 / arg2)
1114
+ end
1115
+ end
1116
+ ```
1117
+
1118
+ **Standalone mode:**
1119
+
1120
+ ```ruby
1121
+ module Divide
1122
+ Expected = BCDD::Result::Expectations.new(
1123
+ success: {
1124
+ numbers: ->(value) { value.is_a?(Array) && value.size == 2 && value.all?(Numeric) },
1125
+ division_completed: Numeric
1126
+ },
1127
+ failure: {
1128
+ invalid_arg: String,
1129
+ division_by_zero: String
1130
+ }
1131
+ )
1132
+
1133
+ def self.call(arg1, arg2)
1134
+ arg1.is_a?(Numeric) or return Expected::Failure(:invalid_arg, 'arg1 must be numeric')
1135
+ arg2.is_a?(Numeric) or return Expected::Failure(:invalid_arg, 'arg2 must be numeric')
1136
+
1137
+ arg2.zero? and return Expected::Failure(:division_by_zero, 'arg2 must not be zero')
1138
+
1139
+ Expected::Success(:division_completed, arg1 / arg2)
1140
+ end
1141
+ end
1142
+ ```
1143
+
1144
+ The value validation will only be performed through the methods `Success()` and `Failure()`.
1145
+
1146
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
1147
+
1148
+ ##### Success()
1149
+
1150
+ ```ruby
1151
+ Divide::Expected::Success(:ok)
1152
+ # type :ok is not allowed. Allowed types: :numbers, :division_completed (BCDD::Result::Expectations::Error::UnexpectedType)
1153
+
1154
+ Divide::Expected::Success(:numbers, [1])
1155
+ # value [1] is not allowed for :numbers type (BCDD::Result::Expectations::Error::UnexpectedValue)
1156
+
1157
+ Divide::Expected::Success(:division_completed, '2')
1158
+ # value "2" is not allowed for :division_completed type (BCDD::Result::Expectations::Error::UnexpectedValue)
1159
+ ```
1160
+
1161
+ ##### Failure()
1162
+
1163
+ ```ruby
1164
+ Divide::Expected::Failure(:err)
1165
+ # type :err is not allowed. Allowed types: :invalid_arg, :division_by_zero (BCDD::Result::Expectations::Error::UnexpectedType)
1166
+
1167
+ Divide::Expected::Failure(:invalid_arg, :arg1_must_be_numeric)
1168
+ # value :arg1_must_be_numeric is not allowed for :invalid_arg type (BCDD::Result::Expectations::Error::UnexpectedValue)
1169
+
1170
+ Divide::Expected::Failure(:division_by_zero, msg: 'arg2 must not be zero')
1171
+ # value {:msg=>"arg2 must not be zero"} is not allowed for :division_by_zero type (BCDD::Result::Expectations::Error::UnexpectedValue)
1172
+ ```
1173
+
1174
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
1175
+
1176
+ #### `BCDD::Result::Expectations.mixin` Addons
1177
+
1178
+ 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
+
1180
+ **Continue**
1181
+
1182
+ 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.
1183
+
1184
+ ```ruby
1185
+ class Divide
1186
+ include BCDD::Result::Expectations.mixin(
1187
+ with: :Continue,
1188
+ success: :division_completed,
1189
+ failure: %i[invalid_arg division_by_zero]
1190
+ )
1191
+
1192
+ def call(arg1, arg2)
1193
+ validate_numbers(arg1, arg2)
1194
+ .and_then(:validate_non_zero)
1195
+ .and_then(:divide)
1196
+ end
1197
+
1198
+ private
1199
+
1200
+ def validate_numbers(arg1, arg2)
1201
+ arg1.is_a?(::Numeric) or return Failure(:invalid_arg, 'arg1 must be numeric')
1202
+ arg2.is_a?(::Numeric) or return Failure(:invalid_arg, 'arg2 must be numeric')
1203
+
1204
+ Continue([arg1, arg2])
1205
+ end
1206
+
1207
+ def validate_non_zero(numbers)
1208
+ return Continue(numbers) unless numbers.last.zero?
1209
+
1210
+ Failure(:division_by_zero, 'arg2 must not be zero')
1211
+ end
1212
+
1213
+ def divide((number1, number2))
1214
+ Success(:division_completed, number1 / number2)
1215
+ end
1216
+ end
1217
+
1218
+ result = Divide.new.call(4,2)
1219
+ # => #<BCDD::Result::Success type=:division_completed value=2>
1220
+
1221
+ # The example below shows an error because the :ok type is not allowed.
1222
+ # But look at the allowed types have only one type (:division_completed).
1223
+ # This is because the :continued type is ignored by the expectations.
1224
+ #
1225
+ result.success?(:ok)
1226
+ # type :ok is not allowed. Allowed types: :division_completed (BCDD::Result::Expectations::Error::UnexpectedType)
1227
+ ```
1228
+
1229
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
1230
+
677
1231
  ## About
678
1232
 
679
1233
  [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.
@@ -2,15 +2,20 @@
2
2
 
3
3
  class BCDD::Result
4
4
  class Data
5
- attr_reader :name, :type, :value, :to_h, :to_a
5
+ attr_reader :name, :type, :value
6
6
 
7
- def initialize(result)
8
- @name = result.send(:name)
9
- @type = result.type
10
- @value = result.value
7
+ def initialize(name, type, value)
8
+ @name = name
9
+ @type = type.to_sym
10
+ @value = value
11
+ end
12
+
13
+ def to_h
14
+ { name: name, type: type, value: value }
15
+ end
11
16
 
12
- @to_h = { name: name, type: type, value: value }
13
- @to_a = [name, type, value]
17
+ def to_a
18
+ [name, type, value]
14
19
  end
15
20
 
16
21
  def inspect