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.
- checksums.yaml +4 -4
 - data/.cursor/prompts/docs.md +9 -0
 - data/.cursor/prompts/rspec.md +13 -12
 - data/.cursor/prompts/yardoc.md +11 -6
 - data/CHANGELOG.md +13 -2
 - data/README.md +1 -0
 - data/docs/ai_prompts.md +269 -195
 - data/docs/basics/call.md +124 -58
 - data/docs/basics/chain.md +190 -160
 - data/docs/basics/context.md +242 -154
 - data/docs/basics/setup.md +302 -32
 - data/docs/callbacks.md +390 -94
 - data/docs/configuration.md +181 -65
 - data/docs/deprecation.md +245 -0
 - data/docs/getting_started.md +161 -39
 - data/docs/internationalization.md +590 -70
 - data/docs/interruptions/exceptions.md +135 -118
 - data/docs/interruptions/faults.md +150 -125
 - data/docs/interruptions/halt.md +134 -80
 - data/docs/logging.md +181 -118
 - data/docs/middlewares.md +150 -377
 - data/docs/outcomes/result.md +140 -112
 - data/docs/outcomes/states.md +134 -99
 - data/docs/outcomes/statuses.md +204 -146
 - data/docs/parameters/coercions.md +232 -281
 - data/docs/parameters/defaults.md +224 -169
 - data/docs/parameters/definitions.md +289 -141
 - data/docs/parameters/namespacing.md +250 -161
 - data/docs/parameters/validations.md +260 -133
 - data/docs/testing.md +191 -197
 - data/docs/workflows.md +143 -98
 - data/lib/cmdx/callback.rb +23 -19
 - data/lib/cmdx/callback_registry.rb +1 -3
 - data/lib/cmdx/chain_inspector.rb +23 -23
 - data/lib/cmdx/chain_serializer.rb +38 -19
 - data/lib/cmdx/coercion.rb +20 -12
 - data/lib/cmdx/coercion_registry.rb +51 -32
 - data/lib/cmdx/configuration.rb +84 -31
 - data/lib/cmdx/context.rb +32 -21
 - data/lib/cmdx/core_ext/hash.rb +13 -13
 - data/lib/cmdx/core_ext/module.rb +1 -1
 - data/lib/cmdx/core_ext/object.rb +12 -12
 - data/lib/cmdx/correlator.rb +60 -39
 - data/lib/cmdx/errors.rb +105 -131
 - data/lib/cmdx/fault.rb +66 -45
 - data/lib/cmdx/immutator.rb +20 -21
 - data/lib/cmdx/lazy_struct.rb +78 -70
 - data/lib/cmdx/log_formatters/json.rb +1 -1
 - data/lib/cmdx/log_formatters/key_value.rb +1 -1
 - data/lib/cmdx/log_formatters/line.rb +1 -1
 - data/lib/cmdx/log_formatters/logstash.rb +1 -1
 - data/lib/cmdx/log_formatters/pretty_json.rb +1 -1
 - data/lib/cmdx/log_formatters/pretty_key_value.rb +1 -1
 - data/lib/cmdx/log_formatters/pretty_line.rb +1 -1
 - data/lib/cmdx/log_formatters/raw.rb +2 -2
 - data/lib/cmdx/logger.rb +19 -14
 - data/lib/cmdx/logger_ansi.rb +33 -17
 - data/lib/cmdx/logger_serializer.rb +85 -24
 - data/lib/cmdx/middleware.rb +39 -21
 - data/lib/cmdx/middleware_registry.rb +4 -3
 - data/lib/cmdx/parameter.rb +151 -89
 - data/lib/cmdx/parameter_inspector.rb +34 -21
 - data/lib/cmdx/parameter_registry.rb +36 -30
 - data/lib/cmdx/parameter_serializer.rb +21 -14
 - data/lib/cmdx/result.rb +136 -135
 - data/lib/cmdx/result_ansi.rb +31 -17
 - data/lib/cmdx/result_inspector.rb +32 -27
 - data/lib/cmdx/result_logger.rb +23 -14
 - data/lib/cmdx/result_serializer.rb +65 -27
 - data/lib/cmdx/task.rb +234 -113
 - data/lib/cmdx/task_deprecator.rb +22 -25
 - data/lib/cmdx/task_processor.rb +89 -88
 - data/lib/cmdx/task_serializer.rb +27 -14
 - data/lib/cmdx/utils/monotonic_runtime.rb +2 -4
 - data/lib/cmdx/validator.rb +25 -16
 - data/lib/cmdx/validator_registry.rb +53 -31
 - data/lib/cmdx/validators/exclusion.rb +1 -1
 - data/lib/cmdx/validators/format.rb +2 -2
 - data/lib/cmdx/validators/inclusion.rb +2 -2
 - data/lib/cmdx/validators/length.rb +2 -2
 - data/lib/cmdx/validators/numeric.rb +3 -3
 - data/lib/cmdx/validators/presence.rb +2 -2
 - data/lib/cmdx/version.rb +1 -1
 - data/lib/cmdx/workflow.rb +54 -33
 - data/lib/generators/cmdx/task_generator.rb +6 -6
 - data/lib/generators/cmdx/workflow_generator.rb +6 -6
 - metadata +3 -1
 
    
        data/docs/basics/call.md
    CHANGED
    
    | 
         @@ -1,6 +1,6 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            # Basics - Call
         
     | 
