bcdd-result 0.11.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +16 -1
  3. data/CHANGELOG.md +97 -15
  4. data/README.md +508 -95
  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 +49 -0
  38. data/lib/bcdd/result/callable_and_then/config.rb +15 -0
  39. data/lib/bcdd/result/callable_and_then/error.rb +11 -0
  40. data/lib/bcdd/result/callable_and_then.rb +9 -0
  41. data/lib/bcdd/result/config/switchers/features.rb +5 -1
  42. data/lib/bcdd/result/config.rb +15 -4
  43. data/lib/bcdd/result/context/callable_and_then.rb +39 -0
  44. data/lib/bcdd/result/context/expectations/mixin.rb +2 -2
  45. data/lib/bcdd/result/context/mixin.rb +3 -3
  46. data/lib/bcdd/result/context/success.rb +29 -7
  47. data/lib/bcdd/result/context.rb +34 -16
  48. data/lib/bcdd/result/contract/for_types.rb +1 -1
  49. data/lib/bcdd/result/contract/for_types_and_values.rb +2 -0
  50. data/lib/bcdd/result/error.rb +20 -11
  51. data/lib/bcdd/result/expectations/mixin.rb +3 -3
  52. data/lib/bcdd/result/expectations.rb +6 -6
  53. data/lib/bcdd/result/ignored_types.rb +14 -0
  54. data/lib/bcdd/result/mixin.rb +3 -3
  55. data/lib/bcdd/result/transitions/config.rb +26 -0
  56. data/lib/bcdd/result/transitions/listener.rb +51 -0
  57. data/lib/bcdd/result/transitions/listeners.rb +87 -0
  58. data/lib/bcdd/result/transitions/tracking/disabled.rb +4 -6
  59. data/lib/bcdd/result/transitions/tracking/enabled.rb +103 -24
  60. data/lib/bcdd/result/transitions/tracking.rb +8 -3
  61. data/lib/bcdd/result/transitions/tree.rb +36 -6
  62. data/lib/bcdd/result/transitions.rb +11 -14
  63. data/lib/bcdd/result/version.rb +1 -1
  64. data/lib/bcdd/result.rb +39 -22
  65. data/sig/bcdd/result/callable_and_then.rbs +60 -0
  66. data/sig/bcdd/result/config.rbs +3 -0
  67. data/sig/bcdd/result/context.rbs +65 -4
  68. data/sig/bcdd/result/error.rbs +9 -6
  69. data/sig/bcdd/result/expectations.rbs +4 -4
  70. data/sig/bcdd/result/ignored_types.rbs +9 -0
  71. data/sig/bcdd/result/transitions.rbs +107 -7
  72. data/sig/bcdd/result.rbs +10 -6
  73. metadata +48 -6
@@ -14,7 +14,7 @@ class BCDD::Result::Context
14
14
  end
15
15
 
16
16
  private def _ResultAs(kind_class, type, value, terminal: nil)
17
- kind_class.new(type: type, value: value, subject: self, terminal: terminal)
17
+ kind_class.new(type: type, value: value, source: self, terminal: terminal)
18
18
  end
19
19
  end
20
20
 
@@ -25,7 +25,7 @@ class BCDD::Result::Context
25
25
  end
26
26
 
27
27
  private def Continue(**value)
28
- _ResultAs(Success, :continued, value)
28
+ _ResultAs(Success, ::BCDD::Result::IgnoredTypes::CONTINUE, value)
29
29
  end
30
30
  end
31
31
 
@@ -33,7 +33,7 @@ class BCDD::Result::Context
33
33
  private def Given(*values)
34
34
  value = values.map(&:to_h).reduce({}) { |acc, val| acc.merge(val) }
35
35
 
36
- _ResultAs(Success, :given, value)
36
+ _ResultAs(Success, ::BCDD::Result::IgnoredTypes::GIVEN, value)
37
37
  end
38
38
  end
39
39
 
@@ -1,15 +1,37 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class BCDD::Result::Context::Success < BCDD::Result::Context
4
- include ::BCDD::Result::Success::Methods
3
+ class BCDD::Result
4
+ class Context::Error < BCDD::Result::Error
5
+ InvalidExposure = ::Class.new(self)
6
+ end
7
+
8
+ class Context::Success < Context
9
+ include ::BCDD::Result::Success::Methods
10
+
11
+ FetchValues = ->(acc_values, keys) do
12
+ fetched_values = acc_values.fetch_values(*keys)
5
13
 
