cmdx 1.1.2 → 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 (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 +56 -1
  8. data/.irbrc +6 -0
  9. data/.rubocop.yml +29 -18
  10. data/CHANGELOG.md +5 -133
  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 +91 -154
  85. data/lib/cmdx/validators/numeric.rb +87 -162
  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
@@ -0,0 +1,78 @@
1
+ # Attributes - Naming
2
+
3
+ Attribute naming provides method name customization to prevent conflicts and enable flexible attribute access patterns. When attributes share names with existing methods or when multiple attributes from different sources have the same name, affixing ensures clean method resolution within tasks.
4
+
5
+ > [!NOTE]
6
+ > Affixing modifies only the generated accessor method names within tasks.
7
+
8
+ ## Table of Contents
9
+
10
+ - [Prefix](#prefix)
11
+ - [Suffix](#suffix)
12
+ - [As](#as)
13
+
14
+ ## Prefix
15
+
16
+ Adds a prefix to the generated accessor method name.
17
+
18
+ ```ruby
19
+ class GenerateReport < CMDx::Task
20
+ # Dynamic from attribute source
21
+ attribute :template, prefix: true
22
+
23
+ # Static
24
+ attribute :format, prefix: "report_"
25
+
26
+ def work
27
+ context_template #=> "monthly_sales"
28
+ report_format #=> "pdf"
29
+ end
30
+ end
31
+
32
+ # Attributes passed as original attribute names
33
+ GenerateReport.execute(template: "monthly_sales", format: "pdf")
34
+ ```
35
+
36
+ ## Suffix
37
+
38
+ Adds a suffix to the generated accessor method name.
39
+
40
+ ```ruby
41
+ class DeployApplication < CMDx::Task
42
+ # Dynamic from attribute source
43
+ attribute :branch, suffix: true
44
+
45
+ # Static
46
+ attribute :version, suffix: "_tag"
47
+
48
+ def work
49
+ branch_context #=> "main"
50
+ version_tag #=> "v1.2.3"
51
+ end
52
+ end
53
+
54
+ # Attributes passed as original attribute names
55
+ DeployApplication.execute(branch: "main", version: "v1.2.3")
56
+ ```
57
+
58
+ ## As
59
+
60
+ Completely renames the generated accessor method.
61
+
62
+ ```ruby
63
+ class ScheduleMaintenance < CMDx::Task
64
+ attribute :scheduled_at, as: :when
65
+
66
+ def work
67
+ when #=> <DateTime>
68
+ end
69
+ end
70
+
71
+ # Attributes passed as original attribute names
72
+ ScheduleMaintenance.execute(scheduled_at: DateTime.new(2024, 12, 15, 2, 0, 0))
73
+ ```
74
+
75
+ ---
76
+
77
+ - **Prev:** [Attributes - Definitions](definitions.md)
78
+ - **Next:** [Attributes - Coercions](coercions.md)
@@ -0,0 +1,309 @@
1
+ # Attributes - Validations
2
+
3
+ Attribute validations ensure task arguments meet specified requirements before execution begins. Validations run after coercions and provide declarative rules for data integrity, supporting both built-in validators and custom validation logic.
4
+
5
+ ## Table of Contents
6
+
7
+ - [Usage](#usage)
8
+ - [Built-in Validators](#built-in-validators)
9
+ - [Common Options](#common-options)
10
+ - [Exclusion](#exclusion)
11
+ - [Format](#format)
12
+ - [Inclusion](#inclusion)
13
+ - [Length](#length)
14
+ - [Numeric](#numeric)
15
+ - [Presence](#presence)
16
+ - [Declarations](#declarations)
17
+ - [Proc or Lambda](#proc-or-lambda)
18
+ - [Class or Module](#class-or-module)
19
+ - [Removals](#removals)
20
+ - [Error Handling](#error-handling)
21
+
22
+ ## Usage
23
+
24
+ Define validation rules on attributes to enforce data requirements:
25
+
26
+ ```ruby
27
+ class ProcessSubscription < CMDx::Task
28
+ # Required field with presence validation
29
+ attribute :user_id, presence: true
30
+
31
+ # String with length constraints
32
+ attribute :preferences, length: { minimum: 10, maximum: 500 }
33
+
34
+ # Numeric range validation
35
+ attribute :tier_level, inclusion: { in: 1..5 }
36
+
37
+ # Format validation for email
38
+ attribute :contact_email, format: /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
39
+
40
+ def work
41
+ user_id #=> "98765"
42
+ preferences #=> "Send weekly digest emails"
43
+ tier_level #=> 3
44
+ contact_email #=> "user@company.com"
45
+ end
46
+ end
47
+
48
+ ProcessSubscription.execute(
49
+ user_id: "98765",
50
+ preferences: "Send weekly digest emails",
51
+ tier_level: 3,
52
+ contact_email: "user@company.com"
53
+ )
54
+ ```
55
+
56
+ > [!TIP]
57
+ > Validations run after coercions, so you can validate the final coerced values rather than raw input.
58
+
59
+ ## Built-in Validators
60
+
61
+ ### Common Options
62
+
63
+ This list of options is available to all validators:
64
+
65
+ | Option | Description |
66
+ |--------|-------------|
67
+ | `:allow_nil` | Skip validation when value is `nil` |
68
+ | `:if` | Symbol, proc, lambda, or callable determining when to validate |
69
+ | `:unless` | Symbol, proc, lambda, or callable determining when to skip validation |
70
+ | `:message` | Custom error message for validation failures |
71
+
72
+ ### Exclusion
73
+
74
+ ```ruby
75
+ class ProcessProduct < CMDx::Task
76
+ attribute :status, exclusion: { in: %w[recalled archived] }
77
+
78
+ def work
79
+ # Your logic here...
80
+ end
81
+ end
82
+ ```
83
+
84
+ | Options | Description |
85
+ |---------|-------------|
86
+ | `:in` | The collection of forbidden values or range |
87
+ | `:within` | Alias for :in option |
88
+ | `:of_message` | Custom message for discrete value exclusions |
89
+ | `:in_message` | Custom message for range-based exclusions |
90
+ | `:within_message` | Alias for :in_message option |
91
+
92
+ ### Format
93
+
94
+ ```ruby
95
+ class ProcessProduct < CMDx::Task
96
+ attribute :sku, format: /\A[A-Z]{3}-[0-9]{4}\z/
97
+
98
+ attribute :sku, format: { with: /\A[A-Z]{3}-[0-9]{4}\z/ }
99
+
100
+ def work
101
+ # Your logic here...
102
+ end
103
+ end
104
+ ```
105
+
106
+ | Options | Description |
107
+ |---------|-------------|
108
+ | `regexp` | Alias for :with option |
109
+ | `:with` | Regex pattern that the value must match |
110
+ | `:without` | Regex pattern that the value must not match |
111
+
112
+ ### Inclusion
113
+
114
+ ```ruby
115
+ class ProcessProduct < CMDx::Task
116
+ attribute :availability, inclusion: { in: %w[available limited] }
117
+
118
+ def work
119
+ # Your logic here...
120
+ end
121
+ end
122
+ ```
123
+
124
+ | Options | Description |
125
+ |---------|-------------|
126
+ | `:in` | The collection of allowed values or range |
127
+ | `:within` | Alias for :in option |
128
+ | `:of_message` | Custom message for discrete value inclusions |
129
+ | `:in_message` | Custom message for range-based inclusions |
130
+ | `:within_message` | Alias for :in_message option |
131
+
132
+ ### Length
133
+
134
+ ```ruby
135
+ class CreateBlogPost < CMDx::Task
136
+ attribute :title, length: { within: 5..100 }
137
+
138
+ def work
139
+ # Your logic here...
140
+ end
141
+ end
142
+ ```
143
+
144
+ | Options | Description |
145
+ |---------|-------------|
146
+ | `:within` | Range that the length must fall within (inclusive) |
147
+ | `:not_within` | Range that the length must not fall within |
148
+ | `:in` | Alias for :within |
149
+ | `:not_in` | Range that the length must not fall within |
150
+ | `:min` | Minimum allowed length |
151
+ | `:max` | Maximum allowed length |
152
+ | `:is` | Exact required length |
153
+ | `:is_not` | Length that is not allowed |
154
+ | `:within_message` | Custom message for within/range validations |
155
+ | `:in_message` | Custom message for :in validation |
156
+ | `:not_within_message` | Custom message for not_within validation |
157
+ | `:not_in_message` | Custom message for not_in validation |
158
+ | `:min_message` | Custom message for minimum length validation |
159
+ | `:max_message` | Custom message for maximum length validation |
160
+ | `:is_message` | Custom message for exact length validation |
161
+ | `:is_not_message` | Custom message for is_not validation |
162
+
163
+ ### Numeric
164
+
165
+ ```ruby
166
+ class CreateBlogPost < CMDx::Task
167
+ attribute :word_count, numeric: { min: 100 }
168
+
169
+ def work
170
+ # Your logic here...
171
+ end
172
+ end
173
+ ```
174
+
175
+ | Options | Description |
176
+ |---------|-------------|
177
+ | `:within` | Range that the value must fall within (inclusive) |
178
+ | `:not_within` | Range that the value must not fall within |
179
+ | `:in` | Alias for :within option |
180
+ | `:not_in` | Alias for :not_within option |
181
+ | `:min` | Minimum allowed value (inclusive, >=) |
182
+ | `:max` | Maximum allowed value (inclusive, <=) |
183
+ | `:is` | Exact value that must match |
184
+ | `:is_not` | Value that must not match |
185
+ | `:within_message` | Custom message for range validations |
186
+ | `:not_within_message` | Custom message for exclusion validations |
187
+ | `:min_message` | Custom message for minimum validation |
188
+ | `:max_message` | Custom message for maximum validation |
189
+ | `:is_message` | Custom message for exact match validation |
190
+ | `:is_not_message` | Custom message for exclusion validation |
191
+
192
+ ### Presence
193
+
194
+ ```ruby
195
+ class CreateBlogPost < CMDx::Task
196
+ attribute :content, presence: true
197
+
198
+ attribute :content, presence: { message: "cannot be blank" }
199
+
200
+ def work
201
+ # Your logic here...
202
+ end
203
+ end
204
+ ```
205
+
206
+ | Options | Description |
207
+ |---------|-------------|
208
+ | `true` | Ensures value is not nil, empty string, or whitespace |
209
+
210
+ ## Declarations
211
+
212
+ > [!IMPORTANT]
213
+ > Custom validators must raise a `CMDx::ValidationError` and its message is used as part of the fault reason and metadata.
214
+
215
+ ### Proc or Lambda
216
+
217
+ Use anonymous functions for simple validation logic:
218
+
219
+ ```ruby
220
+ class SetupApplication < CMDx::Task
221
+ # Proc
222
+ register :validator, :api_key, proc do |value, options = {}|
223
+ unless value.match?(/\A[a-zA-Z0-9]{32}\z/)
224
+ raise CMDx::ValidationError, "invalid API key format"
225
+ end
226
+ end
227
+
228
+ # Lambda
229
+ register :validator, :api_key, ->(value, options = {}) {
230
+ unless value.match?(/\A[a-zA-Z0-9]{32}\z/)
231
+ raise CMDx::ValidationError, "invalid API key format"
232
+ end
233
+ }
234
+ end
235
+ ```
236
+
237
+ ### Class or Module
238
+
239
+ Register custom validation logic for specialized requirements:
240
+
241
+ ```ruby
242
+ class ApiKeyValidator
243
+ def self.call(value, options = {})
244
+ unless value.match?(/\A[a-zA-Z0-9]{32}\z/)
245
+ raise CMDx::ValidationError, "invalid API key format"
246
+ end
247
+ end
248
+ end
249
+
250
+ class SetupApplication < CMDx::Task
251
+ register :validator, :api_key, ApiKeyValidator
252
+
253
+ attribute :access_key, api_key: true
254
+ end
255
+ ```
256
+
257
+ ## Removals
258
+
259
+ Remove custom validators when no longer needed:
260
+
261
+ > [!WARNING]
262
+ > Only one removal operation is allowed per `deregister` call. Multiple removals require separate calls.
263
+
264
+ ```ruby
265
+ class SetupApplication < CMDx::Task
266
+ deregister :validator, :api_key
267
+ end
268
+ ```
269
+
270
+ ## Error Handling
271
+
272
+ Validation failures provide detailed error information including attribute paths, validation rules, and specific failure reasons:
273
+
274
+ ```ruby
275
+ class CreateProject < CMDx::Task
276
+ attribute :project_name, presence: true, length: { minimum: 3, maximum: 50 }
277
+ attribute :budget, numeric: { greater_than: 1000, less_than: 1000000 }
278
+ attribute :priority, inclusion: { in: [:low, :medium, :high] }
279
+ attribute :contact_email, format: /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
280
+
281
+ def work
282
+ # Your logic here...
283
+ end
284
+ end
285
+
286
+ result = CreateProject.execute(
287
+ project_name: "AB", # Too short
288
+ budget: 500, # Too low
289
+ priority: :urgent, # Not in allowed list
290
+ contact_email: "invalid-email" # Invalid format
291
+ )
292
+
293
+ result.state #=> "interrupted"
294
+ result.status #=> "failed"
295
+ result.reason #=> "project_name is too short (minimum is 3 characters). budget must be greater than 1000. priority is not included in the list. contact_email is invalid."
296
+ result.metadata #=> {
297
+ # messages: {
298
+ # project_name: ["is too short (minimum is 3 characters)"],
299
+ # budget: ["must be greater than 1000"],
300
+ # priority: ["is not included in the list"],
301
+ # contact_email: ["is invalid"]
302
+ # }
303
+ # }
304
+ ```
305
+
306
+ ---
307
+
308
+ - **Prev:** [Attributes - Coercions](coercions.md)
309
+ - **Next:** [Attributes - Defaults](defaults.md)