axn 0.1.0.pre.alpha.2.5.3.1 → 0.1.0.pre.alpha.2.6.1

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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +4 -1
  3. data/CHANGELOG.md +25 -1
  4. data/README.md +2 -11
  5. data/docs/reference/action-result.md +2 -0
  6. data/docs/reference/class.md +12 -4
  7. data/docs/reference/configuration.md +53 -20
  8. data/docs/reference/instance.md +2 -2
  9. data/docs/strategies/index.md +1 -1
  10. data/docs/usage/setup.md +1 -1
  11. data/docs/usage/writing.md +9 -9
  12. data/lib/action/attachable/steps.rb +16 -1
  13. data/lib/action/attachable.rb +3 -3
  14. data/lib/action/{core/configuration.rb → configuration.rb} +3 -4
  15. data/lib/action/context.rb +38 -0
  16. data/lib/action/core/automatic_logging.rb +93 -0
  17. data/lib/action/core/context/facade.rb +69 -0
  18. data/lib/action/core/context/facade_inspector.rb +63 -0
  19. data/lib/action/core/context/internal.rb +32 -0
  20. data/lib/action/core/contract.rb +167 -211
  21. data/lib/action/core/contract_for_subfields.rb +84 -82
  22. data/lib/action/core/contract_validation.rb +62 -0
  23. data/lib/action/core/flow/callbacks.rb +54 -0
  24. data/lib/action/core/flow/exception_execution.rb +79 -0
  25. data/lib/action/core/flow/messages.rb +61 -0
  26. data/lib/action/core/flow.rb +19 -0
  27. data/lib/action/core/hoist_errors.rb +42 -40
  28. data/lib/action/core/hooks.rb +123 -0
  29. data/lib/action/core/logging.rb +22 -20
  30. data/lib/action/core/timing.rb +40 -0
  31. data/lib/action/core/tracing.rb +17 -0
  32. data/lib/action/core/use_strategy.rb +19 -17
  33. data/lib/action/core/validation/fields.rb +2 -0
  34. data/lib/action/core.rb +100 -0
  35. data/lib/action/enqueueable/via_sidekiq.rb +2 -2
  36. data/lib/action/enqueueable.rb +1 -1
  37. data/lib/action/{core/exceptions.rb → exceptions.rb} +1 -19
  38. data/lib/action/result.rb +95 -0
  39. data/lib/axn/factory.rb +27 -9
  40. data/lib/axn/version.rb +1 -1
  41. data/lib/axn.rb +10 -47
  42. metadata +19 -21
  43. data/lib/action/core/context_facade.rb +0 -209
  44. data/lib/action/core/handle_exceptions.rb +0 -163
  45. data/lib/action/core/top_level_around_hook.rb +0 -108
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Action
4
+ module Core
5
+ module ContractValidation
6
+ private
7
+
8
+ def _apply_inbound_preprocessing!
9
+ internal_field_configs.each do |config|
10
+ next unless config.preprocess
11
+
12
+ initial_value = @__context.provided_data[config.field]
13
+ new_value = config.preprocess.call(initial_value)
14
+ @__context.provided_data[config.field] = new_value
15
+ rescue StandardError => e
16
+ raise Action::ContractViolation::PreprocessingError, "Error preprocessing field '#{config.field}': #{e.message}"
17
+ end
18
+ end
19
+
20
+ def _validate_contract!(direction)
21
+ raise ArgumentError, "Invalid direction: #{direction}" unless %i[inbound outbound].include?(direction)
22
+
23
+ configs = direction == :inbound ? internal_field_configs : external_field_configs
24
+ validations = configs.each_with_object({}) do |config, hash|
25
+ hash[config.field] = config.validations
26
+ end
27
+ context = direction == :inbound ? internal_context : result
28
+ exception_klass = direction == :inbound ? Action::InboundValidationError : Action::OutboundValidationError
29
+
30
+ Validation::Fields.validate!(validations:, context:, exception_klass:)
31
+ end
32
+
33
+ def _apply_defaults!(direction)
34
+ raise ArgumentError, "Invalid direction: #{direction}" unless %i[inbound outbound].include?(direction)
35
+
36
+ if direction == :outbound
37
+ # For outbound defaults, first copy values from provided_data for fields that are both expected and exposed
38
+ external_field_configs.each do |config|
39
+ field = config.field
40
+ next if @__context.exposed_data[field].present? # Already has a value
41
+
42
+ @__context.exposed_data[field] = @__context.provided_data[field] if @__context.provided_data[field].present?
43
+ end
44
+ end
45
+
46
+ configs = direction == :inbound ? internal_field_configs : external_field_configs
47
+ defaults_mapping = configs.each_with_object({}) do |config, hash|
48
+ hash[config.field] = config.default
49
+ end.compact
50
+
51
+ defaults_mapping.each do |field, default_value_getter|
52
+ data_hash = direction == :inbound ? @__context.provided_data : @__context.exposed_data
53
+ next if data_hash[field].present?
54
+
55
+ default_value = default_value_getter.respond_to?(:call) ? instance_exec(&default_value_getter) : default_value_getter
56
+
57
+ data_hash[field] = default_value
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "action/core/event_handlers"
4
+
5
+ module Action
6
+ module Core
7
+ module Flow
8
+ module Callbacks
9
+ def self.included(base)
10
+ base.class_eval do
11
+ class_attribute :_error_handlers, default: []
12
+ class_attribute :_exception_handlers, default: []
13
+ class_attribute :_failure_handlers, default: []
14
+ class_attribute :_success_handlers, default: []
15
+
16
+ extend ClassMethods
17
+ end
18
+ end
19
+
20
+ module ClassMethods
21
+ # ONLY raised exceptions (i.e. NOT fail!). Skipped if exception is rescued via .rescues.
22
+ def on_exception(matcher = -> { true }, &handler)
23
+ raise ArgumentError, "on_exception must be called with a block" unless block_given?
24
+
25
+ self._exception_handlers += [Action::EventHandlers::ConditionalHandler.new(matcher:, handler:)]
26
+ end
27
+
28
+ # ONLY raised on fail! (i.e. NOT unhandled exceptions).
29
+ def on_failure(matcher = -> { true }, &handler)
30
+ raise ArgumentError, "on_failure must be called with a block" unless block_given?
31
+
32
+ self._failure_handlers += [Action::EventHandlers::ConditionalHandler.new(matcher:, handler:)]
33
+ end
34
+
35
+ # Handles both fail! and unhandled exceptions... but is NOT affected by .rescues
36
+ def on_error(matcher = -> { true }, &handler)
37
+ raise ArgumentError, "on_error must be called with a block" unless block_given?
38
+
39
+ self._error_handlers += [Action::EventHandlers::ConditionalHandler.new(matcher:, handler:)]
40
+ end
41
+
42
+ # Executes when the action completes successfully (after all after hooks complete successfully)
43
+ # Runs in child-first order (child handlers before parent handlers)
44
+ def on_success(&handler)
45
+ raise ArgumentError, "on_success must be called with a block" unless block_given?
46
+
47
+ # Prepend like after hooks - child handlers run before parent handlers
48
+ self._success_handlers = [handler] + _success_handlers
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Action
4
+ module Core
5
+ module Flow
6
+ module ExceptionExecution
7
+ def self.included(base)
8
+ base.class_eval do
9
+ include InstanceMethods
10
+
11
+ def _trigger_on_exception(exception)
12
+ interceptor = self.class._error_interceptor_for(exception:, action: self)
13
+ return if interceptor&.should_report_error == false
14
+
15
+ # Call any handlers registered on *this specific action* class
16
+ self.class._exception_handlers.each do |handler|
17
+ handler.execute_if_matches(exception:, action: self)
18
+ end
19
+
20
+ # Call any global handlers
21
+ Action.config.on_exception(exception, action: self, context: context_for_logging)
22
+ rescue StandardError => e
23
+ # No action needed -- downstream #on_exception implementation should ideally log any internal failures, but
24
+ # we don't want exception *handling* failures to cascade and overwrite the original exception.
25
+ Axn::Util.piping_error("executing on_exception hooks", action: self, exception: e)
26
+ end
27
+
28
+ def _trigger_on_success
29
+ # Call success handlers in child-first order (like after hooks)
30
+ self.class._success_handlers.each do |handler|
31
+ instance_exec(&handler)
32
+ rescue StandardError => e
33
+ # Log the error but continue with other handlers
34
+ Axn::Util.piping_error("executing on_success hook", action: self, exception: e)
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+ module InstanceMethods
41
+ private
42
+
43
+ def _with_exception_handling
44
+ yield
45
+ rescue StandardError => e
46
+ # on_error handlers run for both unhandled exceptions and fail!
47
+ self.class._error_handlers.each do |handler|
48
+ handler.execute_if_matches(exception: e, action: self)
49
+ end
50
+
51
+ # on_failure handlers run ONLY for fail!
52
+ if e.is_a?(Action::Failure)
53
+ self.class._failure_handlers.each do |handler|
54
+ handler.execute_if_matches(exception: e, action: self)
55
+ end
56
+ else
57
+ # on_exception handlers run for ONLY for unhandled exceptions. AND NOTE: may be skipped if the exception is rescued via `rescues`.
58
+ _trigger_on_exception(e)
59
+
60
+ @__context.exception = e
61
+ end
62
+
63
+ # Set failure state using accessor method
64
+ @__context.send(:failure=, true)
65
+ end
66
+
67
+ def try
68
+ yield
69
+ rescue Action::Failure => e
70
+ # NOTE: re-raising so we can still fail! from inside the block
71
+ raise e
72
+ rescue StandardError => e
73
+ _trigger_on_exception(e)
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Action
4
+ module Core
5
+ module Flow
6
+ module Messages
7
+ def self.included(base)
8
+ base.class_eval do
9
+ class_attribute :_success_msg, :_error_msg
10
+ class_attribute :_custom_error_interceptors, default: []
11
+
12
+ extend ClassMethods
13
+ include InstanceMethods
14
+ end
15
+ end
16
+
17
+ module ClassMethods
18
+ def messages(success: nil, error: nil)
19
+ self._success_msg = success if success.present?
20
+ self._error_msg = error if error.present?
21
+
22
+ true
23
+ end
24
+
25
+ def error_from(matcher = nil, message = nil, **match_and_messages)
26
+ _register_error_interceptor(matcher, message, should_report_error: true, **match_and_messages)
27
+ end
28
+
29
+ def rescues(matcher = nil, message = nil, **match_and_messages)
30
+ _register_error_interceptor(matcher, message, should_report_error: false, **match_and_messages)
31
+ end
32
+
33
+ def default_error = new.internal_context.default_error
34
+
35
+ # Private helpers
36
+
37
+ def _error_interceptor_for(exception:, action:)
38
+ Array(_custom_error_interceptors).detect do |int|
39
+ int.matches?(exception:, action:)
40
+ end
41
+ end
42
+
43
+ def _register_error_interceptor(matcher, message, should_report_error:, **match_and_messages)
44
+ method_name = should_report_error ? "error_from" : "rescues"
45
+ raise ArgumentError, "#{method_name} must be called with a key/value pair, or else keyword args" if [matcher, message].compact.size == 1
46
+
47
+ interceptors = { matcher => message }.compact.merge(match_and_messages).map do |(matcher, message)| # rubocop:disable Lint/ShadowingOuterLocalVariable
48
+ Action::EventHandlers::CustomErrorInterceptor.new(matcher:, message:, should_report_error:)
49
+ end
50
+
51
+ self._custom_error_interceptors += interceptors
52
+ end
53
+ end
54
+
55
+ module InstanceMethods
56
+ delegate :default_error, to: :internal_context
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "action/core/flow/messages"
4
+ require "action/core/flow/callbacks"
5
+ require "action/core/flow/exception_execution"
6
+
7
+ module Action
8
+ module Core
9
+ module Flow
10
+ def self.included(base)
11
+ base.class_eval do
12
+ include Messages
13
+ include Callbacks
14
+ include ExceptionExecution
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -1,55 +1,57 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Action
4
- module HoistErrors
5
- def self.included(base)
6
- base.class_eval do
7
- include InstanceMethods
4
+ module Core
5
+ module HoistErrors
6
+ def self.included(base)
7
+ base.class_eval do
8
+ include InstanceMethods
9
+ end
8
10
  end
