bcdd-result 0.9.1 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 78ccef0bd217127baf1bf96cabf4074c2ab2e8bb7befffd7c7f2f3df4631bf27
4
- data.tar.gz: '0429c37305f2265dd13517c7a64e977929f6a4bde4b70421de01c8a418b67656'
3
+ metadata.gz: 7bf2a03ac7efd3e75e03b2f5617518cb07f40e03cc970c612f3d4dfcdad153ad
4
+ data.tar.gz: 2717e6e600101401f420164aed020689e9c07dd7f7296c78ec5290562620ca3c
5
5
  SHA512:
6
- metadata.gz: f8756ac806f830d458b3c830c15ac667cbb29f1737fa97cb59870541e2f3e8000839ab775c16559370499a7055db7f0938bb8a71071db9b5392507af5f0ec7de
7
- data.tar.gz: 1003e299eec64ddb62a51b1f8d358b9dca2b58add3f8b4b9a4c46a5a70cb825907b2de7339c4f5e94817fb92e48c3a702eae67cd6eeca80942c60bdc80dff071
6
+ metadata.gz: c1761571f7d0c0a6fd4601a9f8a4c8c18688085226cd061057f95c93737da80ac58e0a99a3803a0e0f93aa5040530975d9b16a7b54df79487ca625fd67d405d6
7
+ data.tar.gz: 4b22f37d2282de4c0a6e3781e86c9c63457efa83a7d5dc07db4288b9fe19c5b3868cd13a34a1cfc515776cf466e2095bde9d10503f87b8f546ceab9408ef9617
data/.rubocop.yml CHANGED
@@ -9,6 +9,10 @@ AllCops:
9
9
  NewCops: enable
10
10
  TargetRubyVersion: 2.7
11
11
 
12
+ Lint/RescueException:
13
+ Exclude:
14
+ - lib/bcdd/result/transitions.rb
15
+
12
16
  Layout/LineLength:
13
17
  Max: 120
14
18
 
@@ -70,6 +74,10 @@ Minitest/AssertEmptyLiteral:
70
74
  Minitest/AssertOperator:
71
75
  Enabled: false
72
76
 
77
+ Minitest/AssertWithExpectedArgument:
78
+ Exclude:
79
+ - test/test_helper.rb
80
+
73
81
  Naming/FileName:
74
82
  Exclude:
75
83
  - lib/bcdd-result.rb
