cmdx 1.1.0 → 1.1.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 (87) hide show
  1. checksums.yaml +4 -4
  2. data/.cursor/prompts/docs.md +9 -0
  3. data/.cursor/prompts/rspec.md +13 -12
  4. data/.cursor/prompts/yardoc.md +11 -6
  5. data/CHANGELOG.md +13 -2
  6. data/README.md +1 -0
  7. data/docs/ai_prompts.md +269 -195
  8. data/docs/basics/call.md +124 -58
  9. data/docs/basics/chain.md +190 -160
  10. data/docs/basics/context.md +242 -154
  11. data/docs/basics/setup.md +302 -32
  12. data/docs/callbacks.md +390 -94
  13. data/docs/configuration.md +181 -65
  14. data/docs/deprecation.md +245 -0
  15. data/docs/getting_started.md +161 -39
  16. data/docs/internationalization.md +590 -70
  17. data/docs/interruptions/exceptions.md +135 -118
  18. data/docs/interruptions/faults.md +150 -125
  19. data/docs/interruptions/halt.md +134 -80
  20. data/docs/logging.md +181 -118
  21. data/docs/middlewares.md +150 -377
  22. data/docs/outcomes/result.md +140 -112
  23. data/docs/outcomes/states.md +134 -99
  24. data/docs/outcomes/statuses.md +204 -146
  25. data/docs/parameters/coercions.md +232 -281
  26. data/docs/parameters/defaults.md +224 -169
  27. data/docs/parameters/definitions.md +289 -141
  28. data/docs/parameters/namespacing.md +250 -161
  29. data/docs/parameters/validations.md +260 -133
  30. data/docs/testing.md +191 -197
  31. data/docs/workflows.md +143 -98
  32. data/lib/cmdx/callback.rb +23 -19
  33. data/lib/cmdx/callback_registry.rb +1 -3
  34. data/lib/cmdx/chain_inspector.rb +23 -23
  35. data/lib/cmdx/chain_serializer.rb +38 -19
  36. data/lib/cmdx/coercion.rb +20 -12
  37. data/lib/cmdx/coercion_registry.rb +51 -32
  38. data/lib/cmdx/configuration.rb +84 -31
  39. data/lib/cmdx/context.rb +32 -21
  40. data/lib/cmdx/core_ext/hash.rb +13 -13
  41. data/lib/cmdx/core_ext/module.rb +1 -1
  42. data/lib/cmdx/core_ext/object.rb +12 -12
  43. data/lib/cmdx/correlator.rb +60 -39
  44. data/lib/cmdx/errors.rb +105 -131
  45. data/lib/cmdx/fault.rb +66 -45
  46. data/lib/cmdx/immutator.rb +20 -21
  47. data/lib/cmdx/lazy_struct.rb +78 -70
  48. data/lib/cmdx/log_formatters/json.rb +1 -1
  49. data/lib/cmdx/log_formatters/key_value.rb +1 -1
  50. data/lib/cmdx/log_formatters/line.rb +1 -1
  51. data/lib/cmdx/log_formatters/logstash.rb +1 -1
  52. data/lib/cmdx/log_formatters/pretty_json.rb +1 -1
  53. data/lib/cmdx/log_formatters/pretty_key_value.rb +1 -1
  54. data/lib/cmdx/log_formatters/pretty_line.rb +1 -1
  55. data/lib/cmdx/log_formatters/raw.rb +2 -2
  56. data/lib/cmdx/logger.rb +19 -14
  57. data/lib/cmdx/logger_ansi.rb +33 -17
  58. data/lib/cmdx/logger_serializer.rb +85 -24
  59. data/lib/cmdx/middleware.rb +39 -21
  60. data/lib/cmdx/middleware_registry.rb +4 -3
  61. data/lib/cmdx/parameter.rb +151 -89
  62. data/lib/cmdx/parameter_inspector.rb +34 -21
  63. data/lib/cmdx/parameter_registry.rb +36 -30
  64. data/lib/cmdx/parameter_serializer.rb +21 -14
  65. data/lib/cmdx/result.rb +136 -135
  66. data/lib/cmdx/result_ansi.rb +31 -17
  67. data/lib/cmdx/result_inspector.rb +32 -27
  68. data/lib/cmdx/result_logger.rb +23 -14
  69. data/lib/cmdx/result_serializer.rb +65 -27
  70. data/lib/cmdx/task.rb +234 -113
  71. data/lib/cmdx/task_deprecator.rb +22 -25
  72. data/lib/cmdx/task_processor.rb +89 -88
  73. data/lib/cmdx/task_serializer.rb +27 -14
  74. data/lib/cmdx/utils/monotonic_runtime.rb +2 -4
  75. data/lib/cmdx/validator.rb +25 -16
  76. data/lib/cmdx/validator_registry.rb +53 -31
  77. data/lib/cmdx/validators/exclusion.rb +1 -1
  78. data/lib/cmdx/validators/format.rb +2 -2
  79. data/lib/cmdx/validators/inclusion.rb +2 -2
  80. data/lib/cmdx/validators/length.rb +2 -2
  81. data/lib/cmdx/validators/numeric.rb +3 -3
  82. data/lib/cmdx/validators/presence.rb +2 -2
  83. data/lib/cmdx/version.rb +1 -1
  84. data/lib/cmdx/workflow.rb +54 -33
  85. data/lib/generators/cmdx/task_generator.rb +6 -6
  86. data/lib/generators/cmdx/workflow_generator.rb +6 -6
  87. metadata +3 -1