6
- def and_expose(type, keys, terminal: true)
7
- unless keys.is_a?(::Array) && !keys.empty? && keys.all?(::Symbol)
8
- raise ::ArgumentError, 'keys must be an Array of Symbols'
14
+ keys.zip(fetched_values).to_h
15
+ rescue ::KeyError => e
16
+ message = "#{e.message}. Available to expose: #{acc_values.keys.map(&:inspect).join(', ')}"
17
+
18
+ raise Context::Error::InvalidExposure, message
9
19
  end
10
20
 
11
- exposed_value = acc.merge(value).slice(*keys)
21
+ def and_expose(type, keys, terminal: true)
22
+ unless keys.is_a?(::Array) && !keys.empty? && keys.all?(::Symbol)
23
+ raise ::ArgumentError, 'keys must be an Array of Symbols'
24
+ end
25
+
26
+ Transitions.tracking.reset_and_then!
12
27
 
13
- self.class.new(type: type, value: exposed_value, subject: subject, terminal: terminal)
28
+ acc_values = acc.merge(value)
29
+
30
+ value_to_expose = FetchValues.call(acc_values, keys)
31
+
32
+ expectations = type_checker.expectations
33
+
34
+ self.class.new(type: type, value: value_to_expose, source: source, terminal: terminal, expectations: expectations)
35
+ end
14
36
  end
15
37
  end
@@ -6,6 +6,9 @@ class BCDD::Result
6
6
  require_relative 'context/success'
7
7
  require_relative 'context/mixin'
8
8
  require_relative 'context/expectations'
9
+ require_relative 'context/callable_and_then'
10
+
11
+ EXPECTED_OUTCOME = 'BCDD::Result::Context::Success or BCDD::Result::Context::Failure'
9
12
 
10
13
  def self.Success(type, **value)
11
14
  Success.new(type: type, value: value)
@@ -15,7 +18,7 @@ class BCDD::Result
15
18
  Failure.new(type: type, value: value)
16
19
  end
17
20
 
18
- def initialize(type:, value:, subject: nil, expectations: nil, terminal: nil)
21
+ def initialize(type:, value:, source: nil, expectations: nil, terminal: nil)
19
22
  value.is_a?(::Hash) or raise ::ArgumentError, 'value must be a Hash'
20
23
 
21
24
  @acc = {}
@@ -23,8 +26,16 @@ class BCDD::Result
23
26
  super
24
27
  end
25
28
 
26
- def and_then(method_name = nil, **context_data, &block)
27
- super(method_name, context_data, &block)
29
+ def and_then(method_name = nil, **injected_value, &block)
30
+ super(method_name, injected_value, &block)
31
+ end
32
+
33
+ def and_then!(source, **injected_value)
34
+ _call = injected_value.delete(:_call)
35
+
36
+ acc.merge!(injected_value)
37
+
38
+ super(source, injected_value, _call: _call)
28
39
  end
29
40
 
30
41
  protected
@@ -33,20 +44,23 @@ class BCDD::Result
33
44
 
34
45
  private
35
46
 
36
- SubjectMethodArity = ->(method) do
47
+ SourceMethodArity = ->(method) do
37
48
  return 0 if method.arity.zero?
38
- return 1 if method.parameters.map(&:first).all?(/\Akey/)
49
+
50
+ parameters = method.parameters.map(&:first)
51
+
52
+ return 1 if !parameters.empty? && parameters.all?(/\Akey/)
39
53
 
40
54
  -1
41
55
  end
42
56
 
43
- def call_and_then_subject_method!(method, context_data)
44
- acc.merge!(value.merge(context_data))
57
+ def call_and_then_source_method!(method, injected_value)
58
+ acc.merge!(value.merge(injected_value))
45
59
 
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)
60
+ case SourceMethodArity[method]
61
+ when 0 then source.send(method.name)
62
+ when 1 then source.send(method.name, **acc)
63
+ else raise Error::InvalidSourceMethodArity.build(source: source, method: method, max_arity: 1)
50
64
  end
51
65
  end
52
66
 
@@ -56,20 +70,24 @@ class BCDD::Result
56
70
  block.call(acc)
57
71
  end
58
72
 
73
+ def call_and_then_callable!(source, value:, injected_value:, method_name:)
74
+ acc.merge!(value.merge(injected_value))
75
+
76
+ CallableAndThen::Caller.call(source, value: acc, injected_value: injected_value, method_name: method_name)
77
+ end
78
+
59
79
  def ensure_result_object(result, origin:)
