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/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`
|
data/docs/index.md
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# CMDx
|
|
2
|
+
|
|
3
|
+
Build business logic that's powerful, predictable, and maintainable.
|
|
4
|
+
|
|
5
|
+
[](https://rubygems.org/gems/cmdx)
|
|
6
|
+
[](https://github.com/drexed/cmdx/actions/workflows/ci.yml)
|
|
7
|
+
[](https://github.com/drexed/cmdx/blob/main/LICENSE.txt)
|
|
8
|
+
|
|
9
|
+
Say goodbye to messy service objects. CMDx helps you design business logic with clarity and consistency—build faster, debug easier, and ship with confidence.
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
gem install cmdx
|
|
15
|
+
# - or -
|
|
16
|
+
bundle add cmdx
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Quick Example
|
|
20
|
+
|
|
21
|
+
Build powerful business logic in four simple steps:
|
|
22
|
+
|
|
23
|
+
### 1. Compose
|
|
24
|
+
|
|
25
|
+
=== "Full Featured Task"
|
|
26
|
+
|
|
27
|
+
```ruby
|
|
28
|
+
class AnalyzeMetrics < CMDx::Task
|
|
29
|
+
register :middleware, CMDx::Middlewares::Correlate, id: -> { Current.request_id }
|
|
30
|
+
|
|
31
|
+
on_success :track_analysis_completion!
|
|
32
|
+
|
|
33
|
+
required :dataset_id, type: :integer, numeric: { min: 1 }
|
|
34
|
+
optional :analysis_type, default: "standard"
|
|
35
|
+
|
|
36
|
+
def work
|
|
37
|
+
if dataset.nil?
|
|
38
|
+
fail!("Dataset not found", code: 404)
|
|
39
|
+
elsif dataset.unprocessed?
|
|
40
|
+
skip!("Dataset not ready for analysis")
|
|
41
|
+
else
|
|
42
|
+
context.result = PValueAnalyzer.execute(dataset:, analysis_type:)
|
|
43
|
+
context.analyzed_at = Time.now
|
|
44
|
+
|
|
45
|
+
SendAnalyzedEmail.execute(user_id: Current.account.manager_id)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def dataset
|
|
52
|
+
@dataset ||= Dataset.find_by(id: dataset_id)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def track_analysis_completion!
|
|
56
|
+
dataset.update!(analysis_result_id: context.result.id)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
=== "Minimum Viable Task"
|
|
62
|
+
|
|
63
|
+
```ruby
|
|
64
|
+
class SendAnalyzedEmail < CMDx::Task
|
|
65
|
+
def work
|
|
66
|
+
user = User.find(context.user_id)
|
|
67
|
+
MetricsMailer.analyzed(user).deliver_now
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### 2. Execute
|
|
73
|
+
|
|
74
|
+
```ruby
|
|
75
|
+
result = AnalyzeMetrics.execute(
|
|
76
|
+
dataset_id: 123,
|
|
77
|
+
"analysis_type" => "advanced"
|
|
78
|
+
)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### 3. React
|
|
82
|
+
|
|
83
|
+
```ruby
|
|
84
|
+
if result.success?
|
|
85
|
+
puts "Metrics analyzed at #{result.context.analyzed_at}"
|
|
86
|
+
elsif result.skipped?
|
|
87
|
+
puts "Skipping analyzation due to: #{result.reason}"
|
|
88
|
+
elsif result.failed?
|
|
89
|
+
puts "Analyzation failed due to: #{result.reason} with code #{result.metadata[:code]}"
|
|
90
|
+
end
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### 4. Observe
|
|
94
|
+
|
|
95
|
+
```log
|
|
96
|
+
I, [2022-07-17T18:42:37.000000 #3784] INFO -- CMDx:
|
|
97
|
+
index=1 chain_id="018c2b95-23j4-2kj3-32kj-3n4jk3n4jknf" type="Task" class="SendAnalyzedEmail" state="complete" status="success" metadata={runtime: 347}
|
|
98
|
+
|
|
99
|
+
I, [2022-07-17T18:43:15.000000 #3784] INFO -- CMDx:
|
|
100
|
+
index=0 chain_id="018c2b95-b764-7615-a924-cc5b910ed1e5" type="Task" class="AnalyzeMetrics" state="complete" status="success" metadata={runtime: 187}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Ready to dive in? Check out the [Getting Started](getting_started.md) guide to learn more.
|
|
104
|
+
|
|
105
|
+
## Ecosystem
|
|
106
|
+
|
|
107
|
+
- [cmdx-rspec](https://github.com/drexed/cmdx-rspec) - RSpec test matchers
|
|
108
|
+
|
|
109
|
+
For backwards compatibility of certain functionality:
|
|
110
|
+
|
|
111
|
+
- [cmdx-i18n](https://github.com/drexed/cmdx-i18n) - 85+ translations, `v1.5.0` - `v1.6.2`
|
|
112
|
+
- [cmdx-parallel](https://github.com/drexed/cmdx-parallel) - Parallel workflow tasks, `v1.6.1` - `v1.6.2`
|
|
113
|
+
|
|
114
|
+
## Contributing
|
|
115
|
+
|
|
116
|
+
Bug reports and pull requests are welcome at https://github.com/drexed/cmdx. We're committed to fostering a welcoming, collaborative community. Please follow our [code of conduct](CODE_OF_CONDUCT.md).
|
|
117
|
+
|
|
118
|
+
## License
|
|
119
|
+
|
|
120
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
@@ -1,17 +1,10 @@
|
|
|
1
1
|
# Internationalization (i18n)
|
|
2
2
|
|
|
3
|
-
CMDx
|
|
3
|
+
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.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Usage
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
- [Configuration](#configuration)
|
|
9
|
-
- [Local Copies](#local-copies)
|
|
10
|
-
- [Available Locales](#available-locales)
|
|
11
|
-
|
|
12
|
-
## Localization
|
|
13
|
-
|
|
14
|
-
CMDx automatically localizes all error messages based on the `I18n.locale` setting.
|
|
7
|
+
All error messages are automatically localized based on your current locale:
|
|
15
8
|
|
|
16
9
|
```ruby
|
|
17
10
|
class ProcessQuote < CMDx::Task
|
|
@@ -30,11 +23,11 @@ end
|
|
|
30
23
|
|
|
31
24
|
## Configuration
|
|
32
25
|
|
|
33
|
-
|
|
26
|
+
CMDx uses the `I18n` gem for localization. In Rails, locales load automatically.
|
|
34
27
|
|
|
35
|
-
###
|
|
28
|
+
### Copy Locale Files
|
|
36
29
|
|
|
37
|
-
|
|
30
|
+
Copy locale files to your Rails application's `config/locales` directory:
|
|
38
31
|
|
|
39
32
|
```bash
|
|
40
33
|
rails generate cmdx:locale [LOCALE]
|
|
@@ -131,8 +124,3 @@ rails generate cmdx:locale fr
|
|
|
131
124
|
- zh-HK - Chinese (Hong Kong)
|
|
132
125
|
- zh-TW - Chinese (Traditional)
|
|
133
126
|
- zh-YUE - Chinese (Yue)
|
|
134
|
-
|
|
135
|
-
---
|
|
136
|
-
|
|
137
|
-
- **Prev:** [Logging](logging.md)
|
|
138
|
-
- **Next:** [Deprecation](deprecation.md)
|
|
@@ -1,21 +1,16 @@
|
|
|
1
1
|
# Interruptions - Exceptions
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
## Table of Contents
|
|
6
|
-
|
|
7
|
-
- [Exception Handling](#exception-handling)
|
|
8
|
-
- [Non-bang execution](#non-bang-execution)
|
|
9
|
-
- [Bang execution](#bang-execution)
|
|
3
|
+
Exception handling differs between `execute` and `execute!`. Choose the method that matches your error handling strategy.
|
|
10
4
|
|
|
11
5
|
## Exception Handling
|
|
12
6
|
|
|
13
|
-
|
|
14
|
-
|
|
7
|
+
!!! warning "Important"
|
|
8
|
+
|
|
9
|
+
Prefer `skip!` and `fail!` over raising exceptions—they signal intent more clearly.
|
|
15
10
|
|
|
16
11
|
### Non-bang execution
|
|
17
12
|
|
|
18
|
-
|
|
13
|
+
Captures all exceptions and returns them as failed results:
|
|
19
14
|
|
|
20
15
|
```ruby
|
|
21
16
|
class CompressDocument < CMDx::Task
|
|
@@ -33,9 +28,13 @@ result.reason #=> "[ActiveRecord::NotFoundError] record not found"
|
|
|
33
28
|
result.cause #=> <ActiveRecord::NotFoundError>
|
|
34
29
|
```
|
|
35
30
|
|
|
31
|
+
!!! note
|
|
32
|
+
|
|
33
|
+
Use `exception_handler` with `execute` to send exceptions to APM tools before they become failed results.
|
|
34
|
+
|
|
36
35
|
### Bang execution
|
|
37
36
|
|
|
38
|
-
|
|
37
|
+
Lets exceptions propagate naturally for standard Ruby error handling:
|
|
39
38
|
|
|
40
39
|
```ruby
|
|
41
40
|
class CompressDocument < CMDx::Task
|
|
@@ -51,8 +50,3 @@ rescue ActiveRecord::NotFoundError => e
|
|
|
51
50
|
puts "Handle exception: #{e.message}"
|
|
52
51
|
end
|
|
53
52
|
```
|
|
54
|
-
|
|
55
|
-
---
|
|
56
|
-
|
|
57
|
-
- **Prev:** [Interruptions - Faults](faults.md)
|
|
58
|
-
- **Next:** [Outcomes - Result](../outcomes/result.md)
|
|
@@ -1,19 +1,6 @@
|
|
|
1
1
|
# Interruptions - Faults
|
|
2
2
|
|
|
3
|
-
Faults are
|
|
4
|
-
|
|
5
|
-
## Table of Contents
|
|
6
|
-
|
|
7
|
-
- [Fault Types](#fault-types)
|
|
8
|
-
- [Fault Handling](#fault-handling)
|
|
9
|
-
- [Data Access](#data-access)
|
|
10
|
-
- [Advanced Matching](#advanced-matching)
|
|
11
|
-
- [Task-Specific Matching](#task-specific-matching)
|
|
12
|
-
- [Custom Logic Matching](#custom-logic-matching)
|
|
13
|
-
- [Fault Propagation](#fault-propagation)
|
|
14
|
-
- [Basic Propagation](#basic-propagation)
|
|
15
|
-
- [Additional Metadata](#additional-metadata)
|
|
16
|
-
- [Chain Analysis](#chain-analysis)
|
|
3
|
+
Faults are exceptions raised by `execute!` when tasks halt. They carry rich context about execution state, enabling sophisticated error handling patterns.
|
|
17
4
|
|
|
18
5
|
## Fault Types
|
|
19
6
|
|
|
@@ -23,8 +10,9 @@ Faults are exception mechanisms that halt task execution via `skip!` and `fail!`
|
|
|
23
10
|
| `CMDx::SkipFault` | `skip!` method | Optional processing, early returns |
|
|
24
11
|
| `CMDx::FailFault` | `fail!` method | Validation errors, processing failures |
|
|
25
12
|
|
|
26
|
-
|
|
27
|
-
|
|
13
|
+
!!! warning "Important"
|
|
14
|
+
|
|
15
|
+
All faults inherit from `CMDx::Fault` and expose result, task, context, and chain data.
|
|
28
16
|
|
|
29
17
|
## Fault Handling
|
|
30
18
|
|
|
@@ -45,7 +33,7 @@ end
|
|
|
45
33
|
|
|
46
34
|
## Data Access
|
|
47
35
|
|
|
48
|
-
|
|
36
|
+
Access rich execution data from fault exceptions:
|
|
49
37
|
|
|
50
38
|
```ruby
|
|
51
39
|
begin
|
|
@@ -74,7 +62,7 @@ end
|
|
|
74
62
|
|
|
75
63
|
### Task-Specific Matching
|
|
76
64
|
|
|
77
|
-
|
|
65
|
+
Handle faults only from specific tasks using `for?`:
|
|
78
66
|
|
|
79
67
|
```ruby
|
|
80
68
|
begin
|
|
@@ -104,7 +92,7 @@ end
|
|
|
104
92
|
|
|
105
93
|
## Fault Propagation
|
|
106
94
|
|
|
107
|
-
|
|
95
|
+
Propagate failures with `throw!` to preserve context and maintain the error chain:
|
|
108
96
|
|
|
109
97
|
### Basic Propagation
|
|
110
98
|
|
|
@@ -151,7 +139,7 @@ end
|
|
|
151
139
|
|
|
152
140
|
## Chain Analysis
|
|
153
141
|
|
|
154
|
-
|
|
142
|
+
Trace fault origins and propagation through the execution chain:
|
|
155
143
|
|
|
156
144
|
```ruby
|
|
157
145
|
result = DocumentWorkflow.execute(invalid_data)
|
|
@@ -179,8 +167,3 @@ if result.failed?
|
|
|
179
167
|
end
|
|
180
168
|
end
|
|
181
169
|
```
|
|
182
|
-
|
|
183
|
-
---
|
|
184
|
-
|
|
185
|
-
- **Prev:** [Interruptions - Halt](halt.md)
|
|
186
|
-
- **Next:** [Interruptions - Exceptions](exceptions.md)
|
data/docs/interruptions/halt.md
CHANGED
|
@@ -1,24 +1,14 @@
|
|
|
1
1
|
# Interruptions - Halt
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
## Table of Contents
|
|
6
|
-
|
|
7
|
-
- [Skipping](#skipping)
|
|
8
|
-
- [Failing](#failing)
|
|
9
|
-
- [Metadata Enrichment](#metadata-enrichment)
|
|
10
|
-
- [State Transitions](#state-transitions)
|
|
11
|
-
- [Execution Behavior](#execution-behavior)
|
|
12
|
-
- [Non-bang execution](#non-bang-execution)
|
|
13
|
-
- [Bang execution](#bang-execution)
|
|
14
|
-
- [Best Practices](#best-practices)
|
|
3
|
+
Stop task execution intentionally using `skip!` or `fail!`. Both methods signal clear intent about why execution stopped.
|
|
15
4
|
|
|
16
5
|
## Skipping
|
|
17
6
|
|
|
18
|
-
`skip!`
|
|
7
|
+
Use `skip!` when the task doesn't need to run. It's a no-op, not an error.
|
|
19
8
|
|
|
20
|
-
|
|
21
|
-
|
|
9
|
+
!!! warning "Important"
|
|
10
|
+
|
|
11
|
+
Skipped tasks are considered "good" outcomes—they succeeded by doing nothing.
|
|
22
12
|
|
|
23
13
|
```ruby
|
|
24
14
|
class ProcessInventory < CMDx::Task
|
|
@@ -45,7 +35,7 @@ result = ProcessInventory.execute(inventory_id: 456)
|
|
|
45
35
|
result.status #=> "skipped"
|
|
46
36
|
|
|
47
37
|
# Without a reason
|
|
48
|
-
result.reason #=> "
|
|
38
|
+
result.reason #=> "Unspecified"
|
|
49
39
|
|
|
50
40
|
# With a reason
|
|
51
41
|
result.reason #=> "Warehouse closed"
|
|
@@ -53,7 +43,7 @@ result.reason #=> "Warehouse closed"
|
|
|
53
43
|
|
|
54
44
|
## Failing
|
|
55
45
|
|
|
56
|
-
`fail!`
|
|
46
|
+
Use `fail!` when the task can't complete successfully. It signals controlled, intentional failure:
|
|
57
47
|
|
|
58
48
|
```ruby
|
|
59
49
|
class ProcessRefund < CMDx::Task
|
|
@@ -80,7 +70,7 @@ result = ProcessRefund.execute(refund_id: 789)
|
|
|
80
70
|
result.status #=> "failed"
|
|
81
71
|
|
|
82
72
|
# Without a reason
|
|
83
|
-
result.reason #=> "
|
|
73
|
+
result.reason #=> "Unspecified"
|
|
84
74
|
|
|
85
75
|
# With a reason
|
|
86
76
|
result.reason #=> "Refund period has expired"
|
|
@@ -88,7 +78,7 @@ result.reason #=> "Refund period has expired"
|
|
|
88
78
|
|
|
89
79
|
## Metadata Enrichment
|
|
90
80
|
|
|
91
|
-
|
|
81
|
+
Enrich halt calls with metadata for better debugging and error handling:
|
|
92
82
|
|
|
93
83
|
```ruby
|
|
94
84
|
class ProcessRenewal < CMDx::Task
|
|
@@ -188,7 +178,7 @@ end
|
|
|
188
178
|
|
|
189
179
|
## Best Practices
|
|
190
180
|
|
|
191
|
-
Always
|
|
181
|
+
Always provide a reason for better debugging and clearer exception messages:
|
|
192
182
|
|
|
193
183
|
```ruby
|
|
194
184
|
# Good: Clear, specific reason
|
|
@@ -200,11 +190,27 @@ skip!("Paused")
|
|
|
200
190
|
fail!("Unsupported")
|
|
201
191
|
|
|
202
192
|
# Bad: Default, cannot determine reason
|
|
203
|
-
skip! #=> "
|
|
204
|
-
fail! #=> "
|
|
193
|
+
skip! #=> "Unspecified"
|
|
194
|
+
fail! #=> "Unspecified"
|
|
205
195
|
```
|
|
206
196
|
|
|
207
|
-
|
|
197
|
+
## Manual Errors
|
|
198
|
+
|
|
199
|
+
For rare cases, manually add errors before halting:
|
|
208
200
|
|
|
209
|
-
|
|
210
|
-
|
|
201
|
+
!!! warning "Important"
|
|
202
|
+
|
|
203
|
+
Manual errors don't stop execution—you still need to call `fail!` or `skip!`.
|
|
204
|
+
|
|
205
|
+
```ruby
|
|
206
|
+
class ProcessRenewal < CMDx::Task
|
|
207
|
+
def work
|
|
208
|
+
if document.nonrenewable?
|
|
209
|
+
errors.add(:document, "not renewable")
|
|
210
|
+
fail!("document could not be renewed")
|
|
211
|
+
else
|
|
212
|
+
document.renew!
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
```
|