data/CHANGELOG.md CHANGED
@@ -1,36 +1,46 @@
1
1
  - [\[Unreleased\]](#unreleased)
2
+ - [\[0.10.0\] - 2023-12-31](#0100---2023-12-31)
3
+ - [Added](#added)
2
4
  - [\[0.9.1\] - 2023-12-12](#091---2023-12-12)
3
5
  - [Changed](#changed)
4
6
  - [Fixed](#fixed)
5
7
  - [\[0.9.0\] - 2023-12-12](#090---2023-12-12)
6
- - [Added](#added)
8
+ - [Added](#added-1)
7
9
  - [Changed](#changed-1)
8
10
  - [\[0.8.0\] - 2023-12-11](#080---2023-12-11)
9
- - [Added](#added-1)
11
+ - [Added](#added-2)
10
12
  - [Changed](#changed-2)
11
13
  - [Removed](#removed)
12
14
  - [\[0.7.0\] - 2023-10-27](#070---2023-10-27)
13
- - [Added](#added-2)
15
+ - [Added](#added-3)
14
16
  - [Changed](#changed-3)
15
17
  - [\[0.6.0\] - 2023-10-11](#060---2023-10-11)
16
- - [Added](#added-3)
18
+ - [Added](#added-4)
17
19
  - [Changed](#changed-4)
18
20
  - [\[0.5.0\] - 2023-10-09](#050---2023-10-09)
19
- - [Added](#added-4)
20
- - [\[0.4.0\] - 2023-09-28](#040---2023-09-28)
21
21
  - [Added](#added-5)
22
+ - [\[0.4.0\] - 2023-09-28](#040---2023-09-28)
23
+ - [Added](#added-6)
22
24
  - [Changed](#changed-5)
23
25
  - [Removed](#removed-1)
24
26
  - [\[0.3.0\] - 2023-09-26](#030---2023-09-26)
25
- - [Added](#added-6)
26
- - [\[0.2.0\] - 2023-09-26](#020---2023-09-26)
27
27
  - [Added](#added-7)
28
+ - [\[0.2.0\] - 2023-09-26](#020---2023-09-26)
29
+ - [Added](#added-8)
28
30
  - [Removed](#removed-2)
29
31
  - [\[0.1.0\] - 2023-09-25](#010---2023-09-25)
30
- - [Added](#added-8)
32
+ - [Added](#added-9)
31
33
 
32
34
  ## [Unreleased]
33
35
 
36
+ ## [0.10.0] - 2023-12-31
37
+
38
+ ### Added
39
+
40
+ - Add `BCDD::Result.transitions(&block)` to track all transitions in the same or between different operations. 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.
41
+
42
+ - Add `BCDD::Result.config.feature.disable!(:transitions)` and `BCDD::Result.config.feature.enable!(:transitions)` to turn on/off the `BCDD::Result.transitions` feature.
43
+
34
44
  ## [0.9.1] - 2023-12-12
35
45
 
36
46
  ### Changed
data/README.md CHANGED
@@ -70,6 +70,8 @@ Use it to enable the [Railway Oriented Programming](https://fsharpforfunandprofi
70
70
  - [Module example (Singleton Methods)](#module-example-singleton-methods-1)
71
71
  - [`BCDD::Result::Context::Expectations`](#bcddresultcontextexpectations)
72
72
  - [Mixin add-ons](#mixin-add-ons)
73
+ - [`BCDD::Result.transitions`](#bcddresulttransitions)
74
+ - [Configuration](#configuration)
73
75
  - [`BCDD::Result.configuration`](#bcddresultconfiguration)
74
76
  - [`config.addon.enable!(:continue)`](#configaddonenablecontinue)
75
77
  - [`config.constant_alias.enable!('Result', 'BCDD::Context')`](#configconstant_aliasenableresult-bcddcontext)
@@ -983,7 +985,7 @@ module Divide
983
985
  end
984
986
  ```
985
987
 
986
- In the code above, we define a constant `Divide::Expected`. And because of this (it is a constant), we can use it inside and outside the module.
988
+ In the code above, we define a constant `Divide::Result`. And because of this (it is a constant), we can use it inside and outside the module.
987
989
 
988
990
  Look what happens if you try to create a result without one of the expected types.
989
991
 
@@ -1223,7 +1225,7 @@ Divide.call(4, 2)
1223
1225
 
1224
1226
  #### Value checking - Result Creation
1225
1227
 
1226
- The `Result::Expectations` supports types of validations. The first is the type checking only, and the second is the type and value checking.
1228
+ The `Result::Expectations` supports two types of validations. The first is the type checking only, and the second is the type and value checking.
1227
1229
 
1228
1230
  To define expectations for your result's values, you must declare a Hash with the type as the key and the value as the value. A value validator is any object that responds to `#===` (case equality operator).
1229
1231
 
@@ -1575,7 +1577,7 @@ Divide.new.call(10, 5)
1575
1577
 
1576
1578
  ##### Module example (Singleton Methods)
1577
1579
 
1578
- Yes, the `BCDD::Result::Context.mixin` can produce singleton methods. Look for an example using a module (but it could be a class, too).
1580
+ `BCDD::Result::Context.mixin` can also produce singleton methods. Below is an example using a module (but it could be a class, too).
1579
1581
 
1580
1582
  ```ruby
1581
1583
  module Divide
@@ -1660,7 +1662,7 @@ Divide.new.call(10, 5)
1660
1662
 
1661
1663
  As in the `BCDD::Result::Expectations.mixin`, the `BCDD::Result::Context::Expectations.mixin` will add a Result constant in the target class. It can generate success/failure results, which ensure the mixin expectations.
1662
1664
 
1663
- Let's see this using previous example:
1665
+ Let's see this using the previous example:
1664
1666
 
1665
1667
  ```ruby
1666
1668
  Divide::Result::Success(:division_completed, number: 2)
@@ -1747,6 +1749,160 @@ Divide.call(14, 0)
1747
1749
  #<BCDD::Result::Context::Failure type=:division_by_zero value={:message=>"arg2 must not be zero"}>
1748
1750
  ```
1749
1751
 
1752
+ ### `BCDD::Result.transitions`
1753
+
1754
+ Use `BCDD::Result.transitions(&block)` to track all transitions in the same or between different operations. 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.
1755
+
1756
+ When you wrap the creation of the result with `BCDD::Result.transitions`, the final result will expose all the transition records through the `BCDD::Result#transitions` method.
1757
+
1758
+ ```ruby
1759
+ class Division
1760
+ include BCDD::Result.mixin(config: { addon: { continue: true } })
1761
+
1762
+ def call(arg1, arg2)
1763
+ BCDD::Result.transitions(name: 'Division', desc: 'divide two numbers') do
1764
+ require_numbers(arg1, arg2)
1765
+ .and_then(:check_for_zeros)
1766
+ .and_then(:divide)
1767
+ end
1768
+ end
1769
+
1770
+ private
1771
+
1772
+ ValidNumber = ->(arg) { arg.is_a?(Numeric) && arg != Float::NAN && arg != Float::INFINITY }
1773
+
1774
+ def require_numbers(arg1, arg2)
1775
+ ValidNumber[arg1] or return Failure(:invalid_arg, 'arg1 must be a valid number')
1776
+ ValidNumber[arg2] or return Failure(:invalid_arg, 'arg2 must be a valid number')
1777
+
1778
+ Continue([arg1, arg2])
1779
+ end
1780
+
1781
+ def check_for_zeros(numbers)
1782
+ num1, num2 = numbers
1783
+
1784
+ return Failure(:division_by_zero, 'num2 cannot be zero') if num2.zero?
1785
+
1786
+ num1.zero? ? Success(:division_completed, 0) : Continue(numbers)
1787
+ end
1788
+
1789
+ def divide((num1, num2))
1790
+ Success(:division_completed, num1 / num2)
1791
+ end
1792
+ end
1793
+
1794
+ module SumDivisionsByTwo
1795
+ extend self, BCDD::Result.mixin
1796
+
1797
+ def call(*numbers)
1798
+ BCDD::Result.transitions(name: 'SumDivisionsByTwo') do
1799
+ divisions = numbers.map { |number| Division.new.call(number, 2) }
1800
+
1801
+ if divisions.any?(&:failure?)
1802
+ Failure(:errors, divisions.select(&:failure?).map(&:value))
1803
+ else
1804
+ Success(:sum, divisions.sum(&:value))
1805
+ end
1806
+ end
1807
+ end
1808
+ end
1809
+ ```
1810
+
1811
+ Let's see the result of the `SumDivisionsByTwo` call:
1812
+
1813
+ ```ruby
1814
+ result = SumDivisionsByTwo.call(20, 10)
1815
+ # => #<BCDD::Result::Success type=:sum value=15>
1816
+
1817
+ result.transitions
1818
+ {
1819
+ :version =>1,
1820
+ :metadata => {
1821
+ :duration => 0, # milliseconds
1822
+ :tree_map => [0, [[1, []], [2, []]]], # represents the tree of transitions using the id of each transition block
1823
+ },
1824
+ :records => [
1825
+ {
1826
+ :root => {:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
1827
+ :parent => {:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
1828
+ :current => {:id=>1, :name=>"Division", :desc=>"divide two numbers"},
1829
+ :result => {:kind=>:success, :type=>:continued, :value=>[20, 2]},
1830
+ :and_then => {},
1831
+ :time => 2023-12-31 22:19:33.281619 UTC
1832
+ },
1833
+ {
1834
+ :root=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
1835
+ :parent=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
1836
+ :current=>{:id=>1, :name=>"Division", :desc=>"divide two numbers"},
1837
+ :result=>{:kind=>:success, :type=>:continued, :value=>[20, 2]},
1838
+ :and_then=>{:type=>:method, :arg=>nil, :subject=>#<Division:0x0000000103e5f7c8>, :method_name=>:check_for_zeros},
1839
+ :time=>2023-12-31 22:19:33.281693 UTC
1840
+ },
1841
+ {
1842
+ :root=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
1843
+ :parent=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
1844
+ :current=>{:id=>1, :name=>"Division", :desc=>"divide two numbers"},
1845
+ :result=>{:kind=>:success, :type=>:division_completed, :value=>10},
1846
+ :and_then=>{:type=>:method, :arg=>nil, :subject=>#<Division:0x0000000103e5f7c8>, :method_name=>:divide},
1847
+ :time=>2023-12-31 22:19:33.281715 UTC
1848
+ },
1849
+ {
1850
+ :root=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
1851
+ :parent=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
1852
+ :current=>{:id=>2, :name=>"Division", :desc=>"divide two numbers"},
1853
+ :result=>{:kind=>:success, :type=>:continued, :value=>[10, 2]},
1854
+ :and_then=>{},
1855
+ :time=>2023-12-31 22:19:33.281747 UTC
1856
+ },
1857
+ {
1858
+ :root=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
1859
+ :parent=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
1860
+ :current=>{:id=>2, :name=>"Division", :desc=>"divide two numbers"},
1861
+ :result=>{:kind=>:success, :type=>:continued, :value=>[10, 2]},
1862
+ :and_then=>{:type=>:method, :arg=>nil, :subject=>#<Division:0x0000000103e5f1b0>, :method_name=>:check_for_zeros},
1863
+ :time=>2023-12-31 22:19:33.281755 UTC
1864
+ },
1865
+ {
1866
+ :root=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
1867
+ :parent=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
1868
+ :current=>{:id=>2, :name=>"Division", :desc=>"divide two numbers"},
1869
+ :result=>{:kind=>:success, :type=>:division_completed, :value=>5},
1870
+ :and_then=>{:type=>:method, :arg=>nil, :subject=>#<Division:0x0000000103e5f1b0>, :method_name=>:divide},
1871
+ :time=>2023-12-31 22:19:33.281763 UTC
1872
+ },
1873
+ {
1874
+ :root=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
1875
+ :parent=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
1876
+ :current=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
1877
+ :result=>{:kind=>:success, :type=>:sum, :value=>15},
1878
+ :and_then=>{},
1879
+ :time=>2023-12-31 22:19:33.281784 UTC
1880
+ }
1881
+ ]
1882
+ }
1883
+ ```
1884
+
1885
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
1886
+
1887
+ #### Configuration
1888
+
1889
+ 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.
1890
+
1891
+ ```ruby
1892
+ BCDD::Result.configuration do |config|
1893
+ config.feature.disable!(:transitions)
1894
+ end
1895
+
1896
+ result = SumDivisionsByTwo.call(20, 10)
1897
+ # => #<BCDD::Result::Success type=:sum value=15>
1898
+
1899
+ result.transitions
1900
+
1901
+ {:version=>1, :records=>[], :metadata=>{:duration=>0, :tree_map=>[]}}
1902
+ ```
1903
+
1904
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
1905
+
1750
1906
  ### `BCDD::Result.configuration`
1751
1907
 
1752
1908
  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.
@@ -1793,7 +1949,7 @@ PS: I'm using `::Rails.env.production?` to check the environment, but you can us
1793
1949
 
1794
1950
  ### `BCDD::Result.config`
1795
1951
 
1796
- The `BCDD::Result.config` allows you to access the current configuration. It is useful when you want to check the current configuration.
1952
+ The `BCDD::Result.config` allows you to access the current configuration.
1797
1953
 
1798
1954
  **BCDD::Result.config.addon**
1799
1955
 
@@ -1859,6 +2015,13 @@ BCDD::Result.config.feature.options
1859
2015
  # "BCDD::Result::Expectations,
1860
2016
  # "BCDD::Result::Context::Expectations"
1861
2017
  # ]
2018
+ # },
2019
+ # :transitions=>{
2020
+ # :enabled=>true,
2021
+ # :affects=>[
2022
+ # "BCDD::Result",
2023
+ # "BCDD::Result::Context"
2024
+ # ]
1862
2025
  # }
1863
2026
  # }
1864
2027
  ```
data/Steepfile CHANGED
@@ -10,8 +10,8 @@ target :lib do
10
10
  # check 'app/models/**/*.rb' # Glob
11
11
  # ignore 'lib/templates/*.rb'
12
12
 
13
- library 'singleton' # Standard libraries
14
- # library 'strong_json' # Gems
13
+ library 'singleton', 'securerandom' # Standard libraries
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
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ class BCDD::Result
4
+ class Config
5
+ module Addons
6
+ OPTIONS = {
7
+ continue: {
8
+ default: false,
9
+ affects: %w[BCDD::Result BCDD::Result::Context BCDD::Result::Expectations BCDD::Result::Context::Expectations]
10
+ }
11
+ }.transform_values!(&:freeze).freeze
12
+
13
+ def self.switcher
14
+ Switcher.new(options: OPTIONS)
15
+ end
16
+ end
17
+
18
+ private_constant :Addons
19
+ end
20
+ end
@@ -2,7 +2,7 @@
2
2
 
3
3
  class BCDD::Result
4
4
  class Config
5
- module ConstantAlias
5
+ module ConstantAliases
6
6
  MAPPING = {
7
7
  'Result' => { target: ::Object, name: :Result, value: ::BCDD::Result },
8
8
  'Context' => { target: ::Object, name: :Context, value: ::BCDD::Result::Context },
@@ -15,14 +15,14 @@ class BCDD::Result
15
15
  [option_name, { default: false, affects: [affects].freeze }]
16
16
  end.freeze
17
17
 
18
- Listener = ->(option_name, boolean) do
18
+ Listener = ->(option_name, bool) do
19
19
  mapping = MAPPING.fetch(option_name)
20
20
 
21
21
  target, name, value = mapping.fetch_values(:target, :name, :value)
22
22
 
23
23
  defined = target.const_defined?(name, false)
24
24
 
25
- boolean ? defined || target.const_set(name, value) : defined && target.send(:remove_const, name)
25
+ bool ? defined || target.const_set(name, value) : defined && target.send(:remove_const, name)
26
26
  end
27
27
 
28
28
  def self.switcher
@@ -30,6 +30,6 @@ class BCDD::Result
30
30
  end
31
31
  end
32
32
 
33
- private_constant :ConstantAlias
33
+ private_constant :ConstantAliases
34
34
  end
35
35
  end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ class BCDD::Result
4
+ class Config
5
+ module Features
6
+ OPTIONS = {
7
+ expectations: {
8
+ default: true,
9
+ affects: %w[BCDD::Result::Expectations BCDD::Result::Context::Expectations]
10
+ },
11
+ transitions: {
12
+ default: true,
13
+ affects: %w[BCDD::Result BCDD::Result::Context]
14
+ }
15
+ }.transform_values!(&:freeze).freeze
16
+
17
+ Listener = ->(option_name, _bool) do
18
+ Thread.current[Transitions::THREAD_VAR_NAME] = nil if option_name == :transitions
19
+ end
20
+
21
+ def self.switcher
22
+ Switcher.new(options: OPTIONS, listener: Listener)
23
+ end
24
+ end
25
+
26
+ private_constant :Features
27
+ end
28
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ class BCDD::Result
4
+ class Config
5
+ module PatternMatching
6
+ OPTIONS = {
7
+ nil_as_valid_value_checking: {
8
+ default: false,
9
+ affects: %w[BCDD::Result::Expectations BCDD::Result::Context::Expectations]
10
+ }
11
+ }.transform_values!(&:freeze).freeze
12
+
13
+ def self.switcher
14
+ Switcher.new(options: OPTIONS)
15
+ end
16
+ end
17
+
18
+ private_constant :PatternMatching
19
+ end
20
+ end
@@ -4,40 +4,22 @@ require 'singleton'
4
4
 
5
5
  require_relative 'config/options'
6
6
  require_relative 'config/switcher'
7
- require_relative 'config/constant_alias'
7
+ require_relative 'config/switchers/addons'
8
+ require_relative 'config/switchers/constant_aliases'
9
+ require_relative 'config/switchers/features'
10
+ require_relative 'config/switchers/pattern_matching'
8
11
 
9
12
  class BCDD::Result
10
13
  class Config
11
14
  include Singleton
12
15
 
13
- ADDON = {
14
- continue: {
15
- default: false,
16
- affects: %w[BCDD::Result BCDD::Result::Context BCDD::Result::Expectations BCDD::Result::Context::Expectations]
17
- }
18
- }.transform_values!(&:freeze).freeze
19
-
20
- FEATURE = {
21
- expectations: {
22
- default: true,
23
- affects: %w[BCDD::Result::Expectations BCDD::Result::Context::Expectations]
24
- }
25
- }.transform_values!(&:freeze).freeze
26
-
27
- PATTERN_MATCHING = {
28
- nil_as_valid_value_checking: {
29
- default: false,
30
- affects: %w[BCDD::Result::Expectations BCDD::Result::Context::Expectations]
31
- }
32
- }.transform_values!(&:freeze).freeze
33
-
34
16
  attr_reader :addon, :feature, :constant_alias, :pattern_matching
35
17
 
36
18
  def initialize
37
- @addon = Switcher.new(options: ADDON)
38
- @feature = Switcher.new(options: FEATURE)
39
- @constant_alias = ConstantAlias.switcher
40
- @pattern_matching = Switcher.new(options: PATTERN_MATCHING)
19
+ @addon = Addons.switcher
20
+ @feature = Features.switcher
21
+ @constant_alias = ConstantAliases.switcher
22
+ @pattern_matching = PatternMatching.switcher
41
23
  end
42
24
 
43
25
  def freeze
@@ -65,7 +47,5 @@ class BCDD::Result
65
47
  def inspect
66
48
  "#<#{self.class.name} options=#{options.keys.sort.inspect}>"
67
49
  end
68
-
69
- private_constant :ADDON, :FEATURE, :PATTERN_MATCHING
70
50
  end
71
51
  end
@@ -40,25 +40,20 @@ class BCDD::Result
40
40
  -1
41
41
  end
42
42
 
43
- def call_and_then_subject_method(method_name, context)
44
- method = subject.method(method_name)
45
-
46
- acc.merge!(value.merge(context))
47
-
48
- result =
49
- case SubjectMethodArity[method]
50
- when 0 then subject.send(method_name)
51
- when 1 then subject.send(method_name, **acc)
52
- else raise Error::InvalidSubjectMethodArity.build(subject: subject, method: method, max_arity: 1)
53
- end
54
-
55
- ensure_result_object(result, origin: :method)
43
+ def call_and_then_subject_method!(method, context_data)
44
+ acc.merge!(value.merge(context_data))
45
+
46
+ case SubjectMethodArity[method]
47
+ when 0 then subject.send(method.name)
48
+ when 1 then subject.send(method.name, **acc)
49
+ else raise Error::InvalidSubjectMethodArity.build(subject: subject, method: method, max_arity: 1)
50
+ end
56
51
  end
57
52
 
58
- def call_and_then_block(block)
53
+ def call_and_then_block!(block)
59
54
  acc.merge!(value)
60
55
 
61
- call_and_then_block!(block, acc)
56
+ block.call(acc)
62
57
  end
63
58
 
64
59
  def ensure_result_object(result, origin:)
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BCDD::Result::Transitions
4
+ module Tracking::Disabled
5
+ def self.start(name:, desc:); end
6
+
7
+ def self.finish(result:); end
8
+
9
+ def self.reset!; end
10
+
11
+ def self.record(result); end
12
+
13
+ def self.record_and_then(_type, _data, _subject)
14
+ yield
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BCDD::Result::Transitions
4
+ class Tracking::Enabled
5
+ attr_accessor :tree, :records, :root_started_at
6
+
7
+ private :tree, :tree=, :records, :records=, :root_started_at, :root_started_at=
8
+
9
+ def start(name:, desc:)
10
+ name_and_desc = [name, desc]
11
+
12
+ tree.frozen? ? root_start(name_and_desc) : tree.insert!(name_and_desc)
13
+ end
14
+
15
+ def finish(result:)
16
+ node = tree.current
17
+
18
+ tree.move_up!
19
+
20
+ return unless node.root?
21
+
22
+ duration = (now_in_milliseconds - root_started_at)
23
+
24
+ metadata = { duration: duration, tree_map: tree.nested_ids }
25
+
26
+ result.send(:transitions=, version: Tracking::VERSION, records: records, metadata: metadata)
27
+
28
+ reset!
29
+ end
30
+
31
+ def reset!
32
+ self.tree = Tracking::EMPTY_TREE
33
+ end
34
+
35
+ def record(result)
36
+ return if tree.frozen?
37
+
38
+ track(result, time: ::Time.now.getutc)
39
+ end
40
+
41
+ def record_and_then(type_arg, arg, subject)
42
+ type = type_arg.instance_of?(::Method) ? :method : type_arg
43
+
44
+ unless tree.frozen?
45
+ current_and_then = { type: type, arg: arg, subject: subject }
46
+ current_and_then[:method_name] = type_arg.name if type == :method
47
+
48
+ tree.current.value[1] = current_and_then
49
+ end
50
+
51
+ yield
52
+ end
53
+
54
+ private
55
+
56
+ TreeNodeValueNormalizer = ->(id, (nam, des)) { [{ id: id, name: nam, desc: des }, Tracking::EMPTY_HASH] }
57
+
58
+ def root_start(name_and_desc)
59
+ self.root_started_at = now_in_milliseconds
60
+
61
+ self.records = []
62
+
63
+ self.tree = Tree.new(name_and_desc, normalizer: TreeNodeValueNormalizer)
64
+ end
65
+
66
+ def track(result, time:)
67
+ result = result.data.to_h
68
+
69
+ root, = tree.root_value
70
+ parent, = tree.parent_value
71
+ current, and_then = tree.current_value
72
+
73
+ records << { root: root, parent: parent, current: current, result: result, and_then: and_then, time: time }
74
+ end
75
+
76
+ def now_in_milliseconds
77
+ Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ class BCDD::Result
4
+ module Transitions
5
+ module Tracking
6
+ require_relative 'tracking/enabled'
7
+ require_relative 'tracking/disabled'
8
+
9
+ EMPTY_ARRAY = [].freeze
10
+ EMPTY_HASH = {}.freeze
11
+ EMPTY_TREE = Tree.new(nil).freeze
12
+ VERSION = 1
13
+ EMPTY = { version: VERSION, records: EMPTY_ARRAY, metadata: { duration: 0, tree_map: EMPTY_ARRAY } }.freeze
14
+
15
+ def self.instance
16
+ Config.instance.feature.enabled?(:transitions) ? Tracking::Enabled.new : Tracking::Disabled
17
+ end
18
+ end
19
+ end
20
+ end