60
80
  raise_unexpected_outcome_error(result, origin) unless result.is_a?(Context)
61
81
 
62
- return result.tap { _1.acc.merge!(acc) } if result.subject.equal?(subject)
82
+ return result.tap { _1.acc.merge!(acc) } if result.source.equal?(source)
63
83
 
64
- raise Error::InvalidResultSubject.build(given_result: result, expected_subject: subject)
84
+ raise Error::InvalidResultSource.build(given_result: result, expected_source: source)
65
85
  end
66
86
 
67
- EXPECTED_OUTCOME = 'BCDD::Result::Context::Success or BCDD::Result::Context::Failure'
68
-
69
87
  def raise_unexpected_outcome_error(result, origin)
70
88
  raise Error::UnexpectedOutcome.build(outcome: result, origin: origin, expected: EXPECTED_OUTCOME)
71
89
  end
72
90
 
73
- private_constant :SubjectMethodArity, :EXPECTED_OUTCOME
91
+ private_constant :SourceMethodArity
74
92
  end
75
93
  end
@@ -11,7 +11,7 @@ class BCDD::Result
11
11
  end
12
12
 
13
13
  def type?(type)
14
- allowed_types.member?(type)
14
+ IgnoredTypes.include?(type) || allowed_types.member?(type)
15
15
  end
16
16
 
17
17
  def type!(type)
@@ -30,6 +30,8 @@ class BCDD::Result
30
30
  def type_and_value!(data)
31
31
  type, value = data.type, data.value
32
32
 
33
+ return value if IgnoredTypes.include?(type)
34
+
33
35
  value_checking = @types_and_values[type!(type)]
34
36
 
35
37
  checking_result = value_checking === value
@@ -9,7 +9,7 @@ class BCDD::Result::Error < StandardError
9
9
  end
10
10
 
11
11
  class MissingTypeArgument < self
12
- def initialize(_arg = nil)
12
+ def initialize(_message = nil)
13
13
  super('A type (argument) is required to invoke the #on/#on_type method')
14
14
  end
15
15
  end
@@ -22,29 +22,38 @@ class BCDD::Result::Error < StandardError
22
22
  end
23
23
  end
24
24
 
25
- class InvalidResultSubject < self
26
- def self.build(given_result:, expected_subject:)
25
+ class InvalidResultSource < self
26
+ def self.build(given_result:, expected_source:)
27
27
  message =
28
- "You cannot call #and_then and return a result that does not belong to the subject!\n" \
29
- "Expected subject: #{expected_subject.inspect}\n" \
30
- "Given subject: #{given_result.send(:subject).inspect}\n" \
28
+ "You cannot call #and_then and return a result that does not belong to the same source!\n" \
29
+ "Expected source: #{expected_source.inspect}\n" \
30
+ "Given source: #{given_result.send(:source).inspect}\n" \
31
31
  "Given result: #{given_result.inspect}"
32
32
 
33
33
  new(message)
34
34
  end
35
35
  end
36
36
 
37
- class InvalidSubjectMethodArity < self
38
- def self.build(subject:, method:, max_arity:)
39
- new("#{subject.class}##{method.name} has unsupported arity (#{method.arity}). Expected 0..#{max_arity}")
37
+ class InvalidSourceMethodArity < self
38
+ def self.build(source:, method:, max_arity:)
39
+ new("#{source.class}##{method.name} has unsupported arity (#{method.arity}). Expected 0..#{max_arity}")
40
40
  end
41
41
  end
42
42
 
43
43
  class UnhandledTypes < self
44
44
  def self.build(types:)
45
- subject = types.size == 1 ? 'This was' : 'These were'
45
+ source = types.size == 1 ? 'This was' : 'These were'
46
46
 
47
- new("You must handle all cases. #{subject} not handled: #{types.map(&:inspect).join(', ')}")
47
+ new("You must handle all cases. #{source} not handled: #{types.map(&:inspect).join(', ')}")
48
+ end
49
+ end
50
+
51
+ class CallableAndThenDisabled < self
52
+ def initialize(_message = nil)
53
+ super(
54
+ 'You cannot use #and_then! as the feature is disabled. ' \
55
+ 'Please use BCDD::Result.config.feature.enable!(:and_then!) to enable it.'
56
+ )
48
57
  end
49
58
  end
50
59
  end
@@ -24,7 +24,7 @@ class BCDD::Result
24
24
 
25
25
  FACTORY = <<~RUBY
26
26
  private def _Result
