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.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +88 -71
  3. data/LICENSE.txt +3 -20
  4. data/README.md +8 -7
  5. data/lib/cmdx/attribute.rb +21 -5
  6. data/lib/cmdx/chain.rb +18 -4
  7. data/lib/cmdx/context.rb +18 -0
  8. data/lib/cmdx/executor.rb +35 -30
  9. data/lib/cmdx/result.rb +45 -2
  10. data/lib/cmdx/task.rb +22 -1
  11. data/lib/cmdx/version.rb +1 -1
  12. data/mkdocs.yml +67 -37
  13. metadata +3 -57
  14. data/.cursor/prompts/docs.md +0 -12
  15. data/.cursor/prompts/llms.md +0 -8
  16. data/.cursor/prompts/rspec.md +0 -24
  17. data/.cursor/prompts/yardoc.md +0 -15
  18. data/.cursor/rules/cursor-instructions.mdc +0 -68
  19. data/.irbrc +0 -18
  20. data/.rspec +0 -4
  21. data/.rubocop.yml +0 -95
  22. data/.ruby-version +0 -1
  23. data/.yard-lint.yml +0 -174
  24. data/.yardopts +0 -7
  25. data/docs/.DS_Store +0 -0
  26. data/docs/assets/favicon.ico +0 -0
  27. data/docs/assets/favicon.svg +0 -1
  28. data/docs/attributes/coercions.md +0 -155
  29. data/docs/attributes/defaults.md +0 -77
  30. data/docs/attributes/definitions.md +0 -283
  31. data/docs/attributes/naming.md +0 -68
  32. data/docs/attributes/transformations.md +0 -63
  33. data/docs/attributes/validations.md +0 -336
  34. data/docs/basics/chain.md +0 -108
  35. data/docs/basics/context.md +0 -121
  36. data/docs/basics/execution.md +0 -96
  37. data/docs/basics/setup.md +0 -84
  38. data/docs/callbacks.md +0 -157
  39. data/docs/configuration.md +0 -314
  40. data/docs/deprecation.md +0 -145
  41. data/docs/getting_started.md +0 -126
  42. data/docs/index.md +0 -134
  43. data/docs/internationalization.md +0 -126
  44. data/docs/interruptions/exceptions.md +0 -52
  45. data/docs/interruptions/faults.md +0 -169
  46. data/docs/interruptions/halt.md +0 -216
  47. data/docs/logging.md +0 -94
  48. data/docs/middlewares.md +0 -191
  49. data/docs/outcomes/result.md +0 -194
  50. data/docs/outcomes/states.md +0 -66
  51. data/docs/outcomes/statuses.md +0 -65
  52. data/docs/retries.md +0 -121
  53. data/docs/stylesheets/extra.css +0 -42
  54. data/docs/tips_and_tricks.md +0 -157
  55. data/docs/workflows.md +0 -226
  56. data/examples/active_record_database_transaction.md +0 -27
  57. data/examples/active_record_query_tagging.md +0 -46
  58. data/examples/flipper_feature_flags.md +0 -50
  59. data/examples/paper_trail_whatdunnit.md +0 -39
  60. data/examples/redis_idempotency.md +0 -71
  61. data/examples/sentry_error_tracking.md +0 -46
  62. data/examples/sidekiq_async_execution.md +0 -29
  63. data/examples/stoplight_circuit_breaker.md +0 -36
  64. data/src/cmdx-dark-logo.png +0 -0
  65. data/src/cmdx-favicon.svg +0 -1
  66. data/src/cmdx-light-logo.png +0 -0
  67. 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
@@ -1,7 +0,0 @@
1
- --no-private
2
- --output-dir
3
- docs/api
4
- --exclude
5
- lib/generators/**/*.rb
6
- lib/cmdx/railtie.rb
7
- lib/**/*.rb
data/docs/.DS_Store DELETED
Binary file
Binary file
@@ -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
- ```
@@ -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
- ```