cmdx 1.0.1 → 1.1.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.
- checksums.yaml +4 -4
- data/.cursor/prompts/docs.md +9 -0
- data/.cursor/prompts/rspec.md +21 -0
- data/.cursor/prompts/yardoc.md +13 -0
- data/.rubocop.yml +2 -0
- data/CHANGELOG.md +29 -3
- data/README.md +2 -1
- data/docs/ai_prompts.md +269 -195
- data/docs/basics/call.md +126 -60
- data/docs/basics/chain.md +190 -160
- data/docs/basics/context.md +242 -154
- data/docs/basics/setup.md +302 -32
- data/docs/callbacks.md +382 -119
- data/docs/configuration.md +211 -49
- data/docs/deprecation.md +245 -0
- data/docs/getting_started.md +161 -39
- data/docs/internationalization.md +590 -70
- data/docs/interruptions/exceptions.md +135 -118
- data/docs/interruptions/faults.md +152 -127
- data/docs/interruptions/halt.md +134 -80
- data/docs/logging.md +183 -120
- data/docs/middlewares.md +165 -392
- data/docs/outcomes/result.md +140 -112
- data/docs/outcomes/states.md +134 -99
- data/docs/outcomes/statuses.md +204 -146
- data/docs/parameters/coercions.md +251 -289
- data/docs/parameters/defaults.md +224 -169
- data/docs/parameters/definitions.md +289 -141
- data/docs/parameters/namespacing.md +250 -161
- data/docs/parameters/validations.md +247 -159
- data/docs/testing.md +196 -203
- data/docs/workflows.md +146 -101
- data/lib/cmdx/.DS_Store +0 -0
- data/lib/cmdx/callback.rb +39 -55
- data/lib/cmdx/callback_registry.rb +80 -73
- data/lib/cmdx/chain.rb +65 -122
- data/lib/cmdx/chain_inspector.rb +23 -116
- data/lib/cmdx/chain_serializer.rb +34 -146
- data/lib/cmdx/coercion.rb +57 -0
- data/lib/cmdx/coercion_registry.rb +113 -0
- data/lib/cmdx/coercions/array.rb +18 -36
- data/lib/cmdx/coercions/big_decimal.rb +21 -33
- data/lib/cmdx/coercions/boolean.rb +21 -40
- data/lib/cmdx/coercions/complex.rb +18 -31
- data/lib/cmdx/coercions/date.rb +20 -39
- data/lib/cmdx/coercions/date_time.rb +22 -39
- data/lib/cmdx/coercions/float.rb +19 -32
- data/lib/cmdx/coercions/hash.rb +22 -41
- data/lib/cmdx/coercions/integer.rb +20 -33
- data/lib/cmdx/coercions/rational.rb +20 -32
- data/lib/cmdx/coercions/string.rb +23 -31
- data/lib/cmdx/coercions/time.rb +24 -40
- data/lib/cmdx/coercions/virtual.rb +14 -31
- data/lib/cmdx/configuration.rb +101 -162
- data/lib/cmdx/context.rb +34 -166
- data/lib/cmdx/core_ext/hash.rb +42 -67
- data/lib/cmdx/core_ext/module.rb +35 -79
- data/lib/cmdx/core_ext/object.rb +63 -98
- data/lib/cmdx/correlator.rb +59 -154
- data/lib/cmdx/error.rb +37 -202
- data/lib/cmdx/errors.rb +153 -216
- data/lib/cmdx/fault.rb +68 -150
- data/lib/cmdx/faults.rb +26 -137
- data/lib/cmdx/immutator.rb +22 -110
- data/lib/cmdx/lazy_struct.rb +110 -186
- data/lib/cmdx/log_formatters/json.rb +14 -40
- data/lib/cmdx/log_formatters/key_value.rb +14 -40
- data/lib/cmdx/log_formatters/line.rb +14 -48
- data/lib/cmdx/log_formatters/logstash.rb +14 -57
- data/lib/cmdx/log_formatters/pretty_json.rb +14 -50
- data/lib/cmdx/log_formatters/pretty_key_value.rb +13 -46
- data/lib/cmdx/log_formatters/pretty_line.rb +16 -54
- data/lib/cmdx/log_formatters/raw.rb +19 -49
- data/lib/cmdx/logger.rb +22 -79
- data/lib/cmdx/logger_ansi.rb +31 -72
- data/lib/cmdx/logger_serializer.rb +74 -103
- data/lib/cmdx/middleware.rb +56 -60
- data/lib/cmdx/middleware_registry.rb +82 -77
- data/lib/cmdx/middlewares/correlate.rb +41 -226
- data/lib/cmdx/middlewares/timeout.rb +46 -185
- data/lib/cmdx/parameter.rb +167 -183
- data/lib/cmdx/parameter_evaluator.rb +231 -0
- data/lib/cmdx/parameter_inspector.rb +37 -55
- data/lib/cmdx/parameter_registry.rb +65 -84
- data/lib/cmdx/parameter_serializer.rb +32 -76
- data/lib/cmdx/railtie.rb +24 -107
- data/lib/cmdx/result.rb +254 -259
- data/lib/cmdx/result_ansi.rb +28 -80
- data/lib/cmdx/result_inspector.rb +34 -70
- data/lib/cmdx/result_logger.rb +23 -77
- data/lib/cmdx/result_serializer.rb +59 -125
- data/lib/cmdx/rspec/matchers.rb +28 -0
- data/lib/cmdx/rspec/result_matchers/be_executed.rb +42 -0
- data/lib/cmdx/rspec/result_matchers/be_failed_task.rb +94 -0
- data/lib/cmdx/rspec/result_matchers/be_skipped_task.rb +94 -0
- data/lib/cmdx/rspec/result_matchers/be_state_matchers.rb +59 -0
- data/lib/cmdx/rspec/result_matchers/be_status_matchers.rb +57 -0
- data/lib/cmdx/rspec/result_matchers/be_successful_task.rb +87 -0
- data/lib/cmdx/rspec/result_matchers/have_bad_outcome.rb +51 -0
- data/lib/cmdx/rspec/result_matchers/have_caused_failure.rb +58 -0
- data/lib/cmdx/rspec/result_matchers/have_chain_index.rb +59 -0
- data/lib/cmdx/rspec/result_matchers/have_context.rb +86 -0
- data/lib/cmdx/rspec/result_matchers/have_empty_metadata.rb +54 -0
- data/lib/cmdx/rspec/result_matchers/have_good_outcome.rb +52 -0
- data/lib/cmdx/rspec/result_matchers/have_metadata.rb +114 -0
- data/lib/cmdx/rspec/result_matchers/have_preserved_context.rb +66 -0
- data/lib/cmdx/rspec/result_matchers/have_received_thrown_failure.rb +64 -0
- data/lib/cmdx/rspec/result_matchers/have_runtime.rb +78 -0
- data/lib/cmdx/rspec/result_matchers/have_thrown_failure.rb +76 -0
- data/lib/cmdx/rspec/task_matchers/be_well_formed_task.rb +62 -0
- data/lib/cmdx/rspec/task_matchers/have_callback.rb +85 -0
- data/lib/cmdx/rspec/task_matchers/have_cmd_setting.rb +68 -0
- data/lib/cmdx/rspec/task_matchers/have_executed_callbacks.rb +92 -0
- data/lib/cmdx/rspec/task_matchers/have_middleware.rb +46 -0
- data/lib/cmdx/rspec/task_matchers/have_parameter.rb +181 -0
- data/lib/cmdx/task.rb +336 -427
- data/lib/cmdx/task_deprecator.rb +52 -0
- data/lib/cmdx/task_processor.rb +246 -0
- data/lib/cmdx/task_serializer.rb +34 -69
- data/lib/cmdx/utils/ansi_color.rb +13 -89
- data/lib/cmdx/utils/log_timestamp.rb +13 -42
- data/lib/cmdx/utils/monotonic_runtime.rb +11 -63
- data/lib/cmdx/utils/name_affix.rb +21 -71
- data/lib/cmdx/validator.rb +57 -0
- data/lib/cmdx/validator_registry.rb +108 -0
- data/lib/cmdx/validators/exclusion.rb +55 -94
- data/lib/cmdx/validators/format.rb +31 -85
- data/lib/cmdx/validators/inclusion.rb +65 -110
- data/lib/cmdx/validators/length.rb +117 -133
- data/lib/cmdx/validators/numeric.rb +123 -130
- data/lib/cmdx/validators/presence.rb +38 -79
- data/lib/cmdx/version.rb +1 -7
- data/lib/cmdx/workflow.rb +58 -330
- data/lib/cmdx.rb +1 -1
- data/lib/generators/cmdx/install_generator.rb +14 -31
- data/lib/generators/cmdx/task_generator.rb +39 -55
- data/lib/generators/cmdx/templates/install.rb +24 -6
- data/lib/generators/cmdx/workflow_generator.rb +41 -66
- data/lib/locales/ar.yml +0 -1
- data/lib/locales/cs.yml +0 -1
- data/lib/locales/da.yml +0 -1
- data/lib/locales/de.yml +0 -1
- data/lib/locales/el.yml +0 -1
- data/lib/locales/en.yml +0 -1
- data/lib/locales/es.yml +0 -1
- data/lib/locales/fi.yml +0 -1
- data/lib/locales/fr.yml +0 -1
- data/lib/locales/he.yml +0 -1
- data/lib/locales/hi.yml +0 -1
- data/lib/locales/it.yml +0 -1
- data/lib/locales/ja.yml +0 -1
- data/lib/locales/ko.yml +0 -1
- data/lib/locales/nl.yml +0 -1
- data/lib/locales/no.yml +0 -1
- data/lib/locales/pl.yml +0 -1
- data/lib/locales/pt.yml +0 -1
- data/lib/locales/ru.yml +0 -1
- data/lib/locales/sv.yml +0 -1
- data/lib/locales/th.yml +0 -1
- data/lib/locales/tr.yml +0 -1
- data/lib/locales/vi.yml +0 -1
- data/lib/locales/zh.yml +0 -1
- metadata +36 -8
- data/lib/cmdx/parameter_validator.rb +0 -81
- data/lib/cmdx/parameter_value.rb +0 -244
- data/lib/cmdx/parameters_inspector.rb +0 -72
- data/lib/cmdx/parameters_serializer.rb +0 -115
- data/lib/cmdx/rspec/result_matchers.rb +0 -917
- data/lib/cmdx/rspec/task_matchers.rb +0 -570
- data/lib/cmdx/validators/custom.rb +0 -102
data/docs/outcomes/statuses.md
CHANGED
@@ -1,99 +1,76 @@
|
|
1
1
|
# Outcomes - Statuses
|
2
2
|
|
3
|
-
Statuses represent the outcome of task execution logic, indicating how the task's business logic concluded. Statuses differ from execution states by focusing on the business outcome rather than the technical execution lifecycle. Understanding statuses is crucial for implementing proper business logic branching and error handling.
|
3
|
+
Statuses represent the business outcome of task execution logic, indicating how the task's business logic concluded. Statuses differ from execution states by focusing on the business outcome rather than the technical execution lifecycle. Understanding statuses is crucial for implementing proper business logic branching and error handling.
|
4
4
|
|
5
5
|
## Table of Contents
|
6
6
|
|
7
7
|
- [TLDR](#tldr)
|
8
8
|
- [Status Definitions](#status-definitions)
|
9
|
-
- [Status Characteristics](#status-characteristics)
|
10
|
-
- [Status Predicates](#status-predicates)
|
11
9
|
- [Status Transitions](#status-transitions)
|
10
|
+
- [Status Predicates](#status-predicates)
|
12
11
|
- [Status-Based Callbacks](#status-based-callbacks)
|
13
12
|
- [Status Metadata](#status-metadata)
|
14
13
|
- [Outcome-Based Logic](#outcome-based-logic)
|
15
|
-
- [Status Serialization and Inspection](#status-serialization-and-inspection)
|
16
14
|
- [Status vs State vs Outcome](#status-vs-state-vs-outcome)
|
15
|
+
- [Status Serialization and Inspection](#status-serialization-and-inspection)
|
17
16
|
|
18
17
|
## TLDR
|
19
18
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
## Status Definitions
|
27
|
-
|
28
|
-
| Status | Description |
|
29
|
-
| --------- | ----------- |
|
30
|
-
| `success` | Task execution completed successfully with expected business outcome |
|
31
|
-
| `skipped` | Task intentionally stopped execution because conditions weren't met or continuation was unnecessary |
|
32
|
-
| `failed` | Task stopped execution due to business rule violations, validation errors, or exceptions |
|
33
|
-
|
34
|
-
> [!NOTE]
|
35
|
-
> Statuses focus on business outcomes, not technical execution states. A task can be technically "complete" but have a "failed" status if business logic determined the operation could not succeed.
|
36
|
-
|
37
|
-
## Status Characteristics
|
38
|
-
|
39
|
-
### Success
|
40
|
-
- **Default status** for all newly created tasks
|
41
|
-
- Indicates business logic completed as expected
|
42
|
-
- Remains even if no actual execution occurred (e.g., cached results)
|
43
|
-
- Compatible with both `complete` and `interrupted` states
|
44
|
-
|
45
|
-
### Skipped
|
46
|
-
- Indicates intentional early termination
|
47
|
-
- Business logic determined execution was unnecessary
|
48
|
-
- Often used for conditional workflows and guard clauses
|
49
|
-
- Triggered by `skip!` method with contextual metadata
|
19
|
+
```ruby
|
20
|
+
# Check business outcomes
|
21
|
+
result.success? # → true (default outcome)
|
22
|
+
result.skipped? # → false (via skip!)
|
23
|
+
result.failed? # → false (via fail!)
|
50
24
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
- Contains detailed error information in metadata
|
55
|
-
- Triggered by `fail!` method or automatic exception handling
|
25
|
+
# Outcome-based logic
|
26
|
+
result.good? # → true (success OR skipped)
|
27
|
+
result.bad? # → false (skipped OR failed)
|
56
28
|
|
57
|
-
|
29
|
+
# Status-based callbacks
|
30
|
+
result
|
31
|
+
.on_success { |r| process_success(r) }
|
32
|
+
.on_skipped { |r| handle_skip_condition(r) }
|
33
|
+
.on_failed { |r| handle_business_failure(r) }
|
58
34
|
|
59
|
-
|
35
|
+
# Statuses: HOW it ended, States: WHERE in lifecycle
|
36
|
+
result.status #=> "success" (business outcome)
|
37
|
+
result.state #=> "complete" (execution lifecycle)
|
38
|
+
```
|
60
39
|
|
61
|
-
|
62
|
-
result = ProcessUserOrderTask.call
|
40
|
+
## Status Definitions
|
63
41
|
|
64
|
-
|
65
|
-
|
66
|
-
result.skipped? #=> true/false
|
67
|
-
result.failed? #=> true/false
|
42
|
+
> [!IMPORTANT]
|
43
|
+
> Statuses represent business outcomes, not technical execution states. A task can be technically "complete" but have a "failed" status if business logic determined the operation could not succeed.
|
68
44
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
45
|
+
| Status | Description |
|
46
|
+
| ------ | ----------- |
|
47
|
+
| `success` | Task execution completed successfully with expected business outcome. Default status for all tasks. |
|
48
|
+
| `skipped` | Task intentionally stopped execution because conditions weren't met or continuation was unnecessary. |
|
49
|
+
| `failed` | Task stopped execution due to business rule violations, validation errors, or exceptions. |
|
73
50
|
|
74
51
|
## Status Transitions
|
75
52
|
|
53
|
+
> [!WARNING]
|
54
|
+
> Status transitions are unidirectional and final. Once a task is marked as skipped or failed, it cannot return to success status. Design your business logic accordingly.
|
55
|
+
|
76
56
|
Unlike states, statuses can only transition from success to skipped/failed:
|
77
57
|
|
78
58
|
```ruby
|
79
59
|
# Valid status transitions
|
80
|
-
success
|
81
|
-
success
|
60
|
+
success → skipped # via skip!
|
61
|
+
success → failed # via fail! or exception
|
82
62
|
|
83
63
|
# Invalid transitions (will raise errors)
|
84
|
-
skipped
|
85
|
-
skipped
|
86
|
-
failed
|
87
|
-
failed
|
64
|
+
skipped → success # ❌ Cannot transition
|
65
|
+
skipped → failed # ❌ Cannot transition
|
66
|
+
failed → success # ❌ Cannot transition
|
67
|
+
failed → skipped # ❌ Cannot transition
|
88
68
|
```
|
89
69
|
|
90
|
-
> [!IMPORTANT]
|
91
|
-
> Status transitions are unidirectional and final. Once a task is marked as skipped or failed, it cannot return to success status. Design your business logic accordingly.
|
92
|
-
|
93
70
|
### Status Transition Examples
|
94
71
|
|
95
72
|
```ruby
|
96
|
-
class
|
73
|
+
class ProcessOrderTask < CMDx::Task
|
97
74
|
def call
|
98
75
|
# Task starts with success status
|
99
76
|
context.result.success? #=> true
|
@@ -105,7 +82,7 @@ class ProcessUserOrderTask < CMDx::Task
|
|
105
82
|
end
|
106
83
|
|
107
84
|
# Conditional failure
|
108
|
-
unless context.user.
|
85
|
+
unless context.user.authorized?
|
109
86
|
fail!(reason: "Insufficient permissions")
|
110
87
|
# Status is now failed, execution halts
|
111
88
|
end
|
@@ -117,112 +94,149 @@ class ProcessUserOrderTask < CMDx::Task
|
|
117
94
|
end
|
118
95
|
```
|
119
96
|
|
97
|
+
## Status Predicates
|
98
|
+
|
99
|
+
Use status predicates to check execution outcomes:
|
100
|
+
|
101
|
+
```ruby
|
102
|
+
class PaymentProcessingTask < CMDx::Task
|
103
|
+
def call
|
104
|
+
charge_customer
|
105
|
+
send_receipt
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
result = PaymentProcessingTask.call
|
110
|
+
|
111
|
+
# Individual status checks
|
112
|
+
result.success? #=> true/false
|
113
|
+
result.skipped? #=> true/false
|
114
|
+
result.failed? #=> true/false
|
115
|
+
|
116
|
+
# Outcome categorization
|
117
|
+
result.good? #=> true if success OR skipped
|
118
|
+
result.bad? #=> true if skipped OR failed (not success)
|
119
|
+
```
|
120
|
+
|
121
|
+
### Status Checking in Business Logic
|
122
|
+
|
123
|
+
```ruby
|
124
|
+
def handle_payment_result(result)
|
125
|
+
if result.success?
|
126
|
+
send_confirmation_email(result.context.customer)
|
127
|
+
elsif result.skipped?
|
128
|
+
log_skip_reason(result.metadata[:reason])
|
129
|
+
elsif result.failed?
|
130
|
+
handle_payment_failure(result.metadata)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
```
|
134
|
+
|
120
135
|
## Status-Based Callbacks
|
121
136
|
|
122
|
-
|
137
|
+
> [!TIP]
|
138
|
+
> Use status-based callbacks for business logic branching. The `on_good` and `on_bad` callbacks are particularly useful for handling success/skip vs failed outcomes respectively.
|
123
139
|
|
124
140
|
```ruby
|
125
|
-
|
141
|
+
class OrderFulfillmentTask < CMDx::Task
|
142
|
+
def call
|
143
|
+
validate_inventory
|
144
|
+
process_payment
|
145
|
+
schedule_shipping
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
result = OrderFulfillmentTask.call
|
126
150
|
|
127
151
|
# Individual status callbacks
|
128
152
|
result
|
129
|
-
.on_success { |r|
|
130
|
-
.on_skipped { |r|
|
131
|
-
.on_failed { |r|
|
153
|
+
.on_success { |r| schedule_delivery(r.context.order) }
|
154
|
+
.on_skipped { |r| notify_backorder(r.context.customer) }
|
155
|
+
.on_failed { |r| refund_payment(r.context.payment_id) }
|
132
156
|
|
133
157
|
# Outcome-based callbacks
|
134
158
|
result
|
135
|
-
.on_good { |r|
|
136
|
-
.on_bad { |r| log_negative_outcome(r) }
|
159
|
+
.on_good { |r| update_inventory(r.context.items) }
|
160
|
+
.on_bad { |r| log_negative_outcome(r.metadata) }
|
137
161
|
```
|
138
162
|
|
139
|
-
> [!TIP]
|
140
|
-
> Use status-based callbacks for business logic branching. The `on_good` and `on_bad` callbacks are particularly useful for handling success/skip vs failed outcomes respectively.
|
141
|
-
|
142
163
|
## Status Metadata
|
143
164
|
|
144
|
-
|
165
|
+
> [!NOTE]
|
166
|
+
> Always include rich metadata with skip and fail operations. This information is invaluable for debugging, user feedback, and automated error handling.
|
145
167
|
|
146
168
|
### Success Metadata
|
147
169
|
|
148
170
|
```ruby
|
149
|
-
class
|
171
|
+
class ProcessRefundTask < CMDx::Task
|
150
172
|
def call
|
151
|
-
|
152
|
-
context.
|
153
|
-
context.
|
154
|
-
|
155
|
-
# Success status typically has empty metadata
|
156
|
-
# but can include business-relevant information
|
157
|
-
context.processing_time = Time.now - context.start_time
|
158
|
-
context.confirmation_number = generate_confirmation
|
173
|
+
refund = create_refund(context.payment_id)
|
174
|
+
context.refund_id = refund.id
|
175
|
+
context.processed_at = Time.now
|
159
176
|
end
|
160
177
|
end
|
161
178
|
|
162
|
-
result =
|
179
|
+
result = ProcessRefundTask.call(payment_id: "pay_123")
|
163
180
|
result.success? #=> true
|
164
|
-
result.metadata #=> {} (
|
181
|
+
result.metadata #=> {} (typically empty for success)
|
165
182
|
```
|
166
183
|
|
167
184
|
### Skip Metadata
|
168
185
|
|
169
186
|
```ruby
|
170
|
-
class
|
187
|
+
class ProcessSubscriptionTask < CMDx::Task
|
171
188
|
def call
|
172
|
-
|
189
|
+
subscription = Subscription.find(context.subscription_id)
|
173
190
|
|
174
|
-
if
|
191
|
+
if subscription.cancelled?
|
175
192
|
skip!(
|
176
|
-
reason: "
|
177
|
-
|
178
|
-
|
179
|
-
skip_code: "DUPLICATE_PROCESSING"
|
193
|
+
reason: "Subscription already cancelled",
|
194
|
+
cancelled_at: subscription.cancelled_at,
|
195
|
+
skip_code: "ALREADY_CANCELLED"
|
180
196
|
)
|
181
197
|
end
|
182
198
|
|
183
|
-
|
199
|
+
process_subscription(subscription)
|
184
200
|
end
|
185
201
|
end
|
186
202
|
|
187
|
-
result =
|
203
|
+
result = ProcessSubscriptionTask.call(subscription_id: 123)
|
188
204
|
if result.skipped?
|
189
|
-
result.metadata[:reason]
|
190
|
-
result.metadata[:
|
191
|
-
result.metadata[:
|
192
|
-
result.metadata[:skip_code] #=> "DUPLICATE_PROCESSING"
|
205
|
+
result.metadata[:reason] #=> "Subscription already cancelled"
|
206
|
+
result.metadata[:cancelled_at] #=> 2023-10-01 10:30:00 UTC
|
207
|
+
result.metadata[:skip_code] #=> "ALREADY_CANCELLED"
|
193
208
|
end
|
194
209
|
```
|
195
210
|
|
196
211
|
### Failure Metadata
|
197
212
|
|
198
213
|
```ruby
|
199
|
-
class
|
214
|
+
class ValidateUserDataTask < CMDx::Task
|
200
215
|
def call
|
201
|
-
|
216
|
+
user = User.find(context.user_id)
|
217
|
+
|
218
|
+
unless user.valid?
|
202
219
|
fail!(
|
203
|
-
reason: "
|
204
|
-
errors:
|
220
|
+
reason: "User validation failed",
|
221
|
+
errors: user.errors.full_messages,
|
205
222
|
error_code: "VALIDATION_FAILED",
|
206
|
-
retryable: false
|
207
|
-
failed_at: Time.now
|
223
|
+
retryable: false
|
208
224
|
)
|
209
225
|
end
|
226
|
+
|
227
|
+
context.validated_user = user
|
210
228
|
end
|
211
229
|
end
|
212
230
|
|
213
|
-
result =
|
231
|
+
result = ValidateUserDataTask.call(user_id: 123)
|
214
232
|
if result.failed?
|
215
|
-
result.metadata[:reason] #=> "
|
216
|
-
result.metadata[:errors] #=> ["Name can't be blank"
|
233
|
+
result.metadata[:reason] #=> "User validation failed"
|
234
|
+
result.metadata[:errors] #=> ["Email is invalid", "Name can't be blank"]
|
217
235
|
result.metadata[:error_code] #=> "VALIDATION_FAILED"
|
218
236
|
result.metadata[:retryable] #=> false
|
219
|
-
result.metadata[:failed_at] #=> 2023-10-01 10:30:00 UTC
|
220
237
|
end
|
221
238
|
```
|
222
239
|
|
223
|
-
> [!TIP]
|
224
|
-
> Always try to include rich metadata with skip and fail operations. This information is invaluable for debugging, user feedback, and automated error handling.
|
225
|
-
|
226
240
|
## Outcome-Based Logic
|
227
241
|
|
228
242
|
Statuses enable sophisticated outcome-based decision making:
|
@@ -230,53 +244,51 @@ Statuses enable sophisticated outcome-based decision making:
|
|
230
244
|
### Good vs Bad Outcomes
|
231
245
|
|
232
246
|
```ruby
|
233
|
-
|
234
|
-
|
235
|
-
|
247
|
+
class EmailDeliveryTask < CMDx::Task
|
248
|
+
def call
|
249
|
+
# Business logic here
|
250
|
+
send_email
|
251
|
+
end
|
252
|
+
end
|
236
253
|
|
237
|
-
|
254
|
+
result = EmailDeliveryTask.call
|
255
|
+
|
256
|
+
# Good outcomes (success OR skipped)
|
238
257
|
if result.good?
|
239
258
|
# Both success and skipped are "good" outcomes
|
240
259
|
update_user_interface(result)
|
241
|
-
|
260
|
+
track_completion_metrics(result)
|
242
261
|
end
|
243
262
|
|
263
|
+
# Bad outcomes (skipped OR failed, excluding success)
|
244
264
|
if result.bad?
|
245
|
-
# Handle any non-success outcome
|
265
|
+
# Handle any non-success outcome
|
246
266
|
show_error_message(result.metadata[:reason])
|
247
|
-
|
267
|
+
track_failure_metrics(result)
|
248
268
|
end
|
249
269
|
```
|
250
270
|
|
251
|
-
|
252
|
-
|
253
|
-
Statuses are fully captured in result serialization:
|
271
|
+
### Conditional Processing
|
254
272
|
|
255
273
|
```ruby
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
# status: "success",
|
268
|
-
# outcome: "success",
|
269
|
-
# metadata: {},
|
270
|
-
# # ... other attributes
|
271
|
-
# }
|
272
|
-
|
273
|
-
# Human-readable inspection
|
274
|
-
result.to_s
|
275
|
-
#=> "ProcessUserOrderTask: type=Task index=0 state=complete status=success outcome=success..."
|
274
|
+
def process_batch_results(results)
|
275
|
+
successful_count = results.count(&:success?)
|
276
|
+
skipped_count = results.count(&:skipped?)
|
277
|
+
failed_count = results.count(&:failed?)
|
278
|
+
|
279
|
+
if results.all?(&:good?)
|
280
|
+
mark_batch_complete
|
281
|
+
elsif results.any?(&:failed?)
|
282
|
+
schedule_batch_retry(results.select(&:failed?))
|
283
|
+
end
|
284
|
+
end
|
276
285
|
```
|
277
286
|
|
278
287
|
## Status vs State vs Outcome
|
279
288
|
|
289
|
+
> [!NOTE]
|
290
|
+
> Status tracks the business outcome (how the task ended), while state tracks the execution lifecycle (where the task is). Both provide valuable but different information about task execution.
|
291
|
+
|
280
292
|
Understanding the relationship between these concepts:
|
281
293
|
|
282
294
|
- **Status**: Business execution outcome (`success`, `skipped`, `failed`)
|
@@ -284,24 +296,70 @@ Understanding the relationship between these concepts:
|
|
284
296
|
- **Outcome**: Combined representation for unified logic
|
285
297
|
|
286
298
|
```ruby
|
287
|
-
|
299
|
+
class DataImportTask < CMDx::Task
|
300
|
+
def call
|
301
|
+
import_data
|
302
|
+
validate_data
|
303
|
+
end
|
304
|
+
end
|
288
305
|
|
289
|
-
|
290
|
-
|
291
|
-
|
306
|
+
result = DataImportTask.call
|
307
|
+
|
308
|
+
# Successful execution
|
309
|
+
result.state #=> "complete" (execution finished)
|
310
|
+
result.status #=> "success" (business outcome)
|
292
311
|
result.outcome #=> "success" (same as status when complete)
|
293
312
|
|
294
|
-
# Skipped
|
313
|
+
# Skipped execution
|
314
|
+
skipped_result = DataImportTask.call(skip_import: true)
|
295
315
|
skipped_result.state #=> "complete" (execution finished)
|
296
316
|
skipped_result.status #=> "skipped" (business outcome)
|
297
317
|
skipped_result.outcome #=> "skipped" (same as status)
|
298
318
|
|
299
|
-
# Failed
|
319
|
+
# Failed execution
|
320
|
+
failed_result = DataImportTask.call(invalid_data: true)
|
300
321
|
failed_result.state #=> "interrupted" (execution stopped)
|
301
322
|
failed_result.status #=> "failed" (business outcome)
|
302
323
|
failed_result.outcome #=> "interrupted" (reflects state for interrupted tasks)
|
303
324
|
```
|
304
325
|
|
326
|
+
## Status Serialization and Inspection
|
327
|
+
|
328
|
+
> [!IMPORTANT]
|
329
|
+
> Statuses are automatically captured in result serialization and logging. All status information persists through the complete task execution lifecycle.
|
330
|
+
|
331
|
+
```ruby
|
332
|
+
result = ProcessOrderTask.call
|
333
|
+
|
334
|
+
# Hash representation includes status
|
335
|
+
result.to_h
|
336
|
+
#=> {
|
337
|
+
# class: "ProcessOrderTask",
|
338
|
+
# index: 0,
|
339
|
+
# state: "complete",
|
340
|
+
# status: "success",
|
341
|
+
# outcome: "success",
|
342
|
+
# runtime: 0.045,
|
343
|
+
# metadata: {},
|
344
|
+
# context: { order_id: 123 }
|
345
|
+
# }
|
346
|
+
|
347
|
+
# Human-readable inspection
|
348
|
+
result.to_s
|
349
|
+
#=> "ProcessOrderTask: type=Task index=0 state=complete status=success outcome=success runtime=0.045s"
|
350
|
+
|
351
|
+
# Chain-level status aggregation
|
352
|
+
result.chain.to_h
|
353
|
+
#=> {
|
354
|
+
# id: "chain-550e8400-e29b-41d4-a716-446655440000",
|
355
|
+
# state: "complete",
|
356
|
+
# status: "success",
|
357
|
+
# results: [
|
358
|
+
# { state: "complete", status: "success", ... }
|
359
|
+
# ]
|
360
|
+
# }
|
361
|
+
```
|
362
|
+
|
305
363
|
---
|
306
364
|
|
307
365
|
- **Prev:** [Outcomes - Result](result.md)
|