27
- @_Result ||= Result.with(subject: self, terminal: %<terminal>s)
27
+ @_Result ||= Result.with(source: self, terminal: %<terminal>s)
28
28
  end
29
29
  RUBY
30
30
 
@@ -38,13 +38,13 @@ class BCDD::Result
38
38
  module Addons
39
39
  module Continue
40
40
  private def Continue(value)
41
- Success.new(type: :continued, value: value, subject: self)
41
+ _Result.Success(IgnoredTypes::CONTINUE, value)
42
42
  end
43
43
  end
44
44
 
45
45
  module Given
46
46
  private def Given(value)
47
- Success.new(type: :given, value: value, subject: self)
47
+ _Result.Success(IgnoredTypes::GIVEN, value)
48
48
  end
49
49
  end
50
50
 
@@ -38,10 +38,10 @@ class BCDD::Result
38
38
 
39
39
  private_class_method :mixin!, :mixin_module, :result_factory_without_expectations
40
40
 
41
- def initialize(subject: nil, contract: nil, terminal: nil, **options)
41
+ def initialize(source: nil, contract: nil, terminal: nil, **options)
42
42
  @terminal = terminal
43
43
 
44
- @subject = subject
44
+ @source = source
45
45
 
46
46
  @contract = contract if contract.is_a?(Contract::Evaluator)
47
47
 
@@ -60,16 +60,16 @@ class BCDD::Result
60
60
  _ResultAs(Failure, type, value)
61
61
  end
62
62
 
63
- def with(subject:, terminal: nil)
64
- self.class.new(subject: subject, terminal: terminal, contract: contract)
63
+ def with(source:, terminal: nil)
64
+ self.class.new(source: source, terminal: terminal, contract: contract)
65
65
  end
66
66
 
67
67
  private
68
68
 
69
69
  def _ResultAs(kind_class, type, value)
70
- kind_class.new(type: type, value: value, subject: subject, expectations: contract, terminal: terminal)
70
+ kind_class.new(type: type, value: value, source: source, expectations: contract, terminal: terminal)
71
71
  end
72
72
 
73
- attr_reader :subject, :terminal, :contract
73
+ attr_reader :source, :terminal, :contract
74
74
  end
75
75
  end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ class BCDD::Result
4
+ module IgnoredTypes
5
+ LIST = ::Set[
6
+ GIVEN = :_given_,
7
+ CONTINUE = :_continue_
8
+ ].freeze
9
+
10
+ def self.include?(type)
11
+ LIST.member?(type)
12
+ end
13
+ end
14
+ end
@@ -21,7 +21,7 @@ class BCDD::Result
21
21
  end
22
22
 
23
23
  private def _ResultAs(kind_class, type, value, terminal: nil)
24
- kind_class.new(type: type, value: value, subject: self, terminal: terminal)
24
+ kind_class.new(type: type, value: value, source: self, terminal: terminal)
25
25
  end
26
26
  end
27
27
 
@@ -32,13 +32,13 @@ class BCDD::Result
32
32
  end
33
33
 
34
34
  private def Continue(value)
35
- _ResultAs(Success, :continued, value)
35
+ _ResultAs(Success, IgnoredTypes::CONTINUE, value)
36
36
  end
37
37
  end
38
38
 
39
39
  module Given
40
40
  private def Given(value)
41
- _ResultAs(Success, :given, value)
41
+ _ResultAs(Success, IgnoredTypes::GIVEN, value)
42
42
  end
43
43
  end
