cmdx 1.7.5 → 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/.cursor/prompts/rspec.md +1 -1
- data/.irbrc +14 -2
- data/CHANGELOG.md +62 -29
- data/LLM.md +203 -78
- 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 +19 -29
- data/docs/attributes/defaults.md +3 -16
- data/docs/attributes/definitions.md +29 -39
- data/docs/attributes/naming.md +3 -13
- data/docs/attributes/transformations.md +63 -0
- data/docs/attributes/validations.md +23 -40
- 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 +31 -25
- 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 +9 -2
- data/lib/cmdx/attribute_value.rb +31 -10
- data/lib/cmdx/callback_registry.rb +12 -2
- data/lib/cmdx/coercions/hash.rb +6 -1
- data/lib/cmdx/configuration.rb +10 -2
- data/lib/cmdx/deprecator.rb +3 -3
- data/lib/cmdx/errors.rb +1 -1
- data/lib/cmdx/executor.rb +97 -9
- data/lib/cmdx/log_formatters/logstash.rb +4 -4
- 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/lib/locales/af.yml +2 -2
- data/lib/locales/ar.yml +2 -2
- data/lib/locales/az.yml +2 -2
- data/lib/locales/be.yml +2 -2
- data/lib/locales/bg.yml +2 -2
- data/lib/locales/bn.yml +2 -2
- data/lib/locales/bs.yml +2 -2
- data/lib/locales/ca.yml +2 -2
- data/lib/locales/cnr.yml +2 -2
- data/lib/locales/cs.yml +2 -2
- data/lib/locales/cy.yml +2 -2
- data/lib/locales/da.yml +2 -2
- data/lib/locales/de.yml +2 -2
- data/lib/locales/dz.yml +2 -2
- data/lib/locales/el.yml +2 -2
- data/lib/locales/en.yml +2 -2
- data/lib/locales/eo.yml +2 -2
- data/lib/locales/es.yml +2 -2
- data/lib/locales/et.yml +2 -2
- data/lib/locales/eu.yml +2 -2
- data/lib/locales/fa.yml +2 -2
- data/lib/locales/fi.yml +2 -2
- data/lib/locales/fr.yml +2 -2
- data/lib/locales/fy.yml +2 -2
- data/lib/locales/gd.yml +2 -2
- data/lib/locales/gl.yml +2 -2
- data/lib/locales/he.yml +2 -2
- data/lib/locales/hi.yml +2 -2
- data/lib/locales/hr.yml +2 -2
- data/lib/locales/hu.yml +2 -2
- data/lib/locales/hy.yml +2 -2
- data/lib/locales/id.yml +2 -2
- data/lib/locales/is.yml +2 -2
- data/lib/locales/it.yml +2 -2
- data/lib/locales/ja.yml +2 -2
- data/lib/locales/ka.yml +2 -2
- data/lib/locales/kk.yml +2 -2
- data/lib/locales/km.yml +2 -2
- data/lib/locales/kn.yml +2 -2
- data/lib/locales/ko.yml +2 -2
- data/lib/locales/lb.yml +2 -2
- data/lib/locales/lo.yml +2 -2
- data/lib/locales/lt.yml +2 -2
- data/lib/locales/lv.yml +2 -2
- data/lib/locales/mg.yml +2 -2
- data/lib/locales/mk.yml +2 -2
- data/lib/locales/ml.yml +2 -2
- data/lib/locales/mn.yml +2 -2
- data/lib/locales/mr-IN.yml +2 -2
- data/lib/locales/ms.yml +2 -2
- data/lib/locales/nb.yml +2 -2
- data/lib/locales/ne.yml +2 -2
- data/lib/locales/nl.yml +2 -2
- data/lib/locales/nn.yml +2 -2
- data/lib/locales/oc.yml +2 -2
- data/lib/locales/or.yml +2 -2
- data/lib/locales/pa.yml +2 -2
- data/lib/locales/pl.yml +2 -2
- data/lib/locales/pt.yml +2 -2
- data/lib/locales/rm.yml +2 -2
- data/lib/locales/ro.yml +2 -2
- data/lib/locales/ru.yml +2 -2
- data/lib/locales/sc.yml +2 -2
- data/lib/locales/sk.yml +2 -2
- data/lib/locales/sl.yml +2 -2
- data/lib/locales/sq.yml +2 -2
- data/lib/locales/sr.yml +2 -2
- data/lib/locales/st.yml +2 -2
- data/lib/locales/sv.yml +2 -2
- data/lib/locales/sw.yml +2 -2
- data/lib/locales/ta.yml +2 -2
- data/lib/locales/te.yml +2 -2
- data/lib/locales/th.yml +2 -2
- data/lib/locales/tl.yml +2 -2
- data/lib/locales/tr.yml +2 -2
- data/lib/locales/tt.yml +2 -2
- data/lib/locales/ug.yml +2 -2
- data/lib/locales/uk.yml +2 -2
- data/lib/locales/ur.yml +2 -2
- data/lib/locales/uz.yml +2 -2
- data/lib/locales/vi.yml +2 -2
- data/lib/locales/wo.yml +2 -2
- data/lib/locales/zh-CN.yml +2 -2
- data/lib/locales/zh-HK.yml +2 -2
- data/lib/locales/zh-TW.yml +2 -2
- data/lib/locales/zh-YUE.yml +2 -2
- 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 +15 -4
- data/lib/cmdx/freezer.rb +0 -51
- data/src/cmdx-logo.png +0 -0
data/docs/logging.md
CHANGED
|
@@ -1,16 +1,10 @@
|
|
|
1
1
|
# Logging
|
|
2
2
|
|
|
3
|
-
CMDx
|
|
4
|
-
|
|
5
|
-
## Table of Contents
|
|
6
|
-
|
|
7
|
-
- [Formatters](#formatters)
|
|
8
|
-
- [Structure](#structure)
|
|
9
|
-
- [Usage](#usage)
|
|
3
|
+
CMDx automatically logs every task execution with structured data, making debugging and monitoring effortless. Choose from multiple formatters to match your logging infrastructure.
|
|
10
4
|
|
|
11
5
|
## Formatters
|
|
12
6
|
|
|
13
|
-
|
|
7
|
+
Choose the format that works best for your logging system:
|
|
14
8
|
|
|
15
9
|
| Formatter | Use Case | Output Style |
|
|
16
10
|
|-----------|----------|--------------|
|
|
@@ -40,12 +34,13 @@ E, [2022-07-17T18:43:15.000000 #3784] ERROR -- BillingWorkflow:
|
|
|
40
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"}
|
|
41
35
|
```
|
|
42
36
|
|
|
43
|
-
|
|
44
|
-
|
|
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.
|
|
45
40
|
|
|
46
41
|
## Structure
|
|
47
42
|
|
|
48
|
-
|
|
43
|
+
Every log entry includes rich metadata. Available fields depend on execution context and outcome.
|
|
49
44
|
|
|
50
45
|
### Core Fields
|
|
51
46
|
|
|
@@ -86,7 +81,7 @@ All log entries include comprehensive execution metadata. Field availability dep
|
|
|
86
81
|
|
|
87
82
|
## Usage
|
|
88
83
|
|
|
89
|
-
|
|
84
|
+
Access the framework logger directly within tasks:
|
|
90
85
|
|
|
91
86
|
```ruby
|
|
92
87
|
class ProcessSubscription < CMDx::Task
|
|
@@ -97,8 +92,3 @@ class ProcessSubscription < CMDx::Task
|
|
|
97
92
|
end
|
|
98
93
|
end
|
|
99
94
|
```
|
|
100
|
-
|
|
101
|
-
---
|
|
102
|
-
|
|
103
|
-
- **Prev:** [Middlewares](middlewares.md)
|
|
104
|
-
- **Next:** [Internationalization (i18n)](internationalization.md)
|
data/docs/middlewares.md
CHANGED
|
@@ -1,27 +1,16 @@
|
|
|
1
1
|
# Middlewares
|
|
2
2
|
|
|
3
|
-
|
|
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
4
|
|
|
5
|
-
|
|
5
|
+
See [Global Configuration](getting_started.md#middlewares) for framework-wide setup.
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## Execution Order
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
- [Declarations](#declarations)
|
|
11
|
-
- [Proc or Lambda](#proc-or-lambda)
|
|
12
|
-
- [Class or Module](#class-or-module)
|
|
13
|
-
- [Removals](#removals)
|
|
14
|
-
- [Built-in](#built-in)
|
|
15
|
-
- [Timeout](#timeout)
|
|
16
|
-
- [Correlate](#correlate)
|
|
17
|
-
- [Runtime](#runtime)
|
|
9
|
+
Middleware wraps task execution in layers, like an onion:
|
|
18
10
|
|
|
19
|
-
|
|
11
|
+
!!! note
|
|
20
12
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
> [!NOTE]
|
|
24
|
-
> Middleware executes in the order they are registered, with the first registered middleware being the outermost wrapper.
|
|
13
|
+
First registered = outermost wrapper. They execute in registration order.
|
|
25
14
|
|
|
26
15
|
```ruby
|
|
27
16
|
class ProcessCampaign < CMDx::Task
|
|
@@ -97,10 +86,11 @@ end
|
|
|
97
86
|
|
|
98
87
|
## Removals
|
|
99
88
|
|
|
100
|
-
|
|
89
|
+
Remove class or module-based middleware globally or per-task:
|
|
90
|
+
|
|
91
|
+
!!! warning
|
|
101
92
|
|
|
102
|
-
|
|
103
|
-
> Only one removal operation is allowed per `deregister` call. Multiple removals require separate calls.
|
|
93
|
+
Each `deregister` call removes one middleware. Use multiple calls for batch removals.
|
|
104
94
|
|
|
105
95
|
```ruby
|
|
106
96
|
class ProcessCampaign < CMDx::Task
|
|
@@ -113,7 +103,7 @@ end
|
|
|
113
103
|
|
|
114
104
|
### Timeout
|
|
115
105
|
|
|
116
|
-
|
|
106
|
+
Prevent tasks from running too long:
|
|
117
107
|
|
|
118
108
|
```ruby
|
|
119
109
|
class ProcessReport < CMDx::Task
|
|
@@ -149,7 +139,7 @@ result.metadata #=> { limit: 3 }
|
|
|
149
139
|
|
|
150
140
|
### Correlate
|
|
151
141
|
|
|
152
|
-
|
|
142
|
+
Add correlation IDs for distributed tracing and request tracking:
|
|
153
143
|
|
|
154
144
|
```ruby
|
|
155
145
|
class ProcessExport < CMDx::Task
|
|
@@ -179,8 +169,7 @@ result.metadata #=> { correlation_id: "550e8400-e29b-41d4-a716-446655440000" }
|
|
|
179
169
|
|
|
180
170
|
### Runtime
|
|
181
171
|
|
|
182
|
-
|
|
183
|
-
The calculation uses a monotonic clock and the time is returned in milliseconds.
|
|
172
|
+
Track task execution time in milliseconds using a monotonic clock:
|
|
184
173
|
|
|
185
174
|
```ruby
|
|
186
175
|
class PerformanceMonitoringCheck
|
|
@@ -200,8 +189,3 @@ end
|
|
|
200
189
|
result = ProcessExport.execute
|
|
201
190
|
result.metadata #=> { runtime: 1247 } (ms)
|
|
202
191
|
```
|
|
203
|
-
|
|
204
|
-
---
|
|
205
|
-
|
|
206
|
-
- **Prev:** [Callbacks](callbacks.md)
|
|
207
|
-
- **Next:** [Logging](logging.md)
|
data/docs/outcomes/result.md
CHANGED
|
@@ -1,27 +1,14 @@
|
|
|
1
1
|
# Outcomes - Result
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
## Table of Contents
|
|
6
|
-
|
|
7
|
-
- [Result Attributes](#result-attributes)
|
|
8
|
-
- [Lifecycle Information](#lifecycle-information)
|
|
9
|
-
- [Outcome Analysis](#outcome-analysis)
|
|
10
|
-
- [Chain Analysis](#chain-analysis)
|
|
11
|
-
- [Index and Position](#index-and-position)
|
|
12
|
-
- [Block Yield](#block-yield)
|
|
13
|
-
- [Handlers](#handlers)
|
|
14
|
-
- [Pattern Matching](#pattern-matching)
|
|
15
|
-
- [Array Pattern](#array-pattern)
|
|
16
|
-
- [Hash Pattern](#hash-pattern)
|
|
17
|
-
- [Pattern Guards](#pattern-guards)
|
|
3
|
+
Results are your window into task execution. They expose everything: outcome, state, timing, context, and metadata.
|
|
18
4
|
|
|
19
5
|
## Result Attributes
|
|
20
6
|
|
|
21
|
-
|
|
7
|
+
Access essential execution information:
|
|
22
8
|
|
|
23
|
-
|
|
24
|
-
|
|
9
|
+
!!! warning "Important"
|
|
10
|
+
|
|
11
|
+
Results are immutable after execution completes.
|
|
25
12
|
|
|
26
13
|
```ruby
|
|
27
14
|
result = BuildApplication.execute(version: "1.2.3")
|
|
@@ -43,7 +30,7 @@ result.metadata #=> { error_code: "BUILD_TOOL.NOT_FOUND" }
|
|
|
43
30
|
|
|
44
31
|
## Lifecycle Information
|
|
45
32
|
|
|
46
|
-
|
|
33
|
+
Check execution state and status with predicate methods:
|
|
47
34
|
|
|
48
35
|
```ruby
|
|
49
36
|
result = BuildApplication.execute(version: "1.2.3")
|
|
@@ -65,7 +52,7 @@ result.bad? #=> false (skipped or failed)
|
|
|
65
52
|
|
|
66
53
|
## Outcome Analysis
|
|
67
54
|
|
|
68
|
-
|
|
55
|
+
Get a unified outcome string combining state and status:
|
|
69
56
|
|
|
70
57
|
```ruby
|
|
71
58
|
result = BuildApplication.execute(version: "1.2.3")
|
|
@@ -75,7 +62,7 @@ result.outcome #=> "success" (state and status)
|
|
|
75
62
|
|
|
76
63
|
## Chain Analysis
|
|
77
64
|
|
|
78
|
-
|
|
65
|
+
Trace fault origins and propagation:
|
|
79
66
|
|
|
80
67
|
```ruby
|
|
81
68
|
result = DeploymentWorkflow.execute(app_name: "webapp")
|
|
@@ -116,7 +103,7 @@ result.chain.results[result.index] == result #=> true
|
|
|
116
103
|
|
|
117
104
|
## Block Yield
|
|
118
105
|
|
|
119
|
-
|
|
106
|
+
Execute code with direct result access:
|
|
120
107
|
|
|
121
108
|
```ruby
|
|
122
109
|
BuildApplication.execute(version: "1.2.3") do |result|
|
|
@@ -132,34 +119,35 @@ end
|
|
|
132
119
|
|
|
133
120
|
## Handlers
|
|
134
121
|
|
|
135
|
-
|
|
122
|
+
Handle outcomes with functional-style methods. Handlers return the result for chaining:
|
|
136
123
|
|
|
137
124
|
```ruby
|
|
138
125
|
result = BuildApplication.execute(version: "1.2.3")
|
|
139
126
|
|
|
140
127
|
# Status-based handlers
|
|
141
128
|
result
|
|
142
|
-
.
|
|
143
|
-
.
|
|
144
|
-
.
|
|
129
|
+
.handle_success { |result| notify_deployment_ready(result) }
|
|
130
|
+
.handle_failed { |result| handle_build_failure(result) }
|
|
131
|
+
.handle_skipped { |result| log_skip_reason(result) }
|
|
145
132
|
|
|
146
133
|
# State-based handlers
|
|
147
134
|
result
|
|
148
|
-
.
|
|
149
|
-
.
|
|
135
|
+
.handle_complete { |result| update_build_status(result) }
|
|
136
|
+
.handle_interrupted { |result| cleanup_partial_artifacts(result) }
|
|
150
137
|
|
|
151
138
|
# Outcome-based handlers
|
|
152
139
|
result
|
|
153
|
-
.
|
|
154
|
-
.
|
|
140
|
+
.handle_good { |result| increment_success_counter(result) }
|
|
141
|
+
.handle_bad { |result| alert_operations_team(result) }
|
|
155
142
|
```
|
|
156
143
|
|
|
157
144
|
## Pattern Matching
|
|
158
145
|
|
|
159
|
-
|
|
146
|
+
Use Ruby 3.0+ pattern matching for elegant outcome handling:
|
|
147
|
+
|
|
148
|
+
!!! warning "Important"
|
|
160
149
|
|
|
161
|
-
|
|
162
|
-
> Pattern matching requires Ruby 3.0+
|
|
150
|
+
Pattern matching works with both array and hash deconstruction.
|
|
163
151
|
|
|
164
152
|
### Array Pattern
|
|
165
153
|
|
|
@@ -203,8 +191,3 @@ in { runtime: time } if time > performance_threshold
|
|
|
203
191
|
investigate_build_performance(result)
|
|
204
192
|
end
|
|
205
193
|
```
|
|
206
|
-
|
|
207
|
-
---
|
|
208
|
-
|
|
209
|
-
- **Prev:** [Interruptions - Exceptions](../interruptions/exceptions.md)
|
|
210
|
-
- **Next:** [Outcomes - States](states.md)
|
data/docs/outcomes/states.md
CHANGED
|
@@ -1,16 +1,6 @@
|
|
|
1
1
|
# Outcomes - States
|
|
2
2
|
|
|
3
|
-
States
|
|
4
|
-
the progress of tasks through their complete execution journey. States provide
|
|
5
|
-
insight into where a task is in its lifecycle and enable lifecycle-based
|
|
6
|
-
decision making and monitoring.
|
|
7
|
-
|
|
8
|
-
## Table of Contents
|
|
9
|
-
|
|
10
|
-
- [Definitions](#definitions)
|
|
11
|
-
- [Transitions](#transitions)
|
|
12
|
-
- [Predicates](#predicates)
|
|
13
|
-
- [Handlers](#handlers)
|
|
3
|
+
States track where a task is in its execution lifecycle—from creation through completion or interruption.
|
|
14
4
|
|
|
15
5
|
## Definitions
|
|
16
6
|
|
|
@@ -34,8 +24,9 @@ State-Status combinations:
|
|
|
34
24
|
|
|
35
25
|
## Transitions
|
|
36
26
|
|
|
37
|
-
|
|
38
|
-
|
|
27
|
+
!!! danger "Caution"
|
|
28
|
+
|
|
29
|
+
States are managed automatically—never modify them manually.
|
|
39
30
|
|
|
40
31
|
```ruby
|
|
41
32
|
# Valid state transition flow
|
|
@@ -62,19 +53,14 @@ result.executed? #=> true (complete OR interrupted)
|
|
|
62
53
|
|
|
63
54
|
## Handlers
|
|
64
55
|
|
|
65
|
-
|
|
56
|
+
Handle lifecycle events with state-based handlers. Use `handle_executed` for cleanup that runs regardless of outcome:
|
|
66
57
|
|
|
67
58
|
```ruby
|
|
68
59
|
result = ProcessVideoUpload.execute
|
|
69
60
|
|
|
70
61
|
# Individual state handlers
|
|
71
62
|
result
|
|
72
|
-
.
|
|
73
|
-
.
|
|
74
|
-
.
|
|
63
|
+
.handle_complete { |result| send_upload_notification(result) }
|
|
64
|
+
.handle_interrupted { |result| cleanup_temp_files(result) }
|
|
65
|
+
.handle_executed { |result| log_upload_metrics(result) }
|
|
75
66
|
```
|
|
76
|
-
|
|
77
|
-
---
|
|
78
|
-
|
|
79
|
-
- **Prev:** [Outcomes - Result](result.md)
|
|
80
|
-
- **Next:** [Outcomes - Statuses](statuses.md)
|
data/docs/outcomes/statuses.md
CHANGED
|
@@ -1,13 +1,6 @@
|
|
|
1
1
|
# Outcomes - Statuses
|
|
2
2
|
|
|
3
|
-
Statuses represent the business outcome
|
|
4
|
-
|
|
5
|
-
## Table of Contents
|
|
6
|
-
|
|
7
|
-
- [Definitions](#definitions)
|
|
8
|
-
- [Transitions](#transitions)
|
|
9
|
-
- [Predicates](#predicates)
|
|
10
|
-
- [Handlers](#handlers)
|
|
3
|
+
Statuses represent the business outcome—did the task succeed, skip, or fail? This differs from state, which tracks the execution lifecycle.
|
|
11
4
|
|
|
12
5
|
## Definitions
|
|
13
6
|
|
|
@@ -19,8 +12,9 @@ Statuses represent the business outcome of task execution logic, indicating how
|
|
|
19
12
|
|
|
20
13
|
## Transitions
|
|
21
14
|
|
|
22
|
-
|
|
23
|
-
|
|
15
|
+
!!! warning "Important"
|
|
16
|
+
|
|
17
|
+
Status transitions are final and unidirectional. Once skipped or failed, tasks can't return to success.
|
|
24
18
|
|
|
25
19
|
```ruby
|
|
26
20
|
# Valid status transitions
|
|
@@ -53,24 +47,19 @@ result.bad? #=> true if skipped OR failed (not success)
|
|
|
53
47
|
|
|
54
48
|
## Handlers
|
|
55
49
|
|
|
56
|
-
|
|
50
|
+
Branch business logic with status-based handlers. Use `handle_good` and `handle_bad` for success/skip vs failed outcomes:
|
|
57
51
|
|
|
58
52
|
```ruby
|
|
59
53
|
result = ProcessNotification.execute
|
|
60
54
|
|
|
61
55
|
# Individual status handlers
|
|
62
56
|
result
|
|
63
|
-
.
|
|
64
|
-
.
|
|
65
|
-
.
|
|
57
|
+
.handle_success { |result| mark_notification_sent(result) }
|
|
58
|
+
.handle_skipped { |result| log_notification_skipped(result) }
|
|
59
|
+
.handle_failed { |result| queue_retry_notification(result) }
|
|
66
60
|
|
|
67
61
|
# Outcome-based handlers
|
|
68
62
|
result
|
|
69
|
-
.
|
|
70
|
-
.
|
|
63
|
+
.handle_good { |result| update_message_stats(result) }
|
|
64
|
+
.handle_bad { |result| track_delivery_failure(result) }
|
|
71
65
|
```
|
|
72
|
-
|
|
73
|
-
---
|
|
74
|
-
|
|
75
|
-
- **Prev:** [Outcomes - States](states.md)
|
|
76
|
-
- **Next:** [Attributes - Definitions](../attributes/definitions.md)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
:root > * {
|
|
2
|
+
/* Primary color shades */
|
|
3
|
+
--md-primary-fg-color: #fe1817;
|
|
4
|
+
--md-primary-fg-color--light: #fe1817;
|
|
5
|
+
--md-primary-fg-color--dark: #fe1817;
|
|
6
|
+
|
|
7
|
+
/* Accent color shades */
|
|
8
|
+
--md-accent-fg-color: hsla(#{hex2hsl(#fe1817)}, 1);
|
|
9
|
+
--md-accent-fg-color--transparent: hsla(#{hex2hsl(#fe1817)}, 0.1);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/* Atom One Light Pro syntax highlighting */
|
|
13
|
+
[data-md-color-scheme="default"] {
|
|
14
|
+
--md-code-hl-color: #2c3036;
|
|
15
|
+
--md-code-hl-keyword-color: #a626a4;
|
|
16
|
+
--md-code-hl-string-color: #50a14f;
|
|
17
|
+
--md-code-hl-name-color: #e4564a;
|
|
18
|
+
--md-code-hl-function-color: #4078f2;
|
|
19
|
+
--md-code-hl-number-color: #ca7601;
|
|
20
|
+
--md-code-hl-constant-color: #c18401;
|
|
21
|
+
--md-code-hl-comment-color: #9ca0a4;
|
|
22
|
+
--md-code-hl-operator-color: #0184bc;
|
|
23
|
+
--md-code-hl-punctuation-color:#383a42;
|
|
24
|
+
--md-code-hl-variable-color: #e4564a;
|
|
25
|
+
--md-code-hl-generic-color: #e4564a;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/* Atom One Dark Pro syntax highlighting */
|
|
29
|
+
[data-md-color-scheme="slate"] {
|
|
30
|
+
--md-code-hl-color: #e5e5e6;
|
|
31
|
+
--md-code-hl-keyword-color: #c678dd;
|
|
32
|
+
--md-code-hl-string-color: #98c379;
|
|
33
|
+
--md-code-hl-name-color: #e06c75;
|
|
34
|
+
--md-code-hl-function-color: #61afef;
|
|
35
|
+
--md-code-hl-number-color: #d19a66;
|
|
36
|
+
--md-code-hl-constant-color: #d19a66;
|
|
37
|
+
--md-code-hl-comment-color: #7f848e;
|
|
38
|
+
--md-code-hl-operator-color: #56b6c2;
|
|
39
|
+
--md-code-hl-punctuation-color:#abb2bf;
|
|
40
|
+
--md-code-hl-variable-color: #e06c75;
|
|
41
|
+
--md-code-hl-generic-color: #e06c75;
|
|
42
|
+
}
|
data/docs/tips_and_tricks.md
CHANGED
|
@@ -1,16 +1,6 @@
|
|
|
1
1
|
# Tips and Tricks
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
## Table of Contents
|
|
6
|
-
|
|
7
|
-
- [Project Organization](#project-organization)
|
|
8
|
-
- [Directory Structure](#directory-structure)
|
|
9
|
-
- [Naming Conventions](#naming-conventions)
|
|
10
|
-
- [Story Telling](#story-telling)
|
|
11
|
-
- [Style Guide](#style-guide)
|
|
12
|
-
- [Attribute Options](#attribute-options)
|
|
13
|
-
- [ActiveRecord Query Tagging](#activerecord-query-tagging)
|
|
3
|
+
Best practices, patterns, and techniques to build maintainable CMDx applications.
|
|
14
4
|
|
|
15
5
|
## Project Organization
|
|
16
6
|
|
|
@@ -54,7 +44,7 @@ class TokenGeneration < CMDx::Task; end # ❌ Avoid
|
|
|
54
44
|
|
|
55
45
|
### Story Telling
|
|
56
46
|
|
|
57
|
-
|
|
47
|
+
Break down complex logic into descriptive methods that read like a narrative:
|
|
58
48
|
|
|
59
49
|
```ruby
|
|
60
50
|
class ProcessOrder < CMDx::Task
|
|
@@ -86,7 +76,7 @@ end
|
|
|
86
76
|
|
|
87
77
|
### Style Guide
|
|
88
78
|
|
|
89
|
-
Follow
|
|
79
|
+
Follow this order for consistent, readable tasks:
|
|
90
80
|
|
|
91
81
|
```ruby
|
|
92
82
|
class ExportReport < CMDx::Task
|
|
@@ -129,7 +119,7 @@ end
|
|
|
129
119
|
|
|
130
120
|
## Attribute Options
|
|
131
121
|
|
|
132
|
-
Use
|
|
122
|
+
Use `with_options` to reduce duplication:
|
|
133
123
|
|
|
134
124
|
```ruby
|
|
135
125
|
class ConfigureCompany < CMDx::Task
|
|
@@ -155,36 +145,7 @@ class ConfigureCompany < CMDx::Task
|
|
|
155
145
|
end
|
|
156
146
|
```
|
|
157
147
|
|
|
158
|
-
##
|
|
159
|
-
|
|
160
|
-
Automatically tag SQL queries for better debugging:
|
|
161
|
-
|
|
162
|
-
```ruby
|
|
163
|
-
# config/application.rb
|
|
164
|
-
config.active_record.query_log_tags_enabled = true
|
|
165
|
-
config.active_record.query_log_tags << :cmdx_task_class
|
|
166
|
-
config.active_record.query_log_tags << :cmdx_chain_id
|
|
167
|
-
|
|
168
|
-
# app/tasks/application_task.rb
|
|
169
|
-
class ApplicationTask < CMDx::Task
|
|
170
|
-
before_execution :set_execution_context
|
|
171
|
-
|
|
172
|
-
private
|
|
173
|
-
|
|
174
|
-
def set_execution_context
|
|
175
|
-
# NOTE: This could easily be made into a middleware
|
|
176
|
-
ActiveSupport::ExecutionContext.set(
|
|
177
|
-
cmdx_task_class: self.class.name,
|
|
178
|
-
cmdx_chain_id: chain.id
|
|
179
|
-
)
|
|
180
|
-
end
|
|
181
|
-
end
|
|
182
|
-
|
|
183
|
-
# SQL queries will now include comments like:
|
|
184
|
-
# /*cmdx_task_class:ExportReportTask,cmdx_chain_id:018c2b95-b764-7615*/ SELECT * FROM reports WHERE id = 1
|
|
185
|
-
```
|
|
186
|
-
|
|
187
|
-
---
|
|
148
|
+
## Advanced Examples
|
|
188
149
|
|
|
189
|
-
-
|
|
190
|
-
-
|
|
150
|
+
- [Active Record Query Tagging](https://github.com/drexed/cmdx/blob/main/examples/active_record_query_tagging.md)
|
|
151
|
+
- [Paper Trail Whatdunnit](https://github.com/drexed/cmdx/blob/main/examples/paper_trail_whatdunnit.md)
|
data/docs/workflows.md
CHANGED
|
@@ -1,26 +1,14 @@
|
|
|
1
1
|
# Workflows
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
## Table of Contents
|
|
6
|
-
|
|
7
|
-
- [Declarations](#declarations)
|
|
8
|
-
- [Task](#task)
|
|
9
|
-
- [Group](#group)
|
|
10
|
-
- [Conditionals](#conditionals)
|
|
11
|
-
- [Halt Behavior](#halt-behavior)
|
|
12
|
-
- [Task Configuration](#task-configuration)
|
|
13
|
-
- [Group Configuration](#group-configuration)
|
|
14
|
-
- [Nested Workflows](#nested-workflows)
|
|
15
|
-
- [Parallel Execution](#parallel-execution)
|
|
16
|
-
- [Task Generator](#task-generator)
|
|
3
|
+
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.
|
|
17
4
|
|
|
18
5
|
## Declarations
|
|
19
6
|
|
|
20
|
-
Tasks
|
|
7
|
+
Tasks run in declaration order (FIFO), sharing a common context across the pipeline.
|
|
21
8
|
|
|
22
|
-
|
|
23
|
-
|
|
9
|
+
!!! warning
|
|
10
|
+
|
|
11
|
+
Don't define a `work` method in workflows—the module handles execution automatically.
|
|
24
12
|
|
|
25
13
|
### Task
|
|
26
14
|
|
|
@@ -35,15 +23,17 @@ class OnboardingWorkflow < CMDx::Task
|
|
|
35
23
|
end
|
|
36
24
|
```
|
|
37
25
|
|
|
38
|
-
|
|
39
|
-
|
|
26
|
+
!!! tip
|
|
27
|
+
|
|
28
|
+
Execute tasks in parallel via the [cmdx-parallel](https://github.com/drexed/cmdx-parallel) gem.
|
|
40
29
|
|
|
41
30
|
### Group
|
|
42
31
|
|
|
43
|
-
Group related tasks
|
|
32
|
+
Group related tasks to share configuration:
|
|
44
33
|
|
|
45
|
-
|
|
46
|
-
|
|
34
|
+
!!! warning "Important"
|
|
35
|
+
|
|
36
|
+
Settings and conditionals apply to all tasks in the group.
|
|
47
37
|
|
|
48
38
|
```ruby
|
|
49
39
|
class ContentModerationWorkflow < CMDx::Task
|
|
@@ -78,10 +68,10 @@ class OnboardingWorkflow < CMDx::Task
|
|
|
78
68
|
task SendWelcomeEmail, if: :email_configured?, unless: :email_disabled?
|
|
79
69
|
|
|
80
70
|
# Proc
|
|
81
|
-
task SendWelcomeEmail, if: ->
|
|
71
|
+
task SendWelcomeEmail, if: -> { Rails.env.production? && self.class.name.include?("Premium") }
|
|
82
72
|
|
|
83
73
|
# Lambda
|
|
84
|
-
task SendWelcomeEmail, if: proc {
|
|
74
|
+
task SendWelcomeEmail, if: proc { context.features_enabled? }
|
|
85
75
|
|
|
86
76
|
# Class or Module
|
|
87
77
|
task SendWelcomeEmail, unless: ContentAccessCheck
|
|
@@ -95,7 +85,7 @@ class OnboardingWorkflow < CMDx::Task
|
|
|
95
85
|
private
|
|
96
86
|
|
|
97
87
|
def email_configured?
|
|
98
|
-
context.user.email_address
|
|
88
|
+
context.user.email_address == true
|
|
99
89
|
end
|
|
100
90
|
|
|
101
91
|
def email_disabled?
|
|
@@ -106,9 +96,7 @@ end
|
|
|
106
96
|
|
|
107
97
|
## Halt Behavior
|
|
108
98
|
|
|
109
|
-
By default skipped tasks
|
|
110
|
-
This is configurable via global and task level breakpoint settings. Task and group configurations
|
|
111
|
-
can be used together within a workflow.
|
|
99
|
+
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.
|
|
112
100
|
|
|
113
101
|
```ruby
|
|
114
102
|
class AnalyticsWorkflow < CMDx::Task
|
|
@@ -164,7 +152,7 @@ end
|
|
|
164
152
|
|
|
165
153
|
## Nested Workflows
|
|
166
154
|
|
|
167
|
-
|
|
155
|
+
Build hierarchical workflows by composing workflows within workflows:
|
|
168
156
|
|
|
169
157
|
```ruby
|
|
170
158
|
class EmailPreparationWorkflow < CMDx::Task
|
|
@@ -191,10 +179,11 @@ end
|
|
|
191
179
|
|
|
192
180
|
## Parallel Execution
|
|
193
181
|
|
|
194
|
-
|
|
182
|
+
Run tasks concurrently using the [Parallel](https://github.com/grosser/parallel) gem. It automatically uses all available processors for maximum throughput.
|
|
183
|
+
|
|
184
|
+
!!! warning
|
|
195
185
|
|
|
196
|
-
|
|
197
|
-
> Context cannot be modified during parallel execution. Ensure that all required data is preloaded into the context before parallelization begins.
|
|
186
|
+
Context is read-only during parallel execution. Load all required data beforehand.
|
|
198
187
|
|
|
199
188
|
```ruby
|
|
200
189
|
class SendWelcomeNotifications < CMDx::Task
|
|
@@ -232,10 +221,6 @@ class SendNotifications < CMDx::Task
|
|
|
232
221
|
end
|
|
233
222
|
```
|
|
234
223
|
|
|
235
|
-
|
|
236
|
-
> Use **present tense verbs + pluralized noun** for workflow task names, eg: `SendNotifications`, `DownloadFiles`, `ValidateDocuments`
|
|
237
|
-
|
|
238
|
-
---
|
|
224
|
+
!!! tip
|
|
239
225
|
|
|
240
|
-
|
|
241
|
-
- **Next:** [Tips and Tricks](tips_and_tricks.md)
|
|
226
|
+
Use **present tense verbs + pluralized noun** for workflow task names, eg: `SendNotifications`, `DownloadFiles`, `ValidateDocuments`
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Active Record Query Tagging
|
|
2
|
+
|
|
3
|
+
Add a comment to every query indicating some context to help you track down where that query came from, eg:
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
/*cmdx_task_class:ExportReportTask,cmdx_chain_id:018c2b95-b764-7615*/ SELECT * FROM reports WHERE id = 1
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
### Setup
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
# config/application.rb
|
|
13
|
+
config.active_record.query_log_tags_enabled = true
|
|
14
|
+
config.active_record.query_log_tags += [
|
|
15
|
+
:cmdx_correlation_id,
|
|
16
|
+
:cmdx_chain_id,
|
|
17
|
+
:cmdx_task_class,
|
|
18
|
+
:cmdx_task_id
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
# lib/cmdx_query_tagging_middleware.rb
|
|
22
|
+
class CmdxQueryTaggingMiddleware
|
|
23
|
+
def self.call(task, **options, &)
|
|
24
|
+
ActiveSupport::ExecutionContext.set(
|
|
25
|
+
cmdx_correlation_id: task.result.metadata[:correlation_id],
|
|
26
|
+
cmdx_chain_id: task.chain.id,
|
|
27
|
+
cmdx_task_class: task.class.name,
|
|
28
|
+
cmdx_task_id: task.id,
|
|
29
|
+
&
|
|
30
|
+
)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Usage
|
|
36
|
+
|
|
37
|
+
```ruby
|
|
38
|
+
class MyTask < CMDx::Task
|
|
39
|
+
register :middleware, CmdxQueryTaggingMiddleware
|
|
40
|
+
|
|
41
|
+
def work
|
|
42
|
+
# Do work...
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
end
|
|
46
|
+
```
|