cmdx 1.13.0 → 1.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +84 -76
- data/LICENSE.txt +3 -20
- data/README.md +8 -7
- data/lib/cmdx/attribute.rb +21 -5
- data/lib/cmdx/context.rb +16 -0
- data/lib/cmdx/executor.rb +9 -9
- data/lib/cmdx/result.rb +27 -7
- data/lib/cmdx/task.rb +19 -0
- data/lib/cmdx/version.rb +1 -1
- data/mkdocs.yml +62 -36
- metadata +3 -57
- data/.cursor/prompts/docs.md +0 -12
- data/.cursor/prompts/llms.md +0 -8
- data/.cursor/prompts/rspec.md +0 -24
- data/.cursor/prompts/yardoc.md +0 -15
- data/.cursor/rules/cursor-instructions.mdc +0 -68
- data/.irbrc +0 -18
- data/.rspec +0 -4
- data/.rubocop.yml +0 -95
- data/.ruby-version +0 -1
- data/.yard-lint.yml +0 -174
- data/.yardopts +0 -7
- data/docs/.DS_Store +0 -0
- data/docs/assets/favicon.ico +0 -0
- data/docs/assets/favicon.svg +0 -1
- data/docs/attributes/coercions.md +0 -155
- data/docs/attributes/defaults.md +0 -77
- data/docs/attributes/definitions.md +0 -283
- data/docs/attributes/naming.md +0 -68
- data/docs/attributes/transformations.md +0 -63
- data/docs/attributes/validations.md +0 -336
- data/docs/basics/chain.md +0 -108
- data/docs/basics/context.md +0 -121
- data/docs/basics/execution.md +0 -152
- data/docs/basics/setup.md +0 -107
- data/docs/callbacks.md +0 -157
- data/docs/configuration.md +0 -314
- data/docs/deprecation.md +0 -143
- data/docs/getting_started.md +0 -137
- data/docs/index.md +0 -134
- data/docs/internationalization.md +0 -126
- data/docs/interruptions/exceptions.md +0 -52
- data/docs/interruptions/faults.md +0 -169
- data/docs/interruptions/halt.md +0 -216
- data/docs/logging.md +0 -90
- data/docs/middlewares.md +0 -191
- data/docs/outcomes/result.md +0 -197
- data/docs/outcomes/states.md +0 -66
- data/docs/outcomes/statuses.md +0 -65
- data/docs/retries.md +0 -121
- data/docs/stylesheets/extra.css +0 -42
- data/docs/tips_and_tricks.md +0 -157
- data/docs/workflows.md +0 -226
- data/examples/active_record_database_transaction.md +0 -27
- data/examples/active_record_query_tagging.md +0 -46
- data/examples/flipper_feature_flags.md +0 -50
- data/examples/paper_trail_whatdunnit.md +0 -39
- data/examples/redis_idempotency.md +0 -71
- data/examples/sentry_error_tracking.md +0 -46
- data/examples/sidekiq_async_execution.md +0 -29
- data/examples/stoplight_circuit_breaker.md +0 -36
- data/src/cmdx-dark-logo.png +0 -0
- data/src/cmdx-favicon.svg +0 -1
- data/src/cmdx-light-logo.png +0 -0
- data/src/cmdx-logo.svg +0 -1
|
@@ -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
|
-
```
|
data/docs/attributes/naming.md
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
# Attributes - Naming
|
|
2
|
-
|
|
3
|
-
Customize accessor method names to avoid conflicts and improve clarity. Affixing changes only the generated methods—not the original attribute names.
|
|
4
|
-
|
|
5
|
-
!!! note
|
|
6
|
-
|
|
7
|
-
Use naming when attributes conflict with existing methods or need better clarity in your code.
|
|
8
|
-
|
|
9
|
-
## Prefix
|
|
10
|
-
|
|
11
|
-
Adds a prefix to the generated accessor method name.
|
|
12
|
-
|
|
13
|
-
```ruby
|
|
14
|
-
class GenerateReport < CMDx::Task
|
|
15
|
-
# Dynamic from attribute source
|
|
16
|
-
attribute :template, prefix: true
|
|
17
|
-
|
|
18
|
-
# Static
|
|
19
|
-
attribute :format, prefix: "report_"
|
|
20
|
-
|
|
21
|
-
def work
|
|
22
|
-
context_template #=> "monthly_sales"
|
|
23
|
-
report_format #=> "pdf"
|
|
24
|
-
end
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
# Attributes passed as original attribute names
|
|
28
|
-
GenerateReport.execute(template: "monthly_sales", format: "pdf")
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
## Suffix
|
|
32
|
-
|
|
33
|
-
Adds a suffix to the generated accessor method name.
|
|
34
|
-
|
|
35
|
-
```ruby
|
|
36
|
-
class DeployApplication < CMDx::Task
|
|
37
|
-
# Dynamic from attribute source
|
|
38
|
-
attribute :branch, suffix: true
|
|
39
|
-
|
|
40
|
-
# Static
|
|
41
|
-
attribute :version, suffix: "_tag"
|
|
42
|
-
|
|
43
|
-
def work
|
|
44
|
-
branch_context #=> "main"
|
|
45
|
-
version_tag #=> "v1.2.3"
|
|
46
|
-
end
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
# Attributes passed as original attribute names
|
|
50
|
-
DeployApplication.execute(branch: "main", version: "v1.2.3")
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
## As
|
|
54
|
-
|
|
55
|
-
Completely renames the generated accessor method.
|
|
56
|
-
|
|
57
|
-
```ruby
|
|
58
|
-
class ScheduleMaintenance < CMDx::Task
|
|
59
|
-
attribute :scheduled_at, as: :when
|
|
60
|
-
|
|
61
|
-
def work
|
|
62
|
-
when #=> <DateTime>
|
|
63
|
-
end
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
# Attributes passed as original attribute names
|
|
67
|
-
ScheduleMaintenance.execute(scheduled_at: DateTime.new(2024, 12, 15, 2, 0, 0))
|
|
68
|
-
```
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
# Attributes - Transformations
|
|
2
|
-
|
|
3
|
-
Modify attribute values after coercion but before validation. Perfect for normalization, formatting, and data cleanup.
|
|
4
|
-
|
|
5
|
-
## Declarations
|
|
6
|
-
|
|
7
|
-
### Symbol References
|
|
8
|
-
|
|
9
|
-
Reference instance methods by symbol for dynamic value transformations:
|
|
10
|
-
|
|
11
|
-
```ruby
|
|
12
|
-
class ProcessAnalytics < CMDx::Task
|
|
13
|
-
attribute :options, transform: :compact_blank
|
|
14
|
-
end
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
### Proc or Lambda
|
|
18
|
-
|
|
19
|
-
Use anonymous functions for dynamic value transformations:
|
|
20
|
-
|
|
21
|
-
```ruby
|
|
22
|
-
class CacheContent < CMDx::Task
|
|
23
|
-
# Proc
|
|
24
|
-
attribute :expire_hours, transform: proc { |v| v * 2 }
|
|
25
|
-
|
|
26
|
-
# Lambda
|
|
27
|
-
attribute :compression, transform: ->(v) { v.to_s.upcase.strip[0..2] }
|
|
28
|
-
end
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
### Class or Module
|
|
32
|
-
|
|
33
|
-
Use any object that responds to `call` for reusable transformation logic:
|
|
34
|
-
|
|
35
|
-
```ruby
|
|
36
|
-
class EmailNormalizer
|
|
37
|
-
def call(value)
|
|
38
|
-
value.to_s.downcase.strip
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
class ProcessContacts < CMDx::Task
|
|
43
|
-
# Class or Module
|
|
44
|
-
attribute :email, transform: EmailNormalizer
|
|
45
|
-
|
|
46
|
-
# Instance
|
|
47
|
-
attribute :email, transform: EmailNormalizer.new
|
|
48
|
-
end
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
## Validations
|
|
52
|
-
|
|
53
|
-
Validations run on transformed values, ensuring data consistency:
|
|
54
|
-
|
|
55
|
-
```ruby
|
|
56
|
-
class ScheduleBackup < CMDx::Task
|
|
57
|
-
# Coercions
|
|
58
|
-
attribute :retention_days, type: :integer, transform: proc { |v| v.clamp(1, 5) }
|
|
59
|
-
|
|
60
|
-
# Validations
|
|
61
|
-
optional :frequency, transform: :downcase, inclusion: { in: %w[hourly daily weekly monthly] }
|
|
62
|
-
end
|
|
63
|
-
```
|