9
- end
10
11
 
11
- module InstanceMethods
12
- private
12
+ module InstanceMethods
13
+ private
13
14
 
14
- MinimalFailedResult = Data.define(:error, :exception) do
15
- def ok? = false
16
- end
17
-
18
- # This method is used to ensure that the result of a block is successful before proceeding.
19
- #
20
- # Assumes success unless the block raises an exception or returns a failed result.
21
- # (i.e. if you wrap logic that is NOT an action call, it'll be successful unless it raises an exception)
22
- def hoist_errors(prefix: nil)
23
- raise ArgumentError, "#hoist_errors must be given a block to execute" unless block_given?
24
-
25
- result = begin
26
- yield
27
- rescue StandardError => e
28
- log "hoist_errors block transforming a #{e.class.name} exception: #{e.message}"
29
- MinimalFailedResult.new(error: nil, exception: e)
15
+ MinimalFailedResult = Data.define(:error, :exception) do
16
+ def ok? = false
30
17
  end
31
18
 
32
- # This ensures the last line of hoist_errors was an Action call
33
- #
34
- # CAUTION: if there are multiple calls per block, only the last one will be checked!
19
+ # This method is used to ensure that the result of a block is successful before proceeding.
35
20
  #
36
- unless result.respond_to?(:ok?)
37
- raise ArgumentError,
38
- "#hoist_errors is expected to wrap an Action call, but it returned a #{result.class.name} instead"
21
+ # Assumes success unless the block raises an exception or returns a failed result.
22
+ # (i.e. if you wrap logic that is NOT an action call, it'll be successful unless it raises an exception)
23
+ def hoist_errors(prefix: nil)
24
+ raise ArgumentError, "#hoist_errors must be given a block to execute" unless block_given?
25
+
26
+ result = begin
27
+ yield
28
+ rescue StandardError => e
29
+ log "hoist_errors block transforming a #{e.class.name} exception: #{e.message}"
30
+ MinimalFailedResult.new(error: nil, exception: e)
31
+ end
32
+
33
+ # This ensures the last line of hoist_errors was an Action call
34
+ #
35
+ # CAUTION: if there are multiple calls per block, only the last one will be checked!
36
+ #
37
+ unless result.respond_to?(:ok?)
38
+ raise ArgumentError,
39
+ "#hoist_errors is expected to wrap an Action call, but it returned a #{result.class.name} instead"
40
+ end
41
+
42
+ return result if result.ok?
43
+
44
+ _handle_hoisted_errors(result, prefix:)
39
45
  end
