bcdd-result 0.11.0 → 0.13.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 +16 -1
- data/CHANGELOG.md +97 -15
- data/README.md +508 -95
- data/Steepfile +4 -4
- data/examples/multiple_listeners/Rakefile +55 -0
- data/examples/multiple_listeners/app/models/account/member.rb +10 -0
- data/examples/multiple_listeners/app/models/account/owner_creation.rb +62 -0
- data/examples/multiple_listeners/app/models/account.rb +11 -0
- data/examples/multiple_listeners/app/models/user/creation.rb +67 -0
- data/examples/multiple_listeners/app/models/user/token/creation.rb +51 -0
- data/examples/multiple_listeners/app/models/user/token.rb +7 -0
- data/examples/multiple_listeners/app/models/user.rb +15 -0
- data/examples/multiple_listeners/config/boot.rb +16 -0
- data/examples/multiple_listeners/config/initializers/bcdd.rb +11 -0
- data/examples/multiple_listeners/config.rb +27 -0
- data/examples/multiple_listeners/db/setup.rb +61 -0
- data/examples/multiple_listeners/lib/bcdd/result/rollback_on_failure.rb +15 -0
- data/examples/multiple_listeners/lib/bcdd/result/transitions_record.rb +28 -0
- data/examples/multiple_listeners/lib/runtime_breaker.rb +11 -0
- data/examples/multiple_listeners/lib/transitions_listener/stdout.rb +54 -0
- data/examples/single_listener/Rakefile +92 -0
- data/examples/single_listener/app/models/account/member.rb +10 -0
- data/examples/single_listener/app/models/account/owner_creation.rb +62 -0
- data/examples/single_listener/app/models/account.rb +11 -0
- data/examples/single_listener/app/models/user/creation.rb +67 -0
- data/examples/single_listener/app/models/user/token/creation.rb +51 -0
- data/examples/single_listener/app/models/user/token.rb +7 -0
- data/examples/single_listener/app/models/user.rb +15 -0
- data/examples/single_listener/config/boot.rb +16 -0
- data/examples/single_listener/config/initializers/bcdd.rb +11 -0
- data/examples/single_listener/config.rb +23 -0
- data/examples/single_listener/db/setup.rb +49 -0
- data/examples/single_listener/lib/bcdd/result/rollback_on_failure.rb +15 -0
- data/examples/single_listener/lib/runtime_breaker.rb +11 -0
- data/examples/single_listener/lib/single_transitions_listener.rb +108 -0
- data/lib/bcdd/result/callable_and_then/caller.rb +49 -0
- data/lib/bcdd/result/callable_and_then/config.rb +15 -0
- data/lib/bcdd/result/callable_and_then/error.rb +11 -0
- data/lib/bcdd/result/callable_and_then.rb +9 -0
- data/lib/bcdd/result/config/switchers/features.rb +5 -1
- data/lib/bcdd/result/config.rb +15 -4
- data/lib/bcdd/result/context/callable_and_then.rb +39 -0
- data/lib/bcdd/result/context/expectations/mixin.rb +2 -2
- data/lib/bcdd/result/context/mixin.rb +3 -3
- data/lib/bcdd/result/context/success.rb +29 -7
- data/lib/bcdd/result/context.rb +34 -16
- data/lib/bcdd/result/contract/for_types.rb +1 -1
- data/lib/bcdd/result/contract/for_types_and_values.rb +2 -0
- data/lib/bcdd/result/error.rb +20 -11
- data/lib/bcdd/result/expectations/mixin.rb +3 -3
- data/lib/bcdd/result/expectations.rb +6 -6
- data/lib/bcdd/result/ignored_types.rb +14 -0
- data/lib/bcdd/result/mixin.rb +3 -3
- data/lib/bcdd/result/transitions/config.rb +26 -0
- data/lib/bcdd/result/transitions/listener.rb +51 -0
- data/lib/bcdd/result/transitions/listeners.rb +87 -0
- data/lib/bcdd/result/transitions/tracking/disabled.rb +4 -6
- data/lib/bcdd/result/transitions/tracking/enabled.rb +103 -24
- data/lib/bcdd/result/transitions/tracking.rb +8 -3
- data/lib/bcdd/result/transitions/tree.rb +36 -6
- data/lib/bcdd/result/transitions.rb +11 -14
- data/lib/bcdd/result/version.rb +1 -1
- data/lib/bcdd/result.rb +39 -22
- data/sig/bcdd/result/callable_and_then.rbs +60 -0
- data/sig/bcdd/result/config.rbs +3 -0
- data/sig/bcdd/result/context.rbs +65 -4
- data/sig/bcdd/result/error.rbs +9 -6
- data/sig/bcdd/result/expectations.rbs +4 -4
- data/sig/bcdd/result/ignored_types.rbs +9 -0
- data/sig/bcdd/result/transitions.rbs +107 -7
- data/sig/bcdd/result.rbs +10 -6
- metadata +48 -6
data/README.md
CHANGED
@@ -1,9 +1,11 @@
|
|
1
1
|
<p align="center">
|
2
2
|
<h1 align="center" id="-bcddresult">🔀 BCDD::Result</h1>
|
3
|
-
<p align="center"><i>
|
3
|
+
<p align="center"><i>Unleash a pragmatic and observable use of Result Pattern and Railway-Oriented Programming in Ruby.</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
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
|
+
<a href="https://codeclimate.com/github/B-CDD/result/maintainability"><img src="https://api.codeclimate.com/v1/badges/aa8360f8f012d7dedd62/maintainability" /></a>
|
8
|
+
<a href="https://codeclimate.com/github/B-CDD/result/test_coverage"><img src="https://api.codeclimate.com/v1/badges/aa8360f8f012d7dedd62/test_coverage" /></a>
|
7
9
|
</p>
|
8
10
|
</p>
|
9
11
|
|
@@ -70,14 +72,21 @@ Use it to enable the [Railway Oriented Programming](https://fsharpforfunandprofi
|
|
70
72
|
- [Module example (Singleton Methods)](#module-example-singleton-methods-1)
|
71
73
|
- [`BCDD::Result::Context::Expectations`](#bcddresultcontextexpectations)
|
72
74
|
- [Mixin add-ons](#mixin-add-ons)
|
73
|
-
|
74
|
-
|
75
|
-
- [
|
76
|
-
- [
|
77
|
-
- [`
|
78
|
-
- [`
|
79
|
-
- [`
|
75
|
+
- [`BCDD::Result.transitions`](#bcddresulttransitions)
|
76
|
+
- [`ids_tree` *versus* `ids_matrix`](#ids_tree-versus-ids_matrix)
|
77
|
+
- [Configuration](#configuration)
|
78
|
+
- [Turning on/off](#turning-onoff)
|
79
|
+
- [Setting a `trace_id` fetcher](#setting-a-trace_id-fetcher)
|
80
|
+
- [Setting a `listener`](#setting-a-listener)
|
81
|
+
- [Setting multiple `listeners`](#setting-multiple-listeners)
|
82
|
+
- [`BCDD::Result.configuration`](#bcddresultconfiguration)
|
80
83
|
- [`BCDD::Result.config`](#bcddresultconfig)
|
84
|
+
- [`BCDD::Result#and_then!`](#bcddresultand_then)
|
85
|
+
- [Dependency Injection](#dependency-injection-1)
|
86
|
+
- [Configuration](#configuration-1)
|
87
|
+
- [Analysis: Why is `and_then!` an Anti-pattern?](#analysis-why-is-and_then-an-anti-pattern)
|
88
|
+
- [`#and_then` versus `#and_then!`](#and_then-versus-and_then)
|
89
|
+
- [Analysis: Why is `#and_then` the antidote/standard?](#analysis-why-is-and_then-the-antidotestandard)
|
81
90
|
- [About](#about)
|
82
91
|
- [Development](#development)
|
83
92
|
- [Contributing](#contributing)
|
@@ -649,9 +658,9 @@ Divide.call(2, 2)
|
|
649
658
|
|
650
659
|
This method generates a module that any object can include or extend. It adds two methods to the target object: `Success()` and `Failure()`.
|
651
660
|
|
652
|
-
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
|
661
|
+
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 source.
|
653
662
|
|
654
|
-
Because the result has a
|
663
|
+
Because the result has a source, the `#and_then` method can call methods from it.
|
655
664
|
|
656
665
|
##### Class example (Instance Methods)
|
657
666
|
|
@@ -746,9 +755,9 @@ Divide.call(4, '2') #<BCDD::Result::Failure type=:invalid_arg value="arg2 must b
|
|
746
755
|
|
747
756
|
To use the `#and_then` method to call methods, they must use `Success()` and `Failure()` to produce the results.
|
748
757
|
|
749
|
-
If you try to use `BCDD::Result::
|
758
|
+
If you try to use `BCDD::Result::Success()`/`BCDD::Result::Failure()`, or results from another `BCDD::Result.mixin` instance with `#and_then`, it will raise an error because the sources are different.
|
750
759
|
|
751
|
-
**Note:** You can still use the block syntax, but all the results must be produced by the
|
760
|
+
**Note:** You can still use the block syntax, but all the results must be produced by the source's `Success()` and `Failure()` methods.
|
752
761
|
|
753
762
|
```ruby
|
754
763
|
module ValidateNonzero
|
@@ -794,13 +803,13 @@ Look at the error produced by the code above:
|
|
794
803
|
```ruby
|
795
804
|
Divide.call(2, 0)
|
796
805
|
|
797
|
-
# You cannot call #and_then and return a result that does not belong to the
|
798
|
-
# Expected
|
799
|
-
# Given
|
806
|
+
# You cannot call #and_then and return a result that does not belong to the same source! (BCDD::Result::Error::InvalidResultSource)
|
807
|
+
# Expected source: Divide
|
808
|
+
# Given source: ValidateNonzero
|
800
809
|
# Given result: #<BCDD::Result::Failure type=:division_by_zero value="arg2 must not be zero">
|
801
810
|
```
|
802
811
|
|
803
|
-
In order to fix this, you must handle the result produced by `ValidateNonzero.call()` and return a result that belongs to the
|
812
|
+
In order to fix this, you must handle the result produced by `ValidateNonzero.call()` and return a result that belongs to the same source.
|
804
813
|
|
805
814
|
```ruby
|
806
815
|
module ValidateNonzero
|
@@ -832,7 +841,8 @@ module Divide
|
|
832
841
|
end
|
833
842
|
|
834
843
|
def validate_nonzero(numbers)
|
835
|
-
# In this case we are handling the
|
844
|
+
# In this case we are handling the result from other source
|
845
|
+
# and returning our own
|
836
846
|
ValidateNonzero.call(numbers).handle do |on|
|
837
847
|
on.success { |numbers| Success(:ok, numbers) }
|
838
848
|
|
@@ -858,8 +868,8 @@ Divide.call(2, 0)
|
|
858
868
|
|
859
869
|
##### Dependency Injection
|
860
870
|
|
861
|
-
The `BCDD::Result#and_then` accepts a second argument that will be used to share a value with the
|
862
|
-
To receive this argument, the
|
871
|
+
The `BCDD::Result#and_then` accepts a second argument that will be used to share a value with the source's method.
|
872
|
+
To receive this argument, the source's method must have an arity of two, where the first argument will be the result value and the second will be the injected value.
|
863
873
|
|
864
874
|
```ruby
|
865
875
|
require 'logger'
|
@@ -1411,7 +1421,7 @@ result = Divide.new.call(4, 2)
|
|
1411
1421
|
|
1412
1422
|
# The example below shows an error because the :ok type is not allowed.
|
1413
1423
|
# But look at the allowed types have only one type (:division_completed).
|
1414
|
-
# This is because the :
|
1424
|
+
# This is because the :_continue_ type is ignored by the expectations.
|
1415
1425
|
#
|
1416
1426
|
result.success?(:ok)
|
1417
1427
|
# type :ok is not allowed. Allowed types: :division_completed (BCDD::Result::Contract::Error::UnexpectedType)
|
@@ -1776,7 +1786,9 @@ Division.call(14, 0)
|
|
1776
1786
|
#<BCDD::Result::Context::Failure type=:division_by_zero value={:message=>"arg2 must not be zero"}>
|
1777
1787
|
```
|
1778
1788
|
|
1779
|
-
|
1789
|
+
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
1790
|
+
|
1791
|
+
## `BCDD::Result.transitions`
|
1780
1792
|
|
1781
1793
|
Use `BCDD::Result.transitions(&block)` to track all transitions in the same or between different operations (it works with `BCDD::Result` and `BCDD::Result::Context`). When there is a nesting of transition blocks, this mechanism will be able to correlate parent and child blocks and present the duration of all operations in milliseconds.
|
1782
1794
|
|
@@ -1797,7 +1809,7 @@ class Division
|
|
1797
1809
|
|
1798
1810
|
private
|
1799
1811
|
|
1800
|
-
ValidNumber = ->(arg) { arg.is_a?(Numeric) && arg
|
1812
|
+
ValidNumber = ->(arg) { arg.is_a?(Numeric) && (!arg.respond_to?(:finite?) || arg.finite?) }
|
1801
1813
|
|
1802
1814
|
def require_numbers((arg1, arg2))
|
1803
1815
|
ValidNumber[arg1] or return Failure(:invalid_arg, 'arg1 must be a valid number')
|
@@ -1844,83 +1856,85 @@ result = SumDivisionsByTwo.call(20, 10)
|
|
1844
1856
|
|
1845
1857
|
result.transitions
|
1846
1858
|
{
|
1847
|
-
:version =>1,
|
1859
|
+
:version => 1,
|
1848
1860
|
:metadata => {
|
1849
|
-
:duration => 0,
|
1850
|
-
:
|
1861
|
+
:duration => 0, # milliseconds
|
1862
|
+
:trace_id => nil, # can be set through configuration
|
1863
|
+
:ids_tree => [0, [[1, []], [2, []]]],
|
1864
|
+
:ids_matrix => {0 => [0, 0], 1 => [1, 1], 2 => [2, 1]}
|
1851
1865
|
},
|
1852
|
-
:records
|
1866
|
+
:records=> [
|
1853
1867
|
{
|
1854
|
-
:root=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
|
1855
|
-
:parent=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
|
1856
|
-
:current=>{:id=>1, :name=>"Division", :desc=>"divide two numbers"},
|
1857
|
-
:result=>{:kind=>:success, :type=>:
|
1858
|
-
:and_then=>{},
|
1859
|
-
:time=>2024-01-02
|
1868
|
+
:root => {:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
|
1869
|
+
:parent => {:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
|
1870
|
+
:current => {:id=>1, :name=>"Division", :desc=>"divide two numbers"},
|
1871
|
+
:result => {:kind=>:success, :type=>:_given_, :value=>[20, 2], :source=><Division:0x0000000102fd7ed0>},
|
1872
|
+
:and_then => {},
|
1873
|
+
:time => 2024-01-26 02:53:11.310346 UTC
|
1860
1874
|
},
|
1861
1875
|
{
|
1862
|
-
:root=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
|
1863
|
-
:parent=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
|
1864
|
-
:current=>{:id=>1, :name=>"Division", :desc=>"divide two numbers"},
|
1865
|
-
:result=>{:kind=>:success, :type=>:
|
1866
|
-
:and_then=>{:type=>:method, :arg=>nil, :
|
1867
|
-
:time=>2024-01-02
|
1876
|
+
:root => {:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
|
1877
|
+
:parent => {:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
|
1878
|
+
:current => {:id=>1, :name=>"Division", :desc=>"divide two numbers"},
|
1879
|
+
:result => {:kind=>:success, :type=>:_continue_, :value=>[20, 2], :source=><Division:0x0000000102fd7ed0>},
|
1880
|
+
:and_then => {:type=>:method, :arg=>nil, :method_name=>:require_numbers},
|
1881
|
+
:time => 2024-01-26 02:53:11.310392 UTC
|
1868
1882
|
},
|
1869
1883
|
{
|
1870
|
-
:root=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
|
1871
|
-
:parent=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
|
1872
|
-
:current=>{:id=>1, :name=>"Division", :desc=>"divide two numbers"},
|
1873
|
-
:result=>{:kind=>:success, :type=>:
|
1874
|
-
:and_then=>{:type=>:method, :arg=>nil, :
|
1875
|
-
:time=>2024-01-02
|
1884
|
+
:root => {:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
|
1885
|
+
:parent => {:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
|
1886
|
+
:current => {:id=>1, :name=>"Division", :desc=>"divide two numbers"},
|
1887
|
+
:result => {:kind=>:success, :type=>:_continue_, :value=>[20, 2], :source=><Division:0x0000000102fd7ed0>},
|
1888
|
+
:and_then => {:type=>:method, :arg=>nil, :method_name=>:check_for_zeros},
|
1889
|
+
:time=>2024-01-26 02:53:11.310403 UTC
|
1876
1890
|
},
|
1877
1891
|
{
|
1878
|
-
:root=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
|
1879
|
-
:parent=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
|
1880
|
-
:current=>{:id=>1, :name=>"Division", :desc=>"divide two numbers"},
|
1881
|
-
:result=>{:kind=>:success, :type=>:division_completed, :value=>10},
|
1882
|
-
:and_then=>{:type=>:method, :arg=>nil, :
|
1883
|
-
:time=>2024-01-02
|
1892
|
+
:root => {:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
|
1893
|
+
:parent => {:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
|
1894
|
+
:current => {:id=>1, :name=>"Division", :desc=>"divide two numbers"},
|
1895
|
+
:result => {:kind=>:success, :type=>:division_completed, :value=>10, :source=><Division:0x0000000102fd7ed0>},
|
1896
|
+
:and_then => {:type=>:method, :arg=>nil, :method_name=>:divide},
|
1897
|
+
:time => 2024-01-26 02:53:11.310409 UTC
|
1884
1898
|
},
|
1885
1899
|
{
|
1886
|
-
:root=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
|
1887
|
-
:parent=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
|
1888
|
-
:current=>{:id=>2, :name=>"Division", :desc=>"divide two numbers"},
|
1889
|
-
:result=>{:kind=>:success, :type=>:
|
1890
|
-
:and_then=>{},
|
1891
|
-
:time=>2024-01-02
|
1900
|
+
:root => {:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
|
1901
|
+
:parent => {:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
|
1902
|
+
:current => {:id=>2, :name=>"Division", :desc=>"divide two numbers"},
|
1903
|
+
:result => {:kind=>:success, :type=>:_given_, :value=>[10, 2], :source=><Division:0x0000000102fd6378>},
|
1904
|
+
:and_then => {},
|
1905
|
+
:time => 2024-01-26 02:53:11.310424 UTC
|
1892
1906
|
},
|
1893
1907
|
{
|
1894
|
-
:root=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
|
1895
|
-
:parent=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
|
1896
|
-
:current=>{:id=>2, :name=>"Division", :desc=>"divide two numbers"},
|
1897
|
-
:result=>{:kind=>:success, :type=>:
|
1898
|
-
:and_then=>{:type=>:method, :arg=>nil, :
|
1899
|
-
:time=>2024-01-02
|
1908
|
+
:root => {:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
|
1909
|
+
:parent => {:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
|
1910
|
+
:current => {:id=>2, :name=>"Division", :desc=>"divide two numbers"},
|
1911
|
+
:result => {:kind=>:success, :type=>:_continue_, :value=>[10, 2], :source=><Division:0x0000000102fd6378>},
|
1912
|
+
:and_then => {:type=>:method, :arg=>nil, :method_name=>:require_numbers},
|
1913
|
+
:time => 2024-01-26 02:53:11.310428 UTC
|
1900
1914
|
},
|
1901
1915
|
{
|
1902
|
-
:root=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
|
1903
|
-
:parent=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
|
1904
|
-
:current=>{:id=>2, :name=>"Division", :desc=>"divide two numbers"},
|
1905
|
-
:result=>{:kind=>:success, :type=>:
|
1906
|
-
:and_then=>{:type=>:method, :arg=>nil, :
|
1907
|
-
:time=>2024-01-02
|
1916
|
+
:root => {:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
|
1917
|
+
:parent => {:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
|
1918
|
+
:current => {:id=>2, :name=>"Division", :desc=>"divide two numbers"},
|
1919
|
+
:result => {:kind=>:success, :type=>:_continue_, :value=>[10, 2], :source=><Division:0x0000000102fd6378>},
|
1920
|
+
:and_then => {:type=>:method, :arg=>nil, :method_name=>:check_for_zeros},
|
1921
|
+
:time => 2024-01-26 02:53:11.310431 UTC
|
1908
1922
|
},
|
1909
1923
|
{
|
1910
|
-
:root=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
|
1911
|
-
:parent=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
|
1912
|
-
:current=>{:id=>2, :name=>"Division", :desc=>"divide two numbers"},
|
1913
|
-
:result=>{:kind=>:success, :type=>:division_completed, :value=>5},
|
1914
|
-
:and_then=>{:type=>:method, :arg=>nil, :
|
1915
|
-
:time=>2024-01-02
|
1924
|
+
:root => {:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
|
1925
|
+
:parent => {:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
|
1926
|
+
:current => {:id=>2, :name=>"Division", :desc=>"divide two numbers"},
|
1927
|
+
:result => {:kind=>:success, :type=>:division_completed, :value=>5, :source=><Division:0x0000000102fd6378>},
|
1928
|
+
:and_then => {:type=>:method, :arg=>nil, :method_name=>:divide},
|
1929
|
+
:time => 2024-01-26 02:53:11.310434 UTC
|
1916
1930
|
},
|
1917
1931
|
{
|
1918
|
-
:root=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
|
1919
|
-
:parent=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
|
1920
|
-
:current=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
|
1921
|
-
:result=>{:kind=>:success, :type=>:sum, :value=>15},
|
1922
|
-
:and_then=>{},
|
1923
|
-
:time=>2024-01-02
|
1932
|
+
:root => {:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
|
1933
|
+
:parent => {:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
|
1934
|
+
:current => {:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
|
1935
|
+
:result => {:kind=>:success, :type=>:sum, :value=>15, :source=>SumDivisionsByTwo},
|
1936
|
+
:and_then => {},
|
1937
|
+
:time => 2024-01-26 02:53:11.310444 UTC
|
1924
1938
|
}
|
1925
1939
|
]
|
1926
1940
|
}
|
@@ -1928,7 +1942,46 @@ result.transitions
|
|
1928
1942
|
|
1929
1943
|
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
1930
1944
|
|
1931
|
-
|
1945
|
+
### `ids_tree` *versus* `ids_matrix`
|
1946
|
+
|
1947
|
+
The `:ids_matrix`. It is a simplification of the `:ids_tree` property (a graph/tree representation of the transitions ids).
|
1948
|
+
|
1949
|
+
The matrix rows are the direct transitions from the root transition block, and the columns are the transitions nested from the direct transitions.
|
1950
|
+
|
1951
|
+
Use these data structures to build your own visualization of the transitions.
|
1952
|
+
|
1953
|
+
> Check out [Transitions Listener example](examples/single_listener/lib/single_transitions_listener.rb) to see how a listener can be used to build a visualization of the transitions, using these properties.
|
1954
|
+
|
1955
|
+
```ruby
|
1956
|
+
# ids_tree #
|
1957
|
+
0 # [0, [
|
1958
|
+
|- 1 # [1, [[2, []]]],
|
1959
|
+
| |- 2 # [3, []],
|
1960
|
+
|- 3 # [4, [
|
1961
|
+
|- 4 # [5, []],
|
1962
|
+
| |- 5 # [6, [[7, []]]]
|
1963
|
+
| |- 6 # ]],
|
1964
|
+
| |- 7 # [8, []]
|
1965
|
+
|- 8 # ]]
|
1966
|
+
|
1967
|
+
# ids_matrix # {
|
1968
|
+
0 | 1 | 2 | 3 | 4 # 0 => [0, 0],
|
1969
|
+
- | - | - | - | - # 1 => [1, 1],
|
1970
|
+
0 | | | | # 2 => [1, 2],
|
1971
|
+
1 | 1 | 2 | | # 3 => [2, 1],
|
1972
|
+
2 | 3 | | | # 4 => [3, 1],
|
1973
|
+
3 | 4 | 5 | 6 | 7 # 5 => [3, 2],
|
1974
|
+
4 | 8 | | | # 6 => [3, 3],
|
1975
|
+
# 7 => [3, 4],
|
1976
|
+
# 8 => [4, 1]
|
1977
|
+
# }
|
1978
|
+
```
|
1979
|
+
|
1980
|
+
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
1981
|
+
|
1982
|
+
### Configuration
|
1983
|
+
|
1984
|
+
#### Turning on/off
|
1932
1985
|
|
1933
1986
|
You can use `BCDD::Result.config.feature.disable!(:transitions)` and `BCDD::Result.config.feature.enable!(:transitions)` to turn on/off the `BCDD::Result.transitions` feature.
|
1934
1987
|
|
@@ -1942,15 +1995,179 @@ result = SumDivisionsByTwo.call(20, 10)
|
|
1942
1995
|
|
1943
1996
|
result.transitions
|
1944
1997
|
|
1945
|
-
{:version=>1, :records=>[], :metadata=>{:duration=>0, :
|
1998
|
+
{:version=>1, :records=>[], :metadata=>{:duration=>0, :ids_tree=>[]}}
|
1999
|
+
```
|
2000
|
+
|
2001
|
+
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
2002
|
+
|
2003
|
+
#### Setting a `trace_id` fetcher
|
2004
|
+
|
2005
|
+
You can define a lambda (arity 0) to fetch the trace_id. This lambda will be called before the first transition and will be used to set the `:trace_id` in the `:metadata` property.
|
2006
|
+
|
2007
|
+
Use to correlate different or the same operation (executed multiple times).
|
2008
|
+
|
2009
|
+
```ruby
|
2010
|
+
BCDD::Result.config.transitions.trace_id = -> { Thread.current[:bcdd_result_transitions_trace_id] }
|
2011
|
+
```
|
2012
|
+
|
2013
|
+
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
2014
|
+
|
2015
|
+
#### Setting a `listener`
|
2016
|
+
|
2017
|
+
You can define a listener to be called during the result transitions tracking (check out [this example](examples/single_listener/lib/single_transitions_listener.rb)). It must be a class that includes `BCDD::Result::Transitions::Listener`.
|
2018
|
+
|
2019
|
+
Use it to build your additional logic on top of the transitions tracking. Examples:
|
2020
|
+
- Log the transitions.
|
2021
|
+
- Perform a trace of the transitions.
|
2022
|
+
- Instrument the transitions (measure/report).
|
2023
|
+
- Build a visualization of the transitions (Diagrams, using the `records` + `:ids_tree` and `:ids_matrix` properties).
|
2024
|
+
|
2025
|
+
After implementing your listener, you can set it to the `BCDD::Result.config.transitions.listener=`:
|
2026
|
+
|
2027
|
+
```ruby
|
2028
|
+
BCDD::Result.config.transitions.listener = MyTransitionsListener
|
2029
|
+
```
|
2030
|
+
|
2031
|
+
See the example below to understand how to implement one:
|
2032
|
+
|
2033
|
+
```ruby
|
2034
|
+
class MyTransitionsListener
|
2035
|
+
include BCDD::Result::Transitions::Listener
|
2036
|
+
|
2037
|
+
# A listener will be initialized before the first transition, and it is discarded after the last one.
|
2038
|
+
def initialize
|
2039
|
+
end
|
2040
|
+
|
2041
|
+
# This method will be called before each transition block.
|
2042
|
+
# The parent transition block will be called first in the case of nested transition blocks.
|
2043
|
+
#
|
2044
|
+
# @param scope: {:id=>1, :name=>"SomeOperation", :desc=>"Optional description"}
|
2045
|
+
def on_start(scope:)
|
2046
|
+
end
|
2047
|
+
|
2048
|
+
# This method will wrap all the transitions in the same block.
|
2049
|
+
# It can be used to perform an instrumentation (measure/report) of the transitions.
|
2050
|
+
#
|
2051
|
+
# @param scope: {:id=>1, :name=>"SomeOperation", :desc=>"Optional description"}
|
2052
|
+
def around_transitions(scope:)
|
2053
|
+
yield
|
2054
|
+
end
|
2055
|
+
|
2056
|
+
# This method will wrap each and_then call.
|
2057
|
+
# It can be used to perform an instrumentation (measure/report) of the and_then calls.
|
2058
|
+
#
|
2059
|
+
# @param scope: {:id=>1, :name=>"SomeOperation", :desc=>"Optional description"}
|
2060
|
+
# @param and_then:
|
2061
|
+
# {:type=>:block, :arg=>:some_injected_value}
|
2062
|
+
# {:type=>:method, :arg=>:some_injected_value, :method_name=>:some_method_name}
|
2063
|
+
def around_and_then(scope:, and_then:)
|
2064
|
+
yield
|
2065
|
+
end
|
2066
|
+
|
2067
|
+
# This method will be called after each result recording/tracking.
|
2068
|
+
#
|
2069
|
+
# @param record:
|
2070
|
+
# {
|
2071
|
+
# :root => {:id=>0, :name=>"RootOperation", :desc=>nil},
|
2072
|
+
# :parent => {:id=>0, :name=>"RootOperation", :desc=>nil},
|
2073
|
+
# :current => {:id=>1, :name=>"SomeOperation", :desc=>nil},
|
2074
|
+
# :result => {:kind=>:success, :type=>:_continue_, :value=>{some: :thing}, :source=><MyProcess:0x0000000102fd6378>},
|
2075
|
+
# :and_then => {:type=>:method, :arg=>nil, :method_name=>:some_method},
|
2076
|
+
# :time => 2024-01-26 02:53:11.310431 UTC
|
2077
|
+
# }
|
2078
|
+
def on_record(record:)
|
2079
|
+
end
|
2080
|
+
|
2081
|
+
# This method will be called at the end of the transitions tracking.
|
2082
|
+
#
|
2083
|
+
# @param transitions:
|
2084
|
+
# {
|
2085
|
+
# :version => 1,
|
2086
|
+
# :metadata => {
|
2087
|
+
# :duration => 0,
|
2088
|
+
# :trace_id => nil,
|
2089
|
+
# :ids_tree => [0, [[1, []], [2, []]]],
|
2090
|
+
# :ids_matrix => {0 => [0, 0], 1 => [1, 1], 2 => [2, 1]}
|
2091
|
+
# },
|
2092
|
+
# :records => [
|
2093
|
+
# # ...
|
2094
|
+
# ]
|
2095
|
+
# }
|
2096
|
+
def on_finish(transitions:)
|
2097
|
+
end
|
2098
|
+
|
2099
|
+
# This method will be called when an exception is raised during the transitions tracking.
|
2100
|
+
#
|
2101
|
+
# @param exception: Exception
|
2102
|
+
# @param transitions: Hash
|
2103
|
+
def before_interruption(exception:, transitions:)
|
2104
|
+
end
|
2105
|
+
end
|
2106
|
+
```
|
2107
|
+
|
2108
|
+
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
2109
|
+
|
2110
|
+
#### Setting multiple `listeners`
|
2111
|
+
|
2112
|
+
You can use `BCDD::Result::Transitions::Listeners[]` to creates a listener of listeners (check out [this example](examples/multiple_listeners/Rakefile)), which will be called in the order they were added.
|
2113
|
+
|
2114
|
+
**Attention:** It only allows one listener to handle `around_and_then` and another `around_transitions` events.
|
2115
|
+
|
2116
|
+
> The example below defines different listeners to handle `around_and_then` and `around_transitions,` but it is also possible to define a listener to handle both.
|
2117
|
+
|
2118
|
+
```ruby
|
2119
|
+
class AroundAndThenListener
|
2120
|
+
include BCDD::Result::Transitions::Listener
|
2121
|
+
|
2122
|
+
# It must be a static/singleton method.
|
2123
|
+
def self.around_and_then?
|
2124
|
+
true
|
2125
|
+
end
|
2126
|
+
|
2127
|
+
def around_and_then(scope:, and_then:)
|
2128
|
+
#...
|
2129
|
+
end
|
2130
|
+
end
|
2131
|
+
|
2132
|
+
class AroundTransitionsListener
|
2133
|
+
include BCDD::Result::Transitions::Listener
|
2134
|
+
|
2135
|
+
# It must be a static/singleton method.
|
2136
|
+
def self.around_transitions?
|
2137
|
+
true
|
2138
|
+
end
|
2139
|
+
|
2140
|
+
def around_transitions(scope:)
|
2141
|
+
#...
|
2142
|
+
end
|
2143
|
+
end
|
2144
|
+
|
2145
|
+
class MyTransitionsListener
|
2146
|
+
include BCDD::Result::Transitions::Listener
|
2147
|
+
end
|
2148
|
+
```
|
2149
|
+
|
2150
|
+
How to use it:
|
2151
|
+
|
2152
|
+
```ruby
|
2153
|
+
# The listeners will be called in the order they were added.
|
2154
|
+
BCDD::Result.config.transitions.listener = BCDD::Result::Transitions::Listeners[
|
2155
|
+
MyTransitionsListener,
|
2156
|
+
AroundAndThenListener,
|
2157
|
+
AroundTransitionsListener
|
2158
|
+
]
|
1946
2159
|
```
|
1947
2160
|
|
2161
|
+
> Check out [this example](examples/multiple_listeners) to see a listener to print the transitions and another to store them in the database.
|
2162
|
+
|
1948
2163
|
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
1949
2164
|
|
1950
|
-
|
2165
|
+
## `BCDD::Result.configuration`
|
1951
2166
|
|
1952
2167
|
The `BCDD::Result.configuration` allows you to configure default behaviors for `BCDD::Result` and `BCDD::Result::Context` through a configuration block. After using it, the configuration is frozen, ensuring the expected behaviors for your application.
|
1953
2168
|
|
2169
|
+
> Note: You can use `BCDD::Result.configuration(freeze: false) {}` to avoid the freezing. This can be useful in tests. Please be sure to use it with caution.
|
2170
|
+
|
1954
2171
|
```ruby
|
1955
2172
|
BCDD::Result.configuration do |config|
|
1956
2173
|
config.addon.enable!(:given, :continue)
|
@@ -1967,13 +2184,13 @@ Use `disable!` to disable a feature and `enable!` to enable it.
|
|
1967
2184
|
|
1968
2185
|
Let's see what each configuration in the example above does:
|
1969
2186
|
|
1970
|
-
|
2187
|
+
### `config.addon.enable!(:given, :continue)` <!-- omit in toc -->
|
1971
2188
|
|
1972
2189
|
This configuration enables the `Continue()` method for `BCDD::Result.mixin`, `BCDD::Result::Context.mixin`, `BCDD::Result::Expectation.mixin`, and `BCDD::Result::Context::Expectation.mixin`. Link to documentations: [(1)](#add-ons) [(2)](#mixin-add-ons).
|
1973
2190
|
|
1974
2191
|
It is also enabling the `Given()` which is already enabled by default. Link to documentation: [(1)](#add-ons) [(2)](#mixin-add-ons).
|
1975
2192
|
|
1976
|
-
|
2193
|
+
### `config.constant_alias.enable!('Result', 'BCDD::Context')` <!-- omit in toc -->
|
1977
2194
|
|
1978
2195
|
This configuration make `Result` a constant alias for `BCDD::Result`, and `BCDD::Context` a constant alias for `BCDD::Result::Context`.
|
1979
2196
|
|
@@ -1981,23 +2198,23 @@ Link to documentations:
|
|
1981
2198
|
- [Result alias](#bcddresult-versus-result)
|
1982
2199
|
- [Context aliases](#constant-aliases)
|
1983
2200
|
|
1984
|
-
|
2201
|
+
### `config.pattern_matching.disable!(:nil_as_valid_value_checking)` <!-- omit in toc -->
|
1985
2202
|
|
1986
2203
|
This configuration disables the `nil_as_valid_value_checking` for `BCDD::Result` and `BCDD::Result::Context`. Link to [documentation](#pattern-matching-support).
|
1987
2204
|
|
1988
|
-
|
1989
|
-
|
1990
|
-
#### `config.feature.disable!(:expectations)`
|
2205
|
+
### `config.feature.disable!(:expectations)` <!-- omit in toc -->
|
1991
2206
|
|
1992
2207
|
This configuration turns off the expectations for `BCDD::Result` and `BCDD::Result::Context`. The expectations are helpful in development and test environments, but they can be disabled in production environments for performance gain.
|
1993
2208
|
|
1994
2209
|
PS: I'm using `::Rails.env.production?` to check the environment, but you can use any logic you want.
|
1995
2210
|
|
2211
|
+
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
2212
|
+
|
1996
2213
|
### `BCDD::Result.config`
|
1997
2214
|
|
1998
2215
|
The `BCDD::Result.config` allows you to access the current configuration.
|
1999
2216
|
|
2000
|
-
**BCDD::Result.config.addon**
|
2217
|
+
#### **BCDD::Result.config.addon** <!-- omit in toc -->
|
2001
2218
|
|
2002
2219
|
```ruby
|
2003
2220
|
BCDD::Result.config.addon.enabled?(:continue)
|
@@ -2026,7 +2243,7 @@ BCDD::Result.config.addon.options
|
|
2026
2243
|
# }
|
2027
2244
|
```
|
2028
2245
|
|
2029
|
-
**BCDD::Result.config.constant_alias**
|
2246
|
+
#### **BCDD::Result.config.constant_alias** <!-- omit in toc -->
|
2030
2247
|
|
2031
2248
|
```ruby
|
2032
2249
|
BCDD::Result.config.constant_alias.enabled?('Result')
|
@@ -2041,7 +2258,7 @@ BCDD::Result.config.constant_alias.options
|
|
2041
2258
|
# }
|
2042
2259
|
```
|
2043
2260
|
|
2044
|
-
**BCDD::Result.config.pattern_matching**
|
2261
|
+
#### **BCDD::Result.config.pattern_matching** <!-- omit in toc -->
|
2045
2262
|
|
2046
2263
|
```ruby
|
2047
2264
|
BCDD::Result.config.pattern_matching.enabled?(:nil_as_valid_value_checking)
|
@@ -2058,7 +2275,7 @@ BCDD::Result.config.pattern_matching.options
|
|
2058
2275
|
# }
|
2059
2276
|
```
|
2060
2277
|
|
2061
|
-
**BCDD::Result.config.feature**
|
2278
|
+
#### **BCDD::Result.config.feature** <!-- omit in toc -->
|
2062
2279
|
|
2063
2280
|
```ruby
|
2064
2281
|
BCDD::Result.config.feature.enabled?(:expectations)
|
@@ -2078,13 +2295,209 @@ BCDD::Result.config.feature.options
|
|
2078
2295
|
# "BCDD::Result",
|
2079
2296
|
# "BCDD::Result::Context"
|
2080
2297
|
# ]
|
2081
|
-
# }
|
2298
|
+
# },
|
2299
|
+
# :and_then!=>{
|
2300
|
+
# :enabled=>false,
|
2301
|
+
# :affects=>[
|
2302
|
+
# "BCDD::Result",
|
2303
|
+
# "BCDD::Result::Context"
|
2304
|
+
# ]
|
2305
|
+
# },
|
2082
2306
|
# }
|
2083
2307
|
```
|
2084
2308
|
|
2309
|
+
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
2310
|
+
|
2311
|
+
## `BCDD::Result#and_then!`
|
2312
|
+
|
2313
|
+
In the Ruby ecosystem, several gems facilitate operation composition using classes and modules. Two notable examples are the `interactor` gem and the `u-case` gem.
|
2314
|
+
|
2315
|
+
**`interactor` gem example**
|
2316
|
+
|
2317
|
+
```ruby
|
2318
|
+
class PlaceOrder
|
2319
|
+
include Interactor::Organizer
|
2320
|
+
|
2321
|
+
organize CreateOrder,
|
2322
|
+
PayOrder,
|
2323
|
+
SendOrderConfirmation,
|
2324
|
+
NotifyAdmins
|
2325
|
+
end
|
2326
|
+
```
|
2327
|
+
|
2328
|
+
**`u-case` gem example**
|
2329
|
+
|
2330
|
+
```ruby
|
2331
|
+
class PlaceOrder < Micro::Case
|
2332
|
+
flow CreateOrder, PayOrder, SendOrderConfirmation, NotifyAdmins
|
2333
|
+
end
|
2334
|
+
|
2335
|
+
# Alternative approach
|
2336
|
+
class PlaceOrder < Micro::Case
|
2337
|
+
def call!
|
2338
|
+
call(CreateOrder)
|
2339
|
+
.then(PayOrder)
|
2340
|
+
.then(SendOrderConfirmation)
|
2341
|
+
.then(NotifyAdmins)
|
2342
|
+
end
|
2343
|
+
end
|
2344
|
+
```
|
2345
|
+
|
2346
|
+
To facilitate migration for users accustomed to the above approaches, `bcdd-result` includes the `BCDD::Result#and_then!`/`BCDD::Result::Context#and_then!` methods, which will invoke the method `call` of the given operation and expect it to return a `BCDD::Result`/`BCDD::Result::Context` object.
|
2347
|
+
|
2348
|
+
```ruby
|
2349
|
+
BCDD::Result.configure do |config|
|
2350
|
+
config.feature.enable!(:and_then!)
|
2351
|
+
end
|
2352
|
+
|
2353
|
+
class PlaceOrder
|
2354
|
+
include BCDD::Result::Context.mixin
|
2355
|
+
|
2356
|
+
def call(**input)
|
2357
|
+
Given(input)
|
2358
|
+
.and_then!(CreateOrder.new)
|
2359
|
+
.and_then!(PayOrder.new)
|
2360
|
+
.and_then!(SendOrderConfirmation.new)
|
2361
|
+
.and_then!(NotifyAdmins.new)
|
2362
|
+
end
|
2363
|
+
end
|
2364
|
+
```
|
2365
|
+
|
2366
|
+
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
2367
|
+
|
2368
|
+
#### Dependency Injection
|
2369
|
+
|
2370
|
+
Like `#and_then`, `#and_then!` also supports an additional argument for dependency injection.
|
2371
|
+
|
2372
|
+
**In BCDD::Result**
|
2373
|
+
|
2374
|
+
```ruby
|
2375
|
+
class PlaceOrder
|
2376
|
+
include BCDD::Result.mixin
|
2377
|
+
|
2378
|
+
def call(input, logger:)
|
2379
|
+
Given(input)
|
2380
|
+
.and_then!(CreateOrder.new, logger)
|
2381
|
+
# Further method chaining...
|
2382
|
+
end
|
2383
|
+
end
|
2384
|
+
```
|
2385
|
+
|
2386
|
+
**In BCDD::Result::Context**
|
2387
|
+
|
2388
|
+
```ruby
|
2389
|
+
class PlaceOrder
|
2390
|
+
include BCDD::Result::Context.mixin
|
2391
|
+
|
2392
|
+
def call(logger:, **input)
|
2393
|
+
Given(input)
|
2394
|
+
.and_then!(CreateOrder.new, logger:)
|
2395
|
+
# Further method chaining...
|
2396
|
+
end
|
2397
|
+
end
|
2398
|
+
```
|
2399
|
+
|
2400
|
+
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
2401
|
+
|
2402
|
+
#### Configuration
|
2403
|
+
|
2404
|
+
```ruby
|
2405
|
+
BCDD::Result.configure do |config|
|
2406
|
+
config.feature.enable!(:and_then!)
|
2407
|
+
|
2408
|
+
config.and_then!.default_method_name_to_call = :perform
|
2409
|
+
end
|
2410
|
+
```
|
2411
|
+
|
2412
|
+
**Explanation:**
|
2413
|
+
|
2414
|
+
- `enable!(:and_then!)`: Activates the `and_then!` feature.
|
2415
|
+
|
2416
|
+
- `default_method_name_to_call`: Sets a default method other than `:call` for `and_then!`.
|
2417
|
+
|
2418
|
+
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
2419
|
+
|
2420
|
+
#### Analysis: Why is `and_then!` an Anti-pattern?
|
2421
|
+
|
2422
|
+
The `and_then!` approach, despite its brevity, introduces several issues:
|
2423
|
+
|
2424
|
+
- **Lack of Clarity:** The input/output relationship between the steps is not apparent.
|
2425
|
+
|
2426
|
+
- **Steps Coupling:** Each operation becomes interdependent (high coupling), complicating implementation and compromising the reusability of these operations.
|
2427
|
+
|
2428
|
+
We recommend cautious use of `#and_then!`. Due to these issues, it is turned off by default and considered an antipattern.
|
2429
|
+
|
2430
|
+
It should be a temporary solution, primarily for assisting in migration from another to gem to `bcdd-result`.
|
2431
|
+
|
2432
|
+
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
2433
|
+
|
2434
|
+
#### `#and_then` versus `#and_then!`
|
2435
|
+
|
2436
|
+
The main difference between the `#and_then` and `#and_then!` is that the latter does not check the result source. However, as a drawback, the result source will change.
|
2437
|
+
|
2438
|
+
Attention: to ensure the correct behavior, do not mix `#and_then` and `#and_then!` in the same result chain.
|
2439
|
+
|
2440
|
+
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
2441
|
+
|
2442
|
+
#### Analysis: Why is `#and_then` the antidote/standard?
|
2443
|
+
|
2444
|
+
The `BCDD::Result#and_then`/`BCDD::Result::Context#and_then` methods diverge from the above approach by requiring explicit invocation and mapping of the outcomes at each process step. This approach has the following advantages:
|
2445
|
+
|
2446
|
+
- **Clarity:** The input/output relationship between the steps is apparent and highly understandable.
|
2447
|
+
|
2448
|
+
- **Steps uncoupling:** Each operation becomes independent (low coupling). You can even map a failure result to a success (and vice versa).
|
2449
|
+
|
2450
|
+
See this example to understand what your code should look like:
|
2451
|
+
|
2452
|
+
```ruby
|
2453
|
+
class PlaceOrder
|
2454
|
+
include BCDD::Result::Context.mixin(config: { addon: { continue: true } })
|
2455
|
+
|
2456
|
+
def call(**input)
|
2457
|
+
Given(input)
|
2458
|
+
.and_then(:create_order)
|
2459
|
+
.and_then(:pay_order)
|
2460
|
+
.and_then(:send_order_confirmation)
|
2461
|
+
.and_then(:notify_admins)
|
2462
|
+
.and_expose(:order_placed, %i[order])
|
2463
|
+
end
|
2464
|
+
|
2465
|
+
private
|
2466
|
+
|
2467
|
+
def create_order(customer:, products:)
|
2468
|
+
CreateOrder.new.call(customer:, products:).handle do |on|
|
2469
|
+
on.success { |output| Continue(order: output[:order]) }
|
2470
|
+
on.failure { |error| Failure(:order_creation_failed, error:) }
|
2471
|
+
end
|
2472
|
+
end
|
2473
|
+
|
2474
|
+
def pay_order(customer:, order:, payment_method:, **)
|
2475
|
+
PayOrder.new.call(customer:, payment_method:, order:).handle do |on|
|
2476
|
+
on.success { |output| Continue(payment: output[:payment]) }
|
2477
|
+
on.failure { |error| Failure(:order_payment_failed, error:) }
|
2478
|
+
end
|
2479
|
+
end
|
2480
|
+
|
2481
|
+
def send_order_confirmation(customer:, order:, payment:, **)
|
2482
|
+
SendOrderConfirmation.new.call(customer:, order:, payment:).handle do |on|
|
2483
|
+
on.success { Continue() }
|
2484
|
+
on.failure { |error| Failure(:order_confirmation_failed, error:) }
|
2485
|
+
end
|
2486
|
+
end
|
2487
|
+
|
2488
|
+
def notify_admins(customer:, order:, payment:, **)
|
2489
|
+
NotifyAdmins.new.call(customer:, order:, payment:)
|
2490
|
+
|
2491
|
+
Continue()
|
2492
|
+
end
|
2493
|
+
end
|
2494
|
+
```
|
2495
|
+
|
2496
|
+
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
2497
|
+
|
2085
2498
|
## About
|
2086
2499
|
|
2087
|
-
[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
|
2500
|
+
[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 can be used independently, but it also contains essential features that facilitate the adoption of B/CDD in code.
|
2088
2501
|
|
2089
2502
|
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
2090
2503
|
|