cmdx 1.9.1 → 1.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.cursor/prompts/llms.md +3 -13
- data/CHANGELOG.md +10 -0
- data/LLM.md +429 -376
- data/README.md +1 -1
- data/docs/basics/setup.md +17 -0
- data/docs/callbacks.md +1 -1
- data/docs/getting_started.md +13 -2
- data/docs/retries.md +121 -0
- data/docs/tips_and_tricks.md +2 -1
- data/examples/stoplight_circuit_breaker.md +36 -0
- data/lib/cmdx/configuration.rb +15 -0
- data/lib/cmdx/executor.rb +31 -14
- data/lib/cmdx/version.rb +1 -1
- data/mkdocs.yml +3 -1
- metadata +3 -1
data/LLM.md
CHANGED
|
@@ -1,40 +1,33 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
This file contains all the CMDx documentation consolidated from the docs directory.
|
|
1
|
+
# Getting Started
|
|
4
2
|
|
|
5
|
-
|
|
3
|
+
CMDx is a Ruby framework for building maintainable, observable business logic through composable command objects. It brings structure, consistency, and powerful developer tools to your business processes.
|
|
6
4
|
|
|
7
|
-
|
|
8
|
-
---
|
|
5
|
+
**Common challenges it solves:**
|
|
9
6
|
|
|
10
|
-
|
|
7
|
+
- Inconsistent service object patterns across your codebase
|
|
8
|
+
- Limited logging makes debugging a nightmare
|
|
9
|
+
- Fragile error handling erodes confidence
|
|
11
10
|
|
|
12
|
-
|
|
11
|
+
**What you get:**
|
|
13
12
|
|
|
14
|
-
|
|
13
|
+
- Consistent, standardized architecture
|
|
14
|
+
- Built-in flow control and error handling
|
|
15
|
+
- Composable, reusable workflows
|
|
16
|
+
- Comprehensive logging for observability
|
|
17
|
+
- Attribute validation with type coercions
|
|
18
|
+
- Sensible defaults and developer-friendly APIs
|
|
15
19
|
|
|
16
|
-
|
|
17
|
-
- Minimal or no logging, making debugging painful
|
|
18
|
-
- Fragile designs that erode developer confidence
|
|
20
|
+
## The CERO Pattern
|
|
19
21
|
|
|
20
|
-
|
|
22
|
+
CMDx embraces the Compose, Execute, React, Observe (CERO) pattern—a simple yet powerful approach to building reliable business logic.
|
|
21
23
|
|
|
22
|
-
|
|
23
|
-
- Provides flow control and error handling
|
|
24
|
-
- Supports composable, reusable workflows
|
|
25
|
-
- Includes detailed logging for observability
|
|
26
|
-
- Defines input attributes with fallback defaults
|
|
27
|
-
- Adds validations and type coercions
|
|
28
|
-
- Plus many other developer-friendly tools
|
|
24
|
+
🧩 **Compose** — Define small, focused tasks with typed attributes and validations
|
|
29
25
|
|
|
30
|
-
|
|
26
|
+
⚡ **Execute** — Run tasks with clear outcomes and pluggable behaviors
|
|
31
27
|
|
|
32
|
-
|
|
28
|
+
🔄 **React** — Adapt to outcomes by chaining follow-up tasks or handling faults
|
|
33
29
|
|
|
34
|
-
|
|
35
|
-
- **Execute** → Run tasks with clear outcomes, intentional halts, and pluggable behaviors via middlewares and callbacks.
|
|
36
|
-
- **React** → Adapt to outcomes by chaining follow-up tasks, handling faults, or shaping future flows.
|
|
37
|
-
- **Observe** → Capture immutable results, structured logs, and full execution chains for reliable tracing and insight.
|
|
30
|
+
🔍 **Observe** — Capture structured logs and execution chains for debugging
|
|
38
31
|
|
|
39
32
|
## Installation
|
|
40
33
|
|
|
@@ -54,45 +47,54 @@ This creates `config/initializers/cmdx.rb` file.
|
|
|
54
47
|
|
|
55
48
|
## Configuration Hierarchy
|
|
56
49
|
|
|
57
|
-
CMDx
|
|
50
|
+
CMDx uses a straightforward two-tier configuration system:
|
|
51
|
+
|
|
52
|
+
1. **Global Configuration** — Framework-wide defaults
|
|
53
|
+
2. **Task Settings** — Class-level overrides using `settings`
|
|
58
54
|
|
|
59
|
-
|
|
60
|
-
2. **Task Settings**: Class-level overrides via `settings`
|
|
55
|
+
!!! warning "Important"
|
|
61
56
|
|
|
62
|
-
|
|
63
|
-
> Task-level settings take precedence over global configuration. Settings are inherited from superclasses and can be overridden in subclasses.
|
|
57
|
+
Task settings take precedence over global config. Settings are inherited from parent classes and can be overridden in subclasses.
|
|
64
58
|
|
|
65
59
|
## Global Configuration
|
|
66
60
|
|
|
67
|
-
|
|
68
|
-
Globally these settings are initialized with sensible defaults.
|
|
61
|
+
Configure framework-wide defaults that apply to all tasks. These settings come with sensible defaults out of the box.
|
|
69
62
|
|
|
70
63
|
### Breakpoints
|
|
71
64
|
|
|
72
|
-
|
|
65
|
+
Control when `execute!` raises a `CMDx::Fault` based on task status.
|
|
73
66
|
|
|
74
67
|
```ruby
|
|
75
68
|
CMDx.configure do |config|
|
|
76
|
-
# String or Array[String]
|
|
77
|
-
config.task_breakpoints = "failed"
|
|
69
|
+
config.task_breakpoints = "failed" # String or Array[String]
|
|
78
70
|
end
|
|
79
71
|
```
|
|
80
72
|
|
|
81
|
-
|
|
73
|
+
For workflows, configure which statuses halt the execution pipeline:
|
|
82
74
|
|
|
83
75
|
```ruby
|
|
84
76
|
CMDx.configure do |config|
|
|
85
|
-
# String or Array[String]
|
|
86
77
|
config.workflow_breakpoints = ["skipped", "failed"]
|
|
87
78
|
end
|
|
88
79
|
```
|
|
89
80
|
|
|
81
|
+
### Rollback
|
|
82
|
+
|
|
83
|
+
Control when a `rollback` of task execution is called.
|
|
84
|
+
|
|
85
|
+
```ruby
|
|
86
|
+
CMDx.configure do |config|
|
|
87
|
+
config.rollback_on = ["failed"] # String or Array[String]
|
|
88
|
+
end
|
|
89
|
+
```
|
|
90
|
+
|
|
90
91
|
### Backtraces
|
|
91
92
|
|
|
92
|
-
Enable backtraces
|
|
93
|
+
Enable detailed backtraces for non-fault exceptions to improve debugging. Optionally clean up stack traces to remove framework noise.
|
|
94
|
+
|
|
95
|
+
!!! note
|
|
93
96
|
|
|
94
|
-
|
|
95
|
-
> The `backtrace_cleaner` is set to `Rails.backtrace_cleaner.clean` in a Rails env by default.
|
|
97
|
+
In Rails environments, `backtrace_cleaner` defaults to `Rails.backtrace_cleaner.clean`.
|
|
96
98
|
|
|
97
99
|
```ruby
|
|
98
100
|
CMDx.configure do |config|
|
|
@@ -109,10 +111,11 @@ end
|
|
|
109
111
|
|
|
110
112
|
### Exception Handlers
|
|
111
113
|
|
|
112
|
-
|
|
114
|
+
Register handlers that run when non-fault exceptions occur.
|
|
115
|
+
|
|
116
|
+
!!! tip
|
|
113
117
|
|
|
114
|
-
|
|
115
|
-
> Use exception handlers to send errors to your APM of choice.
|
|
118
|
+
Use exception handlers to send errors to your APM of choice.
|
|
116
119
|
|
|
117
120
|
```ruby
|
|
118
121
|
CMDx.configure do |config|
|
|
@@ -136,7 +139,7 @@ end
|
|
|
136
139
|
|
|
137
140
|
### Middlewares
|
|
138
141
|
|
|
139
|
-
See the [
|
|
142
|
+
See the [Middlewares](https://github.com/drexed/cmdx/blob/main/docs/middlewares.md#declarations) docs for task level configurations.
|
|
140
143
|
|
|
141
144
|
```ruby
|
|
142
145
|
CMDx.configure do |config|
|
|
@@ -160,12 +163,13 @@ CMDx.configure do |config|
|
|
|
160
163
|
end
|
|
161
164
|
```
|
|
162
165
|
|
|
163
|
-
|
|
164
|
-
|
|
166
|
+
!!! note
|
|
167
|
+
|
|
168
|
+
Middlewares are executed in registration order. Each middleware wraps the next, creating an execution chain around task logic.
|
|
165
169
|
|
|
166
170
|
### Callbacks
|
|
167
171
|
|
|
168
|
-
See the [Callbacks](
|
|
172
|
+
See the [Callbacks](https://github.com/drexed/cmdx/blob/main/docs/callbacks.md#declarations) docs for task level configurations.
|
|
169
173
|
|
|
170
174
|
```ruby
|
|
171
175
|
CMDx.configure do |config|
|
|
@@ -191,7 +195,7 @@ end
|
|
|
191
195
|
|
|
192
196
|
### Coercions
|
|
193
197
|
|
|
194
|
-
See the [Attributes - Coercions](
|
|
198
|
+
See the [Attributes - Coercions](https://github.com/drexed/cmdx/blob/main/docs/attributes/coercions.md#declarations) docs for task level configurations.
|
|
195
199
|
|
|
196
200
|
```ruby
|
|
197
201
|
CMDx.configure do |config|
|
|
@@ -217,7 +221,7 @@ end
|
|
|
217
221
|
|
|
218
222
|
### Validators
|
|
219
223
|
|
|
220
|
-
See the [Attributes - Validations](
|
|
224
|
+
See the [Attributes - Validations](https://github.com/drexed/cmdx/blob/main/docs/attributes/validations.md#declarations) docs for task level configurations.
|
|
221
225
|
|
|
222
226
|
```ruby
|
|
223
227
|
CMDx.configure do |config|
|
|
@@ -264,7 +268,8 @@ class GenerateInvoice < CMDx::Task
|
|
|
264
268
|
deprecated: true, # Task deprecations
|
|
265
269
|
retries: 3, # Non-fault exception retries
|
|
266
270
|
retry_on: [External::ApiError], # List of exceptions to retry on
|
|
267
|
-
retry_jitter: 1
|
|
271
|
+
retry_jitter: 1, # Space between retry iteration, eg: current retry num + 1
|
|
272
|
+
rollback_on: ["failed", "skipped"], # Rollback on override
|
|
268
273
|
)
|
|
269
274
|
|
|
270
275
|
def work
|
|
@@ -273,13 +278,13 @@ class GenerateInvoice < CMDx::Task
|
|
|
273
278
|
end
|
|
274
279
|
```
|
|
275
280
|
|
|
276
|
-
|
|
277
|
-
|
|
281
|
+
!!! warning "Important"
|
|
282
|
+
|
|
283
|
+
Retries reuse the same context. By default, all `StandardError` exceptions (including faults) are retried unless you specify `retry_on` option for specific matches.
|
|
278
284
|
|
|
279
285
|
### Registrations
|
|
280
286
|
|
|
281
|
-
Register middlewares, callbacks, coercions, and validators
|
|
282
|
-
Deregister options that should not be available.
|
|
287
|
+
Register or deregister middlewares, callbacks, coercions, and validators for specific tasks:
|
|
283
288
|
|
|
284
289
|
```ruby
|
|
285
290
|
class SendCampaignEmail < CMDx::Task
|
|
@@ -331,8 +336,9 @@ end
|
|
|
331
336
|
|
|
332
337
|
### Resetting
|
|
333
338
|
|
|
334
|
-
|
|
335
|
-
|
|
339
|
+
!!! warning
|
|
340
|
+
|
|
341
|
+
Resetting affects your entire application. Use this primarily in test environments.
|
|
336
342
|
|
|
337
343
|
```ruby
|
|
338
344
|
# Reset to framework defaults
|
|
@@ -369,8 +375,9 @@ class ModerateBlogPost < CMDx::Task
|
|
|
369
375
|
end
|
|
370
376
|
```
|
|
371
377
|
|
|
372
|
-
|
|
373
|
-
|
|
378
|
+
!!! tip
|
|
379
|
+
|
|
380
|
+
Use **present tense verbs + noun** for task names, eg: `ModerateBlogPost`, `ScheduleAppointment`, `ValidateDocument`
|
|
374
381
|
|
|
375
382
|
## Type safety
|
|
376
383
|
|
|
@@ -381,18 +388,13 @@ CMDx includes built-in RBS (Ruby Type Signature) inline annotations throughout t
|
|
|
381
388
|
- **Self-documenting code** — Clear method signatures and return types
|
|
382
389
|
- **Refactoring confidence** — Type-aware refactoring reduces bugs
|
|
383
390
|
|
|
384
|
-
---
|
|
385
|
-
|
|
386
|
-
url: https://github.com/drexed/cmdx/blob/main/docs/basics/setup.md
|
|
387
|
-
---
|
|
388
|
-
|
|
389
391
|
# Basics - Setup
|
|
390
392
|
|
|
391
|
-
Tasks are the
|
|
393
|
+
Tasks are the heart of CMDx—self-contained units of business logic with built-in validation, error handling, and execution tracking.
|
|
392
394
|
|
|
393
395
|
## Structure
|
|
394
396
|
|
|
395
|
-
Tasks inherit from `CMDx::Task` and
|
|
397
|
+
Tasks need only two things: inherit from `CMDx::Task` and define a `work` method:
|
|
396
398
|
|
|
397
399
|
```ruby
|
|
398
400
|
class ValidateDocument < CMDx::Task
|
|
@@ -402,7 +404,7 @@ class ValidateDocument < CMDx::Task
|
|
|
402
404
|
end
|
|
403
405
|
```
|
|
404
406
|
|
|
405
|
-
|
|
407
|
+
Without a `work` method, execution raises `CMDx::UndefinedMethodError`.
|
|
406
408
|
|
|
407
409
|
```ruby
|
|
408
410
|
class IncompleteTask < CMDx::Task
|
|
@@ -412,10 +414,25 @@ end
|
|
|
412
414
|
IncompleteTask.execute #=> raises CMDx::UndefinedMethodError
|
|
413
415
|
```
|
|
414
416
|
|
|
417
|
+
## Rollback
|
|
418
|
+
|
|
419
|
+
Undo any operations linked to the given status, helping to restore a pristine state.
|
|
420
|
+
|
|
421
|
+
```ruby
|
|
422
|
+
class ValidateDocument < CMDx::Task
|
|
423
|
+
def work
|
|
424
|
+
# Your logic here...
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
def rollback
|
|
428
|
+
# Your undo logic...
|
|
429
|
+
end
|
|
430
|
+
end
|
|
431
|
+
```
|
|
432
|
+
|
|
415
433
|
## Inheritance
|
|
416
434
|
|
|
417
|
-
|
|
418
|
-
Create a base class to share common configuration across tasks:
|
|
435
|
+
Share configuration across tasks using inheritance:
|
|
419
436
|
|
|
420
437
|
```ruby
|
|
421
438
|
class ApplicationTask < CMDx::Task
|
|
@@ -441,10 +458,11 @@ end
|
|
|
441
458
|
|
|
442
459
|
## Lifecycle
|
|
443
460
|
|
|
444
|
-
Tasks follow a predictable
|
|
461
|
+
Tasks follow a predictable execution pattern:
|
|
445
462
|
|
|
446
|
-
|
|
447
|
-
|
|
463
|
+
!!! danger "Caution"
|
|
464
|
+
|
|
465
|
+
Tasks are single-use objects. Once executed, they're frozen and immutable.
|
|
448
466
|
|
|
449
467
|
| Stage | State | Status | Description |
|
|
450
468
|
|-------|-------|--------|-------------|
|
|
@@ -453,20 +471,15 @@ Tasks follow a predictable call pattern with specific states and statuses:
|
|
|
453
471
|
| **Execution** | `executing` | `success`/`failed`/`skipped` | `work` method runs |
|
|
454
472
|
| **Completion** | `executed` | `success`/`failed`/`skipped` | Result finalized |
|
|
455
473
|
| **Freezing** | `executed` | `success`/`failed`/`skipped` | Task becomes immutable |
|
|
456
|
-
|
|
457
|
-
---
|
|
458
|
-
|
|
459
|
-
url: https://github.com/drexed/cmdx/blob/main/docs/basics/execution.md
|
|
460
|
-
---
|
|
474
|
+
| **Rollback** | `executed` | `failed`/`skipped` | Work undone |
|
|
461
475
|
|
|
462
476
|
# Basics - Execution
|
|
463
477
|
|
|
464
|
-
|
|
478
|
+
CMDx offers two execution methods with different error handling approaches. Choose based on your needs: safe result handling or exception-based control flow.
|
|
465
479
|
|
|
466
|
-
## Methods
|
|
480
|
+
## Execution Methods
|
|
467
481
|
|
|
468
|
-
|
|
469
|
-
Create a new instance for subsequent executions.
|
|
482
|
+
Both methods return results, but handle failures differently:
|
|
470
483
|
|
|
471
484
|
| Method | Returns | Exceptions | Use Case |
|
|
472
485
|
|--------|---------|------------|----------|
|
|
@@ -475,10 +488,7 @@ Create a new instance for subsequent executions.
|
|
|
475
488
|
|
|
476
489
|
## Non-bang Execution
|
|
477
490
|
|
|
478
|
-
|
|
479
|
-
This is the preferred method for most use cases.
|
|
480
|
-
|
|
481
|
-
Any unhandled exceptions will be caught and returned as a task failure.
|
|
491
|
+
Always returns a `CMDx::Result`, never raises exceptions. Perfect for most use cases.
|
|
482
492
|
|
|
483
493
|
```ruby
|
|
484
494
|
result = CreateAccount.execute(email: "user@example.com")
|
|
@@ -496,23 +506,22 @@ result.status #=> "success"
|
|
|
496
506
|
|
|
497
507
|
## Bang Execution
|
|
498
508
|
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
It raises any unhandled non-fault exceptions caused during execution.
|
|
509
|
+
Raises `CMDx::Fault` exceptions on failure or skip. Returns results only on success.
|
|
502
510
|
|
|
503
511
|
| Exception | Raised When |
|
|
504
512
|
|-----------|-------------|
|
|
505
513
|
| `CMDx::FailFault` | Task execution fails |
|
|
506
514
|
| `CMDx::SkipFault` | Task execution is skipped |
|
|
507
515
|
|
|
508
|
-
|
|
509
|
-
|
|
516
|
+
!!! warning "Important"
|
|
517
|
+
|
|
518
|
+
Behavior depends on `task_breakpoints` or `workflow_breakpoints` config. Default: only failures raise exceptions.
|
|
510
519
|
|
|
511
520
|
```ruby
|
|
512
521
|
begin
|
|
513
522
|
result = CreateAccount.execute!(email: "user@example.com")
|
|
514
523
|
SendWelcomeEmail.execute(result.context)
|
|
515
|
-
rescue CMDx::
|
|
524
|
+
rescue CMDx::FailFault => e
|
|
516
525
|
ScheduleAccountRetryJob.perform_later(e.result.context.email)
|
|
517
526
|
rescue CMDx::SkipFault => e
|
|
518
527
|
Rails.logger.info("Account creation skipped: #{e.result.reason}")
|
|
@@ -534,7 +543,7 @@ task.id #=> "abc123..." (unique task ID)
|
|
|
534
543
|
task.context.email #=> "user@example.com"
|
|
535
544
|
task.context.send_welcome #=> true
|
|
536
545
|
task.result.state #=> "initialized"
|
|
537
|
-
result.status
|
|
546
|
+
task.result.status #=> "success"
|
|
538
547
|
|
|
539
548
|
# Manual execution
|
|
540
549
|
task.execute
|
|
@@ -559,19 +568,15 @@ result.chain #=> Task execution chain
|
|
|
559
568
|
# Context and metadata
|
|
560
569
|
result.context #=> Context with all task data
|
|
561
570
|
result.metadata #=> Hash with execution metadata
|
|
562
|
-
|
|
563
|
-
---
|
|
564
|
-
|
|
565
|
-
url: https://github.com/drexed/cmdx/blob/main/docs/basics/context.md
|
|
566
|
-
---
|
|
571
|
+
```
|
|
567
572
|
|
|
568
573
|
# Basics - Context
|
|
569
574
|
|
|
570
|
-
|
|
575
|
+
Context is your data container for inputs, intermediate values, and outputs. It makes sharing data between tasks effortless.
|
|
571
576
|
|
|
572
577
|
## Assigning Data
|
|
573
578
|
|
|
574
|
-
Context
|
|
579
|
+
Context automatically captures all task inputs, normalizing keys to symbols:
|
|
575
580
|
|
|
576
581
|
```ruby
|
|
577
582
|
# Direct execution
|
|
@@ -581,12 +586,13 @@ CalculateShipping.execute(weight: 2.5, destination: "CA")
|
|
|
581
586
|
CalculateShipping.new(weight: 2.5, "destination" => "CA")
|
|
582
587
|
```
|
|
583
588
|
|
|
584
|
-
|
|
585
|
-
|
|
589
|
+
!!! warning "Important"
|
|
590
|
+
|
|
591
|
+
String keys convert to symbols automatically. Prefer symbols for consistency.
|
|
586
592
|
|
|
587
593
|
## Accessing Data
|
|
588
594
|
|
|
589
|
-
|
|
595
|
+
Access context data using method notation, hash keys, or safe accessors:
|
|
590
596
|
|
|
591
597
|
```ruby
|
|
592
598
|
class CalculateShipping < CMDx::Task
|
|
@@ -609,8 +615,9 @@ class CalculateShipping < CMDx::Task
|
|
|
609
615
|
end
|
|
610
616
|
```
|
|
611
617
|
|
|
612
|
-
|
|
613
|
-
|
|
618
|
+
!!! warning "Important"
|
|
619
|
+
|
|
620
|
+
Undefined attributes return `nil` instead of raising errors—perfect for optional data.
|
|
614
621
|
|
|
615
622
|
## Modifying Context
|
|
616
623
|
|
|
@@ -651,12 +658,13 @@ class CalculateShipping < CMDx::Task
|
|
|
651
658
|
end
|
|
652
659
|
```
|
|
653
660
|
|
|
654
|
-
|
|
655
|
-
|
|
661
|
+
!!! tip
|
|
662
|
+
|
|
663
|
+
Use context for both input values and intermediate results. This creates natural data flow through your task execution pipeline.
|
|
656
664
|
|
|
657
665
|
## Data Sharing
|
|
658
666
|
|
|
659
|
-
|
|
667
|
+
Share context across tasks for seamless data flow:
|
|
660
668
|
|
|
661
669
|
```ruby
|
|
662
670
|
# During execution
|
|
@@ -684,21 +692,17 @@ result = CalculateShipping.execute(destination: "New York, NY")
|
|
|
684
692
|
CreateShippingLabel.execute(result)
|
|
685
693
|
```
|
|
686
694
|
|
|
687
|
-
---
|
|
688
|
-
|
|
689
|
-
url: https://github.com/drexed/cmdx/blob/main/docs/basics/chain.md
|
|
690
|
-
---
|
|
691
|
-
|
|
692
695
|
# Basics - Chain
|
|
693
696
|
|
|
694
|
-
Chains automatically
|
|
697
|
+
Chains automatically track related task executions within a thread. Think of them as execution traces that help you understand what happened and in what order.
|
|
695
698
|
|
|
696
699
|
## Management
|
|
697
700
|
|
|
698
|
-
Each thread maintains its own chain
|
|
701
|
+
Each thread maintains its own isolated chain using thread-local storage.
|
|
702
|
+
|
|
703
|
+
!!! warning
|
|
699
704
|
|
|
700
|
-
|
|
701
|
-
> Chain operations are thread-local. Never share chain references across threads as this can lead to race conditions and data corruption.
|
|
705
|
+
Chains are thread-local. Don't share chain references across threads—it causes race conditions.
|
|
702
706
|
|
|
703
707
|
```ruby
|
|
704
708
|
# Thread A
|
|
@@ -720,10 +724,11 @@ CMDx::Chain.clear #=> Clears current thread's chain
|
|
|
720
724
|
|
|
721
725
|
## Links
|
|
722
726
|
|
|
723
|
-
|
|
727
|
+
Tasks automatically create or join the current thread's chain:
|
|
728
|
+
|
|
729
|
+
!!! warning "Important"
|
|
724
730
|
|
|
725
|
-
|
|
726
|
-
> Chain creation is automatic and transparent. You don't need to manually manage chain lifecycle.
|
|
731
|
+
Chain management is automatic—no manual lifecycle handling needed.
|
|
727
732
|
|
|
728
733
|
```ruby
|
|
729
734
|
class ImportDataset < CMDx::Task
|
|
@@ -746,7 +751,7 @@ end
|
|
|
746
751
|
|
|
747
752
|
## Inheritance
|
|
748
753
|
|
|
749
|
-
|
|
754
|
+
Subtasks automatically inherit the current thread's chain, building a unified execution trail:
|
|
750
755
|
|
|
751
756
|
```ruby
|
|
752
757
|
class ImportDataset < CMDx::Task
|
|
@@ -771,10 +776,11 @@ chain.results.map { |r| r.task.class }
|
|
|
771
776
|
|
|
772
777
|
## Structure
|
|
773
778
|
|
|
774
|
-
Chains
|
|
779
|
+
Chains expose comprehensive execution information:
|
|
775
780
|
|
|
776
|
-
|
|
777
|
-
|
|
781
|
+
!!! warning "Important"
|
|
782
|
+
|
|
783
|
+
Chain state reflects the first (outermost) task result. Subtasks maintain their own states.
|
|
778
784
|
|
|
779
785
|
```ruby
|
|
780
786
|
result = ImportDataset.execute(dataset_id: 456)
|
|
@@ -795,21 +801,17 @@ chain.results.each_with_index do |result, index|
|
|
|
795
801
|
end
|
|
796
802
|
```
|
|
797
803
|
|
|
798
|
-
---
|
|
799
|
-
|
|
800
|
-
url: https://github.com/drexed/cmdx/blob/main/docs/interruptions/halt.md
|
|
801
|
-
---
|
|
802
|
-
|
|
803
804
|
# Interruptions - Halt
|
|
804
805
|
|
|
805
|
-
|
|
806
|
+
Stop task execution intentionally using `skip!` or `fail!`. Both methods signal clear intent about why execution stopped.
|
|
806
807
|
|
|
807
808
|
## Skipping
|
|
808
809
|
|
|
809
|
-
`skip!`
|
|
810
|
+
Use `skip!` when the task doesn't need to run. It's a no-op, not an error.
|
|
811
|
+
|
|
812
|
+
!!! warning "Important"
|
|
810
813
|
|
|
811
|
-
|
|
812
|
-
> Skipping is a no-op, not a failure or error and are considered successful outcomes.
|
|
814
|
+
Skipped tasks are considered "good" outcomes—they succeeded by doing nothing.
|
|
813
815
|
|
|
814
816
|
```ruby
|
|
815
817
|
class ProcessInventory < CMDx::Task
|
|
@@ -844,7 +846,7 @@ result.reason #=> "Warehouse closed"
|
|
|
844
846
|
|
|
845
847
|
## Failing
|
|
846
848
|
|
|
847
|
-
`fail!`
|
|
849
|
+
Use `fail!` when the task can't complete successfully. It signals controlled, intentional failure:
|
|
848
850
|
|
|
849
851
|
```ruby
|
|
850
852
|
class ProcessRefund < CMDx::Task
|
|
@@ -879,7 +881,7 @@ result.reason #=> "Refund period has expired"
|
|
|
879
881
|
|
|
880
882
|
## Metadata Enrichment
|
|
881
883
|
|
|
882
|
-
|
|
884
|
+
Enrich halt calls with metadata for better debugging and error handling:
|
|
883
885
|
|
|
884
886
|
```ruby
|
|
885
887
|
class ProcessRenewal < CMDx::Task
|
|
@@ -979,7 +981,7 @@ end
|
|
|
979
981
|
|
|
980
982
|
## Best Practices
|
|
981
983
|
|
|
982
|
-
Always
|
|
984
|
+
Always provide a reason for better debugging and clearer exception messages:
|
|
983
985
|
|
|
984
986
|
```ruby
|
|
985
987
|
# Good: Clear, specific reason
|
|
@@ -997,10 +999,11 @@ fail! #=> "Unspecified"
|
|
|
997
999
|
|
|
998
1000
|
## Manual Errors
|
|
999
1001
|
|
|
1000
|
-
|
|
1002
|
+
For rare cases, manually add errors before halting:
|
|
1001
1003
|
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
+
!!! warning "Important"
|
|
1005
|
+
|
|
1006
|
+
Manual errors don't stop execution—you still need to call `fail!` or `skip!`.
|
|
1004
1007
|
|
|
1005
1008
|
```ruby
|
|
1006
1009
|
class ProcessRenewal < CMDx::Task
|
|
@@ -1015,14 +1018,9 @@ class ProcessRenewal < CMDx::Task
|
|
|
1015
1018
|
end
|
|
1016
1019
|
```
|
|
1017
1020
|
|
|
1018
|
-
---
|
|
1019
|
-
|
|
1020
|
-
url: https://github.com/drexed/cmdx/blob/main/docs/interruptions/faults.md
|
|
1021
|
-
---
|
|
1022
|
-
|
|
1023
1021
|
# Interruptions - Faults
|
|
1024
1022
|
|
|
1025
|
-
Faults are
|
|
1023
|
+
Faults are exceptions raised by `execute!` when tasks halt. They carry rich context about execution state, enabling sophisticated error handling patterns.
|
|
1026
1024
|
|
|
1027
1025
|
## Fault Types
|
|
1028
1026
|
|
|
@@ -1032,8 +1030,9 @@ Faults are exception mechanisms that halt task execution via `skip!` and `fail!`
|
|
|
1032
1030
|
| `CMDx::SkipFault` | `skip!` method | Optional processing, early returns |
|
|
1033
1031
|
| `CMDx::FailFault` | `fail!` method | Validation errors, processing failures |
|
|
1034
1032
|
|
|
1035
|
-
|
|
1036
|
-
|
|
1033
|
+
!!! warning "Important"
|
|
1034
|
+
|
|
1035
|
+
All faults inherit from `CMDx::Fault` and expose result, task, context, and chain data.
|
|
1037
1036
|
|
|
1038
1037
|
## Fault Handling
|
|
1039
1038
|
|
|
@@ -1054,7 +1053,7 @@ end
|
|
|
1054
1053
|
|
|
1055
1054
|
## Data Access
|
|
1056
1055
|
|
|
1057
|
-
|
|
1056
|
+
Access rich execution data from fault exceptions:
|
|
1058
1057
|
|
|
1059
1058
|
```ruby
|
|
1060
1059
|
begin
|
|
@@ -1083,7 +1082,7 @@ end
|
|
|
1083
1082
|
|
|
1084
1083
|
### Task-Specific Matching
|
|
1085
1084
|
|
|
1086
|
-
|
|
1085
|
+
Handle faults only from specific tasks using `for?`:
|
|
1087
1086
|
|
|
1088
1087
|
```ruby
|
|
1089
1088
|
begin
|
|
@@ -1113,7 +1112,7 @@ end
|
|
|
1113
1112
|
|
|
1114
1113
|
## Fault Propagation
|
|
1115
1114
|
|
|
1116
|
-
|
|
1115
|
+
Propagate failures with `throw!` to preserve context and maintain the error chain:
|
|
1117
1116
|
|
|
1118
1117
|
### Basic Propagation
|
|
1119
1118
|
|
|
@@ -1160,7 +1159,7 @@ end
|
|
|
1160
1159
|
|
|
1161
1160
|
## Chain Analysis
|
|
1162
1161
|
|
|
1163
|
-
|
|
1162
|
+
Trace fault origins and propagation through the execution chain:
|
|
1164
1163
|
|
|
1165
1164
|
```ruby
|
|
1166
1165
|
result = DocumentWorkflow.execute(invalid_data)
|
|
@@ -1189,23 +1188,19 @@ if result.failed?
|
|
|
1189
1188
|
end
|
|
1190
1189
|
```
|
|
1191
1190
|
|
|
1192
|
-
---
|
|
1193
|
-
|
|
1194
|
-
url: https://github.com/drexed/cmdx/blob/main/docs/interruptions/exceptions.md
|
|
1195
|
-
---
|
|
1196
|
-
|
|
1197
1191
|
# Interruptions - Exceptions
|
|
1198
1192
|
|
|
1199
|
-
|
|
1193
|
+
Exception handling differs between `execute` and `execute!`. Choose the method that matches your error handling strategy.
|
|
1200
1194
|
|
|
1201
1195
|
## Exception Handling
|
|
1202
1196
|
|
|
1203
|
-
|
|
1204
|
-
|
|
1197
|
+
!!! warning "Important"
|
|
1198
|
+
|
|
1199
|
+
Prefer `skip!` and `fail!` over raising exceptions—they signal intent more clearly.
|
|
1205
1200
|
|
|
1206
1201
|
### Non-bang execution
|
|
1207
1202
|
|
|
1208
|
-
|
|
1203
|
+
Captures all exceptions and returns them as failed results:
|
|
1209
1204
|
|
|
1210
1205
|
```ruby
|
|
1211
1206
|
class CompressDocument < CMDx::Task
|
|
@@ -1223,12 +1218,13 @@ result.reason #=> "[ActiveRecord::NotFoundError] record not found"
|
|
|
1223
1218
|
result.cause #=> <ActiveRecord::NotFoundError>
|
|
1224
1219
|
```
|
|
1225
1220
|
|
|
1226
|
-
|
|
1227
|
-
|
|
1221
|
+
!!! note
|
|
1222
|
+
|
|
1223
|
+
Use `exception_handler` with `execute` to send exceptions to APM tools before they become failed results.
|
|
1228
1224
|
|
|
1229
1225
|
### Bang execution
|
|
1230
1226
|
|
|
1231
|
-
|
|
1227
|
+
Lets exceptions propagate naturally for standard Ruby error handling:
|
|
1232
1228
|
|
|
1233
1229
|
```ruby
|
|
1234
1230
|
class CompressDocument < CMDx::Task
|
|
@@ -1245,21 +1241,17 @@ rescue ActiveRecord::NotFoundError => e
|
|
|
1245
1241
|
end
|
|
1246
1242
|
```
|
|
1247
1243
|
|
|
1248
|
-
---
|
|
1249
|
-
|
|
1250
|
-
url: https://github.com/drexed/cmdx/blob/main/docs/outcomes/result.md
|
|
1251
|
-
---
|
|
1252
|
-
|
|
1253
1244
|
# Outcomes - Result
|
|
1254
1245
|
|
|
1255
|
-
|
|
1246
|
+
Results are your window into task execution. They expose everything: outcome, state, timing, context, and metadata.
|
|
1256
1247
|
|
|
1257
1248
|
## Result Attributes
|
|
1258
1249
|
|
|
1259
|
-
|
|
1250
|
+
Access essential execution information:
|
|
1251
|
+
|
|
1252
|
+
!!! warning "Important"
|
|
1260
1253
|
|
|
1261
|
-
|
|
1262
|
-
> Result objects are immutable after task execution completes and reflect the final state.
|
|
1254
|
+
Results are immutable after execution completes.
|
|
1263
1255
|
|
|
1264
1256
|
```ruby
|
|
1265
1257
|
result = BuildApplication.execute(version: "1.2.3")
|
|
@@ -1281,7 +1273,7 @@ result.metadata #=> { error_code: "BUILD_TOOL.NOT_FOUND" }
|
|
|
1281
1273
|
|
|
1282
1274
|
## Lifecycle Information
|
|
1283
1275
|
|
|
1284
|
-
|
|
1276
|
+
Check execution state and status with predicate methods:
|
|
1285
1277
|
|
|
1286
1278
|
```ruby
|
|
1287
1279
|
result = BuildApplication.execute(version: "1.2.3")
|
|
@@ -1303,7 +1295,7 @@ result.bad? #=> false (skipped or failed)
|
|
|
1303
1295
|
|
|
1304
1296
|
## Outcome Analysis
|
|
1305
1297
|
|
|
1306
|
-
|
|
1298
|
+
Get a unified outcome string combining state and status:
|
|
1307
1299
|
|
|
1308
1300
|
```ruby
|
|
1309
1301
|
result = BuildApplication.execute(version: "1.2.3")
|
|
@@ -1313,7 +1305,7 @@ result.outcome #=> "success" (state and status)
|
|
|
1313
1305
|
|
|
1314
1306
|
## Chain Analysis
|
|
1315
1307
|
|
|
1316
|
-
|
|
1308
|
+
Trace fault origins and propagation:
|
|
1317
1309
|
|
|
1318
1310
|
```ruby
|
|
1319
1311
|
result = DeploymentWorkflow.execute(app_name: "webapp")
|
|
@@ -1354,7 +1346,7 @@ result.chain.results[result.index] == result #=> true
|
|
|
1354
1346
|
|
|
1355
1347
|
## Block Yield
|
|
1356
1348
|
|
|
1357
|
-
|
|
1349
|
+
Execute code with direct result access:
|
|
1358
1350
|
|
|
1359
1351
|
```ruby
|
|
1360
1352
|
BuildApplication.execute(version: "1.2.3") do |result|
|
|
@@ -1370,7 +1362,7 @@ end
|
|
|
1370
1362
|
|
|
1371
1363
|
## Handlers
|
|
1372
1364
|
|
|
1373
|
-
|
|
1365
|
+
Handle outcomes with functional-style methods. Handlers return the result for chaining:
|
|
1374
1366
|
|
|
1375
1367
|
```ruby
|
|
1376
1368
|
result = BuildApplication.execute(version: "1.2.3")
|
|
@@ -1394,10 +1386,11 @@ result
|
|
|
1394
1386
|
|
|
1395
1387
|
## Pattern Matching
|
|
1396
1388
|
|
|
1397
|
-
|
|
1389
|
+
Use Ruby 3.0+ pattern matching for elegant outcome handling:
|
|
1390
|
+
|
|
1391
|
+
!!! warning "Important"
|
|
1398
1392
|
|
|
1399
|
-
|
|
1400
|
-
> Pattern matching requires Ruby 3.0+
|
|
1393
|
+
Pattern matching works with both array and hash deconstruction.
|
|
1401
1394
|
|
|
1402
1395
|
### Array Pattern
|
|
1403
1396
|
|
|
@@ -1442,17 +1435,9 @@ in { runtime: time } if time > performance_threshold
|
|
|
1442
1435
|
end
|
|
1443
1436
|
```
|
|
1444
1437
|
|
|
1445
|
-
---
|
|
1446
|
-
|
|
1447
|
-
url: https://github.com/drexed/cmdx/blob/main/docs/outcomes/states.md
|
|
1448
|
-
---
|
|
1449
|
-
|
|
1450
1438
|
# Outcomes - States
|
|
1451
1439
|
|
|
1452
|
-
States
|
|
1453
|
-
the progress of tasks through their complete execution journey. States provide
|
|
1454
|
-
insight into where a task is in its lifecycle and enable lifecycle-based
|
|
1455
|
-
decision making and monitoring.
|
|
1440
|
+
States track where a task is in its execution lifecycle—from creation through completion or interruption.
|
|
1456
1441
|
|
|
1457
1442
|
## Definitions
|
|
1458
1443
|
|
|
@@ -1476,8 +1461,9 @@ State-Status combinations:
|
|
|
1476
1461
|
|
|
1477
1462
|
## Transitions
|
|
1478
1463
|
|
|
1479
|
-
|
|
1480
|
-
|
|
1464
|
+
!!! danger "Caution"
|
|
1465
|
+
|
|
1466
|
+
States are managed automatically—never modify them manually.
|
|
1481
1467
|
|
|
1482
1468
|
```ruby
|
|
1483
1469
|
# Valid state transition flow
|
|
@@ -1504,7 +1490,7 @@ result.executed? #=> true (complete OR interrupted)
|
|
|
1504
1490
|
|
|
1505
1491
|
## Handlers
|
|
1506
1492
|
|
|
1507
|
-
|
|
1493
|
+
Handle lifecycle events with state-based handlers. Use `handle_executed` for cleanup that runs regardless of outcome:
|
|
1508
1494
|
|
|
1509
1495
|
```ruby
|
|
1510
1496
|
result = ProcessVideoUpload.execute
|
|
@@ -1516,14 +1502,9 @@ result
|
|
|
1516
1502
|
.handle_executed { |result| log_upload_metrics(result) }
|
|
1517
1503
|
```
|
|
1518
1504
|
|
|
1519
|
-
---
|
|
1520
|
-
|
|
1521
|
-
url: https://github.com/drexed/cmdx/blob/main/docs/outcomes/statuses.md
|
|
1522
|
-
---
|
|
1523
|
-
|
|
1524
1505
|
# Outcomes - Statuses
|
|
1525
1506
|
|
|
1526
|
-
Statuses represent the business outcome
|
|
1507
|
+
Statuses represent the business outcome—did the task succeed, skip, or fail? This differs from state, which tracks the execution lifecycle.
|
|
1527
1508
|
|
|
1528
1509
|
## Definitions
|
|
1529
1510
|
|
|
@@ -1535,8 +1516,9 @@ Statuses represent the business outcome of task execution logic, indicating how
|
|
|
1535
1516
|
|
|
1536
1517
|
## Transitions
|
|
1537
1518
|
|
|
1538
|
-
|
|
1539
|
-
|
|
1519
|
+
!!! warning "Important"
|
|
1520
|
+
|
|
1521
|
+
Status transitions are final and unidirectional. Once skipped or failed, tasks can't return to success.
|
|
1540
1522
|
|
|
1541
1523
|
```ruby
|
|
1542
1524
|
# Valid status transitions
|
|
@@ -1569,7 +1551,7 @@ result.bad? #=> true if skipped OR failed (not success)
|
|
|
1569
1551
|
|
|
1570
1552
|
## Handlers
|
|
1571
1553
|
|
|
1572
|
-
|
|
1554
|
+
Branch business logic with status-based handlers. Use `handle_good` and `handle_bad` for success/skip vs failed outcomes:
|
|
1573
1555
|
|
|
1574
1556
|
```ruby
|
|
1575
1557
|
result = ProcessNotification.execute
|
|
@@ -1586,19 +1568,15 @@ result
|
|
|
1586
1568
|
.handle_bad { |result| track_delivery_failure(result) }
|
|
1587
1569
|
```
|
|
1588
1570
|
|
|
1589
|
-
---
|
|
1590
|
-
|
|
1591
|
-
url: https://github.com/drexed/cmdx/blob/main/docs/attributes/definitions.md
|
|
1592
|
-
---
|
|
1593
|
-
|
|
1594
1571
|
# Attributes - Definitions
|
|
1595
1572
|
|
|
1596
|
-
Attributes define
|
|
1573
|
+
Attributes define your task's interface with automatic validation, type coercion, and accessor generation. They're the contract between callers and your business logic.
|
|
1597
1574
|
|
|
1598
1575
|
## Declarations
|
|
1599
1576
|
|
|
1600
|
-
|
|
1601
|
-
|
|
1577
|
+
!!! tip
|
|
1578
|
+
|
|
1579
|
+
Prefer using the `required` and `optional` alias for `attributes` for brevity and to clearly signal intent.
|
|
1602
1580
|
|
|
1603
1581
|
### Optional
|
|
1604
1582
|
|
|
@@ -1667,7 +1645,7 @@ PublishArticle.execute(
|
|
|
1667
1645
|
|
|
1668
1646
|
## Sources
|
|
1669
1647
|
|
|
1670
|
-
Attributes
|
|
1648
|
+
Attributes read from any accessible object—not just context. Use sources to pull data from models, services, or any callable:
|
|
1671
1649
|
|
|
1672
1650
|
### Context
|
|
1673
1651
|
|
|
@@ -1747,10 +1725,11 @@ end
|
|
|
1747
1725
|
|
|
1748
1726
|
## Nesting
|
|
1749
1727
|
|
|
1750
|
-
|
|
1728
|
+
Build complex structures with nested attributes. Children inherit their parent as source and support all attribute options:
|
|
1751
1729
|
|
|
1752
|
-
|
|
1753
|
-
|
|
1730
|
+
!!! note
|
|
1731
|
+
|
|
1732
|
+
Nested attributes support all features: naming, coercions, validations, defaults, and more.
|
|
1754
1733
|
|
|
1755
1734
|
```ruby
|
|
1756
1735
|
class ConfigureServer < CMDx::Task
|
|
@@ -1803,15 +1782,17 @@ ConfigureServer.execute(
|
|
|
1803
1782
|
)
|
|
1804
1783
|
```
|
|
1805
1784
|
|
|
1806
|
-
|
|
1807
|
-
|
|
1785
|
+
!!! warning "Important"
|
|
1786
|
+
|
|
1787
|
+
Child requirements only apply when the parent is provided—perfect for optional structures.
|
|
1808
1788
|
|
|
1809
1789
|
## Error Handling
|
|
1810
1790
|
|
|
1811
|
-
|
|
1791
|
+
Validation failures provide detailed, structured error messages:
|
|
1792
|
+
|
|
1793
|
+
!!! note
|
|
1812
1794
|
|
|
1813
|
-
|
|
1814
|
-
> Nested attributes are only ever evaluated when the parent attribute is available and valid.
|
|
1795
|
+
Nested attributes are only validated when their parent is present and valid.
|
|
1815
1796
|
|
|
1816
1797
|
```ruby
|
|
1817
1798
|
class ConfigureServer < CMDx::Task
|
|
@@ -1861,17 +1842,13 @@ result.metadata #=> {
|
|
|
1861
1842
|
# }
|
|
1862
1843
|
```
|
|
1863
1844
|
|
|
1864
|
-
---
|
|
1865
|
-
|
|
1866
|
-
url: https://github.com/drexed/cmdx/blob/main/docs/attributes/naming.md
|
|
1867
|
-
---
|
|
1868
|
-
|
|
1869
1845
|
# Attributes - Naming
|
|
1870
1846
|
|
|
1871
|
-
|
|
1847
|
+
Customize accessor method names to avoid conflicts and improve clarity. Affixing changes only the generated methods—not the original attribute names.
|
|
1848
|
+
|
|
1849
|
+
!!! note
|
|
1872
1850
|
|
|
1873
|
-
|
|
1874
|
-
> Affixing modifies only the generated accessor method names within tasks.
|
|
1851
|
+
Use naming when attributes conflict with existing methods or need better clarity in your code.
|
|
1875
1852
|
|
|
1876
1853
|
## Prefix
|
|
1877
1854
|
|
|
@@ -1934,16 +1911,11 @@ end
|
|
|
1934
1911
|
ScheduleMaintenance.execute(scheduled_at: DateTime.new(2024, 12, 15, 2, 0, 0))
|
|
1935
1912
|
```
|
|
1936
1913
|
|
|
1937
|
-
---
|
|
1938
|
-
|
|
1939
|
-
url: https://github.com/drexed/cmdx/blob/main/docs/attributes/coercions.md
|
|
1940
|
-
---
|
|
1941
|
-
|
|
1942
1914
|
# Attributes - Coercions
|
|
1943
1915
|
|
|
1944
|
-
|
|
1916
|
+
Automatically convert inputs to expected types. Coercions handle everything from simple string-to-integer conversions to JSON parsing.
|
|
1945
1917
|
|
|
1946
|
-
|
|
1918
|
+
See [Global Configuration](https://github.com/drexed/cmdx/blob/main/docs/getting_started.md#coercions) for custom coercion setup.
|
|
1947
1919
|
|
|
1948
1920
|
## Usage
|
|
1949
1921
|
|
|
@@ -1974,8 +1946,9 @@ ParseMetrics.execute(
|
|
|
1974
1946
|
)
|
|
1975
1947
|
```
|
|
1976
1948
|
|
|
1977
|
-
|
|
1978
|
-
|
|
1949
|
+
!!! tip
|
|
1950
|
+
|
|
1951
|
+
Specify multiple coercion types for attributes that could be a variety of value formats. CMDx attempts each type in order until one succeeds.
|
|
1979
1952
|
|
|
1980
1953
|
## Built-in Coercions
|
|
1981
1954
|
|
|
@@ -1997,8 +1970,9 @@ ParseMetrics.execute(
|
|
|
1997
1970
|
|
|
1998
1971
|
## Declarations
|
|
1999
1972
|
|
|
2000
|
-
|
|
2001
|
-
|
|
1973
|
+
!!! warning "Important"
|
|
1974
|
+
|
|
1975
|
+
Custom coercions must raise `CMDx::CoercionError` with a descriptive message.
|
|
2002
1976
|
|
|
2003
1977
|
### Proc or Lambda
|
|
2004
1978
|
|
|
@@ -2048,10 +2022,11 @@ end
|
|
|
2048
2022
|
|
|
2049
2023
|
## Removals
|
|
2050
2024
|
|
|
2051
|
-
Remove
|
|
2025
|
+
Remove unwanted coercions:
|
|
2026
|
+
|
|
2027
|
+
!!! warning
|
|
2052
2028
|
|
|
2053
|
-
|
|
2054
|
-
> Only one removal operation is allowed per `deregister` call. Multiple removals require separate calls.
|
|
2029
|
+
Each `deregister` call removes one coercion. Use multiple calls for batch removals.
|
|
2055
2030
|
|
|
2056
2031
|
```ruby
|
|
2057
2032
|
class TransformCoordinates < CMDx::Task
|
|
@@ -2092,16 +2067,11 @@ result.metadata #=> {
|
|
|
2092
2067
|
# }
|
|
2093
2068
|
```
|
|
2094
2069
|
|
|
2095
|
-
---
|
|
2096
|
-
|
|
2097
|
-
url: https://github.com/drexed/cmdx/blob/main/docs/attributes/validations.md
|
|
2098
|
-
---
|
|
2099
|
-
|
|
2100
2070
|
# Attributes - Validations
|
|
2101
2071
|
|
|
2102
|
-
|
|
2072
|
+
Ensure inputs meet requirements before execution. Validations run after coercions, giving you declarative data integrity checks.
|
|
2103
2073
|
|
|
2104
|
-
|
|
2074
|
+
See [Global Configuration](https://github.com/drexed/cmdx/blob/main/docs/getting_started.md#validators) for custom validator setup.
|
|
2105
2075
|
|
|
2106
2076
|
## Usage
|
|
2107
2077
|
|
|
@@ -2137,8 +2107,9 @@ ProcessSubscription.execute(
|
|
|
2137
2107
|
)
|
|
2138
2108
|
```
|
|
2139
2109
|
|
|
2140
|
-
|
|
2141
|
-
|
|
2110
|
+
!!! tip
|
|
2111
|
+
|
|
2112
|
+
Validations run after coercions, so you can validate the final coerced values rather than raw input.
|
|
2142
2113
|
|
|
2143
2114
|
## Built-in Validators
|
|
2144
2115
|
|
|
@@ -2293,8 +2264,9 @@ end
|
|
|
2293
2264
|
|
|
2294
2265
|
## Declarations
|
|
2295
2266
|
|
|
2296
|
-
|
|
2297
|
-
|
|
2267
|
+
!!! warning "Important"
|
|
2268
|
+
|
|
2269
|
+
Custom validators must raise `CMDx::ValidationError` with a descriptive message.
|
|
2298
2270
|
|
|
2299
2271
|
### Proc or Lambda
|
|
2300
2272
|
|
|
@@ -2340,10 +2312,11 @@ end
|
|
|
2340
2312
|
|
|
2341
2313
|
## Removals
|
|
2342
2314
|
|
|
2343
|
-
Remove
|
|
2315
|
+
Remove unwanted validators:
|
|
2344
2316
|
|
|
2345
|
-
|
|
2346
|
-
|
|
2317
|
+
!!! warning
|
|
2318
|
+
|
|
2319
|
+
Each `deregister` call removes one validator. Use multiple calls for batch removals.
|
|
2347
2320
|
|
|
2348
2321
|
```ruby
|
|
2349
2322
|
class SetupApplication < CMDx::Task
|
|
@@ -2353,7 +2326,7 @@ end
|
|
|
2353
2326
|
|
|
2354
2327
|
## Error Handling
|
|
2355
2328
|
|
|
2356
|
-
Validation failures provide detailed
|
|
2329
|
+
Validation failures provide detailed, structured error messages:
|
|
2357
2330
|
|
|
2358
2331
|
```ruby
|
|
2359
2332
|
class CreateProject < CMDx::Task
|
|
@@ -2390,18 +2363,13 @@ result.metadata #=> {
|
|
|
2390
2363
|
# }
|
|
2391
2364
|
```
|
|
2392
2365
|
|
|
2393
|
-
---
|
|
2394
|
-
|
|
2395
|
-
url: https://github.com/drexed/cmdx/blob/main/docs/attributes/defaults.md
|
|
2396
|
-
---
|
|
2397
|
-
|
|
2398
2366
|
# Attributes - Defaults
|
|
2399
2367
|
|
|
2400
|
-
|
|
2368
|
+
Provide fallback values for optional attributes. Defaults kick in when values aren't provided or are `nil`.
|
|
2401
2369
|
|
|
2402
2370
|
## Declarations
|
|
2403
2371
|
|
|
2404
|
-
Defaults
|
|
2372
|
+
Defaults work seamlessly with coercions, validations, and nested attributes:
|
|
2405
2373
|
|
|
2406
2374
|
### Static Values
|
|
2407
2375
|
|
|
@@ -2461,7 +2429,7 @@ end
|
|
|
2461
2429
|
|
|
2462
2430
|
## Coercions and Validations
|
|
2463
2431
|
|
|
2464
|
-
Defaults
|
|
2432
|
+
Defaults follow the same coercion and validation rules as provided values:
|
|
2465
2433
|
|
|
2466
2434
|
```ruby
|
|
2467
2435
|
class ScheduleBackup < CMDx::Task
|
|
@@ -2473,14 +2441,9 @@ class ScheduleBackup < CMDx::Task
|
|
|
2473
2441
|
end
|
|
2474
2442
|
```
|
|
2475
2443
|
|
|
2476
|
-
---
|
|
2477
|
-
|
|
2478
|
-
url: https://github.com/drexed/cmdx/blob/main/docs/attributes/transformations.md
|
|
2479
|
-
---
|
|
2480
|
-
|
|
2481
2444
|
# Attributes - Transformations
|
|
2482
2445
|
|
|
2483
|
-
|
|
2446
|
+
Modify attribute values after coercion but before validation. Perfect for normalization, formatting, and data cleanup.
|
|
2484
2447
|
|
|
2485
2448
|
## Declarations
|
|
2486
2449
|
|
|
@@ -2530,7 +2493,7 @@ end
|
|
|
2530
2493
|
|
|
2531
2494
|
## Validations
|
|
2532
2495
|
|
|
2533
|
-
|
|
2496
|
+
Validations run on transformed values, ensuring data consistency:
|
|
2534
2497
|
|
|
2535
2498
|
```ruby
|
|
2536
2499
|
class ScheduleBackup < CMDx::Task
|
|
@@ -2542,39 +2505,30 @@ class ScheduleBackup < CMDx::Task
|
|
|
2542
2505
|
end
|
|
2543
2506
|
```
|
|
2544
2507
|
|
|
2545
|
-
---
|
|
2546
|
-
|
|
2547
|
-
- **Prev:** [Attributes - Defaults](defaults.md)
|
|
2548
|
-
- **Next:** [Callbacks](../callbacks.md)
|
|
2549
|
-
|
|
2550
|
-
---
|
|
2551
|
-
|
|
2552
|
-
url: https://github.com/drexed/cmdx/blob/main/docs/callbacks.md
|
|
2553
|
-
---
|
|
2554
|
-
|
|
2555
2508
|
# Callbacks
|
|
2556
2509
|
|
|
2557
|
-
|
|
2510
|
+
Run custom logic at specific points during task execution. Callbacks have full access to task context and results, making them perfect for logging, notifications, cleanup, and more.
|
|
2558
2511
|
|
|
2559
|
-
|
|
2512
|
+
See [Global Configuration](https://github.com/drexed/cmdx/blob/main/docs/getting_started.md#callbacks) for framework-wide callback setup.
|
|
2560
2513
|
|
|
2561
|
-
|
|
2562
|
-
|
|
2514
|
+
!!! warning "Important"
|
|
2515
|
+
|
|
2516
|
+
Callbacks execute in declaration order (FIFO). Multiple callbacks of the same type run sequentially.
|
|
2563
2517
|
|
|
2564
2518
|
## Available Callbacks
|
|
2565
2519
|
|
|
2566
|
-
Callbacks execute in
|
|
2520
|
+
Callbacks execute in a predictable lifecycle order:
|
|
2567
2521
|
|
|
2568
2522
|
```ruby
|
|
2569
2523
|
1. before_validation # Pre-validation setup
|
|
2570
|
-
2. before_execution #
|
|
2524
|
+
2. before_execution # Prepare for execution
|
|
2571
2525
|
|
|
2572
|
-
# --- Task#work
|
|
2526
|
+
# --- Task#work executes ---
|
|
2573
2527
|
|
|
2574
|
-
3. on_[complete|interrupted] #
|
|
2575
|
-
4. on_executed #
|
|
2576
|
-
5. on_[success|skipped|failed] #
|
|
2577
|
-
6. on_[good|bad] #
|
|
2528
|
+
3. on_[complete|interrupted] # State-based (execution lifecycle)
|
|
2529
|
+
4. on_executed # Always runs after work completes
|
|
2530
|
+
5. on_[success|skipped|failed] # Status-based (business outcome)
|
|
2531
|
+
6. on_[good|bad] # Outcome-based (success/skip vs fail)
|
|
2578
2532
|
```
|
|
2579
2533
|
|
|
2580
2534
|
## Declarations
|
|
@@ -2626,7 +2580,7 @@ end
|
|
|
2626
2580
|
|
|
2627
2581
|
### Class or Module
|
|
2628
2582
|
|
|
2629
|
-
Implement reusable callback logic in dedicated classes:
|
|
2583
|
+
Implement reusable callback logic in dedicated modules and classes:
|
|
2630
2584
|
|
|
2631
2585
|
```ruby
|
|
2632
2586
|
class BookingConfirmationCallback
|
|
@@ -2693,10 +2647,11 @@ end
|
|
|
2693
2647
|
|
|
2694
2648
|
## Callback Removal
|
|
2695
2649
|
|
|
2696
|
-
Remove callbacks
|
|
2650
|
+
Remove unwanted callbacks dynamically:
|
|
2651
|
+
|
|
2652
|
+
!!! warning "Important"
|
|
2697
2653
|
|
|
2698
|
-
|
|
2699
|
-
> Only one removal operation is allowed per `deregister` call. Multiple removals require separate calls.
|
|
2654
|
+
Each `deregister` call removes one callback. Use multiple calls for batch removals.
|
|
2700
2655
|
|
|
2701
2656
|
```ruby
|
|
2702
2657
|
class ProcessBooking < CMDx::Task
|
|
@@ -2708,23 +2663,19 @@ class ProcessBooking < CMDx::Task
|
|
|
2708
2663
|
end
|
|
2709
2664
|
```
|
|
2710
2665
|
|
|
2711
|
-
---
|
|
2712
|
-
|
|
2713
|
-
url: https://github.com/drexed/cmdx/blob/main/docs/middlewares.md
|
|
2714
|
-
---
|
|
2715
|
-
|
|
2716
2666
|
# Middlewares
|
|
2717
2667
|
|
|
2718
|
-
|
|
2668
|
+
Wrap task execution with middleware for cross-cutting concerns like authentication, caching, timeouts, and monitoring. Think Rack middleware, but for your business logic.
|
|
2719
2669
|
|
|
2720
|
-
|
|
2670
|
+
See [Global Configuration](https://github.com/drexed/cmdx/blob/main/docs/getting_started.md#middlewares) for framework-wide setup.
|
|
2721
2671
|
|
|
2722
|
-
## Order
|
|
2672
|
+
## Execution Order
|
|
2723
2673
|
|
|
2724
|
-
Middleware
|
|
2674
|
+
Middleware wraps task execution in layers, like an onion:
|
|
2725
2675
|
|
|
2726
|
-
|
|
2727
|
-
|
|
2676
|
+
!!! note
|
|
2677
|
+
|
|
2678
|
+
First registered = outermost wrapper. They execute in registration order.
|
|
2728
2679
|
|
|
2729
2680
|
```ruby
|
|
2730
2681
|
class ProcessCampaign < CMDx::Task
|
|
@@ -2800,10 +2751,11 @@ end
|
|
|
2800
2751
|
|
|
2801
2752
|
## Removals
|
|
2802
2753
|
|
|
2803
|
-
|
|
2754
|
+
Remove class or module-based middleware globally or per-task:
|
|
2755
|
+
|
|
2756
|
+
!!! warning
|
|
2804
2757
|
|
|
2805
|
-
|
|
2806
|
-
> Only one removal operation is allowed per `deregister` call. Multiple removals require separate calls.
|
|
2758
|
+
Each `deregister` call removes one middleware. Use multiple calls for batch removals.
|
|
2807
2759
|
|
|
2808
2760
|
```ruby
|
|
2809
2761
|
class ProcessCampaign < CMDx::Task
|
|
@@ -2816,7 +2768,7 @@ end
|
|
|
2816
2768
|
|
|
2817
2769
|
### Timeout
|
|
2818
2770
|
|
|
2819
|
-
|
|
2771
|
+
Prevent tasks from running too long:
|
|
2820
2772
|
|
|
2821
2773
|
```ruby
|
|
2822
2774
|
class ProcessReport < CMDx::Task
|
|
@@ -2852,7 +2804,7 @@ result.metadata #=> { limit: 3 }
|
|
|
2852
2804
|
|
|
2853
2805
|
### Correlate
|
|
2854
2806
|
|
|
2855
|
-
|
|
2807
|
+
Add correlation IDs for distributed tracing and request tracking:
|
|
2856
2808
|
|
|
2857
2809
|
```ruby
|
|
2858
2810
|
class ProcessExport < CMDx::Task
|
|
@@ -2882,8 +2834,7 @@ result.metadata #=> { correlation_id: "550e8400-e29b-41d4-a716-446655440000" }
|
|
|
2882
2834
|
|
|
2883
2835
|
### Runtime
|
|
2884
2836
|
|
|
2885
|
-
|
|
2886
|
-
The calculation uses a monotonic clock and the time is returned in milliseconds.
|
|
2837
|
+
Track task execution time in milliseconds using a monotonic clock:
|
|
2887
2838
|
|
|
2888
2839
|
```ruby
|
|
2889
2840
|
class PerformanceMonitoringCheck
|
|
@@ -2904,18 +2855,13 @@ result = ProcessExport.execute
|
|
|
2904
2855
|
result.metadata #=> { runtime: 1247 } (ms)
|
|
2905
2856
|
```
|
|
2906
2857
|
|
|
2907
|
-
---
|
|
2908
|
-
|
|
2909
|
-
url: https://github.com/drexed/cmdx/blob/main/docs/logging.md
|
|
2910
|
-
---
|
|
2911
|
-
|
|
2912
2858
|
# Logging
|
|
2913
2859
|
|
|
2914
|
-
CMDx
|
|
2860
|
+
CMDx automatically logs every task execution with structured data, making debugging and monitoring effortless. Choose from multiple formatters to match your logging infrastructure.
|
|
2915
2861
|
|
|
2916
2862
|
## Formatters
|
|
2917
2863
|
|
|
2918
|
-
|
|
2864
|
+
Choose the format that works best for your logging system:
|
|
2919
2865
|
|
|
2920
2866
|
| Formatter | Use Case | Output Style |
|
|
2921
2867
|
|-----------|----------|--------------|
|
|
@@ -2945,12 +2891,13 @@ E, [2022-07-17T18:43:15.000000 #3784] ERROR -- BillingWorkflow:
|
|
|
2945
2891
|
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"}
|
|
2946
2892
|
```
|
|
2947
2893
|
|
|
2948
|
-
|
|
2949
|
-
|
|
2894
|
+
!!! tip
|
|
2895
|
+
|
|
2896
|
+
Use logging as a low-level event stream to track all tasks in a request. Combine with correlation for powerful distributed tracing.
|
|
2950
2897
|
|
|
2951
2898
|
## Structure
|
|
2952
2899
|
|
|
2953
|
-
|
|
2900
|
+
Every log entry includes rich metadata. Available fields depend on execution context and outcome.
|
|
2954
2901
|
|
|
2955
2902
|
### Core Fields
|
|
2956
2903
|
|
|
@@ -2991,7 +2938,7 @@ All log entries include comprehensive execution metadata. Field availability dep
|
|
|
2991
2938
|
|
|
2992
2939
|
## Usage
|
|
2993
2940
|
|
|
2994
|
-
|
|
2941
|
+
Access the framework logger directly within tasks:
|
|
2995
2942
|
|
|
2996
2943
|
```ruby
|
|
2997
2944
|
class ProcessSubscription < CMDx::Task
|
|
@@ -3003,18 +2950,13 @@ class ProcessSubscription < CMDx::Task
|
|
|
3003
2950
|
end
|
|
3004
2951
|
```
|
|
3005
2952
|
|
|
3006
|
-
---
|
|
3007
|
-
|
|
3008
|
-
url: https://github.com/drexed/cmdx/blob/main/docs/internationalization.md
|
|
3009
|
-
---
|
|
3010
|
-
|
|
3011
2953
|
# Internationalization (i18n)
|
|
3012
2954
|
|
|
3013
|
-
CMDx
|
|
2955
|
+
CMDx supports 90+ languages out of the box for all error messages, validations, coercions, and faults. Error messages automatically adapt to the current `I18n.locale`, making it easy to build applications for global audiences.
|
|
3014
2956
|
|
|
3015
|
-
##
|
|
2957
|
+
## Usage
|
|
3016
2958
|
|
|
3017
|
-
|
|
2959
|
+
All error messages are automatically localized based on your current locale:
|
|
3018
2960
|
|
|
3019
2961
|
```ruby
|
|
3020
2962
|
class ProcessQuote < CMDx::Task
|
|
@@ -3033,11 +2975,11 @@ end
|
|
|
3033
2975
|
|
|
3034
2976
|
## Configuration
|
|
3035
2977
|
|
|
3036
|
-
|
|
2978
|
+
CMDx uses the `I18n` gem for localization. In Rails, locales load automatically.
|
|
3037
2979
|
|
|
3038
|
-
###
|
|
2980
|
+
### Copy Locale Files
|
|
3039
2981
|
|
|
3040
|
-
|
|
2982
|
+
Copy locale files to your Rails application's `config/locales` directory:
|
|
3041
2983
|
|
|
3042
2984
|
```bash
|
|
3043
2985
|
rails generate cmdx:locale [LOCALE]
|
|
@@ -3135,23 +3077,141 @@ rails generate cmdx:locale fr
|
|
|
3135
3077
|
- zh-TW - Chinese (Traditional)
|
|
3136
3078
|
- zh-YUE - Chinese (Yue)
|
|
3137
3079
|
|
|
3138
|
-
|
|
3080
|
+
# Retries
|
|
3081
|
+
|
|
3082
|
+
CMDx provides automatic retry functionality for tasks that encounter transient failures. This is essential for handling temporary issues like network timeouts, rate limits, or database locks without manual intervention.
|
|
3139
3083
|
|
|
3140
|
-
|
|
3141
|
-
|
|
3084
|
+
## Basic Usage
|
|
3085
|
+
|
|
3086
|
+
Configure retries upto n attempts without any delay.
|
|
3087
|
+
|
|
3088
|
+
```ruby
|
|
3089
|
+
class FetchExternalData < CMDx::Task
|
|
3090
|
+
settings retries: 3
|
|
3091
|
+
|
|
3092
|
+
def work
|
|
3093
|
+
response = HTTParty.get("https://api.example.com/data")
|
|
3094
|
+
context.data = response.parsed_response
|
|
3095
|
+
end
|
|
3096
|
+
end
|
|
3097
|
+
```
|
|
3098
|
+
|
|
3099
|
+
When an exception occurs during execution, CMDx automatically retries up to the configured limit.
|
|
3100
|
+
|
|
3101
|
+
## Selective Retries
|
|
3102
|
+
|
|
3103
|
+
By default, CMDx retries on `StandardError` and its subclasses. Narrow this to specific exception types:
|
|
3104
|
+
|
|
3105
|
+
```ruby
|
|
3106
|
+
class ProcessPayment < CMDx::Task
|
|
3107
|
+
settings retries: 5, retry_on: [Stripe::RateLimitError, Net::ReadTimeout]
|
|
3108
|
+
|
|
3109
|
+
def work
|
|
3110
|
+
# Your logic here...
|
|
3111
|
+
end
|
|
3112
|
+
end
|
|
3113
|
+
```
|
|
3114
|
+
|
|
3115
|
+
!!! warning "Important"
|
|
3116
|
+
|
|
3117
|
+
Only exceptions matching the `retry_on` configuration will trigger retries. Uncaught exceptions immediately fail the task.
|
|
3118
|
+
|
|
3119
|
+
## Retry Jitter
|
|
3120
|
+
|
|
3121
|
+
Add delays between retry attempts to avoid overwhelming external services or to implement exponential backoff strategies.
|
|
3122
|
+
|
|
3123
|
+
### Fixed Value
|
|
3124
|
+
|
|
3125
|
+
Use a numeric value to calculate linear delay (`jitter * current_retry`):
|
|
3126
|
+
|
|
3127
|
+
```ruby
|
|
3128
|
+
class ImportRecords < CMDx::Task
|
|
3129
|
+
settings retries: 3, retry_jitter: 0.5
|
|
3130
|
+
|
|
3131
|
+
def work
|
|
3132
|
+
# Delays: 0s, 0.5s (retry 1), 1.0s (retry 2), 1.5s (retry 3)
|
|
3133
|
+
context.records = ExternalAPI.fetch_records
|
|
3134
|
+
end
|
|
3135
|
+
end
|
|
3136
|
+
```
|
|
3137
|
+
|
|
3138
|
+
### Symbol References
|
|
3139
|
+
|
|
3140
|
+
Define an instance method for custom delay logic:
|
|
3141
|
+
|
|
3142
|
+
```ruby
|
|
3143
|
+
class SyncInventory < CMDx::Task
|
|
3144
|
+
settings retries: 5, retry_jitter: :exponential_backoff
|
|
3145
|
+
|
|
3146
|
+
def work
|
|
3147
|
+
context.inventory = InventoryAPI.sync
|
|
3148
|
+
end
|
|
3149
|
+
|
|
3150
|
+
private
|
|
3151
|
+
|
|
3152
|
+
def exponential_backoff(current_retry)
|
|
3153
|
+
2 ** current_retry # 2s, 4s, 8s, 16s, 32s
|
|
3154
|
+
end
|
|
3155
|
+
end
|
|
3156
|
+
```
|
|
3157
|
+
|
|
3158
|
+
### Proc or Lambda
|
|
3159
|
+
|
|
3160
|
+
Pass a proc for inline delay calculations:
|
|
3161
|
+
|
|
3162
|
+
```ruby
|
|
3163
|
+
class PollJobStatus < CMDx::Task
|
|
3164
|
+
# Proc
|
|
3165
|
+
settings retries: 10, retry_jitter: proc { |retry_count| [retry_count * 0.5, 5.0].min }
|
|
3166
|
+
|
|
3167
|
+
# Lambda
|
|
3168
|
+
settings retries: 10, retry_jitter: ->(retry_count) { [retry_count * 0.5, 5.0].min }
|
|
3169
|
+
|
|
3170
|
+
def work
|
|
3171
|
+
# Delays: 0.5s, 1.0s, 1.5s, 2.0s, 2.5s, 3.0s, 3.5s, 4.0s, 4.5s, 5.0s (capped)
|
|
3172
|
+
context.status = JobAPI.check_status(context.job_id)
|
|
3173
|
+
end
|
|
3174
|
+
end
|
|
3175
|
+
```
|
|
3176
|
+
|
|
3177
|
+
### Class or Module
|
|
3178
|
+
|
|
3179
|
+
Implement reusable delay logic in dedicated modules and classes:
|
|
3180
|
+
|
|
3181
|
+
```ruby
|
|
3182
|
+
class ExponentialBackoff
|
|
3183
|
+
def call(task, retry_count)
|
|
3184
|
+
base_delay = task.context.base_delay || 1.0
|
|
3185
|
+
[base_delay * (2 ** retry_count), 60.0].min
|
|
3186
|
+
end
|
|
3187
|
+
end
|
|
3188
|
+
|
|
3189
|
+
class FetchUserProfile < CMDx::Task
|
|
3190
|
+
# Class or Module
|
|
3191
|
+
settings retries: 4, retry_jitter: ExponentialBackoff
|
|
3192
|
+
|
|
3193
|
+
# Instance
|
|
3194
|
+
settings retries: 4, retry_jitter: ExponentialBackoff.new
|
|
3195
|
+
|
|
3196
|
+
def work
|
|
3197
|
+
# Your logic here...
|
|
3198
|
+
end
|
|
3199
|
+
end
|
|
3200
|
+
```
|
|
3142
3201
|
|
|
3143
3202
|
# Task Deprecation
|
|
3144
3203
|
|
|
3145
|
-
|
|
3204
|
+
Manage legacy tasks gracefully with built-in deprecation support. Choose how to handle deprecated tasks—log warnings for awareness, issue Ruby warnings for development, or prevent execution entirely.
|
|
3146
3205
|
|
|
3147
3206
|
## Modes
|
|
3148
3207
|
|
|
3149
3208
|
### Raise
|
|
3150
3209
|
|
|
3151
|
-
|
|
3210
|
+
Prevent task execution completely. Perfect for tasks that must no longer run.
|
|
3211
|
+
|
|
3212
|
+
!!! warning
|
|
3152
3213
|
|
|
3153
|
-
|
|
3154
|
-
> Use `:raise` mode carefully in production environments as it will break existing workflows immediately.
|
|
3214
|
+
Use `:raise` mode carefully—it will break existing workflows immediately.
|
|
3155
3215
|
|
|
3156
3216
|
```ruby
|
|
3157
3217
|
class ProcessObsoleteAPI < CMDx::Task
|
|
@@ -3168,7 +3228,7 @@ result = ProcessObsoleteAPI.execute
|
|
|
3168
3228
|
|
|
3169
3229
|
### Log
|
|
3170
3230
|
|
|
3171
|
-
|
|
3231
|
+
Allow execution while tracking deprecation in logs. Ideal for gradual migrations.
|
|
3172
3232
|
|
|
3173
3233
|
```ruby
|
|
3174
3234
|
class ProcessLegacyFormat < CMDx::Task
|
|
@@ -3191,7 +3251,7 @@ result.successful? #=> true
|
|
|
3191
3251
|
|
|
3192
3252
|
### Warn
|
|
3193
3253
|
|
|
3194
|
-
|
|
3254
|
+
Issue Ruby warnings visible during development and testing. Keeps production logs clean while alerting developers.
|
|
3195
3255
|
|
|
3196
3256
|
```ruby
|
|
3197
3257
|
class ProcessOldData < CMDx::Task
|
|
@@ -3285,21 +3345,17 @@ class OutdatedConnector < CMDx::Task
|
|
|
3285
3345
|
end
|
|
3286
3346
|
```
|
|
3287
3347
|
|
|
3288
|
-
---
|
|
3289
|
-
|
|
3290
|
-
url: https://github.com/drexed/cmdx/blob/main/docs/workflows.md
|
|
3291
|
-
---
|
|
3292
|
-
|
|
3293
3348
|
# Workflows
|
|
3294
3349
|
|
|
3295
|
-
|
|
3350
|
+
Compose multiple tasks into powerful, sequential pipelines. Workflows provide a declarative way to build complex business processes with conditional execution, shared context, and flexible error handling.
|
|
3296
3351
|
|
|
3297
3352
|
## Declarations
|
|
3298
3353
|
|
|
3299
|
-
Tasks
|
|
3354
|
+
Tasks run in declaration order (FIFO), sharing a common context across the pipeline.
|
|
3355
|
+
|
|
3356
|
+
!!! warning
|
|
3300
3357
|
|
|
3301
|
-
|
|
3302
|
-
> Do **NOT** define a `work` method in workflow tasks. The included module automatically provides the execution logic.
|
|
3358
|
+
Don't define a `work` method in workflows—the module handles execution automatically.
|
|
3303
3359
|
|
|
3304
3360
|
### Task
|
|
3305
3361
|
|
|
@@ -3314,15 +3370,17 @@ class OnboardingWorkflow < CMDx::Task
|
|
|
3314
3370
|
end
|
|
3315
3371
|
```
|
|
3316
3372
|
|
|
3317
|
-
|
|
3318
|
-
|
|
3373
|
+
!!! tip
|
|
3374
|
+
|
|
3375
|
+
Execute tasks in parallel via the [cmdx-parallel](https://github.com/drexed/cmdx-parallel) gem.
|
|
3319
3376
|
|
|
3320
3377
|
### Group
|
|
3321
3378
|
|
|
3322
|
-
Group related tasks
|
|
3379
|
+
Group related tasks to share configuration:
|
|
3323
3380
|
|
|
3324
|
-
|
|
3325
|
-
|
|
3381
|
+
!!! warning "Important"
|
|
3382
|
+
|
|
3383
|
+
Settings and conditionals apply to all tasks in the group.
|
|
3326
3384
|
|
|
3327
3385
|
```ruby
|
|
3328
3386
|
class ContentModerationWorkflow < CMDx::Task
|
|
@@ -3385,9 +3443,7 @@ end
|
|
|
3385
3443
|
|
|
3386
3444
|
## Halt Behavior
|
|
3387
3445
|
|
|
3388
|
-
By default skipped tasks
|
|
3389
|
-
This is configurable via global and task level breakpoint settings. Task and group configurations
|
|
3390
|
-
can be used together within a workflow.
|
|
3446
|
+
By default, skipped tasks don't stop the workflow—they're treated as no-ops. Configure breakpoints globally or per-task to customize this behavior.
|
|
3391
3447
|
|
|
3392
3448
|
```ruby
|
|
3393
3449
|
class AnalyticsWorkflow < CMDx::Task
|
|
@@ -3443,7 +3499,7 @@ end
|
|
|
3443
3499
|
|
|
3444
3500
|
## Nested Workflows
|
|
3445
3501
|
|
|
3446
|
-
|
|
3502
|
+
Build hierarchical workflows by composing workflows within workflows:
|
|
3447
3503
|
|
|
3448
3504
|
```ruby
|
|
3449
3505
|
class EmailPreparationWorkflow < CMDx::Task
|
|
@@ -3470,10 +3526,11 @@ end
|
|
|
3470
3526
|
|
|
3471
3527
|
## Parallel Execution
|
|
3472
3528
|
|
|
3473
|
-
|
|
3529
|
+
Run tasks concurrently using the [Parallel](https://github.com/grosser/parallel) gem. It automatically uses all available processors for maximum throughput.
|
|
3530
|
+
|
|
3531
|
+
!!! warning
|
|
3474
3532
|
|
|
3475
|
-
|
|
3476
|
-
> Context cannot be modified during parallel execution. Ensure that all required data is preloaded into the context before parallelization begins.
|
|
3533
|
+
Context is read-only during parallel execution. Load all required data beforehand.
|
|
3477
3534
|
|
|
3478
3535
|
```ruby
|
|
3479
3536
|
class SendWelcomeNotifications < CMDx::Task
|
|
@@ -3511,17 +3568,13 @@ class SendNotifications < CMDx::Task
|
|
|
3511
3568
|
end
|
|
3512
3569
|
```
|
|
3513
3570
|
|
|
3514
|
-
|
|
3515
|
-
> Use **present tense verbs + pluralized noun** for workflow task names, eg: `SendNotifications`, `DownloadFiles`, `ValidateDocuments`
|
|
3516
|
-
|
|
3517
|
-
---
|
|
3571
|
+
!!! tip
|
|
3518
3572
|
|
|
3519
|
-
|
|
3520
|
-
---
|
|
3573
|
+
Use **present tense verbs + pluralized noun** for workflow task names, eg: `SendNotifications`, `DownloadFiles`, `ValidateDocuments`
|
|
3521
3574
|
|
|
3522
3575
|
# Tips and Tricks
|
|
3523
3576
|
|
|
3524
|
-
|
|
3577
|
+
Best practices, patterns, and techniques to build maintainable CMDx applications.
|
|
3525
3578
|
|
|
3526
3579
|
## Project Organization
|
|
3527
3580
|
|
|
@@ -3565,7 +3618,7 @@ class TokenGeneration < CMDx::Task; end # ❌ Avoid
|
|
|
3565
3618
|
|
|
3566
3619
|
### Story Telling
|
|
3567
3620
|
|
|
3568
|
-
|
|
3621
|
+
Break down complex logic into descriptive methods that read like a narrative:
|
|
3569
3622
|
|
|
3570
3623
|
```ruby
|
|
3571
3624
|
class ProcessOrder < CMDx::Task
|
|
@@ -3597,7 +3650,7 @@ end
|
|
|
3597
3650
|
|
|
3598
3651
|
### Style Guide
|
|
3599
3652
|
|
|
3600
|
-
Follow
|
|
3653
|
+
Follow this order for consistent, readable tasks:
|
|
3601
3654
|
|
|
3602
3655
|
```ruby
|
|
3603
3656
|
class ExportReport < CMDx::Task
|
|
@@ -3640,7 +3693,7 @@ end
|
|
|
3640
3693
|
|
|
3641
3694
|
## Attribute Options
|
|
3642
3695
|
|
|
3643
|
-
Use
|
|
3696
|
+
Use `with_options` to reduce duplication:
|
|
3644
3697
|
|
|
3645
3698
|
```ruby
|
|
3646
3699
|
class ConfigureCompany < CMDx::Task
|
|
@@ -3666,9 +3719,9 @@ class ConfigureCompany < CMDx::Task
|
|
|
3666
3719
|
end
|
|
3667
3720
|
```
|
|
3668
3721
|
|
|
3669
|
-
##
|
|
3722
|
+
## More Examples
|
|
3670
3723
|
|
|
3671
3724
|
- [Active Record Query Tagging](https://github.com/drexed/cmdx/blob/main/examples/active_record_query_tagging.md)
|
|
3672
3725
|
- [Paper Trail Whatdunnit](https://github.com/drexed/cmdx/blob/main/examples/paper_trail_whatdunnit.md)
|
|
3726
|
+
- [Stoplight Circuit Breaker](https://github.com/drexed/cmdx/blob/main/examples/stoplight_circuit_breaker.md)
|
|
3673
3727
|
|
|
3674
|
-
---
|