cmdx 1.1.2 → 1.5.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.
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 +55 -1
  8. data/.irbrc +6 -0
  9. data/.rubocop.yml +29 -18
  10. data/CHANGELOG.md +11 -132
  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 +101 -162
  85. data/lib/cmdx/validators/numeric.rb +95 -170
  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
data/docs/callbacks.md CHANGED
@@ -1,546 +1,168 @@
1
1
  # Callbacks
2
2
 
3
- Callbacks provide precise control over task execution lifecycle, running custom logic at specific transition points. Callback callables have access to the same context and result information as the `call` method, enabling rich integration patterns.
4
-
5
- ## Table of Contents
6
-
7
- - [TLDR](#tldr)
8
- - [Callback Declaration](#callback-declaration)
9
- - [Callback Classes](#callback-classes)
10
- - [Available Callbacks](#available-callbacks)
11
- - [Validation Callbacks](#validation-callbacks)
12
- - [Execution Callbacks](#execution-callbacks)
13
- - [State Callbacks](#state-callbacks)
14
- - [Status Callbacks](#status-callbacks)
15
- - [Outcome Callbacks](#outcome-callbacks)
16
- - [Execution Order](#execution-order)
17
- - [Conditional Execution](#conditional-execution)
18
- - [Error Handling](#error-handling)
19
- - [Callback Inheritance](#callback-inheritance)
20
-
21
- ## TLDR
22
-
23
- ```ruby
24
- # Method name callbacks
25
- after_validation :verify_order_data
26
- on_success :send_notification
27
-
28
- # Proc/lambda callbacks
29
- on_complete -> { send_telemetry_data }
30
-
31
- # Callback class instances
32
- before_execution LoggingCallback.new(:debug)
33
-
34
- # Conditional execution
35
- on_failed :alert_support, if: :critical_order?
36
- after_execution :cleanup, unless: :preserve_data?
37
-
38
- # Multiple callbacks for same event
39
- on_success :increment_counter, :send_notification
40
- ```
3
+ Callbacks provide precise control over task execution lifecycle, running custom logic at specific transition points. Callback callables have access to the same context and result information as the `execute` method, enabling rich integration patterns.
41
4
 
42
5
  > [!IMPORTANT]
43
- > Callbacks execute in declaration order (FIFO) and are inherited by subclasses, making them ideal for application-wide patterns.
6
+ > Callbacks execute in the order they are declared within each hook type. Multiple callbacks of the same type execute in declaration order (FIFO: first in, first out).
44
7
 
45
- ## Callback Declaration
8
+ ## Table of Contents
46
9
 
47
- > [!NOTE]
48
- > Callbacks can be declared using method names, procs/lambdas, Callback class instances, or blocks. All forms have access to the task's context and result.
10
+ - [Available Callbacks](#available-callbacks)
11
+ - [Declarations](#declarations)
12
+ - [Symbol References](#symbol-references)
13
+ - [Proc or Lambda](#proc-or-lambda)
14
+ - [Class or Module](#class-or-module)
15
+ - [Conditional Execution](#conditional-execution)
16
+ - [Callback Removal](#callback-removal)
49
17
 
50
- ### Declaration Methods
18
+ ## Available Callbacks
51
19
 
52
- | Method | Description | Example |
53
- |--------|-------------|---------|
54
- | Method name | References instance method | `on_success :send_email` |
55
- | Proc/Lambda | Inline callable | `on_failed -> { alert_team }` |
56
- | Callback class | Reusable class instance | `before_execution LoggerCallback.new` |
57
- | Block | Inline block | `on_success { increment_counter }` |
20
+ Callbacks execute in precise lifecycle order. Here is the complete execution sequence:
58
21
 
59
22
  ```ruby
60
- class ProcessOrderTask < CMDx::Task
61
- # Method name
62
- before_validation :load_order
63
- after_validation :verify_inventory
64
-
65
- # Proc/lambda
66
- on_executing -> { context.start_time = Time.current }
67
- on_complete lambda { Metrics.increment('orders.processed') }
68
-
69
- # Callback class
70
- before_execution AuditCallback.new(action: :process_order)
71
- on_success NotificationCallback.new(channels: [:email, :slack])
72
-
73
- # Block
74
- on_failed do
75
- ErrorReporter.notify(
76
- error: result.metadata[:error],
77
- order_id: context.order_id,
78
- user_id: context.user_id
79
- )
80
- end
23
+ 1. before_validation # Pre-validation setup
24
+ 2. before_execution # Setup and preparation
81
25
 
82
- # Multiple callbacks
83
- on_success :update_inventory, :send_confirmation, :log_success
84
-
85
- def call
86
- context.order = Order.find(context.order_id)
87
- context.order.process!
88
- end
89
-
90
- private
26
+ # --- Task#work executed ---
91
27
 
92
- def load_order
93
- context.order ||= Order.find(context.order_id)
94
- end
95
-
96
- def verify_inventory
97
- raise "Insufficient inventory" unless context.order.items_available?
98
- end
99
- end
28
+ 3. on_[complete|interrupted] # Based on execution state
29
+ 4. on_executed # Task finished (any outcome)
30
+ 5. on_[success|skipped|failed] # Based on execution status
31
+ 6. on_[good|bad] # Based on outcome classification
100
32
  ```
101
33
 
102
- ## Callback Classes
34
+ ## Declarations
103
35
 
104
- > [!TIP]
105
- > Create reusable Callback classes for complex logic or cross-cutting concerns. Callback classes inherit from `CMDx::Callback` and implement `call(task, type)`.
36
+ ### Symbol References
106
37
 
107
- ```ruby
108
- class AuditCallback < CMDx::Callback
109
- def initialize(action:, level: :info)
110
- @action = action
111
- @level = level
112
- end
113
-
114
- def call(task, type)
115
- AuditLogger.log(
116
- level: @level,
117
- action: @action,
118
- task: task.class.name,
119
- callback_type: type,
120
- user_id: task.context.current_user&.id,
121
- timestamp: Time.current
122
- )
123
- end
124
- end
38
+ Reference instance methods by symbol for simple callback logic:
125
39
 
126
- class NotificationCallback < CMDx::Callback
127
- def initialize(channels:, template: nil)
128
- @channels = Array(channels)
129
- @template = template
130
- end
40
+ ```ruby
41
+ class ProcessBooking < CMDx::Task
42
+ before_execution :find_reservation
131
43
 
132
- def call(task, type)
133
- return unless should_notify?(type)
44
+ # Batch declarations (works for any type)
45
+ on_complete :notify_guest, :update_availability
134
46
 
135
- @channels.each do |channel|
136
- NotificationService.send(
137
- channel: channel,
138
- template: @template || default_template(type),
139
- data: extract_notification_data(task)
140
- )
141
- end
47
+ def work
48
+ # Your logic here...
142
49
  end
143
50
 
144
51
  private
145
52
 
146
- def should_notify?(type)
147
- %i[on_success on_failed].include?(type)
53
+ def find_reservation
54
+ @reservation ||= Reservation.find(context.reservation_id)
148
55
  end
149
56
 
150
- def default_template(type)
151
- type == :on_success ? :task_success : :task_failure
57
+ def notify_guest
58
+ GuestNotifier.call(context.guest, result)
152
59
  end
153
60
 
154
- def extract_notification_data(task)
155
- {
156
- task_name: task.class.name,
157
- status: task.result.status,
158
- runtime: task.result.runtime,
159
- context: task.context.to_h.except(:sensitive_data)
160
- }
61
+ def update_availability
62
+ AvailabilityService.update(context.room_ids, result)
161
63
  end
162
64
  end
163
65
  ```
164
66
 
165
- ## Available Callbacks
166
-
167
- ### Validation Callbacks
67
+ ### Proc or Lambda
168
68
 
169
- Execute around parameter validation:
170
-
171
- | Callback | Timing | Description |
172
- |----------|--------|-------------|
173
- | `before_validation` | Before validation | Setup validation context |
174
- | `after_validation` | After successful validation | Post-validation logic |
69
+ Use anonymous functions for inline callback logic:
175
70
 
176
71
  ```ruby
177
- class CreateUserTask < CMDx::Task
178
- before_validation :normalize_email
179
- after_validation :check_user_limits
180
-
181
- required :email, type: :string
182
- required :plan, type: :string
183
-
184
- def call
185
- User.create!(email: email, plan: plan)
186
- end
72
+ class ProcessBooking < CMDx::Task
73
+ # Proc
74
+ on_interrupted proc { |task| ReservationSystem.pause! }
187
75
 
188
- private
189
-
190
- def normalize_email
191
- context.email = email.downcase.strip
192
- end
193
-
194
- def check_user_limits
195
- current_users = User.where(plan: plan).count
196
- plan_limit = Plan.find_by(name: plan).user_limit
197
-
198
- if current_users >= plan_limit
199
- throw(:skip, reason: "Plan user limit reached")
200
- end
201
- end
76
+ # Lambda
77
+ on_complete -> { ReservationSystem.resume! }
202
78
  end
203
79
  ```
204
80
 
205
- ### Execution Callbacks
81
+ ### Class or Module
206
82
 
207
- Execute around task logic:
208
-
209
- | Callback | Timing | Description |
210
- |----------|--------|-------------|
211
- | `before_execution` | Before `call` method | Setup and preparation |
212
- | `after_execution` | After `call` completes | Cleanup and finalization |
83
+ Implement reusable callback logic in dedicated classes:
213
84
 
214
85
  ```ruby
215
- class ProcessPaymentTask < CMDx::Task
216
- before_execution :acquire_payment_lock
217
- after_execution :release_payment_lock
218
-
219
- def call
220
- Payment.process!(context.payment_data)
221
- end
222
-
223
- private
224
-
225
- def acquire_payment_lock
226
- context.lock_key = "payment:#{context.payment_id}"
227
- Redis.current.set(context.lock_key, "locked", ex: 300)
228
- end
229
-
230
- def release_payment_lock
231
- Redis.current.del(context.lock_key) if context.lock_key
86
+ class BookingConfirmationCallback
87
+ def call(task)
88
+ if task.result.success?
89
+ MessagingApi.send_confirmation(task.context.guest)
90
+ else
91
+ MessagingApi.send_issue_alert(task.context.manager)
92
+ end
232
93
  end
233
94
  end
234
- ```
235
95
 
236
- ### State Callbacks
237
-
238
- Execute based on execution state:
239
-
240
- | Callback | Condition | Description |
241
- |----------|-----------|-------------|
242
- | `on_executing` | Task begins running | Track execution start |
243
- | `on_complete` | Task completes successfully | Handle successful completion |
244
- | `on_interrupted` | Task is halted (skip/failure) | Handle interruptions |
245
- | `on_executed` | Task finishes (any outcome) | Post-execution logic |
246
-
247
- ### Status Callbacks
248
-
249
- Execute based on execution status:
250
-
251
- | Callback | Status | Description |
252
- |----------|--------|-------------|
253
- | `on_success` | Task succeeds | Handle success |
254
- | `on_skipped` | Task is skipped | Handle skips |
255
- | `on_failed` | Task fails | Handle failures |
256
-
257
- ### Outcome Callbacks
258
-
259
- Execute based on outcome classification:
260
-
261
- | Callback | Outcomes | Description |
262
- |----------|----------|-------------|
263
- | `on_good` | Success or skipped | Positive outcomes |
264
- | `on_bad` | Failed | Negative outcomes |
265
-
266
- ```ruby
267
- class EmailCampaignTask < CMDx::Task
268
- on_executing -> { Metrics.increment('campaigns.started') }
269
- on_complete :track_completion
270
- on_interrupted :handle_interruption
271
-
272
- on_success :schedule_followup
273
- on_skipped :log_skip_reason
274
- on_failed :alert_marketing_team
275
-
276
- on_good -> { Metrics.increment('campaigns.positive_outcome') }
277
- on_bad :create_incident_ticket
278
-
279
- def call
280
- EmailService.send_campaign(context.campaign_data)
281
- end
282
-
283
- private
284
-
285
- def track_completion
286
- Campaign.find(context.campaign_id).update!(
287
- sent_at: Time.current,
288
- recipient_count: context.recipients.size
289
- )
290
- end
96
+ class ProcessBooking < CMDx::Task
97
+ # Class or Module
98
+ on_success BookingConfirmationCallback
291
99
 
292
- def handle_interruption
293
- Campaign.find(context.campaign_id).update!(status: :interrupted)
294
- end
100
+ # Instance
101
+ on_interrupted BookingConfirmationCallback.new
295
102
  end
296
103
  ```
297
104
 
298
- ## Execution Order
299
-
300
- > [!IMPORTANT]
301
- > Callbacks execute in precise lifecycle order. Multiple callbacks of the same type execute in declaration order (FIFO: first in, first out).
302
-
303
- ```ruby
304
- 1. before_execution # Setup and preparation
305
- 2. on_executing # Task begins running
306
- 3. before_validation # Pre-validation setup
307
- 4. after_validation # Post-validation logic
308
- 5. [call method] # Your business logic
309
- 6. on_[complete|interrupted] # Based on execution state
310
- 7. on_executed # Task finished (any outcome)
311
- 8. on_[success|skipped|failed] # Based on execution status
312
- 9. on_[good|bad] # Based on outcome classification
313
- 10. after_execution # Cleanup and finalization
314
- ```
315
-
316
- ## Conditional Execution
317
-
318
- > [!TIP]
319
- > Use `:if` and `:unless` options for conditional callback execution. Conditions can be method names, procs, or strings.
105
+ ### Conditional Execution
320
106
 
321
- | Option | Description | Example |
322
- |--------|-------------|---------|
323
- | `:if` | Execute if condition is truthy | `if: :production_env?` |
324
- | `:unless` | Execute if condition is falsy | `unless: :maintenance_mode?` |
107
+ Control callback execution with conditional logic:
325
108
 
326
109
  ```ruby
327
- class ProcessOrderTask < CMDx::Task
328
- # Method name conditions
329
- on_success :send_receipt, if: :email_enabled?
330
- on_failed :retry_payment, unless: :max_retries_reached?
331
-
332
- # Proc conditions
333
- after_execution :log_metrics, if: -> { Rails.env.production? }
334
- on_success :expensive_operation, unless: -> { SystemStatus.overloaded? }
335
-
336
- # String conditions (evaluated as methods)
337
- on_complete :update_analytics, if: "tracking_enabled?"
338
-
339
- # Multiple conditions
340
- on_failed :escalate_to_support, if: :critical_order?, unless: :business_hours?
341
-
342
- # Complex conditional logic
343
- on_success :trigger_automation, if: :automation_conditions_met?
344
-
345
- def call
346
- Order.process!(context.order_data)
347
- end
348
-
349
- private
350
-
351
- def email_enabled?
352
- context.user.email_notifications? && !context.user.email.blank?
353
- end
354
-
355
- def max_retries_reached?
356
- context.retry_count >= 3
357
- end
358
-
359
- def critical_order?
360
- context.order_value > 10_000 || context.priority == :high
361
- end
362
-
363
- def business_hours?
364
- Time.current.hour.between?(9, 17) && Time.current.weekday?
365
- end
366
-
367
- def automation_conditions_met?
368
- context.order_type == :subscription &&
369
- context.user.plan.automation_enabled? &&
370
- !SystemStatus.maintenance_mode?
110
+ class MessagingPermissionCheck
111
+ def call(task)
112
+ task.context.guest.can?(:receive_messages)
371
113
  end
372
114
  end
373
- ```
374
115
 
375
- ## Error Handling
116
+ class ProcessBooking < CMDx::Task
117
+ # If and/or Unless
118
+ before_execution :notify_guest, if: :messaging_enabled?, unless: :messaging_blocked?
376
119
 
377
- > [!WARNING]
378
- > Callback errors can interrupt task execution. Use proper error handling and consider callback isolation for non-critical operations.
120
+ # Proc
121
+ on_failure :increment_failure, if: ->(task) { Rails.env.production? && task.class.name.include?("Legacy") }
379
122
 
380
- ### Callback Error Behavior
123
+ # Lambda
124
+ on_success :ping_housekeeping, if: proc { |task| task.context.rooms_need_cleaning? }
381
125
 
382
- ```ruby
383
- class ProcessDataTask < CMDx::Task
384
- before_execution :critical_setup # Error stops execution
385
- on_success :send_notification # Error stops callback chain
386
- after_execution :cleanup_resources # Always runs
126
+ # Class or Module
127
+ on_complete :send_confirmation, unless: MessagingPermissionCheck
128
+
129
+ # Instance
130
+ on_complete :send_confirmation, if: MessagingPermissionCheck.new
387
131
 
388
- def call
389
- ProcessingService.handle(context.data)
132
+ def work
133
+ # Your logic here...
390
134
  end
391
135
 
392
136
  private
393
137
 
394
- def critical_setup
395
- # Critical callback - let errors bubble up
396
- context.processor = ProcessorService.initialize_secure_processor
138
+ def messaging_enabled?
139
+ context.guest.messaging_preference.present?
397
140
  end
398
141
 
399
- def send_notification
400
- # Non-critical callback - handle errors gracefully
401
- NotificationService.send(context.notification_data)
402
- rescue NotificationService::Error => e
403
- Rails.logger.warn "Notification failed: #{e.message}"
404
- # Don't re-raise - allow other callbacks to continue
405
- end
406
-
407
- def cleanup_resources
408
- # Cleanup callback - always handle errors
409
- context.processor&.cleanup
410
- rescue => e
411
- Rails.logger.error "Cleanup failed: #{e.message}"
412
- # Log but don't re-raise
142
+ def messaging_blocked?
143
+ context.guest.communication_status == :blocked
413
144
  end
414
145
  end
415
146
  ```
416
147
 
417
- ### Isolating Non-Critical Callbacks
418
-
419
- ```ruby
420
- class ResilientCallback < CMDx::Callback
421
- def initialize(callback_proc, isolate: false)
422
- @callback_proc = callback_proc
423
- @isolate = isolate
424
- end
148
+ ## Callback Removal
425
149
 
426
- def call(task, type)
427
- if @isolate
428
- begin
429
- @callback_proc.call(task, type)
430
- rescue => e
431
- Rails.logger.warn "Isolated callback failed: #{e.message}"
432
- end
433
- else
434
- @callback_proc.call(task, type)
435
- end
436
- end
437
- end
150
+ Remove callbacks at runtime for dynamic behavior control:
438
151
 
439
- class ProcessOrderTask < CMDx::Task
440
- # Critical callback
441
- before_execution :validate_payment_method
442
-
443
- # Isolated non-critical callback
444
- on_success ResilientCallback.new(
445
- -> (task, type) { AnalyticsService.track_order(task.context.order_id) },
446
- isolate: true
447
- )
448
-
449
- def call
450
- Order.process!(context.order_data)
451
- end
452
- end
453
- ```
454
-
455
- ## Callback Inheritance
456
-
457
- > [!NOTE]
458
- > Callbacks are inherited from parent classes, enabling application-wide patterns. Child classes can add additional callbacks or override inherited behavior.
152
+ > [!IMPORTANT]
153
+ > Only one removal operation is allowed per `deregister` call. Multiple removals require separate calls.
459
154
 
460
155
  ```ruby
461
- class ApplicationTask < CMDx::Task
462
- # Global logging
463
- before_execution :log_task_start
464
- after_execution :log_task_end
465
-
466
- # Global error handling
467
- on_failed :report_failure
468
-
469
- # Global metrics
470
- on_success :track_success_metrics
471
- on_executed :track_execution_metrics
472
-
473
- private
474
-
475
- def log_task_start
476
- Rails.logger.info "Starting #{self.class.name} with context: #{context.to_h.except(:sensitive_data)}"
477
- end
478
-
479
- def log_task_end
480
- Rails.logger.info "Finished #{self.class.name} in #{result.runtime}ms with status: #{result.status}"
481
- end
482
-
483
- def report_failure
484
- ErrorReporter.notify(
485
- task: self.class.name,
486
- error: result.metadata[:reason],
487
- context: context.to_h.except(:sensitive_data),
488
- backtrace: result.metadata[:backtrace]
489
- )
490
- end
156
+ class ProcessBooking < CMDx::Task
157
+ # Symbol
158
+ deregister :callback, :before_execution, :notify_guest
491
159
 
492
- def track_success_metrics
493
- Metrics.increment("task.#{self.class.name.underscore}.success")
494
- end
495
-
496
- def track_execution_metrics
497
- Metrics.histogram("task.#{self.class.name.underscore}.runtime", result.runtime)
498
- end
499
- end
500
-
501
- class ProcessPaymentTask < ApplicationTask
502
- # Inherits all ApplicationTask callbacks
503
- # Plus payment-specific callbacks
504
-
505
- before_validation :load_payment_method
506
- on_success :send_receipt
507
- on_failed :refund_payment, if: :payment_captured?
508
-
509
- def call
510
- # Inherits global logging, error handling, and metrics
511
- # Plus payment-specific behavior
512
- PaymentProcessor.charge(context.payment_data)
513
- end
514
-
515
- private
516
-
517
- def load_payment_method
518
- context.payment_method = PaymentMethod.find(context.payment_method_id)
519
- end
520
-
521
- def send_receipt
522
- ReceiptService.send(
523
- user: context.user,
524
- payment: context.payment,
525
- template: :payment_success
526
- )
527
- end
528
-
529
- def payment_captured?
530
- context.payment&.status == :captured
531
- end
532
-
533
- def refund_payment
534
- RefundService.process(
535
- payment: context.payment,
536
- reason: :task_failure,
537
- amount: context.payment.amount
538
- )
539
- end
160
+ # Class or Module (no instances)
161
+ deregister :callback, :on_complete, BookingConfirmationCallback
540
162
  end
541
163
  ```
542
164
 
543
165
  ---
544
166
 
545
- - **Prev:** [Parameters - Defaults](parameters/defaults.md)
167
+ - **Prev:** [Attributes - Defaults](attributes/defaults.md)
546
168
  - **Next:** [Middlewares](middlewares.md)