cmdx 1.12.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 +88 -71
- data/LICENSE.txt +3 -20
- data/README.md +8 -7
- data/lib/cmdx/attribute.rb +21 -5
- data/lib/cmdx/chain.rb +18 -4
- data/lib/cmdx/context.rb +18 -0
- data/lib/cmdx/executor.rb +35 -30
- data/lib/cmdx/result.rb +45 -2
- data/lib/cmdx/task.rb +22 -1
- data/lib/cmdx/version.rb +1 -1
- data/mkdocs.yml +67 -37
- 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 -96
- data/docs/basics/setup.md +0 -84
- data/docs/callbacks.md +0 -157
- data/docs/configuration.md +0 -314
- data/docs/deprecation.md +0 -145
- data/docs/getting_started.md +0 -126
- 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 -94
- data/docs/middlewares.md +0 -191
- data/docs/outcomes/result.md +0 -194
- 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/outcomes/result.md
DELETED
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
# Outcomes - Result
|
|
2
|
-
|
|
3
|
-
Results are your window into task execution. They expose everything: outcome, state, timing, context, and metadata.
|
|
4
|
-
|
|
5
|
-
## Result Attributes
|
|
6
|
-
|
|
7
|
-
Access essential execution information:
|
|
8
|
-
|
|
9
|
-
!!! warning "Important"
|
|
10
|
-
|
|
11
|
-
Results are immutable after execution completes.
|
|
12
|
-
|
|
13
|
-
```ruby
|
|
14
|
-
result = BuildApplication.execute(version: "1.2.3")
|
|
15
|
-
|
|
16
|
-
# Object data
|
|
17
|
-
result.task #=> <BuildApplication>
|
|
18
|
-
result.context #=> <CMDx::Context>
|
|
19
|
-
result.chain #=> <CMDx::Chain>
|
|
20
|
-
|
|
21
|
-
# Execution data
|
|
22
|
-
result.state #=> "interrupted"
|
|
23
|
-
result.status #=> "failed"
|
|
24
|
-
|
|
25
|
-
# Fault data
|
|
26
|
-
result.reason #=> "Build tool not found"
|
|
27
|
-
result.cause #=> <CMDx::FailFault>
|
|
28
|
-
result.metadata #=> { error_code: "BUILD_TOOL.NOT_FOUND" }
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
## Lifecycle Information
|
|
32
|
-
|
|
33
|
-
Check execution state and status with predicate methods:
|
|
34
|
-
|
|
35
|
-
```ruby
|
|
36
|
-
result = BuildApplication.execute(version: "1.2.3")
|
|
37
|
-
|
|
38
|
-
# State predicates (execution lifecycle)
|
|
39
|
-
result.complete? #=> true (successful completion)
|
|
40
|
-
result.interrupted? #=> false (no interruption)
|
|
41
|
-
result.executed? #=> true (execution finished)
|
|
42
|
-
|
|
43
|
-
# Status predicates (execution outcome)
|
|
44
|
-
result.success? #=> true (successful execution)
|
|
45
|
-
result.failed? #=> false (no failure)
|
|
46
|
-
result.skipped? #=> false (not skipped)
|
|
47
|
-
|
|
48
|
-
# Outcome categorization
|
|
49
|
-
result.good? #=> true (success or skipped)
|
|
50
|
-
result.bad? #=> false (skipped or failed)
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
## Outcome Analysis
|
|
54
|
-
|
|
55
|
-
Get a unified outcome string combining state and status:
|
|
56
|
-
|
|
57
|
-
```ruby
|
|
58
|
-
result = BuildApplication.execute(version: "1.2.3")
|
|
59
|
-
|
|
60
|
-
result.outcome #=> "success" (state and status)
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
## Chain Analysis
|
|
64
|
-
|
|
65
|
-
Trace fault origins and propagation:
|
|
66
|
-
|
|
67
|
-
```ruby
|
|
68
|
-
result = DeploymentWorkflow.execute(app_name: "webapp")
|
|
69
|
-
|
|
70
|
-
if result.failed?
|
|
71
|
-
# Find the original cause of failure
|
|
72
|
-
if original_failure = result.caused_failure
|
|
73
|
-
puts "Root cause: #{original_failure.task.class.name}"
|
|
74
|
-
puts "Reason: #{original_failure.reason}"
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
# Find what threw the failure to this result
|
|
78
|
-
if throwing_task = result.threw_failure
|
|
79
|
-
puts "Failure source: #{throwing_task.task.class.name}"
|
|
80
|
-
puts "Reason: #{throwing_task.reason}"
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
# Failure classification
|
|
84
|
-
result.caused_failure? #=> true if this result was the original cause
|
|
85
|
-
result.threw_failure? #=> true if this result threw a failure
|
|
86
|
-
result.thrown_failure? #=> true if this result received a thrown failure
|
|
87
|
-
end
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
## Index and Position
|
|
91
|
-
|
|
92
|
-
Results track their position within execution chains:
|
|
93
|
-
|
|
94
|
-
```ruby
|
|
95
|
-
result = BuildApplication.execute(version: "1.2.3")
|
|
96
|
-
|
|
97
|
-
# Position in execution sequence
|
|
98
|
-
result.index #=> 0 (first task in chain)
|
|
99
|
-
|
|
100
|
-
# Access via chain
|
|
101
|
-
result.chain.results[result.index] == result #=> true
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
## Block Yield
|
|
105
|
-
|
|
106
|
-
Execute code with direct result access:
|
|
107
|
-
|
|
108
|
-
```ruby
|
|
109
|
-
BuildApplication.execute(version: "1.2.3") do |result|
|
|
110
|
-
if result.success?
|
|
111
|
-
notify_deployment_ready(result)
|
|
112
|
-
elsif result.failed?
|
|
113
|
-
handle_build_failure(result)
|
|
114
|
-
else
|
|
115
|
-
log_skip_reason(result)
|
|
116
|
-
end
|
|
117
|
-
end
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
## Handlers
|
|
121
|
-
|
|
122
|
-
Handle outcomes with functional-style methods. Handlers return the result for chaining:
|
|
123
|
-
|
|
124
|
-
```ruby
|
|
125
|
-
result = BuildApplication.execute(version: "1.2.3")
|
|
126
|
-
|
|
127
|
-
# Status-based handlers
|
|
128
|
-
result
|
|
129
|
-
.on(:success) { |result| notify_deployment_ready(result) }
|
|
130
|
-
.on(:failed) { |result| handle_build_failure(result) }
|
|
131
|
-
.on(:skipped) { |result| log_skip_reason(result) }
|
|
132
|
-
|
|
133
|
-
# State-based handlers
|
|
134
|
-
result
|
|
135
|
-
.on(:complete) { |result| update_build_status(result) }
|
|
136
|
-
.on(:interrupted) { |result| cleanup_partial_artifacts(result) }
|
|
137
|
-
.on(:executed) { |result| alert_operations_team(result) } #=> .on(:complete, :interrupted)
|
|
138
|
-
|
|
139
|
-
# Outcome-based handlers
|
|
140
|
-
result
|
|
141
|
-
.on(:good) { |result| increment_success_counter(result) } #=> .on(:success, :skipped)
|
|
142
|
-
.on(:bad) { |result| alert_operations_team(result) } #=> .on(:failed, :skipped)
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
## Pattern Matching
|
|
146
|
-
|
|
147
|
-
Use Ruby 3.0+ pattern matching for elegant outcome handling:
|
|
148
|
-
|
|
149
|
-
!!! warning "Important"
|
|
150
|
-
|
|
151
|
-
Pattern matching works with both array and hash deconstruction.
|
|
152
|
-
|
|
153
|
-
### Array Pattern
|
|
154
|
-
|
|
155
|
-
```ruby
|
|
156
|
-
result = BuildApplication.execute(version: "1.2.3")
|
|
157
|
-
|
|
158
|
-
case result
|
|
159
|
-
in ["complete", "success"]
|
|
160
|
-
redirect_to build_success_page
|
|
161
|
-
in ["interrupted", "failed"]
|
|
162
|
-
retry_build_with_backoff(result)
|
|
163
|
-
in ["interrupted", "skipped"]
|
|
164
|
-
log_skip_and_continue
|
|
165
|
-
end
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
### Hash Pattern
|
|
169
|
-
|
|
170
|
-
```ruby
|
|
171
|
-
result = BuildApplication.execute(version: "1.2.3")
|
|
172
|
-
|
|
173
|
-
case result
|
|
174
|
-
in { state: "complete", status: "success" }
|
|
175
|
-
celebrate_build_success
|
|
176
|
-
in { status: "failed", metadata: { retryable: true } }
|
|
177
|
-
schedule_build_retry(result)
|
|
178
|
-
in { bad: true, metadata: { reason: String => reason } }
|
|
179
|
-
escalate_build_error("Build failed: #{reason}")
|
|
180
|
-
end
|
|
181
|
-
```
|
|
182
|
-
|
|
183
|
-
### Pattern Guards
|
|
184
|
-
|
|
185
|
-
```ruby
|
|
186
|
-
case result
|
|
187
|
-
in { status: "failed", metadata: { attempts: n } } if n < 3
|
|
188
|
-
retry_build_with_delay(result, n * 2)
|
|
189
|
-
in { status: "failed", metadata: { attempts: n } } if n >= 3
|
|
190
|
-
mark_build_permanently_failed(result)
|
|
191
|
-
in { runtime: time } if time > performance_threshold
|
|
192
|
-
investigate_build_performance(result)
|
|
193
|
-
end
|
|
194
|
-
```
|
data/docs/outcomes/states.md
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
# Outcomes - States
|
|
2
|
-
|
|
3
|
-
States track where a task is in its execution lifecycle—from creation through completion or interruption.
|
|
4
|
-
|
|
5
|
-
## Definitions
|
|
6
|
-
|
|
7
|
-
| State | Description |
|
|
8
|
-
| ----- | ----------- |
|
|
9
|
-
| `initialized` | Task created but execution not yet started. Default state for new tasks. |
|
|
10
|
-
| `executing` | Task is actively running its business logic. Transient state during execution. |
|
|
11
|
-
| `complete` | Task finished execution successfully without any interruption or halt. |
|
|
12
|
-
| `interrupted` | Task execution was stopped due to a fault, exception, or explicit halt. |
|
|
13
|
-
|
|
14
|
-
State-Status combinations:
|
|
15
|
-
|
|
16
|
-
| State | Status | Meaning |
|
|
17
|
-
| ----- | ------ | ------- |
|
|
18
|
-
| `initialized` | `success` | Task created, not yet executed |
|
|
19
|
-
| `executing` | `success` | Task currently running |
|
|
20
|
-
| `complete` | `success` | Task finished successfully |
|
|
21
|
-
| `complete` | `skipped` | Task finished by skipping execution |
|
|
22
|
-
| `interrupted` | `failed` | Task stopped due to failure |
|
|
23
|
-
| `interrupted` | `skipped` | Task stopped by skip condition |
|
|
24
|
-
|
|
25
|
-
## Transitions
|
|
26
|
-
|
|
27
|
-
!!! danger "Caution"
|
|
28
|
-
|
|
29
|
-
States are managed automatically—never modify them manually.
|
|
30
|
-
|
|
31
|
-
```ruby
|
|
32
|
-
# Valid state transition flow
|
|
33
|
-
initialized → executing → complete (successful execution)
|
|
34
|
-
initialized → executing → interrupted (skipped/failed execution)
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
## Predicates
|
|
38
|
-
|
|
39
|
-
Use state predicates to check the current execution lifecycle:
|
|
40
|
-
|
|
41
|
-
```ruby
|
|
42
|
-
result = ProcessVideoUpload.execute
|
|
43
|
-
|
|
44
|
-
# Individual state checks
|
|
45
|
-
result.initialized? #=> false (after execution)
|
|
46
|
-
result.executing? #=> false (after execution)
|
|
47
|
-
result.complete? #=> true (successful completion)
|
|
48
|
-
result.interrupted? #=> false (no interruption)
|
|
49
|
-
|
|
50
|
-
# State categorization
|
|
51
|
-
result.executed? #=> true (complete OR interrupted)
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
## Handlers
|
|
55
|
-
|
|
56
|
-
Handle lifecycle events with state-based handlers. Use `on(:executed)` for cleanup that runs regardless of outcome:
|
|
57
|
-
|
|
58
|
-
```ruby
|
|
59
|
-
result = ProcessVideoUpload.execute
|
|
60
|
-
|
|
61
|
-
# Individual state handlers
|
|
62
|
-
result
|
|
63
|
-
.on(:complete) { |result| send_upload_notification(result) }
|
|
64
|
-
.on(:interrupted) { |result| cleanup_temp_files(result) }
|
|
65
|
-
.on(:executed) { |result| log_upload_metrics(result) } #=> .on(:complete, :interrupted)
|
|
66
|
-
```
|
data/docs/outcomes/statuses.md
DELETED
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
# Outcomes - Statuses
|
|
2
|
-
|
|
3
|
-
Statuses represent the business outcome—did the task succeed, skip, or fail? This differs from state, which tracks the execution lifecycle.
|
|
4
|
-
|
|
5
|
-
## Definitions
|
|
6
|
-
|
|
7
|
-
| Status | Description |
|
|
8
|
-
| ------ | ----------- |
|
|
9
|
-
| `success` | Task execution completed successfully with expected business outcome. Default status for all tasks. |
|
|
10
|
-
| `skipped` | Task intentionally stopped execution because conditions weren't met or continuation was unnecessary. |
|
|
11
|
-
| `failed` | Task stopped execution due to business rule violations, validation errors, or exceptions. |
|
|
12
|
-
|
|
13
|
-
## Transitions
|
|
14
|
-
|
|
15
|
-
!!! warning "Important"
|
|
16
|
-
|
|
17
|
-
Status transitions are final and unidirectional. Once skipped or failed, tasks can't return to success.
|
|
18
|
-
|
|
19
|
-
```ruby
|
|
20
|
-
# Valid status transitions
|
|
21
|
-
success → skipped # via skip!
|
|
22
|
-
success → failed # via fail! or exception
|
|
23
|
-
|
|
24
|
-
# Invalid transitions (will raise errors)
|
|
25
|
-
skipped → success # ❌ Cannot transition
|
|
26
|
-
skipped → failed # ❌ Cannot transition
|
|
27
|
-
failed → success # ❌ Cannot transition
|
|
28
|
-
failed → skipped # ❌ Cannot transition
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
## Predicates
|
|
32
|
-
|
|
33
|
-
Use status predicates to check execution outcomes:
|
|
34
|
-
|
|
35
|
-
```ruby
|
|
36
|
-
result = ProcessNotification.execute
|
|
37
|
-
|
|
38
|
-
# Individual status checks
|
|
39
|
-
result.success? #=> true/false
|
|
40
|
-
result.skipped? #=> true/false
|
|
41
|
-
result.failed? #=> true/false
|
|
42
|
-
|
|
43
|
-
# Outcome categorization
|
|
44
|
-
result.good? #=> true if success OR skipped
|
|
45
|
-
result.bad? #=> true if skipped OR failed (not success)
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
## Handlers
|
|
49
|
-
|
|
50
|
-
Branch business logic with status-based handlers. Use `on(:good)` and `on(:bad)` for success/skip vs failed outcomes:
|
|
51
|
-
|
|
52
|
-
```ruby
|
|
53
|
-
result = ProcessNotification.execute
|
|
54
|
-
|
|
55
|
-
# Individual status handlers
|
|
56
|
-
result
|
|
57
|
-
.on(:success) { |result| mark_notification_sent(result) }
|
|
58
|
-
.on(:skipped) { |result| log_notification_skipped(result) }
|
|
59
|
-
.on(:failed){ |result| queue_retry_notification(result) }
|
|
60
|
-
|
|
61
|
-
# Outcome-based handlers
|
|
62
|
-
result
|
|
63
|
-
.on(:good) { |result| update_message_stats(result) } #=> .on(:success, :skipped)
|
|
64
|
-
.on(:bad) { |result| track_delivery_failure(result) } #=> .on(:failed, :skipped)
|
|
65
|
-
```
|
data/docs/retries.md
DELETED
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
# Retries
|
|
2
|
-
|
|
3
|
-
CMDx provides automatic retry functionality for tasks that encounter transient failures. This is essential for handling temporary issues like network timeouts, rate limits, or database locks without manual intervention.
|
|
4
|
-
|
|
5
|
-
## Basic Usage
|
|
6
|
-
|
|
7
|
-
Configure retries upto n attempts without any delay.
|
|
8
|
-
|
|
9
|
-
```ruby
|
|
10
|
-
class FetchExternalData < CMDx::Task
|
|
11
|
-
settings retries: 3
|
|
12
|
-
|
|
13
|
-
def work
|
|
14
|
-
response = HTTParty.get("https://api.example.com/data")
|
|
15
|
-
context.data = response.parsed_response
|
|
16
|
-
end
|
|
17
|
-
end
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
When an exception occurs during execution, CMDx automatically retries up to the configured limit. Each retry attempt is logged at the `warn` level with retry metadata. If all retries are exhausted, the task fails with the original exception.
|
|
21
|
-
|
|
22
|
-
## Selective Retries
|
|
23
|
-
|
|
24
|
-
By default, CMDx retries on `StandardError` and its subclasses. Narrow this to specific exception types:
|
|
25
|
-
|
|
26
|
-
```ruby
|
|
27
|
-
class ProcessPayment < CMDx::Task
|
|
28
|
-
settings retries: 5, retry_on: [Stripe::RateLimitError, Net::ReadTimeout]
|
|
29
|
-
|
|
30
|
-
def work
|
|
31
|
-
# Your logic here...
|
|
32
|
-
end
|
|
33
|
-
end
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
!!! warning "Important"
|
|
37
|
-
|
|
38
|
-
Only exceptions matching the `retry_on` configuration will trigger retries. Uncaught exceptions immediately fail the task.
|
|
39
|
-
|
|
40
|
-
## Retry Jitter
|
|
41
|
-
|
|
42
|
-
Add delays between retry attempts to avoid overwhelming external services or to implement exponential backoff strategies.
|
|
43
|
-
|
|
44
|
-
### Fixed Value
|
|
45
|
-
|
|
46
|
-
Use a numeric value to calculate linear delay (`jitter * current_retry`):
|
|
47
|
-
|
|
48
|
-
```ruby
|
|
49
|
-
class ImportRecords < CMDx::Task
|
|
50
|
-
settings retries: 3, retry_jitter: 0.5
|
|
51
|
-
|
|
52
|
-
def work
|
|
53
|
-
# Delays: 0s, 0.5s (retry 1), 1.0s (retry 2), 1.5s (retry 3)
|
|
54
|
-
context.records = ExternalAPI.fetch_records
|
|
55
|
-
end
|
|
56
|
-
end
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
### Symbol References
|
|
60
|
-
|
|
61
|
-
Define an instance method for custom delay logic:
|
|
62
|
-
|
|
63
|
-
```ruby
|
|
64
|
-
class SyncInventory < CMDx::Task
|
|
65
|
-
settings retries: 5, retry_jitter: :exponential_backoff
|
|
66
|
-
|
|
67
|
-
def work
|
|
68
|
-
context.inventory = InventoryAPI.sync
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
private
|
|
72
|
-
|
|
73
|
-
def exponential_backoff(current_retry)
|
|
74
|
-
2 ** current_retry # 2s, 4s, 8s, 16s, 32s
|
|
75
|
-
end
|
|
76
|
-
end
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
### Proc or Lambda
|
|
80
|
-
|
|
81
|
-
Pass a proc for inline delay calculations:
|
|
82
|
-
|
|
83
|
-
```ruby
|
|
84
|
-
class PollJobStatus < CMDx::Task
|
|
85
|
-
# Proc
|
|
86
|
-
settings retries: 10, retry_jitter: proc { |retry_count| [retry_count * 0.5, 5.0].min }
|
|
87
|
-
|
|
88
|
-
# Lambda
|
|
89
|
-
settings retries: 10, retry_jitter: ->(retry_count) { [retry_count * 0.5, 5.0].min }
|
|
90
|
-
|
|
91
|
-
def work
|
|
92
|
-
# Delays: 0.5s, 1.0s, 1.5s, 2.0s, 2.5s, 3.0s, 3.5s, 4.0s, 4.5s, 5.0s (capped)
|
|
93
|
-
context.status = JobAPI.check_status(context.job_id)
|
|
94
|
-
end
|
|
95
|
-
end
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
### Class or Module
|
|
99
|
-
|
|
100
|
-
Implement reusable delay logic in dedicated modules and classes:
|
|
101
|
-
|
|
102
|
-
```ruby
|
|
103
|
-
class ExponentialBackoff
|
|
104
|
-
def call(task, retry_count)
|
|
105
|
-
base_delay = task.context.base_delay || 1.0
|
|
106
|
-
[base_delay * (2 ** retry_count), 60.0].min
|
|
107
|
-
end
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
class FetchUserProfile < CMDx::Task
|
|
111
|
-
# Class or Module
|
|
112
|
-
settings retries: 4, retry_jitter: ExponentialBackoff
|
|
113
|
-
|
|
114
|
-
# Instance
|
|
115
|
-
settings retries: 4, retry_jitter: ExponentialBackoff.new
|
|
116
|
-
|
|
117
|
-
def work
|
|
118
|
-
# Your logic here...
|
|
119
|
-
end
|
|
120
|
-
end
|
|
121
|
-
```
|
data/docs/stylesheets/extra.css
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
:root > * {
|
|
2
|
-
/* Primary color shades */
|
|
3
|
-
--md-primary-fg-color: #fe1817;
|
|
4
|
-
--md-primary-fg-color--light: #fe1817;
|
|
5
|
-
--md-primary-fg-color--dark: #fe1817;
|
|
6
|
-
|
|
7
|
-
/* Accent color shades */
|
|
8
|
-
--md-accent-fg-color: hsla(#{hex2hsl(#fe1817)}, 1);
|
|
9
|
-
--md-accent-fg-color--transparent: hsla(#{hex2hsl(#fe1817)}, 0.1);
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
/* GitHub High Contrast Light syntax highlighting */
|
|
13
|
-
[data-md-color-scheme="default"] {
|
|
14
|
-
--md-code-hl-color: #0e1116;
|
|
15
|
-
--md-code-hl-keyword-color: #a0095d;
|
|
16
|
-
--md-code-hl-string-color: #024c1a;
|
|
17
|
-
--md-code-hl-name-color: #622cbc;
|
|
18
|
-
--md-code-hl-function-color: #622cbc;
|
|
19
|
-
--md-code-hl-number-color: #0349b4;
|
|
20
|
-
--md-code-hl-constant-color: #702c00;
|
|
21
|
-
--md-code-hl-comment-color: #66707b;
|
|
22
|
-
--md-code-hl-operator-color: #a0095d;
|
|
23
|
-
--md-code-hl-punctuation-color:#0e1116;
|
|
24
|
-
--md-code-hl-variable-color: #702c00;
|
|
25
|
-
--md-code-hl-generic-color: #622cbc;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/* GitHub High Contrast Dark syntax highlighting */
|
|
29
|
-
[data-md-color-scheme="slate"] {
|
|
30
|
-
--md-code-hl-color: #f0f3f6;
|
|
31
|
-
--md-code-hl-keyword-color: #ff9492;
|
|
32
|
-
--md-code-hl-string-color: #addcff;
|
|
33
|
-
--md-code-hl-name-color: #dbb7ff;
|
|
34
|
-
--md-code-hl-function-color: #dbb7ff;
|
|
35
|
-
--md-code-hl-number-color: #91cbff;
|
|
36
|
-
--md-code-hl-constant-color: #ffb757;
|
|
37
|
-
--md-code-hl-comment-color: #9ea7b3;
|
|
38
|
-
--md-code-hl-operator-color: #ff9492;
|
|
39
|
-
--md-code-hl-punctuation-color:#f0f3f6;
|
|
40
|
-
--md-code-hl-variable-color: #ffb757;
|
|
41
|
-
--md-code-hl-generic-color: #dbb7ff;
|
|
42
|
-
}
|
data/docs/tips_and_tricks.md
DELETED
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
# Tips and Tricks
|
|
2
|
-
|
|
3
|
-
Best practices, patterns, and techniques to build maintainable CMDx applications.
|
|
4
|
-
|
|
5
|
-
## Project Organization
|
|
6
|
-
|
|
7
|
-
### Directory Structure
|
|
8
|
-
|
|
9
|
-
Create a well-organized command structure for maintainable applications:
|
|
10
|
-
|
|
11
|
-
```text
|
|
12
|
-
/app/
|
|
13
|
-
└── /tasks/
|
|
14
|
-
├── /invoices/
|
|
15
|
-
│ ├── calculate_tax.rb
|
|
16
|
-
│ ├── validate_invoice.rb
|
|
17
|
-
│ ├── send_invoice.rb
|
|
18
|
-
│ └── process_invoice.rb # workflow
|
|
19
|
-
├── /reports/
|
|
20
|
-
│ ├── generate_pdf.rb
|
|
21
|
-
│ ├── compile_data.rb
|
|
22
|
-
│ ├── export_csv.rb
|
|
23
|
-
│ └── create_reports.rb # workflow
|
|
24
|
-
├── application_task.rb # base class
|
|
25
|
-
├── authenticate_session.rb
|
|
26
|
-
└── activate_account.rb
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
### Naming Conventions
|
|
30
|
-
|
|
31
|
-
Follow consistent naming patterns for clarity and maintainability:
|
|
32
|
-
|
|
33
|
-
```ruby
|
|
34
|
-
# Verb + Noun
|
|
35
|
-
class ExportData < CMDx::Task; end
|
|
36
|
-
class CompressFile < CMDx::Task; end
|
|
37
|
-
class ValidateSchema < CMDx::Task; end
|
|
38
|
-
|
|
39
|
-
# Use present tense verbs for actions
|
|
40
|
-
class GenerateToken < CMDx::Task; end # ✓ Good
|
|
41
|
-
class GeneratingToken < CMDx::Task; end # ❌ Avoid
|
|
42
|
-
class TokenGeneration < CMDx::Task; end # ❌ Avoid
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
### Story Telling
|
|
46
|
-
|
|
47
|
-
Break down complex logic into descriptive methods that read like a narrative:
|
|
48
|
-
|
|
49
|
-
```ruby
|
|
50
|
-
class ProcessOrder < CMDx::Task
|
|
51
|
-
def work
|
|
52
|
-
charge_payment_method
|
|
53
|
-
assign_to_warehouse
|
|
54
|
-
send_notification
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
private
|
|
58
|
-
|
|
59
|
-
def charge_payment_method
|
|
60
|
-
order.primary_payment_method.charge!
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
def assign_to_warehouse
|
|
64
|
-
order.ready_for_shipping!
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
def send_notification
|
|
68
|
-
if order.products_out_of_stock?
|
|
69
|
-
OrderMailer.pending(order).deliver
|
|
70
|
-
else
|
|
71
|
-
OrderMailer.preparing(order).deliver
|
|
72
|
-
end
|
|
73
|
-
end
|
|
74
|
-
end
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
### Style Guide
|
|
78
|
-
|
|
79
|
-
Follow this order for consistent, readable tasks:
|
|
80
|
-
|
|
81
|
-
```ruby
|
|
82
|
-
class ExportReport < CMDx::Task
|
|
83
|
-
|
|
84
|
-
# 1. Register functions
|
|
85
|
-
register :middleware, CMDx::Middlewares::Correlate
|
|
86
|
-
register :validator, :format, FormatValidator
|
|
87
|
-
|
|
88
|
-
# 2. Define callbacks
|
|
89
|
-
before_execution :find_report
|
|
90
|
-
on_complete :track_export_metrics, if: ->(task) { Current.tenant.analytics? }
|
|
91
|
-
|
|
92
|
-
# 3. Declare attributes
|
|
93
|
-
attributes :user_id
|
|
94
|
-
required :report_id
|
|
95
|
-
optional :format_type
|
|
96
|
-
|
|
97
|
-
# 4. Define work method
|
|
98
|
-
def work
|
|
99
|
-
report.compile!
|
|
100
|
-
report.export!
|
|
101
|
-
|
|
102
|
-
context.exported_at = Time.now
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
# TIP: Favor private business logic to reduce the surface of the public API.
|
|
106
|
-
private
|
|
107
|
-
|
|
108
|
-
# 5. Build helper functions
|
|
109
|
-
def find_report
|
|
110
|
-
@report ||= Report.find(report_id)
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
def track_export_metrics
|
|
114
|
-
Analytics.increment(:report_exported)
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
end
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
## Attribute Options
|
|
121
|
-
|
|
122
|
-
Use `with_options` to reduce duplication:
|
|
123
|
-
|
|
124
|
-
```ruby
|
|
125
|
-
class ConfigureCompany < CMDx::Task
|
|
126
|
-
# Apply common options to multiple attributes
|
|
127
|
-
with_options(type: :string, presence: true) do
|
|
128
|
-
attributes :website, format: { with: URI::DEFAULT_PARSER.make_regexp(%w[http https]) }
|
|
129
|
-
required :company_name, :industry
|
|
130
|
-
optional :description, format: { with: /\A[\w\s\-\.,!?]+\z/ }
|
|
131
|
-
end
|
|
132
|
-
|
|
133
|
-
# Nested attributes with shared prefix
|
|
134
|
-
required :headquarters do
|
|
135
|
-
with_options(prefix: :hq_) do
|
|
136
|
-
attributes :street, :city, :zip_code, type: :string
|
|
137
|
-
required :country, type: :string, inclusion: { in: VALID_COUNTRIES }
|
|
138
|
-
optional :region, type: :string
|
|
139
|
-
end
|
|
140
|
-
end
|
|
141
|
-
|
|
142
|
-
def work
|
|
143
|
-
# Your logic here...
|
|
144
|
-
end
|
|
145
|
-
end
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
## Useful Examples
|
|
149
|
-
|
|
150
|
-
- [Active Record Database Transaction](https://github.com/drexed/cmdx/blob/main/examples/active_record_database_transaction.md)
|
|
151
|
-
- [Active Record Query Tagging](https://github.com/drexed/cmdx/blob/main/examples/active_record_query_tagging.md)
|
|
152
|
-
- [Flipper Feature Flags](https://github.com/drexed/cmdx/blob/main/examples/flipper_feature_flags.md)
|
|
153
|
-
- [Paper Trail Whatdunnit](https://github.com/drexed/cmdx/blob/main/examples/paper_trail_whatdunnit.md)
|
|
154
|
-
- [Redis Idempotency](https://github.com/drexed/cmdx/blob/main/examples/redis_idempotency.md)
|
|
155
|
-
- [Sentry Error Tracking](https://github.com/drexed/cmdx/blob/main/examples/sentry_error_tracking.md)
|
|
156
|
-
- [Sidekiq Async Execution](https://github.com/drexed/cmdx/blob/main/examples/sidekiq_async_execution.md)
|
|
157
|
-
- [Stoplight Circuit Breaker](https://github.com/drexed/cmdx/blob/main/examples/stoplight_circuit_breaker.md)
|