cmdx 1.8.0 → 1.9.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/.DS_Store +0 -0
- data/.cursor/prompts/docs.md +3 -3
- data/.cursor/prompts/llms.md +1 -3
- data/.irbrc +14 -2
- data/CHANGELOG.md +58 -45
- data/LLM.md +150 -53
- data/README.md +23 -85
- data/docs/.DS_Store +0 -0
- data/docs/assets/favicon.ico +0 -0
- data/docs/assets/favicon.svg +1 -0
- data/docs/attributes/coercions.md +12 -24
- data/docs/attributes/defaults.md +3 -16
- data/docs/attributes/definitions.md +16 -30
- data/docs/attributes/naming.md +3 -13
- data/docs/attributes/transformations.md +63 -0
- data/docs/attributes/validations.md +14 -33
- data/docs/basics/chain.md +14 -23
- data/docs/basics/context.md +13 -22
- data/docs/basics/execution.md +8 -26
- data/docs/basics/setup.md +8 -19
- data/docs/callbacks.md +19 -32
- data/docs/deprecation.md +8 -25
- data/docs/getting_started.md +101 -77
- data/docs/index.md +120 -0
- data/docs/internationalization.md +6 -18
- data/docs/interruptions/exceptions.md +10 -16
- data/docs/interruptions/faults.md +8 -25
- data/docs/interruptions/halt.md +12 -27
- data/docs/logging.md +7 -17
- data/docs/middlewares.md +13 -29
- data/docs/outcomes/result.md +21 -38
- data/docs/outcomes/states.md +8 -22
- data/docs/outcomes/statuses.md +10 -21
- data/docs/stylesheets/extra.css +42 -0
- data/docs/tips_and_tricks.md +7 -46
- data/docs/workflows.md +23 -38
- data/examples/active_record_query_tagging.md +46 -0
- data/examples/paper_trail_whatdunnit.md +39 -0
- data/lib/cmdx/attribute.rb +6 -5
- data/lib/cmdx/attribute_value.rb +31 -10
- data/lib/cmdx/callback_registry.rb +12 -2
- data/lib/cmdx/coercions/hash.rb +2 -0
- data/lib/cmdx/configuration.rb +10 -2
- data/lib/cmdx/deprecator.rb +3 -3
- data/lib/cmdx/executor.rb +93 -7
- data/lib/cmdx/pipeline.rb +4 -4
- data/lib/cmdx/railtie.rb +9 -0
- data/lib/cmdx/result.rb +10 -1
- data/lib/cmdx/task.rb +12 -7
- data/lib/cmdx/version.rb +1 -1
- data/lib/cmdx.rb +1 -0
- data/lib/generators/cmdx/templates/install.rb +9 -0
- data/mkdocs.yml +122 -0
- data/src/cmdx-dark-logo.png +0 -0
- data/src/cmdx-favicon.svg +1 -0
- data/src/cmdx-light-logo.png +0 -0
- data/src/cmdx-logo.svg +1 -0
- metadata +14 -3
- data/lib/cmdx/freezer.rb +0 -51
- data/src/cmdx-logo.png +0 -0
data/docs/basics/context.md
CHANGED
|
@@ -1,17 +1,10 @@
|
|
|
1
1
|
# Basics - Context
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
## Table of Contents
|
|
6
|
-
|
|
7
|
-
- [Assigning Data](#assigning-data)
|
|
8
|
-
- [Accessing Data](#accessing-data)
|
|
9
|
-
- [Modifying Context](#modifying-context)
|
|
10
|
-
- [Data Sharing](#data-sharing)
|
|
3
|
+
Context is your data container for inputs, intermediate values, and outputs. It makes sharing data between tasks effortless.
|
|
11
4
|
|
|
12
5
|
## Assigning Data
|
|
13
6
|
|
|
14
|
-
Context
|
|
7
|
+
Context automatically captures all task inputs, normalizing keys to symbols:
|
|
15
8
|
|
|
16
9
|
```ruby
|
|
17
10
|
# Direct execution
|
|
@@ -21,12 +14,13 @@ CalculateShipping.execute(weight: 2.5, destination: "CA")
|
|
|
21
14
|
CalculateShipping.new(weight: 2.5, "destination" => "CA")
|
|
22
15
|
```
|
|
23
16
|
|
|
24
|
-
|
|
25
|
-
|
|
17
|
+
!!! warning "Important"
|
|
18
|
+
|
|
19
|
+
String keys convert to symbols automatically. Prefer symbols for consistency.
|
|
26
20
|
|
|
27
21
|
## Accessing Data
|
|
28
22
|
|
|
29
|
-
|
|
23
|
+
Access context data using method notation, hash keys, or safe accessors:
|
|
30
24
|
|
|
31
25
|
```ruby
|
|
32
26
|
class CalculateShipping < CMDx::Task
|
|
@@ -49,8 +43,9 @@ class CalculateShipping < CMDx::Task
|
|
|
49
43
|
end
|
|
50
44
|
```
|
|
51
45
|
|
|
52
|
-
|
|
53
|
-
|
|
46
|
+
!!! warning "Important"
|
|
47
|
+
|
|
48
|
+
Undefined attributes return `nil` instead of raising errors—perfect for optional data.
|
|
54
49
|
|
|
55
50
|
## Modifying Context
|
|
56
51
|
|
|
@@ -91,12 +86,13 @@ class CalculateShipping < CMDx::Task
|
|
|
91
86
|
end
|
|
92
87
|
```
|
|
93
88
|
|
|
94
|
-
|
|
95
|
-
|
|
89
|
+
!!! tip
|
|
90
|
+
|
|
91
|
+
Use context for both input values and intermediate results. This creates natural data flow through your task execution pipeline.
|
|
96
92
|
|
|
97
93
|
## Data Sharing
|
|
98
94
|
|
|
99
|
-
|
|
95
|
+
Share context across tasks for seamless data flow:
|
|
100
96
|
|
|
101
97
|
```ruby
|
|
102
98
|
# During execution
|
|
@@ -123,8 +119,3 @@ result = CalculateShipping.execute(destination: "New York, NY")
|
|
|
123
119
|
|
|
124
120
|
CreateShippingLabel.execute(result)
|
|
125
121
|
```
|
|
126
|
-
|
|
127
|
-
---
|
|
128
|
-
|
|
129
|
-
- **Prev:** [Basics - Execution](execution.md)
|
|
130
|
-
- **Next:** [Basics - Chain](chain.md)
|
data/docs/basics/execution.md
CHANGED
|
@@ -1,19 +1,10 @@
|
|
|
1
1
|
# Basics - Execution
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
CMDx offers two execution methods with different error handling approaches. Choose based on your needs: safe result handling or exception-based control flow.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Execution Methods
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
- [Non-bang Execution](#non-bang-execution)
|
|
9
|
-
- [Bang Execution](#bang-execution)
|
|
10
|
-
- [Direct Instantiation](#direct-instantiation)
|
|
11
|
-
- [Result Details](#result-details)
|
|
12
|
-
|
|
13
|
-
## Methods Overview
|
|
14
|
-
|
|
15
|
-
Tasks are single-use objects. Once executed, they are frozen and cannot be executed again.
|
|
16
|
-
Create a new instance for subsequent executions.
|
|
7
|
+
Both methods return results, but handle failures differently:
|
|
17
8
|
|
|
18
9
|
| Method | Returns | Exceptions | Use Case |
|
|
19
10
|
|--------|---------|------------|----------|
|
|
@@ -22,10 +13,7 @@ Create a new instance for subsequent executions.
|
|
|
22
13
|
|
|
23
14
|
## Non-bang Execution
|
|
24
15
|
|
|
25
|
-
|
|
26
|
-
This is the preferred method for most use cases.
|
|
27
|
-
|
|
28
|
-
Any unhandled exceptions will be caught and returned as a task failure.
|
|
16
|
+
Always returns a `CMDx::Result`, never raises exceptions. Perfect for most use cases.
|
|
29
17
|
|
|
30
18
|
```ruby
|
|
31
19
|
result = CreateAccount.execute(email: "user@example.com")
|
|
@@ -43,17 +31,16 @@ result.status #=> "success"
|
|
|
43
31
|
|
|
44
32
|
## Bang Execution
|
|
45
33
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
It raises any unhandled non-fault exceptions caused during execution.
|
|
34
|
+
Raises `CMDx::Fault` exceptions on failure or skip. Returns results only on success.
|
|
49
35
|
|
|
50
36
|
| Exception | Raised When |
|
|
51
37
|
|-----------|-------------|
|
|
52
38
|
| `CMDx::FailFault` | Task execution fails |
|
|
53
39
|
| `CMDx::SkipFault` | Task execution is skipped |
|
|
54
40
|
|
|
55
|
-
|
|
56
|
-
|
|
41
|
+
!!! warning "Important"
|
|
42
|
+
|
|
43
|
+
Behavior depends on `task_breakpoints` or `workflow_breakpoints` config. Default: only failures raise exceptions.
|
|
57
44
|
|
|
58
45
|
```ruby
|
|
59
46
|
begin
|
|
@@ -107,8 +94,3 @@ result.chain #=> Task execution chain
|
|
|
107
94
|
result.context #=> Context with all task data
|
|
108
95
|
result.metadata #=> Hash with execution metadata
|
|
109
96
|
```
|
|
110
|
-
|
|
111
|
-
---
|
|
112
|
-
|
|
113
|
-
- **Prev:** [Basics - Setup](setup.md)
|
|
114
|
-
- **Next:** [Basics - Context](context.md)
|
data/docs/basics/setup.md
CHANGED
|
@@ -1,16 +1,10 @@
|
|
|
1
1
|
# Basics - Setup
|
|
2
2
|
|
|
3
|
-
Tasks are the
|
|
4
|
-
|
|
5
|
-
## Table of Contents
|
|
6
|
-
|
|
7
|
-
- [Structure](#structure)
|
|
8
|
-
- [Inheritance](#inheritance)
|
|
9
|
-
- [Lifecycle](#lifecycle)
|
|
3
|
+
Tasks are the heart of CMDx—self-contained units of business logic with built-in validation, error handling, and execution tracking.
|
|
10
4
|
|
|
11
5
|
## Structure
|
|
12
6
|
|
|
13
|
-
Tasks inherit from `CMDx::Task` and
|
|
7
|
+
Tasks need only two things: inherit from `CMDx::Task` and define a `work` method:
|
|
14
8
|
|
|
15
9
|
```ruby
|
|
16
10
|
class ValidateDocument < CMDx::Task
|
|
@@ -20,7 +14,7 @@ class ValidateDocument < CMDx::Task
|
|
|
20
14
|
end
|
|
21
15
|
```
|
|
22
16
|
|
|
23
|
-
|
|
17
|
+
Without a `work` method, execution raises `CMDx::UndefinedMethodError`.
|
|
24
18
|
|
|
25
19
|
```ruby
|
|
26
20
|
class IncompleteTask < CMDx::Task
|
|
@@ -32,8 +26,7 @@ IncompleteTask.execute #=> raises CMDx::UndefinedMethodError
|
|
|
32
26
|
|
|
33
27
|
## Inheritance
|
|
34
28
|
|
|
35
|
-
|
|
36
|
-
Create a base class to share common configuration across tasks:
|
|
29
|
+
Share configuration across tasks using inheritance:
|
|
37
30
|
|
|
38
31
|
```ruby
|
|
39
32
|
class ApplicationTask < CMDx::Task
|
|
@@ -59,10 +52,11 @@ end
|
|
|
59
52
|
|
|
60
53
|
## Lifecycle
|
|
61
54
|
|
|
62
|
-
Tasks follow a predictable
|
|
55
|
+
Tasks follow a predictable execution pattern:
|
|
56
|
+
|
|
57
|
+
!!! danger "Caution"
|
|
63
58
|
|
|
64
|
-
|
|
65
|
-
> Tasks are single-use objects. Once executed, they are frozen and cannot be executed again.
|
|
59
|
+
Tasks are single-use objects. Once executed, they're frozen and immutable.
|
|
66
60
|
|
|
67
61
|
| Stage | State | Status | Description |
|
|
68
62
|
|-------|-------|--------|-------------|
|
|
@@ -71,8 +65,3 @@ Tasks follow a predictable call pattern with specific states and statuses:
|
|
|
71
65
|
| **Execution** | `executing` | `success`/`failed`/`skipped` | `work` method runs |
|
|
72
66
|
| **Completion** | `executed` | `success`/`failed`/`skipped` | Result finalized |
|
|
73
67
|
| **Freezing** | `executed` | `success`/`failed`/`skipped` | Task becomes immutable |
|
|
74
|
-
|
|
75
|
-
---
|
|
76
|
-
|
|
77
|
-
- **Prev:** [Getting Started](../getting_started.md)
|
|
78
|
-
- **Next:** [Basics - Execution](execution.md)
|
data/docs/callbacks.md
CHANGED
|
@@ -1,36 +1,27 @@
|
|
|
1
1
|
# Callbacks
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
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.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
See [Global Configuration](getting_started.md#callbacks) for framework-wide callback setup.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
> Callbacks execute in the order they are declared within each hook type. Multiple callbacks of the same type execute in declaration order (FIFO: first in, first out).
|
|
7
|
+
!!! warning "Important"
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
- [Available Callbacks](#available-callbacks)
|
|
13
|
-
- [Declarations](#declarations)
|
|
14
|
-
- [Symbol References](#symbol-references)
|
|
15
|
-
- [Proc or Lambda](#proc-or-lambda)
|
|
16
|
-
- [Class or Module](#class-or-module)
|
|
17
|
-
- [Conditional Execution](#conditional-execution)
|
|
18
|
-
- [Callback Removal](#callback-removal)
|
|
9
|
+
Callbacks execute in declaration order (FIFO). Multiple callbacks of the same type run sequentially.
|
|
19
10
|
|
|
20
11
|
## Available Callbacks
|
|
21
12
|
|
|
22
|
-
Callbacks execute in
|
|
13
|
+
Callbacks execute in a predictable lifecycle order:
|
|
23
14
|
|
|
24
15
|
```ruby
|
|
25
16
|
1. before_validation # Pre-validation setup
|
|
26
|
-
2. before_execution #
|
|
17
|
+
2. before_execution # Prepare for execution
|
|
27
18
|
|
|
28
|
-
# --- Task#work
|
|
19
|
+
# --- Task#work executes ---
|
|
29
20
|
|
|
30
|
-
3. on_[complete|interrupted] #
|
|
31
|
-
4. on_executed #
|
|
32
|
-
5. on_[success|skipped|failed] #
|
|
33
|
-
6. on_[good|bad] #
|
|
21
|
+
3. on_[complete|interrupted] # State-based (execution lifecycle)
|
|
22
|
+
4. on_executed # Always runs after work completes
|
|
23
|
+
5. on_[success|skipped|failed] # Status-based (business outcome)
|
|
24
|
+
6. on_[good|bad] # Outcome-based (success/skip vs fail)
|
|
34
25
|
```
|
|
35
26
|
|
|
36
27
|
## Declarations
|
|
@@ -73,7 +64,7 @@ Use anonymous functions for inline callback logic:
|
|
|
73
64
|
```ruby
|
|
74
65
|
class ProcessBooking < CMDx::Task
|
|
75
66
|
# Proc
|
|
76
|
-
on_interrupted proc {
|
|
67
|
+
on_interrupted proc { ReservationSystem.pause! }
|
|
77
68
|
|
|
78
69
|
# Lambda
|
|
79
70
|
on_complete -> { ReservationSystem.resume! }
|
|
@@ -120,10 +111,10 @@ class ProcessBooking < CMDx::Task
|
|
|
120
111
|
before_execution :notify_guest, if: :messaging_enabled?, unless: :messaging_blocked?
|
|
121
112
|
|
|
122
113
|
# Proc
|
|
123
|
-
on_failure :increment_failure, if: ->
|
|
114
|
+
on_failure :increment_failure, if: -> { Rails.env.production? && self.class.name.include?("Legacy") }
|
|
124
115
|
|
|
125
116
|
# Lambda
|
|
126
|
-
on_success :ping_housekeeping, if: proc {
|
|
117
|
+
on_success :ping_housekeeping, if: proc { context.rooms_need_cleaning? }
|
|
127
118
|
|
|
128
119
|
# Class or Module
|
|
129
120
|
on_complete :send_confirmation, unless: MessagingPermissionCheck
|
|
@@ -138,7 +129,7 @@ class ProcessBooking < CMDx::Task
|
|
|
138
129
|
private
|
|
139
130
|
|
|
140
131
|
def messaging_enabled?
|
|
141
|
-
context.guest.messaging_preference
|
|
132
|
+
context.guest.messaging_preference == true
|
|
142
133
|
end
|
|
143
134
|
|
|
144
135
|
def messaging_blocked?
|
|
@@ -149,10 +140,11 @@ end
|
|
|
149
140
|
|
|
150
141
|
## Callback Removal
|
|
151
142
|
|
|
152
|
-
Remove callbacks
|
|
143
|
+
Remove unwanted callbacks dynamically:
|
|
144
|
+
|
|
145
|
+
!!! warning "Important"
|
|
153
146
|
|
|
154
|
-
|
|
155
|
-
> Only one removal operation is allowed per `deregister` call. Multiple removals require separate calls.
|
|
147
|
+
Each `deregister` call removes one callback. Use multiple calls for batch removals.
|
|
156
148
|
|
|
157
149
|
```ruby
|
|
158
150
|
class ProcessBooking < CMDx::Task
|
|
@@ -163,8 +155,3 @@ class ProcessBooking < CMDx::Task
|
|
|
163
155
|
deregister :callback, :on_complete, BookingConfirmationCallback
|
|
164
156
|
end
|
|
165
157
|
```
|
|
166
|
-
|
|
167
|
-
---
|
|
168
|
-
|
|
169
|
-
- **Prev:** [Attributes - Defaults](attributes/defaults.md)
|
|
170
|
-
- **Next:** [Middlewares](middlewares.md)
|
data/docs/deprecation.md
CHANGED
|
@@ -1,28 +1,16 @@
|
|
|
1
1
|
# Task Deprecation
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
## Table of Contents
|
|
6
|
-
|
|
7
|
-
- [Modes](#modes)
|
|
8
|
-
- [Raise](#raise)
|
|
9
|
-
- [Log](#log)
|
|
10
|
-
- [Warn](#warn)
|
|
11
|
-
- [Declarations](#declarations)
|
|
12
|
-
- [Symbol or String](#symbol-or-string)
|
|
13
|
-
- [Boolean or Nil](#boolean-or-nil)
|
|
14
|
-
- [Method](#method)
|
|
15
|
-
- [Proc or Lambda](#proc-or-lambda)
|
|
16
|
-
- [Class or Module](#class-or-module)
|
|
3
|
+
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.
|
|
17
4
|
|
|
18
5
|
## Modes
|
|
19
6
|
|
|
20
7
|
### Raise
|
|
21
8
|
|
|
22
|
-
|
|
9
|
+
Prevent task execution completely. Perfect for tasks that must no longer run.
|
|
10
|
+
|
|
11
|
+
!!! warning
|
|
23
12
|
|
|
24
|
-
|
|
25
|
-
> Use `:raise` mode carefully in production environments as it will break existing workflows immediately.
|
|
13
|
+
Use `:raise` mode carefully—it will break existing workflows immediately.
|
|
26
14
|
|
|
27
15
|
```ruby
|
|
28
16
|
class ProcessObsoleteAPI < CMDx::Task
|
|
@@ -39,7 +27,7 @@ result = ProcessObsoleteAPI.execute
|
|
|
39
27
|
|
|
40
28
|
### Log
|
|
41
29
|
|
|
42
|
-
|
|
30
|
+
Allow execution while tracking deprecation in logs. Ideal for gradual migrations.
|
|
43
31
|
|
|
44
32
|
```ruby
|
|
45
33
|
class ProcessLegacyFormat < CMDx::Task
|
|
@@ -62,7 +50,7 @@ result.successful? #=> true
|
|
|
62
50
|
|
|
63
51
|
### Warn
|
|
64
52
|
|
|
65
|
-
|
|
53
|
+
Issue Ruby warnings visible during development and testing. Keeps production logs clean while alerting developers.
|
|
66
54
|
|
|
67
55
|
```ruby
|
|
68
56
|
class ProcessOldData < CMDx::Task
|
|
@@ -77,7 +65,7 @@ result = ProcessOldData.execute
|
|
|
77
65
|
result.successful? #=> true
|
|
78
66
|
|
|
79
67
|
# Ruby warning appears in stderr:
|
|
80
|
-
# [ProcessOldData] DEPRECATED: migrate to replacement or discontinue use
|
|
68
|
+
# [ProcessOldData] DEPRECATED: migrate to a replacement or discontinue use
|
|
81
69
|
```
|
|
82
70
|
|
|
83
71
|
## Declarations
|
|
@@ -155,8 +143,3 @@ class OutdatedConnector < CMDx::Task
|
|
|
155
143
|
settings(deprecated: OutdatedTaskDeprecator.new)
|
|
156
144
|
end
|
|
157
145
|
```
|
|
158
|
-
|
|
159
|
-
---
|
|
160
|
-
|
|
161
|
-
- **Prev:** [Internationalization (i18n)](internationalization.md)
|
|
162
|
-
- **Next:** [Workflows](workflows.md)
|
data/docs/getting_started.md
CHANGED
|
@@ -1,51 +1,33 @@
|
|
|
1
1
|
# Getting Started
|
|
2
2
|
|
|
3
|
-
CMDx is a Ruby framework for building maintainable, observable business logic through composable command objects.
|
|
4
|
-
|
|
5
|
-
**Common
|
|
6
|
-
|
|
7
|
-
- Inconsistent patterns across
|
|
8
|
-
-
|
|
9
|
-
- Fragile
|
|
10
|
-
|
|
11
|
-
**
|
|
12
|
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
- [Coercions](#coercions)
|
|
32
|
-
- [Validators](#validators)
|
|
33
|
-
- [Task Configuration](#task-configuration)
|
|
34
|
-
- [Settings](#settings)
|
|
35
|
-
- [Registrations](#registrations)
|
|
36
|
-
- [Configuration Management](#configuration-management)
|
|
37
|
-
- [Access](#access)
|
|
38
|
-
- [Resetting](#resetting)
|
|
39
|
-
- [Task Generator](#task-generator)
|
|
40
|
-
|
|
41
|
-
## Compose, Execute, React, Observe pattern
|
|
42
|
-
|
|
43
|
-
CMDx encourages breaking business logic into composable tasks. Each task can be combined into larger workflows, executed with standardized flow control, and fully observed through logging, validations, and context.
|
|
44
|
-
|
|
45
|
-
- *Compose* → Define small, contract-driven tasks with typed attributes, validations, and natural workflow composition.
|
|
46
|
-
- *Execute* → Run tasks with clear outcomes, intentional halts, and pluggable behaviors via middlewares and callbacks.
|
|
47
|
-
- *React* → Adapt to outcomes by chaining follow-up tasks, handling faults, or shaping future flows.
|
|
48
|
-
- *Observe* → Capture immutable results, structured logs, and full execution chains for reliable tracing and insight.
|
|
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.
|
|
4
|
+
|
|
5
|
+
**Common challenges it solves:**
|
|
6
|
+
|
|
7
|
+
- Inconsistent service object patterns across your codebase
|
|
8
|
+
- Limited logging makes debugging a nightmare
|
|
9
|
+
- Fragile error handling erodes confidence
|
|
10
|
+
|
|
11
|
+
**What you get:**
|
|
12
|
+
|
|
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
|
|
19
|
+
|
|
20
|
+
## The CERO Pattern
|
|
21
|
+
|
|
22
|
+
CMDx embraces the Compose, Execute, React, Observe (CERO) pattern—a simple yet powerful approach to building reliable business logic.
|
|
23
|
+
|
|
24
|
+
🧩 **Compose** — Define small, focused tasks with typed attributes and validations
|
|
25
|
+
|
|
26
|
+
⚡ **Execute** — Run tasks with clear outcomes and pluggable behaviors
|
|
27
|
+
|
|
28
|
+
🔄 **React** — Adapt to outcomes by chaining follow-up tasks or handling faults
|
|
29
|
+
|
|
30
|
+
🔍 **Observe** — Capture structured logs and execution chains for debugging
|
|
49
31
|
|
|
50
32
|
## Installation
|
|
51
33
|
|
|
@@ -65,39 +47,78 @@ This creates `config/initializers/cmdx.rb` file.
|
|
|
65
47
|
|
|
66
48
|
## Configuration Hierarchy
|
|
67
49
|
|
|
68
|
-
CMDx
|
|
50
|
+
CMDx uses a straightforward two-tier configuration system:
|
|
69
51
|
|
|
70
|
-
1. **Global Configuration
|
|
71
|
-
2. **Task Settings
|
|
52
|
+
1. **Global Configuration** — Framework-wide defaults
|
|
53
|
+
2. **Task Settings** — Class-level overrides using `settings`
|
|
72
54
|
|
|
73
|
-
|
|
74
|
-
|
|
55
|
+
!!! warning "Important"
|
|
56
|
+
|
|
57
|
+
Task settings take precedence over global config. Settings are inherited from parent classes and can be overridden in subclasses.
|
|
75
58
|
|
|
76
59
|
## Global Configuration
|
|
77
60
|
|
|
78
|
-
|
|
79
|
-
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.
|
|
80
62
|
|
|
81
63
|
### Breakpoints
|
|
82
64
|
|
|
83
|
-
|
|
65
|
+
Control when `execute!` raises a `CMDx::Fault` based on task status.
|
|
84
66
|
|
|
85
67
|
```ruby
|
|
86
68
|
CMDx.configure do |config|
|
|
87
|
-
# String or Array[String]
|
|
88
|
-
config.task_breakpoints = "failed"
|
|
69
|
+
config.task_breakpoints = "failed" # String or Array[String]
|
|
89
70
|
end
|
|
90
71
|
```
|
|
91
72
|
|
|
92
|
-
|
|
73
|
+
For workflows, configure which statuses halt the execution pipeline:
|
|
93
74
|
|
|
94
75
|
```ruby
|
|
95
76
|
CMDx.configure do |config|
|
|
96
|
-
# String or Array[String]
|
|
97
77
|
config.workflow_breakpoints = ["skipped", "failed"]
|
|
98
78
|
end
|
|
99
79
|
```
|
|
100
80
|
|
|
81
|
+
### Backtraces
|
|
82
|
+
|
|
83
|
+
Enable detailed backtraces for non-fault exceptions to improve debugging. Optionally clean up stack traces to remove framework noise.
|
|
84
|
+
|
|
85
|
+
!!! note
|
|
86
|
+
|
|
87
|
+
In Rails environments, `backtrace_cleaner` defaults to `Rails.backtrace_cleaner.clean`.
|
|
88
|
+
|
|
89
|
+
```ruby
|
|
90
|
+
CMDx.configure do |config|
|
|
91
|
+
# Truthy
|
|
92
|
+
config.backtrace = true
|
|
93
|
+
|
|
94
|
+
# Via callable (must respond to `call(backtrace)`)
|
|
95
|
+
config.backtrace_cleaner = AdvanceCleaner.new
|
|
96
|
+
|
|
97
|
+
# Via proc or lambda
|
|
98
|
+
config.backtrace_cleaner = ->(backtrace) { backtrace[0..5] }
|
|
99
|
+
end
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Exception Handlers
|
|
103
|
+
|
|
104
|
+
Register handlers that run when non-fault exceptions occur.
|
|
105
|
+
|
|
106
|
+
!!! tip
|
|
107
|
+
|
|
108
|
+
Use exception handlers to send errors to your APM of choice.
|
|
109
|
+
|
|
110
|
+
```ruby
|
|
111
|
+
CMDx.configure do |config|
|
|
112
|
+
# Via callable (must respond to `call(task, exception)`)
|
|
113
|
+
config.exception_handler = NewRelicReporter
|
|
114
|
+
|
|
115
|
+
# Via proc or lambda
|
|
116
|
+
config.exception_handler = proc do |task, exception|
|
|
117
|
+
APMService.report(exception, extra_data: { task: task.name, id: task.id })
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
```
|
|
121
|
+
|
|
101
122
|
### Logging
|
|
102
123
|
|
|
103
124
|
```ruby
|
|
@@ -108,7 +129,7 @@ end
|
|
|
108
129
|
|
|
109
130
|
### Middlewares
|
|
110
131
|
|
|
111
|
-
See the [
|
|
132
|
+
See the [Middlewares](middlewares.md#declarations) docs for task level configurations.
|
|
112
133
|
|
|
113
134
|
```ruby
|
|
114
135
|
CMDx.configure do |config|
|
|
@@ -132,12 +153,13 @@ CMDx.configure do |config|
|
|
|
132
153
|
end
|
|
133
154
|
```
|
|
134
155
|
|
|
135
|
-
|
|
136
|
-
|
|
156
|
+
!!! note
|
|
157
|
+
|
|
158
|
+
Middlewares are executed in registration order. Each middleware wraps the next, creating an execution chain around task logic.
|
|
137
159
|
|
|
138
160
|
### Callbacks
|
|
139
161
|
|
|
140
|
-
See the [Callbacks](
|
|
162
|
+
See the [Callbacks](callbacks.md#declarations) docs for task level configurations.
|
|
141
163
|
|
|
142
164
|
```ruby
|
|
143
165
|
CMDx.configure do |config|
|
|
@@ -163,7 +185,7 @@ end
|
|
|
163
185
|
|
|
164
186
|
### Coercions
|
|
165
187
|
|
|
166
|
-
See the [Attributes - Coercions](
|
|
188
|
+
See the [Attributes - Coercions](attributes/coercions.md#declarations) docs for task level configurations.
|
|
167
189
|
|
|
168
190
|
```ruby
|
|
169
191
|
CMDx.configure do |config|
|
|
@@ -189,7 +211,7 @@ end
|
|
|
189
211
|
|
|
190
212
|
### Validators
|
|
191
213
|
|
|
192
|
-
See the [Attributes - Validations](
|
|
214
|
+
See the [Attributes - Validations](attributes/validations.md#declarations) docs for task level configurations.
|
|
193
215
|
|
|
194
216
|
```ruby
|
|
195
217
|
CMDx.configure do |config|
|
|
@@ -224,6 +246,8 @@ class GenerateInvoice < CMDx::Task
|
|
|
224
246
|
# Global configuration overrides
|
|
225
247
|
task_breakpoints: ["failed"], # Breakpoint override
|
|
226
248
|
workflow_breakpoints: [], # Breakpoint override
|
|
249
|
+
backtrace: true, # Toggle backtrace
|
|
250
|
+
backtrace_cleaner: ->(bt) { bt[0..5] }, # Backtrace cleaner
|
|
227
251
|
logger: CustomLogger.new($stdout), # Custom logger
|
|
228
252
|
|
|
229
253
|
# Task configuration settings
|
|
@@ -231,7 +255,10 @@ class GenerateInvoice < CMDx::Task
|
|
|
231
255
|
log_level: :info, # Log level override
|
|
232
256
|
log_formatter: CMDx::LogFormatters::Json.new # Log formatter override
|
|
233
257
|
tags: ["billing", "financial"], # Logging tags
|
|
234
|
-
deprecated: true
|
|
258
|
+
deprecated: true, # Task deprecations
|
|
259
|
+
retries: 3, # Non-fault exception retries
|
|
260
|
+
retry_on: [External::ApiError], # List of exceptions to retry on
|
|
261
|
+
retry_jitter: 1 # Space between retry iteration, eg: current retry num + 1
|
|
235
262
|
)
|
|
236
263
|
|
|
237
264
|
def work
|
|
@@ -240,13 +267,13 @@ class GenerateInvoice < CMDx::Task
|
|
|
240
267
|
end
|
|
241
268
|
```
|
|
242
269
|
|
|
243
|
-
|
|
244
|
-
|
|
270
|
+
!!! warning "Important"
|
|
271
|
+
|
|
272
|
+
Retries reuse the same context. By default, all `StandardError` exceptions are retried unless you specify `retry_on`.
|
|
245
273
|
|
|
246
274
|
### Registrations
|
|
247
275
|
|
|
248
|
-
Register middlewares, callbacks, coercions, and validators
|
|
249
|
-
Deregister options that should not be available.
|
|
276
|
+
Register or deregister middlewares, callbacks, coercions, and validators for specific tasks:
|
|
250
277
|
|
|
251
278
|
```ruby
|
|
252
279
|
class SendCampaignEmail < CMDx::Task
|
|
@@ -298,8 +325,9 @@ end
|
|
|
298
325
|
|
|
299
326
|
### Resetting
|
|
300
327
|
|
|
301
|
-
|
|
302
|
-
|
|
328
|
+
!!! warning
|
|
329
|
+
|
|
330
|
+
Resetting affects your entire application. Use this primarily in test environments.
|
|
303
331
|
|
|
304
332
|
```ruby
|
|
305
333
|
# Reset to framework defaults
|
|
@@ -336,10 +364,6 @@ class ModerateBlogPost < CMDx::Task
|
|
|
336
364
|
end
|
|
337
365
|
```
|
|
338
366
|
|
|
339
|
-
|
|
340
|
-
> Use **present tense verbs + noun** for task names, eg: `ModerateBlogPost`, `ScheduleAppointment`, `ValidateDocument`
|
|
341
|
-
|
|
342
|
-
---
|
|
367
|
+
!!! tip
|
|
343
368
|
|
|
344
|
-
|
|
345
|
-
- **Next:** [Basics - Setup](basics/setup.md)
|
|
369
|
+
Use **present tense verbs + noun** for task names, eg: `ModerateBlogPost`, `ScheduleAppointment`, `ValidateDocument`
|