cmdx 1.1.2 → 1.5.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.
Files changed (192) hide show
  1. checksums.yaml +4 -4
  2. data/.DS_Store +0 -0
  3. data/.cursor/prompts/docs.md +4 -1
  4. data/.cursor/prompts/llms.md +20 -0
  5. data/.cursor/prompts/rspec.md +4 -1
  6. data/.cursor/prompts/yardoc.md +3 -2
  7. data/.cursor/rules/cursor-instructions.mdc +55 -1
  8. data/.irbrc +6 -0
  9. data/.rubocop.yml +29 -18
  10. data/CHANGELOG.md +11 -132
  11. data/LLM.md +3317 -0
  12. data/README.md +68 -44
  13. data/docs/attributes/coercions.md +162 -0
  14. data/docs/attributes/defaults.md +90 -0
  15. data/docs/attributes/definitions.md +281 -0
  16. data/docs/attributes/naming.md +78 -0
  17. data/docs/attributes/validations.md +309 -0
  18. data/docs/basics/chain.md +56 -249
  19. data/docs/basics/context.md +56 -289
  20. data/docs/basics/execution.md +114 -0
  21. data/docs/basics/setup.md +37 -334
  22. data/docs/callbacks.md +89 -467
  23. data/docs/deprecation.md +91 -174
  24. data/docs/getting_started.md +212 -202
  25. data/docs/internationalization.md +11 -647
  26. data/docs/interruptions/exceptions.md +23 -198
  27. data/docs/interruptions/faults.md +71 -151
  28. data/docs/interruptions/halt.md +109 -186
  29. data/docs/logging.md +44 -256
  30. data/docs/middlewares.md +113 -426
  31. data/docs/outcomes/result.md +81 -228
  32. data/docs/outcomes/states.md +33 -221
  33. data/docs/outcomes/statuses.md +21 -311
  34. data/docs/tips_and_tricks.md +120 -70
  35. data/docs/workflows.md +99 -283
  36. data/lib/cmdx/.DS_Store +0 -0
  37. data/lib/cmdx/attribute.rb +229 -0
  38. data/lib/cmdx/attribute_registry.rb +94 -0
  39. data/lib/cmdx/attribute_value.rb +193 -0
  40. data/lib/cmdx/callback_registry.rb +69 -77
  41. data/lib/cmdx/chain.rb +56 -73
  42. data/lib/cmdx/coercion_registry.rb +52 -68
  43. data/lib/cmdx/coercions/array.rb +19 -18
  44. data/lib/cmdx/coercions/big_decimal.rb +20 -24
  45. data/lib/cmdx/coercions/boolean.rb +26 -25
  46. data/lib/cmdx/coercions/complex.rb +21 -22
  47. data/lib/cmdx/coercions/date.rb +25 -23
  48. data/lib/cmdx/coercions/date_time.rb +24 -25
  49. data/lib/cmdx/coercions/float.rb +25 -22
  50. data/lib/cmdx/coercions/hash.rb +31 -32
  51. data/lib/cmdx/coercions/integer.rb +30 -24
  52. data/lib/cmdx/coercions/rational.rb +29 -24
  53. data/lib/cmdx/coercions/string.rb +19 -22
  54. data/lib/cmdx/coercions/symbol.rb +37 -0
  55. data/lib/cmdx/coercions/time.rb +26 -25
  56. data/lib/cmdx/configuration.rb +49 -108
  57. data/lib/cmdx/context.rb +222 -44
  58. data/lib/cmdx/deprecator.rb +61 -0
  59. data/lib/cmdx/errors.rb +42 -252
  60. data/lib/cmdx/exceptions.rb +39 -0
  61. data/lib/cmdx/faults.rb +78 -39
  62. data/lib/cmdx/freezer.rb +51 -0
  63. data/lib/cmdx/identifier.rb +30 -0
  64. data/lib/cmdx/locale.rb +52 -0
  65. data/lib/cmdx/log_formatters/json.rb +21 -22
  66. data/lib/cmdx/log_formatters/key_value.rb +20 -22
  67. data/lib/cmdx/log_formatters/line.rb +15 -22
  68. data/lib/cmdx/log_formatters/logstash.rb +22 -23
  69. data/lib/cmdx/log_formatters/raw.rb +16 -22
  70. data/lib/cmdx/middleware_registry.rb +70 -74
  71. data/lib/cmdx/middlewares/correlate.rb +90 -54
  72. data/lib/cmdx/middlewares/runtime.rb +58 -0
  73. data/lib/cmdx/middlewares/timeout.rb +48 -68
  74. data/lib/cmdx/railtie.rb +12 -45
  75. data/lib/cmdx/result.rb +229 -314
  76. data/lib/cmdx/task.rb +194 -366
  77. data/lib/cmdx/utils/call.rb +49 -0
  78. data/lib/cmdx/utils/condition.rb +71 -0
  79. data/lib/cmdx/utils/format.rb +61 -0
  80. data/lib/cmdx/validator_registry.rb +63 -72
  81. data/lib/cmdx/validators/exclusion.rb +38 -67
  82. data/lib/cmdx/validators/format.rb +48 -49
  83. data/lib/cmdx/validators/inclusion.rb +43 -74
  84. data/lib/cmdx/validators/length.rb +101 -162
  85. data/lib/cmdx/validators/numeric.rb +95 -170
  86. data/lib/cmdx/validators/presence.rb +37 -50
  87. data/lib/cmdx/version.rb +1 -1
  88. data/lib/cmdx/worker.rb +178 -0
  89. data/lib/cmdx/workflow.rb +85 -81
  90. data/lib/cmdx.rb +19 -13
  91. data/lib/generators/cmdx/install_generator.rb +14 -13
  92. data/lib/generators/cmdx/task_generator.rb +25 -50
  93. data/lib/generators/cmdx/templates/install.rb +11 -46
  94. data/lib/generators/cmdx/templates/task.rb.tt +3 -2
  95. data/lib/locales/en.yml +18 -4
  96. data/src/cmdx-logo.png +0 -0
  97. metadata +32 -116
  98. data/docs/ai_prompts.md +0 -393
  99. data/docs/basics/call.md +0 -317
  100. data/docs/configuration.md +0 -344
  101. data/docs/parameters/coercions.md +0 -396
  102. data/docs/parameters/defaults.md +0 -335
  103. data/docs/parameters/definitions.md +0 -446
  104. data/docs/parameters/namespacing.md +0 -378
  105. data/docs/parameters/validations.md +0 -405
  106. data/docs/testing.md +0 -553
  107. data/lib/cmdx/callback.rb +0 -53
  108. data/lib/cmdx/chain_inspector.rb +0 -56
  109. data/lib/cmdx/chain_serializer.rb +0 -63
  110. data/lib/cmdx/coercion.rb +0 -57
  111. data/lib/cmdx/coercions/virtual.rb +0 -29
  112. data/lib/cmdx/core_ext/hash.rb +0 -83
  113. data/lib/cmdx/core_ext/module.rb +0 -98
  114. data/lib/cmdx/core_ext/object.rb +0 -125
  115. data/lib/cmdx/correlator.rb +0 -122
  116. data/lib/cmdx/error.rb +0 -67
  117. data/lib/cmdx/fault.rb +0 -140
  118. data/lib/cmdx/immutator.rb +0 -52
  119. data/lib/cmdx/lazy_struct.rb +0 -246
  120. data/lib/cmdx/log_formatters/pretty_json.rb +0 -40
  121. data/lib/cmdx/log_formatters/pretty_key_value.rb +0 -38
  122. data/lib/cmdx/log_formatters/pretty_line.rb +0 -41
  123. data/lib/cmdx/logger.rb +0 -49
  124. data/lib/cmdx/logger_ansi.rb +0 -68
  125. data/lib/cmdx/logger_serializer.rb +0 -116
  126. data/lib/cmdx/middleware.rb +0 -70
  127. data/lib/cmdx/parameter.rb +0 -312
  128. data/lib/cmdx/parameter_evaluator.rb +0 -231
  129. data/lib/cmdx/parameter_inspector.rb +0 -66
  130. data/lib/cmdx/parameter_registry.rb +0 -106
  131. data/lib/cmdx/parameter_serializer.rb +0 -59
  132. data/lib/cmdx/result_ansi.rb +0 -71
  133. data/lib/cmdx/result_inspector.rb +0 -71
  134. data/lib/cmdx/result_logger.rb +0 -59
  135. data/lib/cmdx/result_serializer.rb +0 -104
  136. data/lib/cmdx/rspec/matchers.rb +0 -28
  137. data/lib/cmdx/rspec/result_matchers/be_executed.rb +0 -42
  138. data/lib/cmdx/rspec/result_matchers/be_failed_task.rb +0 -94
  139. data/lib/cmdx/rspec/result_matchers/be_skipped_task.rb +0 -94
  140. data/lib/cmdx/rspec/result_matchers/be_state_matchers.rb +0 -59
  141. data/lib/cmdx/rspec/result_matchers/be_status_matchers.rb +0 -57
  142. data/lib/cmdx/rspec/result_matchers/be_successful_task.rb +0 -87
  143. data/lib/cmdx/rspec/result_matchers/have_bad_outcome.rb +0 -51
  144. data/lib/cmdx/rspec/result_matchers/have_caused_failure.rb +0 -58
  145. data/lib/cmdx/rspec/result_matchers/have_chain_index.rb +0 -59
  146. data/lib/cmdx/rspec/result_matchers/have_context.rb +0 -86
  147. data/lib/cmdx/rspec/result_matchers/have_empty_metadata.rb +0 -54
  148. data/lib/cmdx/rspec/result_matchers/have_good_outcome.rb +0 -52
  149. data/lib/cmdx/rspec/result_matchers/have_metadata.rb +0 -114
  150. data/lib/cmdx/rspec/result_matchers/have_preserved_context.rb +0 -66
  151. data/lib/cmdx/rspec/result_matchers/have_received_thrown_failure.rb +0 -64
  152. data/lib/cmdx/rspec/result_matchers/have_runtime.rb +0 -78
  153. data/lib/cmdx/rspec/result_matchers/have_thrown_failure.rb +0 -76
  154. data/lib/cmdx/rspec/task_matchers/be_well_formed_task.rb +0 -62
  155. data/lib/cmdx/rspec/task_matchers/have_callback.rb +0 -85
  156. data/lib/cmdx/rspec/task_matchers/have_cmd_setting.rb +0 -68
  157. data/lib/cmdx/rspec/task_matchers/have_executed_callbacks.rb +0 -92
  158. data/lib/cmdx/rspec/task_matchers/have_middleware.rb +0 -46
  159. data/lib/cmdx/rspec/task_matchers/have_parameter.rb +0 -181
  160. data/lib/cmdx/task_deprecator.rb +0 -58
  161. data/lib/cmdx/task_processor.rb +0 -246
  162. data/lib/cmdx/task_serializer.rb +0 -57
  163. data/lib/cmdx/utils/ansi_color.rb +0 -73
  164. data/lib/cmdx/utils/log_timestamp.rb +0 -36
  165. data/lib/cmdx/utils/monotonic_runtime.rb +0 -34
  166. data/lib/cmdx/utils/name_affix.rb +0 -52
  167. data/lib/cmdx/validator.rb +0 -57
  168. data/lib/generators/cmdx/templates/workflow.rb.tt +0 -7
  169. data/lib/generators/cmdx/workflow_generator.rb +0 -84
  170. data/lib/locales/ar.yml +0 -35
  171. data/lib/locales/cs.yml +0 -35
  172. data/lib/locales/da.yml +0 -35
  173. data/lib/locales/de.yml +0 -35
  174. data/lib/locales/el.yml +0 -35
  175. data/lib/locales/es.yml +0 -35
  176. data/lib/locales/fi.yml +0 -35
  177. data/lib/locales/fr.yml +0 -35
  178. data/lib/locales/he.yml +0 -35
  179. data/lib/locales/hi.yml +0 -35
  180. data/lib/locales/it.yml +0 -35
  181. data/lib/locales/ja.yml +0 -35
  182. data/lib/locales/ko.yml +0 -35
  183. data/lib/locales/nl.yml +0 -35
  184. data/lib/locales/no.yml +0 -35
  185. data/lib/locales/pl.yml +0 -35
  186. data/lib/locales/pt.yml +0 -35
  187. data/lib/locales/ru.yml +0 -35
  188. data/lib/locales/sv.yml +0 -35
  189. data/lib/locales/th.yml +0 -35
  190. data/lib/locales/tr.yml +0 -35
  191. data/lib/locales/vi.yml +0 -35
  192. data/lib/locales/zh.yml +0 -35
