cmdx 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.cursor/prompts/rspec.md +20 -0
- data/.cursor/prompts/yardoc.md +8 -0
- data/.rubocop.yml +5 -0
- data/CHANGELOG.md +101 -49
- data/README.md +2 -1
- data/docs/ai_prompts.md +10 -0
- data/docs/basics/call.md +11 -2
- data/docs/basics/chain.md +10 -1
- data/docs/basics/context.md +9 -0
- data/docs/basics/setup.md +9 -0
- data/docs/callbacks.md +14 -37
- data/docs/configuration.md +68 -27
- data/docs/getting_started.md +11 -0
- data/docs/internationalization.md +148 -0
- data/docs/interruptions/exceptions.md +10 -1
- data/docs/interruptions/faults.md +11 -2
- data/docs/interruptions/halt.md +9 -0
- data/docs/logging.md +14 -4
- data/docs/middlewares.md +53 -43
- data/docs/outcomes/result.md +9 -0
- data/docs/outcomes/states.md +9 -0
- data/docs/outcomes/statuses.md +9 -0
- data/docs/parameters/coercions.md +58 -38
- data/docs/parameters/defaults.md +10 -1
- data/docs/parameters/definitions.md +9 -0
- data/docs/parameters/namespacing.md +9 -0
- data/docs/parameters/validations.md +8 -67
- data/docs/testing.md +22 -13
- data/docs/tips_and_tricks.md +9 -0
- data/docs/workflows.md +14 -4
- data/lib/cmdx/.DS_Store +0 -0
- data/lib/cmdx/callback.rb +36 -56
- data/lib/cmdx/callback_registry.rb +82 -73
- data/lib/cmdx/chain.rb +65 -122
- data/lib/cmdx/chain_inspector.rb +22 -115
- data/lib/cmdx/chain_serializer.rb +17 -148
- data/lib/cmdx/coercion.rb +49 -0
- data/lib/cmdx/coercion_registry.rb +94 -0
- data/lib/cmdx/coercions/array.rb +18 -36
- data/lib/cmdx/coercions/big_decimal.rb +21 -33
- data/lib/cmdx/coercions/boolean.rb +21 -40
- data/lib/cmdx/coercions/complex.rb +18 -31
- data/lib/cmdx/coercions/date.rb +20 -39
- data/lib/cmdx/coercions/date_time.rb +22 -39
- data/lib/cmdx/coercions/float.rb +19 -32
- data/lib/cmdx/coercions/hash.rb +22 -41
- data/lib/cmdx/coercions/integer.rb +20 -33
- data/lib/cmdx/coercions/rational.rb +20 -32
- data/lib/cmdx/coercions/string.rb +23 -31
- data/lib/cmdx/coercions/time.rb +24 -40
- data/lib/cmdx/coercions/virtual.rb +14 -31
- data/lib/cmdx/configuration.rb +57 -171
- data/lib/cmdx/context.rb +22 -165
- data/lib/cmdx/core_ext/hash.rb +42 -67
- data/lib/cmdx/core_ext/module.rb +35 -79
- data/lib/cmdx/core_ext/object.rb +63 -98
- data/lib/cmdx/correlator.rb +40 -156
- data/lib/cmdx/error.rb +37 -202
- data/lib/cmdx/errors.rb +165 -202
- data/lib/cmdx/fault.rb +55 -158
- data/lib/cmdx/faults.rb +26 -137
- data/lib/cmdx/immutator.rb +22 -109
- data/lib/cmdx/lazy_struct.rb +103 -187
- data/lib/cmdx/log_formatters/json.rb +14 -40
- data/lib/cmdx/log_formatters/key_value.rb +14 -40
- data/lib/cmdx/log_formatters/line.rb +14 -48
- data/lib/cmdx/log_formatters/logstash.rb +14 -57
- data/lib/cmdx/log_formatters/pretty_json.rb +14 -50
- data/lib/cmdx/log_formatters/pretty_key_value.rb +13 -46
- data/lib/cmdx/log_formatters/pretty_line.rb +16 -54
- data/lib/cmdx/log_formatters/raw.rb +19 -49
- data/lib/cmdx/logger.rb +20 -82
- data/lib/cmdx/logger_ansi.rb +18 -75
- data/lib/cmdx/logger_serializer.rb +24 -114
- data/lib/cmdx/middleware.rb +38 -60
- data/lib/cmdx/middleware_registry.rb +81 -77
- data/lib/cmdx/middlewares/correlate.rb +41 -226
- data/lib/cmdx/middlewares/timeout.rb +46 -185
- data/lib/cmdx/parameter.rb +120 -198
- data/lib/cmdx/parameter_evaluator.rb +231 -0
- data/lib/cmdx/parameter_inspector.rb +25 -56
- data/lib/cmdx/parameter_registry.rb +59 -84
- data/lib/cmdx/parameter_serializer.rb +23 -74
- data/lib/cmdx/railtie.rb +24 -107
- data/lib/cmdx/result.rb +254 -260
- data/lib/cmdx/result_ansi.rb +19 -85
- data/lib/cmdx/result_inspector.rb +27 -68
- data/lib/cmdx/result_logger.rb +18 -81
- data/lib/cmdx/result_serializer.rb +28 -132
- data/lib/cmdx/rspec/matchers.rb +28 -0
- data/lib/cmdx/rspec/result_matchers/be_executed.rb +42 -0
- data/lib/cmdx/rspec/result_matchers/be_failed_task.rb +94 -0
- data/lib/cmdx/rspec/result_matchers/be_skipped_task.rb +94 -0
- data/lib/cmdx/rspec/result_matchers/be_state_matchers.rb +59 -0
- data/lib/cmdx/rspec/result_matchers/be_status_matchers.rb +57 -0
- data/lib/cmdx/rspec/result_matchers/be_successful_task.rb +87 -0
- data/lib/cmdx/rspec/result_matchers/have_bad_outcome.rb +51 -0
- data/lib/cmdx/rspec/result_matchers/have_caused_failure.rb +58 -0
- data/lib/cmdx/rspec/result_matchers/have_chain_index.rb +59 -0
- data/lib/cmdx/rspec/result_matchers/have_context.rb +86 -0
- data/lib/cmdx/rspec/result_matchers/have_empty_metadata.rb +54 -0
- data/lib/cmdx/rspec/result_matchers/have_good_outcome.rb +52 -0
- data/lib/cmdx/rspec/result_matchers/have_metadata.rb +114 -0
- data/lib/cmdx/rspec/result_matchers/have_preserved_context.rb +66 -0
- data/lib/cmdx/rspec/result_matchers/have_received_thrown_failure.rb +64 -0
- data/lib/cmdx/rspec/result_matchers/have_runtime.rb +78 -0
- data/lib/cmdx/rspec/result_matchers/have_thrown_failure.rb +76 -0
- data/lib/cmdx/rspec/task_matchers/be_well_formed_task.rb +62 -0
- data/lib/cmdx/rspec/task_matchers/have_callback.rb +85 -0
- data/lib/cmdx/rspec/task_matchers/have_cmd_setting.rb +68 -0
- data/lib/cmdx/rspec/task_matchers/have_executed_callbacks.rb +92 -0
- data/lib/cmdx/rspec/task_matchers/have_middleware.rb +46 -0
- data/lib/cmdx/rspec/task_matchers/have_parameter.rb +181 -0
- data/lib/cmdx/task.rb +213 -425
- data/lib/cmdx/task_deprecator.rb +55 -0
- data/lib/cmdx/task_processor.rb +245 -0
- data/lib/cmdx/task_serializer.rb +22 -70
- data/lib/cmdx/utils/ansi_color.rb +13 -89
- data/lib/cmdx/utils/log_timestamp.rb +13 -42
- data/lib/cmdx/utils/monotonic_runtime.rb +13 -63
- data/lib/cmdx/utils/name_affix.rb +21 -71
- data/lib/cmdx/validator.rb +48 -0
- data/lib/cmdx/validator_registry.rb +86 -0
- data/lib/cmdx/validators/exclusion.rb +55 -94
- data/lib/cmdx/validators/format.rb +31 -85
- data/lib/cmdx/validators/inclusion.rb +65 -110
- data/lib/cmdx/validators/length.rb +117 -133
- data/lib/cmdx/validators/numeric.rb +123 -130
- data/lib/cmdx/validators/presence.rb +38 -79
- data/lib/cmdx/version.rb +1 -7
- data/lib/cmdx/workflow.rb +46 -339
- data/lib/cmdx.rb +1 -1
- data/lib/generators/cmdx/install_generator.rb +14 -31
- data/lib/generators/cmdx/task_generator.rb +39 -55
- data/lib/generators/cmdx/templates/install.rb +61 -11
- data/lib/generators/cmdx/workflow_generator.rb +41 -66
- data/lib/locales/ar.yml +35 -0
- data/lib/locales/cs.yml +35 -0
- data/lib/locales/da.yml +35 -0
- data/lib/locales/de.yml +35 -0
- data/lib/locales/el.yml +35 -0
- data/lib/locales/en.yml +19 -20
- data/lib/locales/es.yml +19 -20
- data/lib/locales/fi.yml +35 -0
- data/lib/locales/fr.yml +35 -0
- data/lib/locales/he.yml +35 -0
- data/lib/locales/hi.yml +35 -0
- data/lib/locales/it.yml +35 -0
- data/lib/locales/ja.yml +35 -0
- data/lib/locales/ko.yml +35 -0
- data/lib/locales/nl.yml +35 -0
- data/lib/locales/no.yml +35 -0
- data/lib/locales/pl.yml +35 -0
- data/lib/locales/pt.yml +35 -0
- data/lib/locales/ru.yml +35 -0
- data/lib/locales/sv.yml +35 -0
- data/lib/locales/th.yml +35 -0
- data/lib/locales/tr.yml +35 -0
- data/lib/locales/vi.yml +35 -0
- data/lib/locales/zh.yml +35 -0
- metadata +57 -8
- data/lib/cmdx/parameter_validator.rb +0 -81
- data/lib/cmdx/parameter_value.rb +0 -244
- data/lib/cmdx/parameters_inspector.rb +0 -72
- data/lib/cmdx/parameters_serializer.rb +0 -115
- data/lib/cmdx/rspec/result_matchers.rb +0 -917
- data/lib/cmdx/rspec/task_matchers.rb +0 -570
- data/lib/cmdx/validators/custom.rb +0 -102
data/docs/middlewares.md
CHANGED
@@ -4,6 +4,7 @@ Middleware provides Rack-style wrappers around task execution for cross-cutting
|
|
4
4
|
|
5
5
|
## Table of Contents
|
6
6
|
|
7
|
+
- [TLDR](#tldr)
|
7
8
|
- [Using Middleware](#using-middleware)
|
8
9
|
- [Class Middleware](#class-middleware)
|
9
10
|
- [Instance Middleware](#instance-middleware)
|
@@ -16,15 +17,24 @@ Middleware provides Rack-style wrappers around task execution for cross-cutting
|
|
16
17
|
- [Correlate Middleware](#correlate-middleware)
|
17
18
|
- [Writing Custom Middleware](#writing-custom-middleware)
|
18
19
|
|
20
|
+
## TLDR
|
21
|
+
|
22
|
+
- **Purpose** - Rack-style wrappers for cross-cutting concerns (auth, logging, caching)
|
23
|
+
- **Declaration** - Use `use` method with classes, instances, or procs
|
24
|
+
- **Execution order** - Nested fashion (first declared wraps all others)
|
25
|
+
- **Short-circuiting** - Middleware can halt execution by not calling next callable
|
26
|
+
- **Inheritance** - Middleware is inherited from parent classes
|
27
|
+
- **Built-in** - Includes Timeout and Correlate middleware
|
28
|
+
|
19
29
|
## Using Middleware
|
20
30
|
|
21
31
|
Declare middleware using the `use` method in your task classes:
|
22
32
|
|
23
33
|
```ruby
|
24
34
|
class ProcessOrderTask < CMDx::Task
|
25
|
-
use AuthenticationMiddleware
|
26
|
-
use LoggingMiddleware, level: :info
|
27
|
-
use CachingMiddleware, ttl: 300
|
35
|
+
use :middleware, AuthenticationMiddleware
|
36
|
+
use :middleware, LoggingMiddleware, level: :info
|
37
|
+
use :middleware, CachingMiddleware, ttl: 300
|
28
38
|
|
29
39
|
def call
|
30
40
|
context.order = Order.find(order_id)
|
@@ -61,7 +71,7 @@ class AuditMiddleware < CMDx::Middleware
|
|
61
71
|
end
|
62
72
|
|
63
73
|
class ProcessOrderTask < CMDx::Task
|
64
|
-
use AuditMiddleware, action: 'process', resource_type: 'Order'
|
74
|
+
use :middleware, AuditMiddleware, action: 'process', resource_type: 'Order'
|
65
75
|
|
66
76
|
def call
|
67
77
|
context.order = Order.find(order_id)
|
@@ -76,7 +86,7 @@ Pre-configured middleware instances for complex initialization:
|
|
76
86
|
|
77
87
|
```ruby
|
78
88
|
class ProcessOrderTask < CMDx::Task
|
79
|
-
use LoggingMiddleware.new(
|
89
|
+
use :middleware, LoggingMiddleware.new(
|
80
90
|
level: :debug,
|
81
91
|
formatter: JSON::JSONFormatter.new,
|
82
92
|
tags: ['order', 'payment']
|
@@ -94,7 +104,7 @@ Inline middleware for simple cases:
|
|
94
104
|
|
95
105
|
```ruby
|
96
106
|
class ProcessOrderTask < CMDx::Task
|
97
|
-
use proc { |task, callable|
|
107
|
+
use :middleware, proc { |task, callable|
|
98
108
|
start_time = Time.now
|
99
109
|
result = callable.call(task)
|
100
110
|
duration = Time.now - start_time
|
@@ -115,9 +125,9 @@ Middleware executes in nested fashion - first declared wraps all others:
|
|
115
125
|
|
116
126
|
```ruby
|
117
127
|
class ProcessOrderTask < CMDx::Task
|
118
|
-
use TimingMiddleware # 1st: outermost
|
119
|
-
use AuthenticationMiddleware # 2nd: middle
|
120
|
-
use ValidationMiddleware # 3rd: innermost
|
128
|
+
use :middleware, TimingMiddleware # 1st: outermost
|
129
|
+
use :middleware, AuthenticationMiddleware # 2nd: middle
|
130
|
+
use :middleware, ValidationMiddleware # 3rd: innermost
|
121
131
|
|
122
132
|
def call
|
123
133
|
# Core logic executes last
|
@@ -163,7 +173,7 @@ class RateLimitMiddleware < CMDx::Middleware
|
|
163
173
|
end
|
164
174
|
|
165
175
|
class SendEmailTask < CMDx::Task
|
166
|
-
use RateLimitMiddleware, limit: 50, window: 1.hour
|
176
|
+
use :middleware, RateLimitMiddleware, limit: 50, window: 1.hour
|
167
177
|
|
168
178
|
def call
|
169
179
|
# Only executes if rate limit check passes
|
@@ -182,14 +192,14 @@ Middleware is inherited from parent classes, enabling application-wide patterns:
|
|
182
192
|
|
183
193
|
```ruby
|
184
194
|
class ApplicationTask < CMDx::Task
|
185
|
-
use RequestIdMiddleware # All tasks get request tracking
|
186
|
-
use PerformanceMiddleware # All tasks get performance monitoring
|
187
|
-
use ErrorReportingMiddleware # All tasks get error reporting
|
195
|
+
use :middleware, RequestIdMiddleware # All tasks get request tracking
|
196
|
+
use :middleware, PerformanceMiddleware # All tasks get performance monitoring
|
197
|
+
use :middleware, ErrorReportingMiddleware # All tasks get error reporting
|
188
198
|
end
|
189
199
|
|
190
200
|
class ProcessOrderTask < ApplicationTask
|
191
|
-
use AuthenticationMiddleware # Specific to order processing
|
192
|
-
use OrderValidationMiddleware # Domain-specific validation
|
201
|
+
use :middleware, AuthenticationMiddleware # Specific to order processing
|
202
|
+
use :middleware, OrderValidationMiddleware # Domain-specific validation
|
193
203
|
|
194
204
|
def call
|
195
205
|
# Inherits all ApplicationTask middleware plus order-specific ones
|
@@ -210,7 +220,7 @@ Enforces execution time limits with support for static and dynamic timeout value
|
|
210
220
|
|
211
221
|
```ruby
|
212
222
|
class ProcessLargeReportTask < CMDx::Task
|
213
|
-
use CMDx::Middlewares::Timeout, seconds: 300 # 5 minutes
|
223
|
+
use :middleware, CMDx::Middlewares::Timeout, seconds: 300 # 5 minutes
|
214
224
|
|
215
225
|
def call
|
216
226
|
# Long-running report generation
|
@@ -219,7 +229,7 @@ end
|
|
219
229
|
|
220
230
|
# Default timeout (3 seconds when no value specified)
|
221
231
|
class QuickValidationTask < CMDx::Task
|
222
|
-
use CMDx::Middlewares::Timeout # Uses 3 seconds default
|
232
|
+
use :middleware, CMDx::Middlewares::Timeout # Uses 3 seconds default
|
223
233
|
|
224
234
|
def call
|
225
235
|
# Fast validation logic
|
@@ -234,7 +244,7 @@ The middleware supports dynamic timeout calculation using method names, procs, a
|
|
234
244
|
```ruby
|
235
245
|
# Method-based timeout calculation
|
236
246
|
class ProcessOrderTask < CMDx::Task
|
237
|
-
use CMDx::Middlewares::Timeout, seconds: :calculate_timeout
|
247
|
+
use :middleware, CMDx::Middlewares::Timeout, seconds: :calculate_timeout
|
238
248
|
|
239
249
|
def call
|
240
250
|
# Task execution with dynamic timeout
|
@@ -255,7 +265,7 @@ end
|
|
255
265
|
|
256
266
|
# Proc-based timeout for inline calculation
|
257
267
|
class ProcessWorkflowTask < CMDx::Task
|
258
|
-
use CMDx::Middlewares::Timeout, seconds: -> {
|
268
|
+
use :middleware, CMDx::Middlewares::Timeout, seconds: -> {
|
259
269
|
context.workflow_size > 100 ? 120 : 60
|
260
270
|
}
|
261
271
|
|
@@ -267,7 +277,7 @@ end
|
|
267
277
|
|
268
278
|
# Context-aware timeout calculation
|
269
279
|
class GenerateReportTask < CMDx::Task
|
270
|
-
use CMDx::Middlewares::Timeout, seconds: :report_timeout
|
280
|
+
use :middleware, CMDx::Middlewares::Timeout, seconds: :report_timeout
|
271
281
|
|
272
282
|
def call
|
273
283
|
context.report = ReportGenerator.create(report_params)
|
@@ -299,12 +309,12 @@ The middleware follows this precedence for determining timeout values:
|
|
299
309
|
```ruby
|
300
310
|
# Static timeout - highest precedence when specified
|
301
311
|
class ProcessOrderTask < CMDx::Task
|
302
|
-
use CMDx::Middlewares::Timeout, seconds: 45 # Always 45 seconds
|
312
|
+
use :middleware, CMDx::Middlewares::Timeout, seconds: 45 # Always 45 seconds
|
303
313
|
end
|
304
314
|
|
305
315
|
# Method-based timeout - calls task method
|
306
316
|
class ProcessOrderTask < CMDx::Task
|
307
|
-
use CMDx::Middlewares::Timeout, seconds: :dynamic_timeout
|
317
|
+
use :middleware, CMDx::Middlewares::Timeout, seconds: :dynamic_timeout
|
308
318
|
|
309
319
|
private
|
310
320
|
def dynamic_timeout
|
@@ -314,7 +324,7 @@ end
|
|
314
324
|
|
315
325
|
# Default fallback when method returns nil
|
316
326
|
class ProcessOrderTask < CMDx::Task
|
317
|
-
use CMDx::Middlewares::Timeout, seconds: :might_return_nil
|
327
|
+
use :middleware, CMDx::Middlewares::Timeout, seconds: :might_return_nil
|
318
328
|
|
319
329
|
private
|
320
330
|
def might_return_nil
|
@@ -330,7 +340,7 @@ Apply timeout middleware conditionally based on environment or task state:
|
|
330
340
|
```ruby
|
331
341
|
# Environment-based conditional timeout
|
332
342
|
class ProcessOrderTask < CMDx::Task
|
333
|
-
use CMDx::Middlewares::Timeout,
|
343
|
+
use :middleware, CMDx::Middlewares::Timeout,
|
334
344
|
seconds: 60,
|
335
345
|
unless: -> { Rails.env.development? }
|
336
346
|
|
@@ -343,7 +353,7 @@ end
|
|
343
353
|
|
344
354
|
# Context-based conditional timeout
|
345
355
|
class SendEmailTask < CMDx::Task
|
346
|
-
use CMDx::Middlewares::Timeout,
|
356
|
+
use :middleware, CMDx::Middlewares::Timeout,
|
347
357
|
seconds: 30,
|
348
358
|
if: :timeout_enabled?
|
349
359
|
|
@@ -360,7 +370,7 @@ end
|
|
360
370
|
|
361
371
|
# Combined dynamic timeout with conditions
|
362
372
|
class ProcessComplexOrderTask < CMDx::Task
|
363
|
-
use CMDx::Middlewares::Timeout,
|
373
|
+
use :middleware, CMDx::Middlewares::Timeout,
|
364
374
|
seconds: :calculate_timeout,
|
365
375
|
unless: :skip_timeout?
|
366
376
|
|
@@ -389,11 +399,11 @@ Apply timeout middleware globally with inheritance:
|
|
389
399
|
|
390
400
|
```ruby
|
391
401
|
class ApplicationTask < CMDx::Task
|
392
|
-
use CMDx::Middlewares::Timeout, seconds: 60 # Default 60 seconds for all tasks
|
402
|
+
use :middleware, CMDx::Middlewares::Timeout, seconds: 60 # Default 60 seconds for all tasks
|
393
403
|
end
|
394
404
|
|
395
405
|
class QuickTask < ApplicationTask
|
396
|
-
use CMDx::Middlewares::Timeout, seconds: 15 # Override with 15 seconds
|
406
|
+
use :middleware, CMDx::Middlewares::Timeout, seconds: 15 # Override with 15 seconds
|
397
407
|
|
398
408
|
def call
|
399
409
|
# Fast operation with shorter timeout
|
@@ -401,7 +411,7 @@ class QuickTask < ApplicationTask
|
|
401
411
|
end
|
402
412
|
|
403
413
|
class LongRunningTask < ApplicationTask
|
404
|
-
use CMDx::Middlewares::Timeout, seconds: :dynamic_timeout
|
414
|
+
use :middleware, CMDx::Middlewares::Timeout, seconds: :dynamic_timeout
|
405
415
|
|
406
416
|
def call
|
407
417
|
# Long operation with dynamic timeout
|
@@ -427,7 +437,7 @@ Manages correlation IDs for request tracing across task boundaries. This middlew
|
|
427
437
|
|
428
438
|
```ruby
|
429
439
|
class ProcessApiRequestTask < CMDx::Task
|
430
|
-
use CMDx::Middlewares::Correlate
|
440
|
+
use :middleware, CMDx::Middlewares::Correlate
|
431
441
|
|
432
442
|
def call
|
433
443
|
# Correlation ID is automatically managed
|
@@ -446,19 +456,19 @@ The middleware follows a hierarchical precedence system for determining correlat
|
|
446
456
|
|
447
457
|
# 1a. Static string ID
|
448
458
|
class ProcessOrderTask < CMDx::Task
|
449
|
-
use CMDx::Middlewares::Correlate, id: "fixed-correlation-123"
|
459
|
+
use :middleware, CMDx::Middlewares::Correlate, id: "fixed-correlation-123"
|
450
460
|
end
|
451
461
|
ProcessOrderTask.call # Always uses "fixed-correlation-123"
|
452
462
|
|
453
463
|
# 1b. Dynamic proc/lambda ID
|
454
464
|
class ProcessOrderTask < CMDx::Task
|
455
|
-
use CMDx::Middlewares::Correlate, id: -> { "order-#{order_id}-#{rand(1000)}" }
|
465
|
+
use :middleware, CMDx::Middlewares::Correlate, id: -> { "order-#{order_id}-#{rand(1000)}" }
|
456
466
|
end
|
457
467
|
ProcessOrderTask.call(order_id: 456) # Uses "order-456-847" (random number varies)
|
458
468
|
|
459
469
|
# 1c. Method-based ID
|
460
470
|
class ProcessOrderTask < CMDx::Task
|
461
|
-
use CMDx::Middlewares::Correlate, id: :correlation_method
|
471
|
+
use :middleware, CMDx::Middlewares::Correlate, id: :correlation_method
|
462
472
|
|
463
473
|
private
|
464
474
|
|
@@ -488,7 +498,7 @@ Set fixed or dynamic correlation IDs for specific tasks or workflows using strin
|
|
488
498
|
```ruby
|
489
499
|
# Static string correlation ID
|
490
500
|
class ProcessPaymentTask < CMDx::Task
|
491
|
-
use CMDx::Middlewares::Correlate, id: "payment-processing"
|
501
|
+
use :middleware, CMDx::Middlewares::Correlate, id: "payment-processing"
|
492
502
|
|
493
503
|
def call
|
494
504
|
# Always uses "payment-processing" as correlation ID
|
@@ -499,7 +509,7 @@ end
|
|
499
509
|
|
500
510
|
# Dynamic correlation ID using proc/lambda
|
501
511
|
class ProcessOrderTask < CMDx::Task
|
502
|
-
use CMDx::Middlewares::Correlate, id: -> { "order-#{order_id}-#{Time.now.to_i}" }
|
512
|
+
use :middleware, CMDx::Middlewares::Correlate, id: -> { "order-#{order_id}-#{Time.now.to_i}" }
|
503
513
|
|
504
514
|
def call
|
505
515
|
# Dynamic correlation ID based on order and timestamp
|
@@ -511,7 +521,7 @@ end
|
|
511
521
|
|
512
522
|
# Method-based correlation ID
|
513
523
|
class ProcessApiRequestTask < CMDx::Task
|
514
|
-
use CMDx::Middlewares::Correlate, id: :generate_correlation_id
|
524
|
+
use :middleware, CMDx::Middlewares::Correlate, id: :generate_correlation_id
|
515
525
|
|
516
526
|
def call
|
517
527
|
# Uses correlation ID from generate_correlation_id method
|
@@ -527,7 +537,7 @@ end
|
|
527
537
|
|
528
538
|
# Symbol fallback when method doesn't exist
|
529
539
|
class ProcessWorkflowTask < CMDx::Task
|
530
|
-
use CMDx::Middlewares::Correlate, id: :workflow_processing
|
540
|
+
use :middleware, CMDx::Middlewares::Correlate, id: :workflow_processing
|
531
541
|
|
532
542
|
def call
|
533
543
|
# Uses :workflow_processing as correlation ID (symbol as-is)
|
@@ -544,7 +554,7 @@ Apply correlation middleware conditionally based on environment or task state:
|
|
544
554
|
```ruby
|
545
555
|
class ProcessOrderTask < CMDx::Task
|
546
556
|
# Only apply correlation in production environments
|
547
|
-
use CMDx::Middlewares::Correlate, unless: -> { Rails.env.development? }
|
557
|
+
use :middleware, CMDx::Middlewares::Correlate, unless: -> { Rails.env.development? }
|
548
558
|
|
549
559
|
def call
|
550
560
|
context.order = Order.find(order_id)
|
@@ -554,7 +564,7 @@ end
|
|
554
564
|
|
555
565
|
class SendEmailTask < CMDx::Task
|
556
566
|
# Apply correlation only when tracing is enabled
|
557
|
-
use CMDx::Middlewares::Correlate, if: :tracing_enabled?
|
567
|
+
use :middleware, CMDx::Middlewares::Correlate, if: :tracing_enabled?
|
558
568
|
|
559
569
|
def call
|
560
570
|
EmailService.deliver(email_params)
|
@@ -574,7 +584,7 @@ Use correlation blocks to establish correlation contexts for groups of related t
|
|
574
584
|
|
575
585
|
```ruby
|
576
586
|
class ProcessOrderWorkflowTask < CMDx::Task
|
577
|
-
use CMDx::Middlewares::Correlate
|
587
|
+
use :middleware, CMDx::Middlewares::Correlate
|
578
588
|
|
579
589
|
def call
|
580
590
|
# Establish correlation context for entire workflow
|
@@ -594,7 +604,7 @@ Apply correlation middleware globally to all tasks:
|
|
594
604
|
|
595
605
|
```ruby
|
596
606
|
class ApplicationTask < CMDx::Task
|
597
|
-
use CMDx::Middlewares::Correlate # All tasks get correlation management
|
607
|
+
use :middleware, CMDx::Middlewares::Correlate # All tasks get correlation management
|
598
608
|
end
|
599
609
|
|
600
610
|
class ProcessOrderTask < ApplicationTask
|
@@ -638,7 +648,7 @@ class ApiController < ApplicationController
|
|
638
648
|
end
|
639
649
|
|
640
650
|
class ProcessOrderTask < CMDx::Task
|
641
|
-
use CMDx::Middlewares::Correlate
|
651
|
+
use :middleware, CMDx::Middlewares::Correlate
|
642
652
|
|
643
653
|
def call
|
644
654
|
# Inherits correlation ID from controller thread context
|
@@ -651,7 +661,7 @@ end
|
|
651
661
|
|
652
662
|
# Alternative: Task-specific correlation for API endpoints
|
653
663
|
class ProcessApiOrderTask < CMDx::Task
|
654
|
-
use CMDx::Middlewares::Correlate, id: -> { "api-order-#{context.request_id}" }
|
664
|
+
use :middleware, CMDx::Middlewares::Correlate, id: -> { "api-order-#{context.request_id}" }
|
655
665
|
|
656
666
|
def call
|
657
667
|
# Uses correlation ID specific to this API request
|
data/docs/outcomes/result.md
CHANGED
@@ -7,6 +7,7 @@ inspecting task execution outcomes and chaining task operations.
|
|
7
7
|
|
8
8
|
## Table of Contents
|
9
9
|
|
10
|
+
- [TLDR](#tldr)
|
10
11
|
- [Core Result Attributes](#core-result-attributes)
|
11
12
|
- [State and Status Information](#state-and-status-information)
|
12
13
|
- [Execution Outcome Analysis](#execution-outcome-analysis)
|
@@ -17,6 +18,14 @@ inspecting task execution outcomes and chaining task operations.
|
|
17
18
|
- [Pattern Matching](#pattern-matching)
|
18
19
|
- [Serialization and Inspection](#serialization-and-inspection)
|
19
20
|
|
21
|
+
## TLDR
|
22
|
+
|
23
|
+
- **Result object** - Comprehensive return value from task execution with `task`, `context`, `chain`, `metadata`
|
24
|
+
- **Status checking** - Use `result.success?`, `result.failed?`, `result.skipped?` for outcomes
|
25
|
+
- **State checking** - Use `result.complete?`, `result.interrupted?`, `result.executed?` for lifecycle
|
26
|
+
- **Callbacks** - Chain with `.on_success`, `.on_failed`, `.on_good`, `.on_bad` for conditional logic
|
27
|
+
- **Failure analysis** - Use `result.caused_failure`, `result.threw_failure` to trace failure chains
|
28
|
+
|
20
29
|
## Core Result Attributes
|
21
30
|
|
22
31
|
Every result provides access to essential execution information:
|
data/docs/outcomes/states.md
CHANGED
@@ -7,6 +7,7 @@ decision making and monitoring.
|
|
7
7
|
|
8
8
|
## Table of Contents
|
9
9
|
|
10
|
+
- [TLDR](#tldr)
|
10
11
|
- [State Definitions](#state-definitions)
|
11
12
|
- [State Transitions](#state-transitions)
|
12
13
|
- [State Predicates](#state-predicates)
|
@@ -15,6 +16,14 @@ decision making and monitoring.
|
|
15
16
|
- [State Inspection and Monitoring](#state-inspection-and-monitoring)
|
16
17
|
- [State Persistence and Logging](#state-persistence-and-logging)
|
17
18
|
|
19
|
+
## TLDR
|
20
|
+
|
21
|
+
- **States** - Track execution lifecycle: `initialized` → `executing` → `complete`/`interrupted`
|
22
|
+
- **Automatic** - States are managed automatically by the framework, never modify manually
|
23
|
+
- **Predicates** - Check with `result.complete?`, `result.interrupted?`, `result.executed?`
|
24
|
+
- **Callbacks** - Use `.on_complete`, `.on_interrupted`, `.on_executed` for lifecycle events
|
25
|
+
- **vs Status** - State = where in lifecycle, Status = how execution ended
|
26
|
+
|
18
27
|
## State Definitions
|
19
28
|
|
20
29
|
| State | Description |
|
data/docs/outcomes/statuses.md
CHANGED
@@ -4,6 +4,7 @@ Statuses represent the outcome of task execution logic, indicating how the task'
|
|
4
4
|
|
5
5
|
## Table of Contents
|
6
6
|
|
7
|
+
- [TLDR](#tldr)
|
7
8
|
- [Status Definitions](#status-definitions)
|
8
9
|
- [Status Characteristics](#status-characteristics)
|
9
10
|
- [Status Predicates](#status-predicates)
|
@@ -14,6 +15,14 @@ Statuses represent the outcome of task execution logic, indicating how the task'
|
|
14
15
|
- [Status Serialization and Inspection](#status-serialization-and-inspection)
|
15
16
|
- [Status vs State vs Outcome](#status-vs-state-vs-outcome)
|
16
17
|
|
18
|
+
## TLDR
|
19
|
+
|
20
|
+
- **Statuses** - Business outcome of execution: `success` (default), `skipped` (via `skip!`), `failed` (via `fail!`)
|
21
|
+
- **One-way transitions** - Only `success` → `skipped`/`failed`, never reverse
|
22
|
+
- **Predicates** - Check with `result.success?`, `result.skipped?`, `result.failed?`
|
23
|
+
- **Outcomes** - `result.good?` = success OR skipped, `result.bad?` = skipped OR failed
|
24
|
+
- **Rich metadata** - Both `skip!()` and `fail!()` accept metadata for context
|
25
|
+
|
17
26
|
## Status Definitions
|
18
27
|
|
19
28
|
| Status | Description |
|
@@ -7,6 +7,7 @@ string-to-integer conversion to complex JSON parsing and custom type handling.
|
|
7
7
|
|
8
8
|
## Table of Contents
|
9
9
|
|
10
|
+
- [TLDR](#tldr)
|
10
11
|
- [Coercion Fundamentals](#coercion-fundamentals)
|
11
12
|
- [Available Coercion Types](#available-coercion-types)
|
12
13
|
- [Basic Type Coercion](#basic-type-coercion)
|
@@ -19,11 +20,19 @@ string-to-integer conversion to complex JSON parsing and custom type handling.
|
|
19
20
|
- [Numeric Coercion](#numeric-coercion)
|
20
21
|
- [Coercion with Nested Parameters](#coercion-with-nested-parameters)
|
21
22
|
- [Coercion Error Handling](#coercion-error-handling)
|
22
|
-
- [Single Type Coercion Errors](#single-type-coercion-errors)
|
23
|
-
- [Multiple Type Coercion Errors](#multiple-type-coercion-errors)
|
24
23
|
- [Custom Coercion Options](#custom-coercion-options)
|
25
24
|
- [Date/Time Format Options](#datetime-format-options)
|
26
25
|
- [BigDecimal Precision Options](#bigdecimal-precision-options)
|
26
|
+
- [Custom Coercions](#custom-coercions)
|
27
|
+
|
28
|
+
## TLDR
|
29
|
+
|
30
|
+
- **Type coercion** - Automatic conversion using `type:` option (`:integer`, `:boolean`, `:array`, `:hash`, etc.)
|
31
|
+
- **Multiple types** - Fallback with `type: [:float, :integer]` - tries each until one succeeds
|
32
|
+
- **No conversion** - Default `:virtual` type returns values unchanged
|
33
|
+
- **Before validation** - Coercion happens automatically before parameter validation
|
34
|
+
- **Rich types** - Supports all Ruby built-ins plus JSON parsing for arrays/hashes
|
35
|
+
- **Custom coercions** - Register custom coercion types
|
27
36
|
|
28
37
|
## Coercion Fundamentals
|
29
38
|
|
@@ -307,13 +316,11 @@ end
|
|
307
316
|
> [!WARNING]
|
308
317
|
> When coercion fails, CMDx provides detailed error information including the parameter name, attempted types, and specific failure reasons.
|
309
318
|
|
310
|
-
### Single Type Coercion Errors
|
311
|
-
|
312
319
|
```ruby
|
313
320
|
class ValidateUserProfileTask < CMDx::Task
|
314
321
|
|
315
322
|
required :age, type: :integer
|
316
|
-
required :salary, type: :float
|
323
|
+
required :salary, type: [:float, :big_decimal]
|
317
324
|
required :is_employed, type: :boolean
|
318
325
|
|
319
326
|
def call
|
@@ -332,46 +339,15 @@ result = ValidateUserProfileTask.call(
|
|
332
339
|
result.failed? #=> true
|
333
340
|
result.metadata
|
334
341
|
#=> {
|
335
|
-
# reason: "age could not coerce into an integer.
|
342
|
+
# reason: "age could not coerce into an integer. could not coerce into one of: float, big_decimal. is_employed could not coerce into a boolean.",
|
336
343
|
# messages: {
|
337
344
|
# age: ["could not coerce into an integer"],
|
338
|
-
# salary: ["could not coerce into
|
345
|
+
# salary: ["could not coerce into one of: float, big_decimal"],
|
339
346
|
# is_employed: ["could not coerce into a boolean"]
|
340
347
|
# }
|
341
348
|
# }
|
342
349
|
```
|
343
350
|
|
344
|
-
### Multiple Type Coercion Errors
|
345
|
-
|
346
|
-
```ruby
|
347
|
-
class ProcessFlexibleDataTask < CMDx::Task
|
348
|
-
|
349
|
-
required :order_value, type: [:float, :integer]
|
350
|
-
required :customer_data, type: [:hash, :array, :string]
|
351
|
-
|
352
|
-
def call
|
353
|
-
# Task logic here
|
354
|
-
end
|
355
|
-
|
356
|
-
end
|
357
|
-
|
358
|
-
# Failed coercion with multiple types
|
359
|
-
result = ProcessFlexibleDataTask.call(
|
360
|
-
order_value: "invalid-number",
|
361
|
-
customer_data: Object.new
|
362
|
-
)
|
363
|
-
|
364
|
-
result.failed? #=> true
|
365
|
-
result.metadata
|
366
|
-
#=> {
|
367
|
-
# reason: "order_value could not coerce into one of: float, integer. customer_data could not coerce into one of: hash, array, string.",
|
368
|
-
# messages: {
|
369
|
-
# order_value: ["could not coerce into one of: float, integer"],
|
370
|
-
# customer_data: ["could not coerce into one of: hash, array, string"]
|
371
|
-
# }
|
372
|
-
# }
|
373
|
-
```
|
374
|
-
|
375
351
|
## Custom Coercion Options
|
376
352
|
|
377
353
|
### Date/Time Format Options
|
@@ -419,6 +395,50 @@ class CalculatePricingTask < CMDx::Task
|
|
419
395
|
end
|
420
396
|
```
|
421
397
|
|
398
|
+
## Custom Coercions
|
399
|
+
|
400
|
+
> [!NOTE]
|
401
|
+
> CMDx allows you to register custom coercions for domain-specific types that aren't covered by the built-in coercions. Custom coercions can be registered globally or per-task basis.
|
402
|
+
|
403
|
+
```ruby
|
404
|
+
module MoneyCoercion
|
405
|
+
module_function
|
406
|
+
|
407
|
+
def call(value, options = {})
|
408
|
+
return value if value.is_a?(BigDecimal)
|
409
|
+
|
410
|
+
# Handle string amounts like "$123.45"
|
411
|
+
if value.is_a?(String)
|
412
|
+
clean_value = value.gsub(/[$,]/, '')
|
413
|
+
BigDecimal(clean_value)
|
414
|
+
else
|
415
|
+
BigDecimal(value.to_s)
|
416
|
+
end
|
417
|
+
end
|
418
|
+
end
|
419
|
+
|
420
|
+
CMDx.configure do |config|
|
421
|
+
config.coercions.register(:money, MoneyCoercion)
|
422
|
+
config.coercions.register(:slug, proc do |value|
|
423
|
+
value.to_s.downcase.gsub(/[^a-z0-9]+/, '-').gsub(/-+/, '-').strip('-')
|
424
|
+
end)
|
425
|
+
end
|
426
|
+
|
427
|
+
# Now use in any task
|
428
|
+
class ProcessProductTask < CMDx::Task
|
429
|
+
required :cost, type: :money
|
430
|
+
required :slug, type: :slug
|
431
|
+
|
432
|
+
def call
|
433
|
+
cost #=> 123.45
|
434
|
+
slug #=> "my-blog-post-title" (URL-friendly)
|
435
|
+
end
|
436
|
+
end
|
437
|
+
```
|
438
|
+
|
439
|
+
> [!TIP]
|
440
|
+
> Custom coercions should be idempotent - calling them multiple times with the same input should produce the same result. This ensures predictable behavior when coercions are applied during parameter processing.
|
441
|
+
|
422
442
|
---
|
423
443
|
|
424
444
|
- **Prev:** [Parameters - Namespacing](namespacing.md)
|
data/docs/parameters/defaults.md
CHANGED
@@ -7,6 +7,7 @@ Defaults work seamlessly with coercion, validation, and nested parameters.
|
|
7
7
|
|
8
8
|
## Table of Contents
|
9
9
|
|
10
|
+
- [TLDR](#tldr)
|
10
11
|
- [Default Value Fundamentals](#default-value-fundamentals)
|
11
12
|
- [Fixed Value Defaults](#fixed-value-defaults)
|
12
13
|
- [Callable Defaults](#callable-defaults)
|
@@ -14,6 +15,14 @@ Defaults work seamlessly with coercion, validation, and nested parameters.
|
|
14
15
|
- [Defaults with Validation](#defaults-with-validation)
|
15
16
|
- [Nested Parameter Defaults](#nested-parameter-defaults)
|
16
17
|
|
18
|
+
## TLDR
|
19
|
+
|
20
|
+
- **Defaults** - Provide fallback values when parameters not provided or are `nil`
|
21
|
+
- **Fixed values** - `default: "normal"`, `default: true`, `default: []`
|
22
|
+
- **Dynamic values** - `default: -> { Time.now }`, `default: :method_name` for callable defaults
|
23
|
+
- **With coercion** - Defaults are subject to same type coercion as provided values
|
24
|
+
- **With validation** - Defaults must pass same validation rules as provided values
|
25
|
+
|
17
26
|
## Default Value Fundamentals
|
18
27
|
|
19
28
|
> [!NOTE]
|
@@ -171,7 +180,7 @@ class ValidateOrderPriorityTask < CMDx::Task
|
|
171
180
|
|
172
181
|
# Custom validation with default
|
173
182
|
optional :approval_code, type: :string, default: :generate_approval_code,
|
174
|
-
|
183
|
+
presence: true
|
175
184
|
|
176
185
|
def call
|
177
186
|
priority #=> "standard" (validated against inclusion list)
|
@@ -4,12 +4,21 @@ Parameters provide a contract to verify that task execution arguments match expe
|
|
4
4
|
|
5
5
|
## Table of Contents
|
6
6
|
|
7
|
+
- [TLDR](#tldr)
|
7
8
|
- [Parameter Fundamentals](#parameter-fundamentals)
|
8
9
|
- [Parameter Sources](#parameter-sources)
|
9
10
|
- [Nested Parameters](#nested-parameters)
|
10
11
|
- [Parameter Method Generation](#parameter-method-generation)
|
11
12
|
- [Error Handling](#error-handling)
|
12
13
|
|
14
|
+
## TLDR
|
15
|
+
|
16
|
+
- **Required/Optional** - Define with `required :param` and `optional :param` class methods
|
17
|
+
- **Method generation** - Parameters become instance methods for easy access
|
18
|
+
- **Sources** - Default `:context` source, or custom with `source: :user`
|
19
|
+
- **Nested params** - Complex structures with `required :address do ... end`
|
20
|
+
- **Call interface** - Parameters passed as keyword arguments to `TaskClass.call(param: value)`
|
21
|
+
|
13
22
|
## Parameter Fundamentals
|
14
23
|
|
15
24
|
Parameters are defined using `required` and `optional` class methods that automatically create accessor methods within task instances. Parameters are matched from call arguments and made available as instance methods.
|
@@ -7,6 +7,7 @@ same name, namespacing ensures clean method resolution within tasks.
|
|
7
7
|
|
8
8
|
## Table of Contents
|
9
9
|
|
10
|
+
- [TLDR](#tldr)
|
10
11
|
- [Namespacing Fundamentals](#namespacing-fundamentals)
|
11
12
|
- [Fixed Value Namespacing](#fixed-value-namespacing)
|
12
13
|
- [Dynamic Source-Based Namespacing](#dynamic-source-based-namespacing)
|
@@ -14,6 +15,14 @@ same name, namespacing ensures clean method resolution within tasks.
|
|
14
15
|
- [Advanced Namespacing Patterns](#advanced-namespacing-patterns)
|
15
16
|
- [Error Handling with Namespacing](#error-handling-with-namespacing)
|
16
17
|
|
18
|
+
## TLDR
|
19
|
+
|
20
|
+
- **Method naming** - Use `prefix:` and `suffix:` to customize parameter method names
|
21
|
+
- **Fixed prefixes** - `prefix: "user_"` creates `user_name` method for `name` parameter
|
22
|
+
- **Dynamic prefixes** - `prefix: true` uses source name (e.g., `context_name`)
|
23
|
+
- **Conflict resolution** - Avoid conflicts with Ruby methods or multiple same-named parameters
|
24
|
+
- **Call arguments** - Always use original parameter names, namespacing only affects method names
|
25
|
+
|
17
26
|
## Namespacing Fundamentals
|
18
27
|
|
19
28
|
> [!IMPORTANT]
|