cmdx 1.13.0 → 1.14.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/CHANGELOG.md +84 -76
- data/LICENSE.txt +3 -20
- data/README.md +8 -7
- data/lib/cmdx/attribute.rb +21 -5
- data/lib/cmdx/context.rb +16 -0
- data/lib/cmdx/executor.rb +9 -9
- data/lib/cmdx/result.rb +27 -7
- data/lib/cmdx/task.rb +19 -0
- data/lib/cmdx/version.rb +1 -1
- data/mkdocs.yml +62 -36
- metadata +3 -57
- data/.cursor/prompts/docs.md +0 -12
- data/.cursor/prompts/llms.md +0 -8
- data/.cursor/prompts/rspec.md +0 -24
- data/.cursor/prompts/yardoc.md +0 -15
- data/.cursor/rules/cursor-instructions.mdc +0 -68
- data/.irbrc +0 -18
- data/.rspec +0 -4
- data/.rubocop.yml +0 -95
- data/.ruby-version +0 -1
- data/.yard-lint.yml +0 -174
- data/.yardopts +0 -7
- data/docs/.DS_Store +0 -0
- data/docs/assets/favicon.ico +0 -0
- data/docs/assets/favicon.svg +0 -1
- data/docs/attributes/coercions.md +0 -155
- data/docs/attributes/defaults.md +0 -77
- data/docs/attributes/definitions.md +0 -283
- data/docs/attributes/naming.md +0 -68
- data/docs/attributes/transformations.md +0 -63
- data/docs/attributes/validations.md +0 -336
- data/docs/basics/chain.md +0 -108
- data/docs/basics/context.md +0 -121
- data/docs/basics/execution.md +0 -152
- data/docs/basics/setup.md +0 -107
- data/docs/callbacks.md +0 -157
- data/docs/configuration.md +0 -314
- data/docs/deprecation.md +0 -143
- data/docs/getting_started.md +0 -137
- data/docs/index.md +0 -134
- data/docs/internationalization.md +0 -126
- data/docs/interruptions/exceptions.md +0 -52
- data/docs/interruptions/faults.md +0 -169
- data/docs/interruptions/halt.md +0 -216
- data/docs/logging.md +0 -90
- data/docs/middlewares.md +0 -191
- data/docs/outcomes/result.md +0 -197
- data/docs/outcomes/states.md +0 -66
- data/docs/outcomes/statuses.md +0 -65
- data/docs/retries.md +0 -121
- data/docs/stylesheets/extra.css +0 -42
- data/docs/tips_and_tricks.md +0 -157
- data/docs/workflows.md +0 -226
- data/examples/active_record_database_transaction.md +0 -27
- data/examples/active_record_query_tagging.md +0 -46
- data/examples/flipper_feature_flags.md +0 -50
- data/examples/paper_trail_whatdunnit.md +0 -39
- data/examples/redis_idempotency.md +0 -71
- data/examples/sentry_error_tracking.md +0 -46
- data/examples/sidekiq_async_execution.md +0 -29
- data/examples/stoplight_circuit_breaker.md +0 -36
- data/src/cmdx-dark-logo.png +0 -0
- data/src/cmdx-favicon.svg +0 -1
- data/src/cmdx-light-logo.png +0 -0
- data/src/cmdx-logo.svg +0 -1
data/docs/basics/setup.md
DELETED
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
# Basics - Setup
|
|
2
|
-
|
|
3
|
-
Tasks are the heart of CMDx—self-contained units of business logic with built-in validation, error handling, and execution tracking.
|
|
4
|
-
|
|
5
|
-
## Structure
|
|
6
|
-
|
|
7
|
-
Tasks need only two things: inherit from `CMDx::Task` and define a `work` method:
|
|
8
|
-
|
|
9
|
-
```ruby
|
|
10
|
-
class ValidateDocument < CMDx::Task
|
|
11
|
-
def work
|
|
12
|
-
# Your logic here...
|
|
13
|
-
end
|
|
14
|
-
end
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
Without a `work` method, execution raises `CMDx::UndefinedMethodError`.
|
|
18
|
-
|
|
19
|
-
```ruby
|
|
20
|
-
class IncompleteTask < CMDx::Task
|
|
21
|
-
# No `work` method defined
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
IncompleteTask.execute #=> raises CMDx::UndefinedMethodError
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
## Rollback
|
|
28
|
-
|
|
29
|
-
Undo any operations linked to the given status, helping to restore a pristine state.
|
|
30
|
-
|
|
31
|
-
```ruby
|
|
32
|
-
class ChargeCard < CMDx::Task
|
|
33
|
-
def work
|
|
34
|
-
# Your logic here, ex: charge $100
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
# Called automatically if a later step in the workflow fails
|
|
38
|
-
def rollback
|
|
39
|
-
# Your undo logic, ex: void $100 charge
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
## Inheritance
|
|
45
|
-
|
|
46
|
-
Share configuration across tasks using inheritance:
|
|
47
|
-
|
|
48
|
-
```ruby
|
|
49
|
-
class ApplicationTask < CMDx::Task
|
|
50
|
-
register :middleware, SecurityMiddleware
|
|
51
|
-
|
|
52
|
-
before_execution :initialize_request_tracking
|
|
53
|
-
|
|
54
|
-
attribute :session_id
|
|
55
|
-
|
|
56
|
-
private
|
|
57
|
-
|
|
58
|
-
def initialize_request_tracking
|
|
59
|
-
context.tracking_id ||= SecureRandom.uuid
|
|
60
|
-
end
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
class SyncInventory < ApplicationTask
|
|
64
|
-
def work
|
|
65
|
-
# Your logic here...
|
|
66
|
-
end
|
|
67
|
-
end
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
## Lifecycle
|
|
71
|
-
|
|
72
|
-
Tasks follow a predictable execution pattern:
|
|
73
|
-
|
|
74
|
-
```mermaid
|
|
75
|
-
stateDiagram-v2
|
|
76
|
-
Initialized: Instantiation
|
|
77
|
-
Initialized --> Validating: execute
|
|
78
|
-
Validating --> Executing: Valid?
|
|
79
|
-
Validating --> Failed: Invalid
|
|
80
|
-
Executing --> Success: Work done
|
|
81
|
-
Executing --> Skipped: skip!
|
|
82
|
-
Executing --> Failed: fail! / Exception
|
|
83
|
-
Executed
|
|
84
|
-
|
|
85
|
-
state Executed {
|
|
86
|
-
Success
|
|
87
|
-
Skipped
|
|
88
|
-
Failed
|
|
89
|
-
Rollback
|
|
90
|
-
|
|
91
|
-
Skipped --> Rollback
|
|
92
|
-
Failed --> Rollback
|
|
93
|
-
}
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
!!! danger "Caution"
|
|
97
|
-
|
|
98
|
-
Tasks are single-use objects. Once executed, they're frozen and immutable.
|
|
99
|
-
|
|
100
|
-
| Stage | State | Status | Description |
|
|
101
|
-
|-------|-------|--------|-------------|
|
|
102
|
-
| **Instantiation** | `initialized` | `success` | Task created with context |
|
|
103
|
-
| **Validation** | `executing` | `success`/`failed` | Attributes validated |
|
|
104
|
-
| **Execution** | `executing` | `success`/`failed`/`skipped` | `work` method runs |
|
|
105
|
-
| **Completion** | `executed` | `success`/`failed`/`skipped` | Result finalized |
|
|
106
|
-
| **Freezing** | `executed` | `success`/`failed`/`skipped` | Task becomes immutable |
|
|
107
|
-
| **Rollback** | `executed` | `failed`/`skipped` | Work undone |
|
data/docs/callbacks.md
DELETED
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
# Callbacks
|
|
2
|
-
|
|
3
|
-
Run custom logic at specific points during task execution. Callbacks have full access to task context and results, making them perfect for logging, notifications, cleanup, and more.
|
|
4
|
-
|
|
5
|
-
See [Global Configuration](getting_started.md#callbacks) for framework-wide callback setup.
|
|
6
|
-
|
|
7
|
-
!!! warning "Important"
|
|
8
|
-
|
|
9
|
-
Callbacks execute in declaration order (FIFO). Multiple callbacks of the same type run sequentially.
|
|
10
|
-
|
|
11
|
-
## Available Callbacks
|
|
12
|
-
|
|
13
|
-
Callbacks execute in a predictable lifecycle order:
|
|
14
|
-
|
|
15
|
-
```ruby
|
|
16
|
-
1. before_validation # Pre-validation setup
|
|
17
|
-
2. before_execution # Prepare for execution
|
|
18
|
-
|
|
19
|
-
# --- Task#work executes ---
|
|
20
|
-
|
|
21
|
-
3. on_[complete|interrupted] # State-based (execution lifecycle)
|
|
22
|
-
4. on_executed # Always runs after work completes
|
|
23
|
-
5. on_[success|skipped|failed] # Status-based (business outcome)
|
|
24
|
-
6. on_[good|bad] # Outcome-based (success/skip vs fail)
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
## Declarations
|
|
28
|
-
|
|
29
|
-
### Symbol References
|
|
30
|
-
|
|
31
|
-
Reference instance methods by symbol for simple callback logic:
|
|
32
|
-
|
|
33
|
-
```ruby
|
|
34
|
-
class ProcessBooking < CMDx::Task
|
|
35
|
-
before_execution :find_reservation
|
|
36
|
-
|
|
37
|
-
# Batch declarations (works for any type)
|
|
38
|
-
on_complete :notify_guest, :update_availability
|
|
39
|
-
|
|
40
|
-
def work
|
|
41
|
-
# Your logic here...
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
private
|
|
45
|
-
|
|
46
|
-
def find_reservation
|
|
47
|
-
@reservation ||= Reservation.find(context.reservation_id)
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
def notify_guest
|
|
51
|
-
GuestNotifier.call(context.guest, result)
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
def update_availability
|
|
55
|
-
AvailabilityService.update(context.room_ids, result)
|
|
56
|
-
end
|
|
57
|
-
end
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
### Proc or Lambda
|
|
61
|
-
|
|
62
|
-
Use anonymous functions for inline callback logic:
|
|
63
|
-
|
|
64
|
-
```ruby
|
|
65
|
-
class ProcessBooking < CMDx::Task
|
|
66
|
-
# Proc
|
|
67
|
-
on_interrupted proc { ReservationSystem.pause! }
|
|
68
|
-
|
|
69
|
-
# Lambda
|
|
70
|
-
on_complete -> { ReservationSystem.resume! }
|
|
71
|
-
end
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
### Class or Module
|
|
75
|
-
|
|
76
|
-
Implement reusable callback logic in dedicated modules and classes:
|
|
77
|
-
|
|
78
|
-
```ruby
|
|
79
|
-
class BookingConfirmationCallback
|
|
80
|
-
def call(task)
|
|
81
|
-
if task.result.success?
|
|
82
|
-
MessagingApi.send_confirmation(task.context.guest)
|
|
83
|
-
else
|
|
84
|
-
MessagingApi.send_issue_alert(task.context.manager)
|
|
85
|
-
end
|
|
86
|
-
end
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
class ProcessBooking < CMDx::Task
|
|
90
|
-
# Class or Module
|
|
91
|
-
on_success BookingConfirmationCallback
|
|
92
|
-
|
|
93
|
-
# Instance
|
|
94
|
-
on_interrupted BookingConfirmationCallback.new
|
|
95
|
-
end
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
### Conditional Execution
|
|
99
|
-
|
|
100
|
-
Control callback execution with conditional logic:
|
|
101
|
-
|
|
102
|
-
```ruby
|
|
103
|
-
class MessagingPermissionCheck
|
|
104
|
-
def call(task)
|
|
105
|
-
task.context.guest.can?(:receive_messages)
|
|
106
|
-
end
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
class ProcessBooking < CMDx::Task
|
|
110
|
-
# If and/or Unless
|
|
111
|
-
before_execution :notify_guest, if: :messaging_enabled?, unless: :messaging_blocked?
|
|
112
|
-
|
|
113
|
-
# Proc
|
|
114
|
-
on_failure :increment_failure, if: -> { Rails.env.production? && self.class.name.include?("Legacy") }
|
|
115
|
-
|
|
116
|
-
# Lambda
|
|
117
|
-
on_success :ping_housekeeping, if: proc { context.rooms_need_cleaning? }
|
|
118
|
-
|
|
119
|
-
# Class or Module
|
|
120
|
-
on_complete :send_confirmation, unless: MessagingPermissionCheck
|
|
121
|
-
|
|
122
|
-
# Instance
|
|
123
|
-
on_complete :send_confirmation, if: MessagingPermissionCheck.new
|
|
124
|
-
|
|
125
|
-
def work
|
|
126
|
-
# Your logic here...
|
|
127
|
-
end
|
|
128
|
-
|
|
129
|
-
private
|
|
130
|
-
|
|
131
|
-
def messaging_enabled?
|
|
132
|
-
context.guest.messaging_preference == true
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
def messaging_blocked?
|
|
136
|
-
context.guest.communication_status == :blocked
|
|
137
|
-
end
|
|
138
|
-
end
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
## Callback Removal
|
|
142
|
-
|
|
143
|
-
Remove unwanted callbacks dynamically:
|
|
144
|
-
|
|
145
|
-
!!! warning "Important"
|
|
146
|
-
|
|
147
|
-
Each `deregister` call removes one callback. Use multiple calls for batch removals.
|
|
148
|
-
|
|
149
|
-
```ruby
|
|
150
|
-
class ProcessBooking < CMDx::Task
|
|
151
|
-
# Symbol
|
|
152
|
-
deregister :callback, :before_execution, :notify_guest
|
|
153
|
-
|
|
154
|
-
# Class or Module (no instances)
|
|
155
|
-
deregister :callback, :on_complete, BookingConfirmationCallback
|
|
156
|
-
end
|
|
157
|
-
```
|
data/docs/configuration.md
DELETED
|
@@ -1,314 +0,0 @@
|
|
|
1
|
-
# Configuration
|
|
2
|
-
|
|
3
|
-
Configure CMDx to customize framework behavior, register components, and control execution flow through global defaults with task-level overrides.
|
|
4
|
-
|
|
5
|
-
## Configuration Hierarchy
|
|
6
|
-
|
|
7
|
-
CMDx uses a straightforward two-tier configuration system:
|
|
8
|
-
|
|
9
|
-
1. **Global Configuration** — Framework-wide defaults
|
|
10
|
-
2. **Task Settings** — Class-level overrides using `settings`
|
|
11
|
-
|
|
12
|
-
!!! warning "Important"
|
|
13
|
-
|
|
14
|
-
Task settings take precedence over global config. Settings are inherited from parent classes and can be overridden in subclasses.
|
|
15
|
-
|
|
16
|
-
## Global Configuration
|
|
17
|
-
|
|
18
|
-
Configure framework-wide defaults that apply to all tasks. These settings come with sensible defaults out of the box.
|
|
19
|
-
|
|
20
|
-
### Breakpoints
|
|
21
|
-
|
|
22
|
-
Control when `execute!` raises a `CMDx::Fault` based on task status.
|
|
23
|
-
|
|
24
|
-
```ruby
|
|
25
|
-
CMDx.configure do |config|
|
|
26
|
-
config.task_breakpoints = "failed" # String or Array[String]
|
|
27
|
-
end
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
For workflows, configure which statuses halt the execution pipeline:
|
|
31
|
-
|
|
32
|
-
```ruby
|
|
33
|
-
CMDx.configure do |config|
|
|
34
|
-
config.workflow_breakpoints = ["skipped", "failed"]
|
|
35
|
-
end
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
### Rollback
|
|
39
|
-
|
|
40
|
-
Control when a `rollback` of task execution is called.
|
|
41
|
-
|
|
42
|
-
```ruby
|
|
43
|
-
CMDx.configure do |config|
|
|
44
|
-
config.rollback_on = ["failed"] # String or Array[String]
|
|
45
|
-
end
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
### Backtraces
|
|
49
|
-
|
|
50
|
-
Enable detailed backtraces for non-fault exceptions to improve debugging. Optionally clean up stack traces to remove framework noise.
|
|
51
|
-
|
|
52
|
-
!!! note
|
|
53
|
-
|
|
54
|
-
In Rails environments, `backtrace_cleaner` defaults to `Rails.backtrace_cleaner.clean`.
|
|
55
|
-
|
|
56
|
-
```ruby
|
|
57
|
-
CMDx.configure do |config|
|
|
58
|
-
# Truthy
|
|
59
|
-
config.backtrace = true
|
|
60
|
-
|
|
61
|
-
# Via callable (must respond to `call(backtrace)`)
|
|
62
|
-
config.backtrace_cleaner = AdvanceCleaner.new
|
|
63
|
-
|
|
64
|
-
# Via proc or lambda
|
|
65
|
-
config.backtrace_cleaner = ->(backtrace) { backtrace[0..5] }
|
|
66
|
-
end
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
### Exception Handlers
|
|
70
|
-
|
|
71
|
-
Register handlers that run when non-fault exceptions occur.
|
|
72
|
-
|
|
73
|
-
!!! tip
|
|
74
|
-
|
|
75
|
-
Use exception handlers to send errors to your APM of choice.
|
|
76
|
-
|
|
77
|
-
```ruby
|
|
78
|
-
CMDx.configure do |config|
|
|
79
|
-
# Via callable (must respond to `call(task, exception)`)
|
|
80
|
-
config.exception_handler = NewRelicReporter
|
|
81
|
-
|
|
82
|
-
# Via proc or lambda
|
|
83
|
-
config.exception_handler = proc do |task, exception|
|
|
84
|
-
APMService.report(exception, extra_data: { task: task.name, id: task.id })
|
|
85
|
-
end
|
|
86
|
-
end
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
### Logging
|
|
90
|
-
|
|
91
|
-
```ruby
|
|
92
|
-
CMDx.configure do |config|
|
|
93
|
-
config.logger = CustomLogger.new($stdout)
|
|
94
|
-
end
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
### Middlewares
|
|
98
|
-
|
|
99
|
-
See the [Middlewares](middlewares.md#declarations) docs for task level configurations.
|
|
100
|
-
|
|
101
|
-
```ruby
|
|
102
|
-
CMDx.configure do |config|
|
|
103
|
-
# Via callable (must respond to `call(task, options)`)
|
|
104
|
-
config.middlewares.register CMDx::Middlewares::Timeout
|
|
105
|
-
|
|
106
|
-
# Via proc or lambda
|
|
107
|
-
config.middlewares.register proc { |task, options|
|
|
108
|
-
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
109
|
-
result = yield
|
|
110
|
-
end_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
111
|
-
Rails.logger.debug { "task completed in #{((end_time - start_time) * 1000).round(2)}ms" }
|
|
112
|
-
result
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
# With options
|
|
116
|
-
config.middlewares.register AuditTrailMiddleware, service_name: "document_processor"
|
|
117
|
-
|
|
118
|
-
# Remove middleware
|
|
119
|
-
config.middlewares.deregister CMDx::Middlewares::Timeout
|
|
120
|
-
end
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
!!! note
|
|
124
|
-
|
|
125
|
-
Middlewares are executed in registration order. Each middleware wraps the next, creating an execution chain around task logic.
|
|
126
|
-
|
|
127
|
-
### Callbacks
|
|
128
|
-
|
|
129
|
-
See the [Callbacks](callbacks.md#declarations) docs for task level configurations.
|
|
130
|
-
|
|
131
|
-
```ruby
|
|
132
|
-
CMDx.configure do |config|
|
|
133
|
-
# Via method
|
|
134
|
-
config.callbacks.register :before_execution, :initialize_user_session
|
|
135
|
-
|
|
136
|
-
# Via callable (must respond to `call(task)`)
|
|
137
|
-
config.callbacks.register :on_success, LogUserActivity
|
|
138
|
-
|
|
139
|
-
# Via proc or lambda
|
|
140
|
-
config.callbacks.register :on_complete, proc { |task|
|
|
141
|
-
execution_time = task.metadata[:runtime]
|
|
142
|
-
Metrics.timer("task.execution_time", execution_time, tags: ["task:#{task.class.name.underscore}"])
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
# With options
|
|
146
|
-
config.callbacks.register :on_failure, :send_alert_notification, if: :critical_task?
|
|
147
|
-
|
|
148
|
-
# Remove callback
|
|
149
|
-
config.callbacks.deregister :on_success, LogUserActivity
|
|
150
|
-
end
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
### Coercions
|
|
154
|
-
|
|
155
|
-
See the [Attributes - Coercions](attributes/coercions.md#declarations) docs for task level configurations.
|
|
156
|
-
|
|
157
|
-
```ruby
|
|
158
|
-
CMDx.configure do |config|
|
|
159
|
-
# Via callable (must respond to `call(value, options)`)
|
|
160
|
-
config.coercions.register :currency, CurrencyCoercion
|
|
161
|
-
|
|
162
|
-
# Via method (must match signature `def coordinates_coercion(value, options)`)
|
|
163
|
-
config.coercions.register :coordinates, :coordinates_coercion
|
|
164
|
-
|
|
165
|
-
# Via proc or lambda
|
|
166
|
-
config.coercions.register :tag_list, proc { |value, options|
|
|
167
|
-
delimiter = options[:delimiter] || ','
|
|
168
|
-
max_tags = options[:max_tags] || 50
|
|
169
|
-
|
|
170
|
-
tags = value.to_s.split(delimiter).map(&:strip).reject(&:empty?)
|
|
171
|
-
tags.first(max_tags)
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
# Remove coercion
|
|
175
|
-
config.coercions.deregister :currency
|
|
176
|
-
end
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
### Validators
|
|
180
|
-
|
|
181
|
-
See the [Attributes - Validations](attributes/validations.md#declarations) docs for task level configurations.
|
|
182
|
-
|
|
183
|
-
```ruby
|
|
184
|
-
CMDx.configure do |config|
|
|
185
|
-
# Via callable (must respond to `call(value, options)`)
|
|
186
|
-
config.validators.register :username, UsernameValidator
|
|
187
|
-
|
|
188
|
-
# Via method (must match signature `def url_validator(value, options)`)
|
|
189
|
-
config.validators.register :url, :url_validator
|
|
190
|
-
|
|
191
|
-
# Via proc or lambda
|
|
192
|
-
config.validators.register :access_token, proc { |value, options|
|
|
193
|
-
expected_prefix = options[:prefix] || "tok_"
|
|
194
|
-
minimum_length = options[:min_length] || 40
|
|
195
|
-
|
|
196
|
-
value.start_with?(expected_prefix) && value.length >= minimum_length
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
# Remove validator
|
|
200
|
-
config.validators.deregister :username
|
|
201
|
-
end
|
|
202
|
-
```
|
|
203
|
-
|
|
204
|
-
## Task Configuration
|
|
205
|
-
|
|
206
|
-
### Settings
|
|
207
|
-
|
|
208
|
-
Override global configuration for specific tasks using `settings`:
|
|
209
|
-
|
|
210
|
-
```ruby
|
|
211
|
-
class GenerateInvoice < CMDx::Task
|
|
212
|
-
settings(
|
|
213
|
-
# Global configuration overrides
|
|
214
|
-
task_breakpoints: ["failed"], # Breakpoint override
|
|
215
|
-
workflow_breakpoints: [], # Breakpoint override
|
|
216
|
-
backtrace: true, # Toggle backtrace
|
|
217
|
-
backtrace_cleaner: ->(bt) { bt[0..5] }, # Backtrace cleaner
|
|
218
|
-
logger: CustomLogger.new($stdout), # Custom logger
|
|
219
|
-
|
|
220
|
-
# Task configuration settings
|
|
221
|
-
breakpoints: ["failed"], # Contextual pointer for :task_breakpoints and :workflow_breakpoints
|
|
222
|
-
log_level: :info, # Log level override
|
|
223
|
-
log_formatter: CMDx::LogFormatters::Json.new # Log formatter override
|
|
224
|
-
tags: ["billing", "financial"], # Logging tags
|
|
225
|
-
deprecated: true, # Task deprecations
|
|
226
|
-
retries: 3, # Non-fault exception retries
|
|
227
|
-
retry_on: [External::ApiError], # List of exceptions to retry on
|
|
228
|
-
retry_jitter: 1, # Space between retry iteration, eg: current retry num + 1
|
|
229
|
-
rollback_on: ["failed", "skipped"], # Rollback on override
|
|
230
|
-
)
|
|
231
|
-
|
|
232
|
-
def work
|
|
233
|
-
# Your logic here...
|
|
234
|
-
end
|
|
235
|
-
end
|
|
236
|
-
```
|
|
237
|
-
|
|
238
|
-
!!! warning "Important"
|
|
239
|
-
|
|
240
|
-
Retries reuse the same context. By default, all `StandardError` exceptions (including faults) are retried unless you specify `retry_on` option for specific matches.
|
|
241
|
-
|
|
242
|
-
### Registrations
|
|
243
|
-
|
|
244
|
-
Register or deregister middlewares, callbacks, coercions, and validators for specific tasks:
|
|
245
|
-
|
|
246
|
-
```ruby
|
|
247
|
-
class SendCampaignEmail < CMDx::Task
|
|
248
|
-
# Middlewares
|
|
249
|
-
register :middleware, CMDx::Middlewares::Timeout
|
|
250
|
-
deregister :middleware, AuditTrailMiddleware
|
|
251
|
-
|
|
252
|
-
# Callbacks
|
|
253
|
-
register :callback, :on_complete, proc { |task|
|
|
254
|
-
runtime = task.metadata[:runtime]
|
|
255
|
-
Analytics.track("email_campaign.sent", runtime, tags: ["task:#{task.class.name}"])
|
|
256
|
-
}
|
|
257
|
-
deregister :callback, :before_execution, :initialize_user_session
|
|
258
|
-
|
|
259
|
-
# Coercions
|
|
260
|
-
register :coercion, :currency, CurrencyCoercion
|
|
261
|
-
deregister :coercion, :coordinates
|
|
262
|
-
|
|
263
|
-
# Validators
|
|
264
|
-
register :validator, :username, :username_validator
|
|
265
|
-
deregister :validator, :url
|
|
266
|
-
|
|
267
|
-
def work
|
|
268
|
-
# Your logic here...
|
|
269
|
-
end
|
|
270
|
-
end
|
|
271
|
-
```
|
|
272
|
-
|
|
273
|
-
## Configuration Management
|
|
274
|
-
|
|
275
|
-
### Access
|
|
276
|
-
|
|
277
|
-
```ruby
|
|
278
|
-
# Global configuration access
|
|
279
|
-
CMDx.configuration.logger #=> <Logger instance>
|
|
280
|
-
CMDx.configuration.task_breakpoints #=> ["failed"]
|
|
281
|
-
CMDx.configuration.middlewares.registry #=> [<Middleware>, ...]
|
|
282
|
-
|
|
283
|
-
# Task configuration access
|
|
284
|
-
class ProcessUpload < CMDx::Task
|
|
285
|
-
settings(tags: ["files", "storage"])
|
|
286
|
-
|
|
287
|
-
def work
|
|
288
|
-
self.class.settings[:logger] #=> Global configuration value
|
|
289
|
-
self.class.settings[:tags] #=> Task configuration value => ["files", "storage"]
|
|
290
|
-
end
|
|
291
|
-
end
|
|
292
|
-
```
|
|
293
|
-
|
|
294
|
-
### Resetting
|
|
295
|
-
|
|
296
|
-
!!! warning
|
|
297
|
-
|
|
298
|
-
Resetting affects your entire application. Use this primarily in test environments.
|
|
299
|
-
|
|
300
|
-
```ruby
|
|
301
|
-
# Reset to framework defaults
|
|
302
|
-
CMDx.reset_configuration!
|
|
303
|
-
|
|
304
|
-
# Verify reset
|
|
305
|
-
CMDx.configuration.task_breakpoints #=> ["failed"] (default)
|
|
306
|
-
CMDx.configuration.middlewares.registry #=> Empty registry
|
|
307
|
-
|
|
308
|
-
# Commonly used in test setup (RSpec example)
|
|
309
|
-
RSpec.configure do |config|
|
|
310
|
-
config.before(:each) do
|
|
311
|
-
CMDx.reset_configuration!
|
|
312
|
-
end
|
|
313
|
-
end
|
|
314
|
-
```
|
data/docs/deprecation.md
DELETED
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
# Task Deprecation
|
|
2
|
-
|
|
3
|
-
Manage legacy tasks gracefully with built-in deprecation support. Choose how to handle deprecated tasks—log warnings for awareness, issue Ruby warnings for development, or prevent execution entirely.
|
|
4
|
-
|
|
5
|
-
## Modes
|
|
6
|
-
|
|
7
|
-
### Raise
|
|
8
|
-
|
|
9
|
-
Prevent task execution completely. Perfect for tasks that must no longer run.
|
|
10
|
-
|
|
11
|
-
!!! warning
|
|
12
|
-
|
|
13
|
-
Use `:raise` mode carefully—it will break existing workflows immediately.
|
|
14
|
-
|
|
15
|
-
```ruby
|
|
16
|
-
class ProcessObsoleteAPI < CMDx::Task
|
|
17
|
-
settings(deprecated: :raise)
|
|
18
|
-
|
|
19
|
-
def work
|
|
20
|
-
# Will never execute...
|
|
21
|
-
end
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
result = ProcessObsoleteAPI.execute
|
|
25
|
-
#=> raises CMDx::DeprecationError: "ProcessObsoleteAPI usage prohibited"
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
### Log
|
|
29
|
-
|
|
30
|
-
Allow execution while tracking deprecation in logs. Ideal for gradual migrations.
|
|
31
|
-
|
|
32
|
-
```ruby
|
|
33
|
-
class ProcessLegacyFormat < CMDx::Task
|
|
34
|
-
settings(deprecated: :log)
|
|
35
|
-
settings(deprecated: true)
|
|
36
|
-
|
|
37
|
-
def work
|
|
38
|
-
# Executes but logs deprecation warning...
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
result = ProcessLegacyFormat.execute
|
|
43
|
-
result.successful? #=> true
|
|
44
|
-
|
|
45
|
-
# Deprecation warning appears in logs:
|
|
46
|
-
# WARN -- : DEPRECATED: ProcessLegacyFormat - migrate to replacement or discontinue use
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
### Warn
|
|
50
|
-
|
|
51
|
-
Issue Ruby warnings visible during development and testing. Keeps production logs clean while alerting developers.
|
|
52
|
-
|
|
53
|
-
```ruby
|
|
54
|
-
class ProcessOldData < CMDx::Task
|
|
55
|
-
settings(deprecated: :warn)
|
|
56
|
-
|
|
57
|
-
def work
|
|
58
|
-
# Executes but emits Ruby warning...
|
|
59
|
-
end
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
result = ProcessOldData.execute
|
|
63
|
-
result.successful? #=> true
|
|
64
|
-
|
|
65
|
-
# Ruby warning appears in stderr:
|
|
66
|
-
# [ProcessOldData] DEPRECATED: migrate to a replacement or discontinue use
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
## Declarations
|
|
70
|
-
|
|
71
|
-
### Symbol or String
|
|
72
|
-
|
|
73
|
-
```ruby
|
|
74
|
-
class OutdatedConnector < CMDx::Task
|
|
75
|
-
# Symbol
|
|
76
|
-
settings(deprecated: :raise)
|
|
77
|
-
|
|
78
|
-
# String
|
|
79
|
-
settings(deprecated: "warn")
|
|
80
|
-
end
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
### Boolean or Nil
|
|
84
|
-
|
|
85
|
-
```ruby
|
|
86
|
-
class OutdatedConnector < CMDx::Task
|
|
87
|
-
# Deprecates with default :log mode
|
|
88
|
-
settings(deprecated: true)
|
|
89
|
-
|
|
90
|
-
# Skips deprecation
|
|
91
|
-
settings(deprecated: false)
|
|
92
|
-
settings(deprecated: nil)
|
|
93
|
-
end
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
### Method
|
|
97
|
-
|
|
98
|
-
```ruby
|
|
99
|
-
class OutdatedConnector < CMDx::Task
|
|
100
|
-
# Symbol
|
|
101
|
-
settings(deprecated: :deprecated?)
|
|
102
|
-
|
|
103
|
-
def work
|
|
104
|
-
# Your logic here...
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
private
|
|
108
|
-
|
|
109
|
-
def deprecated?
|
|
110
|
-
Time.now.year > 2024 ? :raise : false
|
|
111
|
-
end
|
|
112
|
-
end
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
### Proc or Lambda
|
|
116
|
-
|
|
117
|
-
```ruby
|
|
118
|
-
class OutdatedConnector < CMDx::Task
|
|
119
|
-
# Proc
|
|
120
|
-
settings(deprecated: proc { Rails.env.development? ? :raise : :log })
|
|
121
|
-
|
|
122
|
-
# Lambda
|
|
123
|
-
settings(deprecated: -> { Current.tenant.legacy_mode? ? :warn : :raise })
|
|
124
|
-
end
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
### Class or Module
|
|
128
|
-
|
|
129
|
-
```ruby
|
|
130
|
-
class OutdatedTaskDeprecator
|
|
131
|
-
def call(task)
|
|
132
|
-
task.class.name.include?("Outdated")
|
|
133
|
-
end
|
|
134
|
-
end
|
|
135
|
-
|
|
136
|
-
class OutdatedConnector < CMDx::Task
|
|
137
|
-
# Class or Module
|
|
138
|
-
settings(deprecated: OutdatedTaskDeprecator)
|
|
139
|
-
|
|
140
|
-
# Instance
|
|
141
|
-
settings(deprecated: OutdatedTaskDeprecator.new)
|
|
142
|
-
end
|
|
143
|
-
```
|