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.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +84 -76
  3. data/LICENSE.txt +3 -20
  4. data/README.md +8 -7
  5. data/lib/cmdx/attribute.rb +21 -5
  6. data/lib/cmdx/context.rb +16 -0
  7. data/lib/cmdx/executor.rb +9 -9
  8. data/lib/cmdx/result.rb +27 -7
  9. data/lib/cmdx/task.rb +19 -0
  10. data/lib/cmdx/version.rb +1 -1
  11. data/mkdocs.yml +62 -36
  12. metadata +3 -57
  13. data/.cursor/prompts/docs.md +0 -12
  14. data/.cursor/prompts/llms.md +0 -8
  15. data/.cursor/prompts/rspec.md +0 -24
  16. data/.cursor/prompts/yardoc.md +0 -15
  17. data/.cursor/rules/cursor-instructions.mdc +0 -68
  18. data/.irbrc +0 -18
  19. data/.rspec +0 -4
  20. data/.rubocop.yml +0 -95
  21. data/.ruby-version +0 -1
  22. data/.yard-lint.yml +0 -174
  23. data/.yardopts +0 -7
  24. data/docs/.DS_Store +0 -0
  25. data/docs/assets/favicon.ico +0 -0
  26. data/docs/assets/favicon.svg +0 -1
  27. data/docs/attributes/coercions.md +0 -155
  28. data/docs/attributes/defaults.md +0 -77
  29. data/docs/attributes/definitions.md +0 -283
  30. data/docs/attributes/naming.md +0 -68
  31. data/docs/attributes/transformations.md +0 -63
  32. data/docs/attributes/validations.md +0 -336
  33. data/docs/basics/chain.md +0 -108
  34. data/docs/basics/context.md +0 -121
  35. data/docs/basics/execution.md +0 -152
  36. data/docs/basics/setup.md +0 -107
  37. data/docs/callbacks.md +0 -157
  38. data/docs/configuration.md +0 -314
  39. data/docs/deprecation.md +0 -143
  40. data/docs/getting_started.md +0 -137
  41. data/docs/index.md +0 -134
  42. data/docs/internationalization.md +0 -126
  43. data/docs/interruptions/exceptions.md +0 -52
  44. data/docs/interruptions/faults.md +0 -169
  45. data/docs/interruptions/halt.md +0 -216
  46. data/docs/logging.md +0 -90
  47. data/docs/middlewares.md +0 -191
  48. data/docs/outcomes/result.md +0 -197
  49. data/docs/outcomes/states.md +0 -66
  50. data/docs/outcomes/statuses.md +0 -65
  51. data/docs/retries.md +0 -121
  52. data/docs/stylesheets/extra.css +0 -42
  53. data/docs/tips_and_tricks.md +0 -157
  54. data/docs/workflows.md +0 -226
  55. data/examples/active_record_database_transaction.md +0 -27
  56. data/examples/active_record_query_tagging.md +0 -46
  57. data/examples/flipper_feature_flags.md +0 -50
  58. data/examples/paper_trail_whatdunnit.md +0 -39
  59. data/examples/redis_idempotency.md +0 -71
  60. data/examples/sentry_error_tracking.md +0 -46
  61. data/examples/sidekiq_async_execution.md +0 -29
  62. data/examples/stoplight_circuit_breaker.md +0 -36
  63. data/src/cmdx-dark-logo.png +0 -0
  64. data/src/cmdx-favicon.svg +0 -1
  65. data/src/cmdx-light-logo.png +0 -0
  66. data/src/cmdx-logo.svg +0 -1
