cmdx 0.5.0 → 1.0.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 (151) hide show
  1. checksums.yaml +4 -4
  2. data/.DS_Store +0 -0
  3. data/.cursor/rules/cursor-instructions.mdc +6 -0
  4. data/.rubocop.yml +19 -1
  5. data/.ruby-version +1 -1
  6. data/CHANGELOG.md +95 -28
  7. data/README.md +73 -25
  8. data/docs/ai_prompts.md +319 -0
  9. data/docs/basics/call.md +234 -14
  10. data/docs/basics/chain.md +280 -0
  11. data/docs/basics/context.md +241 -33
  12. data/docs/basics/setup.md +85 -12
  13. data/docs/callbacks.md +283 -0
  14. data/docs/configuration.md +155 -30
  15. data/docs/getting_started.md +145 -22
  16. data/docs/internationalization.md +148 -0
  17. data/docs/interruptions/exceptions.md +198 -11
  18. data/docs/interruptions/faults.md +196 -44
  19. data/docs/interruptions/halt.md +188 -35
  20. data/docs/logging.md +204 -53
  21. data/docs/middlewares.md +745 -0
  22. data/docs/outcomes/result.md +305 -10
  23. data/docs/outcomes/states.md +212 -31
  24. data/docs/outcomes/statuses.md +284 -30
  25. data/docs/parameters/coercions.md +411 -29
  26. data/docs/parameters/defaults.md +258 -25
  27. data/docs/parameters/definitions.md +247 -72
  28. data/docs/parameters/namespacing.md +259 -27
  29. data/docs/parameters/validations.md +173 -168
  30. data/docs/testing.md +560 -0
  31. data/docs/tips_and_tricks.md +103 -42
  32. data/docs/workflows.md +329 -0
  33. data/lib/cmdx/.DS_Store +0 -0
  34. data/lib/cmdx/callback.rb +69 -0
  35. data/lib/cmdx/callback_registry.rb +106 -0
  36. data/lib/cmdx/chain.rb +190 -0
  37. data/lib/cmdx/chain_inspector.rb +149 -0
  38. data/lib/cmdx/chain_serializer.rb +175 -0
  39. data/lib/cmdx/coercions/array.rb +37 -0
  40. data/lib/cmdx/coercions/big_decimal.rb +33 -0
  41. data/lib/cmdx/coercions/boolean.rb +41 -1
  42. data/lib/cmdx/coercions/complex.rb +31 -0
  43. data/lib/cmdx/coercions/date.rb +39 -0
  44. data/lib/cmdx/coercions/date_time.rb +39 -0
  45. data/lib/cmdx/coercions/float.rb +31 -0
  46. data/lib/cmdx/coercions/hash.rb +42 -0
  47. data/lib/cmdx/coercions/integer.rb +32 -0
  48. data/lib/cmdx/coercions/rational.rb +31 -0
  49. data/lib/cmdx/coercions/string.rb +31 -0
  50. data/lib/cmdx/coercions/time.rb +39 -0
  51. data/lib/cmdx/coercions/virtual.rb +31 -0
  52. data/lib/cmdx/configuration.rb +217 -9
  53. data/lib/cmdx/context.rb +173 -2
  54. data/lib/cmdx/core_ext/hash.rb +72 -0
  55. data/lib/cmdx/core_ext/module.rb +94 -0
  56. data/lib/cmdx/core_ext/object.rb +105 -0
  57. data/lib/cmdx/correlator.rb +217 -0
  58. data/lib/cmdx/error.rb +210 -8
  59. data/lib/cmdx/errors.rb +256 -1
  60. data/lib/cmdx/fault.rb +177 -2
  61. data/lib/cmdx/faults.rb +158 -2
  62. data/lib/cmdx/immutator.rb +121 -2
  63. data/lib/cmdx/lazy_struct.rb +261 -18
  64. data/lib/cmdx/log_formatters/json.rb +46 -0
  65. data/lib/cmdx/log_formatters/key_value.rb +46 -0
  66. data/lib/cmdx/log_formatters/line.rb +54 -0
  67. data/lib/cmdx/log_formatters/logstash.rb +64 -0
  68. data/lib/cmdx/log_formatters/pretty_json.rb +57 -0
  69. data/lib/cmdx/log_formatters/pretty_key_value.rb +51 -0
  70. data/lib/cmdx/log_formatters/pretty_line.rb +60 -0
  71. data/lib/cmdx/log_formatters/raw.rb +54 -0
  72. data/lib/cmdx/logger.rb +85 -0
  73. data/lib/cmdx/logger_ansi.rb +93 -7
  74. data/lib/cmdx/logger_serializer.rb +116 -0
  75. data/lib/cmdx/middleware.rb +74 -0
  76. data/lib/cmdx/middleware_registry.rb +106 -0
  77. data/lib/cmdx/middlewares/correlate.rb +266 -0
  78. data/lib/cmdx/middlewares/timeout.rb +232 -0
  79. data/lib/cmdx/parameter.rb +228 -1
  80. data/lib/cmdx/parameter_inspector.rb +61 -0
  81. data/lib/cmdx/parameter_registry.rb +125 -0
  82. data/lib/cmdx/parameter_serializer.rb +83 -0
  83. data/lib/cmdx/parameter_validator.rb +62 -0
  84. data/lib/cmdx/parameter_value.rb +109 -1
  85. data/lib/cmdx/parameters_inspector.rb +59 -0
  86. data/lib/cmdx/parameters_serializer.rb +102 -0
  87. data/lib/cmdx/railtie.rb +123 -3
  88. data/lib/cmdx/result.rb +367 -25
  89. data/lib/cmdx/result_ansi.rb +105 -9
  90. data/lib/cmdx/result_inspector.rb +76 -0
  91. data/lib/cmdx/result_logger.rb +90 -3
  92. data/lib/cmdx/result_serializer.rb +137 -0
  93. data/lib/cmdx/rspec/result_matchers.rb +917 -0
  94. data/lib/cmdx/rspec/task_matchers.rb +570 -0
  95. data/lib/cmdx/task.rb +405 -37
  96. data/lib/cmdx/task_serializer.rb +74 -2
  97. data/lib/cmdx/utils/ansi_color.rb +95 -0
  98. data/lib/cmdx/utils/log_timestamp.rb +48 -0
  99. data/lib/cmdx/utils/monotonic_runtime.rb +71 -4
  100. data/lib/cmdx/utils/name_affix.rb +78 -0
  101. data/lib/cmdx/validators/custom.rb +82 -0
  102. data/lib/cmdx/validators/exclusion.rb +94 -0
  103. data/lib/cmdx/validators/format.rb +102 -8
  104. data/lib/cmdx/validators/inclusion.rb +104 -0
  105. data/lib/cmdx/validators/length.rb +128 -0
  106. data/lib/cmdx/validators/numeric.rb +128 -0
  107. data/lib/cmdx/validators/presence.rb +93 -7
  108. data/lib/cmdx/version.rb +7 -1
  109. data/lib/cmdx/workflow.rb +394 -0
  110. data/lib/cmdx.rb +25 -64
  111. data/lib/generators/cmdx/install_generator.rb +37 -1
  112. data/lib/generators/cmdx/task_generator.rb +69 -1
  113. data/lib/generators/cmdx/templates/install.rb +43 -15
  114. data/lib/generators/cmdx/workflow_generator.rb +109 -0
  115. data/lib/locales/ar.yml +36 -0
  116. data/lib/locales/cs.yml +36 -0
  117. data/lib/locales/da.yml +36 -0
  118. data/lib/locales/de.yml +36 -0
  119. data/lib/locales/el.yml +36 -0
  120. data/lib/locales/en.yml +20 -20
  121. data/lib/locales/es.yml +20 -20
  122. data/lib/locales/fi.yml +36 -0
  123. data/lib/locales/fr.yml +36 -0
  124. data/lib/locales/he.yml +36 -0
  125. data/lib/locales/hi.yml +36 -0
  126. data/lib/locales/it.yml +36 -0
  127. data/lib/locales/ja.yml +36 -0
  128. data/lib/locales/ko.yml +36 -0
  129. data/lib/locales/nl.yml +36 -0
  130. data/lib/locales/no.yml +36 -0
  131. data/lib/locales/pl.yml +36 -0
  132. data/lib/locales/pt.yml +36 -0
  133. data/lib/locales/ru.yml +36 -0
  134. data/lib/locales/sv.yml +36 -0
  135. data/lib/locales/th.yml +36 -0
  136. data/lib/locales/tr.yml +36 -0
  137. data/lib/locales/vi.yml +36 -0
  138. data/lib/locales/zh.yml +36 -0
  139. metadata +77 -15
  140. data/docs/basics/run.md +0 -34
  141. data/docs/batch.md +0 -53
  142. data/docs/example.md +0 -82
  143. data/docs/hooks.md +0 -62
  144. data/lib/cmdx/batch.rb +0 -43
  145. data/lib/cmdx/parameters.rb +0 -35
  146. data/lib/cmdx/run.rb +0 -39
  147. data/lib/cmdx/run_inspector.rb +0 -26
  148. data/lib/cmdx/run_serializer.rb +0 -20
  149. data/lib/cmdx/task_hook.rb +0 -18
  150. data/lib/generators/cmdx/batch_generator.rb +0 -30
  151. /data/lib/generators/cmdx/templates/{batch.rb.tt → workflow.rb.tt} +0 -0