44
44
 
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BCDD::Result::Transitions
4
+ class Config
5
+ include ::Singleton
6
+
7
+ attr_reader :listener, :trace_id
8
+
9
+ def initialize
10
+ @trace_id = -> {}
11
+ @listener = Listener::Null.new
12
+ end
13
+
14
+ def listener=(arg)
15
+ Listener.kind?(arg) or raise ::ArgumentError, "#{arg.inspect} must be a #{Listener}"
16
+
17
+ @listener = arg
18
+ end
19
+
20
+ def trace_id=(arg)
21
+ raise ::ArgumentError, 'must be a lambda with arity 0' unless arg.is_a?(::Proc) && arg.lambda? && arg.arity.zero?
22
+
23
+ @trace_id = arg
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BCDD::Result::Transitions
4
+ module Listener
5
+ module ClassMethods
6
+ def around_transitions?
7
+ false
8
+ end
9
+
10
+ def around_and_then?
11
+ false
12
+ end
13
+ end
14
+
15
+ def self.included(base)
16
+ base.extend(ClassMethods)
17
+ end
18
+
19
+ def self.extended(base)
20
+ base.extend(ClassMethods)
21
+ end
22
+
23
+ def self.kind?(arg)
24
+ (arg.is_a?(::Class) && arg < self) || (arg.is_a?(::Module) && arg.is_a?(self)) || arg.is_a?(Listeners::Chain)
25
+ end
26
+
27
+ def on_start(scope:); end
28
+
29
+ def around_transitions(scope:)
30
+ yield
31
+ end
32
+
33
+ def around_and_then(scope:, and_then:)
34
+ yield
35
+ end
36
+
37
+ def on_record(record:); end
38
+
39
+ def on_finish(transitions:); end
40
+
41
+ def before_interruption(exception:, transitions:); end
42
+ end
43
+
44
+ module Listener::Null
45
+ extend Listener
46
+
47
+ def self.new
48
+ self
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BCDD::Result::Transitions
4
+ class Listeners
5
+ class Chain
6
+ include Listener
7
+
8
+ attr_reader :listeners
9
+
10
+ def initialize(list)
11
+ if list.empty? || list.any? { !Listener.kind?(_1) }
12
+ raise ArgumentError, "listeners must be a list of #{Listener}"
13
+ end
14
+
15
+ around_and_then = list.select(&:around_and_then?)
16
+ around_transitions = list.select(&:around_transitions?)
17
+
18
+ raise ArgumentError, 'only one listener can have around_and_then? == true' if around_and_then.size > 1
19
+ raise ArgumentError, 'only one listener can have around_transitions? == true' if around_transitions.size > 1
20
+
21
+ @listeners = { list: list, around_and_then: around_and_then[0], around_transitions: around_transitions[0] }
22
+ end
23
+
24
+ def new
25
+ list, around_and_then, around_transitions = listeners[:list], nil, nil
26
+
27
+ instances = list.map do |item|
28
+ instance = item.new
29
+ around_and_then = instance if listener?(:around_and_then, instance)
30
+ around_transitions = instance if listener?(:around_transitions, instance)
31
+
32
+ instance
33
+ end
34
+
35
+ list.one? ? list[0].new : Listeners.send(:new, instances, around_and_then, around_transitions)
36
+ end
37
+
38
+ private
39
+
40
+ def listener?(name, obj)
41
+ listener = listeners[name]
42
+
43
+ !listener.nil? && (obj.is_a?(listener) || obj == listener)
44
+ end
45
+ end
46
+
47
+ private_class_method :new
48
+
49
+ def self.[](*listeners)
50
+ Chain.new(listeners)
51
+ end
52
+
53
+ attr_reader :listeners, :around_and_then_listener, :around_transitions_listener
54
+
55
+ private :listeners, :around_and_then_listener, :around_transitions_listener
56
+
57
+ def initialize(listeners, around_and_then_listener, around_transitions_listener)
58
+ @listeners = listeners
59
+ @around_and_then_listener = around_and_then_listener || Listener::Null
60
+ @around_transitions_listener = around_transitions_listener || Listener::Null
61
+ end
62
+
63
+ def on_start(scope:)
64
+ listeners.each { _1.on_start(scope: scope) }
65
+ end
66
+
67
+ def around_transitions(scope:, &block)
68
+ around_transitions_listener.around_transitions(scope: scope, &block)
69
+ end
70
+
71
+ def around_and_then(scope:, and_then:, &block)
72
+ around_and_then_listener.around_and_then(scope: scope, and_then: and_then, &block)
73
+ end
74
+
75
+ def on_record(record:)
76
+ listeners.each { _1.on_record(record: record) }
77
+ end
78
+
79
+ def on_finish(transitions:)
80
+ listeners.each { _1.on_finish(transitions: transitions) }
81
+ end
82
+
83
+ def before_interruption(exception:, transitions:)
84
+ listeners.each { _1.before_interruption(exception: exception, transitions: transitions) }
85
+ end
86
+ end
87
+ end
@@ -2,15 +2,13 @@
2
2
 
3
3
  module BCDD::Result::Transitions
4
4
  module Tracking::Disabled
5
- def self.start(name:, desc:); end
6
-
7
- def self.finish(result:); end
8
-
9
- def self.reset!; end
5
+ def self.exec(_name, _desc)
6
+ EnsureResult[yield]
7
+ end
10
8
 
11
9
  def self.record(result); end
12
10
 
13
- def self.record_and_then(_type, _data, _subject)
11
+ def self.record_and_then(_type, _data)
14
12
  yield
15
13
  end
16
14
  end