cmdx 1.1.1 → 1.5.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/.DS_Store +0 -0
- data/.cursor/prompts/docs.md +4 -1
- data/.cursor/prompts/llms.md +20 -0
- data/.cursor/prompts/rspec.md +4 -1
- data/.cursor/prompts/yardoc.md +3 -2
- data/.cursor/rules/cursor-instructions.mdc +56 -1
- data/.irbrc +6 -0
- data/.rubocop.yml +29 -18
- data/.ruby-version +1 -1
- data/CHANGELOG.md +6 -128
- data/LLM.md +3317 -0
- data/README.md +68 -44
- data/docs/attributes/coercions.md +162 -0
- data/docs/attributes/defaults.md +90 -0
- data/docs/attributes/definitions.md +281 -0
- data/docs/attributes/naming.md +78 -0
- data/docs/attributes/validations.md +309 -0
- data/docs/basics/chain.md +56 -249
- data/docs/basics/context.md +56 -289
- data/docs/basics/execution.md +114 -0
- data/docs/basics/setup.md +37 -334
- data/docs/callbacks.md +89 -467
- data/docs/deprecation.md +91 -174
- data/docs/getting_started.md +212 -202
- data/docs/internationalization.md +11 -647
- data/docs/interruptions/exceptions.md +23 -198
- data/docs/interruptions/faults.md +71 -151
- data/docs/interruptions/halt.md +109 -186
- data/docs/logging.md +44 -256
- data/docs/middlewares.md +113 -426
- data/docs/outcomes/result.md +81 -228
- data/docs/outcomes/states.md +33 -221
- data/docs/outcomes/statuses.md +21 -311
- data/docs/tips_and_tricks.md +120 -70
- data/docs/workflows.md +99 -283
- data/lib/cmdx/.DS_Store +0 -0
- data/lib/cmdx/attribute.rb +229 -0
- data/lib/cmdx/attribute_registry.rb +94 -0
- data/lib/cmdx/attribute_value.rb +193 -0
- data/lib/cmdx/callback_registry.rb +69 -77
- data/lib/cmdx/chain.rb +56 -73
- data/lib/cmdx/coercion_registry.rb +52 -68
- data/lib/cmdx/coercions/array.rb +19 -18
- data/lib/cmdx/coercions/big_decimal.rb +20 -24
- data/lib/cmdx/coercions/boolean.rb +26 -25
- data/lib/cmdx/coercions/complex.rb +21 -22
- data/lib/cmdx/coercions/date.rb +25 -23
- data/lib/cmdx/coercions/date_time.rb +24 -25
- data/lib/cmdx/coercions/float.rb +25 -22
- data/lib/cmdx/coercions/hash.rb +31 -32
- data/lib/cmdx/coercions/integer.rb +30 -24
- data/lib/cmdx/coercions/rational.rb +29 -24
- data/lib/cmdx/coercions/string.rb +19 -22
- data/lib/cmdx/coercions/symbol.rb +37 -0
- data/lib/cmdx/coercions/time.rb +26 -25
- data/lib/cmdx/configuration.rb +49 -108
- data/lib/cmdx/context.rb +222 -44
- data/lib/cmdx/deprecator.rb +61 -0
- data/lib/cmdx/errors.rb +42 -252
- data/lib/cmdx/exceptions.rb +39 -0
- data/lib/cmdx/faults.rb +78 -39
- data/lib/cmdx/freezer.rb +51 -0
- data/lib/cmdx/identifier.rb +30 -0
- data/lib/cmdx/locale.rb +52 -0
- data/lib/cmdx/log_formatters/json.rb +21 -22
- data/lib/cmdx/log_formatters/key_value.rb +20 -22
- data/lib/cmdx/log_formatters/line.rb +15 -22
- data/lib/cmdx/log_formatters/logstash.rb +22 -23
- data/lib/cmdx/log_formatters/raw.rb +16 -22
- data/lib/cmdx/middleware_registry.rb +70 -74
- data/lib/cmdx/middlewares/correlate.rb +90 -54
- data/lib/cmdx/middlewares/runtime.rb +58 -0
- data/lib/cmdx/middlewares/timeout.rb +48 -68
- data/lib/cmdx/railtie.rb +12 -45
- data/lib/cmdx/result.rb +229 -314
- data/lib/cmdx/task.rb +194 -366
- data/lib/cmdx/utils/call.rb +49 -0
- data/lib/cmdx/utils/condition.rb +71 -0
- data/lib/cmdx/utils/format.rb +61 -0
- data/lib/cmdx/validator_registry.rb +63 -72
- data/lib/cmdx/validators/exclusion.rb +38 -67
- data/lib/cmdx/validators/format.rb +48 -49
- data/lib/cmdx/validators/inclusion.rb +43 -74
- data/lib/cmdx/validators/length.rb +91 -154
- data/lib/cmdx/validators/numeric.rb +87 -162
- data/lib/cmdx/validators/presence.rb +37 -50
- data/lib/cmdx/version.rb +1 -1
- data/lib/cmdx/worker.rb +178 -0
- data/lib/cmdx/workflow.rb +85 -81
- data/lib/cmdx.rb +19 -13
- data/lib/generators/cmdx/install_generator.rb +14 -13
- data/lib/generators/cmdx/task_generator.rb +25 -50
- data/lib/generators/cmdx/templates/install.rb +11 -46
- data/lib/generators/cmdx/templates/task.rb.tt +3 -2
- data/lib/locales/en.yml +18 -4
- data/src/cmdx-logo.png +0 -0
- metadata +32 -116
- data/docs/ai_prompts.md +0 -393
- data/docs/basics/call.md +0 -317
- data/docs/configuration.md +0 -344
- data/docs/parameters/coercions.md +0 -396
- data/docs/parameters/defaults.md +0 -335
- data/docs/parameters/definitions.md +0 -446
- data/docs/parameters/namespacing.md +0 -378
- data/docs/parameters/validations.md +0 -405
- data/docs/testing.md +0 -553
- data/lib/cmdx/callback.rb +0 -53
- data/lib/cmdx/chain_inspector.rb +0 -56
- data/lib/cmdx/chain_serializer.rb +0 -63
- data/lib/cmdx/coercion.rb +0 -57
- data/lib/cmdx/coercions/virtual.rb +0 -29
- data/lib/cmdx/core_ext/hash.rb +0 -83
- data/lib/cmdx/core_ext/module.rb +0 -98
- data/lib/cmdx/core_ext/object.rb +0 -125
- data/lib/cmdx/correlator.rb +0 -122
- data/lib/cmdx/error.rb +0 -60
- data/lib/cmdx/fault.rb +0 -140
- data/lib/cmdx/immutator.rb +0 -52
- data/lib/cmdx/lazy_struct.rb +0 -246
- data/lib/cmdx/log_formatters/pretty_json.rb +0 -40
- data/lib/cmdx/log_formatters/pretty_key_value.rb +0 -38
- data/lib/cmdx/log_formatters/pretty_line.rb +0 -41
- data/lib/cmdx/logger.rb +0 -49
- data/lib/cmdx/logger_ansi.rb +0 -68
- data/lib/cmdx/logger_serializer.rb +0 -116
- data/lib/cmdx/middleware.rb +0 -70
- data/lib/cmdx/parameter.rb +0 -312
- data/lib/cmdx/parameter_evaluator.rb +0 -231
- data/lib/cmdx/parameter_inspector.rb +0 -66
- data/lib/cmdx/parameter_registry.rb +0 -106
- data/lib/cmdx/parameter_serializer.rb +0 -59
- data/lib/cmdx/result_ansi.rb +0 -71
- data/lib/cmdx/result_inspector.rb +0 -71
- data/lib/cmdx/result_logger.rb +0 -59
- data/lib/cmdx/result_serializer.rb +0 -104
- data/lib/cmdx/rspec/matchers.rb +0 -28
- data/lib/cmdx/rspec/result_matchers/be_executed.rb +0 -42
- data/lib/cmdx/rspec/result_matchers/be_failed_task.rb +0 -94
- data/lib/cmdx/rspec/result_matchers/be_skipped_task.rb +0 -94
- data/lib/cmdx/rspec/result_matchers/be_state_matchers.rb +0 -59
- data/lib/cmdx/rspec/result_matchers/be_status_matchers.rb +0 -57
- data/lib/cmdx/rspec/result_matchers/be_successful_task.rb +0 -87
- data/lib/cmdx/rspec/result_matchers/have_bad_outcome.rb +0 -51
- data/lib/cmdx/rspec/result_matchers/have_caused_failure.rb +0 -58
- data/lib/cmdx/rspec/result_matchers/have_chain_index.rb +0 -59
- data/lib/cmdx/rspec/result_matchers/have_context.rb +0 -86
- data/lib/cmdx/rspec/result_matchers/have_empty_metadata.rb +0 -54
- data/lib/cmdx/rspec/result_matchers/have_good_outcome.rb +0 -52
- data/lib/cmdx/rspec/result_matchers/have_metadata.rb +0 -114
- data/lib/cmdx/rspec/result_matchers/have_preserved_context.rb +0 -66
- data/lib/cmdx/rspec/result_matchers/have_received_thrown_failure.rb +0 -64
- data/lib/cmdx/rspec/result_matchers/have_runtime.rb +0 -78
- data/lib/cmdx/rspec/result_matchers/have_thrown_failure.rb +0 -76
- data/lib/cmdx/rspec/task_matchers/be_well_formed_task.rb +0 -62
- data/lib/cmdx/rspec/task_matchers/have_callback.rb +0 -85
- data/lib/cmdx/rspec/task_matchers/have_cmd_setting.rb +0 -68
- data/lib/cmdx/rspec/task_matchers/have_executed_callbacks.rb +0 -92
- data/lib/cmdx/rspec/task_matchers/have_middleware.rb +0 -46
- data/lib/cmdx/rspec/task_matchers/have_parameter.rb +0 -181
- data/lib/cmdx/task_deprecator.rb +0 -52
- data/lib/cmdx/task_processor.rb +0 -246
- data/lib/cmdx/task_serializer.rb +0 -57
- data/lib/cmdx/utils/ansi_color.rb +0 -73
- data/lib/cmdx/utils/log_timestamp.rb +0 -36
- data/lib/cmdx/utils/monotonic_runtime.rb +0 -34
- data/lib/cmdx/utils/name_affix.rb +0 -52
- data/lib/cmdx/validator.rb +0 -57
- data/lib/generators/cmdx/templates/workflow.rb.tt +0 -7
- data/lib/generators/cmdx/workflow_generator.rb +0 -84
- data/lib/locales/ar.yml +0 -35
- data/lib/locales/cs.yml +0 -35
- data/lib/locales/da.yml +0 -35
- data/lib/locales/de.yml +0 -35
- data/lib/locales/el.yml +0 -35
- data/lib/locales/es.yml +0 -35
- data/lib/locales/fi.yml +0 -35
- data/lib/locales/fr.yml +0 -35
- data/lib/locales/he.yml +0 -35
- data/lib/locales/hi.yml +0 -35
- data/lib/locales/it.yml +0 -35
- data/lib/locales/ja.yml +0 -35
- data/lib/locales/ko.yml +0 -35
- data/lib/locales/nl.yml +0 -35
- data/lib/locales/no.yml +0 -35
- data/lib/locales/pl.yml +0 -35
- data/lib/locales/pt.yml +0 -35
- data/lib/locales/ru.yml +0 -35
- data/lib/locales/sv.yml +0 -35
- data/lib/locales/th.yml +0 -35
- data/lib/locales/tr.yml +0 -35
- data/lib/locales/vi.yml +0 -35
- data/lib/locales/zh.yml +0 -35
data/docs/interruptions/halt.md
CHANGED
@@ -1,184 +1,141 @@
|
|
1
1
|
# Interruptions - Halt
|
2
2
|
|
3
|
-
Halting stops execution
|
3
|
+
Halting stops task execution with explicit intent signaling. Tasks provide two primary halt methods that control execution flow and result in different outcomes.
|
4
4
|
|
5
5
|
## Table of Contents
|
6
6
|
|
7
|
-
- [
|
8
|
-
- [
|
9
|
-
- [Fail (`fail!`)](#fail-fail)
|
7
|
+
- [Skipping](#skipping)
|
8
|
+
- [Failing](#failing)
|
10
9
|
- [Metadata Enrichment](#metadata-enrichment)
|
11
10
|
- [State Transitions](#state-transitions)
|
12
|
-
- [
|
13
|
-
- [
|
14
|
-
- [
|
11
|
+
- [Execution Behavior](#execution-behavior)
|
12
|
+
- [Non-bang execution](#non-bang-execution)
|
13
|
+
- [Bang execution](#bang-execution)
|
14
|
+
- [Best Practices](#best-practices)
|
15
15
|
|
16
|
-
##
|
16
|
+
## Skipping
|
17
17
|
|
18
|
-
|
19
|
-
# Skip when task shouldn't execute (not an error)
|
20
|
-
skip!(reason: "Order already processed")
|
21
|
-
|
22
|
-
# Fail when task encounters error condition
|
23
|
-
fail!(reason: "Insufficient funds", error_code: "PAYMENT_DECLINED")
|
24
|
-
|
25
|
-
# With structured metadata
|
26
|
-
skip!(
|
27
|
-
reason: "User inactive",
|
28
|
-
user_id: 123,
|
29
|
-
last_active: "2023-01-01"
|
30
|
-
)
|
31
|
-
|
32
|
-
# Exception behavior with call vs call!
|
33
|
-
result = Task.call(params) # Returns result object
|
34
|
-
Task.call!(params) # Raises CMDx::Skipped/Failed on halt
|
35
|
-
```
|
36
|
-
|
37
|
-
## Skip (`skip!`)
|
38
|
-
|
39
|
-
> [!NOTE]
|
40
|
-
> Use `skip!` when a task cannot or should not execute under current conditions, but this is not an error. Skipped tasks are considered successful outcomes.
|
41
|
-
|
42
|
-
The `skip!` method indicates that a task did not meet the criteria to continue execution. This represents a controlled, intentional interruption where the task determines that execution is not necessary or appropriate.
|
18
|
+
`skip!` communicates that the task is to be intentionally bypassed. This represents a controlled, intentional interruption where the task determines that execution is not necessary or appropriate.
|
43
19
|
|
44
|
-
|
20
|
+
> [!IMPORTANT]
|
21
|
+
> Skipping is a no-op, not a failure or error and are considered successful outcomes.
|
45
22
|
|
46
23
|
```ruby
|
47
|
-
class
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
context.order = Order.find(order_id)
|
24
|
+
class ProcessInventory < CMDx::Task
|
25
|
+
def work
|
26
|
+
# Without a reason
|
27
|
+
skip! if Array(ENV["DISABLED_TASKS"]).include?(self.class.name)
|
52
28
|
|
53
|
-
#
|
54
|
-
skip!(
|
29
|
+
# With a reason
|
30
|
+
skip!("Warehouse closed") unless Time.now.hour.between?(8, 18)
|
55
31
|
|
56
|
-
|
57
|
-
skip!(reason: "Payment method required") unless context.order.payment_method
|
32
|
+
inventory = Inventory.find(context.inventory_id)
|
58
33
|
|
59
|
-
|
60
|
-
|
34
|
+
if inventory.already_counted?
|
35
|
+
skip!("Inventory already counted today")
|
36
|
+
else
|
37
|
+
inventory.count!
|
38
|
+
end
|
61
39
|
end
|
62
40
|
end
|
63
|
-
```
|
64
41
|
|
65
|
-
|
42
|
+
result = ProcessInventory.execute(inventory_id: 456)
|
66
43
|
|
67
|
-
|
68
|
-
|
69
|
-
| **Already processed** | `skip!(reason: "User already verified")` |
|
70
|
-
| **Prerequisites missing** | `skip!(reason: "Required documents not uploaded")` |
|
71
|
-
| **Business rules** | `skip!(reason: "Outside business hours")` |
|
72
|
-
| **State conditions** | `skip!(reason: "Account suspended")` |
|
44
|
+
# Executed
|
45
|
+
result.status #=> "skipped"
|
73
46
|
|
74
|
-
|
47
|
+
# Without a reason
|
48
|
+
result.reason #=> "no reason given"
|
75
49
|
|
76
|
-
|
77
|
-
|
50
|
+
# With a reason
|
51
|
+
result.reason #=> "Warehouse closed"
|
52
|
+
```
|
78
53
|
|
79
|
-
|
54
|
+
## Failing
|
80
55
|
|
81
|
-
|
56
|
+
`fail!` communicates that the task encountered an impediment that prevents successful completion. This represents controlled failure where the task explicitly determines that execution cannot continue.
|
82
57
|
|
83
58
|
```ruby
|
84
|
-
class
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
59
|
+
class ProcessRefund < CMDx::Task
|
60
|
+
def work
|
61
|
+
# Without a reason
|
62
|
+
fail! if Array(ENV["DISABLED_TASKS"]).include?(self.class.name)
|
63
|
+
|
64
|
+
refund = Refund.find(context.refund_id)
|
65
|
+
|
66
|
+
# With a reason
|
67
|
+
if refund.expired?
|
68
|
+
fail!("Refund period has expired")
|
69
|
+
elsif !refund.amount.positive?
|
70
|
+
fail!("Refund amount must be positive")
|
71
|
+
else
|
72
|
+
refund.process!
|
73
|
+
end
|
98
74
|
end
|
75
|
+
end
|
99
76
|
|
100
|
-
|
77
|
+
result = ProcessRefund.execute(refund_id: 789)
|
101
78
|
|
102
|
-
|
103
|
-
|
104
|
-
end
|
105
|
-
end
|
106
|
-
```
|
79
|
+
# Executed
|
80
|
+
result.status #=> "failed"
|
107
81
|
|
108
|
-
|
82
|
+
# Without a reason
|
83
|
+
result.reason #=> "no reason given"
|
109
84
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
| **Business rule violations** | `fail!(reason: "Credit limit exceeded")` |
|
114
|
-
| **External service errors** | `fail!(reason: "Payment gateway unavailable")` |
|
115
|
-
| **Data integrity issues** | `fail!(reason: "Duplicate transaction detected")` |
|
85
|
+
# With a reason
|
86
|
+
result.reason #=> "Refund period has expired"
|
87
|
+
```
|
116
88
|
|
117
89
|
## Metadata Enrichment
|
118
90
|
|
119
|
-
Both halt methods accept metadata to provide context about the interruption. Metadata is stored as a hash and becomes available through the result object.
|
120
|
-
|
121
|
-
### Structured Metadata
|
91
|
+
Both halt methods accept metadata to provide additional context about the interruption. Metadata is stored as a hash and becomes available through the result object.
|
122
92
|
|
123
93
|
```ruby
|
124
|
-
class
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
skip!(
|
132
|
-
reason: "Subscription expired",
|
133
|
-
user_id: context.user.id,
|
134
|
-
expired_at: context.user.subscription_expires_at,
|
135
|
-
plan_type: context.user.subscription_plan,
|
136
|
-
grace_period_ends: context.user.subscription_expires_at + 7.days
|
137
|
-
)
|
94
|
+
class ProcessRenewal < CMDx::Task
|
95
|
+
def work
|
96
|
+
license = License.find(context.license_id)
|
97
|
+
|
98
|
+
if license.already_renewed?
|
99
|
+
# Without metadata
|
100
|
+
skip!("License already renewed")
|
138
101
|
end
|
139
102
|
|
140
|
-
unless
|
103
|
+
unless license.renewal_eligible?
|
104
|
+
# With metadata
|
141
105
|
fail!(
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
error_code: "PAYMENT_METHOD_INVALID",
|
146
|
-
retry_after: Time.current + 1.hour
|
106
|
+
"License not eligible for renewal",
|
107
|
+
error_code: "LICENSE.NOT_ELIGIBLE",
|
108
|
+
retry_after: Time.current + 30.days
|
147
109
|
)
|
148
110
|
end
|
149
111
|
|
150
|
-
|
112
|
+
process_renewal
|
151
113
|
end
|
152
114
|
end
|
153
|
-
```
|
154
115
|
|
155
|
-
|
116
|
+
result = ProcessRenewal.execute(license_id: 567)
|
156
117
|
|
157
|
-
|
158
|
-
result
|
159
|
-
|
160
|
-
# Check result status
|
161
|
-
result.skipped? #=> true
|
162
|
-
result.failed? #=> false
|
118
|
+
# Without metadata
|
119
|
+
result.metadata #=> {}
|
163
120
|
|
164
|
-
#
|
165
|
-
result.metadata
|
166
|
-
|
167
|
-
|
168
|
-
|
121
|
+
# With metadata
|
122
|
+
result.metadata #=> {
|
123
|
+
# error_code: "LICENSE.NOT_ELIGIBLE",
|
124
|
+
# retry_after: <Time 30 days from now>
|
125
|
+
# }
|
169
126
|
```
|
170
127
|
|
171
128
|
## State Transitions
|
172
129
|
|
173
130
|
Halt methods trigger specific state and status transitions:
|
174
131
|
|
175
|
-
| Method | State
|
176
|
-
|
177
|
-
| `skip!` | `
|
178
|
-
| `fail!` | `
|
132
|
+
| Method | State | Status | Outcome |
|
133
|
+
|--------|-------|--------|---------|
|
134
|
+
| `skip!` | `interrupted` | `skipped` | `good? = true`, `bad? = true` |
|
135
|
+
| `fail!` | `interrupted` | `failed` | `good? = false`, `bad? = true` |
|
179
136
|
|
180
137
|
```ruby
|
181
|
-
result =
|
138
|
+
result = ProcessRenewal.execute(license_id: 567)
|
182
139
|
|
183
140
|
# State information
|
184
141
|
result.state #=> "interrupted"
|
@@ -191,96 +148,62 @@ result.good? #=> true for skipped, false for failed
|
|
191
148
|
result.bad? #=> true for both skipped and failed
|
192
149
|
```
|
193
150
|
|
194
|
-
##
|
151
|
+
## Execution Behavior
|
195
152
|
|
196
153
|
Halt methods behave differently depending on the call method used:
|
197
154
|
|
198
|
-
###
|
155
|
+
### Non-bang execution
|
199
156
|
|
200
|
-
Returns
|
157
|
+
Returns result object without raising exceptions:
|
201
158
|
|
202
159
|
```ruby
|
203
|
-
result =
|
160
|
+
result = ProcessRefund.execute(refund_id: 789)
|
204
161
|
|
205
162
|
case result.status
|
206
163
|
when "success"
|
207
|
-
puts "
|
164
|
+
puts "Refund processed: $#{result.context.refund.amount}"
|
208
165
|
when "skipped"
|
209
|
-
puts "
|
166
|
+
puts "Refund skipped: #{result.reason}"
|
210
167
|
when "failed"
|
211
|
-
puts "
|
212
|
-
|
168
|
+
puts "Refund failed: #{result.reason}"
|
169
|
+
handle_refund_error(result.metadata[:error_code])
|
213
170
|
end
|
214
171
|
```
|
215
172
|
|
216
|
-
###
|
173
|
+
### Bang execution
|
217
174
|
|
218
|
-
|
219
|
-
> The `call!` method raises exceptions for halt conditions based on the `task_halt` configuration. Handle these exceptions appropriately in your application flow.
|
175
|
+
Raises exceptions for halt conditions based on `task_breakpoints` configuration:
|
220
176
|
|
221
177
|
```ruby
|
222
178
|
begin
|
223
|
-
result =
|
224
|
-
puts "Success:
|
225
|
-
rescue CMDx::
|
179
|
+
result = ProcessRefund.execute!(refund_id: 789)
|
180
|
+
puts "Success: Refund processed"
|
181
|
+
rescue CMDx::SkipFault => e
|
226
182
|
puts "Skipped: #{e.message}"
|
227
|
-
|
228
|
-
rescue CMDx::Failed => e
|
183
|
+
rescue CMDx::FailFault => e
|
229
184
|
puts "Failed: #{e.message}"
|
230
|
-
|
231
|
-
notify_payment_team(e.context.payment_id)
|
185
|
+
handle_refund_failure(e.result.metadata[:error_code])
|
232
186
|
end
|
233
187
|
```
|
234
188
|
|
235
|
-
##
|
189
|
+
## Best Practices
|
236
190
|
|
237
|
-
|
238
|
-
|
239
|
-
```ruby
|
240
|
-
class ProcessOrderTask < CMDx::Task
|
241
|
-
def call
|
242
|
-
# This works - metadata accepts any hash
|
243
|
-
skip!(reason: "Valid skip", order_id: 123, custom_data: {nested: true})
|
244
|
-
|
245
|
-
# This also works - no metadata required
|
246
|
-
fail!
|
247
|
-
end
|
248
|
-
end
|
249
|
-
```
|
250
|
-
|
251
|
-
## The Reason Key
|
252
|
-
|
253
|
-
> [!TIP]
|
254
|
-
> Always include a `:reason` key in metadata when using halt methods. This provides clear context for debugging and creates meaningful exception messages.
|
255
|
-
|
256
|
-
The `:reason` key in metadata has special significance:
|
257
|
-
|
258
|
-
- Used as the exception message when faults are raised
|
259
|
-
- Provides human-readable explanation of the halt
|
260
|
-
- Strongly recommended for all halt calls
|
191
|
+
Always try to provide a `reason` when using halt methods. This provides clear context for debugging and creates meaningful exception messages.
|
261
192
|
|
262
193
|
```ruby
|
263
194
|
# Good: Clear, specific reason
|
264
|
-
skip!(
|
265
|
-
fail!(
|
195
|
+
skip!("Document processing paused for compliance review")
|
196
|
+
fail!("File format not supported by processor", code: "FORMAT_UNSUPPORTED")
|
266
197
|
|
267
|
-
# Acceptable:
|
268
|
-
skip!(
|
198
|
+
# Acceptable: Generic, non-specific reason
|
199
|
+
skip!("Paused")
|
200
|
+
fail!("Unsupported")
|
269
201
|
|
270
|
-
#
|
271
|
-
skip!
|
272
|
-
fail!
|
202
|
+
# Bad: Default, cannot determine reason
|
203
|
+
skip! #=> "no reason given"
|
204
|
+
fail! #=> "no reason given"
|
273
205
|
```
|
274
206
|
|
275
|
-
### Reason Best Practices
|
276
|
-
|
277
|
-
| Practice | Example |
|
278
|
-
|----------|---------|
|
279
|
-
| **Be specific** | `"Credit card expired on 2023-12-31"` vs `"Payment error"` |
|
280
|
-
| **Include context** | `"Inventory insufficient: need 5, have 2"` |
|
281
|
-
| **Use actionable language** | `"Email verification required before login"` |
|
282
|
-
| **Avoid technical jargon** | `"Payment declined"` vs `"Gateway returned 402"` |
|
283
|
-
|
284
207
|
---
|
285
208
|
|
286
209
|
- **Prev:** [Basics - Chain](../basics/chain.md)
|