cmdx 1.12.0 → 1.14.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 (67) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +88 -71
  3. data/LICENSE.txt +3 -20
  4. data/README.md +8 -7
  5. data/lib/cmdx/attribute.rb +21 -5
  6. data/lib/cmdx/chain.rb +18 -4
  7. data/lib/cmdx/context.rb +18 -0
  8. data/lib/cmdx/executor.rb +35 -30
  9. data/lib/cmdx/result.rb +45 -2
  10. data/lib/cmdx/task.rb +22 -1
  11. data/lib/cmdx/version.rb +1 -1
  12. data/mkdocs.yml +67 -37
  13. metadata +3 -57
  14. data/.cursor/prompts/docs.md +0 -12
  15. data/.cursor/prompts/llms.md +0 -8
  16. data/.cursor/prompts/rspec.md +0 -24
  17. data/.cursor/prompts/yardoc.md +0 -15
  18. data/.cursor/rules/cursor-instructions.mdc +0 -68
  19. data/.irbrc +0 -18
  20. data/.rspec +0 -4
  21. data/.rubocop.yml +0 -95
  22. data/.ruby-version +0 -1
  23. data/.yard-lint.yml +0 -174
  24. data/.yardopts +0 -7
  25. data/docs/.DS_Store +0 -0
  26. data/docs/assets/favicon.ico +0 -0
  27. data/docs/assets/favicon.svg +0 -1
  28. data/docs/attributes/coercions.md +0 -155
  29. data/docs/attributes/defaults.md +0 -77
  30. data/docs/attributes/definitions.md +0 -283
  31. data/docs/attributes/naming.md +0 -68
  32. data/docs/attributes/transformations.md +0 -63
  33. data/docs/attributes/validations.md +0 -336
  34. data/docs/basics/chain.md +0 -108
  35. data/docs/basics/context.md +0 -121
  36. data/docs/basics/execution.md +0 -96
  37. data/docs/basics/setup.md +0 -84
  38. data/docs/callbacks.md +0 -157
  39. data/docs/configuration.md +0 -314
  40. data/docs/deprecation.md +0 -145
  41. data/docs/getting_started.md +0 -126
  42. data/docs/index.md +0 -134
  43. data/docs/internationalization.md +0 -126
  44. data/docs/interruptions/exceptions.md +0 -52
  45. data/docs/interruptions/faults.md +0 -169
  46. data/docs/interruptions/halt.md +0 -216
  47. data/docs/logging.md +0 -94
  48. data/docs/middlewares.md +0 -191
  49. data/docs/outcomes/result.md +0 -194
  50. data/docs/outcomes/states.md +0 -66
  51. data/docs/outcomes/statuses.md +0 -65
  52. data/docs/retries.md +0 -121
  53. data/docs/stylesheets/extra.css +0 -42
  54. data/docs/tips_and_tricks.md +0 -157
  55. data/docs/workflows.md +0 -226
  56. data/examples/active_record_database_transaction.md +0 -27
  57. data/examples/active_record_query_tagging.md +0 -46
  58. data/examples/flipper_feature_flags.md +0 -50
  59. data/examples/paper_trail_whatdunnit.md +0 -39
  60. data/examples/redis_idempotency.md +0 -71
  61. data/examples/sentry_error_tracking.md +0 -46
  62. data/examples/sidekiq_async_execution.md +0 -29
  63. data/examples/stoplight_circuit_breaker.md +0 -36
  64. data/src/cmdx-dark-logo.png +0 -0
  65. data/src/cmdx-favicon.svg +0 -1
  66. data/src/cmdx-light-logo.png +0 -0
  67. data/src/cmdx-logo.svg +0 -1
