cmdx 1.8.0 → 1.9.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/.DS_Store +0 -0
- data/.cursor/prompts/docs.md +3 -3
- data/.cursor/prompts/llms.md +1 -3
- data/.irbrc +14 -2
- data/CHANGELOG.md +58 -45
- data/LLM.md +150 -53
- data/README.md +23 -85
- data/docs/.DS_Store +0 -0
- data/docs/assets/favicon.ico +0 -0
- data/docs/assets/favicon.svg +1 -0
- data/docs/attributes/coercions.md +12 -24
- data/docs/attributes/defaults.md +3 -16
- data/docs/attributes/definitions.md +16 -30
- data/docs/attributes/naming.md +3 -13
- data/docs/attributes/transformations.md +63 -0
- data/docs/attributes/validations.md +14 -33
- data/docs/basics/chain.md +14 -23
- data/docs/basics/context.md +13 -22
- data/docs/basics/execution.md +8 -26
- data/docs/basics/setup.md +8 -19
- data/docs/callbacks.md +19 -32
- data/docs/deprecation.md +8 -25
- data/docs/getting_started.md +101 -77
- data/docs/index.md +120 -0
- data/docs/internationalization.md +6 -18
- data/docs/interruptions/exceptions.md +10 -16
- data/docs/interruptions/faults.md +8 -25
- data/docs/interruptions/halt.md +12 -27
- data/docs/logging.md +7 -17
- data/docs/middlewares.md +13 -29
- data/docs/outcomes/result.md +21 -38
- data/docs/outcomes/states.md +8 -22
- data/docs/outcomes/statuses.md +10 -21
- data/docs/stylesheets/extra.css +42 -0
- data/docs/tips_and_tricks.md +7 -46
- data/docs/workflows.md +23 -38
- data/examples/active_record_query_tagging.md +46 -0
- data/examples/paper_trail_whatdunnit.md +39 -0
- data/lib/cmdx/attribute.rb +6 -5
- data/lib/cmdx/attribute_value.rb +31 -10
- data/lib/cmdx/callback_registry.rb +12 -2
- data/lib/cmdx/coercions/hash.rb +2 -0
- data/lib/cmdx/configuration.rb +10 -2
- data/lib/cmdx/deprecator.rb +3 -3
- data/lib/cmdx/executor.rb +93 -7
- data/lib/cmdx/pipeline.rb +4 -4
- data/lib/cmdx/railtie.rb +9 -0
- data/lib/cmdx/result.rb +10 -1
- data/lib/cmdx/task.rb +12 -7
- data/lib/cmdx/version.rb +1 -1
- data/lib/cmdx.rb +1 -0
- data/lib/generators/cmdx/templates/install.rb +9 -0
- data/mkdocs.yml +122 -0
- data/src/cmdx-dark-logo.png +0 -0
- data/src/cmdx-favicon.svg +1 -0
- data/src/cmdx-light-logo.png +0 -0
- data/src/cmdx-logo.svg +1 -0
- metadata +14 -3
- data/lib/cmdx/freezer.rb +0 -51
- data/src/cmdx-logo.png +0 -0
data/docs/outcomes/states.md
CHANGED
|
@@ -1,16 +1,6 @@
|
|
|
1
1
|
# Outcomes - States
|
|
2
2
|
|
|
3
|
-
States
|
|
4
|
-
the progress of tasks through their complete execution journey. States provide
|
|
5
|
-
insight into where a task is in its lifecycle and enable lifecycle-based
|
|
6
|
-
decision making and monitoring.
|
|
7
|
-
|
|
8
|
-
## Table of Contents
|
|
9
|
-
|
|
10
|
-
- [Definitions](#definitions)
|
|
11
|
-
- [Transitions](#transitions)
|
|
12
|
-
- [Predicates](#predicates)
|
|
13
|
-
- [Handlers](#handlers)
|
|
3
|
+
States track where a task is in its execution lifecycle—from creation through completion or interruption.
|
|
14
4
|
|
|
15
5
|
## Definitions
|
|
16
6
|
|
|
@@ -34,8 +24,9 @@ State-Status combinations:
|
|
|
34
24
|
|
|
35
25
|
## Transitions
|
|
36
26
|
|
|
37
|
-
|
|
38
|
-
|
|
27
|
+
!!! danger "Caution"
|
|
28
|
+
|
|
29
|
+
States are managed automatically—never modify them manually.
|
|
39
30
|
|
|
40
31
|
```ruby
|
|
41
32
|
# Valid state transition flow
|
|
@@ -62,19 +53,14 @@ result.executed? #=> true (complete OR interrupted)
|
|
|
62
53
|
|
|
63
54
|
## Handlers
|
|
64
55
|
|
|
65
|
-
|
|
56
|
+
Handle lifecycle events with state-based handlers. Use `handle_executed` for cleanup that runs regardless of outcome:
|
|
66
57
|
|
|
67
58
|
```ruby
|
|
68
59
|
result = ProcessVideoUpload.execute
|
|
69
60
|
|
|
70
61
|
# Individual state handlers
|
|
71
62
|
result
|
|
72
|
-
.
|
|
73
|
-
.
|
|
74
|
-
.
|
|
63
|
+
.handle_complete { |result| send_upload_notification(result) }
|
|
64
|
+
.handle_interrupted { |result| cleanup_temp_files(result) }
|
|
65
|
+
.handle_executed { |result| log_upload_metrics(result) }
|
|
75
66
|
```
|
|
76
|
-
|
|
77
|
-
---
|
|
78
|
-
|
|
79
|
-
- **Prev:** [Outcomes - Result](result.md)
|
|
80
|
-
- **Next:** [Outcomes - Statuses](statuses.md)
|
data/docs/outcomes/statuses.md
CHANGED
|
@@ -1,13 +1,6 @@
|
|
|
1
1
|
# Outcomes - Statuses
|
|
2
2
|
|
|
3
|
-
Statuses represent the business outcome
|
|
4
|
-
|
|
5
|
-
## Table of Contents
|
|
6
|
-
|
|
7
|
-
- [Definitions](#definitions)
|
|
8
|
-
- [Transitions](#transitions)
|
|
9
|
-
- [Predicates](#predicates)
|
|
10
|
-
- [Handlers](#handlers)
|
|
3
|
+
Statuses represent the business outcome—did the task succeed, skip, or fail? This differs from state, which tracks the execution lifecycle.
|
|
11
4
|
|
|
12
5
|
## Definitions
|
|
13
6
|
|
|
@@ -19,8 +12,9 @@ Statuses represent the business outcome of task execution logic, indicating how
|
|
|
19
12
|
|
|
20
13
|
## Transitions
|
|
21
14
|
|
|
22
|
-
|
|
23
|
-
|
|
15
|
+
!!! warning "Important"
|
|
16
|
+
|
|
17
|
+
Status transitions are final and unidirectional. Once skipped or failed, tasks can't return to success.
|
|
24
18
|
|
|
25
19
|
```ruby
|
|
26
20
|
# Valid status transitions
|
|
@@ -53,24 +47,19 @@ result.bad? #=> true if skipped OR failed (not success)
|
|
|
53
47
|
|
|
54
48
|
## Handlers
|
|
55
49
|
|
|
56
|
-
|
|
50
|
+
Branch business logic with status-based handlers. Use `handle_good` and `handle_bad` for success/skip vs failed outcomes:
|
|
57
51
|
|
|
58
52
|
```ruby
|
|
59
53
|
result = ProcessNotification.execute
|
|
60
54
|
|
|
61
55
|
# Individual status handlers
|
|
62
56
|
result
|
|
63
|
-
.
|
|
64
|
-
.
|
|
65
|
-
.
|
|
57
|
+
.handle_success { |result| mark_notification_sent(result) }
|
|
58
|
+
.handle_skipped { |result| log_notification_skipped(result) }
|
|
59
|
+
.handle_failed { |result| queue_retry_notification(result) }
|
|
66
60
|
|
|
67
61
|
# Outcome-based handlers
|
|
68
62
|
result
|
|
69
|
-
.
|
|
70
|
-
.
|
|
63
|
+
.handle_good { |result| update_message_stats(result) }
|
|
64
|
+
.handle_bad { |result| track_delivery_failure(result) }
|
|
71
65
|
```
|
|
72
|
-
|
|
73
|
-
---
|
|
74
|
-
|
|
75
|
-
- **Prev:** [Outcomes - States](states.md)
|
|
76
|
-
- **Next:** [Attributes - Definitions](../attributes/definitions.md)
|
|
@@ -0,0 +1,42 @@
|
|
|
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
|
+
/* Atom One Light Pro syntax highlighting */
|
|
13
|
+
[data-md-color-scheme="default"] {
|
|
14
|
+
--md-code-hl-color: #2c3036;
|
|
15
|
+
--md-code-hl-keyword-color: #a626a4;
|
|
16
|
+
--md-code-hl-string-color: #50a14f;
|
|
17
|
+
--md-code-hl-name-color: #e4564a;
|
|
18
|
+
--md-code-hl-function-color: #4078f2;
|
|
19
|
+
--md-code-hl-number-color: #ca7601;
|
|
20
|
+
--md-code-hl-constant-color: #c18401;
|
|
21
|
+
--md-code-hl-comment-color: #9ca0a4;
|
|
22
|
+
--md-code-hl-operator-color: #0184bc;
|
|
23
|
+
--md-code-hl-punctuation-color:#383a42;
|
|
24
|
+
--md-code-hl-variable-color: #e4564a;
|
|
25
|
+
--md-code-hl-generic-color: #e4564a;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/* Atom One Dark Pro syntax highlighting */
|
|
29
|
+
[data-md-color-scheme="slate"] {
|
|
30
|
+
--md-code-hl-color: #e5e5e6;
|
|
31
|
+
--md-code-hl-keyword-color: #c678dd;
|
|
32
|
+
--md-code-hl-string-color: #98c379;
|
|
33
|
+
--md-code-hl-name-color: #e06c75;
|
|
34
|
+
--md-code-hl-function-color: #61afef;
|
|
35
|
+
--md-code-hl-number-color: #d19a66;
|
|
36
|
+
--md-code-hl-constant-color: #d19a66;
|
|
37
|
+
--md-code-hl-comment-color: #7f848e;
|
|
38
|
+
--md-code-hl-operator-color: #56b6c2;
|
|
39
|
+
--md-code-hl-punctuation-color:#abb2bf;
|
|
40
|
+
--md-code-hl-variable-color: #e06c75;
|
|
41
|
+
--md-code-hl-generic-color: #e06c75;
|
|
42
|
+
}
|
data/docs/tips_and_tricks.md
CHANGED
|
@@ -1,16 +1,6 @@
|
|
|
1
1
|
# Tips and Tricks
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
## Table of Contents
|
|
6
|
-
|
|
7
|
-
- [Project Organization](#project-organization)
|
|
8
|
-
- [Directory Structure](#directory-structure)
|
|
9
|
-
- [Naming Conventions](#naming-conventions)
|
|
10
|
-
- [Story Telling](#story-telling)
|
|
11
|
-
- [Style Guide](#style-guide)
|
|
12
|
-
- [Attribute Options](#attribute-options)
|
|
13
|
-
- [ActiveRecord Query Tagging](#activerecord-query-tagging)
|
|
3
|
+
Best practices, patterns, and techniques to build maintainable CMDx applications.
|
|
14
4
|
|
|
15
5
|
## Project Organization
|
|
16
6
|
|
|
@@ -54,7 +44,7 @@ class TokenGeneration < CMDx::Task; end # ❌ Avoid
|
|
|
54
44
|
|
|
55
45
|
### Story Telling
|
|
56
46
|
|
|
57
|
-
|
|
47
|
+
Break down complex logic into descriptive methods that read like a narrative:
|
|
58
48
|
|
|
59
49
|
```ruby
|
|
60
50
|
class ProcessOrder < CMDx::Task
|
|
@@ -86,7 +76,7 @@ end
|
|
|
86
76
|
|
|
87
77
|
### Style Guide
|
|
88
78
|
|
|
89
|
-
Follow
|
|
79
|
+
Follow this order for consistent, readable tasks:
|
|
90
80
|
|
|
91
81
|
```ruby
|
|
92
82
|
class ExportReport < CMDx::Task
|
|
@@ -129,7 +119,7 @@ end
|
|
|
129
119
|
|
|
130
120
|
## Attribute Options
|
|
131
121
|
|
|
132
|
-
Use
|
|
122
|
+
Use `with_options` to reduce duplication:
|
|
133
123
|
|
|
134
124
|
```ruby
|
|
135
125
|
class ConfigureCompany < CMDx::Task
|
|
@@ -155,36 +145,7 @@ class ConfigureCompany < CMDx::Task
|
|
|
155
145
|
end
|
|
156
146
|
```
|
|
157
147
|
|
|
158
|
-
##
|
|
159
|
-
|
|
160
|
-
Automatically tag SQL queries for better debugging:
|
|
161
|
-
|
|
162
|
-
```ruby
|
|
163
|
-
# config/application.rb
|
|
164
|
-
config.active_record.query_log_tags_enabled = true
|
|
165
|
-
config.active_record.query_log_tags << :cmdx_task_class
|
|
166
|
-
config.active_record.query_log_tags << :cmdx_chain_id
|
|
167
|
-
|
|
168
|
-
# app/tasks/application_task.rb
|
|
169
|
-
class ApplicationTask < CMDx::Task
|
|
170
|
-
before_execution :set_execution_context
|
|
171
|
-
|
|
172
|
-
private
|
|
173
|
-
|
|
174
|
-
def set_execution_context
|
|
175
|
-
# NOTE: This could easily be made into a middleware
|
|
176
|
-
ActiveSupport::ExecutionContext.set(
|
|
177
|
-
cmdx_task_class: self.class.name,
|
|
178
|
-
cmdx_chain_id: chain.id
|
|
179
|
-
)
|
|
180
|
-
end
|
|
181
|
-
end
|
|
182
|
-
|
|
183
|
-
# SQL queries will now include comments like:
|
|
184
|
-
# /*cmdx_task_class:ExportReportTask,cmdx_chain_id:018c2b95-b764-7615*/ SELECT * FROM reports WHERE id = 1
|
|
185
|
-
```
|
|
186
|
-
|
|
187
|
-
---
|
|
148
|
+
## Advanced Examples
|
|
188
149
|
|
|
189
|
-
-
|
|
190
|
-
-
|
|
150
|
+
- [Active Record Query Tagging](https://github.com/drexed/cmdx/blob/main/examples/active_record_query_tagging.md)
|
|
151
|
+
- [Paper Trail Whatdunnit](https://github.com/drexed/cmdx/blob/main/examples/paper_trail_whatdunnit.md)
|
data/docs/workflows.md
CHANGED
|
@@ -1,26 +1,14 @@
|
|
|
1
1
|
# Workflows
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
## Table of Contents
|
|
6
|
-
|
|
7
|
-
- [Declarations](#declarations)
|
|
8
|
-
- [Task](#task)
|
|
9
|
-
- [Group](#group)
|
|
10
|
-
- [Conditionals](#conditionals)
|
|
11
|
-
- [Halt Behavior](#halt-behavior)
|
|
12
|
-
- [Task Configuration](#task-configuration)
|
|
13
|
-
- [Group Configuration](#group-configuration)
|
|
14
|
-
- [Nested Workflows](#nested-workflows)
|
|
15
|
-
- [Parallel Execution](#parallel-execution)
|
|
16
|
-
- [Task Generator](#task-generator)
|
|
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.
|
|
17
4
|
|
|
18
5
|
## Declarations
|
|
19
6
|
|
|
20
|
-
Tasks
|
|
7
|
+
Tasks run in declaration order (FIFO), sharing a common context across the pipeline.
|
|
21
8
|
|
|
22
|
-
|
|
23
|
-
|
|
9
|
+
!!! warning
|
|
10
|
+
|
|
11
|
+
Don't define a `work` method in workflows—the module handles execution automatically.
|
|
24
12
|
|
|
25
13
|
### Task
|
|
26
14
|
|
|
@@ -35,15 +23,17 @@ class OnboardingWorkflow < CMDx::Task
|
|
|
35
23
|
end
|
|
36
24
|
```
|
|
37
25
|
|
|
38
|
-
|
|
39
|
-
|
|
26
|
+
!!! tip
|
|
27
|
+
|
|
28
|
+
Execute tasks in parallel via the [cmdx-parallel](https://github.com/drexed/cmdx-parallel) gem.
|
|
40
29
|
|
|
41
30
|
### Group
|
|
42
31
|
|
|
43
|
-
Group related tasks
|
|
32
|
+
Group related tasks to share configuration:
|
|
44
33
|
|
|
45
|
-
|
|
46
|
-
|
|
34
|
+
!!! warning "Important"
|
|
35
|
+
|
|
36
|
+
Settings and conditionals apply to all tasks in the group.
|
|
47
37
|
|
|
48
38
|
```ruby
|
|
49
39
|
class ContentModerationWorkflow < CMDx::Task
|
|
@@ -78,10 +68,10 @@ class OnboardingWorkflow < CMDx::Task
|
|
|
78
68
|
task SendWelcomeEmail, if: :email_configured?, unless: :email_disabled?
|
|
79
69
|
|
|
80
70
|
# Proc
|
|
81
|
-
task SendWelcomeEmail, if: ->
|
|
71
|
+
task SendWelcomeEmail, if: -> { Rails.env.production? && self.class.name.include?("Premium") }
|
|
82
72
|
|
|
83
73
|
# Lambda
|
|
84
|
-
task SendWelcomeEmail, if: proc {
|
|
74
|
+
task SendWelcomeEmail, if: proc { context.features_enabled? }
|
|
85
75
|
|
|
86
76
|
# Class or Module
|
|
87
77
|
task SendWelcomeEmail, unless: ContentAccessCheck
|
|
@@ -95,7 +85,7 @@ class OnboardingWorkflow < CMDx::Task
|
|
|
95
85
|
private
|
|
96
86
|
|
|
97
87
|
def email_configured?
|
|
98
|
-
context.user.email_address
|
|
88
|
+
context.user.email_address == true
|
|
99
89
|
end
|
|
100
90
|
|
|
101
91
|
def email_disabled?
|
|
@@ -106,9 +96,7 @@ end
|
|
|
106
96
|
|
|
107
97
|
## Halt Behavior
|
|
108
98
|
|
|
109
|
-
By default skipped tasks
|
|
110
|
-
This is configurable via global and task level breakpoint settings. Task and group configurations
|
|
111
|
-
can be used together within a workflow.
|
|
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.
|
|
112
100
|
|
|
113
101
|
```ruby
|
|
114
102
|
class AnalyticsWorkflow < CMDx::Task
|
|
@@ -164,7 +152,7 @@ end
|
|
|
164
152
|
|
|
165
153
|
## Nested Workflows
|
|
166
154
|
|
|
167
|
-
|
|
155
|
+
Build hierarchical workflows by composing workflows within workflows:
|
|
168
156
|
|
|
169
157
|
```ruby
|
|
170
158
|
class EmailPreparationWorkflow < CMDx::Task
|
|
@@ -191,10 +179,11 @@ end
|
|
|
191
179
|
|
|
192
180
|
## Parallel Execution
|
|
193
181
|
|
|
194
|
-
|
|
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
|
|
195
185
|
|
|
196
|
-
|
|
197
|
-
> Context cannot be modified during parallel execution. Ensure that all required data is preloaded into the context before parallelization begins.
|
|
186
|
+
Context is read-only during parallel execution. Load all required data beforehand.
|
|
198
187
|
|
|
199
188
|
```ruby
|
|
200
189
|
class SendWelcomeNotifications < CMDx::Task
|
|
@@ -232,10 +221,6 @@ class SendNotifications < CMDx::Task
|
|
|
232
221
|
end
|
|
233
222
|
```
|
|
234
223
|
|
|
235
|
-
|
|
236
|
-
> Use **present tense verbs + pluralized noun** for workflow task names, eg: `SendNotifications`, `DownloadFiles`, `ValidateDocuments`
|
|
237
|
-
|
|
238
|
-
---
|
|
224
|
+
!!! tip
|
|
239
225
|
|
|
240
|
-
|
|
241
|
-
- **Next:** [Tips and Tricks](tips_and_tricks.md)
|
|
226
|
+
Use **present tense verbs + pluralized noun** for workflow task names, eg: `SendNotifications`, `DownloadFiles`, `ValidateDocuments`
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Active Record Query Tagging
|
|
2
|
+
|
|
3
|
+
Add a comment to every query indicating some context to help you track down where that query came from, eg:
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
/*cmdx_task_class:ExportReportTask,cmdx_chain_id:018c2b95-b764-7615*/ SELECT * FROM reports WHERE id = 1
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
### Setup
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
# config/application.rb
|
|
13
|
+
config.active_record.query_log_tags_enabled = true
|
|
14
|
+
config.active_record.query_log_tags += [
|
|
15
|
+
:cmdx_correlation_id,
|
|
16
|
+
:cmdx_chain_id,
|
|
17
|
+
:cmdx_task_class,
|
|
18
|
+
:cmdx_task_id
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
# lib/cmdx_query_tagging_middleware.rb
|
|
22
|
+
class CmdxQueryTaggingMiddleware
|
|
23
|
+
def self.call(task, **options, &)
|
|
24
|
+
ActiveSupport::ExecutionContext.set(
|
|
25
|
+
cmdx_correlation_id: task.result.metadata[:correlation_id],
|
|
26
|
+
cmdx_chain_id: task.chain.id,
|
|
27
|
+
cmdx_task_class: task.class.name,
|
|
28
|
+
cmdx_task_id: task.id,
|
|
29
|
+
&
|
|
30
|
+
)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Usage
|
|
36
|
+
|
|
37
|
+
```ruby
|
|
38
|
+
class MyTask < CMDx::Task
|
|
39
|
+
register :middleware, CmdxQueryTaggingMiddleware
|
|
40
|
+
|
|
41
|
+
def work
|
|
42
|
+
# Do work...
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
end
|
|
46
|
+
```
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Paper Trail Whatdunnit
|
|
2
|
+
|
|
3
|
+
Tag paper trail version records with which service made a change with a custom `whatdunnit` attribute.
|
|
4
|
+
|
|
5
|
+
<https://github.com/paper-trail-gem/paper_trail?tab=readme-ov-file#4c-storing-metadata>
|
|
6
|
+
|
|
7
|
+
### Setup
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
# lib/cmdx_paper_trail_middleware.rb
|
|
11
|
+
class CmdxPaperTrailMiddleware
|
|
12
|
+
def self.call(task, **options, &)
|
|
13
|
+
# This makes sure to reset the whatdunnit value to the previous
|
|
14
|
+
# value for nested task calls
|
|
15
|
+
|
|
16
|
+
begin
|
|
17
|
+
PaperTrail.request.controller_info ||= {}
|
|
18
|
+
old_whatdunnit = PaperTrail.request.controller_info[:whatdunnit]
|
|
19
|
+
PaperTrail.request.controller_info[:whatdunnit] = task.class.name
|
|
20
|
+
yield
|
|
21
|
+
ensure
|
|
22
|
+
PaperTrail.request.controller_info[:whatdunnit] = old_whatdunnit
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Usage
|
|
29
|
+
|
|
30
|
+
```ruby
|
|
31
|
+
class MyTask < CMDx::Task
|
|
32
|
+
register :middleware, CmdxPaperTrailMiddleware
|
|
33
|
+
|
|
34
|
+
def work
|
|
35
|
+
# Do work...
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
end
|
|
39
|
+
```
|
data/lib/cmdx/attribute.rb
CHANGED
|
@@ -66,7 +66,7 @@ module CMDx
|
|
|
66
66
|
if names.none?
|
|
67
67
|
raise ArgumentError, "no attributes given"
|
|
68
68
|
elsif (names.size > 1) && options.key?(:as)
|
|
69
|
-
raise ArgumentError, ":as option only supports one attribute per definition"
|
|
69
|
+
raise ArgumentError, "the :as option only supports one attribute per definition"
|
|
70
70
|
end
|
|
71
71
|
|
|
72
72
|
names.filter_map { |name| new(name, **options, &) }
|
|
@@ -213,10 +213,11 @@ module CMDx
|
|
|
213
213
|
# @raise [RuntimeError] When the method name is already defined on the task
|
|
214
214
|
def define_and_verify
|
|
215
215
|
if task.respond_to?(method_name, true)
|
|
216
|
-
raise <<~MESSAGE
|
|
217
|
-
#{task.class.name}
|
|
218
|
-
|
|
219
|
-
|
|
216
|
+
raise <<~MESSAGE
|
|
217
|
+
The method #{method_name.inspect} is already defined on the #{task.class.name} task.
|
|
218
|
+
This may be due conflicts with one of the task's user defined or internal methods/attributes.
|
|
219
|
+
|
|
220
|
+
Use :as, :prefix, and/or :suffix attribute options to avoid conflicts with existing methods.
|
|
220
221
|
MESSAGE
|
|
221
222
|
end
|
|
222
223
|
|
data/lib/cmdx/attribute_value.rb
CHANGED
|
@@ -51,9 +51,10 @@ module CMDx
|
|
|
51
51
|
return if errors.for?(method_name)
|
|
52
52
|
|
|
53
53
|
coerced_value = coerce_value(derived_value)
|
|
54
|
+
transformed_value = transform_value(coerced_value)
|
|
54
55
|
return if errors.for?(method_name)
|
|
55
56
|
|
|
56
|
-
attributes[method_name] =
|
|
57
|
+
attributes[method_name] = transformed_value
|
|
57
58
|
end
|
|
58
59
|
|
|
59
60
|
# Validates the current attribute value against configured validators.
|
|
@@ -113,7 +114,7 @@ module CMDx
|
|
|
113
114
|
#
|
|
114
115
|
# @example
|
|
115
116
|
# # Default can be symbol, proc, or direct value
|
|
116
|
-
#
|
|
117
|
+
# -> { rand(100) } # => 23
|
|
117
118
|
def default_value
|
|
118
119
|
default = options[:default]
|
|
119
120
|
|
|
@@ -138,7 +139,7 @@ module CMDx
|
|
|
138
139
|
#
|
|
139
140
|
# @example
|
|
140
141
|
# # Derives from hash key, method call, or proc execution
|
|
141
|
-
#
|
|
142
|
+
# context.user_id # => 42
|
|
142
143
|
def derive_value(source_value)
|
|
143
144
|
derived_value =
|
|
144
145
|
case source_value
|
|
@@ -154,9 +155,29 @@ module CMDx
|
|
|
154
155
|
nil
|
|
155
156
|
end
|
|
156
157
|
|
|
158
|
+
# Transforms the derived value using the transform option.
|
|
159
|
+
#
|
|
160
|
+
# @param derived_value [Object] The value to transform
|
|
161
|
+
#
|
|
162
|
+
# @return [Object, nil] The transformed value or nil if transformation failed
|
|
163
|
+
#
|
|
164
|
+
# @example
|
|
165
|
+
# :downcase # => "hello"
|
|
166
|
+
def transform_value(derived_value)
|
|
167
|
+
transform = options[:transform]
|
|
168
|
+
|
|
169
|
+
if transform.is_a?(Symbol) && derived_value.respond_to?(transform, true)
|
|
170
|
+
derived_value.send(transform)
|
|
171
|
+
elsif transform.respond_to?(:call)
|
|
172
|
+
transform.call(derived_value)
|
|
173
|
+
else
|
|
174
|
+
derived_value
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
157
178
|
# Coerces the derived value to the expected type(s) using the coercion registry.
|
|
158
179
|
#
|
|
159
|
-
# @param
|
|
180
|
+
# @param transformed_value [Object] The value to coerce
|
|
160
181
|
#
|
|
161
182
|
# @return [Object, nil] The coerced value or nil if coercion failed
|
|
162
183
|
#
|
|
@@ -165,14 +186,14 @@ module CMDx
|
|
|
165
186
|
# @example
|
|
166
187
|
# # Coerces "42" to Integer, "true" to Boolean, etc.
|
|
167
188
|
# coerce_value("42") # => 42
|
|
168
|
-
def coerce_value(
|
|
169
|
-
return
|
|
189
|
+
def coerce_value(transformed_value)
|
|
190
|
+
return transformed_value if types.empty?
|
|
170
191
|
|
|
171
192
|
registry = task.class.settings[:coercions]
|
|
172
|
-
last_idx =
|
|
193
|
+
last_idx = types.size - 1
|
|
173
194
|
|
|
174
|
-
|
|
175
|
-
break registry.coerce(type, task,
|
|
195
|
+
types.find.with_index do |type, i|
|
|
196
|
+
break registry.coerce(type, task, transformed_value, options)
|
|
176
197
|
rescue CoercionError => e
|
|
177
198
|
next if i != last_idx
|
|
178
199
|
|
|
@@ -180,7 +201,7 @@ module CMDx
|
|
|
180
201
|
if last_idx.zero?
|
|
181
202
|
e.message
|
|
182
203
|
else
|
|
183
|
-
tl =
|
|
204
|
+
tl = types.map { |t| Locale.t("cmdx.types.#{t}") }.join(", ")
|
|
184
205
|
Locale.t("cmdx.coercions.into_any", types: tl)
|
|
185
206
|
end
|
|
186
207
|
|
|
@@ -95,9 +95,19 @@ module CMDx
|
|
|
95
95
|
raise TypeError, "unknown callback type #{type.inspect}" unless TYPES.include?(type)
|
|
96
96
|
|
|
97
97
|
Array(registry[type]).each do |callables, options|
|
|
98
|
-
next unless Utils::Condition.evaluate(task, options
|
|
98
|
+
next unless Utils::Condition.evaluate(task, options)
|
|
99
99
|
|
|
100
|
-
Array(callables).each
|
|
100
|
+
Array(callables).each do |callable|
|
|
101
|
+
if callable.is_a?(Symbol)
|
|
102
|
+
task.send(callable)
|
|
103
|
+
elsif callable.is_a?(Proc)
|
|
104
|
+
task.instance_exec(&callable)
|
|
105
|
+
elsif callable.respond_to?(:call)
|
|
106
|
+
callable.call(task)
|
|
107
|
+
else
|
|
108
|
+
raise "cannot invoke #{callable}"
|
|
109
|
+
end
|
|
110
|
+
end
|
|
101
111
|
end
|
|
102
112
|
end
|
|
103
113
|
|
data/lib/cmdx/coercions/hash.rb
CHANGED
data/lib/cmdx/configuration.rb
CHANGED
|
@@ -3,13 +3,14 @@
|
|
|
3
3
|
module CMDx
|
|
4
4
|
|
|
5
5
|
# Configuration class that manages global settings for CMDx including middlewares,
|
|
6
|
-
# callbacks, coercions, validators, breakpoints, and logging.
|
|
6
|
+
# callbacks, coercions, validators, breakpoints, backtraces, and logging.
|
|
7
7
|
class Configuration
|
|
8
8
|
|
|
9
9
|
DEFAULT_BREAKPOINTS = %w[failed].freeze
|
|
10
10
|
|
|
11
11
|
attr_accessor :middlewares, :callbacks, :coercions, :validators,
|
|
12
|
-
:task_breakpoints, :workflow_breakpoints, :logger
|
|
12
|
+
:task_breakpoints, :workflow_breakpoints, :logger,
|
|
13
|
+
:backtrace, :backtrace_cleaner, :exception_handler
|
|
13
14
|
|
|
14
15
|
# Initializes a new Configuration instance with default values.
|
|
15
16
|
#
|
|
@@ -31,6 +32,10 @@ module CMDx
|
|
|
31
32
|
@task_breakpoints = DEFAULT_BREAKPOINTS
|
|
32
33
|
@workflow_breakpoints = DEFAULT_BREAKPOINTS
|
|
33
34
|
|
|
35
|
+
@backtrace = false
|
|
36
|
+
@backtrace_cleaner = nil
|
|
37
|
+
@exception_handler = nil
|
|
38
|
+
|
|
34
39
|
@logger = Logger.new(
|
|
35
40
|
$stdout,
|
|
36
41
|
progname: "cmdx",
|
|
@@ -55,6 +60,9 @@ module CMDx
|
|
|
55
60
|
validators: @validators,
|
|
56
61
|
task_breakpoints: @task_breakpoints,
|
|
57
62
|
workflow_breakpoints: @workflow_breakpoints,
|
|
63
|
+
backtrace: @backtrace,
|
|
64
|
+
backtrace_cleaner: @backtrace_cleaner,
|
|
65
|
+
exception_handler: @exception_handler,
|
|
58
66
|
logger: @logger
|
|
59
67
|
}
|
|
60
68
|
end
|
data/lib/cmdx/deprecator.rb
CHANGED
|
@@ -44,15 +44,15 @@ module CMDx
|
|
|
44
44
|
# settings(deprecate: :warn)
|
|
45
45
|
# end
|
|
46
46
|
#
|
|
47
|
-
# MyTask.new # => [MyTask] DEPRECATED: migrate to replacement or discontinue use
|
|
47
|
+
# MyTask.new # => [MyTask] DEPRECATED: migrate to a replacement or discontinue use
|
|
48
48
|
def restrict(task)
|
|
49
49
|
type = EVAL.call(task, task.class.settings[:deprecate])
|
|
50
50
|
|
|
51
51
|
case type
|
|
52
52
|
when NilClass, FalseClass # Do nothing
|
|
53
53
|
when TrueClass, /raise/ then raise DeprecationError, "#{task.class.name} usage prohibited"
|
|
54
|
-
when /log/ then task.logger.warn { "DEPRECATED: migrate to replacement or discontinue use" }
|
|
55
|
-
when /warn/ then warn("[#{task.class.name}] DEPRECATED: migrate to replacement or discontinue use", category: :deprecated)
|
|
54
|
+
when /log/ then task.logger.warn { "DEPRECATED: migrate to a replacement or discontinue use" }
|
|
55
|
+
when /warn/ then warn("[#{task.class.name}] DEPRECATED: migrate to a replacement or discontinue use", category: :deprecated)
|
|
56
56
|
else raise "unknown deprecation type #{type.inspect}"
|
|
57
57
|
end
|
|
58
58
|
end
|