cmdx 1.1.0 → 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.
Files changed (87) hide show
  1. checksums.yaml +4 -4
  2. data/.cursor/prompts/docs.md +9 -0
  3. data/.cursor/prompts/rspec.md +13 -12
  4. data/.cursor/prompts/yardoc.md +11 -6
  5. data/CHANGELOG.md +13 -2
  6. data/README.md +1 -0
  7. data/docs/ai_prompts.md +269 -195
  8. data/docs/basics/call.md +124 -58
  9. data/docs/basics/chain.md +190 -160
  10. data/docs/basics/context.md +242 -154
  11. data/docs/basics/setup.md +302 -32
  12. data/docs/callbacks.md +390 -94
  13. data/docs/configuration.md +181 -65
  14. data/docs/deprecation.md +245 -0
  15. data/docs/getting_started.md +161 -39
  16. data/docs/internationalization.md +590 -70
  17. data/docs/interruptions/exceptions.md +135 -118
  18. data/docs/interruptions/faults.md +150 -125
  19. data/docs/interruptions/halt.md +134 -80
  20. data/docs/logging.md +181 -118
  21. data/docs/middlewares.md +150 -377
  22. data/docs/outcomes/result.md +140 -112
  23. data/docs/outcomes/states.md +134 -99
  24. data/docs/outcomes/statuses.md +204 -146
  25. data/docs/parameters/coercions.md +232 -281
  26. data/docs/parameters/defaults.md +224 -169
  27. data/docs/parameters/definitions.md +289 -141
  28. data/docs/parameters/namespacing.md +250 -161
  29. data/docs/parameters/validations.md +260 -133
  30. data/docs/testing.md +191 -197
  31. data/docs/workflows.md +143 -98
  32. data/lib/cmdx/callback.rb +23 -19
  33. data/lib/cmdx/callback_registry.rb +1 -3
  34. data/lib/cmdx/chain_inspector.rb +23 -23
  35. data/lib/cmdx/chain_serializer.rb +38 -19
  36. data/lib/cmdx/coercion.rb +20 -12
  37. data/lib/cmdx/coercion_registry.rb +51 -32
  38. data/lib/cmdx/configuration.rb +84 -31
  39. data/lib/cmdx/context.rb +32 -21
  40. data/lib/cmdx/core_ext/hash.rb +13 -13
  41. data/lib/cmdx/core_ext/module.rb +1 -1
  42. data/lib/cmdx/core_ext/object.rb +12 -12
  43. data/lib/cmdx/correlator.rb +60 -39
  44. data/lib/cmdx/errors.rb +105 -131
  45. data/lib/cmdx/fault.rb +66 -45
  46. data/lib/cmdx/immutator.rb +20 -21
  47. data/lib/cmdx/lazy_struct.rb +78 -70
  48. data/lib/cmdx/log_formatters/json.rb +1 -1
  49. data/lib/cmdx/log_formatters/key_value.rb +1 -1
  50. data/lib/cmdx/log_formatters/line.rb +1 -1
  51. data/lib/cmdx/log_formatters/logstash.rb +1 -1
  52. data/lib/cmdx/log_formatters/pretty_json.rb +1 -1
  53. data/lib/cmdx/log_formatters/pretty_key_value.rb +1 -1
  54. data/lib/cmdx/log_formatters/pretty_line.rb +1 -1
  55. data/lib/cmdx/log_formatters/raw.rb +2 -2
  56. data/lib/cmdx/logger.rb +19 -14
  57. data/lib/cmdx/logger_ansi.rb +33 -17
  58. data/lib/cmdx/logger_serializer.rb +85 -24
  59. data/lib/cmdx/middleware.rb +39 -21
  60. data/lib/cmdx/middleware_registry.rb +4 -3
  61. data/lib/cmdx/parameter.rb +151 -89
  62. data/lib/cmdx/parameter_inspector.rb +34 -21
  63. data/lib/cmdx/parameter_registry.rb +36 -30
  64. data/lib/cmdx/parameter_serializer.rb +21 -14
  65. data/lib/cmdx/result.rb +136 -135
  66. data/lib/cmdx/result_ansi.rb +31 -17
  67. data/lib/cmdx/result_inspector.rb +32 -27
  68. data/lib/cmdx/result_logger.rb +23 -14
  69. data/lib/cmdx/result_serializer.rb +65 -27
  70. data/lib/cmdx/task.rb +234 -113
  71. data/lib/cmdx/task_deprecator.rb +22 -25
  72. data/lib/cmdx/task_processor.rb +89 -88
  73. data/lib/cmdx/task_serializer.rb +27 -14
  74. data/lib/cmdx/utils/monotonic_runtime.rb +2 -4
  75. data/lib/cmdx/validator.rb +25 -16
  76. data/lib/cmdx/validator_registry.rb +53 -31
  77. data/lib/cmdx/validators/exclusion.rb +1 -1
  78. data/lib/cmdx/validators/format.rb +2 -2
  79. data/lib/cmdx/validators/inclusion.rb +2 -2
  80. data/lib/cmdx/validators/length.rb +2 -2
  81. data/lib/cmdx/validators/numeric.rb +3 -3
  82. data/lib/cmdx/validators/presence.rb +2 -2
  83. data/lib/cmdx/version.rb +1 -1
  84. data/lib/cmdx/workflow.rb +54 -33
  85. data/lib/generators/cmdx/task_generator.rb +6 -6
  86. data/lib/generators/cmdx/workflow_generator.rb +6 -6
  87. metadata +3 -1
