cmdx 1.1.1 → 1.5.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 (193) 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 +56 -1
  8. data/.irbrc +6 -0
  9. data/.rubocop.yml +29 -18
  10. data/.ruby-version +1 -1
  11. data/CHANGELOG.md +6 -128
  12. data/LLM.md +3317 -0
  13. data/README.md +68 -44
  14. data/docs/attributes/coercions.md +162 -0
  15. data/docs/attributes/defaults.md +90 -0
  16. data/docs/attributes/definitions.md +281 -0
  17. data/docs/attributes/naming.md +78 -0
  18. data/docs/attributes/validations.md +309 -0
  19. data/docs/basics/chain.md +56 -249
  20. data/docs/basics/context.md +56 -289
  21. data/docs/basics/execution.md +114 -0
  22. data/docs/basics/setup.md +37 -334
  23. data/docs/callbacks.md +89 -467
  24. data/docs/deprecation.md +91 -174
  25. data/docs/getting_started.md +212 -202
  26. data/docs/internationalization.md +11 -647
  27. data/docs/interruptions/exceptions.md +23 -198
  28. data/docs/interruptions/faults.md +71 -151
  29. data/docs/interruptions/halt.md +109 -186
  30. data/docs/logging.md +44 -256
  31. data/docs/middlewares.md +113 -426
  32. data/docs/outcomes/result.md +81 -228
  33. data/docs/outcomes/states.md +33 -221
  34. data/docs/outcomes/statuses.md +21 -311
  35. data/docs/tips_and_tricks.md +120 -70
  36. data/docs/workflows.md +99 -283
  37. data/lib/cmdx/.DS_Store +0 -0
  38. data/lib/cmdx/attribute.rb +229 -0
  39. data/lib/cmdx/attribute_registry.rb +94 -0
  40. data/lib/cmdx/attribute_value.rb +193 -0
  41. data/lib/cmdx/callback_registry.rb +69 -77
  42. data/lib/cmdx/chain.rb +56 -73
  43. data/lib/cmdx/coercion_registry.rb +52 -68
  44. data/lib/cmdx/coercions/array.rb +19 -18
  45. data/lib/cmdx/coercions/big_decimal.rb +20 -24
  46. data/lib/cmdx/coercions/boolean.rb +26 -25
  47. data/lib/cmdx/coercions/complex.rb +21 -22
  48. data/lib/cmdx/coercions/date.rb +25 -23
  49. data/lib/cmdx/coercions/date_time.rb +24 -25
  50. data/lib/cmdx/coercions/float.rb +25 -22
  51. data/lib/cmdx/coercions/hash.rb +31 -32
  52. data/lib/cmdx/coercions/integer.rb +30 -24
  53. data/lib/cmdx/coercions/rational.rb +29 -24
  54. data/lib/cmdx/coercions/string.rb +19 -22
  55. data/lib/cmdx/coercions/symbol.rb +37 -0
  56. data/lib/cmdx/coercions/time.rb +26 -25
  57. data/lib/cmdx/configuration.rb +49 -108
  58. data/lib/cmdx/context.rb +222 -44
  59. data/lib/cmdx/deprecator.rb +61 -0
  60. data/lib/cmdx/errors.rb +42 -252
  61. data/lib/cmdx/exceptions.rb +39 -0
  62. data/lib/cmdx/faults.rb +78 -39
  63. data/lib/cmdx/freezer.rb +51 -0
  64. data/lib/cmdx/identifier.rb +30 -0
  65. data/lib/cmdx/locale.rb +52 -0
  66. data/lib/cmdx/log_formatters/json.rb +21 -22
  67. data/lib/cmdx/log_formatters/key_value.rb +20 -22
  68. data/lib/cmdx/log_formatters/line.rb +15 -22
  69. data/lib/cmdx/log_formatters/logstash.rb +22 -23
  70. data/lib/cmdx/log_formatters/raw.rb +16 -22
  71. data/lib/cmdx/middleware_registry.rb +70 -74
  72. data/lib/cmdx/middlewares/correlate.rb +90 -54
  73. data/lib/cmdx/middlewares/runtime.rb +58 -0
  74. data/lib/cmdx/middlewares/timeout.rb +48 -68
  75. data/lib/cmdx/railtie.rb +12 -45
  76. data/lib/cmdx/result.rb +229 -314
  77. data/lib/cmdx/task.rb +194 -366
  78. data/lib/cmdx/utils/call.rb +49 -0
  79. data/lib/cmdx/utils/condition.rb +71 -0
  80. data/lib/cmdx/utils/format.rb +61 -0
  81. data/lib/cmdx/validator_registry.rb +63 -72
  82. data/lib/cmdx/validators/exclusion.rb +38 -67
  83. data/lib/cmdx/validators/format.rb +48 -49
  84. data/lib/cmdx/validators/inclusion.rb +43 -74
  85. data/lib/cmdx/validators/length.rb +91 -154
  86. data/lib/cmdx/validators/numeric.rb +87 -162
  87. data/lib/cmdx/validators/presence.rb +37 -50
  88. data/lib/cmdx/version.rb +1 -1
  89. data/lib/cmdx/worker.rb +178 -0
  90. data/lib/cmdx/workflow.rb +85 -81
  91. data/lib/cmdx.rb +19 -13
  92. data/lib/generators/cmdx/install_generator.rb +14 -13
  93. data/lib/generators/cmdx/task_generator.rb +25 -50
  94. data/lib/generators/cmdx/templates/install.rb +11 -46
  95. data/lib/generators/cmdx/templates/task.rb.tt +3 -2
  96. data/lib/locales/en.yml +18 -4
  97. data/src/cmdx-logo.png +0 -0
  98. metadata +32 -116
  99. data/docs/ai_prompts.md +0 -393
  100. data/docs/basics/call.md +0 -317
  101. data/docs/configuration.md +0 -344
  102. data/docs/parameters/coercions.md +0 -396
  103. data/docs/parameters/defaults.md +0 -335
  104. data/docs/parameters/definitions.md +0 -446
  105. data/docs/parameters/namespacing.md +0 -378
  106. data/docs/parameters/validations.md +0 -405
  107. data/docs/testing.md +0 -553
  108. data/lib/cmdx/callback.rb +0 -53
  109. data/lib/cmdx/chain_inspector.rb +0 -56
  110. data/lib/cmdx/chain_serializer.rb +0 -63
  111. data/lib/cmdx/coercion.rb +0 -57
  112. data/lib/cmdx/coercions/virtual.rb +0 -29
  113. data/lib/cmdx/core_ext/hash.rb +0 -83
  114. data/lib/cmdx/core_ext/module.rb +0 -98
  115. data/lib/cmdx/core_ext/object.rb +0 -125
  116. data/lib/cmdx/correlator.rb +0 -122
  117. data/lib/cmdx/error.rb +0 -60
  118. data/lib/cmdx/fault.rb +0 -140
  119. data/lib/cmdx/immutator.rb +0 -52
  120. data/lib/cmdx/lazy_struct.rb +0 -246
  121. data/lib/cmdx/log_formatters/pretty_json.rb +0 -40
  122. data/lib/cmdx/log_formatters/pretty_key_value.rb +0 -38
  123. data/lib/cmdx/log_formatters/pretty_line.rb +0 -41
  124. data/lib/cmdx/logger.rb +0 -49
  125. data/lib/cmdx/logger_ansi.rb +0 -68
  126. data/lib/cmdx/logger_serializer.rb +0 -116
  127. data/lib/cmdx/middleware.rb +0 -70
  128. data/lib/cmdx/parameter.rb +0 -312
  129. data/lib/cmdx/parameter_evaluator.rb +0 -231
  130. data/lib/cmdx/parameter_inspector.rb +0 -66
  131. data/lib/cmdx/parameter_registry.rb +0 -106
  132. data/lib/cmdx/parameter_serializer.rb +0 -59
  133. data/lib/cmdx/result_ansi.rb +0 -71
  134. data/lib/cmdx/result_inspector.rb +0 -71
  135. data/lib/cmdx/result_logger.rb +0 -59
  136. data/lib/cmdx/result_serializer.rb +0 -104
  137. data/lib/cmdx/rspec/matchers.rb +0 -28
  138. data/lib/cmdx/rspec/result_matchers/be_executed.rb +0 -42
  139. data/lib/cmdx/rspec/result_matchers/be_failed_task.rb +0 -94
  140. data/lib/cmdx/rspec/result_matchers/be_skipped_task.rb +0 -94
  141. data/lib/cmdx/rspec/result_matchers/be_state_matchers.rb +0 -59
  142. data/lib/cmdx/rspec/result_matchers/be_status_matchers.rb +0 -57
  143. data/lib/cmdx/rspec/result_matchers/be_successful_task.rb +0 -87
  144. data/lib/cmdx/rspec/result_matchers/have_bad_outcome.rb +0 -51
  145. data/lib/cmdx/rspec/result_matchers/have_caused_failure.rb +0 -58
  146. data/lib/cmdx/rspec/result_matchers/have_chain_index.rb +0 -59
  147. data/lib/cmdx/rspec/result_matchers/have_context.rb +0 -86
  148. data/lib/cmdx/rspec/result_matchers/have_empty_metadata.rb +0 -54
  149. data/lib/cmdx/rspec/result_matchers/have_good_outcome.rb +0 -52
  150. data/lib/cmdx/rspec/result_matchers/have_metadata.rb +0 -114
  151. data/lib/cmdx/rspec/result_matchers/have_preserved_context.rb +0 -66
  152. data/lib/cmdx/rspec/result_matchers/have_received_thrown_failure.rb +0 -64
  153. data/lib/cmdx/rspec/result_matchers/have_runtime.rb +0 -78
  154. data/lib/cmdx/rspec/result_matchers/have_thrown_failure.rb +0 -76
  155. data/lib/cmdx/rspec/task_matchers/be_well_formed_task.rb +0 -62
  156. data/lib/cmdx/rspec/task_matchers/have_callback.rb +0 -85
  157. data/lib/cmdx/rspec/task_matchers/have_cmd_setting.rb +0 -68
  158. data/lib/cmdx/rspec/task_matchers/have_executed_callbacks.rb +0 -92
  159. data/lib/cmdx/rspec/task_matchers/have_middleware.rb +0 -46
  160. data/lib/cmdx/rspec/task_matchers/have_parameter.rb +0 -181
  161. data/lib/cmdx/task_deprecator.rb +0 -52
  162. data/lib/cmdx/task_processor.rb +0 -246
  163. data/lib/cmdx/task_serializer.rb +0 -57
  164. data/lib/cmdx/utils/ansi_color.rb +0 -73
  165. data/lib/cmdx/utils/log_timestamp.rb +0 -36
  166. data/lib/cmdx/utils/monotonic_runtime.rb +0 -34
  167. data/lib/cmdx/utils/name_affix.rb +0 -52
  168. data/lib/cmdx/validator.rb +0 -57
  169. data/lib/generators/cmdx/templates/workflow.rb.tt +0 -7
  170. data/lib/generators/cmdx/workflow_generator.rb +0 -84
  171. data/lib/locales/ar.yml +0 -35
  172. data/lib/locales/cs.yml +0 -35
  173. data/lib/locales/da.yml +0 -35
  174. data/lib/locales/de.yml +0 -35
  175. data/lib/locales/el.yml +0 -35
  176. data/lib/locales/es.yml +0 -35
  177. data/lib/locales/fi.yml +0 -35
  178. data/lib/locales/fr.yml +0 -35
  179. data/lib/locales/he.yml +0 -35
  180. data/lib/locales/hi.yml +0 -35
  181. data/lib/locales/it.yml +0 -35
  182. data/lib/locales/ja.yml +0 -35
  183. data/lib/locales/ko.yml +0 -35
  184. data/lib/locales/nl.yml +0 -35
  185. data/lib/locales/no.yml +0 -35
  186. data/lib/locales/pl.yml +0 -35
  187. data/lib/locales/pt.yml +0 -35
  188. data/lib/locales/ru.yml +0 -35
  189. data/lib/locales/sv.yml +0 -35
  190. data/lib/locales/th.yml +0 -35
  191. data/lib/locales/tr.yml +0 -35
  192. data/lib/locales/vi.yml +0 -35
  193. 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)