40
46
 
41
- return result if result.ok?
47
+ # Separate method to allow overriding in subclasses
48
+ def _handle_hoisted_errors(result, prefix: nil)
49
+ @__context.exception = result.exception if result.exception.present?
50
+ @__context.error_prefix = prefix if prefix.present?
42
51
 
43
- _handle_hoisted_errors(result, prefix:)
44
- end
45
-
46
- # Separate method to allow overriding in subclasses
47
- def _handle_hoisted_errors(result, prefix: nil)
48
- @context.exception = result.exception if result.exception.present?
49
- @context.error_prefix = prefix if prefix.present?
50
-
51
- error = result.exception.is_a?(Action::Failure) ? result.exception.message : result.error
52
- fail! error
52
+ error = result.exception.is_a?(Action::Failure) ? result.exception.message : result.error
53
+ fail! error
54
+ end
53
55
  end
54
56
  end
55
57
  end
@@ -0,0 +1,123 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Action
4
+ module Core
5
+ module Hooks
6
+ def self.included(base)
7
+ base.class_eval do
8
+ class_attribute :around_hooks, default: []
9
+ class_attribute :before_hooks, default: []
10
+ class_attribute :after_hooks, default: []
11
+
12
+ extend ClassMethods
13
+ end
14
+ end
15
+
16
+ module ClassMethods
17
+ # Public: Declare hooks to run around action execution. The around
18
+ # method may be called multiple times; subsequent calls append declared
19
+ # hooks to existing around hooks.
20
+ #
21
+ # Around hooks wrap the entire action execution, including before and
22
+ # after hooks. Parent hooks wrap child hooks (parent outside, child inside).
23
+ #
24
+ # hooks - Zero or more Symbol method names representing instance methods
25
+ # to be called around action execution. Each instance method
26
+ # invocation receives an argument representing the next link in
27
+ # the around hook chain.
28
+ # block - An optional block to be executed as a hook. If given, the block
29
+ # is executed after methods corresponding to any given Symbols.
30
+ def around(*hooks, &block)
31
+ hooks << block if block
32
+ hooks.each { |hook| self.around_hooks += [hook] }
33
+ end
34
+
35
+ # Public: Declare hooks to run before action execution. The before
36
+ # method may be called multiple times; subsequent calls append declared
37
+ # hooks to existing before hooks.
38
+ #
39
+ # Before hooks run in parent-first order (general setup first, then specific).
40
+ # Parent hooks run before child hooks.
41
+ #
42
+ # hooks - Zero or more Symbol method names representing instance methods
43
+ # to be called before action execution.
44
+ # block - An optional block to be executed as a hook. If given, the block
45
+ # is executed after methods corresponding to any given Symbols.
46
+ def before(*hooks, &block)
47
+ hooks << block if block
48
+ hooks.each { |hook| self.before_hooks += [hook] }
49
+ end
50
+
51
+ # Public: Declare hooks to run after action execution. The after
52
+ # method may be called multiple times; subsequent calls prepend declared
53
+ # hooks to existing after hooks.
54
+ #
55
+ # After hooks run in child-first order (specific cleanup first, then general).
56
+ # Child hooks run before parent hooks.
57
+ #
58
+ # hooks - Zero or more Symbol method names representing instance methods
59
+ # to be called after action execution.
60
+ # block - An optional block to be executed as a hook. If given, the block
61
+ # is executed before methods corresponding to any given Symbols.
62
+ def after(*hooks, &block)
63
+ hooks << block if block
64
+ hooks.each { |hook| self.after_hooks = [hook] + after_hooks }
65
+ end
66
+ end
67
+
68
+ private
69
+
70
+ def _with_hooks
71
+ _run_around_hooks do
72
+ _run_before_hooks
73
+ yield
74
+ _run_after_hooks
75
+ end
76
+ end
77
+
78
+ # Around hooks are reversed before injection to ensure parent hooks wrap
79
+ # child hooks (parent outside, child inside).
80
+ def _run_around_hooks(&block)
81
+ self.class.around_hooks.reverse.inject(block) do |chain, hook|
82
+ proc { _run_hook(hook, chain) }
83
+ end.call
84
+ end
85
+
86
+ # Before hooks run in the order they were added (parent first, then child).
87
+ def _run_before_hooks
88
+ _run_hooks(self.class.before_hooks)
89
+ end
90
+
91
+ # After hooks are reversed to ensure child hooks run before parent hooks
92
+ # (specific cleanup first, then general).
93
+ def _run_after_hooks
94
+ _run_hooks(self.class.after_hooks.reverse)
95
+ end
96
+
97
+ # Internal: Run a collection of hooks. The "_run_hooks" method is the common
98
+ # interface by which collections of either before or after hooks are run.
99
+ #
100
+ # hooks - An Array of Symbol and Procs.
101
+ #
102
+ # Returns nothing.
103
+ def _run_hooks(hooks)
104
+ hooks.each { |hook| _run_hook(hook) }
105
+ end
106
+
107
+ # Internal: Run an individual hook. The "_run_hook" method is the common
108
+ # interface by which an individual hook is run. If the given hook is a
109
+ # symbol, the method is invoked whether public or private. If the hook is a
110
+ # proc, the proc is evaluated in the context of the current instance.
111
+ #
112
+ # hook - A Symbol or Proc hook.
113
+ # args - Zero or more arguments to be passed as block arguments into the
114
+ # given block or as arguments into the method described by the given
115
+ # Symbol method name.
116
+ #
117
+ # Returns nothing.
118
+ def _run_hook(hook, *)
119
+ hook.is_a?(Symbol) ? send(hook, *) : instance_exec(*, &hook)
120
+ end
121
+ end
122
+ end
123
+ end
@@ -3,33 +3,35 @@
3
3
  require "active_support/core_ext/module/delegation"