@@ -1,279 +1,334 @@
1
1
  # Parameters - Defaults
2
2
 
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.
3
+ Parameter defaults provide fallback values when arguments are not provided or resolve to `nil`. Defaults ensure tasks have sensible values for optional parameters while maintaining flexibility for callers to override when needed.
7
4
 
8
5
  ## Table of Contents
9
6
 
10
7
  - [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)
8
+ - [Default Fundamentals](#default-fundamentals)
9
+ - [Dynamic Defaults](#dynamic-defaults)
10
+ - [Defaults with Coercion and Validation](#defaults-with-coercion-and-validation)
16
11
  - [Nested Parameter Defaults](#nested-parameter-defaults)
12
+ - [Error Handling](#error-handling)
17
13
 
18
14
  ## TLDR
19
15
 
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
16
+ ```ruby
17
+ # Fixed defaults
18
+ optional :priority, default: "normal"
19
+ optional :retries, type: :integer, default: 3
20
+ optional :tags, type: :array, default: []
25
21
 
26
- ## Default Value Fundamentals
22
+ # Dynamic defaults
23
+ optional :created_at, default: -> { Time.now }
24
+ optional :template, default: :determine_template
27
25
 
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.
26
+ # With coercion - defaults are coerced too
27
+ optional :max_items, type: :integer, default: "50" # 50
30
28
 
31
- ### Fixed Value Defaults
29
+ # Nested defaults
30
+ optional :config, type: :hash, default: {} do
31
+ optional :timeout, default: 30
32
+ end
33
+ ```
32
34
 
33
- The simplest defaults use fixed values that are applied consistently:
35
+ ## Default Fundamentals
36
+
37
+ > [!NOTE]
38
+ > Defaults apply when parameters are not provided or resolve to `nil`. They work seamlessly with coercion, validation, and nested parameters.
34
39
 
35
40
  ```ruby
36
- class ProcessUserOrderTask < CMDx::Task
41
+ class ProcessOrderTask < CMDx::Task
42
+ required :order_id, type: :integer
37
43
 
38
- required :user_id, type: :integer
39
- optional :priority, type: :string, default: "normal"
40
- optional :send_confirmation, type: :boolean, default: true
44
+ # Fixed value defaults
45
+ optional :priority, default: "standard"
46
+ optional :send_email, type: :boolean, default: true
41
47
  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 }
48
+ optional :tags, type: :array, default: []
49
+ optional :metadata, type: :hash, default: {}
46
50
 
47
51
  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
52
+ # Defaults used when parameters not provided
53
+ process_order_with_priority(priority) # "standard"
54
+ send_notification if send_email # true
55
+ retry_failed_steps(max_retries) # 3
55
56
  end
56
-
57
57
  end
58
58
 
59
- # Defaults applied for missing parameters
60
- ProcessUserOrderTask.call(user_id: 12345)
61
- # priority: "normal", send_confirmation: true, max_retries: 3, etc.
59
+ # Using defaults
60
+ ProcessOrderTask.call(order_id: 123)
61
+ # priority: "standard", send_email: true, max_retries: 3
62
62
 
63
- # Explicit values override defaults
64
- ProcessUserOrderTask.call(
65
- user_id: 12345,
63
+ # Overriding defaults
64
+ ProcessOrderTask.call(
65
+ order_id: 123,
66
66
  priority: "urgent",
67
- send_confirmation: false,
68
- notification_tags: ["rush_order"]
67
+ send_email: false,
68
+ tags: ["rush"]
69
69
  )
70
70
  ```
71
71
 
72
- ### Callable Defaults
72
+ ## Dynamic Defaults
73
73
 
74
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.
75
+ > Use procs, lambdas, or method symbols for dynamic defaults evaluated at runtime. Essential for timestamps, UUIDs, and context-dependent values.
76
76
 
77
77
  ```ruby
78
- class SendOrderNotificationTask < CMDx::Task
79
-
80
- required :order_id, type: :integer
78
+ class SendNotificationTask < CMDx::Task
79
+ required :user_id, type: :integer
80
+ required :message, type: :string
81
81
 
82
- # Dynamic defaults using procs
82
+ # Proc defaults - evaluated when accessed
83
83
  optional :sent_at, type: :datetime, default: -> { Time.now }
84
- optional :tracking_id, type: :string, default: -> { SecureRandom.uuid }
84
+ optional :tracking_id, default: -> { SecureRandom.uuid }
85
85
 
86
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 }
87
+ optional :service, default: -> { Rails.env.production? ? "sendgrid" : "test" }
89
88
 
90
89
  # Method symbol defaults
91
- optional :template_name, type: :string, default: :determine_template
92
- optional :delivery_time, type: :datetime, default: :calculate_delivery_window
90
+ optional :template, default: :default_template
91
+ optional :priority, default: :calculate_priority
93
92
 
94
93
  def call
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
94
+ notification = {
95
+ message: message,
96
+ sent_at: sent_at, # Current time when accessed
97
+ tracking_id: tracking_id, # Unique UUID when accessed
98
+ template: template, # Result of default_template method
99
+ priority: priority # Result of calculate_priority method
100
+ }
101
+
102
+ NotificationService.send(notification, service: service)
101
103
  end
102
104
 
103
105
  private
104
106
 
105
- def determine_template
106
- order.priority == "urgent" ? "urgent_order" : "standard_order"
107
+ def default_template
108
+ user.premium? ? "premium_notification" : "standard_notification"
107
109
  end
108
110
 
109
- def calculate_delivery_window
110
- order.priority == "urgent" ? 15.minutes.from_now : 1.hour.from_now
111
+ def calculate_priority
112
+ user.vip? ? "high" : "normal"
111
113
  end
112
114
 
113
- def order
114
- @order ||= Order.find(order_id)
115
+ def user
116
+ @user ||= User.find(user_id)
115
117
  end
116
-
117
118
  end
118
119
  ```
119
120
 
120
- ## Defaults with Type Coercion
121
+ ## Defaults with Coercion and Validation
121
122
 
122
123
  > [!IMPORTANT]
123
- > Defaults work seamlessly with type coercion, with the default value being subject to the same coercion rules as provided values.
124
+ > Defaults are subject to the same coercion and validation rules as provided values, ensuring consistency and catching configuration errors early.
125
+
126
+ ### Coercion with Defaults
124
127
 
125
128
  ```ruby
126
- class ConfigureOrderSettingsTask < CMDx::Task
129
+ class ConfigureServiceTask < CMDx::Task
130
+ # String defaults coerced to target types
131
+ optional :max_connections, type: :integer, default: "100"
132
+ optional :config, type: :hash, default: '{"timeout": 30}'
133
+ optional :allowed_hosts, type: :array, default: '["localhost"]'
134
+ optional :debug_mode, type: :boolean, default: "false"
135
+
136
+ # Dynamic defaults with coercion
137
+ optional :session_id, type: :string, default: -> { Time.now.to_i }
127
138
 
128
- # String defaults coerced to integers
129
- optional :max_items, type: :integer, default: "50"
139
+ def call
140
+ max_connections # 100 (Integer from "100")
141
+ config # → {"timeout" => 30} (Hash from JSON)
142
+ allowed_hosts # → ["localhost"] (Array from JSON)
143
+ debug_mode # → false (Boolean from "false")
144
+ session_id # → "1640995200" (String from Integer)
145
+ end
146
+ end
147
+ ```
130
148
 
131
- # JSON string defaults coerced to hash
132
- optional :shipping_config, type: :hash, default: '{"carrier": "ups", "speed": "standard"}'
149
+ ### Validation with Defaults
133
150
 
134
- # String defaults coerced to arrays
135
- optional :allowed_countries, type: :array, default: '["US", "CA", "UK"]'
151
+ ```ruby
152
+ class ScheduleTaskTask < CMDx::Task
153
+ required :task_name, type: :string
136
154
 
137
- # String defaults coerced to booleans
138
- optional :require_signature, type: :boolean, default: "true"
155
+ # Default must pass validation rules
156
+ optional :priority, default: "medium",
157
+ inclusion: { in: %w[low medium high urgent] }
139
158
 
140
- # String defaults coerced to dates
141
- optional :embargo_date, type: :date, default: "2024-01-01"
159
+ optional :timeout, type: :integer, default: 300,
160
+ numeric: { min: 60, max: 3600 }
142
161
 
143
- # Dynamic defaults with coercion
144
- optional :order_number, type: :string, default: -> { Time.now.to_i }
162
+ optional :retry_count, type: :integer, default: 3,
163
+ numeric: { min: 0, max: 10 }
145
164
 
146
165
  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)
166
+ # All defaults validated against their rules
167
+ schedule_task(task_name, priority: priority, timeout: timeout)
153
168
  end
154
-
155
169
  end
156
- ```
157
170
 
158
- ## Defaults with Validation
171
+ # Invalid default would cause validation error
172
+ # optional :priority, default: "invalid", inclusion: { in: %w[low medium high] }
173
+ # → CMDx::ValidationError: priority invalid is not included in the list
174
+ ```
159
175
 
160
- > [!WARNING]
161
- > Default values are subject to the same validation rules as provided values, ensuring consistency and catching configuration errors early.
176
+ ## Nested Parameter Defaults
162
177
 
163
178
  ```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] }
179
+ class ProcessPaymentTask < CMDx::Task
180
+ required :amount, type: :float
181
+ required :user_id, type: :integer
171
182
 
172
- # Numeric default with range validation
173
- optional :processing_timeout, type: :integer, default: 300,
174
- numeric: { min: 60, max: 3600 }
183
+ # Nested structure with defaults at multiple levels
184
+ optional :payment_config, type: :hash, default: {} do
185
+ optional :method, default: "credit_card"
186
+ optional :currency, default: "USD"
187
+ optional :require_cvv, type: :boolean, default: true
175
188
 
176
- # Email default with format validation
177
- optional :escalation_email, type: :string,
178
- default: -> { "support@#{Rails.application.config.domain}" },
179
- format: { with: /@/ }
189
+ optional :billing_address, type: :hash, default: -> { user_default_address } do
190
+ optional :country, default: "US"
191
+ optional :state, default: -> { user_default_state }
192
+ end
180
193
 
181
- # Custom validation with default
182
- optional :approval_code, type: :string, default: :generate_approval_code,
183
- presence: true
194
+ optional :notification_settings, type: :hash, default: {} do
195
+ optional :send_receipt, type: :boolean, default: true
196
+ optional :send_sms, type: :boolean, default: false
197
+ end
198
+ end
184
199
 
185
200
  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)
201
+ # Process payment with defaults applied at each level
202
+ PaymentProcessor.charge(
203
+ amount: amount,
204
+ method: payment_config[:method], # "credit_card"
205
+ currency: payment_config[:currency], # "USD"
206
+ billing_address: payment_config[:billing_address],
207
+ notifications: payment_config[:notification_settings]
208
+ )
190
209
  end
191
210
 
192
211
  private
193
212
 
194
- def generate_approval_code
195
- "APV_#{SecureRandom.hex(8).upcase}"
213
+ def user
214
+ @user ||= User.find(user_id)
215
+ end
216
+
217
+ def user_default_address
218
+ user.billing_address&.to_hash || {}
196
219
  end
197
220
 
221
+ def user_default_state
222
+ user.billing_address&.state || "CA"
223
+ end
198
224
  end
225
+
226
+ # Usage with nested defaults
227
+ ProcessPaymentTask.call(amount: 99.99, user_id: 123)
228
+ # payment_config automatically gets:
229
+ # {
230
+ # method: "credit_card",
231
+ # currency: "USD",
232
+ # require_cvv: true,
233
+ # billing_address: { country: "US", state: "CA" },
234
+ # notification_settings: { send_receipt: true, send_sms: false }
235
+ # }
199
236
  ```
200
237
 
201
- ## Nested Parameter Defaults
238
+ ## Error Handling
202
239
 
203
- ```ruby
204
- class ProcessOrderShippingTask < CMDx::Task
240
+ > [!WARNING]
241
+ > Default values that fail coercion or validation will cause task execution to fail with detailed error information.
205
242
 
206
- required :order_id, type: :integer
243
+ ### Validation Errors with Defaults
207
244
 
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 }
245
+ ```ruby
246
+ class BadDefaultsTask < CMDx::Task
247
+ # This default will fail validation
248
+ optional :priority, default: "invalid",
249
+ inclusion: { in: %w[low medium high] }
213
250
 
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
251
+ # This default will fail coercion
252
+ optional :count, type: :integer, default: "not-a-number"
253
+
254
+ def call
255
+ # Won't reach here due to validation/coercion failures
219
256
  end
257
+ end
220
258
 
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
259
+ result = BadDefaultsTask.call
260
+ result.failed? # true
261
+ result.metadata
262
+ # {
263
+ # reason: "priority invalid is not included in the list. count could not coerce into an integer.",
264
+ # messages: {
265
+ # priority: ["invalid is not included in the list"],
266
+ # count: ["could not coerce into an integer"]
267
+ # }
268
+ # }
269
+ ```
225
270
 
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
271
+ ### Dynamic Default Errors
272
+
273
+ ```ruby
274
+ class ProblematicDefaultsTask < CMDx::Task
275
+ # Method that might raise an error
276
+ optional :config, default: :load_external_config
277
+
278
+ # Proc that might fail
279
+ optional :api_key, default: -> { fetch_api_key_from_vault }
231
280
 
232
281
  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
282
+ # Task logic
246
283
  end
247
284
 
248
285
  private
249
286
 
250
- def order
251
- @order ||= Order.find(order_id)
287
+ def load_external_config
288
+ # This might raise if external service is down
289
+ ExternalConfigService.fetch_config
290
+ rescue => e
291
+ raise CMDx::Error, "Failed to load default config: #{e.message}"
252
292
  end
253
293
 
254
- def order_value
255
- order.total_amount
294
+ def fetch_api_key_from_vault
295
+ # This might raise if vault is unavailable
296
+ VaultService.get_secret("api_key")
297
+ rescue => e
298
+ raise CMDx::Error, "Failed to fetch default API key: #{e.message}"
256
299
  end
300
+ end
301
+ ```
257
302
 
258
- def customer_default_address
259
- order.customer.default_shipping_address&.to_hash || {}
260
- end
303
+ ### Nil vs Missing Parameters
261
304
 
262
- def determine_default_state
263
- order.customer.billing_address&.state || "CA"
264
- end
305
+ ```ruby
306
+ class NilHandlingTask < CMDx::Task
307
+ optional :status, default: "active"
308
+ optional :tags, type: :array, default: []
265
309
 
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
- }
310
+ def call
311
+ status # Default applied based on input
312
+ tags # Default applied based on input
272
313
  end
273
-
274
314
  end
315
+
316
+ # Missing parameters use defaults
317
+ NilHandlingTask.call
318
+ # status: "active", tags: []
319
+
320
+ # Explicitly nil parameters also use defaults
321
+ NilHandlingTask.call(status: nil, tags: nil)
322
+ # status: "active", tags: []
323
+
324
+ # Empty string is NOT nil - no default applied
325
+ NilHandlingTask.call(status: "", tags: "")
326
+ # status: "", tags: "" (string, not array - may cause coercion error)
275
327
  ```
276
328
 
329
+ > [!TIP]
330
+ > Defaults only apply to `nil` values. Empty strings, empty arrays, or false values are considered valid inputs and won't trigger defaults.
331
+
277
332
  ---
278
333
 
279
334
  - **Prev:** [Parameters - Validations](validations.md)