axn 0.1.0.pre.alpha.2.7 → 0.1.0.pre.alpha.2.8

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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +11 -5
  3. data/CHANGELOG.md +14 -1
  4. data/Rakefile +12 -0
  5. data/docs/intro/about.md +2 -2
  6. data/docs/intro/overview.md +18 -0
  7. data/docs/recipes/rubocop-integration.md +352 -0
  8. data/docs/reference/action-result.md +2 -2
  9. data/docs/reference/class.md +122 -6
  10. data/docs/reference/configuration.md +43 -9
  11. data/docs/reference/instance.md +0 -52
  12. data/docs/usage/setup.md +4 -0
  13. data/docs/usage/steps.md +335 -0
  14. data/docs/usage/writing.md +70 -3
  15. data/lib/action/attachable/steps.rb +18 -17
  16. data/lib/action/attachable/subactions.rb +1 -1
  17. data/lib/action/context.rb +10 -14
  18. data/lib/action/core/context/facade.rb +11 -2
  19. data/lib/action/core/context/facade_inspector.rb +3 -2
  20. data/lib/action/core/context/internal.rb +3 -11
  21. data/lib/action/core/contract_validation.rb +1 -1
  22. data/lib/action/core/flow/callbacks.rb +27 -12
  23. data/lib/action/core/flow/exception_execution.rb +2 -5
  24. data/lib/action/core/flow/handlers/{base_handler.rb → base_descriptor.rb} +7 -4
  25. data/lib/action/core/flow/handlers/descriptors/callback_descriptor.rb +17 -0
  26. data/lib/action/core/flow/handlers/descriptors/message_descriptor.rb +53 -0
  27. data/lib/action/core/flow/handlers/matcher.rb +41 -2
  28. data/lib/action/core/flow/handlers/resolvers/base_resolver.rb +28 -0
  29. data/lib/action/core/flow/handlers/resolvers/callback_resolver.rb +29 -0
  30. data/lib/action/core/flow/handlers/resolvers/message_resolver.rb +59 -0
  31. data/lib/action/core/flow/handlers.rb +7 -4
  32. data/lib/action/core/flow/messages.rb +15 -41
  33. data/lib/action/core/nesting_tracking.rb +31 -0
  34. data/lib/action/core/timing.rb +1 -1
  35. data/lib/action/core.rb +20 -12
  36. data/lib/action/exceptions.rb +20 -2
  37. data/lib/action/result.rb +30 -32
  38. data/lib/axn/factory.rb +22 -23
  39. data/lib/axn/rubocop.rb +10 -0
  40. data/lib/axn/version.rb +1 -1
  41. data/lib/rubocop/cop/axn/README.md +237 -0
  42. data/lib/rubocop/cop/axn/unchecked_result.rb +327 -0
  43. metadata +14 -6
  44. data/lib/action/core/flow/handlers/callback_handler.rb +0 -21
  45. data/lib/action/core/flow/handlers/message_handler.rb +0 -27
  46. data/lib/action/core/hoist_errors.rb +0 -58
@@ -11,28 +11,24 @@ module Action
11
11
  # Framework-managed fields
12
12
  @failure = false
13
13
  @exception = nil
14
- @error_from_user = nil
15
- @error_prefix = nil
16
14
  @elapsed_time = nil
17
15
  end
18
16
 
19
- def fail!(message = nil)
20
- @error_from_user = message if message.present?
21
- raise Action::Failure, message
22
- end
23
-
24
- # INTERNAL: base for further filtering (for logging) or providing user with usage hints
25
- def __combined_data = @provided_data.merge(@exposed_data)
26
-
27
17
  # Framework state methods
28
18
  def ok? = !@failure
29
19
  def failed? = @failure || false
30
20
 
31
21
  # Framework field accessors
32
- attr_accessor :exception, :error_from_user, :error_prefix, :elapsed_time
22
+ attr_accessor :elapsed_time
23
+ attr_reader :exception
24
+ private :elapsed_time=
33
25
 
34
- # Internal failure state setter (for framework use)
35
- attr_writer :failure
36
- private :failure=
26
+ # INTERNAL: base for further filtering (for logging) or providing user with usage hints
27
+ def __combined_data = @provided_data.merge(@exposed_data)
28
+
29
+ def __record_exception(e)
30
+ @exception = e
31
+ @failure = true
32
+ end
37
33
  end
38
34
  end