4
4
 
5
5
  module Action
6
- module Logging
7
- LEVELS = %i[debug info warn error fatal].freeze
8
-
9
- def self.included(base)
10
- base.class_eval do
11
- extend ClassMethods
12
- delegate :log, *LEVELS, to: :class
6
+ module Core
7
+ module Logging
8
+ LEVELS = %i[debug info warn error fatal].freeze
9
+
10
+ def self.included(base)
11
+ base.class_eval do
12
+ extend ClassMethods
13
+ delegate :log, *LEVELS, to: :class
14
+ end
13
15
  end
14
- end
15
16
 
16
- module ClassMethods
17
- def default_log_level = Action.config.default_log_level
17
+ module ClassMethods
18
+ def log_level = Action.config.log_level
18
19
 
19
- def log(message, level: default_log_level, before: nil, after: nil)
20
- msg = [_log_prefix, message].compact_blank.join(" ")
21
- msg = [before, msg, after].compact_blank.join if before || after
20
+ def log(message, level: log_level, before: nil, after: nil)
21
+ msg = [_log_prefix, message].compact_blank.join(" ")
22
+ msg = [before, msg, after].compact_blank.join if before || after
22
23
 
23
- Action.config.logger.send(level, msg)
24
- end
24
+ Action.config.logger.send(level, msg)
25
+ end
25
26
 
