cmdx 0.5.0 → 1.0.1
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/rules/cursor-instructions.mdc +6 -0
- data/.rubocop.yml +19 -1
- data/.ruby-version +1 -1
- data/CHANGELOG.md +95 -28
- data/README.md +73 -25
- data/docs/ai_prompts.md +319 -0
- data/docs/basics/call.md +234 -14
- data/docs/basics/chain.md +280 -0
- data/docs/basics/context.md +241 -33
- data/docs/basics/setup.md +85 -12
- data/docs/callbacks.md +283 -0
- data/docs/configuration.md +155 -30
- data/docs/getting_started.md +145 -22
- data/docs/internationalization.md +148 -0
- data/docs/interruptions/exceptions.md +198 -11
- data/docs/interruptions/faults.md +196 -44
- data/docs/interruptions/halt.md +188 -35
- data/docs/logging.md +204 -53
- data/docs/middlewares.md +745 -0
- data/docs/outcomes/result.md +305 -10
- data/docs/outcomes/states.md +212 -31
- data/docs/outcomes/statuses.md +284 -30
- data/docs/parameters/coercions.md +411 -29
- data/docs/parameters/defaults.md +258 -25
- data/docs/parameters/definitions.md +247 -72
- data/docs/parameters/namespacing.md +259 -27
- data/docs/parameters/validations.md +173 -168
- data/docs/testing.md +560 -0
- data/docs/tips_and_tricks.md +103 -42
- data/docs/workflows.md +329 -0
- data/lib/cmdx/.DS_Store +0 -0
- data/lib/cmdx/callback.rb +69 -0
- data/lib/cmdx/callback_registry.rb +106 -0
- data/lib/cmdx/chain.rb +190 -0
- data/lib/cmdx/chain_inspector.rb +149 -0
- data/lib/cmdx/chain_serializer.rb +175 -0
- data/lib/cmdx/coercions/array.rb +37 -0
- data/lib/cmdx/coercions/big_decimal.rb +33 -0
- data/lib/cmdx/coercions/boolean.rb +41 -1
- data/lib/cmdx/coercions/complex.rb +31 -0
- data/lib/cmdx/coercions/date.rb +39 -0
- data/lib/cmdx/coercions/date_time.rb +39 -0
- data/lib/cmdx/coercions/float.rb +31 -0
- data/lib/cmdx/coercions/hash.rb +42 -0
- data/lib/cmdx/coercions/integer.rb +32 -0
- data/lib/cmdx/coercions/rational.rb +31 -0
- data/lib/cmdx/coercions/string.rb +31 -0
- data/lib/cmdx/coercions/time.rb +39 -0
- data/lib/cmdx/coercions/virtual.rb +31 -0
- data/lib/cmdx/configuration.rb +217 -9
- data/lib/cmdx/context.rb +173 -2
- data/lib/cmdx/core_ext/hash.rb +72 -0
- data/lib/cmdx/core_ext/module.rb +94 -0
- data/lib/cmdx/core_ext/object.rb +105 -0
- data/lib/cmdx/correlator.rb +217 -0
- data/lib/cmdx/error.rb +210 -8
- data/lib/cmdx/errors.rb +256 -1
- data/lib/cmdx/fault.rb +177 -2
- data/lib/cmdx/faults.rb +158 -2
- data/lib/cmdx/immutator.rb +121 -2
- data/lib/cmdx/lazy_struct.rb +261 -18
- data/lib/cmdx/log_formatters/json.rb +46 -0
- data/lib/cmdx/log_formatters/key_value.rb +46 -0
- data/lib/cmdx/log_formatters/line.rb +54 -0
- data/lib/cmdx/log_formatters/logstash.rb +64 -0
- data/lib/cmdx/log_formatters/pretty_json.rb +57 -0
- data/lib/cmdx/log_formatters/pretty_key_value.rb +51 -0
- data/lib/cmdx/log_formatters/pretty_line.rb +60 -0
- data/lib/cmdx/log_formatters/raw.rb +54 -0
- data/lib/cmdx/logger.rb +85 -0
- data/lib/cmdx/logger_ansi.rb +93 -7
- data/lib/cmdx/logger_serializer.rb +116 -0
- data/lib/cmdx/middleware.rb +74 -0
- data/lib/cmdx/middleware_registry.rb +106 -0
- data/lib/cmdx/middlewares/correlate.rb +266 -0
- data/lib/cmdx/middlewares/timeout.rb +232 -0
- data/lib/cmdx/parameter.rb +228 -1
- data/lib/cmdx/parameter_inspector.rb +61 -0
- data/lib/cmdx/parameter_registry.rb +125 -0
- data/lib/cmdx/parameter_serializer.rb +83 -0
- data/lib/cmdx/parameter_validator.rb +62 -0
- data/lib/cmdx/parameter_value.rb +109 -1
- data/lib/cmdx/parameters_inspector.rb +59 -0
- data/lib/cmdx/parameters_serializer.rb +102 -0
- data/lib/cmdx/railtie.rb +123 -3
- data/lib/cmdx/result.rb +367 -25
- data/lib/cmdx/result_ansi.rb +105 -9
- data/lib/cmdx/result_inspector.rb +76 -0
- data/lib/cmdx/result_logger.rb +90 -3
- data/lib/cmdx/result_serializer.rb +137 -0
- data/lib/cmdx/rspec/result_matchers.rb +917 -0
- data/lib/cmdx/rspec/task_matchers.rb +570 -0
- data/lib/cmdx/task.rb +405 -37
- data/lib/cmdx/task_serializer.rb +74 -2
- data/lib/cmdx/utils/ansi_color.rb +95 -0
- data/lib/cmdx/utils/log_timestamp.rb +48 -0
- data/lib/cmdx/utils/monotonic_runtime.rb +71 -4
- data/lib/cmdx/utils/name_affix.rb +78 -0
- data/lib/cmdx/validators/custom.rb +82 -0
- data/lib/cmdx/validators/exclusion.rb +94 -0
- data/lib/cmdx/validators/format.rb +102 -8
- data/lib/cmdx/validators/inclusion.rb +104 -0
- data/lib/cmdx/validators/length.rb +128 -0
- data/lib/cmdx/validators/numeric.rb +128 -0
- data/lib/cmdx/validators/presence.rb +93 -7
- data/lib/cmdx/version.rb +7 -1
- data/lib/cmdx/workflow.rb +394 -0
- data/lib/cmdx.rb +25 -64
- data/lib/generators/cmdx/install_generator.rb +37 -1
- data/lib/generators/cmdx/task_generator.rb +69 -1
- data/lib/generators/cmdx/templates/install.rb +43 -15
- data/lib/generators/cmdx/workflow_generator.rb +109 -0
- data/lib/locales/ar.yml +36 -0
- data/lib/locales/cs.yml +36 -0
- data/lib/locales/da.yml +36 -0
- data/lib/locales/de.yml +36 -0
- data/lib/locales/el.yml +36 -0
- data/lib/locales/en.yml +20 -20
- data/lib/locales/es.yml +20 -20
- data/lib/locales/fi.yml +36 -0
- data/lib/locales/fr.yml +36 -0
- data/lib/locales/he.yml +36 -0
- data/lib/locales/hi.yml +36 -0
- data/lib/locales/it.yml +36 -0
- data/lib/locales/ja.yml +36 -0
- data/lib/locales/ko.yml +36 -0
- data/lib/locales/nl.yml +36 -0
- data/lib/locales/no.yml +36 -0
- data/lib/locales/pl.yml +36 -0
- data/lib/locales/pt.yml +36 -0
- data/lib/locales/ru.yml +36 -0
- data/lib/locales/sv.yml +36 -0
- data/lib/locales/th.yml +36 -0
- data/lib/locales/tr.yml +36 -0
- data/lib/locales/vi.yml +36 -0
- data/lib/locales/zh.yml +36 -0
- metadata +77 -15
- data/docs/basics/run.md +0 -34
- data/docs/batch.md +0 -53
- data/docs/example.md +0 -82
- data/docs/hooks.md +0 -62
- data/lib/cmdx/batch.rb +0 -43
- data/lib/cmdx/parameters.rb +0 -35
- data/lib/cmdx/run.rb +0 -39
- data/lib/cmdx/run_inspector.rb +0 -26
- data/lib/cmdx/run_serializer.rb +0 -20
- data/lib/cmdx/task_hook.rb +0 -18
- data/lib/generators/cmdx/batch_generator.rb +0 -30
- /data/lib/generators/cmdx/templates/{batch.rb.tt → workflow.rb.tt} +0 -0
data/docs/configuration.md
CHANGED
@@ -1,57 +1,182 @@
|
|
1
1
|
# Configuration
|
2
2
|
|
3
|
-
|
3
|
+
CMDx provides a flexible configuration system that allows customization at both global and task levels. Configuration follows a hierarchy where global settings serve as defaults that can be overridden at the task level.
|
4
4
|
|
5
|
-
##
|
5
|
+
## Table of Contents
|
6
6
|
|
7
|
-
|
8
|
-
|
7
|
+
- [TLDR](#tldr)
|
8
|
+
- [Configuration Hierarchy](#configuration-hierarchy)
|
9
|
+
- [Global Configuration](#global-configuration)
|
10
|
+
- [Configuration Options](#configuration-options)
|
11
|
+
- [Global Middlewares](#global-middlewares)
|
12
|
+
- [Global Callbacks](#global-callbacks)
|
13
|
+
- [Task Settings](#task-settings)
|
14
|
+
- [Available Task Settings](#available-task-settings)
|
15
|
+
- [Workflow Configuration](#workflow-configuration)
|
16
|
+
- [Configuration Management](#configuration-management)
|
17
|
+
- [Accessing Configuration](#accessing-configuration)
|
18
|
+
- [Resetting Configuration](#resetting-configuration)
|
19
|
+
|
20
|
+
## TLDR
|
21
|
+
|
22
|
+
- **Hierarchy** - Global → Task Settings → Runtime (each level overrides previous)
|
23
|
+
- **Global config** - Framework-wide defaults via `CMDx.configure`
|
24
|
+
- **Task settings** - Class-level overrides using `task_settings!`
|
25
|
+
- **Key options** - `task_halt`, `workflow_halt`, `logger`, `middlewares`, `callbacks`
|
26
|
+
- **Generator** - Use `rails g cmdx:install` to create configuration file
|
27
|
+
- **Inheritance** - Settings are inherited from parent classes
|
28
|
+
|
29
|
+
## Configuration Hierarchy
|
30
|
+
|
31
|
+
CMDx follows a three-tier configuration hierarchy:
|
32
|
+
|
33
|
+
1. **Global Configuration**: Framework-wide defaults
|
34
|
+
2. **Task Settings**: Class-level overrides via `task_settings!`
|
35
|
+
3. **Runtime Parameters**: Instance-specific overrides during execution
|
36
|
+
|
37
|
+
> [!IMPORTANT]
|
38
|
+
> Task-level settings take precedence over global configuration. Settings are inherited from superclasses and can be overridden in subclasses.
|
39
|
+
|
40
|
+
## Global Configuration
|
41
|
+
|
42
|
+
Generate a configuration file using the Rails generator:
|
43
|
+
|
44
|
+
```bash
|
45
|
+
rails g cmdx:install
|
46
|
+
```
|
47
|
+
|
48
|
+
This creates `config/initializers/cmdx.rb` with default settings.
|
49
|
+
|
50
|
+
### Configuration Options
|
51
|
+
|
52
|
+
| Option | Type | Default | Description |
|
53
|
+
|---------------|-----------------------|----------------|-------------|
|
54
|
+
| `task_halt` | String, Array<String> | `"failed"` | Result statuses that cause `call!` to raise faults |
|
55
|
+
| `workflow_halt` | String, Array<String> | `"failed"` | Result statuses that halt workflow execution |
|
56
|
+
| `logger` | Logger | Line formatter | Logger instance for task execution logging |
|
57
|
+
| `middlewares` | MiddlewareRegistry | Empty registry | Global middleware registry applied to all tasks |
|
58
|
+
| `callbacks` | CallbackRegistry | Empty registry | Global callback registry applied to all tasks |
|
59
|
+
|
60
|
+
### Global Middlewares
|
61
|
+
|
62
|
+
Configure middlewares that automatically apply to all tasks in your application:
|
9
63
|
|
10
64
|
```ruby
|
11
65
|
CMDx.configure do |config|
|
12
|
-
#
|
13
|
-
|
14
|
-
config.task_halt = CMDx::Result::FAILED
|
66
|
+
# Add middlewares without arguments
|
67
|
+
config.middlewares.use CMDx::Middlewares::Timeout
|
15
68
|
|
16
|
-
#
|
17
|
-
config.
|
69
|
+
# Add middlewares with arguments
|
70
|
+
config.middlewares.use CMDx::Middlewares::Timeout, seconds: 30
|
18
71
|
|
19
|
-
#
|
20
|
-
|
21
|
-
|
22
|
-
|
72
|
+
# Add middleware instances
|
73
|
+
config.middlewares.use CMDx::Middlewares::Timeout.new(seconds: 30)
|
74
|
+
end
|
75
|
+
```
|
23
76
|
|
24
|
-
|
25
|
-
# TIP: remember to account for all defined tasks when setting this value
|
26
|
-
config.batch_timeout = nil
|
77
|
+
### Global Callbacks
|
27
78
|
|
28
|
-
|
29
|
-
|
30
|
-
|
79
|
+
Configure callbacks that automatically apply to all tasks in your application:
|
80
|
+
|
81
|
+
```ruby
|
82
|
+
CMDx.configure do |config|
|
83
|
+
# Add method callbacks
|
84
|
+
config.callbacks.register :before_execution, :log_task_start
|
85
|
+
config.callbacks.register :after_execution, :log_task_end
|
86
|
+
|
87
|
+
# Add callback instances
|
88
|
+
config.callbacks.register :on_success, NotificationCallback.new([:slack])
|
89
|
+
config.callbacks.register :on_failure, AlertCallback.new(severity: :critical)
|
90
|
+
|
91
|
+
# Add conditional callbacks
|
92
|
+
config.callbacks.register :on_failure, :page_admin, if: :production?
|
93
|
+
config.callbacks.register :before_validation, :skip_validation, unless: :validate_params?
|
94
|
+
|
95
|
+
# Add proc callbacks
|
96
|
+
config.callbacks.register :on_complete, proc { |task, callback_type|
|
97
|
+
Metrics.increment("task.#{task.class.name.underscore}.completed")
|
98
|
+
}
|
31
99
|
end
|
32
100
|
```
|
33
101
|
|
34
|
-
## Task
|
102
|
+
## Task Settings
|
35
103
|
|
36
|
-
|
104
|
+
Override global configuration for specific tasks or workflows using `task_settings!`:
|
37
105
|
|
38
106
|
```ruby
|
39
|
-
class
|
40
|
-
|
41
|
-
|
42
|
-
|
107
|
+
class ProcessPaymentTask < CMDx::Task
|
108
|
+
task_settings!(
|
109
|
+
task_halt: ["failed"], # Only halt on failures
|
110
|
+
tags: ["payments", "critical"], # Add logging tags
|
111
|
+
logger: Rails.logger, # Use Rails logger
|
112
|
+
log_level: :info, # Set log level
|
113
|
+
log_formatter: CMDx::LogFormatters::Json.new # JSON formatter
|
114
|
+
)
|
43
115
|
|
44
116
|
def call
|
45
|
-
#
|
117
|
+
# Process payment logic
|
46
118
|
end
|
119
|
+
end
|
120
|
+
```
|
121
|
+
|
122
|
+
### Available Task Settings
|
123
|
+
|
124
|
+
| Setting | Type | Description |
|
125
|
+
|-----------------|-----------------------|-------------|
|
126
|
+
| `task_halt` | String, Array<String> | Result statuses that cause `call!` to raise faults |
|
127
|
+
| `workflow_halt` | String, Array<String> | Result statuses that halt workflow execution |
|
128
|
+
| `tags` | Array<String> | Tags automatically appended to logs |
|
129
|
+
| `logger` | Logger | Custom logger instance |
|
130
|
+
| `log_level` | Symbol | Log level (`:debug`, `:info`, `:warn`, `:error`, `:fatal`) |
|
131
|
+
| `log_formatter` | LogFormatter | Custom log formatter |
|
47
132
|
|
133
|
+
### Workflow Configuration
|
134
|
+
|
135
|
+
Configure halt behavior for workflows:
|
136
|
+
|
137
|
+
```ruby
|
138
|
+
class OrderProcessingWorkflow < CMDx::Workflow
|
139
|
+
# Strict workflow - halt on any failure
|
140
|
+
task_settings!(workflow_halt: ["failed", "skipped"])
|
141
|
+
|
142
|
+
process ValidateOrderTask
|
143
|
+
process ChargePaymentTask
|
144
|
+
process FulfillOrderTask
|
48
145
|
end
|
49
146
|
```
|
50
147
|
|
51
|
-
|
52
|
-
|
148
|
+
## Configuration Management
|
149
|
+
|
150
|
+
### Accessing Configuration
|
151
|
+
|
152
|
+
```ruby
|
153
|
+
# Global configuration
|
154
|
+
CMDx.configuration.logger #=> <Logger instance>
|
155
|
+
CMDx.configuration.task_halt #=> "failed"
|
156
|
+
CMDx.configuration.middlewares #=> <MiddlewareRegistry instance>
|
157
|
+
CMDx.configuration.callbacks #=> <CallbackRegistry instance>
|
158
|
+
|
159
|
+
# Task-specific settings
|
160
|
+
class AnalyzeDataTask < CMDx::Task
|
161
|
+
task_settings!(tags: ["analytics"])
|
162
|
+
|
163
|
+
def call
|
164
|
+
tags = task_setting(:tags) # Gets ["analytics"]
|
165
|
+
halt_statuses = task_setting(:task_halt) # Gets global default
|
166
|
+
end
|
167
|
+
end
|
168
|
+
```
|
169
|
+
|
170
|
+
### Resetting Configuration
|
171
|
+
|
172
|
+
Reset configuration to defaults (useful for testing):
|
173
|
+
|
174
|
+
```ruby
|
175
|
+
CMDx.reset_configuration!
|
176
|
+
CMDx.configuration.task_halt #=> "failed" (default)
|
177
|
+
```
|
53
178
|
|
54
179
|
---
|
55
180
|
|
56
|
-
- **Prev:** [
|
57
|
-
- **Next:** [Basics - Setup](
|
181
|
+
- **Prev:** [Getting Started](getting_started.md)
|
182
|
+
- **Next:** [Basics - Setup](basics/setup.md)
|
data/docs/getting_started.md
CHANGED
@@ -1,47 +1,170 @@
|
|
1
|
-
# Getting
|
1
|
+
# Getting Started
|
2
2
|
|
3
|
-
|
3
|
+
CMDx is a Ruby framework for building maintainable, observable business logic through composable
|
4
|
+
command objects. Design robust workflows with automatic parameter validation, structured error
|
5
|
+
handling, comprehensive logging, and intelligent execution flow control that scales from simple
|
6
|
+
tasks to complex multi-step processes.
|
4
7
|
|
5
|
-
|
6
|
-
- Provide easy branching, nesting, and composition of complex tasks
|
7
|
-
- Supply intent, severity, and reasoning to halting execution of tasks
|
8
|
-
- Demystify root causes of complex multi-level tasks with exhaustive tracing
|
8
|
+
## Table of Contents
|
9
9
|
|
10
|
-
|
10
|
+
- [TLDR](#tldr)
|
11
|
+
- [Installation](#installation)
|
12
|
+
- [Quick Setup](#quick-setup)
|
13
|
+
- [Execution](#execution)
|
14
|
+
- [Result Handling](#result-handling)
|
15
|
+
- [Exception Handling](#exception-handling)
|
16
|
+
- [Building Workflows](#building-workflows)
|
17
|
+
- [Code Generation](#code-generation)
|
18
|
+
|
19
|
+
## TLDR
|
20
|
+
|
21
|
+
- **Installation** - Add `gem 'cmdx'` to Gemfile, run `rails g cmdx:install`
|
22
|
+
- **Tasks** - Ruby classes inheriting from `CMDx::Task` with `call` method
|
23
|
+
- **Execution** - Use `call` (returns result) or `call!` (raises on failure/skip)
|
24
|
+
- **Parameters** - Define with `required`/`optional` with type coercion and validation
|
25
|
+
- **Results** - Check `result.status` for success/skipped/failed outcomes
|
26
|
+
- **Workflows** - Orchestrate multiple tasks with `CMDx::Workflow`
|
27
|
+
- **Generators** - Use `rails g cmdx:task` and `rails g cmdx:workflow` for scaffolding
|
28
|
+
|
29
|
+
## Installation
|
30
|
+
|
31
|
+
Add CMDx to your Gemfile:
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
gem 'cmdx'
|
35
|
+
```
|
36
|
+
|
37
|
+
For Rails applications, generate the configuration:
|
38
|
+
|
39
|
+
```bash
|
40
|
+
rails generate cmdx:install
|
41
|
+
```
|
42
|
+
|
43
|
+
> [!NOTE]
|
44
|
+
> This creates `config/initializers/cmdx.rb` with default settings.
|
45
|
+
|
46
|
+
## Quick Setup
|
11
47
|
|
12
48
|
```ruby
|
13
49
|
class ProcessOrderTask < CMDx::Task
|
50
|
+
required :order_id, type: :integer
|
51
|
+
optional :send_email, type: :boolean, default: true
|
14
52
|
|
15
53
|
def call
|
16
|
-
|
17
|
-
skip!(reason: "Order is processing") if context.order.processing?
|
54
|
+
context.order = Order.find(order_id)
|
18
55
|
|
19
|
-
|
20
|
-
|
56
|
+
if context.order.canceled?
|
57
|
+
fail!(reason: "Order canceled", canceled_at: context.order.canceled_at)
|
58
|
+
elsif context.order.completed?
|
59
|
+
skip!(reason: "Already processed")
|
60
|
+
else
|
61
|
+
context.order.update!(status: 'completed', completed_at: Time.now)
|
62
|
+
EmailService.send_confirmation(context.order) if send_email
|
63
|
+
end
|
21
64
|
end
|
22
|
-
|
23
65
|
end
|
24
66
|
```
|
25
67
|
|
68
|
+
> [!TIP]
|
69
|
+
> Use **present tense verbs** for clarity: `ProcessOrderTask`, `SendEmailTask`, `ValidatePaymentTask`
|
70
|
+
|
26
71
|
## Execution
|
27
72
|
|
73
|
+
Execute tasks using class methods:
|
74
|
+
|
75
|
+
```ruby
|
76
|
+
# Returns Result object
|
77
|
+
result = ProcessOrderTask.call(order_id: 123)
|
78
|
+
|
79
|
+
# Raises exceptions on failure/skip, else returns Result object
|
80
|
+
result = ProcessOrderTask.call!(order_id: 123, send_email: false)
|
81
|
+
```
|
82
|
+
|
83
|
+
## Result Handling
|
84
|
+
|
85
|
+
Check execution outcomes:
|
86
|
+
|
87
|
+
```ruby
|
88
|
+
result = ProcessOrderTask.call(order_id: 123)
|
89
|
+
|
90
|
+
case result.status
|
91
|
+
when 'success'
|
92
|
+
redirect_to order_path(result.context.order), notice: "Order processed!"
|
93
|
+
when 'skipped'
|
94
|
+
redirect_to order_path(result.context.order), notice: result.metadata[:reason]
|
95
|
+
when 'failed'
|
96
|
+
redirect_to orders_path, alert: "Error: #{result.metadata[:reason]}"
|
97
|
+
end
|
98
|
+
|
99
|
+
# Access execution metadata
|
100
|
+
puts "Runtime: #{result.runtime}s, Task ID: #{result.id}"
|
101
|
+
```
|
102
|
+
|
103
|
+
## Exception Handling
|
104
|
+
|
105
|
+
Use `call!` for exception-based control flow:
|
106
|
+
|
28
107
|
```ruby
|
29
|
-
|
108
|
+
begin
|
109
|
+
result = ProcessOrderTask.call!(order_id: 123)
|
110
|
+
redirect_to order_path(result.context.order), notice: "Success!"
|
111
|
+
rescue CMDx::Skipped => e
|
112
|
+
redirect_to orders_path, notice: e.result.metadata[:reason]
|
113
|
+
rescue CMDx::Failed => e
|
114
|
+
redirect_to order_path(123), alert: e.result.metadata[:reason]
|
115
|
+
end
|
30
116
|
```
|
31
117
|
|
32
|
-
##
|
118
|
+
## Building Workflows
|
119
|
+
|
120
|
+
Combine tasks using workflows:
|
33
121
|
|
34
122
|
```ruby
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
123
|
+
class OrderProcessingWorkflow < CMDx::Workflow
|
124
|
+
required :order_id, type: :integer
|
125
|
+
|
126
|
+
before_execution :log_workflow_start
|
127
|
+
on_failed :notify_support
|
128
|
+
|
129
|
+
process ValidateOrderTask
|
130
|
+
process ChargePaymentTask
|
131
|
+
process UpdateInventoryTask
|
132
|
+
process SendConfirmationTask, if: proc { context.payment_successful? }
|
133
|
+
|
134
|
+
# NO call method
|
135
|
+
|
136
|
+
private
|
137
|
+
|
138
|
+
def log_workflow_start
|
139
|
+
Rails.logger.info "Starting order workflow for order #{order_id}"
|
140
|
+
end
|
141
|
+
|
142
|
+
def notify_support
|
143
|
+
SupportNotifier.alert("Order workflow failed", context: context.to_h)
|
144
|
+
end
|
41
145
|
end
|
146
|
+
|
147
|
+
result = ProcessOrderWorkflow.call(order_id: 123)
|
42
148
|
```
|
43
149
|
|
150
|
+
## Code Generation
|
151
|
+
|
152
|
+
Generate tasks and workflows with proper structure:
|
153
|
+
|
154
|
+
```bash
|
155
|
+
# Generate individual task
|
156
|
+
rails generate cmdx:task ProcessOrder
|
157
|
+
# Creates: app/cmds/process_order_task.rb
|
158
|
+
|
159
|
+
# Generate task workflow
|
160
|
+
rails generate cmdx:workflow OrderDeliveryWorkflow
|
161
|
+
# Creates: app/cmds/order_delivery_workflow.rb
|
162
|
+
```
|
163
|
+
|
164
|
+
> [!NOTE]
|
165
|
+
> Generators automatically handle naming conventions and inherit from `ApplicationTask`/`ApplicationWorkflow` when available.
|
166
|
+
|
44
167
|
---
|
45
168
|
|
46
|
-
- **Prev:** [
|
47
|
-
- **Next:** [Configuration](
|
169
|
+
- **Prev:** [Tips and Tricks](tips_and_tricks.md)
|
170
|
+
- **Next:** [Configuration](configuration.md)
|
@@ -0,0 +1,148 @@
|
|
1
|
+
# Internationalization (i18n)
|
2
|
+
|
3
|
+
CMDx provides comprehensive internationalization support for all error messages, including parameter coercion errors, validation failures, and fault messages. All error text is automatically localized based on the current `I18n.locale`.
|
4
|
+
|
5
|
+
## Table of Contents
|
6
|
+
|
7
|
+
- [TLDR](#tldr)
|
8
|
+
- [Available Locales](#available-locales)
|
9
|
+
- [Fault Messages](#fault-messages)
|
10
|
+
- [Parameter Messages](#parameter-messages)
|
11
|
+
- [Coercion Messages](#coercion-messages)
|
12
|
+
- [Validation Messages](#validation-messages)
|
13
|
+
|
14
|
+
## TLDR
|
15
|
+
|
16
|
+
- **24 languages** - Built-in translations for major world languages
|
17
|
+
- **Automatic localization** - Based on `I18n.locale` setting
|
18
|
+
- **Complete coverage** - Coercion errors, validation failures, and fault messages
|
19
|
+
- **Custom overrides** - Parameter-specific messages override locale defaults
|
20
|
+
|
21
|
+
## Available Locales
|
22
|
+
|
23
|
+
CMDx includes built-in translations for 24 languages:
|
24
|
+
|
25
|
+
| Language | Locale | Language | Locale |
|
26
|
+
|----------|--------|----------|--------|
|
27
|
+
| English | `:en` | Russian | `:ru` |
|
28
|
+
| Spanish | `:es` | Arabic | `:ar` |
|
29
|
+
| French | `:fr` | Korean | `:ko` |
|
30
|
+
| German | `:de` | Dutch | `:nl` |
|
31
|
+
| Portuguese | `:pt` | Swedish | `:sv` |
|
32
|
+
| Italian | `:it` | Hindi | `:hi` |
|
33
|
+
| Japanese | `:ja` | Polish | `:pl` |
|
34
|
+
| Chinese | `:zh` | Turkish | `:tr` |
|
35
|
+
| Norwegian | `:no` | Danish | `:da` |
|
36
|
+
| Finnish | `:fi` | Greek | `:el` |
|
37
|
+
| Hebrew | `:he` | Thai | `:th` |
|
38
|
+
| Vietnamese | `:vi` | Czech | `:cs` |
|
39
|
+
|
40
|
+
## Fault Messages
|
41
|
+
|
42
|
+
Default fault messages from `skip!` and `fail!` methods are localized:
|
43
|
+
|
44
|
+
```ruby
|
45
|
+
class ProcessPaymentTask < CMDx::Task
|
46
|
+
def call
|
47
|
+
# When no reason is provided, uses localized default
|
48
|
+
fail! if payment_declined?
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# English
|
53
|
+
I18n.locale = :en
|
54
|
+
result = ProcessPaymentTask.call(payment_id: 123)
|
55
|
+
result.metadata[:reason] #=> "no reason given"
|
56
|
+
|
57
|
+
# Chinese
|
58
|
+
I18n.locale = :zh
|
59
|
+
result = ProcessPaymentTask.call(payment_id: 123)
|
60
|
+
result.metadata[:reason] #=> "未提供原因"
|
61
|
+
```
|
62
|
+
|
63
|
+
## Parameter Messages
|
64
|
+
|
65
|
+
Parameter required or undefined source errors are automatically localized:
|
66
|
+
|
67
|
+
```ruby
|
68
|
+
class ProcessOrderTask < CMDx::Task
|
69
|
+
required :order_id, type: :integer
|
70
|
+
optional :user_name, source: :nonexistent_method
|
71
|
+
|
72
|
+
def call
|
73
|
+
# Task implementation
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# English locale
|
78
|
+
I18n.locale = :en
|
79
|
+
result = ProcessOrderTask.call({}) # Missing required parameter
|
80
|
+
result.metadata[:messages][:order_id] #=> ["is a required parameter"]
|
81
|
+
|
82
|
+
result = ProcessOrderTask.call(order_id: 123) # Undefined source method
|
83
|
+
result.metadata[:messages][:user_name] #=> ["delegates to undefined method nonexistent_method"]
|
84
|
+
|
85
|
+
# Spanish locale
|
86
|
+
I18n.locale = :es
|
87
|
+
result = ProcessOrderTask.call({}) # Missing required parameter
|
88
|
+
result.metadata[:messages][:order_id] #=> ["es un parámetro requerido"]
|
89
|
+
|
90
|
+
result = ProcessOrderTask.call(order_id: 123) # Undefined source method
|
91
|
+
result.metadata[:messages][:user_name] #=> ["delegado al método indefinido nonexistent_method"]
|
92
|
+
```
|
93
|
+
|
94
|
+
## Coercion Messages
|
95
|
+
|
96
|
+
Type conversion errors are automatically localized:
|
97
|
+
|
98
|
+
```ruby
|
99
|
+
class ProcessOrderTask < CMDx::Task
|
100
|
+
required :order_id, type: :integer
|
101
|
+
required :amount, type: :float
|
102
|
+
|
103
|
+
def call
|
104
|
+
# Task implementation
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# English
|
109
|
+
I18n.locale = :en
|
110
|
+
result = ProcessOrderTask.call(order_id: "invalid", amount: "bad")
|
111
|
+
result.metadata[:messages][:order_id] #=> ["could not coerce into an integer"]
|
112
|
+
|
113
|
+
# Spanish
|
114
|
+
I18n.locale = :es
|
115
|
+
result = ProcessOrderTask.call(order_id: "invalid", amount: "bad")
|
116
|
+
result.metadata[:messages][:order_id] #=> ["no podía coacciona el valor a un integer"]
|
117
|
+
```
|
118
|
+
|
119
|
+
## Validation Messages
|
120
|
+
|
121
|
+
All validator error messages support internationalization:
|
122
|
+
|
123
|
+
```ruby
|
124
|
+
class RegisterUserTask < CMDx::Task
|
125
|
+
required :email, format: { with: /@/ }
|
126
|
+
required :age, numeric: { min: 18 }
|
127
|
+
required :status, inclusion: { in: %w[active inactive] }
|
128
|
+
|
129
|
+
def call
|
130
|
+
# Task implementation
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# English
|
135
|
+
I18n.locale = :en
|
136
|
+
result = RegisterUserTask.call(email: "invalid", age: 16, status: "unknown")
|
137
|
+
result.metadata[:messages][:email] #=> ["is an invalid format"]
|
138
|
+
|
139
|
+
# Japanese
|
140
|
+
I18n.locale = :ja
|
141
|
+
result = RegisterUserTask.call(email: "invalid", age: 16, status: "unknown")
|
142
|
+
result.metadata[:messages][:email] #=> ["無効な形式です"]
|
143
|
+
```
|
144
|
+
|
145
|
+
---
|
146
|
+
|
147
|
+
- **Prev:** [Logging](logging.md)
|
148
|
+
- **Next:** [Testing](testing.md)
|