@@ -15,7 +15,7 @@ module Action
15
15
 
16
16
  (@declared_fields + Array(implicitly_allowed_fields)).each do |field|
17
17
  singleton_class.define_method(field) do
18
- context_data_source[field]
18
+ _context_data_source[field]
19
19
  end
20
20
  end
21
21
  end
@@ -34,6 +34,15 @@ module Action
34
34
 
35
35
  def action_name = @action.class.name.presence || "The action"
36
36
 
37
- def context_data_source = raise NotImplementedError
37
+ def _context_data_source = raise NotImplementedError
38
+
39
+ def _msg_resolver(event_type, exception:)
40
+ Action::Core::Flow::Handlers::Resolvers::MessageResolver.new(
41
+ action._messages_registry,
42
+ event_type,
43
+ action:,
44
+ exception:,
45
+ )
46
+ end
38
47
  end
39
48
  end
@@ -22,8 +22,9 @@ module Action
22
22
  return unless facade.is_a?(Action::Result)
23
23
 
24
24
  return "[OK]" if context.ok?
25
- unless context.exception
26
- return context.error_from_user.present? ? "[failed with '#{context.error_from_user}']" : "[failed]"
25
+
26
+ if context.exception.is_a?(Action::Failure)
27
+ return context.exception.message.present? ? "[failed with '#{context.exception.message}']" : "[failed]"
27
28
  end
28
29
 
