bcdd-result 0.12.0 → 0.13.0

Sign up to get free protection for your applications and to get access to all the features.
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