@@ -1,405 +0,0 @@
1
- # Parameters - Validations
2
-
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
-
5
- ## Table of Contents
6
-
7
- - [TLDR](#tldr)
8
- - [Common Options](#common-options)
9
- - [Presence](#presence)
10
- - [Format](#format)
11
- - [Inclusion](#inclusion)
12
- - [Exclusion](#exclusion)
13
- - [Length](#length)
14
- - [Numeric](#numeric)
15
- - [Error Handling](#error-handling)
16
- - [Conditional Validation](#conditional-validation)
17
-
18
- ## TLDR
19
-
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 }
25
-
26
- # Conditional validation
27
- optional :phone, presence: { if: :phone_required? }
28
- required :age, numeric: { min: 18, unless: :minor_allowed? }
29
-
30
- # Custom messages
31
- required :username, exclusion: { in: %w[admin root], message: "reserved name" }
32
- ```
33
-
34
- ## Common Options
35
-
36
- > [!NOTE]
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 |
47
-
48
- ## Presence
49
-
50
- Validates that parameter values are not empty using intelligent type checking:
51
-
52
- - **Strings**: Must contain non-whitespace characters
53
- - **Collections**: Must not be empty (arrays, hashes, sets)
54
- - **Other objects**: Must not be `nil`
55
-
56
- > [!TIP]
57
- > For boolean fields accepting `true` and `false`, use `inclusion: { in: [true, false] }` instead of presence validation.
58
-
59
- ```ruby
60
- class CreateUserTask < CMDx::Task
61
- required :email, presence: true
62
- required :name, presence: { message: "cannot be blank" }
63
- required :active, inclusion: { in: [true, false] }
64
-
65
- def call
66
- User.create!(email: email, name: name, active: active)
67
- end
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"
76
- ```
77
-
78
- ## Format
79
-
80
- Validates parameter values against regular expression patterns. Supports positive matching (`with`), negative matching (`without`), or both.
81
-
82
- ```ruby
83
- class RegisterUserTask < CMDx::Task
84
- required :email, format: { with: /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i }
85
- required :username, format: { without: /\A(admin|root|system)\z/i }
86
-
87
- optional :password, format: {
88
- with: /\A(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}\z/,
89
- without: /password|123456/i,
90
- if: :secure_password_required?
91
- }
92
-
93
- def call
94
- create_user_account
95
- end
96
-
97
- private
98
-
99
- def secure_password_required?
100
- context.security_policy.enforce_strong_passwords?
101
- end
102
- end
103
- ```
104
-
105
- **Options:**
106
-
107
- | Option | Description |
108
- |--------|-------------|
109
- | `:with` | Regular expression that value must match |
110
- | `:without` | Regular expression that value must not match |
111
-
112
- ## Inclusion
113
-
114
- > [!IMPORTANT]
115
- > Validates that parameter values are within a specific set of allowed values (array, range, or other enumerable).
116
-
117
- ```ruby
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
- }
126
-
127
- def call
128
- update_order_attributes
129
- end
130
-
131
- private
132
-
133
- def digital_product?
134
- context.order.items.all?(&:digital?)
135
- end
136
- end
137
- ```
138
-
139
- **Options:**
140
-
141
- | Option | Description |
142
- |--------|-------------|
143
- | `:in` | Enumerable of allowed values |
144
- | `:within` | Alias for `:in` |
145
-
146
- **Custom Error Messages:**
147
-
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}") |
152
- | `:within_message` | Alias for `:in_message` |
153
-
154
- ## Exclusion
155
-
156
- Validates that parameter values are not within a specific set of forbidden values.
157
-
158
- ```ruby
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"
166
- }
167
-
168
- def call
169
- charge_payment_method
170
- end
171
- end
172
-
173
- # Valid usage
174
- ProcessPaymentTask.call(
175
- payment_method: "credit_card",
176
- amount: 29.99,
177
- promo_code: "SAVE20"
178
- )
179
- ```
180
-
181
- **Options:**
182
-
183
- | Option | Description |
184
- |--------|-------------|
185
- | `:in` | Enumerable of forbidden values |
186
- | `:within` | Alias for `:in` |
187
-
188
- **Custom Error Messages:**
189
-
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}") |
194
- | `:within_message` | Alias for `:in_message` |
195
-
196
- ## Length
197
-
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.
199
-
200
- ```ruby
201
- class CreatePostTask < CMDx::Task
202
- required :title, length: { within: 5..100 }
203
- required :content, length: { min: 50 }
204
- required :slug, length: { min: 3, max: 50 }
205
-
206
- optional :summary, length: { max: 200, allow_nil: true }
207
- optional :category_code, length: { is: 3 }
208
-
209
- def call
210
- Post.create!(title: title, content: content, slug: slug)
211
- end
212
- end
213
- ```
214
-
215
- **Constraint Options:**
216
-
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 |
225
-
226
- **Error Messages:**
227
-
228
- | Option | Description |
229
- |--------|-------------|
230
- | `:within_message` | "length must be within %{min} and %{max}" |
231
- | `:not_within_message` | "length must not be within %{min} and %{max}" |
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}" |
236
-
237
- ## Numeric
238
-
239
- Validates numeric values against constraints. Works with any numeric type including integers, floats, and decimals.
240
-
241
- ```ruby
242
- class ProcessOrderTask < CMDx::Task
243
- required :quantity, numeric: { within: 1..100 }
244
- required :price, numeric: { min: 0.01 }
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 }
249
-
250
- def call
251
- calculate_order_total
252
- end
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"
262
- ```
263
-
264
- **Constraint Options:**
265
-
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 |
274
-
275
- ## Error Handling
276
-
277
- > [!WARNING]
278
- > Validation failures cause tasks to enter a failed state with detailed error information including parameter paths and specific violation messages.
279
-
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 }
285
-
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
318
-
319
- ```ruby
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
334
- end
335
-
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
400
- ```
401
-
402
- ---
403
-
404
- - **Prev:** [Parameters - Coercions](coercions.md)
405
- - **Next:** [Parameters - Defaults](defaults.md)