@@ -1,47 +1,280 @@
1
1
  # Parameters - Defaults
2
2
 
3
- Assign default values for parameters that return a `nil` value.
3
+ Parameter defaults provide fallback values when arguments are not provided or
4
+ resolve to `nil`. Defaults ensure tasks have sensible values for optional
5
+ parameters while maintaining flexibility for callers to override when needed.
6
+ Defaults work seamlessly with coercion, validation, and nested parameters.
7
+
8
+ ## Table of Contents
9
+
10
+ - [TLDR](#tldr)
11
+ - [Default Value Fundamentals](#default-value-fundamentals)
12
+ - [Fixed Value Defaults](#fixed-value-defaults)
13
+ - [Callable Defaults](#callable-defaults)
14
+ - [Defaults with Type Coercion](#defaults-with-type-coercion)
15
+ - [Defaults with Validation](#defaults-with-validation)
16
+ - [Nested Parameter Defaults](#nested-parameter-defaults)
17
+
18
+ ## TLDR
19
+
20
+ - **Defaults** - Provide fallback values when parameters not provided or are `nil`
21
+ - **Fixed values** - `default: "normal"`, `default: true`, `default: []`
22
+ - **Dynamic values** - `default: -> { Time.now }`, `default: :method_name` for callable defaults
23
+ - **With coercion** - Defaults are subject to same type coercion as provided values
24
+ - **With validation** - Defaults must pass same validation rules as provided values
25
+
26
+ ## Default Value Fundamentals
27
+
28
+ > [!NOTE]
29
+ > Defaults are specified using the `:default` option and are applied when a parameter value resolves to `nil`. This includes cases where optional parameters are not provided in call arguments or when source objects return `nil` values.
30
+
31
+ ### Fixed Value Defaults
32
+
33
+ The simplest defaults use fixed values that are applied consistently:
34
+
35
+ ```ruby
36
+ class ProcessUserOrderTask < CMDx::Task
37
+
38
+ required :user_id, type: :integer
39
+ optional :priority, type: :string, default: "normal"
40
+ optional :send_confirmation, type: :boolean, default: true
41
+ optional :max_retries, type: :integer, default: 3
42
+
43
+ optional :notification_tags, type: :array, default: []
44
+ optional :order_metadata, type: :hash, default: {}
45
+ optional :created_at, type: :datetime, default: -> { Time.now }
46
+
47
+ def call
48
+ user_id #=> provided value (required)
49
+ priority #=> "normal" if not provided
50
+ send_confirmation #=> true if not provided
51
+ max_retries #=> 3 if not provided
52
+ notification_tags #=> [] if not provided
53
+ order_metadata #=> {} if not provided
54
+ created_at #=> current time if not provided
55
+ end
56
+
57
+ end
58
+
59
+ # Defaults applied for missing parameters
60
+ ProcessUserOrderTask.call(user_id: 12345)
61
+ # priority: "normal", send_confirmation: true, max_retries: 3, etc.
62
+
63
+ # Explicit values override defaults
64
+ ProcessUserOrderTask.call(
65
+ user_id: 12345,
66
+ priority: "urgent",
67
+ send_confirmation: false,
68
+ notification_tags: ["rush_order"]
69
+ )
70
+ ```
71
+
72
+ ### Callable Defaults
73
+
74
+ > [!TIP]
75
+ > Use procs, lambdas, or method symbols for dynamic defaults that are evaluated at parameter resolution time. This is especially useful for timestamps, UUIDs, and context-dependent values.
4
76
 
5
77
  ```ruby
6
- class DetermineBoxSizeTask < CMDx::Task
78
+ class SendOrderNotificationTask < CMDx::Task
79
+
80
+ required :order_id, type: :integer
7
81
 
8
- # Fixed value
9
- required :width, default: 12
82
+ # Dynamic defaults using procs
83
+ optional :sent_at, type: :datetime, default: -> { Time.now }
84
+ optional :tracking_id, type: :string, default: -> { SecureRandom.uuid }
10
85
 
11
- # Proc or lambda
12
- optional :length, default: -> { Current.account.usa? ? 12 : 18 }
86
+ # Environment-aware defaults
87
+ optional :notification_service, type: :string, default: -> { Rails.env.production? ? "sendgrid" : "mock" }
88
+ optional :sender_email, type: :string, default: -> { Rails.application.credentials.sender_email }
13
89
 
14
- # Symbol or string
15
- optional :depth, default: :depth_by_country
90
+ # Method symbol defaults
91
+ optional :template_name, type: :string, default: :determine_template
92
+ optional :delivery_time, type: :datetime, default: :calculate_delivery_window
16
93
 
17
94
  def call
18
- width #=> 12
19
- length #=> 18
20
- depth #=> 48
95
+ sent_at #=> current time when accessed
96
+ tracking_id #=> unique UUID when accessed
97
+ notification_service #=> production or test service
98
+ sender_email #=> configured sender email
99
+ template_name #=> result of determine_template method
100
+ delivery_time #=> result of calculate_delivery_window method
21
101
  end
22
102
 
23
103
  private
24
104
 
25
- def depth_by_country
26
- case Current.account.country
27
- when "usa" then 12
28
- when "can" then 18
29
- when "mex" then 24
30
- else 48
31
- end
105
+ def determine_template
106
+ order.priority == "urgent" ? "urgent_order" : "standard_order"
107
+ end
108
+
109
+ def calculate_delivery_window
110
+ order.priority == "urgent" ? 15.minutes.from_now : 1.hour.from_now
111
+ end
112
+
113
+ def order
114
+ @order ||= Order.find(order_id)
32
115
  end
33
116
 
34
117
  end
118
+ ```
119
+
120
+ ## Defaults with Type Coercion
121
+
122
+ > [!IMPORTANT]
123
+ > Defaults work seamlessly with type coercion, with the default value being subject to the same coercion rules as provided values.
124
+
125
+ ```ruby
126
+ class ConfigureOrderSettingsTask < CMDx::Task
127
+
128
+ # String defaults coerced to integers
129
+ optional :max_items, type: :integer, default: "50"
130
+
131
+ # JSON string defaults coerced to hash
132
+ optional :shipping_config, type: :hash, default: '{"carrier": "ups", "speed": "standard"}'
35
133
 
36
- # Initializes with default values
37
- DetermineBoxSizeTask.call(width: nil, length: nil)
134
+ # String defaults coerced to arrays
135
+ optional :allowed_countries, type: :array, default: '["US", "CA", "UK"]'
136
+
137
+ # String defaults coerced to booleans
138
+ optional :require_signature, type: :boolean, default: "true"
139
+
140
+ # String defaults coerced to dates
141
+ optional :embargo_date, type: :date, default: "2024-01-01"
142
+
143
+ # Dynamic defaults with coercion
144
+ optional :order_number, type: :string, default: -> { Time.now.to_i }
145
+
146
+ def call
147
+ max_items #=> 50 (integer)
148
+ shipping_config #=> {"carrier" => "ups", "speed" => "standard"} (hash)
149
+ allowed_countries #=> ["US", "CA", "UK"] (array)
150
+ require_signature #=> true (boolean)
151
+ embargo_date #=> Date object
152
+ order_number #=> "1640995200" (string from integer)
153
+ end
154
+
155
+ end
38
156
  ```
39
157
 
40
- > [!NOTE]
41
- > Defaults are subject to coercion and validations so take care setting
42
- > the fallback value to contain valid conditions.
158
+ ## Defaults with Validation
159
+
160
+ > [!WARNING]
161
+ > Default values are subject to the same validation rules as provided values, ensuring consistency and catching configuration errors early.
162
+
163
+ ```ruby
164
+ class ValidateOrderPriorityTask < CMDx::Task
165
+
166
+ required :order_id, type: :integer
167
+
168
+ # Default must pass inclusion validation
169
+ optional :priority, type: :string, default: "standard",
170
+ inclusion: { in: %w[low standard high urgent] }
171
+
172
+ # Numeric default with range validation
173
+ optional :processing_timeout, type: :integer, default: 300,
174
+ numeric: { min: 60, max: 3600 }
175
+
176
+ # Email default with format validation
177
+ optional :escalation_email, type: :string,
178
+ default: -> { "support@#{Rails.application.config.domain}" },
179
+ format: { with: /@/ }
180
+
181
+ # Custom validation with default
182
+ optional :approval_code, type: :string, default: :generate_approval_code,
183
+ custom: { validator: ApprovalCodeValidator }
184
+
185
+ def call
186
+ priority #=> "standard" (validated against inclusion list)
187
+ processing_timeout #=> 300 (validated within range)
188
+ escalation_email #=> support email (validated format)
189
+ approval_code #=> generated code (custom validated)
190
+ end
191
+
192
+ private
193
+
194
+ def generate_approval_code
195
+ "APV_#{SecureRandom.hex(8).upcase}"
196
+ end
197
+
198
+ end
199
+ ```
200
+
201
+ ## Nested Parameter Defaults
202
+
203
+ ```ruby
204
+ class ProcessOrderShippingTask < CMDx::Task
205
+
206
+ required :order_id, type: :integer
207
+
208
+ # Parent parameter with default
209
+ optional :shipping_details, type: :hash, default: {} do
210
+ optional :carrier, type: :string, default: "fedex"
211
+ optional :expedited, type: :boolean, default: false
212
+ optional :insurance_required, type: :boolean, default: -> { order_value > 500 }
213
+
214
+ optional :delivery_address, type: :hash, default: -> { customer_default_address } do
215
+ optional :country, type: :string, default: "US"
216
+ optional :state, type: :string, default: -> { determine_default_state }
217
+ optional :requires_appointment, type: :boolean, default: false
218
+ end
219
+ end
220
+
221
+ # Complex nested defaults
222
+ optional :notification_preferences, type: :hash, default: -> { customer_notification_defaults } do
223
+ optional :email_updates, type: :boolean, default: true
224
+ optional :sms_updates, type: :boolean, default: false
225
+
226
+ optional :delivery_window, type: :hash, default: {} do
227
+ optional :preferred_time, type: :string, default: "anytime"
228
+ optional :weekend_delivery, type: :boolean, default: false
229
+ end
230
+ end
231
+
232
+ def call
233
+ # Parent defaults applied when not provided
234
+ shipping_details #=> {} if not provided
235
+ notification_preferences #=> customer defaults if not provided
236
+
237
+ # Child defaults (when parent exists)
238
+ carrier #=> "fedex"
239
+ expedited #=> false
240
+ insurance_required #=> true if order > $500
241
+ country #=> "US"
242
+ state #=> determined by logic
243
+ email_updates #=> true
244
+ preferred_time #=> "anytime"
245
+ weekend_delivery #=> false
246
+ end
247
+
248
+ private
249
+
250
+ def order
251
+ @order ||= Order.find(order_id)
252
+ end
253
+
254
+ def order_value
255
+ order.total_amount
256
+ end
257
+
258
+ def customer_default_address
259
+ order.customer.default_shipping_address&.to_hash || {}
260
+ end
261
+
262
+ def determine_default_state
263
+ order.customer.billing_address&.state || "CA"
264
+ end
265
+
266
+ def customer_notification_defaults
267
+ prefs = order.customer.notification_preferences
268
+ {
269
+ email_updates: prefs.email_enabled?,
270
+ sms_updates: prefs.sms_enabled?
271
+ }
272
+ end
273
+
274
+ end
275
+ ```
43
276
 
44
277
  ---
45
278
 
46
- - **Prev:** [Validations](https://github.com/drexed/cmdx/blob/main/docs/parameters/validations.md)
47
- - **Next:** [Results](https://github.com/drexed/cmdx/blob/main/docs/outcomes.md)
279
+ - **Prev:** [Parameters - Validations](validations.md)
280
+ - **Next:** [Callbacks](../callbacks.md)
@@ -1,123 +1,298 @@
1
- ## Parameters - Definitions
1
+ # Parameters - Definitions
2
2
 
3
- Parameters provide a contract to verify that a task only executes if the arguments
4
- of a call match.
3
+ Parameters provide a contract to verify that task execution arguments match expected requirements and structure. They define the interface between task callers and task implementation, enabling automatic validation, type coercion, and method generation for clean parameter access within tasks.
5
4
 
6
- ## Basics
5
+ ## Table of Contents
7
6
 
8
- Parameters are defined based on methods that can be delegated to a source object
9
- (default `:context`) or keys on a hash. Parameters are automatically defined as
10
- attributes within the task instance. `optional` parameters that do not respond or
11
- missing the hash key will still delegate but return `nil` as a value.
7
+ - [TLDR](#tldr)
8
+ - [Parameter Fundamentals](#parameter-fundamentals)
9
+ - [Parameter Sources](#parameter-sources)
10
+ - [Nested Parameters](#nested-parameters)
11
+ - [Parameter Method Generation](#parameter-method-generation)
12
+ - [Error Handling](#error-handling)
12
13
 
13
- ```ruby
14
- class DetermineBoxSizeTask < CMDx::Task
14
+ ## TLDR
15
+
16
+ - **Required/Optional** - Define with `required :param` and `optional :param` class methods
17
+ - **Method generation** - Parameters become instance methods for easy access
18
+ - **Sources** - Default `:context` source, or custom with `source: :user`
19
+ - **Nested params** - Complex structures with `required :address do ... end`
20
+ - **Call interface** - Parameters passed as keyword arguments to `TaskClass.call(param: value)`
21
+
22
+ ## Parameter Fundamentals
23
+
24
+ Parameters are defined using `required` and `optional` class methods that automatically create accessor methods within task instances. Parameters are matched from call arguments and made available as instance methods.
25
+
26
+ > [!IMPORTANT]
27
+ > Required parameters must be provided in call arguments or task execution will fail.
15
28
 
16
- # Must be passed as call arguments
17
- required :material
29
+ ### Basic Parameter Definition
18
30
 
19
- # Returns value if passed as a call arguments, else returns nil
20
- optional :depth
31
+ ```ruby
32
+ class CreateOrderTask < CMDx::Task
33
+ # Must be provided in call arguments
34
+ required :order_id
35
+
36
+ # Optional - returns nil if not provided
37
+ optional :priority
21
38
 
22
- # Define multiple parameters one line
23
- optional :width, :height
39
+ # Multiple parameters in one declaration
40
+ required :customer_id, :product_id
41
+ optional :notes, :shipping_method
24
42
 
25
43
  def call
26
- material #=> "cardboard"
27
- depth #=> nil
28
- height #=> 12
29
- width #=> 24
44
+ order_id #=> 123 (from call arguments)
45
+ priority #=> "high" or nil
46
+ customer_id #=> 456 (from call arguments)
47
+ shipping_method #=> "express" or nil
30
48
  end
31
-
32
49
  end
33
50
 
34
- # Initializes local variables matching the parameter name
35
- DetermineBoxSizeTask.call(material: "cardboard", height: 12, width: 24)
51
+ # Parameters passed as keyword arguments
52
+ CreateOrderTask.call(
53
+ order_id: 123,
54
+ customer_id: 456,
55
+ product_id: 789,
56
+ priority: "high",
57
+ shipping_method: "express"
58
+ )
36
59
  ```
37
60
 
38
- ## Source
61
+ ## Parameter Sources
39
62
 
40
- Parameters will be delegated to the task context by default but any delegatable
41
- object within the task will do.
63
+ Parameters delegate to source objects within the task context. The default source is `:context`, but any accessible method or object can serve as a parameter source.
64
+
65
+ ### Default Context Source
42
66
 
43
67
  ```ruby
44
- class UpdateUserDetailsTask < CMDx::Task
68
+ class UpdateUserTask < CMDx::Task
69
+ # Delegates to context.user_id (default source)
70
+ required :user_id
45
71
 
46
- # Default (:context)
47
- required :user
72
+ # Explicitly specified context source
73
+ required :email, source: :context
48
74
 
49
- # Defined parameter
50
- required :email, source: :user
75
+ def call
76
+ user_id #=> delegates to context.user_id
77
+ email #=> delegates to context.email
78
+ end
79
+ end
51
80
 
52
- # Proc or lambda
53
- optional :address, source: -> { user.address }
81
+ UpdateUserTask.call(user_id: 123, email: "user@example.com")
82
+ ```
54
83
 
55
- # Symbol or string
56
- optional :name, source: :company
84
+ ### Custom Object Sources
85
+
86
+ ```ruby
87
+ class ProcessUserOrderTask < CMDx::Task
88
+ # Delegate to user object
89
+ required :name, :email, source: :user
90
+
91
+ # Delegate to order object
92
+ required :total, :status, source: :order
93
+ optional :discount, source: :order
57
94
 
58
95
  def call
59
- user #=> <User #a1b2c3d>
60
- email #=> "bill@bigcorp.com"
61
- address #=> "123 Maple St, Miami, Fl 33023"
62
- name #=> "Big Corp."
96
+ name #=> delegates to user.name
97
+ email #=> delegates to user.email
98
+ total #=> delegates to order.total
99
+ status #=> delegates to order.status
100
+ discount #=> delegates to order.discount
63
101
  end
64
102
 
65
103
  private
66
104
 
67
- def company
68
- user.account.company
105
+ def user
106
+ @user ||= User.find(context.user_id)
69
107
  end
70
108
 
109
+ def order
110
+ @order ||= user.orders.find(context.order_id)
111
+ end
71
112
  end
72
113
 
73
- # Hash or delegatable object
74
- user = User.new(email: "bill@bigcorp.com")
75
- UpdateUserDetailsTask.call(user: user)
114
+ ProcessUserOrderTask.call(user_id: 123, order_id: 456)
76
115
  ```
77
116
 
78
- ## Nesting
79
-
80
- Nesting builds upon parameter source option. Build complex parameter blocks that
81
- delegate to the parent parameter automatically.
117
+ ### Dynamic Sources
82
118
 
83
119
  ```ruby
84
- class UpdateUserDetailsTask < CMDx::Task
120
+ class ProcessDynamicParameterTask < CMDx::Task
121
+ # Lambda source for dynamic resolution
122
+ required :company_name, source: -> { user.company }
123
+
124
+ # Method name sources
125
+ required :account_type, source: :determine_account_type
126
+ optional :access_level, source: :calculate_access_level
127
+
128
+ def call
129
+ company_name #=> resolved via lambda
130
+ account_type #=> result of determine_account_type method
131
+ access_level #=> result of calculate_access_level method
132
+ end
133
+
134
+ private
135
+
136
+ def user
137
+ @user ||= User.find(context.user_id)
138
+ end
139
+
140
+ def determine_account_type
141
+ user.premium? ? "premium" : "standard"
142
+ end
143
+
144
+ def calculate_access_level
145
+ user.admin? ? "admin" : "user"
146
+ end
147
+ end
148
+ ```
149
+
150
+ ## Nested Parameters
151
+
152
+ Nested parameters allow complex parameter structures where child parameters automatically inherit their parent as the source. This enables validation and access of structured data.
153
+
154
+ > [!NOTE]
155
+ > Child parameters are only required when their parent parameter is provided.
156
+
157
+ ### Basic Nesting
85
158
 
86
- required :address do
87
- required :street1
88
- optional :street2
159
+ ```ruby
160
+ class CreateShippingLabelTask < CMDx::Task
161
+ # Parent parameter with nested children
162
+ required :shipping_address do
163
+ required :street, :city, :state, :zip_code
164
+ optional :apartment_number
89
165
  end
90
166
 
91
- optional :locality do
92
- required :city, :state # Required if locality argument is passed
93
- optional :zipcode
167
+ # Optional parent with required children
168
+ optional :billing_address do
169
+ required :street, :city # Only required if billing_address provided
170
+ optional :same_as_shipping
94
171
  end
95
172
 
96
173
  def call
97
- address #=> { city: "Miami", state: "Fl" }
98
- street1 #=> "123 Maple St."
99
- street2 #=> nil
174
+ # Parent parameter access
175
+ shipping_address #=> { street: "123 Main St", city: "Miami", ... }
176
+
177
+ # Child parameter access (delegates to parent)
178
+ street #=> "123 Main St" (from shipping_address.street)
179
+ city #=> "Miami" (from shipping_address.city)
180
+ apartment_number #=> nil (optional, not provided)
181
+ end
182
+ end
183
+
184
+ CreateShippingLabelTask.call(
185
+ shipping_address: {
186
+ street: "123 Main St",
187
+ city: "Miami",
188
+ state: "FL",
189
+ zip_code: "33101"
190
+ }
191
+ )
192
+ ```
193
+
194
+ ### Multi-Level Nesting
100
195
 
101
- locality #=> { city: "Miami", state: "Fl" }
102
- city #=> "Miami"
103
- state #=> "Fl"
104
- zipcode #=> nil
196
+ ```ruby
197
+ class CreateUserProfileTask < CMDx::Task
198
+ required :user do
199
+ required :name, :email
200
+
201
+ required :profile do
202
+ required :age
203
+ optional :bio
204
+
205
+ optional :preferences do
206
+ optional :theme, :language
207
+ required :notifications # Required if preferences provided
208
+ end
209
+ end
105
210
  end
106
211
 
212
+ def call
213
+ # Access at any nesting level
214
+ name #=> delegates to user.name
215
+ email #=> delegates to user.email
216
+ age #=> delegates to user.profile.age
217
+ theme #=> delegates to user.profile.preferences.theme
218
+ end
107
219
  end
220
+ ```
221
+
222
+ ## Parameter Method Generation
223
+
224
+ Parameters automatically generate accessor methods that delegate to their configured sources.
225
+
226
+ > [!TIP]
227
+ > Parameter names become instance methods accessible within the task.
228
+
229
+ ```ruby
230
+ class ProcessPaymentTask < CMDx::Task
231
+ # Standard method generation
232
+ required :payment_id # Generates: payment_id method
233
+
234
+ # Custom source with method name
235
+ required :account_name, source: :account # Generates: account_name method
236
+
237
+ # Nested parameter method generation
238
+ required :billing_info do
239
+ required :card_number # Generates: card_number method
240
+ required :expiry_date # Generates: expiry_date method
241
+ end
108
242
 
109
- # Hash or delegatable object
110
- address = Address.new(street1: "123 Maple St.")
111
- locality = { city: "Miami", state: "Fl" }
112
- UpdateUserDetailsTask.call(address: address, locality: locality)
243
+ def call
244
+ payment_id #=> accesses context.payment_id
245
+ account_name #=> accesses account.account_name
246
+ card_number #=> accesses billing_info.card_number
247
+ expiry_date #=> accesses billing_info.expiry_date
248
+ end
249
+
250
+ private
251
+
252
+ def account
253
+ @account ||= Account.find(context.account_id)
254
+ end
255
+ end
113
256
  ```
114
257
 
115
- > [!NOTE]
116
- > Optional parent parameters that have required child parameters will only have
117
- > the child parameters be required if the parent option is a delegatable source
118
- > or default value.
258
+ ## Error Handling
259
+
260
+ Parameter validation failures result in structured error information:
261
+
262
+ > [!WARNING]
263
+ > Invalid parameters will cause task execution to fail with detailed error messages.
264
+
265
+ ```ruby
266
+ class ValidateUserTask < CMDx::Task
267
+ required :age, type: :integer, numeric: { min: 18, max: 120 }
268
+ required :email, type: :string, format: { with: /@/ }
269
+ optional :phone, type: :string, format: { with: /\A\d{10}\z/ }
270
+
271
+ def call
272
+ # Task logic here
273
+ end
274
+ end
275
+
276
+ # Invalid parameters
277
+ result = ValidateUserTask.call(
278
+ age: "invalid",
279
+ email: "not-an-email",
280
+ phone: "123"
281
+ )
282
+
283
+ result.failed? #=> true
284
+ result.metadata
285
+ #=> {
286
+ # reason: "age could not coerce into an integer. email format is not valid. phone format is not valid.",
287
+ # messages: {
288
+ # age: ["could not coerce into an integer"],
289
+ # email: ["format is not valid"],
290
+ # phone: ["format is not valid"]
291
+ # }
292
+ # }
293
+ ```
119
294
 
120
295
  ---
121
296
 
122
- - **Prev:** [Parameters](https://github.com/drexed/cmdx/blob/main/docs/parameters.md)
123
- - **Next:** [Namespacing](https://github.com/drexed/cmdx/blob/main/docs/parameters/namespacing.md)
297
+ - **Prev:** [Configuration](../configuration.md)
298
+ - **Next:** [Parameters - Namespacing](namespacing.md)