data/docs/callbacks.md CHANGED
@@ -1,13 +1,10 @@
1
1
  # Callbacks
2
2
 
3
- Callbacks (callbacks) provide precise control over task execution lifecycle, running custom logic at
4
- specific transition points. Callback callables have access to the same context and result information
5
- as the `call` method, enabling rich integration patterns.
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.
6
4
 
7
5
  ## Table of Contents
8
6
 
9
7
  - [TLDR](#tldr)
10
- - [Overview](#overview)
11
8
  - [Callback Declaration](#callback-declaration)
12
9
  - [Callback Classes](#callback-classes)
13
10
  - [Available Callbacks](#available-callbacks)
@@ -18,82 +15,150 @@ as the `call` method, enabling rich integration patterns.
18
15
  - [Outcome Callbacks](#outcome-callbacks)
19
16
  - [Execution Order](#execution-order)
20
17
  - [Conditional Execution](#conditional-execution)
18
+ - [Error Handling](#error-handling)
21
19
  - [Callback Inheritance](#callback-inheritance)
22
20
 
23
21
  ## TLDR
24
22
 
25
- - **Purpose** - Execute custom logic at specific points in task lifecycle
26
- - **Declaration** - Use method names, procs, class instances, or blocks
27
- - **Callback types** - Validation, execution, state, status, and outcome callbacks
28
- - **Execution order** - Runs in precise lifecycle order (before_execution → validation → call → status → after_execution)
29
- - **Conditional** - Support `:if` and `:unless` options for conditional execution
30
- - **Inheritance** - Callbacks are inherited, perfect for global patterns
23
+ ```ruby
24
+ # Method name callbacks
25
+ after_validation :verify_order_data
26
+ on_success :send_notification
31
27
 
32
- > [!TIP]
33
- > Callbacks are inheritable, making them perfect for setting up global logic execution patterns like tracking markers, account plan checks, or logging standards.
28
+ # Proc/lambda callbacks
29
+ on_complete -> { send_telemetry_data }
34
30
 
35
- ## Overview
31
+ # Callback class instances
32
+ before_execution LoggingCallback.new(:debug)
36
33
 
37
- Callbacks can be declared in multiple ways: method names, procs/lambdas, Callback class instances, or blocks.
34
+ # Conditional execution
35
+ on_failed :alert_support, if: :critical_order?
36
+ after_execution :cleanup, unless: :preserve_data?
38
37
 
39
- ```ruby
40
- class ProcessOrderTask < CMDx::Task
41
- # Method name declaration
42
- after_validation :verify_order_data
38
+ # Multiple callbacks for same event
39
+ on_success :increment_counter, :send_notification
40
+ ```
43
41
 
44
- # Proc/lambda declaration
45
- on_complete -> { send_telemetry_data }
42
+ > [!IMPORTANT]
43
+ > Callbacks execute in declaration order (FIFO) and are inherited by subclasses, making them ideal for application-wide patterns.
44
+
45
+ ## Callback Declaration
46
46
 
47
- # Callback class declaration
48
- before_execution LoggingCallback.new(:debug)
49
- on_success NotificationCallback.new([:email, :slack])
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.
50
49
 
51
- # Multiple callbacks for same event
52
- on_success :increment_counter, :send_notification
50
+ ### Declaration Methods
53
51
 
54
- # Conditional execution
55
- on_failed :alert_support, if: :critical_order?
56
- after_execution :cleanup_resources, unless: :preserve_data?
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 }` |
57
58
 
58
- # Block declaration
59
- before_execution do
60
- context.processing_start = Time.now
59
+ ```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
+ )
61
80
  end
62
81
 
82
+ # Multiple callbacks
83
+ on_success :update_inventory, :send_confirmation, :log_success
84
+
63
85
  def call
64
- context.order = Order.find(order_id)
86
+ context.order = Order.find(context.order_id)
65
87
  context.order.process!
66
88
  end
67
89
 
68
90
  private
69
91
 
70
- def critical_order?
71
- context.order.value > 10_000
92
+ def load_order
93
+ context.order ||= Order.find(context.order_id)
72
94
  end
73
95
 
74
- def preserve_data?
75
- Rails.env.development?
96
+ def verify_inventory
97
+ raise "Insufficient inventory" unless context.order.items_available?
76
98
  end
77
99
  end
78
100
  ```
79
101
 
80
102
  ## Callback Classes
81
103
 
82
- For complex callback logic or reusable patterns, you can create Callback classes similar to Middleware classes. Callback classes inherit from `CMDx::Callback` and implement the `call(task, type)` method.
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)`.
83
106
 
84
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
125
+
85
126
  class NotificationCallback < CMDx::Callback
86
- def initialize(channels)
127
+ def initialize(channels:, template: nil)
87
128
  @channels = Array(channels)
129
+ @template = template
88
130
  end
89
131
 
90
132
  def call(task, type)
91
- return unless type == :on_success
133
+ return unless should_notify?(type)
92
134
 
93
135
  @channels.each do |channel|
94
- NotificationService.send(channel, "Task #{task.class.name} completed")
136
+ NotificationService.send(
137
+ channel: channel,
138
+ template: @template || default_template(type),
139
+ data: extract_notification_data(task)
140
+ )
95
141
  end
96
142
  end
143
+
144
+ private
145
+
146
+ def should_notify?(type)
147
+ %i[on_success on_failed].include?(type)
148
+ end
149
+
150
+ def default_template(type)
151
+ type == :on_success ? :task_success : :task_failure
152
+ end
153
+
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
+ }
161
+ end
97
162
  end
98
163
  ```
99
164
 
@@ -103,46 +168,137 @@ end
103
168
 
104
169
  Execute around parameter validation:
105
170
 
106
- - `before_validation` - Before parameter validation
107
- - `after_validation` - After successful parameter validation
171
+ | Callback | Timing | Description |
172
+ |----------|--------|-------------|
173
+ | `before_validation` | Before validation | Setup validation context |
174
+ | `after_validation` | After successful validation | Post-validation logic |
175
+
176
+ ```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
187
+
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
202
+ end
203
+ ```
108
204
 
109
205
  ### Execution Callbacks
110
206
 
111
207
  Execute around task logic:
112
208
 
113
- - `before_execution` - Before task logic begins
114
- - `after_execution` - After task logic completes (success or failure)
209
+ | Callback | Timing | Description |
210
+ |----------|--------|-------------|
211
+ | `before_execution` | Before `call` method | Setup and preparation |
212
+ | `after_execution` | After `call` completes | Cleanup and finalization |
213
+
214
+ ```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
232
+ end
233
+ end
234
+ ```
115
235
 
116
236
  ### State Callbacks
117
237
 
118
238
  Execute based on execution state:
119
239
 
120
- - `on_executing` - Task begins running
121
- - `on_complete` - Task completes successfully
122
- - `on_interrupted` - Task is halted (skip/failure)
123
- - `on_executed` - Task finishes (complete or interrupted)
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 |
124
246
 
125
247
  ### Status Callbacks
126
248
 
127
249
  Execute based on execution status:
128
250
 
129
- - `on_success` - Task succeeds
130
- - `on_skipped` - Task is skipped
131
- - `on_failed` - Task fails
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 |
132
256
 
133
257
  ### Outcome Callbacks
134
258
 
135
259
  Execute based on outcome classification:
136
260
 
137
- - `on_good` - Positive outcomes (success or skipped)
138
- - `on_bad` - Negative outcomes (skipped or failed)
261
+ | Callback | Outcomes | Description |
262
+ |----------|----------|-------------|
263
+ | `on_good` | Success or skipped | Positive outcomes |
264
+ | `on_bad` | Failed | Negative outcomes |
139
265
 
140
- ## Execution Order
266
+ ```ruby
267
+ class EmailCampaignTask < CMDx::Task
268
+ on_executing -> { Metrics.increment('campaigns.started') }
269
+ on_complete :track_completion
270
+ on_interrupted :handle_interruption
141
271
 
142
- Callbacks execute in precise order during task lifecycle:
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
291
+
292
+ def handle_interruption
293
+ Campaign.find(context.campaign_id).update!(status: :interrupted)
294
+ end
295
+ end
296
+ ```
297
+
298
+ ## Execution Order
143
299
 
144
300
  > [!IMPORTANT]
145
- > Multiple callbacks of the same type execute in declaration order (FIFO: first in, first out).
301
+ > Callbacks execute in precise lifecycle order. Multiple callbacks of the same type execute in declaration order (FIFO: first in, first out).
146
302
 
147
303
  ```ruby
148
304
  1. before_execution # Setup and preparation
@@ -157,94 +313,234 @@ Callbacks execute in precise order during task lifecycle:
157
313
  10. after_execution # Cleanup and finalization
158
314
  ```
159
315
 
160
- > [!IMPORTANT]
161
- > Multiple callbacks of the same type execute in declaration order (FIFO: first in, first out).
162
-
163
316
  ## Conditional Execution
164
317
 
165
- Callbacks support conditional execution through `:if` and `:unless` options:
318
+ > [!TIP]
319
+ > Use `:if` and `:unless` options for conditional callback execution. Conditions can be method names, procs, or strings.
166
320
 
167
- | Option | Description |
168
- | --------- | ----------- |
169
- | `:if` | Execute callback only if condition is truthy |
170
- | `:unless` | Execute callback only if condition is falsy |
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?` |
171
325
 
172
326
  ```ruby
173
- class ProcessPaymentTask < CMDx::Task
174
- # Method name condition
327
+ class ProcessOrderTask < CMDx::Task
328
+ # Method name conditions
175
329
  on_success :send_receipt, if: :email_enabled?
330
+ on_failed :retry_payment, unless: :max_retries_reached?
176
331
 
177
- # Proc condition
178
- on_failure :retry_payment, if: -> { retry_count < 3 }
332
+ # Proc conditions
333
+ after_execution :log_metrics, if: -> { Rails.env.production? }
334
+ on_success :expensive_operation, unless: -> { SystemStatus.overloaded? }
179
335
 
180
- # String condition (evaluated as method)
181
- after_execution :log_metrics, unless: "Rails.env.test?"
336
+ # String conditions (evaluated as methods)
337
+ on_complete :update_analytics, if: "tracking_enabled?"
182
338
 
183
339
  # Multiple conditions
184
- on_complete :expensive_operation, if: :production_env?, unless: :maintenance_mode?
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
185
348
 
186
349
  private
187
350
 
188
351
  def email_enabled?
189
- context.user.email_notifications?
352
+ context.user.email_notifications? && !context.user.email.blank?
190
353
  end
191
354
 
192
- def production_env?
193
- Rails.env.production?
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
194
361
  end
195
362
 
196
- def maintenance_mode?
197
- SystemStatus.maintenance_mode?
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?
371
+ end
372
+ end
373
+ ```
374
+
375
+ ## Error Handling
376
+
377
+ > [!WARNING]
378
+ > Callback errors can interrupt task execution. Use proper error handling and consider callback isolation for non-critical operations.
379
+
380
+ ### Callback Error Behavior
381
+
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
387
+
388
+ def call
389
+ ProcessingService.handle(context.data)
390
+ end
391
+
392
+ private
393
+
394
+ def critical_setup
395
+ # Critical callback - let errors bubble up
396
+ context.processor = ProcessorService.initialize_secure_processor
397
+ end
398
+
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
413
+ end
414
+ end
415
+ ```
416
+
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
425
+
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
438
+
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)
198
451
  end
199
452
  end
200
453
  ```
201
454
 
202
455
  ## Callback Inheritance
203
456
 
204
- Callbacks are inherited from parent classes, enabling application-wide patterns:
457
+ > [!NOTE]
458
+ > Callbacks are inherited from parent classes, enabling application-wide patterns. Child classes can add additional callbacks or override inherited behavior.
205
459
 
206
460
  ```ruby
207
461
  class ApplicationTask < CMDx::Task
208
- before_execution :log_task_start # All tasks get execution logging
209
- after_execution :log_task_end # All tasks get completion logging
210
- on_failed :report_failure # All tasks get error reporting
211
- on_success :track_success_metrics # All tasks get success tracking
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
212
472
 
213
473
  private
214
474
 
215
475
  def log_task_start
216
- Rails.logger.info "Starting #{self.class.name}"
476
+ Rails.logger.info "Starting #{self.class.name} with context: #{context.to_h.except(:sensitive_data)}"
217
477
  end
218
478
 
219
479
  def log_task_end
220
- Rails.logger.info "Finished #{self.class.name} in #{result.runtime}s"
480
+ Rails.logger.info "Finished #{self.class.name} in #{result.runtime}ms with status: #{result.status}"
221
481
  end
222
482
 
223
483
  def report_failure
224
- ErrorReporter.notify(result.metadata)
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
+ )
225
490
  end
226
491
 
227
492
  def track_success_metrics
228
493
  Metrics.increment("task.#{self.class.name.underscore}.success")
229
494
  end
495
+
496
+ def track_execution_metrics
497
+ Metrics.histogram("task.#{self.class.name.underscore}.runtime", result.runtime)
498
+ end
230
499
  end
231
500
 
232
- class ProcessOrderTask < ApplicationTask
233
- before_validation :load_order # Specific to order processing
234
- on_success :send_confirmation # Domain-specific success action
235
- on_failed :refund_payment, if: :payment_captured? # Order-specific failure handling
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?
236
508
 
237
509
  def call
238
- # Inherits all ApplicationTask callbacks plus order-specific ones
239
- context.order.process!
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
+ )
240
539
  end
241
540
  end
242
541
  ```
243
542
 
244
- > [!TIP]
245
- > Callbacks are inherited by subclasses, making them ideal for setting up global lifecycle patterns across all tasks in your application.
246
-
247
543
  ---
248
544
 
249
545
  - **Prev:** [Parameters - Defaults](parameters/defaults.md)
250
- - **Prev:** [Middlewares](middlewares.md)
546
+ - **Next:** [Middlewares](middlewares.md)