cmdx 1.1.0 → 1.1.1
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/.cursor/prompts/docs.md +9 -0
- data/.cursor/prompts/rspec.md +13 -12
- data/.cursor/prompts/yardoc.md +11 -6
- data/CHANGELOG.md +13 -2
- data/README.md +1 -0
- data/docs/ai_prompts.md +269 -195
- data/docs/basics/call.md +124 -58
- data/docs/basics/chain.md +190 -160
- data/docs/basics/context.md +242 -154
- data/docs/basics/setup.md +302 -32
- data/docs/callbacks.md +390 -94
- data/docs/configuration.md +181 -65
- data/docs/deprecation.md +245 -0
- data/docs/getting_started.md +161 -39
- data/docs/internationalization.md +590 -70
- data/docs/interruptions/exceptions.md +135 -118
- data/docs/interruptions/faults.md +150 -125
- data/docs/interruptions/halt.md +134 -80
- data/docs/logging.md +181 -118
- data/docs/middlewares.md +150 -377
- data/docs/outcomes/result.md +140 -112
- data/docs/outcomes/states.md +134 -99
- data/docs/outcomes/statuses.md +204 -146
- data/docs/parameters/coercions.md +232 -281
- data/docs/parameters/defaults.md +224 -169
- data/docs/parameters/definitions.md +289 -141
- data/docs/parameters/namespacing.md +250 -161
- data/docs/parameters/validations.md +260 -133
- data/docs/testing.md +191 -197
- data/docs/workflows.md +143 -98
- data/lib/cmdx/callback.rb +23 -19
- data/lib/cmdx/callback_registry.rb +1 -3
- data/lib/cmdx/chain_inspector.rb +23 -23
- data/lib/cmdx/chain_serializer.rb +38 -19
- data/lib/cmdx/coercion.rb +20 -12
- data/lib/cmdx/coercion_registry.rb +51 -32
- data/lib/cmdx/configuration.rb +84 -31
- data/lib/cmdx/context.rb +32 -21
- data/lib/cmdx/core_ext/hash.rb +13 -13
- data/lib/cmdx/core_ext/module.rb +1 -1
- data/lib/cmdx/core_ext/object.rb +12 -12
- data/lib/cmdx/correlator.rb +60 -39
- data/lib/cmdx/errors.rb +105 -131
- data/lib/cmdx/fault.rb +66 -45
- data/lib/cmdx/immutator.rb +20 -21
- data/lib/cmdx/lazy_struct.rb +78 -70
- data/lib/cmdx/log_formatters/json.rb +1 -1
- data/lib/cmdx/log_formatters/key_value.rb +1 -1
- data/lib/cmdx/log_formatters/line.rb +1 -1
- data/lib/cmdx/log_formatters/logstash.rb +1 -1
- data/lib/cmdx/log_formatters/pretty_json.rb +1 -1
- data/lib/cmdx/log_formatters/pretty_key_value.rb +1 -1
- data/lib/cmdx/log_formatters/pretty_line.rb +1 -1
- data/lib/cmdx/log_formatters/raw.rb +2 -2
- data/lib/cmdx/logger.rb +19 -14
- data/lib/cmdx/logger_ansi.rb +33 -17
- data/lib/cmdx/logger_serializer.rb +85 -24
- data/lib/cmdx/middleware.rb +39 -21
- data/lib/cmdx/middleware_registry.rb +4 -3
- data/lib/cmdx/parameter.rb +151 -89
- data/lib/cmdx/parameter_inspector.rb +34 -21
- data/lib/cmdx/parameter_registry.rb +36 -30
- data/lib/cmdx/parameter_serializer.rb +21 -14
- data/lib/cmdx/result.rb +136 -135
- data/lib/cmdx/result_ansi.rb +31 -17
- data/lib/cmdx/result_inspector.rb +32 -27
- data/lib/cmdx/result_logger.rb +23 -14
- data/lib/cmdx/result_serializer.rb +65 -27
- data/lib/cmdx/task.rb +234 -113
- data/lib/cmdx/task_deprecator.rb +22 -25
- data/lib/cmdx/task_processor.rb +89 -88
- data/lib/cmdx/task_serializer.rb +27 -14
- data/lib/cmdx/utils/monotonic_runtime.rb +2 -4
- data/lib/cmdx/validator.rb +25 -16
- data/lib/cmdx/validator_registry.rb +53 -31
- data/lib/cmdx/validators/exclusion.rb +1 -1
- data/lib/cmdx/validators/format.rb +2 -2
- data/lib/cmdx/validators/inclusion.rb +2 -2
- data/lib/cmdx/validators/length.rb +2 -2
- data/lib/cmdx/validators/numeric.rb +3 -3
- data/lib/cmdx/validators/presence.rb +2 -2
- data/lib/cmdx/version.rb +1 -1
- data/lib/cmdx/workflow.rb +54 -33
- data/lib/generators/cmdx/task_generator.rb +6 -6
- data/lib/generators/cmdx/workflow_generator.rb +6 -6
- metadata +3 -1
@@ -1,6 +1,6 @@
|
|
1
1
|
# Parameters - Validations
|
2
2
|
|
3
|
-
Parameter
|
3
|
+
Parameter validations ensure data integrity by applying constraints to task inputs. All validators integrate with CMDx's error handling system and support internationalization for consistent error messaging across different locales.
|
4
4
|
|
5
5
|
## Table of Contents
|
6
6
|
|
@@ -8,53 +8,71 @@ Parameter values can be validated using built-in validators or custom validation
|
|
8
8
|
- [Common Options](#common-options)
|
9
9
|
- [Presence](#presence)
|
10
10
|
- [Format](#format)
|
11
|
-
- [Exclusion](#exclusion)
|
12
11
|
- [Inclusion](#inclusion)
|
12
|
+
- [Exclusion](#exclusion)
|
13
13
|
- [Length](#length)
|
14
14
|
- [Numeric](#numeric)
|
15
|
-
- [
|
15
|
+
- [Error Handling](#error-handling)
|
16
|
+
- [Conditional Validation](#conditional-validation)
|
16
17
|
|
17
18
|
## TLDR
|
18
19
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
20
|
+
```ruby
|
21
|
+
# Basic validation
|
22
|
+
required :email, presence: true, format: { with: /@/ }
|
23
|
+
required :status, inclusion: { in: %w[pending active] }
|
24
|
+
required :password, length: { min: 8 }
|
23
25
|
|
24
|
-
|
26
|
+
# Conditional validation
|
27
|
+
optional :phone, presence: { if: :phone_required? }
|
28
|
+
required :age, numeric: { min: 18, unless: :minor_allowed? }
|
25
29
|
|
26
|
-
|
30
|
+
# Custom messages
|
31
|
+
required :username, exclusion: { in: %w[admin root], message: "reserved name" }
|
32
|
+
```
|
27
33
|
|
28
|
-
|
29
|
-
| ------------ | ----------- |
|
30
|
-
| `:allow_nil` | Skip validation if the parameter value is `nil` |
|
31
|
-
| `:if` | Callable method, proc or string to determine if validation should occur |
|
32
|
-
| `:unless` | Callable method, proc, or string to determine if validation should not occur |
|
33
|
-
| `:message` | Error message for violations. Fallback for specific error keys not provided |
|
34
|
+
## Common Options
|
34
35
|
|
35
36
|
> [!NOTE]
|
36
|
-
> Validators on `optional` parameters only execute when arguments are
|
37
|
+
> Validators on `optional` parameters only execute when arguments are provided.
|
38
|
+
|
39
|
+
All validators support these common options:
|
40
|
+
|
41
|
+
| Option | Description |
|
42
|
+
|--------|-------------|
|
43
|
+
| `:allow_nil` | Skip validation when value is `nil` |
|
44
|
+
| `:if` | Method, proc, or string determining when to validate |
|
45
|
+
| `:unless` | Method, proc, or string determining when to skip validation |
|
46
|
+
| `:message` | Custom error message for validation failures |
|
37
47
|
|
38
48
|
## Presence
|
39
49
|
|
40
50
|
Validates that parameter values are not empty using intelligent type checking:
|
51
|
+
|
41
52
|
- **Strings**: Must contain non-whitespace characters
|
42
|
-
- **Collections**: Must not be empty (arrays, hashes,
|
53
|
+
- **Collections**: Must not be empty (arrays, hashes, sets)
|
43
54
|
- **Other objects**: Must not be `nil`
|
44
55
|
|
45
56
|
> [!TIP]
|
46
|
-
> For boolean fields
|
57
|
+
> For boolean fields accepting `true` and `false`, use `inclusion: { in: [true, false] }` instead of presence validation.
|
47
58
|
|
48
59
|
```ruby
|
49
60
|
class CreateUserTask < CMDx::Task
|
50
61
|
required :email, presence: true
|
51
|
-
|
62
|
+
required :name, presence: { message: "cannot be blank" }
|
52
63
|
required :active, inclusion: { in: [true, false] }
|
53
64
|
|
54
65
|
def call
|
55
|
-
User.create!(email: email,
|
66
|
+
User.create!(email: email, name: name, active: active)
|
56
67
|
end
|
57
68
|
end
|
69
|
+
|
70
|
+
# Valid inputs
|
71
|
+
CreateUserTask.call(email: "user@example.com", name: "John", active: true)
|
72
|
+
|
73
|
+
# Invalid inputs
|
74
|
+
CreateUserTask.call(email: "", name: " ", active: nil)
|
75
|
+
# → ValidationError: "email can't be blank. name cannot be blank. active must be one of: true, false"
|
58
76
|
```
|
59
77
|
|
60
78
|
## Format
|
@@ -65,10 +83,11 @@ Validates parameter values against regular expression patterns. Supports positiv
|
|
65
83
|
class RegisterUserTask < CMDx::Task
|
66
84
|
required :email, format: { with: /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i }
|
67
85
|
required :username, format: { without: /\A(admin|root|system)\z/i }
|
86
|
+
|
68
87
|
optional :password, format: {
|
69
88
|
with: /\A(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}\z/,
|
70
89
|
without: /password|123456/i,
|
71
|
-
if: :
|
90
|
+
if: :secure_password_required?
|
72
91
|
}
|
73
92
|
|
74
93
|
def call
|
@@ -77,199 +96,307 @@ class RegisterUserTask < CMDx::Task
|
|
77
96
|
|
78
97
|
private
|
79
98
|
|
80
|
-
def
|
81
|
-
context.
|
99
|
+
def secure_password_required?
|
100
|
+
context.security_policy.enforce_strong_passwords?
|
82
101
|
end
|
83
102
|
end
|
84
103
|
```
|
85
104
|
|
86
105
|
**Options:**
|
87
106
|
|
88
|
-
| Option
|
89
|
-
|
90
|
-
| `:with`
|
107
|
+
| Option | Description |
|
108
|
+
|--------|-------------|
|
109
|
+
| `:with` | Regular expression that value must match |
|
91
110
|
| `:without` | Regular expression that value must not match |
|
92
111
|
|
93
|
-
##
|
112
|
+
## Inclusion
|
94
113
|
|
95
|
-
|
114
|
+
> [!IMPORTANT]
|
115
|
+
> Validates that parameter values are within a specific set of allowed values (array, range, or other enumerable).
|
96
116
|
|
97
117
|
```ruby
|
98
|
-
class
|
99
|
-
required :
|
100
|
-
required :
|
101
|
-
|
118
|
+
class UpdateOrderTask < CMDx::Task
|
119
|
+
required :status, inclusion: { in: %w[pending processing shipped delivered] }
|
120
|
+
required :priority, inclusion: { in: 1..5 }
|
121
|
+
|
122
|
+
optional :shipping_method, inclusion: {
|
123
|
+
in: %w[standard express overnight],
|
124
|
+
unless: :digital_product?
|
125
|
+
}
|
102
126
|
|
103
127
|
def call
|
104
|
-
|
128
|
+
update_order_attributes
|
129
|
+
end
|
130
|
+
|
131
|
+
private
|
132
|
+
|
133
|
+
def digital_product?
|
134
|
+
context.order.items.all?(&:digital?)
|
105
135
|
end
|
106
136
|
end
|
107
137
|
```
|
108
138
|
|
109
139
|
**Options:**
|
110
140
|
|
111
|
-
| Option
|
112
|
-
|
113
|
-
| `:in`
|
114
|
-
| `:within`
|
141
|
+
| Option | Description |
|
142
|
+
|--------|-------------|
|
143
|
+
| `:in` | Enumerable of allowed values |
|
144
|
+
| `:within` | Alias for `:in` |
|
115
145
|
|
116
|
-
**Error Messages:**
|
146
|
+
**Custom Error Messages:**
|
117
147
|
|
118
|
-
| Option
|
119
|
-
|
120
|
-
| `:of_message`
|
121
|
-
| `:in_message`
|
148
|
+
| Option | Description |
|
149
|
+
|--------|-------------|
|
150
|
+
| `:of_message` | Error for array validation (default: "must be one of: %{values}") |
|
151
|
+
| `:in_message` | Error for range validation (default: "must be within %{min} and %{max}") |
|
122
152
|
| `:within_message` | Alias for `:in_message` |
|
123
153
|
|
124
|
-
##
|
154
|
+
## Exclusion
|
125
155
|
|
126
|
-
Validates that parameter values are
|
156
|
+
Validates that parameter values are not within a specific set of forbidden values.
|
127
157
|
|
128
158
|
```ruby
|
129
|
-
class
|
130
|
-
required :
|
131
|
-
required :
|
132
|
-
|
133
|
-
|
134
|
-
|
159
|
+
class ProcessPaymentTask < CMDx::Task
|
160
|
+
required :payment_method, exclusion: { in: %w[cash check] }
|
161
|
+
required :amount, exclusion: { in: 0.0..0.99, in_message: "must be at least $1.00" }
|
162
|
+
|
163
|
+
optional :promo_code, exclusion: {
|
164
|
+
in: %w[EXPIRED INVALID],
|
165
|
+
of_message: "is not valid"
|
135
166
|
}
|
136
167
|
|
137
168
|
def call
|
138
|
-
|
139
|
-
end
|
140
|
-
|
141
|
-
private
|
142
|
-
|
143
|
-
def digital_order?
|
144
|
-
context.order.digital_items_only?
|
169
|
+
charge_payment_method
|
145
170
|
end
|
146
171
|
end
|
172
|
+
|
173
|
+
# Valid usage
|
174
|
+
ProcessPaymentTask.call(
|
175
|
+
payment_method: "credit_card",
|
176
|
+
amount: 29.99,
|
177
|
+
promo_code: "SAVE20"
|
178
|
+
)
|
147
179
|
```
|
148
180
|
|
149
181
|
**Options:**
|
150
182
|
|
151
|
-
| Option
|
152
|
-
|
153
|
-
| `:in`
|
154
|
-
| `:within`
|
183
|
+
| Option | Description |
|
184
|
+
|--------|-------------|
|
185
|
+
| `:in` | Enumerable of forbidden values |
|
186
|
+
| `:within` | Alias for `:in` |
|
155
187
|
|
156
|
-
**Error Messages:**
|
188
|
+
**Custom Error Messages:**
|
157
189
|
|
158
|
-
| Option
|
159
|
-
|
160
|
-
| `:of_message`
|
161
|
-
| `:in_message`
|
190
|
+
| Option | Description |
|
191
|
+
|--------|-------------|
|
192
|
+
| `:of_message` | Error for array validation (default: "must not be one of: %{values}") |
|
193
|
+
| `:in_message` | Error for range validation (default: "must not be within %{min} and %{max}") |
|
162
194
|
| `:within_message` | Alias for `:in_message` |
|
163
195
|
|
164
196
|
## Length
|
165
197
|
|
166
|
-
Validates parameter length
|
198
|
+
Validates parameter length for any object responding to `#size` or `#length`. Only one constraint option can be used at a time, except `:min` and `:max` which can be combined.
|
167
199
|
|
168
200
|
```ruby
|
169
201
|
class CreatePostTask < CMDx::Task
|
170
202
|
required :title, length: { within: 5..100 }
|
171
|
-
required :
|
172
|
-
optional :summary, length: { max: 200 }
|
203
|
+
required :content, length: { min: 50 }
|
173
204
|
required :slug, length: { min: 3, max: 50 }
|
174
|
-
|
205
|
+
|
206
|
+
optional :summary, length: { max: 200, allow_nil: true }
|
207
|
+
optional :category_code, length: { is: 3 }
|
175
208
|
|
176
209
|
def call
|
177
|
-
|
210
|
+
Post.create!(title: title, content: content, slug: slug)
|
178
211
|
end
|
179
212
|
end
|
180
213
|
```
|
181
214
|
|
182
|
-
**Options:**
|
215
|
+
**Constraint Options:**
|
183
216
|
|
184
|
-
| Option
|
185
|
-
|
186
|
-
| `:within`
|
187
|
-
| `:not_within` | Range specifying forbidden
|
188
|
-
| `:
|
189
|
-
| `:
|
190
|
-
| `:
|
191
|
-
| `:
|
192
|
-
| `:is` | Exact size required |
|
193
|
-
| `:is_not` | Size that is forbidden |
|
217
|
+
| Option | Description |
|
218
|
+
|--------|-------------|
|
219
|
+
| `:within` / `:in` | Range specifying min and max length |
|
220
|
+
| `:not_within` / `:not_in` | Range specifying forbidden length range |
|
221
|
+
| `:min` | Minimum length required |
|
222
|
+
| `:max` | Maximum length allowed |
|
223
|
+
| `:is` | Exact length required |
|
224
|
+
| `:is_not` | Length that is forbidden |
|
194
225
|
|
195
226
|
**Error Messages:**
|
196
227
|
|
197
|
-
| Option
|
198
|
-
|
199
|
-
| `:within_message`
|
228
|
+
| Option | Description |
|
229
|
+
|--------|-------------|
|
230
|
+
| `:within_message` | "length must be within %{min} and %{max}" |
|
200
231
|
| `:not_within_message` | "length must not be within %{min} and %{max}" |
|
201
|
-
| `:min_message`
|
202
|
-
| `:max_message`
|
203
|
-
| `:is_message`
|
204
|
-
| `:is_not_message`
|
232
|
+
| `:min_message` | "length must be at least %{min}" |
|
233
|
+
| `:max_message` | "length must be at most %{max}" |
|
234
|
+
| `:is_message` | "length must be %{is}" |
|
235
|
+
| `:is_not_message` | "length must not be %{is_not}" |
|
205
236
|
|
206
237
|
## Numeric
|
207
238
|
|
208
|
-
Validates numeric values against constraints. Works with any numeric type
|
239
|
+
Validates numeric values against constraints. Works with any numeric type including integers, floats, and decimals.
|
209
240
|
|
210
241
|
```ruby
|
211
242
|
class ProcessOrderTask < CMDx::Task
|
212
243
|
required :quantity, numeric: { within: 1..100 }
|
213
244
|
required :price, numeric: { min: 0.01 }
|
214
|
-
|
215
|
-
|
216
|
-
|
245
|
+
required :tax_rate, numeric: { min: 0, max: 0.25 }
|
246
|
+
|
247
|
+
optional :discount, numeric: { max: 50, allow_nil: true }
|
248
|
+
optional :api_version, numeric: { is: 2 }
|
217
249
|
|
218
250
|
def call
|
219
251
|
calculate_order_total
|
220
252
|
end
|
221
253
|
end
|
254
|
+
|
255
|
+
# Error example
|
256
|
+
ProcessOrderTask.call(
|
257
|
+
quantity: 0, # Below minimum
|
258
|
+
price: -5.00, # Below minimum
|
259
|
+
tax_rate: 0.30 # Above maximum
|
260
|
+
)
|
261
|
+
# → ValidationError: "quantity must be within 1 and 100. price must be at least 0.01. tax_rate must be at most 0.25"
|
222
262
|
```
|
223
263
|
|
224
|
-
**Options:**
|
264
|
+
**Constraint Options:**
|
225
265
|
|
226
|
-
| Option
|
227
|
-
|
228
|
-
| `:within`
|
229
|
-
| `:not_within` | Range specifying forbidden value range |
|
230
|
-
| `:
|
231
|
-
| `:
|
232
|
-
| `:
|
233
|
-
| `:
|
234
|
-
| `:is` | Exact value required |
|
235
|
-
| `:is_not` | Value that is forbidden |
|
266
|
+
| Option | Description |
|
267
|
+
|--------|-------------|
|
268
|
+
| `:within` / `:in` | Range specifying min and max value |
|
269
|
+
| `:not_within` / `:not_in` | Range specifying forbidden value range |
|
270
|
+
| `:min` | Minimum value required |
|
271
|
+
| `:max` | Maximum value allowed |
|
272
|
+
| `:is` | Exact value required |
|
273
|
+
| `:is_not` | Value that is forbidden |
|
236
274
|
|
237
|
-
|
275
|
+
## Error Handling
|
238
276
|
|
239
|
-
|
240
|
-
|
241
|
-
| `:within_message` | "must be within %{min} and %{max}" |
|
242
|
-
| `:not_within_message` | "must not be within %{min} and %{max}" |
|
243
|
-
| `:min_message` | "must be at least %{min}" |
|
244
|
-
| `:max_message` | "must be at most %{max}" |
|
245
|
-
| `:is_message` | "must be %{is}" |
|
246
|
-
| `:is_not_message` | "must not be %{is_not}" |
|
277
|
+
> [!WARNING]
|
278
|
+
> Validation failures cause tasks to enter a failed state with detailed error information including parameter paths and specific violation messages.
|
247
279
|
|
248
|
-
|
280
|
+
```ruby
|
281
|
+
class CreateUserTask < CMDx::Task
|
282
|
+
required :email, format: { with: /@/, message: "must be valid" }
|
283
|
+
required :username, presence: true, length: { min: 3 }
|
284
|
+
required :age, numeric: { min: 13, max: 120 }
|
249
285
|
|
250
|
-
|
286
|
+
def call
|
287
|
+
# Process user
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
result = CreateUserTask.call(
|
292
|
+
email: "invalid-email",
|
293
|
+
username: "",
|
294
|
+
age: 5
|
295
|
+
)
|
296
|
+
|
297
|
+
result.state # → "interrupted"
|
298
|
+
result.status # → "failed"
|
299
|
+
result.failed? # → true
|
300
|
+
|
301
|
+
# Detailed error information
|
302
|
+
result.metadata
|
303
|
+
# {
|
304
|
+
# reason: "email must be valid. username can't be blank. username length must be at least 3. age must be at least 13.",
|
305
|
+
# messages: {
|
306
|
+
# email: ["must be valid"],
|
307
|
+
# username: ["can't be blank", "length must be at least 3"],
|
308
|
+
# age: ["must be at least 13"]
|
309
|
+
# }
|
310
|
+
# }
|
311
|
+
|
312
|
+
# Access specific parameter errors
|
313
|
+
result.metadata[:messages][:email] # → ["must be valid"]
|
314
|
+
result.metadata[:messages][:username] # → ["can't be blank", "length must be at least 3"]
|
315
|
+
```
|
316
|
+
|
317
|
+
### Nested Parameter Validation
|
251
318
|
|
252
319
|
```ruby
|
253
|
-
class
|
254
|
-
required :
|
255
|
-
|
320
|
+
class ProcessOrderTask < CMDx::Task
|
321
|
+
required :order, type: :hash do
|
322
|
+
required :customer_email, format: { with: /@/ }
|
323
|
+
required :items, type: :array, length: { min: 1 }
|
324
|
+
|
325
|
+
optional :shipping, type: :hash do
|
326
|
+
required :method, inclusion: { in: %w[standard express] }
|
327
|
+
required :address, presence: true
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
def call
|
332
|
+
# Process validated order
|
333
|
+
end
|
256
334
|
end
|
257
335
|
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
336
|
+
# Nested validation errors
|
337
|
+
result = ProcessOrderTask.call(
|
338
|
+
order: {
|
339
|
+
customer_email: "invalid",
|
340
|
+
items: [],
|
341
|
+
shipping: {
|
342
|
+
method: "invalid",
|
343
|
+
address: ""
|
344
|
+
}
|
345
|
+
}
|
346
|
+
)
|
347
|
+
|
348
|
+
result.metadata[:messages]
|
349
|
+
# {
|
350
|
+
# "order.customer_email" => ["is invalid"],
|
351
|
+
# "order.items" => ["length must be at least 1"],
|
352
|
+
# "order.shipping.method" => ["must be one of: standard, express"],
|
353
|
+
# "order.shipping.address" => ["can't be blank"]
|
354
|
+
# }
|
355
|
+
```
|
356
|
+
|
357
|
+
## Conditional Validation
|
358
|
+
|
359
|
+
> [!TIP]
|
360
|
+
> Use `:if` and `:unless` options to apply validations conditionally based on runtime context or other parameter values.
|
361
|
+
|
362
|
+
```ruby
|
363
|
+
class UserRegistrationTask < CMDx::Task
|
364
|
+
required :email, presence: true, format: { with: /@/ }
|
365
|
+
required :user_type, inclusion: { in: %w[individual business] }
|
366
|
+
|
367
|
+
# Conditional validations based on user type
|
368
|
+
optional :company_name, presence: { if: :business_user? }
|
369
|
+
optional :tax_id, format: { with: /\A\d{2}-\d{7}\z/, if: :business_user? }
|
370
|
+
|
371
|
+
# Conditional validation with procs
|
372
|
+
optional :phone, presence: {
|
373
|
+
if: proc { |task| task.context.require_phone_verification? }
|
374
|
+
}
|
375
|
+
|
376
|
+
# Multiple conditions
|
377
|
+
optional :parent_email, presence: {
|
378
|
+
if: :minor_user?,
|
379
|
+
format: { with: /@/, unless: :parent_present? }
|
380
|
+
}
|
381
|
+
|
382
|
+
def call
|
383
|
+
create_user_account
|
384
|
+
end
|
385
|
+
|
386
|
+
private
|
387
|
+
|
388
|
+
def business_user?
|
389
|
+
user_type == "business"
|
390
|
+
end
|
391
|
+
|
392
|
+
def minor_user?
|
393
|
+
context.user_age < 18
|
394
|
+
end
|
395
|
+
|
396
|
+
def parent_present?
|
397
|
+
context.parent_guardian_present?
|
398
|
+
end
|
399
|
+
end
|
273
400
|
```
|
274
401
|
|
275
402
|
---
|