bcdd-result 0.11.0 → 0.13.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +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
|
|