| 
       2 
2 
     | 
    
         | 
| 
       3 
     | 
    
         
            -
             
     | 
| 
      
 3 
     | 
    
         
            +
            Task execution in CMDx provides two distinct methods that handle success and failure scenarios differently. Understanding when to use each method is crucial for proper error handling and control flow in your application workflows.
         
     | 
| 
       4 
4 
     | 
    
         | 
| 
       5 
5 
     | 
    
         
             
            ## Table of Contents
         
     | 
| 
       6 
6 
     | 
    
         | 
| 
         @@ -13,18 +13,38 @@ Calling a task executes the business logic within it. Tasks provide two executio 
     | 
|
| 
       13 
13 
     | 
    
         
             
            - [Result Propagation (`throw!`)](#result-propagation-throw)
         
     | 
| 
       14 
14 
     | 
    
         
             
            - [Result Callbacks](#result-callbacks)
         
     | 
| 
       15 
15 
     | 
    
         
             
            - [Task State Lifecycle](#task-state-lifecycle)
         
     | 
| 
      
 16 
     | 
    
         
            +
            - [Error Handling](#error-handling)
         
     | 
| 
       16 
17 
     | 
    
         
             
            - [Return Value Details](#return-value-details)
         
     | 
| 
       17 
18 
     | 
    
         | 
| 
       18 
19 
     | 
    
         
             
            ## TLDR
         
     | 
| 
       19 
20 
     | 
    
         | 
| 
       20 
     | 
    
         
            -
             
     | 
| 
       21 
     | 
    
         
            -
             
     | 
| 
       22 
     | 
    
         
            -
             
     | 
| 
       23 
     | 
    
         
            -
             
     | 
| 
       24 
     | 
    
         
            -
             
     | 
| 
      
 21 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 22 
     | 
    
         
            +
            # Standard execution (preferred)
         
     | 
| 
      
 23 
     | 
    
         
            +
            result = ProcessOrderTask.call(order_id: 12345)
         
     | 
| 
      
 24 
     | 
    
         
            +
            result.success?  # → true/false
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
            # Exception-based execution
         
     | 
| 
      
 27 
     | 
    
         
            +
            begin
         
     | 
| 
      
 28 
     | 
    
         
            +
              result = ProcessOrderTask.call!(order_id: 12345)
         
     | 
| 
      
 29 
     | 
    
         
            +
              # Handle success
         
     | 
| 
      
 30 
     | 
    
         
            +
            rescue CMDx::Failed => e
         
     | 
| 
      
 31 
     | 
    
         
            +
              # Handle failure
         
     | 
| 
      
 32 
     | 
    
         
            +
            end
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
            # Result callbacks
         
     | 
| 
      
 35 
     | 
    
         
            +
            ProcessOrderTask.call(order_id: 12345)
         
     | 
| 
      
 36 
     | 
    
         
            +
              .on_success { |result| notify_customer(result) }
         
     | 
| 
      
 37 
     | 
    
         
            +
              .on_failed { |result| handle_error(result) }
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
            # Propagate failures
         
     | 
| 
      
 40 
     | 
    
         
            +
            throw!(validation_result) if validation_result.failed?
         
     | 
| 
      
 41 
     | 
    
         
            +
            ```
         
     | 
| 
       25 
42 
     | 
    
         | 
| 
       26 
43 
     | 
    
         
             
            ## Execution Methods Overview
         
     | 
| 
       27 
44 
     | 
    
         | 
| 
      
 45 
     | 
    
         
            +
            > [!NOTE]
         
     | 
| 
      
 46 
     | 
    
         
            +
            > Tasks are single-use objects. Once executed, they are frozen and cannot be called again. Create a new instance for subsequent executions.
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
       28 
48 
     | 
    
         
             
            | Method | Returns | Exceptions | Use Case |
         
     | 
| 
       29 
49 
     | 
    
         
             
            |--------|---------|------------|----------|
         
     | 
| 
       30 
50 
     | 
    
         
             
            | `call` | Always returns `CMDx::Result` | Never raises | Predictable result handling |
         
     | 
| 
         @@ -38,15 +58,15 @@ The `call` method always returns a `CMDx::Result` object regardless of execution 
     | 
|
| 
       38 
58 
     | 
    
         
             
            result = ProcessOrderTask.call(order_id: 12345)
         
     | 
| 
       39 
59 
     | 
    
         | 
| 
       40 
60 
     | 
    
         
             
            # Check execution state
         
     | 
| 
       41 
     | 
    
         
            -
            result.success?          
     | 
| 
       42 
     | 
    
         
            -
            result.failed?           
     | 
| 
       43 
     | 
    
         
            -
            result.skipped?          
     | 
| 
      
 61 
     | 
    
         
            +
            result.success?         # → true/false
         
     | 
| 
      
 62 
     | 
    
         
            +
            result.failed?          # → true/false
         
     | 
| 
      
 63 
     | 
    
         
            +
            result.skipped?         # → true/false
         
     | 
| 
       44 
64 
     | 
    
         | 
| 
       45 
65 
     | 
    
         
             
            # Access result data
         
     | 
| 
       46 
     | 
    
         
            -
            result.context.order_id  
     | 
| 
       47 
     | 
    
         
            -
            result.runtime           
     | 
| 
       48 
     | 
    
         
            -
            result.state             
     | 
| 
       49 
     | 
    
         
            -
            result.status            
     | 
| 
      
 66 
     | 
    
         
            +
            result.context.order_id # → 12345
         
     | 
| 
      
 67 
     | 
    
         
            +
            result.runtime          # → 0.05 (seconds)
         
     | 
| 
      
 68 
     | 
    
         
            +
            result.state            # → "complete"
         
     | 
| 
      
 69 
     | 
    
         
            +
            result.status           # → "success"
         
     | 
| 
       50 
70 
     | 
    
         
             
            ```
         
     | 
| 
       51 
71 
     | 
    
         | 
| 
       52 
72 
     | 
    
         
             
            ### Handling Different Outcomes
         
     | 
| 
         @@ -56,37 +76,38 @@ result = ProcessOrderTask.call(order_id: 12345) 
     | 
|
| 
       56 
76 
     | 
    
         | 
| 
       57 
77 
     | 
    
         
             
            case result.status
         
     | 
| 
       58 
78 
     | 
    
         
             
            when "success"
         
     | 
| 
       59 
     | 
    
         
            -
               
     | 
| 
      
 79 
     | 
    
         
            +
              SendConfirmationTask.call(result.context)
         
     | 
| 
       60 
80 
     | 
    
         
             
            when "skipped"
         
     | 
| 
       61 
     | 
    
         
            -
               
     | 
| 
      
 81 
     | 
    
         
            +
              Rails.logger.info("Order skipped: #{result.metadata[:reason]}")
         
     | 
| 
       62 
82 
     | 
    
         
             
            when "failed"
         
     | 
| 
       63 
     | 
    
         
            -
               
     | 
| 
      
 83 
     | 
    
         
            +
              RetryOrderJob.perform_later(result.context.order_id)
         
     | 
| 
       64 
84 
     | 
    
         
             
            end
         
     | 
| 
       65 
85 
     | 
    
         
             
            ```
         
     | 
| 
       66 
86 
     | 
    
         | 
| 
       67 
87 
     | 
    
         
             
            ## Bang Call (`call!`)
         
     | 
| 
       68 
88 
     | 
    
         | 
| 
       69 
     | 
    
         
            -
            The bang `call!` method raises a `CMDx::Fault` exception when tasks fail or are skipped 
     | 
| 
      
 89 
     | 
    
         
            +
            The bang `call!` method raises a `CMDx::Fault` exception when tasks fail or are skipped. It returns a `CMDx::Result` object only on success.
         
     | 
| 
      
 90 
     | 
    
         
            +
             
     | 
| 
      
 91 
     | 
    
         
            +
            > [!WARNING]
         
     | 
| 
      
 92 
     | 
    
         
            +
            > `call!` behavior depends on the `task_halt` configuration. By default, it raises exceptions for both failures and skips.
         
     | 
| 
       70 
93 
     | 
    
         | 
| 
       71 
94 
     | 
    
         
             
            ```ruby
         
     | 
| 
       72 
95 
     | 
    
         
             
            begin
         
     | 
| 
       73 
96 
     | 
    
         
             
              result = ProcessOrderTask.call!(order_id: 12345)
         
     | 
| 
       74 
     | 
    
         
            -
               
     | 
| 
      
 97 
     | 
    
         
            +
              SendConfirmationTask.call(result.context)
         
     | 
| 
       75 
98 
     | 
    
         
             
            rescue CMDx::Failed => e
         
     | 
| 
       76 
     | 
    
         
            -
              # Handle failure
         
     | 
| 
       77 
99 
     | 
    
         
             
              RetryOrderJob.perform_later(e.result.context.order_id)
         
     | 
| 
       78 
100 
     | 
    
         
             
            rescue CMDx::Skipped => e
         
     | 
| 
       79 
     | 
    
         
            -
              # Handle skip
         
     | 
| 
       80 
101 
     | 
    
         
             
              Rails.logger.info("Order skipped: #{e.result.metadata[:reason]}")
         
     | 
| 
       81 
102 
     | 
    
         
             
            end
         
     | 
| 
       82 
103 
     | 
    
         
             
            ```
         
     | 
| 
       83 
104 
     | 
    
         | 
| 
       84 
105 
     | 
    
         
             
            ### Exception Types
         
     | 
| 
       85 
106 
     | 
    
         | 
| 
       86 
     | 
    
         
            -
            | Exception | Raised When |  
     | 
| 
       87 
     | 
    
         
            -
             
     | 
| 
       88 
     | 
    
         
            -
            | `CMDx::Failed` | Task execution fails |  
     | 
| 
       89 
     | 
    
         
            -
            | `CMDx::Skipped` | Task execution is skipped |  
     | 
| 
      
 107 
     | 
    
         
            +
            | Exception | Raised When | Access Result |
         
     | 
| 
      
 108 
     | 
    
         
            +
            |-----------|-------------|---------------|
         
     | 
| 
      
 109 
     | 
    
         
            +
            | `CMDx::Failed` | Task execution fails | `exception.result` |
         
     | 
| 
      
 110 
     | 
    
         
            +
            | `CMDx::Skipped` | Task execution is skipped | `exception.result` |
         
     | 
| 
       90 
111 
     | 
    
         | 
| 
       91 
112 
     | 
    
         
             
            ## Direct Instantiation
         
     | 
| 
       92 
113 
     | 
    
         | 
| 
         @@ -97,14 +118,14 @@ Tasks can be instantiated directly for advanced use cases, testing, and custom e 
     | 
|
| 
       97 
118 
     | 
    
         
             
            task = ProcessOrderTask.new(order_id: 12345, notify_customer: true)
         
     | 
| 
       98 
119 
     | 
    
         | 
| 
       99 
120 
     | 
    
         
             
            # Access properties before execution
         
     | 
| 
       100 
     | 
    
         
            -
            task.id                       
     | 
| 
       101 
     | 
    
         
            -
            task.context.order_id         
     | 
| 
       102 
     | 
    
         
            -
            task.context.notify_customer  
     | 
| 
       103 
     | 
    
         
            -
            task.result.state             
     | 
| 
      
 121 
     | 
    
         
            +
            task.id                      # → "abc123..." (unique task ID)
         
     | 
| 
      
 122 
     | 
    
         
            +
            task.context.order_id        # → 12345
         
     | 
| 
      
 123 
     | 
    
         
            +
            task.context.notify_customer # → true
         
     | 
| 
      
 124 
     | 
    
         
            +
            task.result.state            # → "initialized"
         
     | 
| 
       104 
125 
     | 
    
         | 
| 
       105 
126 
     | 
    
         
             
            # Manual execution
         
     | 
| 
       106 
127 
     | 
    
         
             
            task.process
         
     | 
| 
       107 
     | 
    
         
            -
            task.result.success?          
     | 
| 
      
 128 
     | 
    
         
            +
            task.result.success?         # → true/false
         
     | 
| 
       108 
129 
     | 
    
         
             
            ```
         
     | 
| 
       109 
130 
     | 
    
         | 
| 
       110 
131 
     | 
    
         
             
            ### Execution Approaches
         
     | 
| 
         @@ -115,9 +136,6 @@ task.result.success?         #=> true/false 
     | 
|
| 
       115 
136 
     | 
    
         
             
            | `TaskClass.call!(...)` | Exception-based flow | Automatic fault raising |
         
     | 
| 
       116 
137 
     | 
    
         
             
            | `TaskClass.new(...).process` | Advanced scenarios | Full control, testing flexibility |
         
     | 
| 
       117 
138 
     | 
    
         | 
| 
       118 
     | 
    
         
            -
            > [!NOTE]
         
     | 
| 
       119 
     | 
    
         
            -
            > Direct instantiation gives you access to the task instance before and after execution, but you must call the execution method manually.
         
     | 
| 
       120 
     | 
    
         
            -
             
     | 
| 
       121 
139 
     | 
    
         
             
            ## Parameter Passing
         
     | 
| 
       122 
140 
     | 
    
         | 
| 
       123 
141 
     | 
    
         
             
            All methods accept parameters that become available in the task context:
         
     | 
| 
         @@ -133,31 +151,36 @@ result = ProcessOrderTask.call( 
     | 
|
| 
       133 
151 
     | 
    
         
             
            # From another task result
         
     | 
| 
       134 
152 
     | 
    
         
             
            validation_result = ValidateOrderTask.call(order_id: 12345)
         
     | 
| 
       135 
153 
     | 
    
         | 
| 
       136 
     | 
    
         
            -
            #  
     | 
| 
       137 
     | 
    
         
            -
            result = ProcessOrderTask.call 
     | 
| 
      
 154 
     | 
    
         
            +
            # Pass Result object directly
         
     | 
| 
      
 155 
     | 
    
         
            +
            result = ProcessOrderTask.call(validation_result)
         
     | 
| 
       138 
156 
     | 
    
         | 
| 
       139 
     | 
    
         
            -
            #  
     | 
| 
       140 
     | 
    
         
            -
            result = ProcessOrderTask. 
     | 
| 
      
 157 
     | 
    
         
            +
            # Pass context from previous result
         
     | 
| 
      
 158 
     | 
    
         
            +
            result = ProcessOrderTask.call(validation_result.context)
         
     | 
| 
       141 
159 
     | 
    
         
             
            ```
         
     | 
| 
       142 
160 
     | 
    
         | 
| 
       143 
161 
     | 
    
         
             
            ## Result Propagation (`throw!`)
         
     | 
| 
       144 
162 
     | 
    
         | 
| 
       145 
163 
     | 
    
         
             
            The `throw!` method enables result propagation, allowing tasks to bubble up failures from subtasks while preserving the original fault information:
         
     | 
| 
       146 
164 
     | 
    
         | 
| 
      
 165 
     | 
    
         
            +
            > [!IMPORTANT]
         
     | 
| 
      
 166 
     | 
    
         
            +
            > Use `throw!` to maintain failure context and prevent nested error handling in complex workflows.
         
     | 
| 
      
 167 
     | 
    
         
            +
             
     | 
| 
       147 
168 
     | 
    
         
             
            ```ruby
         
     | 
| 
       148 
169 
     | 
    
         
             
            class ProcessOrderTask < CMDx::Task
         
     | 
| 
       149 
170 
     | 
    
         
             
              def call
         
     | 
| 
      
 171 
     | 
    
         
            +
                # Validate order
         
     | 
| 
       150 
172 
     | 
    
         
             
                validation_result = ValidateOrderTask.call(context)
         
     | 
| 
       151 
173 
     | 
    
         
             
                throw!(validation_result) if validation_result.failed?
         
     | 
| 
       152 
174 
     | 
    
         | 
| 
      
 175 
     | 
    
         
            +
                # Process payment
         
     | 
| 
       153 
176 
     | 
    
         
             
                payment_result = ProcessPaymentTask.call(context)
         
     | 
| 
       154 
     | 
    
         
            -
                throw!(payment_result) if payment_result. 
     | 
| 
      
 177 
     | 
    
         
            +
                throw!(payment_result) if payment_result.failed?
         
     | 
| 
       155 
178 
     | 
    
         | 
| 
      
 179 
     | 
    
         
            +
                # Schedule delivery
         
     | 
| 
       156 
180 
     | 
    
         
             
                delivery_result = ScheduleDeliveryTask.call(context)
         
     | 
| 
       157 
     | 
    
         
            -
                throw!(delivery_result)  
     | 
| 
      
 181 
     | 
    
         
            +
                throw!(delivery_result) unless delivery_result.success?
         
     | 
| 
       158 
182 
     | 
    
         | 
| 
       159 
183 
     | 
    
         
             
                # Continue with main logic
         
     | 
| 
       160 
     | 
    
         
            -
                context.order = Order.find(context.order_id)
         
     | 
| 
       161 
184 
     | 
    
         
             
                finalize_order_processing
         
     | 
| 
       162 
185 
     | 
    
         
             
              end
         
     | 
| 
       163 
186 
     | 
    
         
             
            end
         
     | 
| 
         @@ -174,15 +197,18 @@ ProcessOrderTask 
     | 
|
| 
       174 
197 
     | 
    
         
             
                SendOrderConfirmationTask.call(result.context)
         
     | 
| 
       175 
198 
     | 
    
         
             
              }
         
     | 
| 
       176 
199 
     | 
    
         
             
              .on_failed { |result|
         
     | 
| 
       177 
     | 
    
         
            -
                 
     | 
| 
      
 200 
     | 
    
         
            +
                ErrorReportingService.notify(result.metadata[:error])
         
     | 
| 
       178 
201 
     | 
    
         
             
              }
         
     | 
| 
       179 
202 
     | 
    
         
             
              .on_executed { |result|
         
     | 
| 
       180 
     | 
    
         
            -
                 
     | 
| 
      
 203 
     | 
    
         
            +
                MetricsService.timing('order.processing_time', result.runtime)
         
     | 
| 
       181 
204 
     | 
    
         
             
              }
         
     | 
| 
       182 
205 
     | 
    
         
             
            ```
         
     | 
| 
       183 
206 
     | 
    
         | 
| 
       184 
207 
     | 
    
         
             
            ### Available Callbacks
         
     | 
| 
       185 
208 
     | 
    
         | 
| 
      
 209 
     | 
    
         
            +
            > [!TIP]
         
     | 
| 
      
 210 
     | 
    
         
            +
            > Callbacks return the result object, enabling method chaining for complex conditional logic.
         
     | 
| 
      
 211 
     | 
    
         
            +
             
     | 
| 
       186 
212 
     | 
    
         
             
            ```ruby
         
     | 
| 
       187 
213 
     | 
    
         
             
            result = ProcessOrderTask.call(order_id: 12345)
         
     | 
| 
       188 
214 
     | 
    
         | 
| 
         @@ -200,8 +226,8 @@ result 
     | 
|
| 
       200 
226 
     | 
    
         | 
| 
       201 
227 
     | 
    
         
             
            # Outcome-based callbacks
         
     | 
| 
       202 
228 
     | 
    
         
             
            result
         
     | 
| 
       203 
     | 
    
         
            -
              .on_good { |r| log_positive_outcome(r) }
         
     | 
| 
       204 
     | 
    
         
            -
              .on_bad { |r| log_negative_outcome(r) }
         
     | 
| 
      
 229 
     | 
    
         
            +
              .on_good { |r| log_positive_outcome(r) }    # success or skipped
         
     | 
| 
      
 230 
     | 
    
         
            +
              .on_bad { |r| log_negative_outcome(r) }     # failed only
         
     | 
| 
       205 
231 
     | 
    
         
             
            ```
         
     | 
| 
       206 
232 
     | 
    
         | 
| 
       207 
233 
     | 
    
         
             
            ## Task State Lifecycle
         
     | 
| 
         @@ -212,10 +238,52 @@ Tasks progress through defined states during execution: 
     | 
|
| 
       212 
238 
     | 
    
         
             
            result = ProcessOrderTask.call(order_id: 12345)
         
     | 
| 
       213 
239 
     | 
    
         | 
| 
       214 
240 
     | 
    
         
             
            # Execution states
         
     | 
| 
       215 
     | 
    
         
            -
            result.state  
     | 
| 
      
 241 
     | 
    
         
            +
            result.state # → "initialized" → "executing" → "complete"/"interrupted"
         
     | 
| 
       216 
242 
     | 
    
         | 
| 
       217 
243 
     | 
    
         
             
            # Outcome statuses
         
     | 
| 
       218 
     | 
    
         
            -
            result.status  
     | 
| 
      
 244 
     | 
    
         
            +
            result.status # → "success"/"failed"/"skipped"
         
     | 
| 
      
 245 
     | 
    
         
            +
            ```
         
     | 
| 
      
 246 
     | 
    
         
            +
             
     | 
| 
      
 247 
     | 
    
         
            +
            ## Error Handling
         
     | 
| 
      
 248 
     | 
    
         
            +
             
     | 
| 
      
 249 
     | 
    
         
            +
            ### Common Error Scenarios
         
     | 
| 
      
 250 
     | 
    
         
            +
             
     | 
| 
      
 251 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 252 
     | 
    
         
            +
            # Parameter validation failure
         
     | 
| 
      
 253 
     | 
    
         
            +
            result = ProcessOrderTask.call(order_id: nil)
         
     | 
| 
      
 254 
     | 
    
         
            +
            result.failed?                    # → true
         
     | 
| 
      
 255 
     | 
    
         
            +
            result.metadata[:reason]          # → "order_id is required"
         
     | 
| 
      
 256 
     | 
    
         
            +
             
     | 
| 
      
 257 
     | 
    
         
            +
            # Business logic failure
         
     | 
| 
      
 258 
     | 
    
         
            +
            result = ProcessOrderTask.call(order_id: 99999)
         
     | 
| 
      
 259 
     | 
    
         
            +
            result.failed?                    # → true
         
     | 
| 
      
 260 
     | 
    
         
            +
            result.metadata[:error].class     # → ActiveRecord::RecordNotFound
         
     | 
| 
      
 261 
     | 
    
         
            +
             
     | 
| 
      
 262 
     | 
    
         
            +
            # Task skipped due to conditions
         
     | 
| 
      
 263 
     | 
    
         
            +
            result = ProcessOrderTask.call(order_id: 12345, force: false)
         
     | 
| 
      
 264 
     | 
    
         
            +
            result.skipped?                   # → true (if order already processed)
         
     | 
| 
      
 265 
     | 
    
         
            +
            result.metadata[:reason]          # → "Order already processed"
         
     | 
| 
      
 266 
     | 
    
         
            +
            ```
         
     | 
| 
      
 267 
     | 
    
         
            +
             
     | 
| 
      
 268 
     | 
    
         
            +
            ### Exception Handling with `call!`
         
     | 
| 
      
 269 
     | 
    
         
            +
             
     | 
| 
      
 270 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 271 
     | 
    
         
            +
            begin
         
     | 
| 
      
 272 
     | 
    
         
            +
              result = ProcessOrderTask.call!(order_id: 12345)
         
     | 
| 
      
 273 
     | 
    
         
            +
            rescue CMDx::Failed => e
         
     | 
| 
      
 274 
     | 
    
         
            +
              # Access original error details
         
     | 
| 
      
 275 
     | 
    
         
            +
              error_type = e.result.metadata[:error].class
         
     | 
| 
      
 276 
     | 
    
         
            +
              error_message = e.result.metadata[:reason]
         
     | 
| 
      
 277 
     | 
    
         
            +
             
     | 
| 
      
 278 
     | 
    
         
            +
              case error_type
         
     | 
| 
      
 279 
     | 
    
         
            +
              when ActiveRecord::RecordNotFound
         
     | 
| 
      
 280 
     | 
    
         
            +
                render json: { error: "Order not found" }, status: 404
         
     | 
| 
      
 281 
     | 
    
         
            +
              when PaymentError
         
     | 
| 
      
 282 
     | 
    
         
            +
                render json: { error: "Payment failed" }, status: 402
         
     | 
| 
      
 283 
     | 
    
         
            +
              else
         
     | 
| 
      
 284 
     | 
    
         
            +
                render json: { error: "Processing failed" }, status: 500
         
     | 
| 
      
 285 
     | 
    
         
            +
              end
         
     | 
| 
      
 286 
     | 
    
         
            +
            end
         
     | 
| 
       219 
287 
     | 
    
         
             
            ```
         
     | 
| 
       220 
288 
     | 
    
         | 
| 
       221 
289 
     | 
    
         
             
            ## Return Value Details
         
     | 
| 
         @@ -226,25 +294,23 @@ The `Result` object provides comprehensive execution information: 
     | 
|
| 
       226 
294 
     | 
    
         
             
            result = ProcessOrderTask.call(order_id: 12345)
         
     | 
| 
       227 
295 
     | 
    
         | 
| 
       228 
296 
     | 
    
         
             
            # Execution metadata
         
     | 
| 
       229 
     | 
    
         
            -
            result.id            
     | 
| 
       230 
     | 
    
         
            -
            result.runtime       
     | 
| 
       231 
     | 
    
         
            -
            result.task          
     | 
| 
       232 
     | 
    
         
            -
            result.chain         
     | 
| 
      
 297 
     | 
    
         
            +
            result.id           # → "abc123..."  (unique execution ID)
         
     | 
| 
      
 298 
     | 
    
         
            +
            result.runtime      # → 0.05         (execution time in seconds)
         
     | 
| 
      
 299 
     | 
    
         
            +
            result.task         # → ProcessOrderTask instance
         
     | 
| 
      
 300 
     | 
    
         
            +
            result.chain        # → Chain object for tracking executions
         
     | 
| 
       233 
301 
     | 
    
         | 
| 
       234 
302 
     | 
    
         
             
            # Context and metadata
         
     | 
| 
       235 
     | 
    
         
            -
            result.context       
     | 
| 
       236 
     | 
    
         
            -
            result.metadata      
     | 
| 
      
 303 
     | 
    
         
            +
            result.context      # → Context with all task data
         
     | 
| 
      
 304 
     | 
    
         
            +
            result.metadata     # → Hash with execution metadata
         
     | 
| 
       237 
305 
     | 
    
         | 
| 
       238 
306 
     | 
    
         
             
            # State checking methods
         
     | 
| 
       239 
     | 
    
         
            -
            result.good?         
     | 
| 
       240 
     | 
    
         
            -
            result.bad?          
     | 
| 
       241 
     | 
    
         
            -
            result.complete?     
     | 
| 
       242 
     | 
    
         
            -
            result.interrupted?  
     | 
| 
      
 307 
     | 
    
         
            +
            result.good?        # → true for success/skipped
         
     | 
| 
      
 308 
     | 
    
         
            +
            result.bad?         # → true for failed only
         
     | 
| 
      
 309 
     | 
    
         
            +
            result.complete?    # → true when execution finished normally
         
     | 
| 
      
 310 
     | 
    
         
            +
            result.interrupted? # → true for failed/skipped
         
     | 
| 
      
 311 
     | 
    
         
            +
            result.executed?    # → true for any completed execution
         
     | 
| 
       243 
312 
     | 
    
         
             
            ```
         
     | 
| 
       244 
313 
     | 
    
         | 
| 
       245 
     | 
    
         
            -
            > [!IMPORTANT]
         
     | 
| 
       246 
     | 
    
         
            -
            > Tasks are single-use objects. Once executed, they are frozen and cannot be called again. Create a new task instance to execute the same task again.
         
     | 
| 
       247 
     | 
    
         
            -
             
     | 
| 
       248 
314 
     | 
    
         
             
            ---
         
     | 
| 
       249 
315 
     | 
    
         | 
| 
       250 
316 
     | 
    
         
             
            - **Prev:** [Basics - Setup](setup.md)
         
     |