cmdx 1.11.0 → 1.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.cursor/rules/cursor-instructions.mdc +8 -0
- data/.yard-lint.yml +174 -0
- data/CHANGELOG.md +24 -0
- data/docs/attributes/definitions.md +5 -1
- data/docs/attributes/validations.md +47 -6
- data/docs/basics/execution.md +56 -0
- data/docs/basics/setup.md +26 -3
- data/docs/deprecation.md +0 -2
- data/docs/getting_started.md +11 -0
- data/docs/logging.md +6 -10
- data/docs/outcomes/result.md +12 -8
- data/docs/outcomes/states.md +4 -4
- data/docs/outcomes/statuses.md +6 -6
- data/docs/tips_and_tricks.md +4 -0
- data/examples/active_record_database_transaction.md +27 -0
- data/examples/flipper_feature_flags.md +50 -0
- data/examples/redis_idempotency.md +71 -0
- data/examples/sentry_error_tracking.md +46 -0
- data/lib/cmdx/attribute.rb +6 -0
- data/lib/cmdx/callback_registry.rb +2 -1
- data/lib/cmdx/chain.rb +20 -7
- data/lib/cmdx/coercion_registry.rb +2 -1
- data/lib/cmdx/coercions/boolean.rb +3 -3
- data/lib/cmdx/coercions/complex.rb +1 -0
- data/lib/cmdx/coercions/string.rb +1 -0
- data/lib/cmdx/coercions/symbol.rb +1 -0
- data/lib/cmdx/configuration.rb +1 -3
- data/lib/cmdx/context.rb +5 -2
- data/lib/cmdx/executor.rb +29 -24
- data/lib/cmdx/middleware_registry.rb +1 -0
- data/lib/cmdx/result.rb +32 -89
- data/lib/cmdx/task.rb +10 -11
- data/lib/cmdx/utils/call.rb +3 -1
- data/lib/cmdx/utils/condition.rb +0 -3
- data/lib/cmdx/utils/format.rb +1 -1
- data/lib/cmdx/validators/exclusion.rb +2 -0
- data/lib/cmdx/validators/inclusion.rb +2 -0
- data/lib/cmdx/validators/length.rb +6 -0
- data/lib/cmdx/validators/numeric.rb +6 -0
- data/lib/cmdx/version.rb +1 -1
- data/lib/generators/cmdx/locale_generator.rb +6 -5
- data/mkdocs.yml +5 -1
- metadata +20 -1
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Flipper Feature Flags
|
|
2
|
+
|
|
3
|
+
Control task execution based on Flipper feature flags.
|
|
4
|
+
|
|
5
|
+
<https://github.com/flippercloud/flipper>
|
|
6
|
+
|
|
7
|
+
### Setup
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
# lib/cmdx_flipper_middleware.rb
|
|
11
|
+
class CmdxFlipperMiddleware
|
|
12
|
+
def self.call(task, **options, &)
|
|
13
|
+
feature_name = options.fetch(:feature)
|
|
14
|
+
actor = options.fetch(:actor, -> { task.context[:user] })
|
|
15
|
+
|
|
16
|
+
# Resolve actor if it's a proc
|
|
17
|
+
actor = actor.call if actor.respond_to?(:call)
|
|
18
|
+
|
|
19
|
+
if Flipper.enabled?(feature_name, actor)
|
|
20
|
+
yield
|
|
21
|
+
else
|
|
22
|
+
# Option 1: Skip the task
|
|
23
|
+
task.skip!("Feature #{feature_name} is disabled")
|
|
24
|
+
|
|
25
|
+
# Option 2: Fail the task
|
|
26
|
+
# task.fail!("Feature #{feature_name} is disabled")
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Usage
|
|
33
|
+
|
|
34
|
+
```ruby
|
|
35
|
+
class NewFeatureTask < CMDx::Task
|
|
36
|
+
# Execute only if :new_feature is enabled for the user in context
|
|
37
|
+
register :middleware, CmdxFlipperMiddleware,
|
|
38
|
+
feature: :new_feature
|
|
39
|
+
|
|
40
|
+
# Customize the actor resolution
|
|
41
|
+
register :middleware, CmdxFlipperMiddleware,
|
|
42
|
+
feature: :beta_access,
|
|
43
|
+
actor: -> { task.context[:company] }
|
|
44
|
+
|
|
45
|
+
def work
|
|
46
|
+
# ...
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
```
|
|
50
|
+
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Redis Idempotency
|
|
2
|
+
|
|
3
|
+
Ensure tasks are executed exactly once using Redis to store execution state. This is critical for non-idempotent operations like charging a credit card or sending an email.
|
|
4
|
+
|
|
5
|
+
### Setup
|
|
6
|
+
|
|
7
|
+
```ruby
|
|
8
|
+
# lib/cmdx_redis_idempotency_middleware.rb
|
|
9
|
+
class CmdxRedisIdempotencyMiddleware
|
|
10
|
+
def self.call(task, **options, &block)
|
|
11
|
+
key = generate_key(task, options[:key])
|
|
12
|
+
ttl = options[:ttl] || 5.minutes.to_i
|
|
13
|
+
|
|
14
|
+
# Attempt to lock the key
|
|
15
|
+
if Redis.current.set(key, "processing", nx: true, ex: ttl)
|
|
16
|
+
begin
|
|
17
|
+
block.call.tap |result|
|
|
18
|
+
Redis.current.set(key, result.status, xx: true, ex: ttl)
|
|
19
|
+
end
|
|
20
|
+
rescue => e
|
|
21
|
+
Redis.current.del(key)
|
|
22
|
+
raise(e)
|
|
23
|
+
end
|
|
24
|
+
else
|
|
25
|
+
# Key exists, handle duplicate
|
|
26
|
+
status = Redis.current.get(key)
|
|
27
|
+
|
|
28
|
+
if status == "processing"
|
|
29
|
+
task.result.tap { |r| r.skip!("Duplicate request: currently processing", halt: true) }
|
|
30
|
+
else
|
|
31
|
+
task.result.tap { |r| r.skip!("Duplicate request: already processed (#{status})", halt: true) }
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def self.generate_key(task, key_gen)
|
|
37
|
+
id = if key_gen.respond_to?(:call)
|
|
38
|
+
key_gen.call(task)
|
|
39
|
+
elsif key_gen.is_a?(Symbol)
|
|
40
|
+
task.send(key_gen)
|
|
41
|
+
else
|
|
42
|
+
task.context[:idempotency_key]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
"cmdx:idempotency:#{task.class.name}:#{id}"
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Usage
|
|
51
|
+
|
|
52
|
+
```ruby
|
|
53
|
+
class ChargeCustomer < CMDx::Task
|
|
54
|
+
# Use context[:payment_id] as the unique key
|
|
55
|
+
register :middleware, CmdxIdempotencyMiddleware,
|
|
56
|
+
key: ->(t) { t.context[:payment_id] }
|
|
57
|
+
|
|
58
|
+
def work
|
|
59
|
+
# Charge logic...
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# First run: Executes
|
|
64
|
+
ChargeCustomer.call(payment_id: "123")
|
|
65
|
+
# => Success
|
|
66
|
+
|
|
67
|
+
# Second run: Skips
|
|
68
|
+
ChargeCustomer.call(payment_id: "123")
|
|
69
|
+
# => Skipped (reason: "Duplicate request: already processed (success)")
|
|
70
|
+
```
|
|
71
|
+
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Sentry Error Tracking
|
|
2
|
+
|
|
3
|
+
Report unhandled exceptions and unexpected task failures to Sentry with detailed context.
|
|
4
|
+
|
|
5
|
+
<https://github.com/getsentry/sentry-ruby>
|
|
6
|
+
|
|
7
|
+
### Setup
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
# lib/cmdx_sentry_middleware.rb
|
|
11
|
+
class CmdxSentryMiddleware
|
|
12
|
+
def self.call(task, **options, &)
|
|
13
|
+
Sentry.with_scope do |scope|
|
|
14
|
+
scope.set_tags(task: task.class.name)
|
|
15
|
+
scope.set_context(:user, Current.user.sentry_attributes)
|
|
16
|
+
|
|
17
|
+
yield.tap do |result|
|
|
18
|
+
# Optional: Report logical failures if needed
|
|
19
|
+
if Array(options[:report_on]).include?(result.status)
|
|
20
|
+
Sentry.capture_message("Task #{result.status}: #{result.reason}", level: :warning)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
rescue => e
|
|
25
|
+
Sentry.capture_exception(e)
|
|
26
|
+
raise(e) # Re-raise to let the task handle the error or bubble up
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Usage
|
|
32
|
+
|
|
33
|
+
```ruby
|
|
34
|
+
class ProcessPayment < CMDx::Task
|
|
35
|
+
# Report exceptions only
|
|
36
|
+
register :middleware, CmdxSentryMiddleware
|
|
37
|
+
|
|
38
|
+
# Report exceptions AND logical failures (result.failure?)
|
|
39
|
+
register :middleware, CmdxSentryMiddleware, report_on: %w[failed skipped]
|
|
40
|
+
|
|
41
|
+
def work
|
|
42
|
+
# ...
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
```
|
|
46
|
+
|
data/lib/cmdx/attribute.rb
CHANGED
|
@@ -112,6 +112,7 @@ module CMDx
|
|
|
112
112
|
#
|
|
113
113
|
# @param names [Array<Symbol, String>] The names of the attributes to create
|
|
114
114
|
# @param options [Hash] Configuration options for the attributes
|
|
115
|
+
# @option options [Object] :* Any attribute configuration option
|
|
115
116
|
#
|
|
116
117
|
# @yield [self] Block to configure nested attributes
|
|
117
118
|
#
|
|
@@ -137,6 +138,7 @@ module CMDx
|
|
|
137
138
|
#
|
|
138
139
|
# @param names [Array<Symbol, String>] The names of the attributes to create
|
|
139
140
|
# @param options [Hash] Configuration options for the attributes
|
|
141
|
+
# @option options [Object] :* Any attribute configuration option
|
|
140
142
|
#
|
|
141
143
|
# @yield [self] Block to configure nested attributes
|
|
142
144
|
#
|
|
@@ -154,6 +156,7 @@ module CMDx
|
|
|
154
156
|
#
|
|
155
157
|
# @param names [Array<Symbol, String>] The names of the attributes to create
|
|
156
158
|
# @param options [Hash] Configuration options for the attributes
|
|
159
|
+
# @option options [Object] :* Any attribute configuration option
|
|
157
160
|
#
|
|
158
161
|
# @yield [self] Block to configure nested attributes
|
|
159
162
|
#
|
|
@@ -238,6 +241,7 @@ module CMDx
|
|
|
238
241
|
#
|
|
239
242
|
# @param names [Array<Symbol, String>] The names of the child attributes
|
|
240
243
|
# @param options [Hash] Configuration options for the child attributes
|
|
244
|
+
# @option options [Object] :* Any attribute configuration option
|
|
241
245
|
#
|
|
242
246
|
# @yield [self] Block to configure the child attributes
|
|
243
247
|
#
|
|
@@ -257,6 +261,7 @@ module CMDx
|
|
|
257
261
|
#
|
|
258
262
|
# @param names [Array<Symbol, String>] The names of the optional child attributes
|
|
259
263
|
# @param options [Hash] Configuration options for the child attributes
|
|
264
|
+
# @option options [Object] :* Any attribute configuration option
|
|
260
265
|
#
|
|
261
266
|
# @yield [self] Block to configure the child attributes
|
|
262
267
|
#
|
|
@@ -274,6 +279,7 @@ module CMDx
|
|
|
274
279
|
#
|
|
275
280
|
# @param names [Array<Symbol, String>] The names of the required child attributes
|
|
276
281
|
# @param options [Hash] Configuration options for the child attributes
|
|
282
|
+
# @option options [Object] :* Any attribute configuration option
|
|
277
283
|
#
|
|
278
284
|
# @yield [self] Block to configure the child attributes
|
|
279
285
|
#
|
|
@@ -53,9 +53,9 @@ module CMDx
|
|
|
53
53
|
# @param type [Symbol] The callback type from TYPES
|
|
54
54
|
# @param callables [Array<#call>] Callable objects to register
|
|
55
55
|
# @param options [Hash] Options to pass to the callback
|
|
56
|
+
# @param block [Proc] Optional block to register as a callable
|
|
56
57
|
# @option options [Hash] :if Condition hash for conditional execution
|
|
57
58
|
# @option options [Hash] :unless Inverse condition hash for conditional execution
|
|
58
|
-
# @param block [Proc] Optional block to register as a callable
|
|
59
59
|
#
|
|
60
60
|
# @return [CallbackRegistry] self for method chaining
|
|
61
61
|
#
|
|
@@ -83,6 +83,7 @@ module CMDx
|
|
|
83
83
|
# @param callables [Array<#call>] Callable objects to remove
|
|
84
84
|
# @param options [Hash] Options that were used during registration
|
|
85
85
|
# @param block [Proc] Optional block to remove
|
|
86
|
+
# @option options [Object] :* Any option key-value pairs
|
|
86
87
|
#
|
|
87
88
|
# @return [CallbackRegistry] self for method chaining
|
|
88
89
|
#
|
data/lib/cmdx/chain.rb
CHANGED
|
@@ -39,9 +39,10 @@ module CMDx
|
|
|
39
39
|
# @return [Chain] A new chain instance
|
|
40
40
|
#
|
|
41
41
|
# @rbs () -> void
|
|
42
|
-
def initialize
|
|
42
|
+
def initialize(dry_run: false)
|
|
43
43
|
@id = Identifier.generate
|
|
44
44
|
@results = []
|
|
45
|
+
@dry_run = !!dry_run
|
|
45
46
|
end
|
|
46
47
|
|
|
47
48
|
class << self
|
|
@@ -102,24 +103,35 @@ module CMDx
|
|
|
102
103
|
# puts "Chain size: #{chain.size}"
|
|
103
104
|
#
|
|
104
105
|
# @rbs (Result result) -> Chain
|
|
105
|
-
def build(result)
|
|
106
|
+
def build(result, dry_run: false)
|
|
106
107
|
raise TypeError, "must be a CMDx::Result" unless result.is_a?(Result)
|
|
107
108
|
|
|
108
|
-
self.current ||= new
|
|
109
|
+
self.current ||= new(dry_run:)
|
|
109
110
|
current.results << result
|
|
110
111
|
current
|
|
111
112
|
end
|
|
112
113
|
|
|
113
114
|
end
|
|
114
115
|
|
|
115
|
-
#
|
|
116
|
+
# Returns whether the chain is running in dry-run mode.
|
|
116
117
|
#
|
|
117
|
-
# @return [
|
|
118
|
+
# @return [Boolean] Whether the chain is running in dry-run mode
|
|
118
119
|
#
|
|
119
|
-
# @
|
|
120
|
+
# @example
|
|
121
|
+
# chain.dry_run? # => true
|
|
120
122
|
#
|
|
123
|
+
# @rbs () -> bool
|
|
124
|
+
def dry_run?
|
|
125
|
+
!!@dry_run
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Converts the chain to a hash representation.
|
|
129
|
+
#
|
|
130
|
+
# @option return [String] :id The chain identifier
|
|
121
131
|
# @option return [Array<Hash>] :results Array of result hashes
|
|
122
132
|
#
|
|
133
|
+
# @return [Hash] Hash containing chain id and serialized results
|
|
134
|
+
#
|
|
123
135
|
# @example
|
|
124
136
|
# chain_hash = chain.to_h
|
|
125
137
|
# puts chain_hash[:id]
|
|
@@ -128,7 +140,8 @@ module CMDx
|
|
|
128
140
|
# @rbs () -> Hash[Symbol, untyped]
|
|
129
141
|
def to_h
|
|
130
142
|
{
|
|
131
|
-
id
|
|
143
|
+
id:,
|
|
144
|
+
dry_run: dry_run?,
|
|
132
145
|
results: results.map(&:to_h)
|
|
133
146
|
}
|
|
134
147
|
end
|
|
@@ -20,7 +20,7 @@ module CMDx
|
|
|
20
20
|
|
|
21
21
|
# Initialize a new coercion registry.
|
|
22
22
|
#
|
|
23
|
-
# @param registry [Hash
|
|
23
|
+
# @param registry [Hash{Symbol => Class}, nil] optional initial registry hash
|
|
24
24
|
#
|
|
25
25
|
# @example
|
|
26
26
|
# registry = CoercionRegistry.new
|
|
@@ -95,6 +95,7 @@ module CMDx
|
|
|
95
95
|
# @param task [Object] the task context for the coercion
|
|
96
96
|
# @param value [Object] the value to coerce
|
|
97
97
|
# @param options [Hash] additional options for the coercion
|
|
98
|
+
# @option options [Object] :* Any coercion option key-value pairs
|
|
98
99
|
#
|
|
99
100
|
# @return [Object] the coerced value
|
|
100
101
|
#
|
|
@@ -11,10 +11,10 @@ module CMDx
|
|
|
11
11
|
extend self
|
|
12
12
|
|
|
13
13
|
# @rbs FALSEY: Regexp
|
|
14
|
-
FALSEY =
|
|
14
|
+
FALSEY = /\A(false|f|no|n|0)\z/i
|
|
15
15
|
|
|
16
16
|
# @rbs TRUTHY: Regexp
|
|
17
|
-
TRUTHY =
|
|
17
|
+
TRUTHY = /\A(true|t|yes|y|1)\z/i
|
|
18
18
|
|
|
19
19
|
# Converts a value to a Boolean
|
|
20
20
|
#
|
|
@@ -40,7 +40,7 @@ module CMDx
|
|
|
40
40
|
#
|
|
41
41
|
# @rbs (untyped value, ?Hash[Symbol, untyped] options) -> bool
|
|
42
42
|
def call(value, options = {})
|
|
43
|
-
case value.to_s
|
|
43
|
+
case value.to_s
|
|
44
44
|
when FALSEY then false
|
|
45
45
|
when TRUTHY then true
|
|
46
46
|
else
|
|
@@ -14,6 +14,7 @@ module CMDx
|
|
|
14
14
|
#
|
|
15
15
|
# @param value [Object] The value to convert to Complex
|
|
16
16
|
# @param options [Hash] Optional configuration parameters (currently unused)
|
|
17
|
+
# @option options [Object] :* Any configuration option (unused)
|
|
17
18
|
#
|
|
18
19
|
# @return [Complex] The converted Complex number value
|
|
19
20
|
#
|
|
@@ -15,6 +15,7 @@ module CMDx
|
|
|
15
15
|
#
|
|
16
16
|
# @param value [Object] The value to coerce to a string
|
|
17
17
|
# @param options [Hash] Optional configuration parameters (unused in this coercion)
|
|
18
|
+
# @option options [Object] :* Any configuration option (unused)
|
|
18
19
|
#
|
|
19
20
|
# @return [String] The coerced string value
|
|
20
21
|
#
|
|
@@ -15,6 +15,7 @@ module CMDx
|
|
|
15
15
|
#
|
|
16
16
|
# @param value [Object] The value to coerce to a symbol
|
|
17
17
|
# @param options [Hash] Optional configuration parameters (unused in this coercion)
|
|
18
|
+
# @option options [Object] :* Any configuration option (unused)
|
|
18
19
|
#
|
|
19
20
|
# @return [Symbol] The coerced symbol value
|
|
20
21
|
#
|
data/lib/cmdx/configuration.rb
CHANGED
|
@@ -160,7 +160,7 @@ module CMDx
|
|
|
160
160
|
|
|
161
161
|
# Converts the configuration to a hash representation.
|
|
162
162
|
#
|
|
163
|
-
# @return [Hash
|
|
163
|
+
# @return [Hash{Symbol => Object}] hash containing all configuration values
|
|
164
164
|
#
|
|
165
165
|
# @example
|
|
166
166
|
# config = Configuration.new
|
|
@@ -205,8 +205,6 @@ module CMDx
|
|
|
205
205
|
|
|
206
206
|
# Configures CMDx using a block that receives the configuration instance.
|
|
207
207
|
#
|
|
208
|
-
# @param block [Proc] the configuration block
|
|
209
|
-
#
|
|
210
208
|
# @yield [Configuration] the configuration instance to configure
|
|
211
209
|
#
|
|
212
210
|
# @return [Configuration] the configured configuration instance
|
data/lib/cmdx/context.rb
CHANGED
|
@@ -109,7 +109,6 @@ module CMDx
|
|
|
109
109
|
# Fetches a value from the context by key, with optional default value.
|
|
110
110
|
#
|
|
111
111
|
# @param key [String, Symbol] the key to fetch
|
|
112
|
-
# @param default [Object] the default value if key is not found
|
|
113
112
|
#
|
|
114
113
|
# @yield [key] a block to compute the default value
|
|
115
114
|
#
|
|
@@ -165,6 +164,7 @@ module CMDx
|
|
|
165
164
|
args.to_h.each { |key, value| self[key.to_sym] = value }
|
|
166
165
|
self
|
|
167
166
|
end
|
|
167
|
+
alias merge merge!
|
|
168
168
|
|
|
169
169
|
# Deletes a key-value pair from the context.
|
|
170
170
|
#
|
|
@@ -183,6 +183,7 @@ module CMDx
|
|
|
183
183
|
def delete!(key, &)
|
|
184
184
|
table.delete(key.to_sym, &)
|
|
185
185
|
end
|
|
186
|
+
alias delete delete!
|
|
186
187
|
|
|
187
188
|
# Compares this context with another object for equality.
|
|
188
189
|
#
|
|
@@ -255,6 +256,7 @@ module CMDx
|
|
|
255
256
|
# @param method_name [Symbol] the method name that was called
|
|
256
257
|
# @param args [Array<Object>] arguments passed to the method
|
|
257
258
|
# @param _kwargs [Hash] keyword arguments (unused)
|
|
259
|
+
# @option _kwargs [Object] :* Any keyword arguments (unused)
|
|
258
260
|
#
|
|
259
261
|
# @yield [Object] optional block
|
|
260
262
|
#
|
|
@@ -263,7 +265,8 @@ module CMDx
|
|
|
263
265
|
# @rbs (Symbol method_name, *untyped args, **untyped _kwargs) ?{ () -> untyped } -> untyped
|
|
264
266
|
def method_missing(method_name, *args, **_kwargs, &)
|
|
265
267
|
fetch(method_name) do
|
|
266
|
-
|
|
268
|
+
str_name = method_name.to_s
|
|
269
|
+
store(str_name.chop, args.first) if str_name.end_with?("=")
|
|
267
270
|
end
|
|
268
271
|
end
|
|
269
272
|
|
data/lib/cmdx/executor.rb
CHANGED
|
@@ -8,6 +8,8 @@ module CMDx
|
|
|
8
8
|
# and proper error handling for different types of failures.
|
|
9
9
|
class Executor
|
|
10
10
|
|
|
11
|
+
extend Forwardable
|
|
12
|
+
|
|
11
13
|
# Returns the task being executed.
|
|
12
14
|
#
|
|
13
15
|
# @return [Task] The task instance
|
|
@@ -18,6 +20,8 @@ module CMDx
|
|
|
18
20
|
# @rbs @task: Task
|
|
19
21
|
attr_reader :task
|
|
20
22
|
|
|
23
|
+
def_delegators :task, :result
|
|
24
|
+
|
|
21
25
|
# @param task [CMDx::Task] The task to execute
|
|
22
26
|
#
|
|
23
27
|
# @return [CMDx::Executor] A new executor instance
|
|
@@ -65,13 +69,13 @@ module CMDx
|
|
|
65
69
|
rescue UndefinedMethodError => e
|
|
66
70
|
raise(e) # No need to clear the Chain since exception is not being re-raised
|
|
67
71
|
rescue Fault => e
|
|
68
|
-
|
|
72
|
+
result.throw!(e.result, halt: false, cause: e)
|
|
69
73
|
rescue StandardError => e
|
|
70
74
|
retry if retry_execution?(e)
|
|
71
|
-
|
|
75
|
+
result.fail!("[#{e.class}] #{e.message}", halt: false, cause: e)
|
|
72
76
|
task.class.settings[:exception_handler]&.call(task, e)
|
|
73
77
|
ensure
|
|
74
|
-
|
|
78
|
+
result.executed!
|
|
75
79
|
post_execution!
|
|
76
80
|
end
|
|
77
81
|
|
|
@@ -96,14 +100,14 @@ module CMDx
|
|
|
96
100
|
rescue UndefinedMethodError => e
|
|
97
101
|
raise_exception(e)
|
|
98
102
|
rescue Fault => e
|
|
99
|
-
|
|
103
|
+
result.throw!(e.result, halt: false, cause: e)
|
|
100
104
|
halt_execution?(e) ? raise_exception(e) : post_execution!
|
|
101
105
|
rescue StandardError => e
|
|
102
106
|
retry if retry_execution?(e)
|
|
103
|
-
|
|
107
|
+
result.fail!("[#{e.class}] #{e.message}", halt: false, cause: e)
|
|
104
108
|
raise_exception(e)
|
|
105
109
|
else
|
|
106
|
-
|
|
110
|
+
result.executed!
|
|
107
111
|
post_execution!
|
|
108
112
|
end
|
|
109
113
|
|
|
@@ -137,14 +141,14 @@ module CMDx
|
|
|
137
141
|
available_retries = (task.class.settings[:retries] || 0).to_i
|
|
138
142
|
return false unless available_retries.positive?
|
|
139
143
|
|
|
140
|
-
current_retries = (
|
|
144
|
+
current_retries = (result.metadata[:retries] ||= 0).to_i
|
|
141
145
|
remaining_retries = available_retries - current_retries
|
|
142
146
|
return false unless remaining_retries.positive?
|
|
143
147
|
|
|
144
148
|
exceptions = Array(task.class.settings[:retry_on] || StandardError)
|
|
145
149
|
return false unless exceptions.any? { |e| exception.class <= e }
|
|
146
150
|
|
|
147
|
-
|
|
151
|
+
result.metadata[:retries] += 1
|
|
148
152
|
|
|
149
153
|
task.logger.warn do
|
|
150
154
|
reason = "[#{exception.class}] #{exception.message}"
|
|
@@ -208,7 +212,7 @@ module CMDx
|
|
|
208
212
|
task.class.settings[:attributes].define_and_verify(task)
|
|
209
213
|
return if task.errors.empty?
|
|
210
214
|
|
|
211
|
-
|
|
215
|
+
result.fail!(
|
|
212
216
|
Locale.t("cmdx.faults.invalid"),
|
|
213
217
|
errors: {
|
|
214
218
|
full_message: task.errors.to_s,
|
|
@@ -223,7 +227,7 @@ module CMDx
|
|
|
223
227
|
def execution!
|
|
224
228
|
invoke_callbacks(:before_execution)
|
|
225
229
|
|
|
226
|
-
|
|
230
|
+
result.executing!
|
|
227
231
|
task.work
|
|
228
232
|
end
|
|
229
233
|
|
|
@@ -231,12 +235,12 @@ module CMDx
|
|
|
231
235
|
#
|
|
232
236
|
# @rbs () -> void
|
|
233
237
|
def post_execution!
|
|
234
|
-
invoke_callbacks(:"on_#{
|
|
235
|
-
invoke_callbacks(:on_executed) if
|
|
238
|
+
invoke_callbacks(:"on_#{result.state}")
|
|
239
|
+
invoke_callbacks(:on_executed) if result.executed?
|
|
236
240
|
|
|
237
|
-
invoke_callbacks(:"on_#{
|
|
238
|
-
invoke_callbacks(:on_good) if
|
|
239
|
-
invoke_callbacks(:on_bad) if
|
|
241
|
+
invoke_callbacks(:"on_#{result.status}")
|
|
242
|
+
invoke_callbacks(:on_good) if result.good?
|
|
243
|
+
invoke_callbacks(:on_bad) if result.bad?
|
|
240
244
|
end
|
|
241
245
|
|
|
242
246
|
# Finalizes execution by freezing the task, logging results, and rolling back work.
|
|
@@ -246,26 +250,25 @@ module CMDx
|
|
|
246
250
|
log_execution!
|
|
247
251
|
log_backtrace! if task.class.settings[:backtrace]
|
|
248
252
|
|
|
253
|
+
rollback_execution!
|
|
249
254
|
freeze_execution!
|
|
250
255
|
clear_chain!
|
|
251
|
-
|
|
252
|
-
rollback_execution!
|
|
253
256
|
end
|
|
254
257
|
|
|
255
258
|
# Logs the execution result at the configured log level.
|
|
256
259
|
#
|
|
257
260
|
# @rbs () -> void
|
|
258
261
|
def log_execution!
|
|
259
|
-
task.logger.info {
|
|
262
|
+
task.logger.info { result.to_h }
|
|
260
263
|
end
|
|
261
264
|
|
|
262
265
|
# Logs the backtrace of the exception if the task failed.
|
|
263
266
|
#
|
|
264
267
|
# @rbs () -> void
|
|
265
268
|
def log_backtrace!
|
|
266
|
-
return unless
|
|
269
|
+
return unless result.failed?
|
|
267
270
|
|
|
268
|
-
exception =
|
|
271
|
+
exception = result.caused_failure.cause
|
|
269
272
|
return if exception.is_a?(Fault)
|
|
270
273
|
|
|
271
274
|
task.logger.error do
|
|
@@ -287,11 +290,11 @@ module CMDx
|
|
|
287
290
|
return if Coercions::Boolean.call(skip_freezing)
|
|
288
291
|
|
|
289
292
|
task.freeze
|
|
290
|
-
|
|
293
|
+
result.freeze
|
|
291
294
|
|
|
292
295
|
# Freezing the context and chain can only be done
|
|
293
296
|
# once the outer-most task has completed.
|
|
294
|
-
return unless
|
|
297
|
+
return unless result.index.zero?
|
|
295
298
|
|
|
296
299
|
task.context.freeze
|
|
297
300
|
task.chain.freeze
|
|
@@ -301,7 +304,7 @@ module CMDx
|
|
|
301
304
|
#
|
|
302
305
|
# @rbs () -> void
|
|
303
306
|
def clear_chain!
|
|
304
|
-
return unless
|
|
307
|
+
return unless result.index.zero?
|
|
305
308
|
|
|
306
309
|
Chain.clear
|
|
307
310
|
end
|
|
@@ -310,12 +313,14 @@ module CMDx
|
|
|
310
313
|
#
|
|
311
314
|
# @rbs () -> void
|
|
312
315
|
def rollback_execution!
|
|
316
|
+
return if result.rolled_back?
|
|
313
317
|
return unless task.respond_to?(:rollback)
|
|
314
318
|
|
|
315
319
|
statuses = task.class.settings[:rollback_on]
|
|
316
320
|
statuses = Array(statuses).map(&:to_s).uniq
|
|
317
|
-
return unless statuses.include?(
|
|
321
|
+
return unless statuses.include?(result.status)
|
|
318
322
|
|
|
323
|
+
result.rolled_back!
|
|
319
324
|
task.rollback
|
|
320
325
|
end
|
|
321
326
|
|
|
@@ -109,6 +109,7 @@ module CMDx
|
|
|
109
109
|
#
|
|
110
110
|
# @param index [Integer] Current middleware index in the chain
|
|
111
111
|
# @param task [Object] The task object being processed
|
|
112
|
+
# @param block [Proc] Block to execute after middleware processing
|
|
112
113
|
#
|
|
113
114
|
# @yield [task] Block to execute after middleware processing
|
|
114
115
|
# @yieldparam task [Object] The processed task object
|