cmdx 1.1.2 → 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.
Files changed (192) hide show
  1. checksums.yaml +4 -4
  2. data/.DS_Store +0 -0
  3. data/.cursor/prompts/docs.md +4 -1
  4. data/.cursor/prompts/llms.md +20 -0
  5. data/.cursor/prompts/rspec.md +4 -1
  6. data/.cursor/prompts/yardoc.md +3 -2
  7. data/.cursor/rules/cursor-instructions.mdc +56 -1
  8. data/.irbrc +6 -0
  9. data/.rubocop.yml +29 -18
  10. data/CHANGELOG.md +5 -133
  11. data/LLM.md +3317 -0
  12. data/README.md +68 -44
  13. data/docs/attributes/coercions.md +162 -0
  14. data/docs/attributes/defaults.md +90 -0
  15. data/docs/attributes/definitions.md +281 -0
  16. data/docs/attributes/naming.md +78 -0
  17. data/docs/attributes/validations.md +309 -0
  18. data/docs/basics/chain.md +56 -249
  19. data/docs/basics/context.md +56 -289
  20. data/docs/basics/execution.md +114 -0
  21. data/docs/basics/setup.md +37 -334
  22. data/docs/callbacks.md +89 -467
  23. data/docs/deprecation.md +91 -174
  24. data/docs/getting_started.md +212 -202
  25. data/docs/internationalization.md +11 -647
  26. data/docs/interruptions/exceptions.md +23 -198
  27. data/docs/interruptions/faults.md +71 -151
  28. data/docs/interruptions/halt.md +109 -186
  29. data/docs/logging.md +44 -256
  30. data/docs/middlewares.md +113 -426
  31. data/docs/outcomes/result.md +81 -228
  32. data/docs/outcomes/states.md +33 -221
  33. data/docs/outcomes/statuses.md +21 -311
  34. data/docs/tips_and_tricks.md +120 -70
  35. data/docs/workflows.md +99 -283
  36. data/lib/cmdx/.DS_Store +0 -0
  37. data/lib/cmdx/attribute.rb +229 -0
  38. data/lib/cmdx/attribute_registry.rb +94 -0
  39. data/lib/cmdx/attribute_value.rb +193 -0
  40. data/lib/cmdx/callback_registry.rb +69 -77
  41. data/lib/cmdx/chain.rb +56 -73
  42. data/lib/cmdx/coercion_registry.rb +52 -68
  43. data/lib/cmdx/coercions/array.rb +19 -18
  44. data/lib/cmdx/coercions/big_decimal.rb +20 -24
  45. data/lib/cmdx/coercions/boolean.rb +26 -25
  46. data/lib/cmdx/coercions/complex.rb +21 -22
  47. data/lib/cmdx/coercions/date.rb +25 -23
  48. data/lib/cmdx/coercions/date_time.rb +24 -25
  49. data/lib/cmdx/coercions/float.rb +25 -22
  50. data/lib/cmdx/coercions/hash.rb +31 -32
  51. data/lib/cmdx/coercions/integer.rb +30 -24
  52. data/lib/cmdx/coercions/rational.rb +29 -24
  53. data/lib/cmdx/coercions/string.rb +19 -22
  54. data/lib/cmdx/coercions/symbol.rb +37 -0
  55. data/lib/cmdx/coercions/time.rb +26 -25
  56. data/lib/cmdx/configuration.rb +49 -108
  57. data/lib/cmdx/context.rb +222 -44
  58. data/lib/cmdx/deprecator.rb +61 -0
  59. data/lib/cmdx/errors.rb +42 -252
  60. data/lib/cmdx/exceptions.rb +39 -0
  61. data/lib/cmdx/faults.rb +78 -39
  62. data/lib/cmdx/freezer.rb +51 -0
  63. data/lib/cmdx/identifier.rb +30 -0
  64. data/lib/cmdx/locale.rb +52 -0
  65. data/lib/cmdx/log_formatters/json.rb +21 -22
  66. data/lib/cmdx/log_formatters/key_value.rb +20 -22
  67. data/lib/cmdx/log_formatters/line.rb +15 -22
  68. data/lib/cmdx/log_formatters/logstash.rb +22 -23
  69. data/lib/cmdx/log_formatters/raw.rb +16 -22
  70. data/lib/cmdx/middleware_registry.rb +70 -74
  71. data/lib/cmdx/middlewares/correlate.rb +90 -54
  72. data/lib/cmdx/middlewares/runtime.rb +58 -0
  73. data/lib/cmdx/middlewares/timeout.rb +48 -68
  74. data/lib/cmdx/railtie.rb +12 -45
  75. data/lib/cmdx/result.rb +229 -314
  76. data/lib/cmdx/task.rb +194 -366
  77. data/lib/cmdx/utils/call.rb +49 -0
  78. data/lib/cmdx/utils/condition.rb +71 -0
  79. data/lib/cmdx/utils/format.rb +61 -0
  80. data/lib/cmdx/validator_registry.rb +63 -72
  81. data/lib/cmdx/validators/exclusion.rb +38 -67
  82. data/lib/cmdx/validators/format.rb +48 -49
  83. data/lib/cmdx/validators/inclusion.rb +43 -74
  84. data/lib/cmdx/validators/length.rb +91 -154
  85. data/lib/cmdx/validators/numeric.rb +87 -162
  86. data/lib/cmdx/validators/presence.rb +37 -50
  87. data/lib/cmdx/version.rb +1 -1
  88. data/lib/cmdx/worker.rb +178 -0
  89. data/lib/cmdx/workflow.rb +85 -81
  90. data/lib/cmdx.rb +19 -13
  91. data/lib/generators/cmdx/install_generator.rb +14 -13
  92. data/lib/generators/cmdx/task_generator.rb +25 -50
  93. data/lib/generators/cmdx/templates/install.rb +11 -46
  94. data/lib/generators/cmdx/templates/task.rb.tt +3 -2
  95. data/lib/locales/en.yml +18 -4
  96. data/src/cmdx-logo.png +0 -0
  97. metadata +32 -116
  98. data/docs/ai_prompts.md +0 -393
  99. data/docs/basics/call.md +0 -317
  100. data/docs/configuration.md +0 -344
  101. data/docs/parameters/coercions.md +0 -396
  102. data/docs/parameters/defaults.md +0 -335
  103. data/docs/parameters/definitions.md +0 -446
  104. data/docs/parameters/namespacing.md +0 -378
  105. data/docs/parameters/validations.md +0 -405
  106. data/docs/testing.md +0 -553
  107. data/lib/cmdx/callback.rb +0 -53
  108. data/lib/cmdx/chain_inspector.rb +0 -56
  109. data/lib/cmdx/chain_serializer.rb +0 -63
  110. data/lib/cmdx/coercion.rb +0 -57
  111. data/lib/cmdx/coercions/virtual.rb +0 -29
  112. data/lib/cmdx/core_ext/hash.rb +0 -83
  113. data/lib/cmdx/core_ext/module.rb +0 -98
  114. data/lib/cmdx/core_ext/object.rb +0 -125
  115. data/lib/cmdx/correlator.rb +0 -122
  116. data/lib/cmdx/error.rb +0 -67
  117. data/lib/cmdx/fault.rb +0 -140
  118. data/lib/cmdx/immutator.rb +0 -52
  119. data/lib/cmdx/lazy_struct.rb +0 -246
  120. data/lib/cmdx/log_formatters/pretty_json.rb +0 -40
  121. data/lib/cmdx/log_formatters/pretty_key_value.rb +0 -38
  122. data/lib/cmdx/log_formatters/pretty_line.rb +0 -41
  123. data/lib/cmdx/logger.rb +0 -49
  124. data/lib/cmdx/logger_ansi.rb +0 -68
  125. data/lib/cmdx/logger_serializer.rb +0 -116
  126. data/lib/cmdx/middleware.rb +0 -70
  127. data/lib/cmdx/parameter.rb +0 -312
  128. data/lib/cmdx/parameter_evaluator.rb +0 -231
  129. data/lib/cmdx/parameter_inspector.rb +0 -66
  130. data/lib/cmdx/parameter_registry.rb +0 -106
  131. data/lib/cmdx/parameter_serializer.rb +0 -59
  132. data/lib/cmdx/result_ansi.rb +0 -71
  133. data/lib/cmdx/result_inspector.rb +0 -71
  134. data/lib/cmdx/result_logger.rb +0 -59
  135. data/lib/cmdx/result_serializer.rb +0 -104
  136. data/lib/cmdx/rspec/matchers.rb +0 -28
  137. data/lib/cmdx/rspec/result_matchers/be_executed.rb +0 -42
  138. data/lib/cmdx/rspec/result_matchers/be_failed_task.rb +0 -94
  139. data/lib/cmdx/rspec/result_matchers/be_skipped_task.rb +0 -94
  140. data/lib/cmdx/rspec/result_matchers/be_state_matchers.rb +0 -59
  141. data/lib/cmdx/rspec/result_matchers/be_status_matchers.rb +0 -57
  142. data/lib/cmdx/rspec/result_matchers/be_successful_task.rb +0 -87
  143. data/lib/cmdx/rspec/result_matchers/have_bad_outcome.rb +0 -51
  144. data/lib/cmdx/rspec/result_matchers/have_caused_failure.rb +0 -58
  145. data/lib/cmdx/rspec/result_matchers/have_chain_index.rb +0 -59
  146. data/lib/cmdx/rspec/result_matchers/have_context.rb +0 -86
  147. data/lib/cmdx/rspec/result_matchers/have_empty_metadata.rb +0 -54
  148. data/lib/cmdx/rspec/result_matchers/have_good_outcome.rb +0 -52
  149. data/lib/cmdx/rspec/result_matchers/have_metadata.rb +0 -114
  150. data/lib/cmdx/rspec/result_matchers/have_preserved_context.rb +0 -66
  151. data/lib/cmdx/rspec/result_matchers/have_received_thrown_failure.rb +0 -64
  152. data/lib/cmdx/rspec/result_matchers/have_runtime.rb +0 -78
  153. data/lib/cmdx/rspec/result_matchers/have_thrown_failure.rb +0 -76
  154. data/lib/cmdx/rspec/task_matchers/be_well_formed_task.rb +0 -62
  155. data/lib/cmdx/rspec/task_matchers/have_callback.rb +0 -85
  156. data/lib/cmdx/rspec/task_matchers/have_cmd_setting.rb +0 -68
  157. data/lib/cmdx/rspec/task_matchers/have_executed_callbacks.rb +0 -92
  158. data/lib/cmdx/rspec/task_matchers/have_middleware.rb +0 -46
  159. data/lib/cmdx/rspec/task_matchers/have_parameter.rb +0 -181
  160. data/lib/cmdx/task_deprecator.rb +0 -58
  161. data/lib/cmdx/task_processor.rb +0 -246
  162. data/lib/cmdx/task_serializer.rb +0 -57
  163. data/lib/cmdx/utils/ansi_color.rb +0 -73
  164. data/lib/cmdx/utils/log_timestamp.rb +0 -36
  165. data/lib/cmdx/utils/monotonic_runtime.rb +0 -34
  166. data/lib/cmdx/utils/name_affix.rb +0 -52
  167. data/lib/cmdx/validator.rb +0 -57
  168. data/lib/generators/cmdx/templates/workflow.rb.tt +0 -7
  169. data/lib/generators/cmdx/workflow_generator.rb +0 -84
  170. data/lib/locales/ar.yml +0 -35
  171. data/lib/locales/cs.yml +0 -35
  172. data/lib/locales/da.yml +0 -35
  173. data/lib/locales/de.yml +0 -35
  174. data/lib/locales/el.yml +0 -35
  175. data/lib/locales/es.yml +0 -35
  176. data/lib/locales/fi.yml +0 -35
  177. data/lib/locales/fr.yml +0 -35
  178. data/lib/locales/he.yml +0 -35
  179. data/lib/locales/hi.yml +0 -35
  180. data/lib/locales/it.yml +0 -35
  181. data/lib/locales/ja.yml +0 -35
  182. data/lib/locales/ko.yml +0 -35
  183. data/lib/locales/nl.yml +0 -35
  184. data/lib/locales/no.yml +0 -35
  185. data/lib/locales/pl.yml +0 -35
  186. data/lib/locales/pt.yml +0 -35
  187. data/lib/locales/ru.yml +0 -35
  188. data/lib/locales/sv.yml +0 -35
  189. data/lib/locales/th.yml +0 -35
  190. data/lib/locales/tr.yml +0 -35
  191. data/lib/locales/vi.yml +0 -35
  192. data/lib/locales/zh.yml +0 -35
