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/.yard-lint.yml
DELETED
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
# YARD-Lint Configuration
|
|
2
|
-
# See https://github.com/mensfeld/yard-lint for documentation
|
|
3
|
-
|
|
4
|
-
# Global settings for all validators
|
|
5
|
-
AllValidators:
|
|
6
|
-
# YARD command-line options (applied to all validators by default)
|
|
7
|
-
YardOptions:
|
|
8
|
-
- --private
|
|
9
|
-
- --protected
|
|
10
|
-
|
|
11
|
-
# Global file exclusion patterns
|
|
12
|
-
Exclude:
|
|
13
|
-
- '\.git'
|
|
14
|
-
- 'vendor/**/*'
|
|
15
|
-
- 'node_modules/**/*'
|
|
16
|
-
- 'spec/**/*'
|
|
17
|
-
- 'test/**/*'
|
|
18
|
-
|
|
19
|
-
# Exit code behavior (error, warning, convention, never)
|
|
20
|
-
FailOnSeverity: warning
|
|
21
|
-
|
|
22
|
-
# Minimum documentation coverage percentage (0-100)
|
|
23
|
-
# Fails if coverage is below this threshold
|
|
24
|
-
# MinCoverage: 80.0
|
|
25
|
-
|
|
26
|
-
# Diff mode settings
|
|
27
|
-
DiffMode:
|
|
28
|
-
# Default base ref for --diff (auto-detects main/master if not specified)
|
|
29
|
-
DefaultBaseRef: ~
|
|
30
|
-
|
|
31
|
-
# Documentation validators
|
|
32
|
-
Documentation/UndocumentedObjects:
|
|
33
|
-
Description: 'Checks for classes, modules, and methods without documentation.'
|
|
34
|
-
Enabled: false
|
|
35
|
-
Severity: warning
|
|
36
|
-
ExcludedMethods:
|
|
37
|
-
- 'initialize/0' # Exclude parameter-less initialize
|
|
38
|
-
- '/^_/' # Exclude private methods (by convention)
|
|
39
|
-
|
|
40
|
-
Documentation/UndocumentedMethodArguments:
|
|
41
|
-
Description: 'Checks for method parameters without @param tags.'
|
|
42
|
-
Enabled: true
|
|
43
|
-
Severity: warning
|
|
44
|
-
|
|
45
|
-
Documentation/UndocumentedBooleanMethods:
|
|
46
|
-
Description: 'Checks that question mark methods document their boolean return.'
|
|
47
|
-
Enabled: true
|
|
48
|
-
Severity: warning
|
|
49
|
-
|
|
50
|
-
Documentation/UndocumentedOptions:
|
|
51
|
-
Description: 'Detects methods with options hash parameters but no @option tags.'
|
|
52
|
-
Enabled: true
|
|
53
|
-
Severity: warning
|
|
54
|
-
|
|
55
|
-
Documentation/MarkdownSyntax:
|
|
56
|
-
Description: 'Detects common markdown syntax errors in documentation.'
|
|
57
|
-
Enabled: true
|
|
58
|
-
Severity: warning
|
|
59
|
-
|
|
60
|
-
# Tags validators
|
|
61
|
-
Tags/Order:
|
|
62
|
-
Description: 'Enforces consistent ordering of YARD tags.'
|
|
63
|
-
Enabled: true
|
|
64
|
-
Severity: convention
|
|
65
|
-
EnforcedOrder:
|
|
66
|
-
- param
|
|
67
|
-
- option
|
|
68
|
-
- return
|
|
69
|
-
- raise
|
|
70
|
-
- example
|
|
71
|
-
|
|
72
|
-
Tags/InvalidTypes:
|
|
73
|
-
Description: 'Validates type definitions in @param, @return, @option tags.'
|
|
74
|
-
Enabled: true
|
|
75
|
-
Severity: warning
|
|
76
|
-
ValidatedTags:
|
|
77
|
-
- param
|
|
78
|
-
- option
|
|
79
|
-
- return
|
|
80
|
-
|
|
81
|
-
Tags/TypeSyntax:
|
|
82
|
-
Description: 'Validates YARD type syntax using YARD parser.'
|
|
83
|
-
Enabled: true
|
|
84
|
-
Severity: warning
|
|
85
|
-
ValidatedTags:
|
|
86
|
-
- param
|
|
87
|
-
- option
|
|
88
|
-
- return
|
|
89
|
-
- yieldreturn
|
|
90
|
-
|
|
91
|
-
Tags/MeaninglessTag:
|
|
92
|
-
Description: 'Detects @param/@option tags on classes, modules, or constants.'
|
|
93
|
-
Enabled: true
|
|
94
|
-
Severity: warning
|
|
95
|
-
CheckedTags:
|
|
96
|
-
- param
|
|
97
|
-
- option
|
|
98
|
-
InvalidObjectTypes:
|
|
99
|
-
- class
|
|
100
|
-
- module
|
|
101
|
-
- constant
|
|
102
|
-
|
|
103
|
-
Tags/CollectionType:
|
|
104
|
-
Description: 'Validates Hash collection syntax consistency.'
|
|
105
|
-
Enabled: true
|
|
106
|
-
Severity: convention
|
|
107
|
-
EnforcedStyle: long # 'long' for Hash{K => V} (YARD standard), 'short' for {K => V}
|
|
108
|
-
ValidatedTags:
|
|
109
|
-
- param
|
|
110
|
-
- option
|
|
111
|
-
- return
|
|
112
|
-
- yieldreturn
|
|
113
|
-
|
|
114
|
-
Tags/TagTypePosition:
|
|
115
|
-
Description: 'Validates type annotation position in tags.'
|
|
116
|
-
Enabled: true
|
|
117
|
-
Severity: convention
|
|
118
|
-
CheckedTags:
|
|
119
|
-
- param
|
|
120
|
-
- option
|
|
121
|
-
# EnforcedStyle: 'type_after_name' (YARD standard: @param name [Type])
|
|
122
|
-
# or 'type_first' (@param [Type] name)
|
|
123
|
-
EnforcedStyle: type_after_name
|
|
124
|
-
|
|
125
|
-
Tags/ApiTags:
|
|
126
|
-
Description: 'Enforces @api tags on public objects.'
|
|
127
|
-
Enabled: false # Opt-in validator
|
|
128
|
-
Severity: warning
|
|
129
|
-
AllowedApis:
|
|
130
|
-
- public
|
|
131
|
-
- private
|
|
132
|
-
- internal
|
|
133
|
-
|
|
134
|
-
Tags/OptionTags:
|
|
135
|
-
Description: 'Requires @option tags for methods with options parameters.'
|
|
136
|
-
Enabled: true
|
|
137
|
-
Severity: warning
|
|
138
|
-
|
|
139
|
-
# Warnings validators - catches YARD parser errors
|
|
140
|
-
Warnings/UnknownTag:
|
|
141
|
-
Description: 'Detects unknown YARD tags.'
|
|
142
|
-
Enabled: false
|
|
143
|
-
Severity: error
|
|
144
|
-
|
|
145
|
-
Warnings/UnknownDirective:
|
|
146
|
-
Description: 'Detects unknown YARD directives.'
|
|
147
|
-
Enabled: true
|
|
148
|
-
Severity: error
|
|
149
|
-
|
|
150
|
-
Warnings/InvalidTagFormat:
|
|
151
|
-
Description: 'Detects malformed tag syntax.'
|
|
152
|
-
Enabled: true
|
|
153
|
-
Severity: error
|
|
154
|
-
|
|
155
|
-
Warnings/InvalidDirectiveFormat:
|
|
156
|
-
Description: 'Detects malformed directive syntax.'
|
|
157
|
-
Enabled: true
|
|
158
|
-
Severity: error
|
|
159
|
-
|
|
160
|
-
Warnings/DuplicatedParameterName:
|
|
161
|
-
Description: 'Detects duplicate @param tags.'
|
|
162
|
-
Enabled: true
|
|
163
|
-
Severity: error
|
|
164
|
-
|
|
165
|
-
Warnings/UnknownParameterName:
|
|
166
|
-
Description: 'Detects @param tags for non-existent parameters.'
|
|
167
|
-
Enabled: true
|
|
168
|
-
Severity: error
|
|
169
|
-
|
|
170
|
-
# Semantic validators
|
|
171
|
-
Semantic/AbstractMethods:
|
|
172
|
-
Description: 'Ensures @abstract methods do not have real implementations.'
|
|
173
|
-
Enabled: true
|
|
174
|
-
Severity: warning
|
data/.yardopts
DELETED
data/docs/.DS_Store
DELETED
|
Binary file
|
data/docs/assets/favicon.ico
DELETED
|
Binary file
|
data/docs/assets/favicon.svg
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="1000" height="1000"><g clip-path="url(#SvgjsClipPath1038)"><rect width="1000" height="1000" fill="#ffffff"></rect><g transform="matrix(6.235191420376605,0,0,6.235191420376605,175.46452176081812,150)"><svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="104.098" height="112.266"><svg width="104.098" height="112.266" viewBox="0 0 60 64.708" xmlns="http://www.w3.org/2000/svg"><path d="M29.907 17.723 26.4 23.17 13.384 3.323h3.507l9.508 14.77 1.938-3.139L18.737 0H7.291L26.4 29.262 45.045 0h-3.97L30 17.54zM9.23 61.293H6.091l18.646-29.447L4.43.093H.46l20.308 31.846L0 64.708l10.985-.092 39.138-61.2h3.323L31.57 37.754l13.662 21.415h3.97L35.445 37.754 59.537.093H48.37zm29.63-23.262 15.047 23.262H43.384l-13.662-20.77-15.508 24.093h3.97l11.63-18 11.723 18H60L41.17 35.354l-.278-.461z" fill="#000"></path></svg></svg></g></g><defs><clipPath id="SvgjsClipPath1038"><rect width="1000" height="1000" x="0" y="0" rx="150" ry="150"></rect></clipPath></defs></svg>
|
|
@@ -1,155 +0,0 @@
|
|
|
1
|
-
# Attributes - Coercions
|
|
2
|
-
|
|
3
|
-
Automatically convert inputs to expected types. Coercions handle everything from simple string-to-integer conversions to JSON parsing.
|
|
4
|
-
|
|
5
|
-
See [Global Configuration](../getting_started.md#coercions) for custom coercion setup.
|
|
6
|
-
|
|
7
|
-
## Usage
|
|
8
|
-
|
|
9
|
-
Define attribute types to enable automatic coercion:
|
|
10
|
-
|
|
11
|
-
```ruby
|
|
12
|
-
class ParseMetrics < CMDx::Task
|
|
13
|
-
# Coerce into a symbol
|
|
14
|
-
attribute :measurement_type, type: :symbol
|
|
15
|
-
|
|
16
|
-
# Coerce into a rational fallback to big decimal
|
|
17
|
-
attribute :value, type: [:rational, :big_decimal]
|
|
18
|
-
|
|
19
|
-
# Coerce with options
|
|
20
|
-
attribute :recorded_at, type: :date, strptime: "%m-%d-%Y"
|
|
21
|
-
|
|
22
|
-
def work
|
|
23
|
-
measurement_type #=> :temperature
|
|
24
|
-
recorded_at #=> <Date 2024-01-23>
|
|
25
|
-
value #=> 98.6 (Float)
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
ParseMetrics.execute(
|
|
30
|
-
measurement_type: "temperature",
|
|
31
|
-
recorded_at: "01-23-2020",
|
|
32
|
-
value: "98.6"
|
|
33
|
-
)
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
!!! tip
|
|
37
|
-
|
|
38
|
-
Specify multiple coercion types for attributes that could be a variety of value formats. CMDx attempts each type in order until one succeeds.
|
|
39
|
-
|
|
40
|
-
## Built-in Coercions
|
|
41
|
-
|
|
42
|
-
| Type | Options | Description | Examples |
|
|
43
|
-
|------|---------|-------------|----------|
|
|
44
|
-
| `:array` | | Array conversion with JSON support | `"val"` → `["val"]`<br>`"[1,2,3]"` → `[1, 2, 3]` |
|
|
45
|
-
| `:big_decimal` | `:precision` | High-precision decimal | `"123.456"` → `BigDecimal("123.456")` |
|
|
46
|
-
| `:boolean` | | Boolean with text patterns | `"yes"` → `true`, `"no"` → `false` |
|
|
47
|
-
| `:complex` | | Complex numbers | `"1+2i"` → `Complex(1, 2)` |
|
|
48
|
-
| `:date` | `:strptime` | Date objects | `"2024-01-23"` → `Date.new(2024, 1, 23)` |
|
|
49
|
-
| `:datetime` | `:strptime` | DateTime objects | `"2024-01-23 10:30"` → `DateTime.new(2024, 1, 23, 10, 30)` |
|
|
50
|
-
| `:float` | | Floating-point numbers | `"123.45"` → `123.45` |
|
|
51
|
-
| `:hash` | | Hash conversion with JSON support | `'{"a":1}'` → `{"a" => 1}` |
|
|
52
|
-
| `:integer` | | Integer with hex/octal support | `"0xFF"` → `255`, `"077"` → `63` |
|
|
53
|
-
| `:rational` | | Rational numbers | `"1/2"` → `Rational(1, 2)` |
|
|
54
|
-
| `:string` | | String conversion | `123` → `"123"` |
|
|
55
|
-
| `:symbol` | | Symbol conversion | `"abc"` → `:abc` |
|
|
56
|
-
| `:time` | `:strptime` | Time objects | `"10:30:00"` → `Time.new(2024, 1, 23, 10, 30)` |
|
|
57
|
-
|
|
58
|
-
## Declarations
|
|
59
|
-
|
|
60
|
-
!!! warning "Important"
|
|
61
|
-
|
|
62
|
-
Custom coercions must raise `CMDx::CoercionError` with a descriptive message.
|
|
63
|
-
|
|
64
|
-
### Proc or Lambda
|
|
65
|
-
|
|
66
|
-
Use anonymous functions for simple coercion logic:
|
|
67
|
-
|
|
68
|
-
```ruby
|
|
69
|
-
class TransformCoordinates < CMDx::Task
|
|
70
|
-
# Proc
|
|
71
|
-
register :callback, :geolocation, proc do |value, options = {}|
|
|
72
|
-
begin
|
|
73
|
-
Geolocation(value)
|
|
74
|
-
rescue StandardError
|
|
75
|
-
raise CMDx::CoercionError, "could not convert into a geolocation"
|
|
76
|
-
end
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
# Lambda
|
|
80
|
-
register :callback, :geolocation, ->(value, options = {}) {
|
|
81
|
-
begin
|
|
82
|
-
Geolocation(value)
|
|
83
|
-
rescue StandardError
|
|
84
|
-
raise CMDx::CoercionError, "could not convert into a geolocation"
|
|
85
|
-
end
|
|
86
|
-
}
|
|
87
|
-
end
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
### Class or Module
|
|
91
|
-
|
|
92
|
-
Register custom coercion logic for specialized type handling:
|
|
93
|
-
|
|
94
|
-
```ruby
|
|
95
|
-
class GeolocationCoercion
|
|
96
|
-
def self.call(value, options = {})
|
|
97
|
-
Geolocation(value)
|
|
98
|
-
rescue StandardError
|
|
99
|
-
raise CMDx::CoercionError, "could not convert into a geolocation"
|
|
100
|
-
end
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
class TransformCoordinates < CMDx::Task
|
|
104
|
-
register :coercion, :geolocation, GeolocationCoercion
|
|
105
|
-
|
|
106
|
-
attribute :latitude, type: :geolocation
|
|
107
|
-
end
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
## Removals
|
|
111
|
-
|
|
112
|
-
Remove unwanted coercions:
|
|
113
|
-
|
|
114
|
-
!!! warning
|
|
115
|
-
|
|
116
|
-
Each `deregister` call removes one coercion. Use multiple calls for batch removals.
|
|
117
|
-
|
|
118
|
-
```ruby
|
|
119
|
-
class TransformCoordinates < CMDx::Task
|
|
120
|
-
deregister :coercion, :geolocation
|
|
121
|
-
end
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
## Error Handling
|
|
125
|
-
|
|
126
|
-
Coercion failures provide detailed error information including attribute paths, attempted types, and specific failure reasons:
|
|
127
|
-
|
|
128
|
-
```ruby
|
|
129
|
-
class AnalyzePerformance < CMDx::Task
|
|
130
|
-
attribute :iterations, type: :integer
|
|
131
|
-
attribute :score, type: [:float, :big_decimal]
|
|
132
|
-
|
|
133
|
-
def work
|
|
134
|
-
# Your logic here...
|
|
135
|
-
end
|
|
136
|
-
end
|
|
137
|
-
|
|
138
|
-
result = AnalyzePerformance.execute(
|
|
139
|
-
iterations: "not-a-number",
|
|
140
|
-
score: "invalid-float"
|
|
141
|
-
)
|
|
142
|
-
|
|
143
|
-
result.state #=> "interrupted"
|
|
144
|
-
result.status #=> "failed"
|
|
145
|
-
result.reason #=> "Invalid"
|
|
146
|
-
result.metadata #=> {
|
|
147
|
-
# errors: {
|
|
148
|
-
# full_message: "iterations could not coerce into an integer. score could not coerce into one of: float, big_decimal.",
|
|
149
|
-
# messages: {
|
|
150
|
-
# iterations: ["could not coerce into an integer"],
|
|
151
|
-
# score: ["could not coerce into one of: float, big_decimal"]
|
|
152
|
-
# }
|
|
153
|
-
# }
|
|
154
|
-
# }
|
|
155
|
-
```
|
data/docs/attributes/defaults.md
DELETED
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
# Attributes - Defaults
|
|
2
|
-
|
|
3
|
-
Provide fallback values for optional attributes. Defaults kick in when values aren't provided or are `nil`.
|
|
4
|
-
|
|
5
|
-
## Declarations
|
|
6
|
-
|
|
7
|
-
Defaults work seamlessly with coercions, validations, and nested attributes:
|
|
8
|
-
|
|
9
|
-
### Static Values
|
|
10
|
-
|
|
11
|
-
```ruby
|
|
12
|
-
class OptimizeDatabase < CMDx::Task
|
|
13
|
-
attribute :strategy, default: :incremental
|
|
14
|
-
attribute :level, default: "basic"
|
|
15
|
-
attribute :notify_admin, default: true
|
|
16
|
-
attribute :timeout_minutes, default: 30
|
|
17
|
-
attribute :indexes, default: []
|
|
18
|
-
attribute :options, default: {}
|
|
19
|
-
|
|
20
|
-
def work
|
|
21
|
-
strategy #=> :incremental
|
|
22
|
-
level #=> "basic"
|
|
23
|
-
notify_admin #=> true
|
|
24
|
-
timeout_minutes #=> 30
|
|
25
|
-
indexes #=> []
|
|
26
|
-
options #=> {}
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
### Symbol References
|
|
32
|
-
|
|
33
|
-
Reference instance methods by symbol for dynamic default values:
|
|
34
|
-
|
|
35
|
-
```ruby
|
|
36
|
-
class ProcessAnalytics < CMDx::Task
|
|
37
|
-
attribute :granularity, default: :default_granularity
|
|
38
|
-
|
|
39
|
-
def work
|
|
40
|
-
# Your logic here...
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
private
|
|
44
|
-
|
|
45
|
-
def default_granularity
|
|
46
|
-
Current.user.premium? ? "hourly" : "daily"
|
|
47
|
-
end
|
|
48
|
-
end
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
### Proc or Lambda
|
|
52
|
-
|
|
53
|
-
Use anonymous functions for dynamic default values:
|
|
54
|
-
|
|
55
|
-
```ruby
|
|
56
|
-
class CacheContent < CMDx::Task
|
|
57
|
-
# Proc
|
|
58
|
-
attribute :expire_hours, default: proc { Current.tenant.cache_duration || 24 }
|
|
59
|
-
|
|
60
|
-
# Lambda
|
|
61
|
-
attribute :compression, default: -> { Current.tenant.premium? ? "gzip" : "none" }
|
|
62
|
-
end
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
## Coercions and Validations
|
|
66
|
-
|
|
67
|
-
Defaults follow the same coercion and validation rules as provided values:
|
|
68
|
-
|
|
69
|
-
```ruby
|
|
70
|
-
class ScheduleBackup < CMDx::Task
|
|
71
|
-
# Coercions
|
|
72
|
-
attribute :retention_days, default: "7", type: :integer
|
|
73
|
-
|
|
74
|
-
# Validations
|
|
75
|
-
optional :frequency, default: "daily", inclusion: { in: %w[hourly daily weekly monthly] }
|
|
76
|
-
end
|
|
77
|
-
```
|
|
@@ -1,283 +0,0 @@
|
|
|
1
|
-
# Attributes - Definitions
|
|
2
|
-
|
|
3
|
-
Attributes define your task's interface with automatic validation, type coercion, and accessor generation. They're the contract between callers and your business logic.
|
|
4
|
-
|
|
5
|
-
## Declarations
|
|
6
|
-
|
|
7
|
-
!!! warning "Important"
|
|
8
|
-
|
|
9
|
-
Attributes are order-dependent, so if you need to reference them as a source or use them in conditions, make sure they’re defined in the correct order.
|
|
10
|
-
|
|
11
|
-
!!! tip
|
|
12
|
-
|
|
13
|
-
Prefer using the `required` and `optional` alias for `attributes` for brevity and to clearly signal intent.
|
|
14
|
-
|
|
15
|
-
### Optional
|
|
16
|
-
|
|
17
|
-
Optional attributes return `nil` when not provided.
|
|
18
|
-
|
|
19
|
-
```ruby
|
|
20
|
-
class ScheduleEvent < CMDx::Task
|
|
21
|
-
attribute :title
|
|
22
|
-
attributes :duration, :location
|
|
23
|
-
|
|
24
|
-
# Alias for attributes (preferred)
|
|
25
|
-
optional :description
|
|
26
|
-
optional :visibility, :attendees
|
|
27
|
-
|
|
28
|
-
def work
|
|
29
|
-
title #=> "Team Standup"
|
|
30
|
-
duration #=> 30
|
|
31
|
-
location #=> nil
|
|
32
|
-
description #=> nil
|
|
33
|
-
visibility #=> nil
|
|
34
|
-
attendees #=> ["alice@company.com", "bob@company.com"]
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
# Attributes passed as keyword arguments
|
|
39
|
-
ScheduleEvent.execute(
|
|
40
|
-
title: "Team Standup",
|
|
41
|
-
duration: 30,
|
|
42
|
-
attendees: ["alice@company.com", "bob@company.com"]
|
|
43
|
-
)
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
### Required
|
|
47
|
-
|
|
48
|
-
Required attributes must be provided in call arguments or task execution will fail.
|
|
49
|
-
|
|
50
|
-
```ruby
|
|
51
|
-
class PublishArticle < CMDx::Task
|
|
52
|
-
attribute :title, required: true
|
|
53
|
-
attributes :content, :author_id, required: true
|
|
54
|
-
|
|
55
|
-
# Alias for attributes => required: true (preferred)
|
|
56
|
-
required :category
|
|
57
|
-
required :status, :tags
|
|
58
|
-
|
|
59
|
-
# Conditionally required
|
|
60
|
-
required :publisher, if: :magazine?
|
|
61
|
-
attribute :approver, required: true, unless: proc { status == :published }
|
|
62
|
-
|
|
63
|
-
def work
|
|
64
|
-
title #=> "Getting Started with Ruby"
|
|
65
|
-
content #=> "This is a comprehensive guide..."
|
|
66
|
-
author_id #=> 42
|
|
67
|
-
category #=> "programming"
|
|
68
|
-
status #=> :published
|
|
69
|
-
tags #=> ["ruby", "beginner"]
|
|
70
|
-
publisher #=> "Eastbay"
|
|
71
|
-
approver #=> #<Editor ...>
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
private
|
|
75
|
-
|
|
76
|
-
def magazine?
|
|
77
|
-
context.title.ends_with?("[M]")
|
|
78
|
-
end
|
|
79
|
-
end
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
!!! note
|
|
83
|
-
|
|
84
|
-
When a required attribute's condition evaluates to `false`, the attribute behaves as optional. All other attribute features such as coercions, validations, defaults, and transformations still apply normally.
|
|
85
|
-
|
|
86
|
-
## Sources
|
|
87
|
-
|
|
88
|
-
Attributes read from any accessible object—not just context. Use sources to pull data from models, services, or any callable:
|
|
89
|
-
|
|
90
|
-
### Context
|
|
91
|
-
|
|
92
|
-
```ruby
|
|
93
|
-
class BackupDatabase < CMDx::Task
|
|
94
|
-
# Default source is :context
|
|
95
|
-
required :database_name
|
|
96
|
-
optional :compression_level
|
|
97
|
-
|
|
98
|
-
# Explicitly specify context source
|
|
99
|
-
attribute :backup_path, source: :context
|
|
100
|
-
|
|
101
|
-
def work
|
|
102
|
-
database_name #=> context.database_name
|
|
103
|
-
backup_path #=> context.backup_path
|
|
104
|
-
compression_level #=> context.compression_level
|
|
105
|
-
end
|
|
106
|
-
end
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
### Symbol References
|
|
110
|
-
|
|
111
|
-
Reference instance methods by symbol for dynamic source values:
|
|
112
|
-
|
|
113
|
-
```ruby
|
|
114
|
-
class BackupDatabase < CMDx::Task
|
|
115
|
-
attributes :host, :credentials, source: :database_config
|
|
116
|
-
|
|
117
|
-
# Access from declared attributes
|
|
118
|
-
attribute :connection_string, source: :credentials
|
|
119
|
-
|
|
120
|
-
def work
|
|
121
|
-
# Your logic here...
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
private
|
|
125
|
-
|
|
126
|
-
def database_config
|
|
127
|
-
@database_config ||= DatabaseConfig.find(context.database_name)
|
|
128
|
-
end
|
|
129
|
-
end
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
### Proc or Lambda
|
|
133
|
-
|
|
134
|
-
Use anonymous functions for dynamic source values:
|
|
135
|
-
|
|
136
|
-
```ruby
|
|
137
|
-
class BackupDatabase < CMDx::Task
|
|
138
|
-
# Proc
|
|
139
|
-
attribute :timestamp, source: proc { Time.current }
|
|
140
|
-
|
|
141
|
-
# Lambda
|
|
142
|
-
attribute :server, source: -> { Current.server }
|
|
143
|
-
end
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
### Class or Module
|
|
147
|
-
|
|
148
|
-
For complex source logic, use classes or modules:
|
|
149
|
-
|
|
150
|
-
```ruby
|
|
151
|
-
class DatabaseResolver
|
|
152
|
-
def self.call(task)
|
|
153
|
-
Database.find(task.context.database_name)
|
|
154
|
-
end
|
|
155
|
-
end
|
|
156
|
-
|
|
157
|
-
class BackupDatabase < CMDx::Task
|
|
158
|
-
# Class or Module
|
|
159
|
-
attribute :schema, source: DatabaseResolver
|
|
160
|
-
|
|
161
|
-
# Instance
|
|
162
|
-
attribute :metadata, source: DatabaseResolver.new
|
|
163
|
-
end
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
## Nesting
|
|
167
|
-
|
|
168
|
-
Build complex structures with nested attributes. Children inherit their parent as source and support all attribute options:
|
|
169
|
-
|
|
170
|
-
!!! note
|
|
171
|
-
|
|
172
|
-
Nested attributes support all features: naming, coercions, validations, defaults, and more.
|
|
173
|
-
|
|
174
|
-
```ruby
|
|
175
|
-
class ConfigureServer < CMDx::Task
|
|
176
|
-
# Required parent with required children
|
|
177
|
-
required :network_config do
|
|
178
|
-
required :hostname, :port, :protocol, :subnet
|
|
179
|
-
optional :load_balancer
|
|
180
|
-
attribute :firewall_rules
|
|
181
|
-
end
|
|
182
|
-
|
|
183
|
-
# Optional parent with conditional children
|
|
184
|
-
optional :ssl_config do
|
|
185
|
-
required :certificate_path, :private_key # Only required if ssl_config provided
|
|
186
|
-
optional :enable_http2, prefix: true
|
|
187
|
-
end
|
|
188
|
-
|
|
189
|
-
# Multi-level nesting
|
|
190
|
-
attribute :monitoring do
|
|
191
|
-
required :provider
|
|
192
|
-
|
|
193
|
-
optional :alerting do
|
|
194
|
-
required :threshold_percentage
|
|
195
|
-
optional :notification_channel
|
|
196
|
-
end
|
|
197
|
-
end
|
|
198
|
-
|
|
199
|
-
def work
|
|
200
|
-
network_config #=> { hostname: "api.company.com" ... }
|
|
201
|
-
hostname #=> "api.company.com"
|
|
202
|
-
load_balancer #=> nil
|
|
203
|
-
end
|
|
204
|
-
end
|
|
205
|
-
|
|
206
|
-
ConfigureServer.execute(
|
|
207
|
-
server_id: "srv-001",
|
|
208
|
-
network_config: {
|
|
209
|
-
hostname: "api.company.com",
|
|
210
|
-
port: 443,
|
|
211
|
-
protocol: "https",
|
|
212
|
-
subnet: "10.0.1.0/24",
|
|
213
|
-
firewall_rules: "allow_web_traffic"
|
|
214
|
-
},
|
|
215
|
-
monitoring: {
|
|
216
|
-
provider: "datadog",
|
|
217
|
-
alerting: {
|
|
218
|
-
threshold_percentage: 85.0,
|
|
219
|
-
notification_channel: "slack"
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
)
|
|
223
|
-
```
|
|
224
|
-
|
|
225
|
-
!!! warning "Important"
|
|
226
|
-
|
|
227
|
-
Child requirements only apply when the parent is provided—perfect for optional structures.
|
|
228
|
-
|
|
229
|
-
## Error Handling
|
|
230
|
-
|
|
231
|
-
Validation failures provide detailed, structured error messages:
|
|
232
|
-
|
|
233
|
-
!!! note
|
|
234
|
-
|
|
235
|
-
Nested attributes are only validated when their parent is present and valid.
|
|
236
|
-
|
|
237
|
-
```ruby
|
|
238
|
-
class ConfigureServer < CMDx::Task
|
|
239
|
-
required :server_id, :environment
|
|
240
|
-
required :network_config do
|
|
241
|
-
required :hostname, :port
|
|
242
|
-
end
|
|
243
|
-
|
|
244
|
-
def work
|
|
245
|
-
# Your logic here...
|
|
246
|
-
end
|
|
247
|
-
end
|
|
248
|
-
|
|
249
|
-
# Missing required top-level attributes
|
|
250
|
-
result = ConfigureServer.execute(server_id: "srv-001")
|
|
251
|
-
|
|
252
|
-
result.state #=> "interrupted"
|
|
253
|
-
result.status #=> "failed"
|
|
254
|
-
result.reason #=> "Invalid"
|
|
255
|
-
result.metadata #=> {
|
|
256
|
-
# errors: {
|
|
257
|
-
# full_message: "environment is required. network_config is required.",
|
|
258
|
-
# messages: {
|
|
259
|
-
# environment: ["is required"],
|
|
260
|
-
# network_config: ["is required"]
|
|
261
|
-
# }
|
|
262
|
-
# }
|
|
263
|
-
# }
|
|
264
|
-
|
|
265
|
-
# Missing required nested attributes
|
|
266
|
-
result = ConfigureServer.execute(
|
|
267
|
-
server_id: "srv-001",
|
|
268
|
-
environment: "production",
|
|
269
|
-
network_config: { hostname: "api.company.com" } # Missing port
|
|
270
|
-
)
|
|
271
|
-
|
|
272
|
-
result.state #=> "interrupted"
|
|
273
|
-
result.status #=> "failed"
|
|
274
|
-
result.reason #=> "Invalid"
|
|
275
|
-
result.metadata #=> {
|
|
276
|
-
# errors: {
|
|
277
|
-
# full_message: "port is required.",
|
|
278
|
-
# messages: {
|
|
279
|
-
# port: ["is required"]
|
|
280
|
-
# }
|
|
281
|
-
# }
|
|
282
|
-
# }
|
|
283
|
-
```
|