cmdx 1.1.1 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.DS_Store +0 -0
- data/.cursor/prompts/docs.md +4 -1
- data/.cursor/prompts/llms.md +20 -0
- data/.cursor/prompts/rspec.md +4 -1
- data/.cursor/prompts/yardoc.md +3 -2
- data/.cursor/rules/cursor-instructions.mdc +56 -1
- data/.irbrc +6 -0
- data/.rubocop.yml +29 -18
- data/.ruby-version +1 -1
- data/CHANGELOG.md +6 -128
- data/LLM.md +3317 -0
- data/README.md +68 -44
- data/docs/attributes/coercions.md +162 -0
- data/docs/attributes/defaults.md +90 -0
- data/docs/attributes/definitions.md +281 -0
- data/docs/attributes/naming.md +78 -0
- data/docs/attributes/validations.md +309 -0
- data/docs/basics/chain.md +56 -249
- data/docs/basics/context.md +56 -289
- data/docs/basics/execution.md +114 -0
- data/docs/basics/setup.md +37 -334
- data/docs/callbacks.md +89 -467
- data/docs/deprecation.md +91 -174
- data/docs/getting_started.md +212 -202
- data/docs/internationalization.md +11 -647
- data/docs/interruptions/exceptions.md +23 -198
- data/docs/interruptions/faults.md +71 -151
- data/docs/interruptions/halt.md +109 -186
- data/docs/logging.md +44 -256
- data/docs/middlewares.md +113 -426
- data/docs/outcomes/result.md +81 -228
- data/docs/outcomes/states.md +33 -221
- data/docs/outcomes/statuses.md +21 -311
- data/docs/tips_and_tricks.md +120 -70
- data/docs/workflows.md +99 -283
- data/lib/cmdx/.DS_Store +0 -0
- data/lib/cmdx/attribute.rb +229 -0
- data/lib/cmdx/attribute_registry.rb +94 -0
- data/lib/cmdx/attribute_value.rb +193 -0
- data/lib/cmdx/callback_registry.rb +69 -77
- data/lib/cmdx/chain.rb +56 -73
- data/lib/cmdx/coercion_registry.rb +52 -68
- data/lib/cmdx/coercions/array.rb +19 -18
- data/lib/cmdx/coercions/big_decimal.rb +20 -24
- data/lib/cmdx/coercions/boolean.rb +26 -25
- data/lib/cmdx/coercions/complex.rb +21 -22
- data/lib/cmdx/coercions/date.rb +25 -23
- data/lib/cmdx/coercions/date_time.rb +24 -25
- data/lib/cmdx/coercions/float.rb +25 -22
- data/lib/cmdx/coercions/hash.rb +31 -32
- data/lib/cmdx/coercions/integer.rb +30 -24
- data/lib/cmdx/coercions/rational.rb +29 -24
- data/lib/cmdx/coercions/string.rb +19 -22
- data/lib/cmdx/coercions/symbol.rb +37 -0
- data/lib/cmdx/coercions/time.rb +26 -25
- data/lib/cmdx/configuration.rb +49 -108
- data/lib/cmdx/context.rb +222 -44
- data/lib/cmdx/deprecator.rb +61 -0
- data/lib/cmdx/errors.rb +42 -252
- data/lib/cmdx/exceptions.rb +39 -0
- data/lib/cmdx/faults.rb +78 -39
- data/lib/cmdx/freezer.rb +51 -0
- data/lib/cmdx/identifier.rb +30 -0
- data/lib/cmdx/locale.rb +52 -0
- data/lib/cmdx/log_formatters/json.rb +21 -22
- data/lib/cmdx/log_formatters/key_value.rb +20 -22
- data/lib/cmdx/log_formatters/line.rb +15 -22
- data/lib/cmdx/log_formatters/logstash.rb +22 -23
- data/lib/cmdx/log_formatters/raw.rb +16 -22
- data/lib/cmdx/middleware_registry.rb +70 -74
- data/lib/cmdx/middlewares/correlate.rb +90 -54
- data/lib/cmdx/middlewares/runtime.rb +58 -0
- data/lib/cmdx/middlewares/timeout.rb +48 -68
- data/lib/cmdx/railtie.rb +12 -45
- data/lib/cmdx/result.rb +229 -314
- data/lib/cmdx/task.rb +194 -366
- data/lib/cmdx/utils/call.rb +49 -0
- data/lib/cmdx/utils/condition.rb +71 -0
- data/lib/cmdx/utils/format.rb +61 -0
- data/lib/cmdx/validator_registry.rb +63 -72
- data/lib/cmdx/validators/exclusion.rb +38 -67
- data/lib/cmdx/validators/format.rb +48 -49
- data/lib/cmdx/validators/inclusion.rb +43 -74
- data/lib/cmdx/validators/length.rb +91 -154
- data/lib/cmdx/validators/numeric.rb +87 -162
- data/lib/cmdx/validators/presence.rb +37 -50
- data/lib/cmdx/version.rb +1 -1
- data/lib/cmdx/worker.rb +178 -0
- data/lib/cmdx/workflow.rb +85 -81
- data/lib/cmdx.rb +19 -13
- data/lib/generators/cmdx/install_generator.rb +14 -13
- data/lib/generators/cmdx/task_generator.rb +25 -50
- data/lib/generators/cmdx/templates/install.rb +11 -46
- data/lib/generators/cmdx/templates/task.rb.tt +3 -2
- data/lib/locales/en.yml +18 -4
- data/src/cmdx-logo.png +0 -0
- metadata +32 -116
- data/docs/ai_prompts.md +0 -393
- data/docs/basics/call.md +0 -317
- data/docs/configuration.md +0 -344
- data/docs/parameters/coercions.md +0 -396
- data/docs/parameters/defaults.md +0 -335
- data/docs/parameters/definitions.md +0 -446
- data/docs/parameters/namespacing.md +0 -378
- data/docs/parameters/validations.md +0 -405
- data/docs/testing.md +0 -553
- data/lib/cmdx/callback.rb +0 -53
- data/lib/cmdx/chain_inspector.rb +0 -56
- data/lib/cmdx/chain_serializer.rb +0 -63
- data/lib/cmdx/coercion.rb +0 -57
- data/lib/cmdx/coercions/virtual.rb +0 -29
- data/lib/cmdx/core_ext/hash.rb +0 -83
- data/lib/cmdx/core_ext/module.rb +0 -98
- data/lib/cmdx/core_ext/object.rb +0 -125
- data/lib/cmdx/correlator.rb +0 -122
- data/lib/cmdx/error.rb +0 -60
- data/lib/cmdx/fault.rb +0 -140
- data/lib/cmdx/immutator.rb +0 -52
- data/lib/cmdx/lazy_struct.rb +0 -246
- data/lib/cmdx/log_formatters/pretty_json.rb +0 -40
- data/lib/cmdx/log_formatters/pretty_key_value.rb +0 -38
- data/lib/cmdx/log_formatters/pretty_line.rb +0 -41
- data/lib/cmdx/logger.rb +0 -49
- data/lib/cmdx/logger_ansi.rb +0 -68
- data/lib/cmdx/logger_serializer.rb +0 -116
- data/lib/cmdx/middleware.rb +0 -70
- data/lib/cmdx/parameter.rb +0 -312
- data/lib/cmdx/parameter_evaluator.rb +0 -231
- data/lib/cmdx/parameter_inspector.rb +0 -66
- data/lib/cmdx/parameter_registry.rb +0 -106
- data/lib/cmdx/parameter_serializer.rb +0 -59
- data/lib/cmdx/result_ansi.rb +0 -71
- data/lib/cmdx/result_inspector.rb +0 -71
- data/lib/cmdx/result_logger.rb +0 -59
- data/lib/cmdx/result_serializer.rb +0 -104
- data/lib/cmdx/rspec/matchers.rb +0 -28
- data/lib/cmdx/rspec/result_matchers/be_executed.rb +0 -42
- data/lib/cmdx/rspec/result_matchers/be_failed_task.rb +0 -94
- data/lib/cmdx/rspec/result_matchers/be_skipped_task.rb +0 -94
- data/lib/cmdx/rspec/result_matchers/be_state_matchers.rb +0 -59
- data/lib/cmdx/rspec/result_matchers/be_status_matchers.rb +0 -57
- data/lib/cmdx/rspec/result_matchers/be_successful_task.rb +0 -87
- data/lib/cmdx/rspec/result_matchers/have_bad_outcome.rb +0 -51
- data/lib/cmdx/rspec/result_matchers/have_caused_failure.rb +0 -58
- data/lib/cmdx/rspec/result_matchers/have_chain_index.rb +0 -59
- data/lib/cmdx/rspec/result_matchers/have_context.rb +0 -86
- data/lib/cmdx/rspec/result_matchers/have_empty_metadata.rb +0 -54
- data/lib/cmdx/rspec/result_matchers/have_good_outcome.rb +0 -52
- data/lib/cmdx/rspec/result_matchers/have_metadata.rb +0 -114
- data/lib/cmdx/rspec/result_matchers/have_preserved_context.rb +0 -66
- data/lib/cmdx/rspec/result_matchers/have_received_thrown_failure.rb +0 -64
- data/lib/cmdx/rspec/result_matchers/have_runtime.rb +0 -78
- data/lib/cmdx/rspec/result_matchers/have_thrown_failure.rb +0 -76
- data/lib/cmdx/rspec/task_matchers/be_well_formed_task.rb +0 -62
- data/lib/cmdx/rspec/task_matchers/have_callback.rb +0 -85
- data/lib/cmdx/rspec/task_matchers/have_cmd_setting.rb +0 -68
- data/lib/cmdx/rspec/task_matchers/have_executed_callbacks.rb +0 -92
- data/lib/cmdx/rspec/task_matchers/have_middleware.rb +0 -46
- data/lib/cmdx/rspec/task_matchers/have_parameter.rb +0 -181
- data/lib/cmdx/task_deprecator.rb +0 -52
- data/lib/cmdx/task_processor.rb +0 -246
- data/lib/cmdx/task_serializer.rb +0 -57
- data/lib/cmdx/utils/ansi_color.rb +0 -73
- data/lib/cmdx/utils/log_timestamp.rb +0 -36
- data/lib/cmdx/utils/monotonic_runtime.rb +0 -34
- data/lib/cmdx/utils/name_affix.rb +0 -52
- data/lib/cmdx/validator.rb +0 -57
- data/lib/generators/cmdx/templates/workflow.rb.tt +0 -7
- data/lib/generators/cmdx/workflow_generator.rb +0 -84
- data/lib/locales/ar.yml +0 -35
- data/lib/locales/cs.yml +0 -35
- data/lib/locales/da.yml +0 -35
- data/lib/locales/de.yml +0 -35
- data/lib/locales/el.yml +0 -35
- data/lib/locales/es.yml +0 -35
- data/lib/locales/fi.yml +0 -35
- data/lib/locales/fr.yml +0 -35
- data/lib/locales/he.yml +0 -35
- data/lib/locales/hi.yml +0 -35
- data/lib/locales/it.yml +0 -35
- data/lib/locales/ja.yml +0 -35
- data/lib/locales/ko.yml +0 -35
- data/lib/locales/nl.yml +0 -35
- data/lib/locales/no.yml +0 -35
- data/lib/locales/pl.yml +0 -35
- data/lib/locales/pt.yml +0 -35
- data/lib/locales/ru.yml +0 -35
- data/lib/locales/sv.yml +0 -35
- data/lib/locales/th.yml +0 -35
- data/lib/locales/tr.yml +0 -35
- data/lib/locales/vi.yml +0 -35
- data/lib/locales/zh.yml +0 -35
data/docs/basics/context.md
CHANGED
@@ -1,363 +1,130 @@
|
|
1
1
|
# Basics - Context
|
2
2
|
|
3
|
-
Task context provides flexible data storage and sharing
|
3
|
+
Task context provides flexible data storage, access, and sharing within task execution. It serves as the primary data container for all task inputs, intermediate results, and outputs.
|
4
4
|
|
5
5
|
## Table of Contents
|
6
6
|
|
7
|
-
- [
|
8
|
-
- [Context Fundamentals](#context-fundamentals)
|
7
|
+
- [Assigning Data](#assigning-data)
|
9
8
|
- [Accessing Data](#accessing-data)
|
10
9
|
- [Modifying Context](#modifying-context)
|
11
|
-
- [Data Sharing
|
12
|
-
- [Result Object Context Passing](#result-object-context-passing)
|
13
|
-
- [Context Inspection and Debugging](#context-inspection-and-debugging)
|
14
|
-
- [Error Handling](#error-handling)
|
10
|
+
- [Data Sharing](#data-sharing)
|
15
11
|
|
16
|
-
##
|
12
|
+
## Assigning Data
|
17
13
|
|
18
|
-
|
19
|
-
# Automatic parameter loading
|
20
|
-
ProcessOrderTask.call(user_id: 123, amount: 99.99)
|
21
|
-
|
22
|
-
# Dynamic attribute access
|
23
|
-
context.user_id # → 123
|
24
|
-
context[:amount] # → 99.99
|
25
|
-
|
26
|
-
# Safe modification
|
27
|
-
context.total = amount * 1.08
|
28
|
-
context.merge!(status: "processed", completed_at: Time.now)
|
29
|
-
|
30
|
-
# Task chaining with context preservation
|
31
|
-
result = ValidateOrderTask.call(context)
|
32
|
-
ProcessPaymentTask.call(result) if result.success?
|
33
|
-
```
|
34
|
-
|
35
|
-
## Context Fundamentals
|
36
|
-
|
37
|
-
> [!IMPORTANT]
|
38
|
-
> Context is automatically populated with all parameters passed to a task. Parameters become accessible as dynamic attributes using both method and hash-style access patterns.
|
39
|
-
|
40
|
-
### Automatic Parameter Loading
|
41
|
-
|
42
|
-
When calling tasks, all parameters automatically become context attributes:
|
14
|
+
Context is automatically populated with all inputs passed to a task. All keys are normalized to symbols for consistent access:
|
43
15
|
|
44
16
|
```ruby
|
45
|
-
|
46
|
-
|
47
|
-
required :amount, type: :float
|
48
|
-
optional :currency, default: "USD"
|
49
|
-
|
50
|
-
def call
|
51
|
-
# Parameters automatically available in context
|
52
|
-
context.user_id # → 123
|
53
|
-
context.amount # → 99.99
|
54
|
-
context.currency # → "USD"
|
55
|
-
end
|
56
|
-
end
|
17
|
+
# Direct execution
|
18
|
+
CalculateShipping.execute(weight: 2.5, destination: "CA")
|
57
19
|
|
58
|
-
|
20
|
+
# Instance creation
|
21
|
+
CalculateShipping.new(weight: 2.5, "destination" => "CA")
|
59
22
|
```
|
60
23
|
|
61
|
-
|
62
|
-
|
63
|
-
All keys are automatically normalized to symbols for consistent access:
|
64
|
-
|
65
|
-
```ruby
|
66
|
-
# String and symbol keys both work
|
67
|
-
ProcessOrderTask.call("user_id" => 123, :amount => 99.99)
|
68
|
-
|
69
|
-
# Both accessible as symbols
|
70
|
-
context.user_id # → 123
|
71
|
-
context.amount # → 99.99
|
72
|
-
```
|
24
|
+
> [!IMPORTANT]
|
25
|
+
> String keys are automatically converted to symbols. Use symbols for consistency in your code.
|
73
26
|
|
74
27
|
## Accessing Data
|
75
28
|
|
76
29
|
Context provides multiple access patterns with automatic nil safety:
|
77
30
|
|
78
31
|
```ruby
|
79
|
-
class
|
80
|
-
def
|
81
|
-
# Method
|
82
|
-
|
83
|
-
|
32
|
+
class CalculateShipping < CMDx::Task
|
33
|
+
def work
|
34
|
+
# Method style access (preferred)
|
35
|
+
weight = context.weight
|
36
|
+
destination = context.destination
|
84
37
|
|
85
|
-
# Hash
|
86
|
-
|
87
|
-
|
38
|
+
# Hash style access
|
39
|
+
service_type = context[:service_type]
|
40
|
+
options = context["options"]
|
88
41
|
|
89
42
|
# Safe access with defaults
|
90
|
-
|
91
|
-
|
43
|
+
rush_delivery = context.fetch!(:rush_delivery, false)
|
44
|
+
carrier = context.dig(:options, :carrier)
|
92
45
|
|
93
46
|
# Shorter alias
|
94
|
-
|
47
|
+
cost = ctx.weight * ctx.rate_per_pound # ctx aliases context
|
95
48
|
end
|
96
49
|
end
|
97
50
|
```
|
98
51
|
|
99
|
-
> [!
|
100
|
-
> Accessing undefined attributes returns `nil` instead of raising errors, enabling graceful handling of optional
|
101
|
-
|
102
|
-
### Type Safety
|
103
|
-
|
104
|
-
Context accepts any data type without restrictions:
|
105
|
-
|
106
|
-
```ruby
|
107
|
-
context.string_value = "Order #12345"
|
108
|
-
context.numeric_value = 42
|
109
|
-
context.array_value = [1, 2, 3]
|
110
|
-
context.hash_value = { total: 99.99, tax: 8.99 }
|
111
|
-
context.object_value = User.find(123)
|
112
|
-
context.timestamp = Time.now
|
113
|
-
```
|
52
|
+
> [!IMPORTANT]
|
53
|
+
> Accessing undefined context attributes returns `nil` instead of raising errors, enabling graceful handling of optional attributes.
|
114
54
|
|
115
55
|
## Modifying Context
|
116
56
|
|
117
57
|
Context supports dynamic modification during task execution:
|
118
58
|
|
119
59
|
```ruby
|
120
|
-
class
|
121
|
-
def
|
60
|
+
class CalculateShipping < CMDx::Task
|
61
|
+
def work
|
122
62
|
# Direct assignment
|
123
|
-
context.
|
124
|
-
context.
|
125
|
-
context.
|
63
|
+
context.carrier = Carrier.find_by(code: context.carrier_code)
|
64
|
+
context.package = Package.new(weight: context.weight)
|
65
|
+
context.calculated_at = Time.now
|
126
66
|
|
127
67
|
# Hash-style assignment
|
128
|
-
context[:status] = "
|
129
|
-
context["
|
68
|
+
context[:status] = "calculating"
|
69
|
+
context["tracking_number"] = "SHIP#{SecureRandom.hex(6)}"
|
130
70
|
|
131
71
|
# Conditional assignment
|
132
|
-
context.
|
72
|
+
context.insurance_included ||= false
|
133
73
|
|
134
74
|
# Batch updates
|
135
75
|
context.merge!(
|
136
76
|
status: "completed",
|
137
|
-
|
138
|
-
|
77
|
+
shipping_cost: calculate_cost,
|
78
|
+
estimated_delivery: Time.now + 3.days
|
139
79
|
)
|
140
80
|
|
141
81
|
# Remove sensitive data
|
142
|
-
context.delete!(:
|
82
|
+
context.delete!(:credit_card_token)
|
143
83
|
end
|
144
84
|
|
145
85
|
private
|
146
86
|
|
147
|
-
def
|
148
|
-
|
87
|
+
def calculate_cost
|
88
|
+
base_rate = context.weight * context.rate_per_pound
|
89
|
+
base_rate + (base_rate * context.tax_percentage)
|
149
90
|
end
|
150
91
|
end
|
151
92
|
```
|
152
93
|
|
153
94
|
> [!TIP]
|
154
|
-
> Use context for both input
|
95
|
+
> Use context for both input values and intermediate results. This creates natural data flow through your task execution pipeline.
|
155
96
|
|
156
|
-
## Data Sharing
|
97
|
+
## Data Sharing
|
157
98
|
|
158
99
|
Context enables seamless data flow between related tasks in complex workflows:
|
159
100
|
|
160
|
-
### Task Composition
|
161
|
-
|
162
101
|
```ruby
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
102
|
+
# During execution
|
103
|
+
class CalculateShipping < CMDx::Task
|
104
|
+
def work
|
105
|
+
# Validate shipping data
|
106
|
+
validation_result = ValidateAddress.execute(context)
|
168
107
|
|
169
|
-
#
|
170
|
-
|
171
|
-
throw!(payment_result) unless payment_result.success?
|
108
|
+
# Via context
|
109
|
+
CalculateInsurance.execute(context)
|
172
110
|
|
173
|
-
#
|
174
|
-
|
111
|
+
# Via result
|
112
|
+
NotifyShippingCalculated.execute(validation_result)
|
175
113
|
|
176
114
|
# Context now contains accumulated data from all tasks
|
177
|
-
context.
|
178
|
-
context.
|
179
|
-
context.notification_sent
|
115
|
+
context.address_validated #=> true (from validation)
|
116
|
+
context.insurance_calculated #=> true (from insurance)
|
117
|
+
context.notification_sent #=> true (from notification)
|
180
118
|
end
|
181
119
|
end
|
182
|
-
```
|
183
|
-
|
184
|
-
### Workflow Chains
|
185
|
-
|
186
|
-
```ruby
|
187
|
-
# Initialize workflow context
|
188
|
-
initial_data = { user_id: 123, product_ids: [1, 2, 3] }
|
189
120
|
|
190
|
-
#
|
191
|
-
|
121
|
+
# After execution
|
122
|
+
result = CalculateShipping.execute(destination: "New York, NY")
|
192
123
|
|
193
|
-
|
194
|
-
# Context accumulates data through the chain
|
195
|
-
inventory_result = CheckInventoryTask.call(validation_result.context)
|
196
|
-
payment_result = ProcessPaymentTask.call(inventory_result.context)
|
197
|
-
shipping_result = CreateShipmentTask.call(payment_result.context)
|
198
|
-
end
|
124
|
+
CreateShippingLabel.execute(result)
|
199
125
|
```
|
200
126
|
|
201
|
-
## Result Object Context Passing
|
202
|
-
|
203
|
-
> [!IMPORTANT]
|
204
|
-
> CMDx automatically extracts context when Result objects are passed to task methods, enabling powerful workflow compositions where task output becomes the next task's input.
|
205
|
-
|
206
|
-
```ruby
|
207
|
-
# Seamless task chaining
|
208
|
-
extraction_result = ExtractDataTask.call(source_id: 123)
|
209
|
-
processing_result = ProcessDataTask.call(extraction_result)
|
210
|
-
|
211
|
-
# Context flows automatically between tasks
|
212
|
-
processing_result.context.source_id # → 123 (from first task)
|
213
|
-
processing_result.context.extracted_records # → [...] (from first task)
|
214
|
-
processing_result.context.processed_count # → 50 (from second task)
|
215
|
-
```
|
216
|
-
|
217
|
-
### Error Propagation in Chains
|
218
|
-
|
219
|
-
```ruby
|
220
|
-
# Non-raising chain with error handling
|
221
|
-
extraction_result = ExtractDataTask.call(source_id: 123)
|
222
|
-
|
223
|
-
if extraction_result.failed?
|
224
|
-
# Context preserved even in failure scenarios
|
225
|
-
error_handler_result = HandleExtractionErrorTask.call(extraction_result)
|
226
|
-
return error_handler_result
|
227
|
-
end
|
228
|
-
|
229
|
-
# Continue processing with successful result
|
230
|
-
ProcessDataTask.call(extraction_result)
|
231
|
-
```
|
232
|
-
|
233
|
-
### Exception-Based Chains
|
234
|
-
|
235
|
-
```ruby
|
236
|
-
begin
|
237
|
-
# Raising version propagates exceptions while preserving context
|
238
|
-
extraction_result = ExtractDataTask.call!(source_id: 123)
|
239
|
-
processing_result = ProcessDataTask.call!(extraction_result)
|
240
|
-
notification_result = NotifyCompletionTask.call!(processing_result)
|
241
|
-
rescue CMDx::Failed => e
|
242
|
-
# Access failed task's context for error analysis
|
243
|
-
ErrorReportingTask.call(
|
244
|
-
error: e.message,
|
245
|
-
failed_context: e.result.context,
|
246
|
-
user_id: e.result.context.user_id
|
247
|
-
)
|
248
|
-
end
|
249
|
-
```
|
250
|
-
|
251
|
-
## Context Inspection and Debugging
|
252
|
-
|
253
|
-
Context provides comprehensive inspection capabilities for debugging and logging:
|
254
|
-
|
255
|
-
```ruby
|
256
|
-
class DebuggableTask < CMDx::Task
|
257
|
-
def call
|
258
|
-
# Log current context state
|
259
|
-
Rails.logger.info "Context: #{context.inspect}"
|
260
|
-
|
261
|
-
# Convert to hash for serialization
|
262
|
-
context_data = context.to_h
|
263
|
-
# → { user_id: 123, amount: 99.99, status: "processing" }
|
264
|
-
|
265
|
-
# Iterate over context data
|
266
|
-
context.each_pair do |key, value|
|
267
|
-
puts "#{key}: #{value.class} = #{value}"
|
268
|
-
end
|
269
|
-
|
270
|
-
# Check for specific keys
|
271
|
-
has_user = context.key?(:user_id) # → true
|
272
|
-
has_admin = context.key?(:admin_mode) # → false
|
273
|
-
end
|
274
|
-
end
|
275
|
-
```
|
276
|
-
|
277
|
-
### Production Logging
|
278
|
-
|
279
|
-
```ruby
|
280
|
-
class OrderProcessingTask < CMDx::Task
|
281
|
-
def call
|
282
|
-
log_context_snapshot("start")
|
283
|
-
|
284
|
-
process_order
|
285
|
-
|
286
|
-
log_context_snapshot("complete")
|
287
|
-
end
|
288
|
-
|
289
|
-
private
|
290
|
-
|
291
|
-
def log_context_snapshot(stage)
|
292
|
-
Rails.logger.info({
|
293
|
-
stage: stage,
|
294
|
-
task: self.class.name,
|
295
|
-
context: context.to_h.except(:sensitive_data)
|
296
|
-
}.to_json)
|
297
|
-
end
|
298
|
-
end
|
299
|
-
```
|
300
|
-
|
301
|
-
## Error Handling
|
302
|
-
|
303
|
-
> [!WARNING]
|
304
|
-
> Context operations are generally safe, but understanding error scenarios helps build robust applications.
|
305
|
-
|
306
|
-
### Safe Access Patterns
|
307
|
-
|
308
|
-
```ruby
|
309
|
-
class RobustTask < CMDx::Task
|
310
|
-
def call
|
311
|
-
# Safe: returns nil for missing attributes
|
312
|
-
user_id = context.user_id || 'anonymous'
|
313
|
-
|
314
|
-
# Safe: fetch with default
|
315
|
-
timeout = context.fetch!(:timeout, 30)
|
316
|
-
|
317
|
-
# Safe: deep access with nil protection
|
318
|
-
api_key = context.dig(:credentials, :api_key)
|
319
|
-
|
320
|
-
# Safe: conditional assignment
|
321
|
-
context.processed_at ||= Time.now
|
322
|
-
end
|
323
|
-
end
|
324
|
-
```
|
325
|
-
|
326
|
-
### Common Error Scenarios
|
327
|
-
|
328
|
-
```ruby
|
329
|
-
# Missing required context data
|
330
|
-
class PaymentTask < CMDx::Task
|
331
|
-
def call
|
332
|
-
# Check for required context before proceeding
|
333
|
-
unless context.user_id && context.amount
|
334
|
-
context.error_message = "Missing required payment data"
|
335
|
-
fail!(reason: "Cannot process payment")
|
336
|
-
end
|
337
|
-
|
338
|
-
process_payment
|
339
|
-
end
|
340
|
-
end
|
341
|
-
|
342
|
-
# Invalid context modifications
|
343
|
-
class ValidationTask < CMDx::Task
|
344
|
-
def call
|
345
|
-
# Context cannot be replaced entirely
|
346
|
-
# context = {} # This won't work as expected
|
347
|
-
|
348
|
-
# Instead, clear individual keys or use merge!
|
349
|
-
context.delete!(:temporary_data)
|
350
|
-
context.merge!(validation_status: "complete")
|
351
|
-
end
|
352
|
-
end
|
353
|
-
```
|
354
|
-
|
355
|
-
> [!TIP]
|
356
|
-
> Use context inspection methods liberally during development and testing. The `to_h` method is particularly useful for logging and debugging complex workflows.
|
357
|
-
|
358
|
-
[Learn more](../../lib/cmdx/lazy_struct.rb) about the `LazyStruct` implementation that powers context functionality.
|
359
|
-
|
360
127
|
---
|
361
128
|
|
362
|
-
- **Prev:** [Basics -
|
129
|
+
- **Prev:** [Basics - Execution](execution.md)
|
363
130
|
- **Next:** [Basics - Chain](chain.md)
|
@@ -0,0 +1,114 @@
|
|
1
|
+
# Basics - Execution
|
2
|
+
|
3
|
+
Task execution in CMDx provides two distinct methods that handle success and halt scenarios differently. Understanding when to use each method is crucial for proper error handling and control flow in your application workflows.
|
4
|
+
|
5
|
+
## Table of Contents
|
6
|
+
|
7
|
+
- [Methods Overview](#methods-overview)
|
8
|
+
- [Non-bang Execution](#non-bang-execution)
|
9
|
+
- [Bang Execution](#bang-execution)
|
10
|
+
- [Direct Instantiation](#direct-instantiation)
|
11
|
+
- [Result Details](#result-details)
|
12
|
+
|
13
|
+
## Methods Overview
|
14
|
+
|
15
|
+
Tasks are single-use objects. Once executed, they are frozen and cannot be executed again.
|
16
|
+
Create a new instance for subsequent executions.
|
17
|
+
|
18
|
+
| Method | Returns | Exceptions | Use Case |
|
19
|
+
|--------|---------|------------|----------|
|
20
|
+
| `execute` | Always returns `CMDx::Result` | Never raises | Predictable result handling |
|
21
|
+
| `execute!` | Returns `CMDx::Result` on success | Raises `CMDx::Fault` when skipped or failed | Exception-based control flow |
|
22
|
+
|
23
|
+
## Non-bang Execution
|
24
|
+
|
25
|
+
The `execute` method always returns a `CMDx::Result` object regardless of execution outcome.
|
26
|
+
This is the preferred method for most use cases.
|
27
|
+
|
28
|
+
Any unhandled exceptions will be caught and returned as a task failure.
|
29
|
+
|
30
|
+
```ruby
|
31
|
+
result = CreateAccount.execute(email: "user@example.com")
|
32
|
+
|
33
|
+
# Check execution state
|
34
|
+
result.success? #=> true/false
|
35
|
+
result.failed? #=> true/false
|
36
|
+
result.skipped? #=> true/false
|
37
|
+
|
38
|
+
# Access result data
|
39
|
+
result.context.email #=> "user@example.com"
|
40
|
+
result.state #=> "complete"
|
41
|
+
result.status #=> "success"
|
42
|
+
```
|
43
|
+
|
44
|
+
## Bang Execution
|
45
|
+
|
46
|
+
The bang `execute!` method raises a `CMDx::Fault` based exception when tasks fail or are skipped, and returns a `CMDx::Result` object only on success.
|
47
|
+
|
48
|
+
It raises any unhandled non-fault exceptions caused during execution.
|
49
|
+
|
50
|
+
| Exception | Raised When |
|
51
|
+
|-----------|-------------|
|
52
|
+
| `CMDx::FailFault` | Task execution fails |
|
53
|
+
| `CMDx::SkipFault` | Task execution is skipped |
|
54
|
+
|
55
|
+
> [!IMPORTANT]
|
56
|
+
> `execute!` behavior depends on the `task_breakpoints` or `workflow_breakpoints` configuration. By default, it raises exceptions only on failures.
|
57
|
+
|
58
|
+
```ruby
|
59
|
+
begin
|
60
|
+
result = CreateAccount.execute!(email: "user@example.com")
|
61
|
+
SendWelcomeEmail.execute(result.context)
|
62
|
+
rescue CMDx::FailFault => e
|
63
|
+
ScheduleAccountRetryJob.perform_later(e.result.context.email)
|
64
|
+
rescue CMDx::SkipFault => e
|
65
|
+
Rails.logger.info("Account creation skipped: #{e.result.reason}")
|
66
|
+
rescue Exception => e
|
67
|
+
ErrorTracker.capture(unhandled_exception: e)
|
68
|
+
end
|
69
|
+
```
|
70
|
+
|
71
|
+
## Direct Instantiation
|
72
|
+
|
73
|
+
Tasks can be instantiated directly for advanced use cases, testing, and custom execution patterns:
|
74
|
+
|
75
|
+
```ruby
|
76
|
+
# Direct instantiation
|
77
|
+
task = CreateAccount.new(email: "user@example.com", send_welcome: true)
|
78
|
+
|
79
|
+
# Access properties before execution
|
80
|
+
task.id #=> "abc123..." (unique task ID)
|
81
|
+
task.context.email #=> "user@example.com"
|
82
|
+
task.context.send_welcome #=> true
|
83
|
+
task.result.state #=> "initialized"
|
84
|
+
task.result.status #=> "success"
|
85
|
+
|
86
|
+
# Manual execution
|
87
|
+
task.execute
|
88
|
+
# or
|
89
|
+
task.execute!
|
90
|
+
|
91
|
+
task.result.success? #=> true/false
|
92
|
+
```
|
93
|
+
|
94
|
+
## Result Details
|
95
|
+
|
96
|
+
The `Result` object provides comprehensive execution information:
|
97
|
+
|
98
|
+
```ruby
|
99
|
+
result = CreateAccount.execute(email: "user@example.com")
|
100
|
+
|
101
|
+
# Execution metadata
|
102
|
+
result.id #=> "abc123..." (unique execution ID)
|
103
|
+
result.task #=> CreateAccount instance (frozen)
|
104
|
+
result.chain #=> Task execution chain
|
105
|
+
|
106
|
+
# Context and metadata
|
107
|
+
result.context #=> Context with all task data
|
108
|
+
result.metadata #=> Hash with execution metadata
|
109
|
+
```
|
110
|
+
|
111
|
+
---
|
112
|
+
|
113
|
+
- **Prev:** [Basics - Setup](setup.md)
|
114
|
+
- **Next:** [Basics - Context](context.md)
|