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
@@ -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)