@@ -1,169 +0,0 @@
1
- # Interruptions - Faults
2
-
3
- Faults are exceptions raised by `execute!` when tasks halt. They carry rich context about execution state, enabling sophisticated error handling patterns.
4
-
5
- ## Fault Types
6
-
7
- | Type | Triggered By | Use Case |
8
- |------|--------------|----------|
9
- | `CMDx::Fault` | Base class | Catch-all for any interruption |
10
- | `CMDx::SkipFault` | `skip!` method | Optional processing, early returns |
11
- | `CMDx::FailFault` | `fail!` method | Validation errors, processing failures |
12
-
13
- !!! warning "Important"
14
-
15
- All faults inherit from `CMDx::Fault` and expose result, task, context, and chain data.
16
-
17
- ## Fault Handling
18
-
19
- ```ruby
20
- begin
21
- ProcessTicket.execute!(ticket_id: 456)
22
- rescue CMDx::SkipFault => e
23
- logger.info "Ticket processing skipped: #{e.message}"
24
- schedule_retry(e.context.ticket_id)
25
- rescue CMDx::FailFault => e
26
- logger.error "Ticket processing failed: #{e.message}"
27
- notify_admin(e.context.assigned_agent, e.result.metadata[:error_code])
28
- rescue CMDx::Fault => e
29
- logger.warn "Ticket processing interrupted: #{e.message}"
30
- rollback_changes
31
- end
32
- ```
33
-
34
- ## Data Access
35
-
36
- Access rich execution data from fault exceptions:
37
-
38
- ```ruby
39
- begin
40
- LicenseActivation.execute!(license_key: key, machine_id: machine)
41
- rescue CMDx::Fault => e
42
- # Result information
43
- e.result.state #=> "interrupted"
44
- e.result.status #=> "failed" or "skipped"
45
- e.result.reason #=> "License key already activated"
46
-
47
- # Task information
48
- e.task.class #=> <LicenseActivation>
49
- e.task.id #=> "abc123..."
50
-
51
- # Context data
52
- e.context.license_key #=> "ABC-123-DEF"
53
- e.context.machine_id #=> "[FILTERED]"
54
-
55
- # Chain information
56
- e.chain.id #=> "def456..."
57
- e.chain.size #=> 3
58
- end
59
- ```
60
-
61
- ## Advanced Matching
62
-
63
- ### Task-Specific Matching
64
-
65
- Handle faults only from specific tasks using `for?`:
66
-
67
- ```ruby
68
- begin
69
- DocumentWorkflow.execute!(document_data: data)
70
- rescue CMDx::FailFault.for?(FormatValidator, ContentProcessor) => e
71
- # Handle only document-related failures
72
- retry_with_alternate_parser(e.context)
73
- rescue CMDx::SkipFault.for?(VirusScanner, ContentFilter) => e
74
- # Handle security-related skips
75
- quarantine_for_review(e.context.document_id)
76
- end
77
- ```
78
-
79
- ### Custom Logic Matching
80
-
81
- ```ruby
82
- begin
83
- ReportGenerator.execute!(report: report_data)
84
- rescue CMDx::Fault.matches? { |f| f.context.data_size > 10_000 } => e
85
- escalate_large_dataset_failure(e)
86
- rescue CMDx::FailFault.matches? { |f| f.result.metadata[:attempt_count] > 3 } => e
87
- abandon_report_generation(e)
88
- rescue CMDx::Fault.matches? { |f| f.result.metadata[:error_type] == "memory" } => e
89
- increase_memory_and_retry(e)
90
- end
91
- ```
92
-
93
- ## Fault Propagation
94
-
95
- Propagate failures with `throw!` to preserve context and maintain the error chain:
96
-
97
- ### Basic Propagation
98
-
99
- ```ruby
100
- class ReportGenerator < CMDx::Task
101
- def work
102
- # Throw if skipped or failed
103
- validation_result = DataValidator.execute(context)
104
- throw!(validation_result)
105
-
106
- # Only throw if skipped
107
- check_permissions = CheckPermissions.execute(context)
108
- throw!(check_permissions) if check_permissions.skipped?
109
-
110
- # Only throw if failed
111
- data_result = DataProcessor.execute(context)
112
- throw!(data_result) if data_result.failed?
113
-
114
- # Continue processing
115
- generate_report
116
- end
117
- end
118
- ```
119
-
120
- ### Additional Metadata
121
-
122
- ```ruby
123
- class BatchProcessor < CMDx::Task
124
- def work
125
- step_result = FileValidation.execute(context)
126
-
127
- if step_result.failed?
128
- throw!(step_result, {
129
- batch_stage: "validation",
130
- can_retry: true,
131
- next_step: "file_repair"
132
- })
133
- end
134
-
135
- continue_batch
136
- end
137
- end
138
- ```
139
-
140
- ## Chain Analysis
141
-
142
- Trace fault origins and propagation through the execution chain:
143
-
144
- ```ruby
145
- result = DocumentWorkflow.execute(invalid_data)
146
-
147
- if result.failed?
148
- # Trace the original failure
149
- original = result.caused_failure
150
- if original
151
- puts "Original failure: #{original.task.class.name}"
152
- puts "Reason: #{original.reason}"
153
- end
154
-
155
- # Find what propagated the failure
156
- thrower = result.threw_failure
157
- puts "Propagated by: #{thrower.task.class.name}" if thrower
158
-
159
- # Analyze failure type
160
- case
161
- when result.caused_failure?
162
- puts "This task was the original source"
163
- when result.threw_failure?
164
- puts "This task propagated a failure"
165
- when result.thrown_failure?
166
- puts "This task failed due to propagation"
167
- end
168
- end
169
- ```
@@ -1,216 +0,0 @@
1
- # Interruptions - Halt
2
-
3
- Stop task execution intentionally using `skip!` or `fail!`. Both methods signal clear intent about why execution stopped.
4
-
5
- ## Skipping
6
-
7
- Use `skip!` when the task doesn't need to run. It's a no-op, not an error.
8
-
9
- !!! warning "Important"
10
-
11
- Skipped tasks are considered "good" outcomes—they succeeded by doing nothing.
12
-
13
- ```ruby
14
- class ProcessInventory < CMDx::Task
15
- def work
16
- # Without a reason
17
- skip! if Array(ENV["DISABLED_TASKS"]).include?(self.class.name)
18
-
19
- # With a reason
20
- skip!("Warehouse closed") unless Time.now.hour.between?(8, 18)
21
-
22
- inventory = Inventory.find(context.inventory_id)
23
-
24
- if inventory.already_counted?
25
- skip!("Inventory already counted today")
26
- else
27
- inventory.count!
28
- end
29
- end
30
- end
31
-
32
- result = ProcessInventory.execute(inventory_id: 456)
33
-
34
- # Executed
35
- result.status #=> "skipped"
36
-
37
- # Without a reason
38
- result.reason #=> "Unspecified"
39
-
40
- # With a reason
41
- result.reason #=> "Warehouse closed"
42
- ```
43
-
44
- ## Failing
45
-
46
- Use `fail!` when the task can't complete successfully. It signals controlled, intentional failure:
47
-
48
- ```ruby
49
- class ProcessRefund < CMDx::Task
50
- def work
51
- # Without a reason
52
- fail! if Array(ENV["DISABLED_TASKS"]).include?(self.class.name)
53
-
54
- refund = Refund.find(context.refund_id)
55
-
56
- # With a reason
57
- if refund.expired?
58
- fail!("Refund period has expired")
59
- elsif !refund.amount.positive?
60
- fail!("Refund amount must be positive")
61
- else
62
- refund.process!
63
- end
64
- end
65
- end
66
-
67
- result = ProcessRefund.execute(refund_id: 789)
68
-
69
- # Executed
70
- result.status #=> "failed"
71
-
72
- # Without a reason
73
- result.reason #=> "Unspecified"
74
-
75
- # With a reason
76
- result.reason #=> "Refund period has expired"
77
- ```
78
-
79
- ## Metadata Enrichment
80
-
81
- Enrich halt calls with metadata for better debugging and error handling:
82
-
83
- ```ruby
84
- class ProcessRenewal < CMDx::Task
85
- def work
86
- license = License.find(context.license_id)
87
-
88
- if license.already_renewed?
89
- # Without metadata
90
- skip!("License already renewed")
91
- end
92
-
93
- unless license.renewal_eligible?
94
- # With metadata
95
- fail!(
96
- "License not eligible for renewal",
97
- error_code: "LICENSE.NOT_ELIGIBLE",
98
- retry_after: Time.current + 30.days
99
- )
100
- end
101
-
102
- process_renewal
103
- end
104
- end
105
-
106
- result = ProcessRenewal.execute(license_id: 567)
107
-
108
- # Without metadata
109
- result.metadata #=> {}
110
-
111
- # With metadata
112
- result.metadata #=> {
113
- # error_code: "LICENSE.NOT_ELIGIBLE",
114
- # retry_after: <Time 30 days from now>
115
- # }
116
- ```
117
-
118
- ## State Transitions
119
-
120
- Halt methods trigger specific state and status transitions:
121
-
122
- | Method | State | Status | Outcome |
123
- |--------|-------|--------|---------|
124
- | `skip!` | `interrupted` | `skipped` | `good? = true`, `bad? = true` |
125
- | `fail!` | `interrupted` | `failed` | `good? = false`, `bad? = true` |
126
-
127
- ```ruby
128
- result = ProcessRenewal.execute(license_id: 567)
129
-
130
- # State information
131
- result.state #=> "interrupted"
132
- result.status #=> "skipped" or "failed"
133
- result.interrupted? #=> true
134
- result.complete? #=> false
135
-
136
- # Outcome categorization
137
- result.good? #=> true for skipped, false for failed
138
- result.bad? #=> true for both skipped and failed
139
- ```
140
-
141
- ## Execution Behavior
142
-
143
- Halt methods behave differently depending on the call method used:
144
-
145
- ### Non-bang execution
146
-
147
- Returns result object without raising exceptions:
148
-
149
- ```ruby
150
- result = ProcessRefund.execute(refund_id: 789)
151
-
152
- case result.status
153
- when "success"
154
- puts "Refund processed: $#{result.context.refund.amount}"
155
- when "skipped"
156
- puts "Refund skipped: #{result.reason}"
157
- when "failed"
158
- puts "Refund failed: #{result.reason}"
159
- handle_refund_error(result.metadata[:error_code])
160
- end
161
- ```
162
-
163
- ### Bang execution
164
-
165
- Raises exceptions for halt conditions based on `task_breakpoints` configuration:
166
-
167
- ```ruby
168
- begin
169
- result = ProcessRefund.execute!(refund_id: 789)
170
- puts "Success: Refund processed"
171
- rescue CMDx::SkipFault => e
172
- puts "Skipped: #{e.message}"
173
- rescue CMDx::FailFault => e
174
- puts "Failed: #{e.message}"
175
- handle_refund_failure(e.result.metadata[:error_code])
176
- end
177
- ```
178
-
179
- ## Best Practices
180
-
181
- Always provide a reason for better debugging and clearer exception messages:
182
-
183
- ```ruby
184
- # Good: Clear, specific reason
185
- skip!("Document processing paused for compliance review")
186
- fail!("File format not supported by processor", code: "FORMAT_UNSUPPORTED")
187
-
188
- # Acceptable: Generic, non-specific reason
189
- skip!("Paused")
190
- fail!("Unsupported")
191
-
192
- # Bad: Default, cannot determine reason
193
- skip! #=> "Unspecified"
194
- fail! #=> "Unspecified"
195
- ```
196
-
197
- ## Manual Errors
198
-
199
- For rare cases, manually add errors before halting:
200
-
201
- !!! warning "Important"
202
-
203
- Manual errors don't stop execution—you still need to call `fail!` or `skip!`.
204
-
205
- ```ruby
206
- class ProcessRenewal < CMDx::Task
207
- def work
208
- if document.nonrenewable?
209
- errors.add(:document, "not renewable")
210
- fail!("document could not be renewed")
211
- else
212
- document.renew!
213
- end
214
- end
215
- end
216
- ```
data/docs/logging.md DELETED
@@ -1,94 +0,0 @@
1
- # Logging
2
-
3
- CMDx automatically logs every task execution with structured data, making debugging and monitoring effortless. Choose from multiple formatters to match your logging infrastructure.
4
-
5
- ## Formatters
6
-
7
- Choose the format that works best for your logging system:
8
-
9
- | Formatter | Use Case | Output Style |
10
- |-----------|----------|--------------|
11
- | `Line` | Traditional logging | Single-line format |
12
- | `Json` | Structured systems | Compact JSON |
13
- | `KeyValue` | Log parsing | `key=value` pairs |
14
- | `Logstash` | ELK stack | JSON with @version/@timestamp |
15
- | `Raw` | Minimal output | Message content only |
16
-
17
- Sample output:
18
-
19
- ```log
20
- <!-- Success (INFO level) -->
21
- I, [2022-07-17T18:43:15.000000 #3784] INFO -- GenerateInvoice:
22
- index=0 chain_id="018c2b95-b764-7615-a924-cc5b910ed1e5" type="Task" class="GenerateInvoice" state="complete" status="success" metadata={runtime: 187}
23
-
24
- <!-- Skipped (WARN level) -->
25
- W, [2022-07-17T18:43:15.000000 #3784] WARN -- ValidateCustomer:
26
- index=1 chain_id="018c2b95-b764-7615-a924-cc5b910ed1e5" type="Task" class="ValidateCustomer" state="interrupted" status="skipped" reason="Customer already validated"
27
-
28
- <!-- Failed (ERROR level) -->
29
- E, [2022-07-17T18:43:15.000000 #3784] ERROR -- CalculateTax:
30
- index=2 chain_id="018c2b95-b764-7615-a924-cc5b910ed1e5" type="Task" class="CalculateTax" state="interrupted" status="failed" metadata={error_code: "TAX_SERVICE_UNAVAILABLE"}
31
-
32
- <!-- Failed Chain -->
33
- E, [2022-07-17T18:43:15.000000 #3784] ERROR -- BillingWorkflow:
34
- index=3 chain_id="018c2b95-b764-7615-a924-cc5b910ed1e5" type="Task" class="BillingWorkflow" state="interrupted" status="failed" caused_failure={index: 2, class: "CalculateTax", status: "failed"} threw_failure={index: 1, class: "ValidateCustomer", status: "failed"}
35
- ```
36
-
37
- !!! tip
38
-
39
- Use logging as a low-level event stream to track all tasks in a request. Combine with correlation for powerful distributed tracing.
40
-
41
- ## Structure
42
-
43
- Every log entry includes rich metadata. Available fields depend on execution context and outcome.
44
-
45
- ### Core Fields
46
-
47
- | Field | Description | Example |
48
- |-------|-------------|---------|
49
- | `severity` | Log level | `INFO`, `WARN`, `ERROR` |
50
- | `timestamp` | ISO 8601 execution time | `2022-07-17T18:43:15.000000` |
51
- | `pid` | Process ID | `3784` |
52
-
53
- ### Task Information
54
-
55
- | Field | Description | Example |
56
- |-------|-------------|---------|
57
- | `index` | Execution sequence position | `0`, `1`, `2` |
58
- | `chain_id` | Unique execution chain ID | `018c2b95-b764-7615...` |
59
- | `type` | Execution unit type | `Task`, `Workflow` |
60
- | `class` | Task class name | `GenerateInvoiceTask` |
61
- | `id` | Unique task instance ID | `018c2b95-b764-7615...` |
62
- | `tags` | Custom categorization | `["billing", "financial"]` |
63
-
64
- ### Execution Data
65
-
66
- | Field | Description | Example |
67
- |-------|-------------|---------|
68
- | `state` | Lifecycle state | `complete`, `interrupted` |
69
- | `status` | Business outcome | `success`, `skipped`, `failed` |
70
- | `outcome` | Final classification | `success`, `interrupted` |
71
- | `metadata` | Custom task data | `{order_id: 123, amount: 99.99}` |
72
-
73
- ### Failure Chain
74
-
75
- | Field | Description |
76
- |-------|-------------|
77
- | `reason` | Reason given for the stoppage |
78
- | `caused` | Cause exception details |
79
- | `caused_failure` | Original failing task details |
80
- | `threw_failure` | Task that propagated the failure |
81
-
82
- ## Usage
83
-
84
- Access the framework logger directly within tasks:
85
-
86
- ```ruby
87
- class ProcessSubscription < CMDx::Task
88
- def work
89
- logger.debug { "Activated feature flags: #{Features.active_flags}" }
90
- # Your logic here...
91
- logger.info("Subscription processed")
92
- end
93
- end
94
- ```
data/docs/middlewares.md DELETED
@@ -1,191 +0,0 @@
1
- # Middlewares
2
-
3
- Wrap task execution with middleware for cross-cutting concerns like authentication, caching, timeouts, and monitoring. Think Rack middleware, but for your business logic.
4
-
5
- See [Global Configuration](getting_started.md#middlewares) for framework-wide setup.
6
-
7
- ## Execution Order
8
-
9
- Middleware wraps task execution in layers, like an onion:
10
-
11
- !!! note
12
-
13
- First registered = outermost wrapper. They execute in registration order.
14
-
15
- ```ruby
16
- class ProcessCampaign < CMDx::Task
17
- register :middleware, AuditMiddleware # 1st: outermost wrapper
18
- register :middleware, AuthorizationMiddleware # 2nd: middle wrapper
19
- register :middleware, CacheMiddleware # 3rd: innermost wrapper
20
-
21
- def work
22
- # Your logic here...
23
- end
24
- end
25
-
26
- # Execution flow:
27
- # 1. AuditMiddleware (before)
28
- # 2. AuthorizationMiddleware (before)
29
- # 3. CacheMiddleware (before)
30
- # 4. [task execution]
31
- # 5. CacheMiddleware (after)
32
- # 6. AuthorizationMiddleware (after)
33
- # 7. AuditMiddleware (after)
34
- ```
35
-
36
- ## Declarations
37
-
38
- ### Proc or Lambda
39
-
40
- Use anonymous functions for simple middleware logic:
41
-
42
- ```ruby
43
- class ProcessCampaign < CMDx::Task
44
- # Proc
45
- register :middleware, proc do |task, options, &block|
46
- result = block.call
47
- Analytics.track(result.status)
48
- result
49
- end
50
-
51
- # Lambda
52
- register :middleware, ->(task, options, &block) {
53
- result = block.call
54
- Analytics.track(result.status)
55
- result
56
- }
57
- end
58
- ```
59
-
60
- ### Class or Module
61
-
62
- For complex middleware logic, use classes or modules:
63
-
64
- ```ruby
65
- class TelemetryMiddleware
66
- def call(task, options)
67
- result = yield
68
- Telemetry.record(result.status)
69
- ensure
70
- result # Always return result
71
- end
72
- end
73
-
74
- class ProcessCampaign < CMDx::Task
75
- # Class or Module
76
- register :middleware, TelemetryMiddleware
77
-
78
- # Instance
79
- register :middleware, TelemetryMiddleware.new
80
-
81
- # With options
82
- register :middleware, MonitoringMiddleware, service_key: ENV["MONITORING_KEY"]
83
- register :middleware, MonitoringMiddleware.new(ENV["MONITORING_KEY"])
84
- end
85
- ```
86
-
87
- ## Removals
88
-
89
- Remove class or module-based middleware globally or per-task:
90
-
91
- !!! warning
92
-
93
- Each `deregister` call removes one middleware. Use multiple calls for batch removals.
94
-
95
- ```ruby
96
- class ProcessCampaign < CMDx::Task
97
- # Class or Module (no instances)
98
- deregister :middleware, TelemetryMiddleware
99
- end
100
- ```
101
-
102
- ## Built-in
103
-
104
- ### Timeout
105
-
106
- Prevent tasks from running too long:
107
-
108
- ```ruby
109
- class ProcessReport < CMDx::Task
110
- # Default timeout: 3 seconds
111
- register :middleware, CMDx::Middlewares::Timeout
112
-
113
- # Seconds (takes Numeric, Symbol, Proc, Lambda, Class, Module)
114
- register :middleware, CMDx::Middlewares::Timeout, seconds: :max_processing_time
115
-
116
- # If or Unless (takes Symbol, Proc, Lambda, Class, Module)
117
- register :middleware, CMDx::Middlewares::Timeout, unless: -> { self.class.name.include?("Quick") }
118
-
119
- def work
120
- # Your logic here...
121
- end
122
-
123
- private
124
-
125
- def max_processing_time
126
- Rails.env.production? ? 2 : 10
127
- end
128
- end
129
-
130
- # Slow task
131
- result = ProcessReport.execute
132
-
133
- result.state #=> "interrupted"
134
- result.status #=> "failure"
135
- result.reason #=> "[CMDx::TimeoutError] execution exceeded 3 seconds"
136
- result.cause #=> <CMDx::TimeoutError>
137
- result.metadata #=> { limit: 3 }
138
- ```
139
-
140
- ### Correlate
141
-
142
- Add correlation IDs for distributed tracing and request tracking:
143
-
144
- ```ruby
145
- class ProcessExport < CMDx::Task
146
- # Default correlation ID generation
147
- register :middleware, CMDx::Middlewares::Correlate
148
-
149
- # Seconds (takes Object, Symbol, Proc, Lambda, Class, Module)
150
- register :middleware, CMDx::Middlewares::Correlate, id: proc { |task| task.context.session_id }
151
-
152
- # If or Unless (takes Symbol, Proc, Lambda, Class, Module)
153
- register :middleware, CMDx::Middlewares::Correlate, if: :correlation_enabled?
154
-
155
- def work
156
- # Your logic here...
157
- end
158
-
159
- private
160
-
161
- def correlation_enabled?
162
- ENV["CORRELATION_ENABLED"] == "true"
163
- end
164
- end
165
-
166
- result = ProcessExport.execute
167
- result.metadata #=> { correlation_id: "550e8400-e29b-41d4-a716-446655440000" }
168
- ```
169
-
170
- ### Runtime
171
-
172
- Track task execution time in milliseconds using a monotonic clock:
173
-
174
- ```ruby
175
- class PerformanceMonitoringCheck
176
- def call(task)
177
- task.context.tenant.monitoring_enabled?
178
- end
179
- end
180
-
181
- class ProcessExport < CMDx::Task
182
- # Default timeout is 3 seconds
183
- register :middleware, CMDx::Middlewares::Runtime
184
-
185
- # If or Unless (takes Symbol, Proc, Lambda, Class, Module)
186
- register :middleware, CMDx::Middlewares::Runtime, if: PerformanceMonitoringCheck
187
- end
188
-
189
- result = ProcessExport.execute
190
- result.metadata #=> { runtime: 1247 } (ms)
191
- ```