@@ -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
- ```
@@ -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
- ```
@@ -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
- }
@@ -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)
data/docs/workflows.md DELETED
@@ -1,226 +0,0 @@
1
- # Workflows
2
-
3
- Compose multiple tasks into powerful, sequential pipelines. Workflows provide a declarative way to build complex business processes with conditional execution, shared context, and flexible error handling.
4
-
5
- ## Declarations
6
-
7
- Tasks run in declaration order (FIFO), sharing a common context across the pipeline.
8
-
9
- !!! warning
10
-
11
- Don't define a `work` method in workflows—the module handles execution automatically.
12
-
13
- ### Task
14
-
15
- ```ruby
16
- class OnboardingWorkflow < CMDx::Task
17
- include CMDx::Workflow
18
-
19
- task CreateUserProfile
20
- task SetupAccountPreferences
21
-
22
- tasks SendWelcomeEmail, SendWelcomeSms, CreateDashboard
23
- end
24
- ```
25
-
26
- !!! tip
27
-
28
- Execute tasks in parallel via the [cmdx-parallel](https://github.com/drexed/cmdx-parallel) gem.
29
-
30
- ### Group
31
-
32
- Group related tasks to share configuration:
33
-
34
- !!! warning "Important"
35
-
36
- Settings and conditionals apply to all tasks in the group.
37
-
38
- ```ruby
39
- class ContentModerationWorkflow < CMDx::Task
40
- include CMDx::Workflow
41
-
42
- # Screening phase
43
- tasks ScanForProfanity, CheckForSpam, ValidateImages, breakpoints: ["skipped"]
44
-
45
- # Review phase
46
- tasks ApplyFilters, ScoreContent, FlagSuspicious
47
-
48
- # Decision phase
49
- tasks PublishContent, QueueForReview, NotifyModerators
50
- end
51
- ```
52
-
53
- ### Conditionals
54
-
55
- Conditionals support multiple syntaxes for flexible execution control:
56
-
57
- ```ruby
58
- class ContentAccessCheck
59
- def call(task)
60
- task.context.user.can?(:publish_content)
61
- end
62
- end
63
-
64
- class OnboardingWorkflow < CMDx::Task
65
- include CMDx::Workflow
66
-
67
- # If and/or Unless
68
- task SendWelcomeEmail, if: :email_configured?, unless: :email_disabled?
69
-
70
- # Proc
71
- task SendWelcomeEmail, if: -> { Rails.env.production? && self.class.name.include?("Premium") }
72
-
73
- # Lambda
74
- task SendWelcomeEmail, if: proc { context.features_enabled? }
75
-
76
- # Class or Module
77
- task SendWelcomeEmail, unless: ContentAccessCheck
78
-
79
- # Instance
80
- task SendWelcomeEmail, if: ContentAccessCheck.new
81
-
82
- # Conditional applies to all tasks of this declaration group
83
- tasks SendWelcomeEmail, CreateDashboard, SetupTutorial, if: :email_configured?
84
-
85
- private
86
-
87
- def email_configured?
88
- context.user.email_address == true
89
- end
90
-
91
- def email_disabled?
92
- context.user.communication_preference == :disabled
93
- end
94
- end
95
- ```
96
-
97
- ## Halt Behavior
98
-
99
- By default, skipped tasks don't stop the workflow—they're treated as no-ops. Configure breakpoints globally or per-task to customize this behavior.
100
-
101
- ```ruby
102
- class AnalyticsWorkflow < CMDx::Task
103
- include CMDx::Workflow
104
-
105
- task CollectMetrics # If fails → workflow stops
106
- task FilterOutliers # If skipped → workflow continues
107
- task GenerateDashboard # Only runs if no failures occurred
108
- end
109
- ```
110
-
111
- ### Task Configuration
112
-
113
- Configure halt behavior for the entire workflow:
114
-
115
- ```ruby
116
- class SecurityWorkflow < CMDx::Task
117
- include CMDx::Workflow
118
-
119
- # Halt on both failed and skipped results
120
- settings(workflow_breakpoints: ["skipped", "failed"])
121
-
122
- task PerformSecurityScan
123
- task ValidateSecurityRules
124
- end
125
-
126
- class OptionalTasksWorkflow < CMDx::Task
127
- include CMDx::Workflow
128
-
129
- # Never halt, always continue
130
- settings(breakpoints: [])
131
-
132
- task TryBackupData
133
- task TryCleanupLogs
134
- task TryOptimizeCache
135
- end
136
- ```
137
-
138
- ### Group Configuration
139
-
140
- Different task groups can have different halt behavior:
141
-
142
- ```ruby
143
- class SubscriptionWorkflow < CMDx::Task
144
- include CMDx::Workflow
145
-
146
- task CreateSubscription, ValidatePayment, workflow_breakpoints: ["skipped", "failed"]
147
-
148
- # Never halt, always continue
149
- task SendConfirmationEmail, UpdateBilling, breakpoints: []
150
- end
151
- ```
152
-
153
- ## Nested Workflows
154
-
155
- Build hierarchical workflows by composing workflows within workflows:
156
-
157
- ```ruby
158
- class EmailPreparationWorkflow < CMDx::Task
159
- include CMDx::Workflow
160
-
161
- task ValidateRecipients
162
- task CompileTemplate
163
- end
164
-
165
- class EmailDeliveryWorkflow < CMDx::Task
166
- include CMDx::Workflow
167
-
168
- tasks SendEmails, TrackDeliveries
169
- end
170
-
171
- class CompleteEmailWorkflow < CMDx::Task
172
- include CMDx::Workflow
173
-
174
- task EmailPreparationWorkflow
175
- task EmailDeliveryWorkflow, if: proc { context.preparation_successful? }
176
- task GenerateDeliveryReport
177
- end
178
- ```
179
-
180
- ## Parallel Execution
181
-
182
- Run tasks concurrently using the [Parallel](https://github.com/grosser/parallel) gem. It automatically uses all available processors for maximum throughput.
183
-
184
- !!! warning
185
-
186
- Context is read-only during parallel execution. Load all required data beforehand.
187
-
188
- ```ruby
189
- class SendWelcomeNotifications < CMDx::Task
190
- include CMDx::Workflow
191
-
192
- # Default options (dynamically calculated to available processors)
193
- tasks SendWelcomeEmail, SendWelcomeSms, SendWelcomePush, strategy: :parallel
194
-
195
- # Fix number of threads
196
- tasks SendWelcomeEmail, SendWelcomeSms, SendWelcomePush, strategy: :parallel, in_threads: 2
197
-
198
- # Fix number of forked processes
199
- tasks SendWelcomeEmail, SendWelcomeSms, SendWelcomePush, strategy: :parallel, in_processes: 2
200
-
201
- # NOTE: Reactors are not supported
202
- end
203
- ```
204
-
205
- ## Task Generator
206
-
207
- Generate new CMDx workflow tasks quickly using the built-in generator:
208
-
209
- ```bash
210
- rails generate cmdx:workflow SendNotifications
211
- ```
212
-
213
- This creates a new workflow task file with the basic structure:
214
-
215
- ```ruby
216
- # app/tasks/send_notifications.rb
217
- class SendNotifications < CMDx::Task
218
- include CMDx::Workflow
219
-
220
- tasks Task1, Task2
221
- end
222
- ```
223
-
224
- !!! tip
225
-
226
- Use **present tense verbs + pluralized noun** for workflow task names, eg: `SendNotifications`, `DownloadFiles`, `ValidateDocuments`
@@ -1,27 +0,0 @@
1
- # Active Record Query Tagging
2
-
3
- Wrap task or workflow execution in a database transaction. This is essential for data integrity when multiple steps modify the database.
4
-
5
- ### Setup
6
-
7
- ```ruby
8
- # lib/cmdx_database_transaction_middleware.rb
9
- class CmdxDatabaseTransactionMiddleware
10
- def self.call(task, **options, &)
11
- ActiveRecord::Base.transaction(requires_new: true, &)
12
- end
13
- end
14
- ```
15
-
16
- ### Usage
17
-
18
- ```ruby
19
- class MyTask < CMDx::Task
20
- register :middleware, CmdxDatabaseTransactionMiddleware
21
-
22
- def work
23
- # Do work...
24
- end
25
-
26
- end
27
- ```