cmdx 1.0.1 → 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 (170) hide show
  1. checksums.yaml +4 -4
  2. data/.cursor/prompts/docs.md +9 -0
  3. data/.cursor/prompts/rspec.md +21 -0
  4. data/.cursor/prompts/yardoc.md +13 -0
  5. data/.rubocop.yml +2 -0
  6. data/CHANGELOG.md +29 -3
  7. data/README.md +2 -1
  8. data/docs/ai_prompts.md +269 -195
  9. data/docs/basics/call.md +126 -60
  10. data/docs/basics/chain.md +190 -160
  11. data/docs/basics/context.md +242 -154
  12. data/docs/basics/setup.md +302 -32
  13. data/docs/callbacks.md +382 -119
  14. data/docs/configuration.md +211 -49
  15. data/docs/deprecation.md +245 -0
  16. data/docs/getting_started.md +161 -39
  17. data/docs/internationalization.md +590 -70
  18. data/docs/interruptions/exceptions.md +135 -118
  19. data/docs/interruptions/faults.md +152 -127
  20. data/docs/interruptions/halt.md +134 -80
  21. data/docs/logging.md +183 -120
  22. data/docs/middlewares.md +165 -392
  23. data/docs/outcomes/result.md +140 -112
  24. data/docs/outcomes/states.md +134 -99
  25. data/docs/outcomes/statuses.md +204 -146
  26. data/docs/parameters/coercions.md +251 -289
  27. data/docs/parameters/defaults.md +224 -169
  28. data/docs/parameters/definitions.md +289 -141
  29. data/docs/parameters/namespacing.md +250 -161
  30. data/docs/parameters/validations.md +247 -159
  31. data/docs/testing.md +196 -203
  32. data/docs/workflows.md +146 -101
  33. data/lib/cmdx/.DS_Store +0 -0
  34. data/lib/cmdx/callback.rb +39 -55
  35. data/lib/cmdx/callback_registry.rb +80 -73
  36. data/lib/cmdx/chain.rb +65 -122
  37. data/lib/cmdx/chain_inspector.rb +23 -116
  38. data/lib/cmdx/chain_serializer.rb +34 -146
  39. data/lib/cmdx/coercion.rb +57 -0
  40. data/lib/cmdx/coercion_registry.rb +113 -0
  41. data/lib/cmdx/coercions/array.rb +18 -36
  42. data/lib/cmdx/coercions/big_decimal.rb +21 -33
  43. data/lib/cmdx/coercions/boolean.rb +21 -40
  44. data/lib/cmdx/coercions/complex.rb +18 -31
  45. data/lib/cmdx/coercions/date.rb +20 -39
  46. data/lib/cmdx/coercions/date_time.rb +22 -39
  47. data/lib/cmdx/coercions/float.rb +19 -32
  48. data/lib/cmdx/coercions/hash.rb +22 -41
  49. data/lib/cmdx/coercions/integer.rb +20 -33
  50. data/lib/cmdx/coercions/rational.rb +20 -32
  51. data/lib/cmdx/coercions/string.rb +23 -31
  52. data/lib/cmdx/coercions/time.rb +24 -40
  53. data/lib/cmdx/coercions/virtual.rb +14 -31
  54. data/lib/cmdx/configuration.rb +101 -162
  55. data/lib/cmdx/context.rb +34 -166
  56. data/lib/cmdx/core_ext/hash.rb +42 -67
  57. data/lib/cmdx/core_ext/module.rb +35 -79
  58. data/lib/cmdx/core_ext/object.rb +63 -98
  59. data/lib/cmdx/correlator.rb +59 -154
  60. data/lib/cmdx/error.rb +37 -202
  61. data/lib/cmdx/errors.rb +153 -216
  62. data/lib/cmdx/fault.rb +68 -150
  63. data/lib/cmdx/faults.rb +26 -137
  64. data/lib/cmdx/immutator.rb +22 -110
  65. data/lib/cmdx/lazy_struct.rb +110 -186
  66. data/lib/cmdx/log_formatters/json.rb +14 -40
  67. data/lib/cmdx/log_formatters/key_value.rb +14 -40
  68. data/lib/cmdx/log_formatters/line.rb +14 -48
  69. data/lib/cmdx/log_formatters/logstash.rb +14 -57
  70. data/lib/cmdx/log_formatters/pretty_json.rb +14 -50
  71. data/lib/cmdx/log_formatters/pretty_key_value.rb +13 -46
  72. data/lib/cmdx/log_formatters/pretty_line.rb +16 -54
  73. data/lib/cmdx/log_formatters/raw.rb +19 -49
  74. data/lib/cmdx/logger.rb +22 -79
  75. data/lib/cmdx/logger_ansi.rb +31 -72
  76. data/lib/cmdx/logger_serializer.rb +74 -103
  77. data/lib/cmdx/middleware.rb +56 -60
  78. data/lib/cmdx/middleware_registry.rb +82 -77
  79. data/lib/cmdx/middlewares/correlate.rb +41 -226
  80. data/lib/cmdx/middlewares/timeout.rb +46 -185
  81. data/lib/cmdx/parameter.rb +167 -183
  82. data/lib/cmdx/parameter_evaluator.rb +231 -0
  83. data/lib/cmdx/parameter_inspector.rb +37 -55
  84. data/lib/cmdx/parameter_registry.rb +65 -84
  85. data/lib/cmdx/parameter_serializer.rb +32 -76
  86. data/lib/cmdx/railtie.rb +24 -107
  87. data/lib/cmdx/result.rb +254 -259
  88. data/lib/cmdx/result_ansi.rb +28 -80
  89. data/lib/cmdx/result_inspector.rb +34 -70
  90. data/lib/cmdx/result_logger.rb +23 -77
  91. data/lib/cmdx/result_serializer.rb +59 -125
  92. data/lib/cmdx/rspec/matchers.rb +28 -0
  93. data/lib/cmdx/rspec/result_matchers/be_executed.rb +42 -0
  94. data/lib/cmdx/rspec/result_matchers/be_failed_task.rb +94 -0
  95. data/lib/cmdx/rspec/result_matchers/be_skipped_task.rb +94 -0
  96. data/lib/cmdx/rspec/result_matchers/be_state_matchers.rb +59 -0
  97. data/lib/cmdx/rspec/result_matchers/be_status_matchers.rb +57 -0
  98. data/lib/cmdx/rspec/result_matchers/be_successful_task.rb +87 -0
  99. data/lib/cmdx/rspec/result_matchers/have_bad_outcome.rb +51 -0
  100. data/lib/cmdx/rspec/result_matchers/have_caused_failure.rb +58 -0
  101. data/lib/cmdx/rspec/result_matchers/have_chain_index.rb +59 -0
  102. data/lib/cmdx/rspec/result_matchers/have_context.rb +86 -0
  103. data/lib/cmdx/rspec/result_matchers/have_empty_metadata.rb +54 -0
  104. data/lib/cmdx/rspec/result_matchers/have_good_outcome.rb +52 -0
  105. data/lib/cmdx/rspec/result_matchers/have_metadata.rb +114 -0
  106. data/lib/cmdx/rspec/result_matchers/have_preserved_context.rb +66 -0
  107. data/lib/cmdx/rspec/result_matchers/have_received_thrown_failure.rb +64 -0
  108. data/lib/cmdx/rspec/result_matchers/have_runtime.rb +78 -0
  109. data/lib/cmdx/rspec/result_matchers/have_thrown_failure.rb +76 -0
  110. data/lib/cmdx/rspec/task_matchers/be_well_formed_task.rb +62 -0
  111. data/lib/cmdx/rspec/task_matchers/have_callback.rb +85 -0
  112. data/lib/cmdx/rspec/task_matchers/have_cmd_setting.rb +68 -0
  113. data/lib/cmdx/rspec/task_matchers/have_executed_callbacks.rb +92 -0
  114. data/lib/cmdx/rspec/task_matchers/have_middleware.rb +46 -0
  115. data/lib/cmdx/rspec/task_matchers/have_parameter.rb +181 -0
  116. data/lib/cmdx/task.rb +336 -427
  117. data/lib/cmdx/task_deprecator.rb +52 -0
  118. data/lib/cmdx/task_processor.rb +246 -0
  119. data/lib/cmdx/task_serializer.rb +34 -69
  120. data/lib/cmdx/utils/ansi_color.rb +13 -89
  121. data/lib/cmdx/utils/log_timestamp.rb +13 -42
  122. data/lib/cmdx/utils/monotonic_runtime.rb +11 -63
  123. data/lib/cmdx/utils/name_affix.rb +21 -71
  124. data/lib/cmdx/validator.rb +57 -0
  125. data/lib/cmdx/validator_registry.rb +108 -0
  126. data/lib/cmdx/validators/exclusion.rb +55 -94
  127. data/lib/cmdx/validators/format.rb +31 -85
  128. data/lib/cmdx/validators/inclusion.rb +65 -110
  129. data/lib/cmdx/validators/length.rb +117 -133
  130. data/lib/cmdx/validators/numeric.rb +123 -130
  131. data/lib/cmdx/validators/presence.rb +38 -79
  132. data/lib/cmdx/version.rb +1 -7
  133. data/lib/cmdx/workflow.rb +58 -330
  134. data/lib/cmdx.rb +1 -1
  135. data/lib/generators/cmdx/install_generator.rb +14 -31
  136. data/lib/generators/cmdx/task_generator.rb +39 -55
  137. data/lib/generators/cmdx/templates/install.rb +24 -6
  138. data/lib/generators/cmdx/workflow_generator.rb +41 -66
  139. data/lib/locales/ar.yml +0 -1
  140. data/lib/locales/cs.yml +0 -1
  141. data/lib/locales/da.yml +0 -1
  142. data/lib/locales/de.yml +0 -1
  143. data/lib/locales/el.yml +0 -1
  144. data/lib/locales/en.yml +0 -1
  145. data/lib/locales/es.yml +0 -1
  146. data/lib/locales/fi.yml +0 -1
  147. data/lib/locales/fr.yml +0 -1
  148. data/lib/locales/he.yml +0 -1
  149. data/lib/locales/hi.yml +0 -1
  150. data/lib/locales/it.yml +0 -1
  151. data/lib/locales/ja.yml +0 -1
  152. data/lib/locales/ko.yml +0 -1
  153. data/lib/locales/nl.yml +0 -1
  154. data/lib/locales/no.yml +0 -1
  155. data/lib/locales/pl.yml +0 -1
  156. data/lib/locales/pt.yml +0 -1
  157. data/lib/locales/ru.yml +0 -1
  158. data/lib/locales/sv.yml +0 -1
  159. data/lib/locales/th.yml +0 -1
  160. data/lib/locales/tr.yml +0 -1
  161. data/lib/locales/vi.yml +0 -1
  162. data/lib/locales/zh.yml +0 -1
  163. metadata +36 -8
  164. data/lib/cmdx/parameter_validator.rb +0 -81
  165. data/lib/cmdx/parameter_value.rb +0 -244
  166. data/lib/cmdx/parameters_inspector.rb +0 -72
  167. data/lib/cmdx/parameters_serializer.rb +0 -115
  168. data/lib/cmdx/rspec/result_matchers.rb +0 -917
  169. data/lib/cmdx/rspec/task_matchers.rb +0 -570
  170. data/lib/cmdx/validators/custom.rb +0 -102