26
- LEVELS.each do |level|
27
- define_method(level) do |message, before: nil, after: nil|
28
- log(message, level:, before:, after:)
27
+ LEVELS.each do |level|
28
+ define_method(level) do |message, before: nil, after: nil|
29
+ log(message, level:, before:, after:)
30
+ end
29
31
  end
30
- end
31
32
 
32
- def _log_prefix = "[#{name.presence || "Anonymous Class"}]"
33
+ def _log_prefix = "[#{name.presence || "Anonymous Class"}]"
34
+ end
33
35
  end
34
36
  end
35
37
  end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Action
4
+ module Core
5
+ module Timing
6
+ def self.included(base)
7
+ base.class_eval do
8
+ include InstanceMethods
9
+ end
10
+ end
11
+
12
+ # Get the current monotonic time
13
+ def self.now
14
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
15
+ end
16
+
17
+ # Calculate elapsed time in milliseconds
18
+ def self.elapsed_ms(start_time)
19
+ ((now - start_time) * 1000).round(3)
20
+ end
21
+
22
+ # Calculate elapsed time in seconds
23
+ def self.elapsed_seconds(start_time)
24
+ (now - start_time).round(6)
25
+ end
26
+
27
+ module InstanceMethods
28
+ private
29
+
30
+ def _with_timing
31
+ timing_start = Core::Timing.now
32
+ yield
33
+ ensure
34
+ elapsed_mils = Core::Timing.elapsed_ms(timing_start)
35
+ @__context.elapsed_time = elapsed_mils
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Action
4
+ module Core
5
+ module Tracing
6
+ private
7
+
8
+ def _with_tracing(&)
9
+ return yield unless Action.config.wrap_with_trace
10
+
11
+ Action.config.wrap_with_trace.call(self.class.name || "AnonymousClass", &)
12
+ rescue StandardError => e
13
+ Axn::Util.piping_error("running trace hook", action: self, exception: e)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -1,27 +1,29 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Action
4
- module UseStrategy
5
- extend ActiveSupport::Concern
4
+ module Core
5
+ module UseStrategy
6
+ extend ActiveSupport::Concern
6
7
 
