bcdd-result 0.12.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.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +16 -1
  3. data/CHANGELOG.md +70 -16
  4. data/README.md +293 -83
  5. data/Steepfile +4 -4
  6. data/examples/multiple_listeners/Rakefile +55 -0
  7. data/examples/multiple_listeners/app/models/account/member.rb +10 -0
  8. data/examples/multiple_listeners/app/models/account/owner_creation.rb +62 -0
  9. data/examples/multiple_listeners/app/models/account.rb +11 -0
  10. data/examples/multiple_listeners/app/models/user/creation.rb +67 -0
  11. data/examples/multiple_listeners/app/models/user/token/creation.rb +51 -0
  12. data/examples/multiple_listeners/app/models/user/token.rb +7 -0
  13. data/examples/multiple_listeners/app/models/user.rb +15 -0
  14. data/examples/multiple_listeners/config/boot.rb +16 -0
  15. data/examples/multiple_listeners/config/initializers/bcdd.rb +11 -0
  16. data/examples/multiple_listeners/config.rb +27 -0
  17. data/examples/multiple_listeners/db/setup.rb +61 -0
  18. data/examples/multiple_listeners/lib/bcdd/result/rollback_on_failure.rb +15 -0
  19. data/examples/multiple_listeners/lib/bcdd/result/transitions_record.rb +28 -0
  20. data/examples/multiple_listeners/lib/runtime_breaker.rb +11 -0
  21. data/examples/multiple_listeners/lib/transitions_listener/stdout.rb +54 -0
  22. data/examples/single_listener/Rakefile +92 -0
  23. data/examples/single_listener/app/models/account/member.rb +10 -0
  24. data/examples/single_listener/app/models/account/owner_creation.rb +62 -0
  25. data/examples/single_listener/app/models/account.rb +11 -0
  26. data/examples/single_listener/app/models/user/creation.rb +67 -0
  27. data/examples/single_listener/app/models/user/token/creation.rb +51 -0
  28. data/examples/single_listener/app/models/user/token.rb +7 -0
  29. data/examples/single_listener/app/models/user.rb +15 -0
  30. data/examples/single_listener/config/boot.rb +16 -0
  31. data/examples/single_listener/config/initializers/bcdd.rb +11 -0
  32. data/examples/single_listener/config.rb +23 -0
  33. data/examples/single_listener/db/setup.rb +49 -0
  34. data/examples/single_listener/lib/bcdd/result/rollback_on_failure.rb +15 -0
  35. data/examples/single_listener/lib/runtime_breaker.rb +11 -0
  36. data/examples/single_listener/lib/single_transitions_listener.rb +108 -0
  37. data/lib/bcdd/result/callable_and_then/caller.rb +1 -1
  38. data/lib/bcdd/result/config.rb +6 -1
  39. data/lib/bcdd/result/context/expectations/mixin.rb +2 -2
  40. data/lib/bcdd/result/context/mixin.rb +2 -2
  41. data/lib/bcdd/result/context/success.rb +20 -2
  42. data/lib/bcdd/result/contract/for_types.rb +1 -1
  43. data/lib/bcdd/result/contract/for_types_and_values.rb +2 -0
  44. data/lib/bcdd/result/expectations/mixin.rb +2 -2
  45. data/lib/bcdd/result/ignored_types.rb +14 -0
  46. data/lib/bcdd/result/mixin.rb +2 -2
  47. data/lib/bcdd/result/transitions/config.rb +26 -0
  48. data/lib/bcdd/result/transitions/listener.rb +51 -0
  49. data/lib/bcdd/result/transitions/listeners.rb +87 -0
  50. data/lib/bcdd/result/transitions/tracking/disabled.rb +1 -13
  51. data/lib/bcdd/result/transitions/tracking/enabled.rb +76 -17
  52. data/lib/bcdd/result/transitions/tracking.rb +8 -3
  53. data/lib/bcdd/result/transitions/tree.rb +26 -0
  54. data/lib/bcdd/result/transitions.rb +3 -4
  55. data/lib/bcdd/result/version.rb +1 -1
  56. data/lib/bcdd/result.rb +7 -5
  57. data/sig/bcdd/result/config.rbs +1 -0
  58. data/sig/bcdd/result/context.rbs +9 -0
  59. data/sig/bcdd/result/ignored_types.rbs +9 -0
  60. data/sig/bcdd/result/transitions.rbs +96 -7
  61. data/sig/bcdd/result.rbs +2 -2
  62. metadata +42 -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>Empower Ruby apps with pragmatic use of Result pattern (monad), Railway Oriented Programming, and B/CDD.</i></p>
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,13 +72,14 @@ 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
- - [`BCDD::Result.transitions`](#bcddresulttransitions)
74
- - [Configuration](#configuration)
75
- - [`BCDD::Result.configuration`](#bcddresultconfiguration)
76
- - [`config.addon.enable!(:given, :continue)`](#configaddonenablegiven-continue)
77
- - [`config.constant_alias.enable!('Result', 'BCDD::Context')`](#configconstant_aliasenableresult-bcddcontext)
78
- - [`config.pattern_matching.disable!(:nil_as_valid_value_checking)`](#configpattern_matchingdisablenil_as_valid_value_checking)
79
- - [`config.feature.disable!(:expectations)`](#configfeaturedisableexpectations)
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)
81
84
  - [`BCDD::Result#and_then!`](#bcddresultand_then)
82
85
  - [Dependency Injection](#dependency-injection-1)
@@ -1418,7 +1421,7 @@ result = Divide.new.call(4, 2)
1418
1421
 
1419
1422
  # The example below shows an error because the :ok type is not allowed.
1420
1423
  # But look at the allowed types have only one type (:division_completed).
1421
- # This is because the :continued type is ignored by the expectations.
1424
+ # This is because the :_continue_ type is ignored by the expectations.
1422
1425
  #
1423
1426
  result.success?(:ok)
1424
1427
  # type :ok is not allowed. Allowed types: :division_completed (BCDD::Result::Contract::Error::UnexpectedType)
@@ -1783,7 +1786,9 @@ Division.call(14, 0)
1783
1786
  #<BCDD::Result::Context::Failure type=:division_by_zero value={:message=>"arg2 must not be zero"}>
1784
1787
  ```
1785
1788
 
1786
- ### `BCDD::Result.transitions`
1789
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
1790
+
1791
+ ## `BCDD::Result.transitions`
1787
1792
 
1788
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.
1789
1794
 
@@ -1804,7 +1809,7 @@ class Division
1804
1809
 
1805
1810
  private
1806
1811
 
1807
- ValidNumber = ->(arg) { arg.is_a?(Numeric) && arg != Float::NAN && arg != Float::INFINITY }
1812
+ ValidNumber = ->(arg) { arg.is_a?(Numeric) && (!arg.respond_to?(:finite?) || arg.finite?) }
1808
1813
 
1809
1814
  def require_numbers((arg1, arg2))
1810
1815
  ValidNumber[arg1] or return Failure(:invalid_arg, 'arg1 must be a valid number')
@@ -1851,83 +1856,85 @@ result = SumDivisionsByTwo.call(20, 10)
1851
1856
 
1852
1857
  result.transitions
1853
1858
  {
1854
- :version =>1,
1859
+ :version => 1,
1855
1860
  :metadata => {
1856
- :duration => 0, # milliseconds
1857
- :tree_map => [0, [[1, []], [2, []]]], # represents the tree of transitions using the id of each transition block
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]}
1858
1865
  },
1859
- :records => [
1866
+ :records=> [
1860
1867
  {
1861
- :root=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
1862
- :parent=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
1863
- :current=>{:id=>1, :name=>"Division", :desc=>"divide two numbers"},
1864
- :result=>{:kind=>:success, :type=>:given, :value=>[20, 2]},
1865
- :and_then=>{},
1866
- :time=>2024-01-02 03:35:11.248418 UTC
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
1867
1874
  },
1868
1875
  {
1869
- :root=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
1870
- :parent=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
1871
- :current=>{:id=>1, :name=>"Division", :desc=>"divide two numbers"},
1872
- :result=>{:kind=>:success, :type=>:continued, :value=>[20, 2]},
1873
- :and_then=>{:type=>:method, :arg=>nil, :source=><Division:0x0000000106099028>, :method_name=>:require_numbers},
1874
- :time=>2024-01-02 03:35:11.248558 UTC
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
1875
1882
  },
1876
1883
  {
1877
- :root=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
1878
- :parent=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
1879
- :current=>{:id=>1, :name=>"Division", :desc=>"divide two numbers"},
1880
- :result=>{:kind=>:success, :type=>:continued, :value=>[20, 2]},
1881
- :and_then=>{:type=>:method, :arg=>nil, :source=><Division:0x0000000106099028>, :method_name=>:check_for_zeros},
1882
- :time=>2024-01-02 03:35:11.248587 UTC
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
1883
1890
  },
1884
1891
  {
1885
- :root=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
1886
- :parent=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
1887
- :current=>{:id=>1, :name=>"Division", :desc=>"divide two numbers"},
1888
- :result=>{:kind=>:success, :type=>:division_completed, :value=>10},
1889
- :and_then=>{:type=>:method, :arg=>nil, :source=><Division:0x0000000106099028>, :method_name=>:divide},
1890
- :time=>2024-01-02 03:35:11.248607 UTC
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
1891
1898
  },
1892
1899
  {
1893
- :root=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
1894
- :parent=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
1895
- :current=>{:id=>2, :name=>"Division", :desc=>"divide two numbers"},
1896
- :result=>{:kind=>:success, :type=>:given, :value=>[10, 2]},
1897
- :and_then=>{},
1898
- :time=>2024-01-02 03:35:11.24865 UTC
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
1899
1906
  },
1900
1907
  {
1901
- :root=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
1902
- :parent=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
1903
- :current=>{:id=>2, :name=>"Division", :desc=>"divide two numbers"},
1904
- :result=>{:kind=>:success, :type=>:continued, :value=>[10, 2]},
1905
- :and_then=>{:type=>:method, :arg=>nil, :source=><Division:0x0000000106097ed0>, :method_name=>:require_numbers},
1906
- :time=>2024-01-02 03:35:11.248661 UTC
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
1907
1914
  },
1908
1915
  {
1909
- :root=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
1910
- :parent=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
1911
- :current=>{:id=>2, :name=>"Division", :desc=>"divide two numbers"},
1912
- :result=>{:kind=>:success, :type=>:continued, :value=>[10, 2]},
1913
- :and_then=>{:type=>:method, :arg=>nil, :source=><Division:0x0000000106097ed0>, :method_name=>:check_for_zeros},
1914
- :time=>2024-01-02 03:35:11.248672 UTC
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
1915
1922
  },
1916
1923
  {
1917
- :root=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
1918
- :parent=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
1919
- :current=>{:id=>2, :name=>"Division", :desc=>"divide two numbers"},
1920
- :result=>{:kind=>:success, :type=>:division_completed, :value=>5},
1921
- :and_then=>{:type=>:method, :arg=>nil, :source=><Division:0x0000000106097ed0>, :method_name=>:divide},
1922
- :time=>2024-01-02 03:35:11.248682 UTC
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
1923
1930
  },
1924
1931
  {
1925
- :root=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
1926
- :parent=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
1927
- :current=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
1928
- :result=>{:kind=>:success, :type=>:sum, :value=>15},
1929
- :and_then=>{},
1930
- :time=>2024-01-02 03:35:11.248721 UTC
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
1931
1938
  }
1932
1939
  ]
1933
1940
  }
@@ -1935,7 +1942,46 @@ result.transitions
1935
1942
 
1936
1943
  <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
1937
1944
 
1938
- #### Configuration
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">⬆️ &nbsp;back to top</a></p>
1981
+
1982
+ ### Configuration
1983
+
1984
+ #### Turning on/off
1939
1985
 
1940
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.
1941
1987
 
@@ -1949,15 +1995,179 @@ result = SumDivisionsByTwo.call(20, 10)
1949
1995
 
1950
1996
  result.transitions
1951
1997
 
1952
- {:version=>1, :records=>[], :metadata=>{:duration=>0, :tree_map=>[]}}
1998
+ {:version=>1, :records=>[], :metadata=>{:duration=>0, :ids_tree=>[]}}
1953
1999
  ```
1954
2000
 
1955
2001
  <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
1956
2002
 
1957
- ### `BCDD::Result.configuration`
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">⬆️ &nbsp;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">⬆️ &nbsp;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
+ ]
2159
+ ```
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
+
2163
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
2164
+
2165
+ ## `BCDD::Result.configuration`
1958
2166
 
1959
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.
1960
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
+
1961
2171
  ```ruby
1962
2172
  BCDD::Result.configuration do |config|
1963
2173
  config.addon.enable!(:given, :continue)
@@ -1974,13 +2184,13 @@ Use `disable!` to disable a feature and `enable!` to enable it.
1974
2184
 
1975
2185
  Let's see what each configuration in the example above does:
1976
2186
 
1977
- #### `config.addon.enable!(:given, :continue)`
2187
+ ### `config.addon.enable!(:given, :continue)` <!-- omit in toc -->
1978
2188
 
1979
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).
1980
2190
 
1981
2191
  It is also enabling the `Given()` which is already enabled by default. Link to documentation: [(1)](#add-ons) [(2)](#mixin-add-ons).
1982
2192
 
1983
- #### `config.constant_alias.enable!('Result', 'BCDD::Context')`
2193
+ ### `config.constant_alias.enable!('Result', 'BCDD::Context')` <!-- omit in toc -->
1984
2194
 
1985
2195
  This configuration make `Result` a constant alias for `BCDD::Result`, and `BCDD::Context` a constant alias for `BCDD::Result::Context`.
1986
2196
 
@@ -1988,23 +2198,23 @@ Link to documentations:
1988
2198
  - [Result alias](#bcddresult-versus-result)
1989
2199
  - [Context aliases](#constant-aliases)
1990
2200
 
1991
- #### `config.pattern_matching.disable!(:nil_as_valid_value_checking)`
2201
+ ### `config.pattern_matching.disable!(:nil_as_valid_value_checking)` <!-- omit in toc -->
1992
2202
 
1993
2203
  This configuration disables the `nil_as_valid_value_checking` for `BCDD::Result` and `BCDD::Result::Context`. Link to [documentation](#pattern-matching-support).
1994
2204
 
1995
- <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
1996
-
1997
- #### `config.feature.disable!(:expectations)`
2205
+ ### `config.feature.disable!(:expectations)` <!-- omit in toc -->
1998
2206
 
1999
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.
2000
2208
 
2001
2209
  PS: I'm using `::Rails.env.production?` to check the environment, but you can use any logic you want.
2002
2210
 
2211
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
2212
+
2003
2213
  ### `BCDD::Result.config`
2004
2214
 
2005
2215
  The `BCDD::Result.config` allows you to access the current configuration.
2006
2216
 
2007
- **BCDD::Result.config.addon**
2217
+ #### **BCDD::Result.config.addon** <!-- omit in toc -->
2008
2218
 
2009
2219
  ```ruby
2010
2220
  BCDD::Result.config.addon.enabled?(:continue)
@@ -2033,7 +2243,7 @@ BCDD::Result.config.addon.options
2033
2243
  # }
2034
2244
  ```
2035
2245
 
2036
- **BCDD::Result.config.constant_alias**
2246
+ #### **BCDD::Result.config.constant_alias** <!-- omit in toc -->
2037
2247
 
2038
2248
  ```ruby
2039
2249
  BCDD::Result.config.constant_alias.enabled?('Result')
@@ -2048,7 +2258,7 @@ BCDD::Result.config.constant_alias.options
2048
2258
  # }
2049
2259
  ```
2050
2260
 
2051
- **BCDD::Result.config.pattern_matching**
2261
+ #### **BCDD::Result.config.pattern_matching** <!-- omit in toc -->
2052
2262
 
2053
2263
  ```ruby
2054
2264
  BCDD::Result.config.pattern_matching.enabled?(:nil_as_valid_value_checking)
@@ -2065,7 +2275,7 @@ BCDD::Result.config.pattern_matching.options
2065
2275
  # }
2066
2276
  ```
2067
2277
 
2068
- **BCDD::Result.config.feature**
2278
+ #### **BCDD::Result.config.feature** <!-- omit in toc -->
2069
2279
 
2070
2280
  ```ruby
2071
2281
  BCDD::Result.config.feature.enabled?(:expectations)
@@ -2287,7 +2497,7 @@ end
2287
2497
 
2288
2498
  ## About
2289
2499
 
2290
- [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.
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.
2291
2501
 
2292
2502
  <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
2293
2503
 
data/Steepfile CHANGED
@@ -10,16 +10,16 @@ target :lib do
10
10
  # check 'app/models/**/*.rb' # Glob
11
11
  # ignore 'lib/templates/*.rb'
12
12
 
13
- library 'singleton', 'securerandom' # Standard libraries
13
+ library 'singleton' # Standard libraries
14
14
  # library 'strong_json' # Gems
15
15
 
16
16
  # configure_code_diagnostics(D::Ruby.default) # `default` diagnostics setting (applies by default)
17
17
  # configure_code_diagnostics(D::Ruby.strict) # `strict` diagnostics setting
18
18
  # configure_code_diagnostics(D::Ruby.lenient) # `lenient` diagnostics setting
19
19
  # configure_code_diagnostics(D::Ruby.silent) # `silent` diagnostics setting
20
- # configure_code_diagnostics do |hash| # You can setup everything yourself
21
- # hash[D::Ruby::NoMethod] = :information
22
- # end
20
+ configure_code_diagnostics do |hash| # You can setup everything yourself
21
+ hash[D::Ruby::NoMethod] = :information
22
+ end
23
23
  end
24
24
 
25
25
  # target :test do
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ if RUBY_VERSION <= '3.1'
4
+ puts 'This example requires Ruby 3.1 or higher.'
5
+ exit! 1
6
+ end
7
+
8
+ # Usage:
9
+ #
10
+ # rake DISABLE_TRANSITIONS=t
11
+ # rake DISABLE_LISTENER=t
12
+ #
13
+ # rake HIDE_GIVEN_AND_CONTINUE=t
14
+ #
15
+ # rake BREAK_ACCOUNT_CREATION=t
16
+ # rake BREAK_USER_CREATION=t
17
+ # rake BREAK_USER_TOKEN_CREATION=t
18
+ #
19
+ # rake BREAK_ACCOUNT_CREATION=t HIDE_GIVEN_AND_CONTINUE=t
20
+ task default: %i[bcdd_result_transitions]
21
+
22
+ desc 'creates an account and an owner user through BCDD::Result'
23
+ task :bcdd_result_transitions do
24
+ require_relative 'config'
25
+
26
+ BCDD::Result.configuration do |config|
27
+ config.feature.disable!(:transitions) if ENV['DISABLE_TRANSITIONS']
28
+
29
+ unless ENV['DISABLE_LISTENER']
30
+ config.transitions.listener = BCDD::Result::Transitions::Listeners[
31
+ TransitionsListener::Stdout,
32
+ BCDD::Result::TransitionsRecord::Listener
33
+ ]
34
+ end
35
+ end
36
+
37
+ result = nil
38
+
39
+ bench = Benchmark.measure do
40
+ result = Account::OwnerCreation.new.call(
41
+ owner: {
42
+ name: "\tJohn Doe \n",
43
+ email: ' JOHN.doe@email.com',
44
+ password: '123123123',
45
+ password_confirmation: '123123123'
46
+ }
47
+ )
48
+ rescue RuntimeBreaker::Interruption => e
49
+ nil
50
+ end
51
+
52
+ puts "\nBCDD::Result::TransitionsRecord.count: #{BCDD::Result::TransitionsRecord.count}"
53
+
54
+ puts "\nBenchmark: #{bench}"
55
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Account::Member < ActiveRecord::Base
4
+ self.table_name = 'account_members'
5
+
6
+ enum role: { owner: 0, admin: 1, contributor: 2 }
7
+
8
+ belongs_to :user, inverse_of: :memberships
9
+ belongs_to :account, inverse_of: :memberships
10
+ end