bcdd-result 0.11.0 → 0.13.0

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