7
- class_methods do
8
- def use(strategy_name, **config, &block)
9
- strategy = Action::Strategies.all[strategy_name.to_sym]
10
- raise StrategyNotFound, "Strategy #{strategy_name} not found" if strategy.blank?
11
- raise ArgumentError, "Strategy #{strategy_name} does not support config" if config.any? && !strategy.respond_to?(:setup)
8
+ class_methods do
9
+ def use(strategy_name, **config, &block)
10
+ strategy = Action::Strategies.all[strategy_name.to_sym]
11
+ raise StrategyNotFound, "Strategy #{strategy_name} not found" if strategy.blank?
12
+ raise ArgumentError, "Strategy #{strategy_name} does not support config" if config.any? && !strategy.respond_to?(:setup)
12
13
 
13
- # Allow dynamic setup of strategy (i.e. dynamically define module before returning)
14
- if strategy.respond_to?(:setup)
15
- configured = strategy.setup(**config, &block)
16
- raise ArgumentError, "Strategy #{strategy_name} setup method must return a module" unless configured.is_a?(Module)
14
+ # Allow dynamic setup of strategy (i.e. dynamically define module before returning)
15
+ if strategy.respond_to?(:setup)
16
+ configured = strategy.setup(**config, &block)
17
+ raise ArgumentError, "Strategy #{strategy_name} setup method must return a module" unless configured.is_a?(Module)
17
18
 
18
- strategy = configured
19
- else
20
- raise ArgumentError, "Strategy #{strategy_name} does not support config (define #setup method)" if config.any?
21
- raise ArgumentError, "Strategy #{strategy_name} does not support blocks (define #setup method)" if block_given?
22
- end
19
+ strategy = configured
20
+ else
21
+ raise ArgumentError, "Strategy #{strategy_name} does not support config (define #setup method)" if config.any?
22
+ raise ArgumentError, "Strategy #{strategy_name} does not support blocks (define #setup method)" if block_given?
23
+ end
23
24
 
24
- include strategy
25
+ include strategy
26
+ end
25
27
  end
26
28
  end
27
29
  end
@@ -15,6 +15,8 @@ module Action
15
15
  end
16
16
 
17
17
  def read_attribute_for_validation(attr)
18
+ # The context here is actually a facade (InternalContext or Result)
19
+ # which already handles reading from the correct data source
18
20
  @context.public_send(attr)
19
21
  end
20
22