29
30
  %([failed with #{context.exception.class.name}: '#{context.exception.message}'])
@@ -5,20 +5,12 @@ require "action/core/context/facade"
5
5
  module Action
6
6
  # Inbound / Internal ContextFacade
7
7
  class InternalContext < ContextFacade
8
- # Available for use from within message callables
9
- def default_error
10
- msg = action.class._static_message_for(:error, action:, exception: @context.exception || Action::Failure.new)
11
- [@context.error_prefix, msg.presence || "Something went wrong"].compact.join(" ").squeeze(" ")
12
- end
13
-
14
- def default_success
15
- msg = action.class._static_message_for(:success, action:, exception: nil)
16
- msg.presence || "Action completed successfully"
17
- end
8
+ def default_error = _msg_resolver(:error, exception: Action::Failure.new).resolve_default_message
9
+ def default_success = _msg_resolver(:success, exception: nil).resolve_default_message
18
10
 
19
11
  private
20
12
 
21
- def context_data_source = @context.provided_data
13
+ def _context_data_source = @context.provided_data
22
14
 
23
15
  def method_missing(method_name, ...) # rubocop:disable Style/MissingRespondToMissing (because we're not actually responding to anything additional)
24
16
  if @context.__combined_data.key?(method_name.to_sym)
@@ -13,7 +13,7 @@ module Action
13
13
  new_value = config.preprocess.call(initial_value)
14
14
  @__context.provided_data[config.field] = new_value
15
15
  rescue StandardError => e
16
- raise Action::ContractViolation::PreprocessingError, "Error preprocessing field '#{config.field}': #{e.message}"
16
+ raise Action::ContractViolation::PreprocessingError, "Error preprocessing field '#{config.field}': #{e.message}", cause: e
17
17
  end
18
18
  end
19
19
 
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "action/core/flow/handlers"
4
+ require "action/core/flow/handlers/resolvers/callback_resolver"
4
5
 
5
6
  module Action
6
7
  module Core
@@ -17,35 +18,49 @@ module Action
17
18
  module ClassMethods
18
19
  # Internal dispatcher
19
20
  def _dispatch_callbacks(event_type, action:, exception: nil)
20
- _callbacks_registry.for(event_type).each do |handler|
21
- handler.apply(action:, exception:)
22
- end
21
+ resolver = Action::Core::Flow::Handlers::Resolvers::CallbackResolver.new(
22
+ _callbacks_registry,
23
+ event_type,
24
+ action:,
25
+ exception:,
26
+ )
27
+ resolver.execute_callbacks
23
28
  end
24
29
 
25
30
  # ONLY raised exceptions (i.e. NOT fail!).
26
- def on_exception(**, &block) = _add_callback(:exception, **, block:)
31
+ def on_exception(handler = nil, **, &block) = _add_callback(:exception, handler:, **, block:)
27
32
 
28
33
  # ONLY raised on fail! (i.e. NOT unhandled exceptions).
29
- def on_failure(**, &block) = _add_callback(:failure, **, block:)
34
+ def on_failure(handler = nil, **, &block) = _add_callback(:failure, handler:, **, block:)
30
35
 
31
36
  # Handles both fail! and unhandled exceptions
32
- def on_error(**, &block) = _add_callback(:error, **, block:)
37
+ def on_error(handler = nil, **, &block) = _add_callback(:error, handler:, **, block:)
33
38
 
34
39
  # Executes when the action completes successfully (after all after hooks complete successfully)
35
40
  # Runs in child-first order (child handlers before parent handlers)
36
- def on_success(**, &block) = _add_callback(:success, **, block:)
41
+ def on_success(handler = nil, **, &block) = _add_callback(:success, handler:, **, block:)
37
42
 
38
43
  private
39
44
 
40
- def _add_callback(event_type, block:, **kwargs)
45
+ def _add_callback(event_type, handler: nil, block: nil, **kwargs)
41
46
  raise ArgumentError, "on_#{event_type} cannot be called with both :if and :unless" if kwargs.key?(:if) && kwargs.key?(:unless)
47
+ raise ArgumentError, "on_#{event_type} cannot be called with both a block and a handler" if block && handler
48
+ raise ArgumentError, "on_#{event_type} must be called with a block or symbol" unless block || handler
42
49
 
43
- condition = kwargs.key?(:if) ? kwargs[:if] : kwargs[:unless]
44
- raise ArgumentError, "on_#{event_type} must be called with a block" unless block
50
+ # If handler is already a descriptor, use it directly
51
+ entry = if handler.is_a?(Action::Core::Flow::Handlers::Descriptors::CallbackDescriptor)
52
+ raise ArgumentError, "Cannot pass additional configuration with prebuilt descriptor" if kwargs.any? || block
53
+
54
+ handler
55
+ else
56
+ Action::Core::Flow::Handlers::Descriptors::CallbackDescriptor.build(
57
+ handler: handler || block,
58
+ **kwargs,
59
+ )
60
+ end
45
61
 
46
- matcher = condition.nil? ? nil : Action::Core::Flow::Handlers::Matcher.new(condition, invert: kwargs.key?(:unless))
47
- entry = Action::Core::Flow::Handlers::CallbackHandler.new(matcher:, handler: block)
48
62
  self._callbacks_registry = _callbacks_registry.register(event_type:, entry:)
63
+ true
49
64
  end
50
65
  end
51
66
  end
@@ -33,6 +33,8 @@ module Action
33
33
  def _with_exception_handling
34
34
  yield
35
35
  rescue StandardError => e
36
+ @__context.__record_exception(e)
37
+
36
38
  # on_error handlers run for both unhandled exceptions and fail!
37
39
  self.class._dispatch_callbacks(:error, action: self, exception: e)
38
40
 
@@ -42,12 +44,7 @@ module Action
42
44
  else
43
45
  # on_exception handlers run for ONLY for unhandled exceptions.
44
46
  _trigger_on_exception(e)
45
-
46
- @__context.exception = e
47
47
  end
48
-
49
- # Set failure state using accessor method
50
- @__context.send(:failure=, true)
51
48
  end
52
49
 
53
50
  def try
@@ -8,15 +8,15 @@ module Action
8
8
  # "Handlers" doesn't feel like *quite* the right name for this, but basically things in this namespace
9
9
  # relate to conditionally-invoked code blocks (e.g. callbacks, messages, etc.)
10
10
  module Handlers
11
- class BaseHandler
11
+ class BaseDescriptor
12
12
  def initialize(matcher: nil, handler: nil)
13
13
  @matcher = matcher
14
14
  @handler = handler
15
15
  end
16
16
 
17
- attr_reader :handler
17
+ attr_reader :handler, :matcher
18
18
 
19
- def static? = @matcher.nil?
19
+ def static? = @matcher.nil? || @matcher.static?
20
20
 
21
21
  def matches?(action:, exception:)
22
22
  return true if static?
@@ -24,7 +24,10 @@ module Action
24
24
  @matcher.call(exception:, action:)
25
25
  end
26
26
 
27
- # Subclasses should implement `apply(action:, exception:)`
27
+ def self.build(handler: nil, if: nil, unless: nil, **)
28
+ matcher = Matcher.build(if:, unless:)
29
+ new(matcher:, handler:)
30
+ end
28
31
  end
29
32
  end
30
33
  end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "action/core/flow/handlers/base_descriptor"
4
+
5
+ module Action
6
+ module Core
7
+ module Flow
8
+ module Handlers
9
+ module Descriptors
10
+ # Data structure for callback configuration - no behavior, just data
11
+ class CallbackDescriptor < BaseDescriptor
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "action/core/flow/handlers/base_descriptor"
4
+
5
+ module Action
6
+ module Core
7
+ module Flow
8
+ module Handlers
9
+ module Descriptors
10
+ # Data structure for message configuration - no behavior, just data
11
+ class MessageDescriptor < BaseDescriptor
12
+ attr_reader :prefix
13
+
14
+ def initialize(matcher:, handler:, prefix: nil)
15
+ super(matcher:, handler:)
16
+ @prefix = prefix
17
+ end
18
+
19
+ def self.build(handler: nil, if: nil, unless: nil, prefix: nil, from: nil, **)
20
+ new(
21
+ handler:,
22
+ prefix:,
23
+ matcher: _build_matcher(if:, unless:, from:),
24
+ )
25
+ end
26
+
27
+ def self._build_matcher(if:, unless:, from:)
28
+ rules = [
29
+ binding.local_variable_get(:if),
30
+ binding.local_variable_get(:unless),
31
+ _build_rule_for_from_condition(from),
32
+ ].compact
33
+
34
+ Action::Core::Flow::Handlers::Matcher.new(rules, invert: !!binding.local_variable_get(:unless))
35
+ end
36
+
37
+ def self._build_rule_for_from_condition(from_class)
38
+ return nil unless from_class
39
+
40
+ if from_class.is_a?(String)
41
+ lambda { |exception:, **|
42
+ exception.is_a?(Action::Failure) && exception.source&.class&.name == from_class
43
+ }
44
+ else
45
+ ->(exception:, **) { exception.is_a?(Action::Failure) && exception.source.is_a?(from_class) }
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -6,14 +6,15 @@ module Action
6
6
  module Core
7
7
  module Flow
8
8
  module Handlers
9
- class Matcher
9
+ class SingleRuleMatcher
10
10
  def initialize(rule, invert: false)
11
11
  @rule = rule
12
12
  @invert = invert
13
13
  end
14
14
 
15
15
  def call(exception:, action:)
16
- @invert ? !matches?(exception:, action:) : matches?(exception:, action:)
16
+ result = matches?(exception:, action:)
17
+ @invert ? !result : result
17
18
  rescue StandardError => e
18
19
  Axn::Util.piping_error("determining if handler applies to exception", action:, exception: e)
19
20
  end
@@ -79,6 +80,44 @@ module Action
79
80
  false
80
81
  end
81
82
  end
83
+
84
+ class Matcher
85
+ # NOTE: invert means it's an unless rather than an if. This will apply to ALL rules (sufficient for current use case,
86
+ # but flagging if we ever extend this for complex matching)
87
+ def initialize(rules, invert: false)
88
+ @rules = Array(rules).compact
89
+ @invert = invert
90
+ end
91
+
92
+ def call(exception:, action:)
93
+ matches?(exception:, action:)
94
+ rescue StandardError => e
95
+ Axn::Util.piping_error("determining if handler applies to exception", action:, exception: e)
96
+ end
97
+
98
+ def static? = @rules.empty?
99
+ def invert? = !!@invert
100
+
101
+ # Class method to build matcher from kwargs
102
+ def self.build(if: nil, unless: nil)
103
+ if_condition = binding.local_variable_get(:if)
104
+ unless_condition = binding.local_variable_get(:unless)
105
+
106
+ raise Action::UnsupportedArgument, "providing both :if and :unless" if if_condition && unless_condition
107
+
108
+ new(Array(if_condition || unless_condition).compact, invert: !!unless_condition)
109
+ end
110
+
111
+ private
112
+
113
+ def matches?(exception:, action:)
114
+ return true if @rules.empty?
115
+
116
+ @rules.all? do |rule|
117
+ SingleRuleMatcher.new(rule, invert: @invert).call(exception:, action:)
118
+ end
119
+ end
120
+ end
82
121
  end
83
122
  end
84
123
  end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Action
4
+ module Core
5
+ module Flow
6
+ module Handlers
7
+ module Resolvers
8
+ class BaseResolver
9
+ def initialize(registry, event_type, action:, exception:)
10
+ @registry = registry
11
+ @event_type = event_type
12
+ @action = action
13
+ @exception = exception
14
+ end
15
+
16
+ protected
17
+
18
+ attr_reader :registry, :event_type, :action, :exception
19
+
20
+ def candidate_entries = registry.for(event_type)
21
+ def matching_entries = candidate_entries.select { |descriptor| descriptor.matches?(action:, exception:) }
22
+ def static_entries = candidate_entries.select(&:static?)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "action/core/flow/handlers/invoker"
4
+
5
+ module Action
6
+ module Core
7
+ module Flow
8
+ module Handlers
9
+ module Resolvers
10
+ # Internal: resolves and executes callbacks
11
+ class CallbackResolver < BaseResolver
12
+ def execute_callbacks
13
+ matching_entries.each do |descriptor|
14
+ execute_callback(descriptor)
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ # Executes a specific callback descriptor
21
+ def execute_callback(descriptor)
22
+ Invoker.call(operation: "executing callback", action:, handler: descriptor.handler, exception:)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "action/core/flow/handlers/invoker"
4
+
5
+ module Action
6
+ module Core
7
+ module Flow
8
+ module Handlers
9
+ module Resolvers
10
+ # Internal: resolves messages with different strategies
11
+ class MessageResolver < BaseResolver
12
+ DEFAULT_ERROR = "Something went wrong"
13
+ DEFAULT_SUCCESS = "Action completed successfully"
14
+
15
+ def resolve_message
16
+ descriptor = matching_entries.detect { |d| message_from(d) }
17
+ message_from(descriptor) || fallback_message
18
+ end
19
+
20
+ def resolve_default_message
21
+ message_from(default_descriptor) || fallback_message
22
+ end
23
+
24
+ private
25
+
26
+ def default_descriptor
27
+ # NOTE: descriptor.handler check avoids returning a prefix-only descriptor (which
28
+ # needs to look up a default handler via this method to return a message)
29
+ static_entries.detect { |descriptor| descriptor.handler && message_from(descriptor) }
30
+ end
31
+
32
+ def message_from(descriptor)
33
+ message = resolved_message_body(descriptor)
34
+ return nil unless message.present?
35
+
36
+ descriptor.prefix ? "#{descriptor.prefix}#{message}" : message
37
+ end
38
+
39
+ def resolved_message_body(descriptor)
40
+ return nil unless descriptor
41
+
42
+ if descriptor.handler
43
+ invoke_handler(descriptor.handler)
44
+ elsif exception
45
+ exception.message
46
+ elsif descriptor.prefix
47
+ # For prefix-only success messages, find a default message from other descriptors
48
+ invoke_handler(default_descriptor&.handler)
49
+ end
50
+ end
51
+
52
+ def invoke_handler(handler) = handler ? Invoker.call(operation: "determining message callable", action:, handler:, exception:).presence : nil
53
+ def fallback_message = event_type == :success ? DEFAULT_SUCCESS : DEFAULT_ERROR
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -9,9 +9,12 @@ module Action
9
9
  end
10
10
  end
11
11
 
12
+ require "action/core/flow/handlers/base_descriptor"
13
+ require "action/core/flow/handlers/matcher"
14
+ require "action/core/flow/handlers/resolvers/base_resolver"
15
+ require "action/core/flow/handlers/descriptors/message_descriptor"
16
+ require "action/core/flow/handlers/descriptors/callback_descriptor"
12
17
  require "action/core/flow/handlers/invoker"
18
+ require "action/core/flow/handlers/resolvers/callback_resolver"
13
19
  require "action/core/flow/handlers/registry"
14
- require "action/core/flow/handlers/matcher"
15
- require "action/core/flow/handlers/base_handler"
16
- require "action/core/flow/handlers/callback_handler"
17
- require "action/core/flow/handlers/message_handler"
20
+ require "action/core/flow/handlers/resolvers/message_resolver"
@@ -11,64 +11,38 @@ module Action
11
11
  class_attribute :_messages_registry, default: Action::Core::Flow::Handlers::Registry.empty
12
12
 
13
13
  extend ClassMethods
14
- include InstanceMethods
15
14
  end
16
15
  end
17
16
 
18
17
  module ClassMethods
19
- # Internal: resolve a message for the given event (conditional first, then static)
20
- def _message_for(event_type, action:, exception: nil)
21
- _conditional_message_for(event_type, action:, exception:) ||
22
- _static_message_for(event_type, action:, exception:)
23
- end
24
-
25
- def _conditional_message_for(event_type, action:, exception: nil)
26
- _messages_registry.for(event_type).each do |handler|
27
- next if handler.respond_to?(:static?) && handler.static?
28
-
29
- msg = handler.apply(action:, exception:)
30
- return msg if msg.present?
31
- end
32
- nil
33
- end
34
-
35
- def _static_message_for(event_type, action:, exception: nil)
36
- _messages_registry.for(event_type).each do |handler|
37
- next unless handler.respond_to?(:static?) && handler.static?
38
-
39
- msg = handler.apply(action:, exception:)
40
- return msg if msg.present?
41
- end
42
- nil
43
- end
44
-
45
18
  def success(message = nil, **, &) = _add_message(:success, message:, **, &)
46
19
  def error(message = nil, **, &) = _add_message(:error, message:, **, &)
47
20
 
48
- def default_error = new.internal_context.default_error
49
- def default_success = new.internal_context.default_success
50
-
51
21
  private
52
22
 
53
23
  def _add_message(kind, message:, **kwargs, &block)
54
- raise ArgumentError, "#{kind} cannot be called with both :if and :unless" if kwargs.key?(:if) && kwargs.key?(:unless)
55
-
56
- condition = kwargs.key?(:if) ? kwargs[:if] : kwargs[:unless]
24
+ raise Action::UnsupportedArgument, "calling #{kind} with both :if and :unless" if kwargs.key?(:if) && kwargs.key?(:unless)
25
+ raise Action::UnsupportedArgument, "Combining from: with if: or unless:" if kwargs.key?(:from) && (kwargs.key?(:if) || kwargs.key?(:unless))
57
26
  raise ArgumentError, "Provide either a message or a block, not both" if message && block_given?
58
- raise ArgumentError, "Provide a message or a block" unless message || block_given?
27
+ raise ArgumentError, "Provide a message, block, or prefix" unless message || block_given? || kwargs[:prefix]
28
+ raise ArgumentError, "from: only applies to error messages" if kwargs.key?(:from) && kind != :error
59
29
 
60
- handler = block_given? ? block : message
30
+ # If message is already a descriptor, use it directly
31
+ entry = if message.is_a?(Action::Core::Flow::Handlers::Descriptors::MessageDescriptor)
32
+ raise ArgumentError, "Cannot pass additional configuration with prebuilt descriptor" if kwargs.any? || block_given?
33
+
34
+ message
35
+ else
36
+ Action::Core::Flow::Handlers::Descriptors::MessageDescriptor.build(
37
+ handler: block_given? ? block : message,
38
+ **kwargs,
39
+ )
40
+ end
61
41
 
62
- matcher = condition.nil? ? nil : Action::Core::Flow::Handlers::Matcher.new(condition, invert: kwargs.key?(:unless))
63
- entry = Action::Core::Flow::Handlers::MessageHandler.new(matcher:, handler:)
64
42
  self._messages_registry = _messages_registry.register(event_type: kind, entry:)
65
43
  true
66
44
  end
67
45
  end
68
-
69
- module InstanceMethods
70
- delegate :default_error, :default_success, to: :internal_context
71
- end
72
46
  end
73
47
  end
74
48
  end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Action
4
+ module Core
5
+ module NestingTracking
6
+ def self.included(base)
7
+ base.class_eval do
8
+ extend ClassMethods
9
+ end
10
+ end
11
+
12
+ module ClassMethods
13
+ def _nested_in_another_axn?
14
+ NestingTracking._current_axn_stack.any?
15
+ end
16
+ end
17
+
18
+ def _tracking_nesting(axn)
19
+ NestingTracking._current_axn_stack.push(axn)
20
+ yield
21
+ ensure
22
+ NestingTracking._current_axn_stack.pop
23
+ end
24
+
25
+ # Shared method for both class and instance access
26
+ def self._current_axn_stack
27
+ ActiveSupport::IsolatedExecutionState[:_axn_stack] ||= []
28
+ end
29
+ end
30
+ end
31
+ end
@@ -32,7 +32,7 @@ module Action
32
32
  yield
33
33
  ensure
34
34
  elapsed_mils = Core::Timing.elapsed_ms(timing_start)
35
- @__context.elapsed_time = elapsed_mils
35
+ @__context.send(:elapsed_time=, elapsed_mils)
36
36
  end
37
37
  end
38
38
  end