@@ -1,9 +1,6 @@
1
1
  # Parameters - Namespacing
2
2
 
3
- Parameter namespacing provides method name customization to prevent conflicts
4
- and enable flexible parameter access patterns. When parameters share names with
5
- existing methods or when multiple parameters from different sources have the
6
- same name, namespacing ensures clean method resolution within tasks.
3
+ Parameter namespacing provides method name customization to prevent conflicts and enable flexible parameter access patterns. When parameters share names with existing methods or when multiple parameters from different sources have the same name, namespacing ensures clean method resolution within tasks.
7
4
 
8
5
  ## Table of Contents
9
6
 
@@ -12,158 +9,162 @@ same name, namespacing ensures clean method resolution within tasks.
12
9
  - [Fixed Value Namespacing](#fixed-value-namespacing)
13
10
  - [Dynamic Source-Based Namespacing](#dynamic-source-based-namespacing)
14
11
  - [Conflict Resolution](#conflict-resolution)
15
- - [Advanced Namespacing Patterns](#advanced-namespacing-patterns)
16
- - [Error Handling with Namespacing](#error-handling-with-namespacing)
12
+ - [Advanced Patterns](#advanced-patterns)
13
+ - [Error Handling](#error-handling)
17
14
 
18
15
  ## TLDR
19
16
 
20
- - **Method naming** - Use `prefix:` and `suffix:` to customize parameter method names
21
- - **Fixed prefixes** - `prefix: "user_"` creates `user_name` method for `name` parameter
22
- - **Dynamic prefixes** - `prefix: true` uses source name (e.g., `context_name`)
23
- - **Conflict resolution** - Avoid conflicts with Ruby methods or multiple same-named parameters
24
- - **Call arguments** - Always use original parameter names, namespacing only affects method names
17
+ ```ruby
18
+ # Fixed prefixes/suffixes
19
+ required :name, prefix: "user_" # user_name method
20
+ required :email, suffix: "_address" # email_address method
25
21
 
26
- ## Namespacing Fundamentals
22
+ # Dynamic source-based namespacing
23
+ required :id, prefix: true # → context_id method (from context source)
24
+ required :name, source: :profile, suffix: true # → name_profile method
27
25
 
28
- > [!IMPORTANT]
29
- > The `:prefix` and `:suffix` options modify only the generated accessor method names while preserving the original parameter names for call arguments.
26
+ # Conflict resolution
27
+ required :context, suffix: "_data" # Avoids CMDx::Task#context method
28
+ required :name, prefix: "customer_" # Avoids Ruby's Object#name method
30
29
 
31
- This separation allows for flexible method naming without affecting the task interface.
30
+ # Call arguments always use original parameter names
31
+ TaskClass.call(name: "John", email: "john@example.com", context: {...})
32
+ ```
32
33
 
33
- ### Fixed Value Namespacing
34
+ ## Namespacing Fundamentals
34
35
 
35
- Use string or symbol values to add consistent prefixes or suffixes to parameter
36
- method names:
36
+ > [!IMPORTANT]
37
+ > Namespacing modifies only the generated accessor method names within tasks. Parameter names in call arguments remain unchanged, ensuring a clean external interface.
37
38
 
38
- ```ruby
39
- class CreateOrderTask < CMDx::Task
39
+ ### Namespacing Options
40
+
41
+ | Option | Type | Description | Example |
42
+ |--------|------|-------------|---------|
43
+ | `prefix:` | String/Symbol | Fixed prefix | `prefix: "user_"` → `user_name` |
44
+ | `prefix:` | Boolean | Dynamic prefix from source | `prefix: true` → `context_name` |
45
+ | `suffix:` | String/Symbol | Fixed suffix | `suffix: "_data"` → `name_data` |
46
+ | `suffix:` | Boolean | Dynamic suffix from source | `suffix: true` → `name_context` |
40
47
 
41
- # Fixed prefix for shipping dimensions
42
- required :width, prefix: "shipping_"
43
- required :height, prefix: "shipping_"
48
+ ## Fixed Value Namespacing
44
49
 
45
- # Fixed suffix for user contact info
46
- required :email, suffix: "_contact"
47
- required :phone, suffix: "_contact"
50
+ Use string or symbol values for consistent prefixes or suffixes:
48
51
 
49
- # Combined prefix and suffix
50
- required :weight, prefix: "item_", suffix: "_kg"
52
+ ```ruby
53
+ class UpdateCustomerTask < CMDx::Task
54
+ required :id, prefix: "customer_"
55
+ required :name, prefix: "customer_"
56
+ required :email, suffix: "_address"
57
+ required :phone, suffix: "_number"
51
58
 
52
59
  def call
53
- # Generated method names with namespacing
54
- shipping_width #=> accesses width parameter
55
- shipping_height #=> accesses height parameter
56
- email_contact #=> accesses email parameter
57
- phone_contact #=> accesses phone parameter
58
- item_weight_kg #=> accesses weight parameter
60
+ customer = Customer.find(customer_id)
61
+ customer.update!(
62
+ name: customer_name,
63
+ email: email_address,
64
+ phone: phone_number
65
+ )
59
66
  end
60
-
61
67
  end
62
68
 
63
- # Call arguments use original parameter names
64
- CreateOrderTask.call(
65
- width: 10,
66
- height: 20,
67
- email: "customer@example.com",
68
- phone: "555-1234",
69
- weight: 2.5
69
+ # Call uses original parameter names
70
+ UpdateCustomerTask.call(
71
+ id: 123,
72
+ name: "Jane Smith",
73
+ email: "jane@example.com",
74
+ phone: "555-0123"
70
75
  )
71
76
  ```
72
77
 
73
- ### Dynamic Source-Based Namespacing
78
+ ## Dynamic Source-Based Namespacing
74
79
 
75
- Use `true` value to automatically generate prefixes or suffixes based on the
76
- parameter source name:
80
+ > [!TIP]
81
+ > Use `true` with `prefix:` or `suffix:` to automatically generate method names based on parameter sources, creating self-documenting code.
77
82
 
78
83
  ```ruby
79
- class ProcessUserRegistrationTask < CMDx::Task
80
-
81
- # Automatic prefix from default source (:context)
82
- required :user_id, prefix: true # Generates: context_user_id
83
-
84
- # Automatic suffix from custom source
85
- required :name, source: :profile, suffix: true # Generates: name_profile
86
-
87
- # Combined automatic namespacing
88
- required :email, source: :account, prefix: true, suffix: true # Generates: account_email_account
84
+ class GenerateInvoiceTask < CMDx::Task
85
+ required :id, prefix: true # → context_id
86
+ required :amount, source: :order, prefix: true # order_amount
87
+ required :tax_rate, source: :settings, suffix: true # tax_rate_settings
89
88
 
90
89
  def call
91
- context_user_id #=> accesses context.user_id
92
- name_profile #=> accesses profile.name
93
- account_email_account #=> accesses account.email
90
+ customer = Customer.find(context_id)
91
+ total = order_amount * (1 + tax_rate_settings)
92
+
93
+ Invoice.create!(
94
+ customer: customer,
95
+ amount: order_amount,
96
+ tax_rate: tax_rate_settings,
97
+ total: total
98
+ )
94
99
  end
95
100
 
96
101
  private
97
102
 
98
- def profile
99
- @profile ||= User.find(context.user_id).profile
103
+ def order
104
+ @order ||= Order.find(context.order_id)
100
105
  end
101
106
 
102
- def account
103
- @account ||= User.find(context.user_id).account
107
+ def settings
108
+ @settings ||= TaxSettings.for_region(context.region)
104
109
  end
105
-
106
110
  end
107
111
  ```
108
112
 
109
- > [!NOTE]
110
- > Call arguments always use original parameter names regardless of namespacing configuration.
111
-
112
113
  ## Conflict Resolution
113
114
 
114
- Namespacing is essential when dealing with method name conflicts or when
115
- accessing multiple objects with similar attribute names:
115
+ > [!WARNING]
116
+ > Parameter names that conflict with existing Ruby or CMDx methods can cause unexpected behavior. Always use namespacing to avoid method collisions.
116
117
 
117
- ### Method Name Conflicts
118
+ ### Ruby Method Conflicts
118
119
 
119
120
  ```ruby
120
- class UpdateUserProfileTask < CMDx::Task
121
-
122
- # Avoid conflict with Ruby's built-in 'name' method
123
- required :name, prefix: "user_"
124
-
125
- # Avoid conflict with custom private methods
126
- required :status, suffix: "_param"
121
+ class ProcessAccountTask < CMDx::Task
122
+ # Avoid conflicts with Ruby's built-in methods
123
+ required :name, prefix: "account_" # Not Object#name
124
+ required :class, suffix: "_type" # Not Object#class
125
+ required :method, prefix: "http_" # Not Object#method
127
126
 
128
127
  def call
129
- user_name #=> parameter value, not Ruby's Object#name
130
- status_param #=> parameter value, not custom status method
128
+ Account.create!(
129
+ name: account_name,
130
+ classification: class_type,
131
+ request_method: http_method
132
+ )
131
133
  end
134
+ end
135
+ ```
132
136
 
133
- private
137
+ ### CMDx Method Conflicts
134
138
 
135
- def status
136
- "processing" # Custom method that would conflict without suffix
137
- end
139
+ ```ruby
140
+ class DataProcessingTask < CMDx::Task
141
+ # Avoid conflicts with CMDx::Task methods
142
+ required :context, suffix: "_payload" # Not CMDx::Task#context
143
+ required :result, prefix: "api_" # Not CMDx::Task#result
144
+ required :logger, suffix: "_config" # Not CMDx::Task#logger
138
145
 
146
+ def call
147
+ process_data(context_payload, api_result, logger_config)
148
+ end
139
149
  end
140
150
  ```
141
151
 
142
- ### Multiple Source Disambiguation
152
+ ### Multi-Source Disambiguation
143
153
 
144
154
  ```ruby
145
- class GenerateInvoiceTask < CMDx::Task
146
-
147
- # Customer information
155
+ class SyncDataTask < CMDx::Task
156
+ # Customer and vendor both have overlapping attributes
157
+ required :id, source: :customer, prefix: "customer_"
148
158
  required :name, source: :customer, prefix: "customer_"
149
159
  required :email, source: :customer, prefix: "customer_"
150
160
 
151
- # Company information
152
- required :name, source: :company, prefix: "company_"
153
- required :email, source: :company, prefix: "company_"
154
-
155
- # Order information
156
- required :total, source: :order, suffix: "_amount"
157
- required :status, source: :order, suffix: "_state"
161
+ required :id, source: :vendor, prefix: "vendor_"
162
+ required :name, source: :vendor, prefix: "vendor_"
163
+ required :email, source: :vendor, prefix: "vendor_"
158
164
 
159
165
  def call
160
- # Clear disambiguation of same-named attributes
161
- customer_name #=> customer.name
162
- company_name #=> company.name
163
- customer_email #=> customer.email
164
- company_email #=> company.email
165
- total_amount #=> order.total
166
- status_state #=> order.status
166
+ sync_customer_data(customer_id, customer_name, customer_email)
167
+ sync_vendor_data(vendor_id, vendor_name, vendor_email)
167
168
  end
168
169
 
169
170
  private
@@ -172,117 +173,205 @@ class GenerateInvoiceTask < CMDx::Task
172
173
  @customer ||= Customer.find(context.customer_id)
173
174
  end
174
175
 
175
- def company
176
- @company ||= Company.find(context.company_id)
176
+ def vendor
177
+ @vendor ||= Vendor.find(context.vendor_id)
177
178
  end
178
-
179
- def order
180
- @order ||= Order.find(context.order_id)
181
- end
182
-
183
179
  end
184
180
  ```
185
181
 
186
- ## Advanced Namespacing Patterns
187
-
188
- ### Hierarchical Namespacing
182
+ ## Advanced Patterns
189
183
 
190
- Combine namespacing with nested parameters for complex data structures:
184
+ ### Hierarchical Parameter Organization
191
185
 
192
186
  ```ruby
193
187
  class CreateShipmentTask < CMDx::Task
188
+ required :address, source: :origin, prefix: "origin_" do
189
+ required :street, :city, :state, :zip_code
190
+ end
194
191
 
195
- # Origin address with prefix
196
- required :origin_address, source: :shipment, prefix: "from_" do
197
- required :street, :city, :state, :zip
192
+ required :address, source: :destination, prefix: "destination_" do
193
+ required :street, :city, :state, :zip_code
198
194
  end
199
195
 
200
- # Destination address with suffix
201
- required :destination_address, source: :shipment, suffix: "_to" do
202
- required :street, :city, :state, :zip
196
+ optional :preferences, suffix: "_config" do
197
+ required :priority, type: :string
198
+ optional :signature_required, type: :boolean, default: false
203
199
  end
204
200
 
205
201
  def call
206
- from_origin_address #=> shipment.origin_address
207
- destination_address_to #=> shipment.destination_address
208
-
209
- # Nested parameters access depends on current context
210
- street #=> current address context street
211
- city #=> current address context city
202
+ shipment = Shipment.create!(
203
+ origin_address: origin_address,
204
+ destination_address: destination_address,
205
+ priority: preferences_config[:priority],
206
+ signature_required: preferences_config[:signature_required]
207
+ )
212
208
  end
213
209
 
214
210
  private
215
211
 
216
- def shipment
217
- @shipment ||= Shipment.find(context.shipment_id)
212
+ def origin
213
+ @origin ||= Address.find(context.origin_address_id)
218
214
  end
219
215
 
216
+ def destination
217
+ @destination ||= Address.find(context.destination_address_id)
218
+ end
220
219
  end
221
220
  ```
222
221
 
223
- ### Conditional Namespacing
224
-
225
- Apply namespacing based on runtime conditions:
222
+ ### Domain-Specific Grouping
226
223
 
227
224
  ```ruby
228
225
  class ProcessPaymentTask < CMDx::Task
226
+ # Payment-related parameters
227
+ required :amount, prefix: "payment_", type: :big_decimal
228
+ required :currency, prefix: "payment_", type: :string
229
+ required :method, prefix: "payment_", type: :string
230
+
231
+ # Customer billing parameters
232
+ required :address, source: :billing, prefix: "billing_" do
233
+ required :street, :city, :country
234
+ end
229
235
 
230
- # Different namespacing based on payment type
231
- required :reference_id,
232
- prefix: -> { context.payment_type == "credit_card" ? "card_" : "bank_" }
236
+ # Merchant processing parameters
237
+ required :fee_rate, source: :processor, prefix: "processor_", type: :float
238
+ required :timeout, source: :processor, prefix: "processor_", type: :integer
233
239
 
234
240
  def call
235
- # Method names determined at runtime
236
- if context.payment_type == "credit_card"
237
- card_reference_id #=> accesses reference_id parameter
238
- else
239
- bank_reference_id #=> accesses reference_id parameter
240
- end
241
+ charge = PaymentProcessor.charge(
242
+ amount: payment_amount,
243
+ currency: payment_currency,
244
+ method: payment_method,
245
+ billing_address: billing_address,
246
+ processor_fee: payment_amount * processor_fee_rate,
247
+ timeout: processor_timeout
248
+ )
241
249
  end
242
250
 
251
+ private
252
+
253
+ def billing
254
+ @billing ||= BillingAddress.find(context.billing_address_id)
255
+ end
256
+
257
+ def processor
258
+ @processor ||= PaymentProcessor.for_method(payment_method)
259
+ end
243
260
  end
244
261
  ```
245
262
 
246
- ## Error Handling with Namespacing
263
+ ## Error Handling
247
264
 
248
- ```ruby
249
- class ValidateUserDataTask < CMDx::Task
265
+ > [!WARNING]
266
+ > Validation errors reference namespaced method names, not original parameter names. This affects error message interpretation and debugging.
250
267
 
251
- required :email,
252
- prefix: "user_",
253
- type: :string,
254
- format: { with: /@/ }
268
+ ### Validation Error Messages
255
269
 
256
- required :age,
257
- suffix: "_years",
258
- type: :integer,
259
- numeric: { min: 18 }
270
+ ```ruby
271
+ class CreateUserTask < CMDx::Task
272
+ required :email, prefix: "user_", format: { with: /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i }
273
+ required :age, suffix: "_value", type: :integer, numeric: { min: 18, max: 120 }
274
+ required :role, source: :account, prefix: "account_", inclusion: { in: %w[admin user guest] }
260
275
 
261
276
  def call
262
- # Access via namespaced methods
263
- user_email #=> validated email
264
- age_years #=> validated age
277
+ User.create!(
278
+ email: user_email,
279
+ age: age_value,
280
+ role: account_role
281
+ )
265
282
  end
266
283
 
284
+ private
285
+
286
+ def account
287
+ @account ||= Account.find(context.account_id)
288
+ end
267
289
  end
268
290
 
269
- # Invalid parameters
270
- result = ValidateUserDataTask.call(
291
+ # Invalid input produces namespaced error messages
292
+ result = CreateUserTask.call(
271
293
  email: "invalid-email",
272
- age: "not-a-number"
294
+ age: "fifteen",
295
+ account: OpenStruct.new(role: "superuser")
273
296
  )
274
297
 
275
- result.failed? #=> true
298
+ result.failed? # → true
276
299
  result.metadata
277
- #=> {
278
- # reason: "email format is not valid. age could not coerce into an integer.",
279
- # messages: {
280
- # user_email: ["format is not valid"],
281
- # age_years: ["could not coerce into an integer"]
282
- # }
300
+ # {
301
+ # reason: "user_email format is not valid. age_value could not coerce into an integer. account_role inclusion is not valid.",
302
+ # messages: {
303
+ # user_email: ["format is not valid"],
304
+ # age_value: ["could not coerce into an integer"],
305
+ # account_role: ["inclusion is not valid"]
283
306
  # }
307
+ # }
308
+ ```
309
+
310
+ ### Common Namespacing Mistakes
311
+
312
+ ```ruby
313
+ class ProblematicTask < CMDx::Task
314
+ required :data, prefix: "user_"
315
+ required :config, source: :settings, suffix: "_data"
316
+
317
+ def call
318
+ # ❌ WRONG: Using original parameter names in task methods
319
+ process(data) # NoMethodError: undefined method `data`
320
+ apply(config) # NoMethodError: undefined method `config`
321
+
322
+ # ✅ CORRECT: Using namespaced method names
323
+ process(user_data) # Works correctly
324
+ apply(config_data) # Works correctly
325
+ end
326
+
327
+ private
328
+
329
+ def settings
330
+ @settings ||= AppSettings.current
331
+ end
332
+ end
333
+
334
+ # ❌ WRONG: Using namespaced names in call arguments
335
+ ProblematicTask.call(
336
+ user_data: { name: "John" }, # ArgumentError: unknown parameter
337
+ config_data: { theme: "dark" } # ArgumentError: unknown parameter
338
+ )
339
+
340
+ # ✅ CORRECT: Using original parameter names in call arguments
341
+ ProblematicTask.call(
342
+ data: { name: "John" }, # Correct
343
+ config: { theme: "dark" } # Correct
344
+ )
345
+ ```
346
+
347
+ ### Debugging Namespaced Parameters
348
+
349
+ ```ruby
350
+ class DebuggingTask < CMDx::Task
351
+ required :id, prefix: "user_"
352
+ required :data, source: :profile, suffix: "_payload"
353
+
354
+ def call
355
+ # Use introspection to understand parameter mapping
356
+ puts "Available methods: #{methods.grep(/^(user_|.*_payload$)/)}"
357
+ # → ["user_id", "data_payload"]
358
+
359
+ # Access parameters using correct namespaced names
360
+ user = User.find(user_id)
361
+ user.update!(data_payload)
362
+ end
363
+
364
+ private
365
+
366
+ def profile
367
+ @profile ||= UserProfile.find(context.profile_id)
368
+ end
369
+ end
284
370
  ```
285
371
 
372
+ > [!NOTE]
373
+ > When debugging namespaced parameters, remember that error messages, method introspection, and stack traces will show the namespaced method names, not the original parameter names used in task calls.
374
+
286
375
  ---
287
376
 
288
377
  - **Prev:** [Parameters - Definitions](definitions.md)