axn 0.1.0.pre.alpha.2.5.3.1 → 0.1.0.pre.alpha.2.6
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +12 -1
- data/README.md +2 -11
- data/docs/reference/configuration.md +15 -4
- data/docs/reference/instance.md +2 -2
- data/docs/strategies/index.md +1 -1
- data/docs/usage/setup.md +1 -1
- data/docs/usage/writing.md +8 -8
- data/lib/action/attachable.rb +3 -3
- data/lib/action/{core/configuration.rb → configuration.rb} +1 -1
- data/lib/action/context.rb +28 -0
- data/lib/action/core/automatic_logging.rb +77 -0
- data/lib/action/core/context_facade.rb +1 -1
- data/lib/action/core/contract.rb +153 -214
- data/lib/action/core/contract_for_subfields.rb +84 -82
- data/lib/action/core/contract_validation.rb +51 -0
- data/lib/action/core/handle_exceptions.rb +102 -122
- data/lib/action/core/hoist_errors.rb +42 -40
- data/lib/action/core/hooks.rb +123 -0
- data/lib/action/core/logging.rb +22 -20
- data/lib/action/core/timing.rb +22 -0
- data/lib/action/core/use_strategy.rb +19 -17
- data/lib/action/core.rb +153 -0
- data/lib/action/enqueueable.rb +1 -1
- data/lib/action/{core/exceptions.rb → exceptions.rb} +1 -19
- data/lib/axn/factory.rb +0 -12
- data/lib/axn/version.rb +1 -1
- data/lib/axn.rb +10 -47
- metadata +10 -19
- data/lib/action/core/top_level_around_hook.rb +0 -108
@@ -1,163 +1,143 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
# TODO: maybe namespace those under core?
|
4
|
+
require "action/core/event_handlers"
|
4
5
|
|
5
6
|
module Action
|
6
|
-
module
|
7
|
-
|
8
|
-
base
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
if e.is_a?(Action::Failure)
|
28
|
-
@context.instance_variable_set("@error_from_user", e.message) if e.message.present?
|
29
|
-
|
30
|
-
self.class._failure_handlers.each do |handler|
|
31
|
-
handler.execute_if_matches(exception: e, action: self)
|
7
|
+
module Core
|
8
|
+
module HandleExceptions
|
9
|
+
def self.included(base)
|
10
|
+
base.class_eval do
|
11
|
+
class_attribute :_success_msg, :_error_msg
|
12
|
+
class_attribute :_custom_error_interceptors, default: []
|
13
|
+
class_attribute :_error_handlers, default: []
|
14
|
+
class_attribute :_exception_handlers, default: []
|
15
|
+
class_attribute :_failure_handlers, default: []
|
16
|
+
class_attribute :_success_handlers, default: []
|
17
|
+
|
18
|
+
include InstanceMethods
|
19
|
+
extend ClassMethods
|
20
|
+
|
21
|
+
def trigger_on_exception(exception)
|
22
|
+
interceptor = self.class._error_interceptor_for(exception:, action: self)
|
23
|
+
return if interceptor&.should_report_error == false
|
24
|
+
|
25
|
+
# Call any handlers registered on *this specific action* class
|
26
|
+
self.class._exception_handlers.each do |handler|
|
27
|
+
handler.execute_if_matches(exception:, action: self)
|
32
28
|
end
|
33
|
-
else
|
34
|
-
# on_exception handlers run for ONLY for unhandled exceptions. AND NOTE: may be skipped if the exception is rescued via `rescues`.
|
35
|
-
trigger_on_exception(e)
|
36
29
|
|
37
|
-
|
30
|
+
# Call any global handlers
|
31
|
+
Action.config.on_exception(exception,
|
32
|
+
action: self,
|
33
|
+
context: respond_to?(:context_for_logging) ? context_for_logging : @context.to_h)
|
34
|
+
rescue StandardError => e
|
35
|
+
# No action needed -- downstream #on_exception implementation should ideally log any internal failures, but
|
36
|
+
# we don't want exception *handling* failures to cascade and overwrite the original exception.
|
37
|
+
Axn::Util.piping_error("executing on_exception hooks", action: self, exception: e)
|
38
38
|
end
|
39
39
|
|
40
|
-
|
40
|
+
def trigger_on_success
|
41
|
+
# Call success handlers in child-first order (like after hooks)
|
42
|
+
self.class._success_handlers.each do |handler|
|
43
|
+
instance_exec(&handler)
|
44
|
+
rescue StandardError => e
|
45
|
+
# Log the error but continue with other handlers
|
46
|
+
Axn::Util.piping_error("executing on_success hook", action: self, exception: e)
|
47
|
+
end
|
48
|
+
end
|
41
49
|
end
|
50
|
+
end
|
42
51
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
# Call any handlers registered on *this specific action* class
|
48
|
-
self.class._exception_handlers.each do |handler|
|
49
|
-
handler.execute_if_matches(exception:, action: self)
|
50
|
-
end
|
52
|
+
module ClassMethods
|
53
|
+
def messages(success: nil, error: nil)
|
54
|
+
self._success_msg = success if success.present?
|
55
|
+
self._error_msg = error if error.present?
|
51
56
|
|
52
|
-
|
53
|
-
Action.config.on_exception(exception,
|
54
|
-
action: self,
|
55
|
-
context: respond_to?(:context_for_logging) ? context_for_logging : @context.to_h)
|
56
|
-
rescue StandardError => e
|
57
|
-
# No action needed -- downstream #on_exception implementation should ideally log any internal failures, but
|
58
|
-
# we don't want exception *handling* failures to cascade and overwrite the original exception.
|
59
|
-
Axn::Util.piping_error("executing on_exception hooks", action: self, exception: e)
|
57
|
+
true
|
60
58
|
end
|
61
59
|
|
62
|
-
|
63
|
-
|
64
|
-
result = call(context)
|
65
|
-
return result if result.ok?
|
66
|
-
|
67
|
-
raise result.exception || Action::Failure.new(result.error)
|
68
|
-
end
|
60
|
+
def error_from(matcher = nil, message = nil, **match_and_messages)
|
61
|
+
_register_error_interceptor(matcher, message, should_report_error: true, **match_and_messages)
|
69
62
|
end
|
70
|
-
end
|
71
|
-
end
|
72
63
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
self._error_msg = error if error.present?
|
64
|
+
def rescues(matcher = nil, message = nil, **match_and_messages)
|
65
|
+
_register_error_interceptor(matcher, message, should_report_error: false, **match_and_messages)
|
66
|
+
end
|
77
67
|
|
78
|
-
|
79
|
-
|
68
|
+
# ONLY raised exceptions (i.e. NOT fail!). Skipped if exception is rescued via .rescues.
|
69
|
+
def on_exception(matcher = -> { true }, &handler)
|
70
|
+
raise ArgumentError, "on_exception must be called with a block" unless block_given?
|
80
71
|
|
81
|
-
|
82
|
-
|
83
|
-
end
|
72
|
+
self._exception_handlers += [Action::EventHandlers::ConditionalHandler.new(matcher:, handler:)]
|
73
|
+
end
|
84
74
|
|
85
|
-
|
86
|
-
|
87
|
-
|
75
|
+
# ONLY raised on fail! (i.e. NOT unhandled exceptions).
|
76
|
+
def on_failure(matcher = -> { true }, &handler)
|
77
|
+
raise ArgumentError, "on_failure must be called with a block" unless block_given?
|
88
78
|
|
89
|
-
|
90
|
-
|
91
|
-
raise ArgumentError, "on_exception must be called with a block" unless block_given?
|
79
|
+
self._failure_handlers += [Action::EventHandlers::ConditionalHandler.new(matcher:, handler:)]
|
80
|
+
end
|
92
81
|
|
93
|
-
|
94
|
-
|
82
|
+
# Handles both fail! and unhandled exceptions... but is NOT affected by .rescues
|
83
|
+
def on_error(matcher = -> { true }, &handler)
|
84
|
+
raise ArgumentError, "on_error must be called with a block" unless block_given?
|
95
85
|
|
96
|
-
|
97
|
-
|
98
|
-
raise ArgumentError, "on_failure must be called with a block" unless block_given?
|
86
|
+
self._error_handlers += [Action::EventHandlers::ConditionalHandler.new(matcher:, handler:)]
|
87
|
+
end
|
99
88
|
|
100
|
-
|
101
|
-
|
89
|
+
# Executes when the action completes successfully (after all after hooks complete successfully)
|
90
|
+
# Runs in child-first order (child handlers before parent handlers)
|
91
|
+
def on_success(&handler)
|
92
|
+
raise ArgumentError, "on_success must be called with a block" unless block_given?
|
102
93
|
|
103
|
-
|
104
|
-
|
105
|
-
|
94
|
+
# Prepend like after hooks - child handlers run before parent handlers
|
95
|
+
self._success_handlers = [handler] + _success_handlers
|
96
|
+
end
|
106
97
|
|
107
|
-
|
108
|
-
end
|
98
|
+
def default_error = new.internal_context.default_error
|
109
99
|
|
110
|
-
|
111
|
-
def on_success(&block)
|
112
|
-
raise ArgumentError, "on_success must be called with a block" unless block_given?
|
100
|
+
# Private helpers
|
113
101
|
|
114
|
-
|
115
|
-
|
102
|
+
def _error_interceptor_for(exception:, action:)
|
103
|
+
Array(_custom_error_interceptors).detect do |int|
|
104
|
+
int.matches?(exception:, action:)
|
105
|
+
end
|
116
106
|
end
|
117
|
-
end
|
118
107
|
|
119
|
-
|
108
|
+
def _register_error_interceptor(matcher, message, should_report_error:, **match_and_messages)
|
109
|
+
method_name = should_report_error ? "error_from" : "rescues"
|
110
|
+
raise ArgumentError, "#{method_name} must be called with a key/value pair, or else keyword args" if [matcher, message].compact.size == 1
|
120
111
|
|
121
|
-
|
112
|
+
interceptors = { matcher => message }.compact.merge(match_and_messages).map do |(matcher, message)| # rubocop:disable Lint/ShadowingOuterLocalVariable
|
113
|
+
Action::EventHandlers::CustomErrorInterceptor.new(matcher:, message:, should_report_error:)
|
114
|
+
end
|
122
115
|
|
123
|
-
|
124
|
-
Array(_custom_error_interceptors).detect do |int|
|
125
|
-
int.matches?(exception:, action:)
|
116
|
+
self._custom_error_interceptors += interceptors
|
126
117
|
end
|
127
118
|
end
|
128
119
|
|
129
|
-
|
130
|
-
|
131
|
-
raise ArgumentError, "#{method_name} must be called with a key/value pair, or else keyword args" if [matcher, message].compact.size == 1
|
132
|
-
|
133
|
-
interceptors = { matcher => message }.compact.merge(match_and_messages).map do |(matcher, message)| # rubocop:disable Lint/ShadowingOuterLocalVariable
|
134
|
-
Action::EventHandlers::CustomErrorInterceptor.new(matcher:, message:, should_report_error:)
|
135
|
-
end
|
120
|
+
module InstanceMethods
|
121
|
+
private
|
136
122
|
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
module InstanceMethods
|
142
|
-
private
|
123
|
+
def fail!(message = nil)
|
124
|
+
@context.instance_variable_set("@failure", true)
|
125
|
+
@context.error_from_user = message if message.present?
|
143
126
|
|
144
|
-
|
145
|
-
|
146
|
-
@context.error_from_user = message if message.present?
|
127
|
+
raise Action::Failure, message
|
128
|
+
end
|
147
129
|
|
148
|
-
|
149
|
-
|
130
|
+
def try
|
131
|
+
yield
|
132
|
+
rescue Action::Failure => e
|
133
|
+
# NOTE: re-raising so we can still fail! from inside the block
|
134
|
+
raise e
|
135
|
+
rescue StandardError => e
|
136
|
+
trigger_on_exception(e)
|
137
|
+
end
|
150
138
|
|
151
|
-
|
152
|
-
yield
|
153
|
-
rescue Action::Failure => e
|
154
|
-
# NOTE: re-raising so we can still fail! from inside the block
|
155
|
-
raise e
|
156
|
-
rescue StandardError => e
|
157
|
-
trigger_on_exception(e)
|
139
|
+
delegate :default_error, to: :internal_context
|
158
140
|
end
|
159
|
-
|
160
|
-
delegate :default_error, to: :internal_context
|
161
141
|
end
|
162
142
|
end
|
163
143
|
end
|
@@ -1,55 +1,57 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Action
|
4
|
-
module
|
5
|
-
|
6
|
-
base
|
7
|
-
|
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
|
-
|
12
|
-
|
12
|
+
module InstanceMethods
|
13
|
+
private
|
13
14
|
|
14
|
-
|
15
|
-
|
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
|
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.
|
37
|
-
|
38
|
-
|
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
|
-
|
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
|
-
|
44
|
-
|
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
|
data/lib/action/core/logging.rb
CHANGED
@@ -3,33 +3,35 @@
|
|
3
3
|
require "active_support/core_ext/module/delegation"
|
4
4
|
|
5
5
|
module Action
|
6
|
-
module
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
base
|
11
|
-
|
12
|
-
|
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
|
-
|
17
|
-
|
17
|
+
module ClassMethods
|
18
|
+
def default_log_level = Action.config.default_log_level
|
18
19
|
|
19
|
-
|
20
|
-
|
21
|
-
|
20
|
+
def log(message, level: default_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
|
-
|
24
|
-
|
24
|
+
Action.config.logger.send(level, msg)
|
25
|
+
end
|
25
26
|
|
26
|
-
|
27
|
-
|
28
|
-
|
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
|
-
|
33
|
+
def _log_prefix = "[#{name.presence || "Anonymous Class"}]"
|
34
|
+
end
|
33
35
|
end
|
34
36
|
end
|
35
37
|
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Action
|
4
|
+
module Core
|
5
|
+
module Timing
|
6
|
+
# Get the current monotonic time
|
7
|
+
def self.now
|
8
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
9
|
+
end
|
10
|
+
|
11
|
+
# Calculate elapsed time in milliseconds
|
12
|
+
def self.elapsed_ms(start_time)
|
13
|
+
((now - start_time) * 1000).round(3)
|
14
|
+
end
|
15
|
+
|
16
|
+
# Calculate elapsed time in seconds
|
17
|
+
def self.elapsed_seconds(start_time)
|
18
|
+
(now - start_time).round(6)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -1,27 +1,29 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Action
|
4
|
-
module
|
5
|
-
|
4
|
+
module Core
|
5
|
+
module UseStrategy
|
6
|
+
extend ActiveSupport::Concern
|
6
7
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
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
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
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
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
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
|
-
|
25
|
+
include strategy
|
26
|
+
end
|
25
27
|
end
|
26
28
|
end
|
27
29
|
end
|