@@ -1,184 +1,141 @@
1
1
  # Interruptions - Halt
2
2
 
3
- Halting stops execution of a task with explicit intent signaling. Tasks provide two primary halt methods that control execution flow and result in different outcomes, each serving specific use cases in business logic.
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
- - [TLDR](#tldr)
8
- - [Skip (`skip!`)](#skip-skip)
9
- - [Fail (`fail!`)](#fail-fail)
7
+ - [Skipping](#skipping)
8
+ - [Failing](#failing)
10
9
  - [Metadata Enrichment](#metadata-enrichment)
11
10
  - [State Transitions](#state-transitions)
12
- - [Exception Behavior](#exception-behavior)
13
- - [Error Handling](#error-handling)
14
- - [The Reason Key](#the-reason-key)
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
- ## TLDR
16
+ ## Skipping
17
17
 
18
- ```ruby
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
- ### Basic Usage
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 ProcessOrderTask < CMDx::Task
48
- required :order_id, type: :integer
49
-
50
- def call
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
- # Skip if order already processed
54
- skip!(reason: "Order already processed") if context.order.processed?
29
+ # With a reason
30
+ skip!("Warehouse closed") unless Time.now.hour.between?(8, 18)
55
31
 
56
- # Skip if prerequisites not met
57
- skip!(reason: "Payment method required") unless context.order.payment_method
32
+ inventory = Inventory.find(context.inventory_id)
58
33
 
59
- # Continue with business logic
60
- context.order.process!
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
- ### Common Skip Scenarios
42
+ result = ProcessInventory.execute(inventory_id: 456)
66
43
 
67
- | Scenario | Example |
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
- ## Fail (`fail!`)
47
+ # Without a reason
48
+ result.reason #=> "no reason given"
75
49
 
76
- > [!IMPORTANT]
77
- > Use `fail!` when a task encounters an error that prevents successful completion. Failed tasks represent error conditions that need to be handled or corrected.
50
+ # With a reason
51
+ result.reason #=> "Warehouse closed"
52
+ ```
78
53
 
79
- The `fail!` method indicates that a task encountered an error condition that prevents successful completion. This represents controlled failure where the task explicitly determines that execution cannot continue.
54
+ ## Failing
80
55
 
81
- ### Basic Usage
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 ProcessPaymentTask < CMDx::Task
85
- required :payment_id, type: :integer
86
-
87
- def call
88
- context.payment = Payment.find(payment_id)
89
-
90
- # Fail on validation errors
91
- fail!(reason: "Payment amount must be positive") unless context.payment.amount > 0
92
-
93
- # Fail on business rule violations
94
- fail!(reason: "Insufficient funds", code: "INSUFFICIENT_FUNDS") unless sufficient_funds?
95
-
96
- # Continue with processing
97
- charge_payment
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
- private
77
+ result = ProcessRefund.execute(refund_id: 789)
101
78
 
102
- def sufficient_funds?
103
- context.payment.account.balance >= context.payment.amount
104
- end
105
- end
106
- ```
79
+ # Executed
80
+ result.status #=> "failed"
107
81
 
108
- ### Common Fail Scenarios
82
+ # Without a reason
83
+ result.reason #=> "no reason given"
109
84
 
110
- | Scenario | Example |
111
- |----------|---------|
112
- | **Validation errors** | `fail!(reason: "Invalid email format")` |
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 ProcessSubscriptionTask < CMDx::Task
125
- required :user_id, type: :integer
126
-
127
- def call
128
- context.user = User.find(user_id)
129
-
130
- if context.user.subscription_expired?
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 context.user.payment_method_valid?
103
+ unless license.renewal_eligible?
104
+ # With metadata
141
105
  fail!(
142
- reason: "Invalid payment method",
143
- user_id: context.user.id,
144
- payment_method_id: context.user.payment_method&.id,
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
- process_subscription
112
+ process_renewal
151
113
  end
152
114
  end
153
- ```
154
115
 
155
- ### Accessing Metadata
116
+ result = ProcessRenewal.execute(license_id: 567)
156
117
 
157
- ```ruby
158
- result = ProcessSubscriptionTask.call(user_id: 123)
159
-
160
- # Check result status
161
- result.skipped? #=> true
162
- result.failed? #=> false
118
+ # Without metadata
119
+ result.metadata #=> {}
163
120
 
164
- # Access metadata
165
- result.metadata[:reason] #=> "Subscription expired"
166
- result.metadata[:user_id] #=> 123
167
- result.metadata[:expired_at] #=> 2023-01-01 10:00:00 UTC
168
- result.metadata[:grace_period_ends] #=> 2023-01-08 10:00:00 UTC
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 Transition | Status | Outcome |
176
- |--------|------------------|--------|---------|
177
- | `skip!` | `executing` → `interrupted` | `skipped` | `good? = true`, `bad? = true` |
178
- | `fail!` | `executing` → `interrupted` | `failed` | `good? = false`, `bad? = true` |
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 = ProcessSubscriptionTask.call(user_id: 123)
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
- ## Exception Behavior
151
+ ## Execution Behavior
195
152
 
196
153
  Halt methods behave differently depending on the call method used:
197
154
 
198
- ### With `call` (Non-bang)
155
+ ### Non-bang execution
199
156
 
200
- Returns a result object without raising exceptions:
157
+ Returns result object without raising exceptions:
201
158
 
202
159
  ```ruby
203
- result = ProcessPaymentTask.call(payment_id: 123)
160
+ result = ProcessRefund.execute(refund_id: 789)
204
161
 
205
162
  case result.status
206
163
  when "success"
207
- puts "Payment processed: $#{result.context.payment.amount}"
164
+ puts "Refund processed: $#{result.context.refund.amount}"
208
165
  when "skipped"
209
- puts "Payment skipped: #{result.metadata[:reason]}"
166
+ puts "Refund skipped: #{result.reason}"
210
167
  when "failed"
211
- puts "Payment failed: #{result.metadata[:reason]}"
212
- handle_payment_error(result.metadata[:code])
168
+ puts "Refund failed: #{result.reason}"
169
+ handle_refund_error(result.metadata[:error_code])
213
170
  end
214
171
  ```
215
172
 
216
- ### With `call!` (Bang)
173
+ ### Bang execution
217
174
 
218
- > [!WARNING]
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 = ProcessPaymentTask.call!(payment_id: 123)
224
- puts "Success: Payment processed for $#{result.context.payment.amount}"
225
- rescue CMDx::Skipped => e
179
+ result = ProcessRefund.execute!(refund_id: 789)
180
+ puts "Success: Refund processed"
181
+ rescue CMDx::SkipFault => e
226
182
  puts "Skipped: #{e.message}"
227
- log_skip_event(e.context.payment_id, e.result.metadata)
228
- rescue CMDx::Failed => e
183
+ rescue CMDx::FailFault => e
229
184
  puts "Failed: #{e.message}"
230
- handle_payment_failure(e.result.metadata[:code])
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
- ## Error Handling
189
+ ## Best Practices
236
190
 
237
- ### Invalid Metadata
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!(reason: "User account suspended until manual review")
265
- fail!(reason: "Credit card declined by issuer", code: "CARD_DECLINED")
195
+ skip!("Document processing paused for compliance review")
196
+ fail!("File format not supported by processor", code: "FORMAT_UNSUPPORTED")
266
197
 
267
- # Acceptable: Other metadata without reason
268
- skip!(status: "redundant", processed_at: Time.current)
198
+ # Acceptable: Generic, non-specific reason
199
+ skip!("Paused")
200
+ fail!("Unsupported")
269
201
 
270
- # Fallback: Default message if no reason provided
271
- skip! # Exception message: "no reason given"
272
- fail! # Exception message: "no reason given"
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)