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