cmdx 1.7.5 → 1.9.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 +3 -3
- data/.cursor/prompts/llms.md +1 -3
- data/.cursor/prompts/rspec.md +1 -1
- data/.irbrc +14 -2
- data/CHANGELOG.md +62 -29
- data/LLM.md +203 -78
- data/README.md +23 -85
- data/docs/.DS_Store +0 -0
- data/docs/assets/favicon.ico +0 -0
- data/docs/assets/favicon.svg +1 -0
- data/docs/attributes/coercions.md +19 -29
- data/docs/attributes/defaults.md +3 -16
- data/docs/attributes/definitions.md +29 -39
- data/docs/attributes/naming.md +3 -13
- data/docs/attributes/transformations.md +63 -0
- data/docs/attributes/validations.md +23 -40
- data/docs/basics/chain.md +14 -23
- data/docs/basics/context.md +13 -22
- data/docs/basics/execution.md +8 -26
- data/docs/basics/setup.md +8 -19
- data/docs/callbacks.md +19 -32
- data/docs/deprecation.md +8 -25
- data/docs/getting_started.md +101 -77
- data/docs/index.md +120 -0
- data/docs/internationalization.md +6 -18
- data/docs/interruptions/exceptions.md +10 -16
- data/docs/interruptions/faults.md +8 -25
- data/docs/interruptions/halt.md +31 -25
- data/docs/logging.md +7 -17
- data/docs/middlewares.md +13 -29
- data/docs/outcomes/result.md +21 -38
- data/docs/outcomes/states.md +8 -22
- data/docs/outcomes/statuses.md +10 -21
- data/docs/stylesheets/extra.css +42 -0
- data/docs/tips_and_tricks.md +7 -46
- data/docs/workflows.md +23 -38
- data/examples/active_record_query_tagging.md +46 -0
- data/examples/paper_trail_whatdunnit.md +39 -0
- data/lib/cmdx/attribute.rb +9 -2
- data/lib/cmdx/attribute_value.rb +31 -10
- data/lib/cmdx/callback_registry.rb +12 -2
- data/lib/cmdx/coercions/hash.rb +6 -1
- data/lib/cmdx/configuration.rb +10 -2
- data/lib/cmdx/deprecator.rb +3 -3
- data/lib/cmdx/errors.rb +1 -1
- data/lib/cmdx/executor.rb +97 -9
- data/lib/cmdx/log_formatters/logstash.rb +4 -4
- data/lib/cmdx/pipeline.rb +4 -4
- data/lib/cmdx/railtie.rb +9 -0
- data/lib/cmdx/result.rb +10 -1
- data/lib/cmdx/task.rb +12 -7
- data/lib/cmdx/version.rb +1 -1
- data/lib/cmdx.rb +1 -0
- data/lib/generators/cmdx/templates/install.rb +9 -0
- data/lib/locales/af.yml +2 -2
- data/lib/locales/ar.yml +2 -2
- data/lib/locales/az.yml +2 -2
- data/lib/locales/be.yml +2 -2
- data/lib/locales/bg.yml +2 -2
- data/lib/locales/bn.yml +2 -2
- data/lib/locales/bs.yml +2 -2
- data/lib/locales/ca.yml +2 -2
- data/lib/locales/cnr.yml +2 -2
- data/lib/locales/cs.yml +2 -2
- data/lib/locales/cy.yml +2 -2
- data/lib/locales/da.yml +2 -2
- data/lib/locales/de.yml +2 -2
- data/lib/locales/dz.yml +2 -2
- data/lib/locales/el.yml +2 -2
- data/lib/locales/en.yml +2 -2
- data/lib/locales/eo.yml +2 -2
- data/lib/locales/es.yml +2 -2
- data/lib/locales/et.yml +2 -2
- data/lib/locales/eu.yml +2 -2
- data/lib/locales/fa.yml +2 -2
- data/lib/locales/fi.yml +2 -2
- data/lib/locales/fr.yml +2 -2
- data/lib/locales/fy.yml +2 -2
- data/lib/locales/gd.yml +2 -2
- data/lib/locales/gl.yml +2 -2
- data/lib/locales/he.yml +2 -2
- data/lib/locales/hi.yml +2 -2
- data/lib/locales/hr.yml +2 -2
- data/lib/locales/hu.yml +2 -2
- data/lib/locales/hy.yml +2 -2
- data/lib/locales/id.yml +2 -2
- data/lib/locales/is.yml +2 -2
- data/lib/locales/it.yml +2 -2
- data/lib/locales/ja.yml +2 -2
- data/lib/locales/ka.yml +2 -2
- data/lib/locales/kk.yml +2 -2
- data/lib/locales/km.yml +2 -2
- data/lib/locales/kn.yml +2 -2
- data/lib/locales/ko.yml +2 -2
- data/lib/locales/lb.yml +2 -2
- data/lib/locales/lo.yml +2 -2
- data/lib/locales/lt.yml +2 -2
- data/lib/locales/lv.yml +2 -2
- data/lib/locales/mg.yml +2 -2
- data/lib/locales/mk.yml +2 -2
- data/lib/locales/ml.yml +2 -2
- data/lib/locales/mn.yml +2 -2
- data/lib/locales/mr-IN.yml +2 -2
- data/lib/locales/ms.yml +2 -2
- data/lib/locales/nb.yml +2 -2
- data/lib/locales/ne.yml +2 -2
- data/lib/locales/nl.yml +2 -2
- data/lib/locales/nn.yml +2 -2
- data/lib/locales/oc.yml +2 -2
- data/lib/locales/or.yml +2 -2
- data/lib/locales/pa.yml +2 -2
- data/lib/locales/pl.yml +2 -2
- data/lib/locales/pt.yml +2 -2
- data/lib/locales/rm.yml +2 -2
- data/lib/locales/ro.yml +2 -2
- data/lib/locales/ru.yml +2 -2
- data/lib/locales/sc.yml +2 -2
- data/lib/locales/sk.yml +2 -2
- data/lib/locales/sl.yml +2 -2
- data/lib/locales/sq.yml +2 -2
- data/lib/locales/sr.yml +2 -2
- data/lib/locales/st.yml +2 -2
- data/lib/locales/sv.yml +2 -2
- data/lib/locales/sw.yml +2 -2
- data/lib/locales/ta.yml +2 -2
- data/lib/locales/te.yml +2 -2
- data/lib/locales/th.yml +2 -2
- data/lib/locales/tl.yml +2 -2
- data/lib/locales/tr.yml +2 -2
- data/lib/locales/tt.yml +2 -2
- data/lib/locales/ug.yml +2 -2
- data/lib/locales/uk.yml +2 -2
- data/lib/locales/ur.yml +2 -2
- data/lib/locales/uz.yml +2 -2
- data/lib/locales/vi.yml +2 -2
- data/lib/locales/wo.yml +2 -2
- data/lib/locales/zh-CN.yml +2 -2
- data/lib/locales/zh-HK.yml +2 -2
- data/lib/locales/zh-TW.yml +2 -2
- data/lib/locales/zh-YUE.yml +2 -2
- data/mkdocs.yml +122 -0
- data/src/cmdx-dark-logo.png +0 -0
- data/src/cmdx-favicon.svg +1 -0
- data/src/cmdx-light-logo.png +0 -0
- data/src/cmdx-logo.svg +1 -0
- metadata +15 -4
- data/lib/cmdx/freezer.rb +0 -51
- data/src/cmdx-logo.png +0 -0
    
        data/README.md
    CHANGED
    
    | @@ -1,68 +1,46 @@ | |
| 1 | 
            -
            < | 
| 2 | 
            -
              <img src="./src/cmdx-logo.png" width="200" alt="CMDx Logo">
         | 
| 3 | 
            -
             | 
| 1 | 
            +
            <div align="center">
         | 
| 2 | 
            +
              <img src="./src/cmdx-light-logo.png#gh-light-mode-only" width="200" alt="CMDx Logo">
         | 
| 3 | 
            +
              <img src="./src/cmdx-dark-logo.png#gh-dark-mode-only" width="200" alt="CMDx Logo">
         | 
| 4 | 
            +
             | 
| 5 | 
            +
              ---
         | 
| 6 | 
            +
             | 
| 7 | 
            +
              Build business logic that’s powerful, predictable, and maintainable.
         | 
| 8 | 
            +
             | 
| 9 | 
            +
              [Documentation](https://drexed.github.io/cmdx) · [Changelog](./CHANGELOG.md) · [Report Bug](https://github.com/drexed/cmdx/issues) · [Request Feature](https://github.com/drexed/cmdx/issues)
         | 
| 4 10 |  | 
| 5 | 
            -
            <p align="center">
         | 
| 6 11 | 
             
              <img alt="Version" src="https://img.shields.io/gem/v/cmdx">
         | 
| 7 12 | 
             
              <img alt="Build" src="https://github.com/drexed/cmdx/actions/workflows/ci.yml/badge.svg">
         | 
| 8 13 | 
             
              <img alt="License" src="https://img.shields.io/github/license/drexed/cmdx">
         | 
| 9 | 
            -
            </ | 
| 14 | 
            +
            </div>
         | 
| 10 15 |  | 
| 11 | 
            -
            #  | 
| 16 | 
            +
            # CMDx
         | 
| 12 17 |  | 
| 13 | 
            -
             | 
| 18 | 
            +
            Say goodbye to messy service objects. CMDx helps you design business logic with clarity and consistency—build faster, debug easier, and ship with confidence.
         | 
| 14 19 |  | 
| 15 | 
            -
             | 
| 16 | 
            -
            - Scale to complex tasks and multi-step workflows
         | 
| 17 | 
            -
            - Get built-in flow control, logging, validations, and more...
         | 
| 20 | 
            +
            ## Requirements
         | 
| 18 21 |  | 
| 19 | 
            -
             | 
| 22 | 
            +
            - Ruby: MRI 3.1+ or JRuby 9.4+.
         | 
| 20 23 |  | 
| 21 | 
            -
             | 
| 22 | 
            -
             | 
| 23 | 
            -
            CMDx encourages breaking business logic into composable tasks. Each task can be combined into larger workflows, executed with standardized flow control, and fully observed through logging, validations, and context.
         | 
| 24 | 
            -
             | 
| 25 | 
            -
            - **Compose** → Define small, contract-driven tasks with typed attributes, validations, and natural workflow composition.
         | 
| 26 | 
            -
            - **Execute** → Run tasks with clear outcomes, intentional halts, and pluggable behaviors via middlewares and callbacks.
         | 
| 27 | 
            -
            - **React** → Adapt to outcomes by chaining follow-up tasks, handling faults, or shaping future flows.
         | 
| 28 | 
            -
            - **Observe** → Capture immutable results, structured logs, and full execution chains for reliable tracing and insight.
         | 
| 24 | 
            +
            CMDx works with any Ruby framework. Rails support is built-in, but it's framework-agnostic at its core.
         | 
| 29 25 |  | 
| 30 26 | 
             
            ## Installation
         | 
| 31 27 |  | 
| 32 | 
            -
             | 
| 33 | 
            -
             | 
| 34 | 
            -
             | 
| 35 | 
            -
             | 
| 28 | 
            +
            ```sh
         | 
| 29 | 
            +
            gem install cmdx
         | 
| 30 | 
            +
            # - or -
         | 
| 31 | 
            +
            bundle add cmdx
         | 
| 36 32 | 
             
            ```
         | 
| 37 33 |  | 
| 38 | 
            -
            And then execute:
         | 
| 39 | 
            -
             | 
| 40 | 
            -
                $ bundle
         | 
| 41 | 
            -
             | 
| 42 | 
            -
            Or install it yourself as:
         | 
| 43 | 
            -
             | 
| 44 | 
            -
                $ gem install cmdx
         | 
| 45 | 
            -
             | 
| 46 34 | 
             
            ## Quick Example
         | 
| 47 35 |  | 
| 48 | 
            -
             | 
| 36 | 
            +
            Build powerful business logic in four simple steps:
         | 
| 49 37 |  | 
| 50 38 | 
             
            ### 1. Compose
         | 
| 51 39 |  | 
| 52 | 
            -
            #### Minimum Viable Task
         | 
| 53 | 
            -
             | 
| 54 40 | 
             
            ```ruby
         | 
| 55 | 
            -
             | 
| 56 | 
            -
             | 
| 57 | 
            -
                user = User.find(context.user_id)
         | 
| 58 | 
            -
                MetricsMailer.analyzed(user).deliver_now
         | 
| 59 | 
            -
              end
         | 
| 60 | 
            -
            end
         | 
| 61 | 
            -
            ```
         | 
| 62 | 
            -
             | 
| 63 | 
            -
            #### Fully Featured Task
         | 
| 41 | 
            +
            # Full-featured task example
         | 
| 42 | 
            +
            # See docs for minimum viable task examples
         | 
| 64 43 |  | 
| 65 | 
            -
            ```ruby
         | 
| 66 44 | 
             
            class AnalyzeMetrics < CMDx::Task
         | 
| 67 45 | 
             
              register :middleware, CMDx::Middlewares::Correlate, id: -> { Current.request_id }
         | 
| 68 46 |  | 
| @@ -127,36 +105,6 @@ I, [2022-07-17T18:43:15.000000 #3784] INFO -- CMDx: | |
| 127 105 | 
             
            index=0 chain_id="018c2b95-b764-7615-a924-cc5b910ed1e5" type="Task" class="AnalyzeMetrics" state="complete" status="success" metadata={runtime: 187}
         | 
| 128 106 | 
             
            ```
         | 
| 129 107 |  | 
| 130 | 
            -
            ## Table of contents
         | 
| 131 | 
            -
             | 
| 132 | 
            -
            - [Getting Started](docs/getting_started.md)
         | 
| 133 | 
            -
            - Basics
         | 
| 134 | 
            -
              - [Setup](docs/basics/setup.md)
         | 
| 135 | 
            -
              - [Execution](docs/basics/execution.md)
         | 
| 136 | 
            -
              - [Context](docs/basics/context.md)
         | 
| 137 | 
            -
              - [Chain](docs/basics/chain.md)
         | 
| 138 | 
            -
            - Interruptions
         | 
| 139 | 
            -
              - [Halt](docs/interruptions/halt.md)
         | 
| 140 | 
            -
              - [Faults](docs/interruptions/faults.md)
         | 
| 141 | 
            -
              - [Exceptions](docs/interruptions/exceptions.md)
         | 
| 142 | 
            -
            - Outcomes
         | 
| 143 | 
            -
              - [Result](docs/outcomes/result.md)
         | 
| 144 | 
            -
              - [States](docs/outcomes/states.md)
         | 
| 145 | 
            -
              - [Statuses](docs/outcomes/statuses.md)
         | 
| 146 | 
            -
            - Attributes
         | 
| 147 | 
            -
              - [Definitions](docs/attributes/definitions.md)
         | 
| 148 | 
            -
              - [Naming](docs/attributes/naming.md)
         | 
| 149 | 
            -
              - [Coercions](docs/attributes/coercions.md)
         | 
| 150 | 
            -
              - [Validations](docs/attributes/validations.md)
         | 
| 151 | 
            -
              - [Defaults](docs/attributes/defaults.md)
         | 
| 152 | 
            -
            - [Callbacks](docs/callbacks.md)
         | 
| 153 | 
            -
            - [Middlewares](docs/middlewares.md)
         | 
| 154 | 
            -
            - [Logging](docs/logging.md)
         | 
| 155 | 
            -
            - [Internationalization (i18n)](docs/internationalization.md)
         | 
| 156 | 
            -
            - [Deprecation](docs/deprecation.md)
         | 
| 157 | 
            -
            - [Workflows](docs/workflows.md)
         | 
| 158 | 
            -
            - [Tips and Tricks](docs/tips_and_tricks.md)
         | 
| 159 | 
            -
             | 
| 160 108 | 
             
            ## Ecosystem
         | 
| 161 109 |  | 
| 162 110 | 
             
            - [cmdx-rspec](https://github.com/drexed/cmdx-rspec) - RSpec test matchers
         | 
| @@ -166,20 +114,10 @@ For backwards compatibility of certain functionality: | |
| 166 114 | 
             
            - [cmdx-i18n](https://github.com/drexed/cmdx-i18n) - 85+ translations, `v1.5.0` - `v1.6.2`
         | 
| 167 115 | 
             
            - [cmdx-parallel](https://github.com/drexed/cmdx-parallel) - Parallel workflow tasks, `v1.6.1` - `v1.6.2`
         | 
| 168 116 |  | 
| 169 | 
            -
            ## Development
         | 
| 170 | 
            -
             | 
| 171 | 
            -
            After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
         | 
| 172 | 
            -
             | 
| 173 | 
            -
            To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
         | 
| 174 | 
            -
             | 
| 175 117 | 
             
            ## Contributing
         | 
| 176 118 |  | 
| 177 | 
            -
            Bug reports and pull requests are welcome  | 
| 119 | 
            +
            Bug reports and pull requests are welcome at https://github.com/drexed/cmdx. We're committed to fostering a welcoming, collaborative community. Please follow our [code of conduct](CODE_OF_CONDUCT.md).
         | 
| 178 120 |  | 
| 179 121 | 
             
            ## License
         | 
| 180 122 |  | 
| 181 123 | 
             
            The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
         | 
| 182 | 
            -
             | 
| 183 | 
            -
            ## Code of Conduct
         | 
| 184 | 
            -
             | 
| 185 | 
            -
            Everyone interacting in the CMDx project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](CODE_OF_CONDUCT.md).
         | 
    
        data/docs/.DS_Store
    ADDED
    
    | Binary file | 
| Binary file | 
| @@ -0,0 +1 @@ | |
| 1 | 
            +
            <svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="1000" height="1000"><g clip-path="url(#SvgjsClipPath1038)"><rect width="1000" height="1000" fill="#ffffff"></rect><g transform="matrix(6.235191420376605,0,0,6.235191420376605,175.46452176081812,150)"><svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="104.098" height="112.266"><svg width="104.098" height="112.266" viewBox="0 0 60 64.708" xmlns="http://www.w3.org/2000/svg"><path d="M29.907 17.723 26.4 23.17 13.384 3.323h3.507l9.508 14.77 1.938-3.139L18.737 0H7.291L26.4 29.262 45.045 0h-3.97L30 17.54zM9.23 61.293H6.091l18.646-29.447L4.43.093H.46l20.308 31.846L0 64.708l10.985-.092 39.138-61.2h3.323L31.57 37.754l13.662 21.415h3.97L35.445 37.754 59.537.093H48.37zm29.63-23.262 15.047 23.262H43.384l-13.662-20.77-15.508 24.093h3.97l11.63-18 11.723 18H60L41.17 35.354l-.278-.461z" fill="#000"></path></svg></svg></g></g><defs><clipPath id="SvgjsClipPath1038"><rect width="1000" height="1000" x="0" y="0" rx="150" ry="150"></rect></clipPath></defs></svg>
         | 
| @@ -1,18 +1,8 @@ | |
| 1 1 | 
             
            # Attributes - Coercions
         | 
| 2 2 |  | 
| 3 | 
            -
             | 
| 3 | 
            +
            Automatically convert inputs to expected types. Coercions handle everything from simple string-to-integer conversions to JSON parsing.
         | 
| 4 4 |  | 
| 5 | 
            -
             | 
| 6 | 
            -
             | 
| 7 | 
            -
            ## Table of Contents
         | 
| 8 | 
            -
             | 
| 9 | 
            -
            - [Usage](#usage)
         | 
| 10 | 
            -
            - [Built-in Coercions](#built-in-coercions)
         | 
| 11 | 
            -
            - [Declarations](#declarations)
         | 
| 12 | 
            -
              - [Proc or Lambda](#proc-or-lambda)
         | 
| 13 | 
            -
              - [Class or Module](#class-or-module)
         | 
| 14 | 
            -
            - [Removals](#removals)
         | 
| 15 | 
            -
            - [Error Handling](#error-handling)
         | 
| 5 | 
            +
            See [Global Configuration](../getting_started.md#coercions) for custom coercion setup.
         | 
| 16 6 |  | 
| 17 7 | 
             
            ## Usage
         | 
| 18 8 |  | 
| @@ -43,8 +33,9 @@ ParseMetrics.execute( | |
| 43 33 | 
             
            )
         | 
| 44 34 | 
             
            ```
         | 
| 45 35 |  | 
| 46 | 
            -
             | 
| 47 | 
            -
             | 
| 36 | 
            +
            !!! tip
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                Specify multiple coercion types for attributes that could be a variety of value formats. CMDx attempts each type in order until one succeeds.
         | 
| 48 39 |  | 
| 49 40 | 
             
            ## Built-in Coercions
         | 
| 50 41 |  | 
| @@ -66,8 +57,9 @@ ParseMetrics.execute( | |
| 66 57 |  | 
| 67 58 | 
             
            ## Declarations
         | 
| 68 59 |  | 
| 69 | 
            -
             | 
| 70 | 
            -
             | 
| 60 | 
            +
            !!! warning "Important"
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                Custom coercions must raise `CMDx::CoercionError` with a descriptive message.
         | 
| 71 63 |  | 
| 72 64 | 
             
            ### Proc or Lambda
         | 
| 73 65 |  | 
| @@ -117,10 +109,11 @@ end | |
| 117 109 |  | 
| 118 110 | 
             
            ## Removals
         | 
| 119 111 |  | 
| 120 | 
            -
            Remove  | 
| 112 | 
            +
            Remove unwanted coercions:
         | 
| 121 113 |  | 
| 122 | 
            -
             | 
| 123 | 
            -
             | 
| 114 | 
            +
            !!! warning
         | 
| 115 | 
            +
             | 
| 116 | 
            +
                Each `deregister` call removes one coercion. Use multiple calls for batch removals.
         | 
| 124 117 |  | 
| 125 118 | 
             
            ```ruby
         | 
| 126 119 | 
             
            class TransformCoordinates < CMDx::Task
         | 
| @@ -149,17 +142,14 @@ result = AnalyzePerformance.execute( | |
| 149 142 |  | 
| 150 143 | 
             
            result.state    #=> "interrupted"
         | 
| 151 144 | 
             
            result.status   #=> "failed"
         | 
| 152 | 
            -
            result.reason   #=> "Invalid | 
| 145 | 
            +
            result.reason   #=> "Invalid"
         | 
| 153 146 | 
             
            result.metadata #=> {
         | 
| 154 | 
            -
                            #      | 
| 155 | 
            -
                            # | 
| 156 | 
            -
                            #        | 
| 157 | 
            -
                            # | 
| 147 | 
            +
                            #     errors: {
         | 
| 148 | 
            +
                            #       full_message: "iterations could not coerce into an integer. score could not coerce into one of: float, big_decimal.",
         | 
| 149 | 
            +
                            #       messages: {
         | 
| 150 | 
            +
                            #         iterations: ["could not coerce into an integer"],
         | 
| 151 | 
            +
                            #         score: ["could not coerce into one of: float, big_decimal"]
         | 
| 152 | 
            +
                            #       }
         | 
| 158 153 | 
             
                            #     }
         | 
| 159 154 | 
             
                            #   }
         | 
| 160 155 | 
             
            ```
         | 
| 161 | 
            -
             | 
| 162 | 
            -
            ---
         | 
| 163 | 
            -
             | 
| 164 | 
            -
            - **Prev:** [Attributes - Naming](naming.md)
         | 
| 165 | 
            -
            - **Next:** [Attributes - Validations](validations.md)
         | 
    
        data/docs/attributes/defaults.md
    CHANGED
    
    | @@ -1,18 +1,10 @@ | |
| 1 1 | 
             
            # Attributes - Defaults
         | 
| 2 2 |  | 
| 3 | 
            -
             | 
| 4 | 
            -
             | 
| 5 | 
            -
            ## Table of Contents
         | 
| 6 | 
            -
             | 
| 7 | 
            -
            - [Declarations](#declarations)
         | 
| 8 | 
            -
              - [Static Values](#static-values)
         | 
| 9 | 
            -
              - [Symbol References](#symbol-references)
         | 
| 10 | 
            -
              - [Proc or Lambda](#proc-or-lambda)
         | 
| 11 | 
            -
            - [Coercions and Validations](#coercions-and-validations)
         | 
| 3 | 
            +
            Provide fallback values for optional attributes. Defaults kick in when values aren't provided or are `nil`.
         | 
| 12 4 |  | 
| 13 5 | 
             
            ## Declarations
         | 
| 14 6 |  | 
| 15 | 
            -
            Defaults  | 
| 7 | 
            +
            Defaults work seamlessly with coercions, validations, and nested attributes:
         | 
| 16 8 |  | 
| 17 9 | 
             
            ### Static Values
         | 
| 18 10 |  | 
| @@ -72,7 +64,7 @@ end | |
| 72 64 |  | 
| 73 65 | 
             
            ## Coercions and Validations
         | 
| 74 66 |  | 
| 75 | 
            -
            Defaults  | 
| 67 | 
            +
            Defaults follow the same coercion and validation rules as provided values:
         | 
| 76 68 |  | 
| 77 69 | 
             
            ```ruby
         | 
| 78 70 | 
             
            class ScheduleBackup < CMDx::Task
         | 
| @@ -83,8 +75,3 @@ class ScheduleBackup < CMDx::Task | |
| 83 75 | 
             
              optional :frequency, default: "daily", inclusion: { in: %w[hourly daily weekly monthly] }
         | 
| 84 76 | 
             
            end
         | 
| 85 77 | 
             
            ```
         | 
| 86 | 
            -
             | 
| 87 | 
            -
            ---
         | 
| 88 | 
            -
             | 
| 89 | 
            -
            - **Prev:** [Attributes - Validations](validations.md)
         | 
| 90 | 
            -
            - **Next:** [Callbacks](../callbacks.md)
         | 
| @@ -1,24 +1,12 @@ | |
| 1 1 | 
             
            # Attributes - Definitions
         | 
| 2 2 |  | 
| 3 | 
            -
            Attributes define  | 
| 4 | 
            -
             | 
| 5 | 
            -
            ## Table of Contents
         | 
| 6 | 
            -
             | 
| 7 | 
            -
            - [Declarations](#declarations)
         | 
| 8 | 
            -
              - [Optional](#optional)
         | 
| 9 | 
            -
              - [Required](#required)
         | 
| 10 | 
            -
            - [Sources](#sources)
         | 
| 11 | 
            -
              - [Context](#context)
         | 
| 12 | 
            -
              - [Symbol References](#symbol-references)
         | 
| 13 | 
            -
              - [Proc or Lambda](#proc-or-lambda)
         | 
| 14 | 
            -
              - [Class or Module](#class-or-module)
         | 
| 15 | 
            -
            - [Nesting](#nesting)
         | 
| 16 | 
            -
            - [Error Handling](#error-handling)
         | 
| 3 | 
            +
            Attributes define your task's interface with automatic validation, type coercion, and accessor generation. They're the contract between callers and your business logic.
         | 
| 17 4 |  | 
| 18 5 | 
             
            ## Declarations
         | 
| 19 6 |  | 
| 20 | 
            -
             | 
| 21 | 
            -
             | 
| 7 | 
            +
            !!! tip
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                Prefer using the `required` and `optional` alias for `attributes` for brevity and to clearly signal intent.
         | 
| 22 10 |  | 
| 23 11 | 
             
            ### Optional
         | 
| 24 12 |  | 
| @@ -87,7 +75,7 @@ PublishArticle.execute( | |
| 87 75 |  | 
| 88 76 | 
             
            ## Sources
         | 
| 89 77 |  | 
| 90 | 
            -
            Attributes  | 
| 78 | 
            +
            Attributes read from any accessible object—not just context. Use sources to pull data from models, services, or any callable:
         | 
| 91 79 |  | 
| 92 80 | 
             
            ### Context
         | 
| 93 81 |  | 
| @@ -167,10 +155,11 @@ end | |
| 167 155 |  | 
| 168 156 | 
             
            ## Nesting
         | 
| 169 157 |  | 
| 170 | 
            -
             | 
| 158 | 
            +
            Build complex structures with nested attributes. Children inherit their parent as source and support all attribute options:
         | 
| 159 | 
            +
             | 
| 160 | 
            +
            !!! note
         | 
| 171 161 |  | 
| 172 | 
            -
             | 
| 173 | 
            -
            > All options available to top-level attributes are available to nested attributes, eg: naming, coercions, and validations
         | 
| 162 | 
            +
                Nested attributes support all features: naming, coercions, validations, defaults, and more.
         | 
| 174 163 |  | 
| 175 164 | 
             
            ```ruby
         | 
| 176 165 | 
             
            class ConfigureServer < CMDx::Task
         | 
| @@ -223,15 +212,17 @@ ConfigureServer.execute( | |
| 223 212 | 
             
            )
         | 
| 224 213 | 
             
            ```
         | 
| 225 214 |  | 
| 226 | 
            -
             | 
| 227 | 
            -
             | 
| 215 | 
            +
            !!! warning "Important"
         | 
| 216 | 
            +
             | 
| 217 | 
            +
                Child requirements only apply when the parent is provided—perfect for optional structures.
         | 
| 228 218 |  | 
| 229 219 | 
             
            ## Error Handling
         | 
| 230 220 |  | 
| 231 | 
            -
             | 
| 221 | 
            +
            Validation failures provide detailed, structured error messages:
         | 
| 222 | 
            +
             | 
| 223 | 
            +
            !!! note
         | 
| 232 224 |  | 
| 233 | 
            -
             | 
| 234 | 
            -
            > Nested attributes are only ever evaluated when the parent attribute is available and valid.
         | 
| 225 | 
            +
                Nested attributes are only validated when their parent is present and valid.
         | 
| 235 226 |  | 
| 236 227 | 
             
            ```ruby
         | 
| 237 228 | 
             
            class ConfigureServer < CMDx::Task
         | 
| @@ -250,12 +241,14 @@ result = ConfigureServer.execute(server_id: "srv-001") | |
| 250 241 |  | 
| 251 242 | 
             
            result.state    #=> "interrupted"
         | 
| 252 243 | 
             
            result.status   #=> "failed"
         | 
| 253 | 
            -
            result.reason   #=> "Invalid | 
| 244 | 
            +
            result.reason   #=> "Invalid"
         | 
| 254 245 | 
             
            result.metadata #=> {
         | 
| 255 | 
            -
                            #      | 
| 256 | 
            -
                            # | 
| 257 | 
            -
                            #        | 
| 258 | 
            -
                            # | 
| 246 | 
            +
                            #     errors: {
         | 
| 247 | 
            +
                            #       full_message: "environment is required. network_config is required.",
         | 
| 248 | 
            +
                            #       messages: {
         | 
| 249 | 
            +
                            #         environment: ["is required"],
         | 
| 250 | 
            +
                            #         network_config: ["is required"]
         | 
| 251 | 
            +
                            #       }
         | 
| 259 252 | 
             
                            #     }
         | 
| 260 253 | 
             
                            #   }
         | 
| 261 254 |  | 
| @@ -268,16 +261,13 @@ result = ConfigureServer.execute( | |
| 268 261 |  | 
| 269 262 | 
             
            result.state    #=> "interrupted"
         | 
| 270 263 | 
             
            result.status   #=> "failed"
         | 
| 271 | 
            -
            result.reason   #=> "Invalid | 
| 264 | 
            +
            result.reason   #=> "Invalid"
         | 
| 272 265 | 
             
            result.metadata #=> {
         | 
| 273 | 
            -
                            #      | 
| 274 | 
            -
                            # | 
| 275 | 
            -
                            #        | 
| 266 | 
            +
                            #     errors: {
         | 
| 267 | 
            +
                            #       full_message: "port is required.",
         | 
| 268 | 
            +
                            #       messages: {
         | 
| 269 | 
            +
                            #         port: ["is required"]
         | 
| 270 | 
            +
                            #       }
         | 
| 276 271 | 
             
                            #     }
         | 
| 277 272 | 
             
                            #   }
         | 
| 278 273 | 
             
            ```
         | 
| 279 | 
            -
             | 
| 280 | 
            -
            ---
         | 
| 281 | 
            -
             | 
| 282 | 
            -
            - **Prev:** [Outcomes - States](../outcomes/states.md)
         | 
| 283 | 
            -
            - **Next:** [Attributes - Naming](naming.md)
         | 
    
        data/docs/attributes/naming.md
    CHANGED
    
    | @@ -1,15 +1,10 @@ | |
| 1 1 | 
             
            # Attributes - Naming
         | 
| 2 2 |  | 
| 3 | 
            -
             | 
| 3 | 
            +
            Customize accessor method names to avoid conflicts and improve clarity. Affixing changes only the generated methods—not the original attribute names.
         | 
| 4 4 |  | 
| 5 | 
            -
             | 
| 6 | 
            -
            > Affixing modifies only the generated accessor method names within tasks.
         | 
| 5 | 
            +
            !!! note
         | 
| 7 6 |  | 
| 8 | 
            -
             | 
| 9 | 
            -
             | 
| 10 | 
            -
            - [Prefix](#prefix)
         | 
| 11 | 
            -
            - [Suffix](#suffix)
         | 
| 12 | 
            -
            - [As](#as)
         | 
| 7 | 
            +
                Use naming when attributes conflict with existing methods or need better clarity in your code.
         | 
| 13 8 |  | 
| 14 9 | 
             
            ## Prefix
         | 
| 15 10 |  | 
| @@ -71,8 +66,3 @@ end | |
| 71 66 | 
             
            # Attributes passed as original attribute names
         | 
| 72 67 | 
             
            ScheduleMaintenance.execute(scheduled_at: DateTime.new(2024, 12, 15, 2, 0, 0))
         | 
| 73 68 | 
             
            ```
         | 
| 74 | 
            -
             | 
| 75 | 
            -
            ---
         | 
| 76 | 
            -
             | 
| 77 | 
            -
            - **Prev:** [Attributes - Definitions](definitions.md)
         | 
| 78 | 
            -
            - **Next:** [Attributes - Coercions](coercions.md)
         | 
| @@ -0,0 +1,63 @@ | |
| 1 | 
            +
            # Attributes - Transformations
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            Modify attribute values after coercion but before validation. Perfect for normalization, formatting, and data cleanup.
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            ## Declarations
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            ### Symbol References
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            Reference instance methods by symbol for dynamic value transformations:
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            ```ruby
         | 
| 12 | 
            +
            class ProcessAnalytics < CMDx::Task
         | 
| 13 | 
            +
              attribute :options, transform: :compact_blank
         | 
| 14 | 
            +
            end
         | 
| 15 | 
            +
            ```
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            ### Proc or Lambda
         | 
| 18 | 
            +
             | 
| 19 | 
            +
            Use anonymous functions for dynamic value transformations:
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            ```ruby
         | 
| 22 | 
            +
            class CacheContent < CMDx::Task
         | 
| 23 | 
            +
              # Proc
         | 
| 24 | 
            +
              attribute :expire_hours, transform: proc { |v| v * 2 }
         | 
| 25 | 
            +
             | 
| 26 | 
            +
              # Lambda
         | 
| 27 | 
            +
              attribute :compression, transform: ->(v) { v.to_s.upcase.strip[0..2]  }
         | 
| 28 | 
            +
            end
         | 
| 29 | 
            +
            ```
         | 
| 30 | 
            +
             | 
| 31 | 
            +
            ### Class or Module
         | 
| 32 | 
            +
             | 
| 33 | 
            +
            Use any object that responds to `call` for reusable transformation logic:
         | 
| 34 | 
            +
             | 
| 35 | 
            +
            ```ruby
         | 
| 36 | 
            +
            class EmailNormalizer
         | 
| 37 | 
            +
              def call(value)
         | 
| 38 | 
            +
                value.to_s.downcase.strip
         | 
| 39 | 
            +
              end
         | 
| 40 | 
            +
            end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
            class ProcessContacts < CMDx::Task
         | 
| 43 | 
            +
              # Class or Module
         | 
| 44 | 
            +
              attribute :email, transform: EmailNormalizer
         | 
| 45 | 
            +
             | 
| 46 | 
            +
              # Instance
         | 
| 47 | 
            +
              attribute :email, transform: EmailNormalizer.new
         | 
| 48 | 
            +
            end
         | 
| 49 | 
            +
            ```
         | 
| 50 | 
            +
             | 
| 51 | 
            +
            ## Validations
         | 
| 52 | 
            +
             | 
| 53 | 
            +
            Validations run on transformed values, ensuring data consistency:
         | 
| 54 | 
            +
             | 
| 55 | 
            +
            ```ruby
         | 
| 56 | 
            +
            class ScheduleBackup < CMDx::Task
         | 
| 57 | 
            +
              # Coercions
         | 
| 58 | 
            +
              attribute :retention_days, type: :integer, transform: proc { |v| v.clamp(1, 5) }
         | 
| 59 | 
            +
             | 
| 60 | 
            +
              # Validations
         | 
| 61 | 
            +
              optional :frequency, transform: :downcase, inclusion: { in: %w[hourly daily weekly monthly] }
         | 
| 62 | 
            +
            end
         | 
| 63 | 
            +
            ```
         | 
| @@ -1,25 +1,8 @@ | |
| 1 1 | 
             
            # Attributes - Validations
         | 
| 2 2 |  | 
| 3 | 
            -
             | 
| 4 | 
            -
             | 
| 5 | 
            -
             | 
| 6 | 
            -
             | 
| 7 | 
            -
            ## Table of Contents
         | 
| 8 | 
            -
             | 
| 9 | 
            -
            - [Usage](#usage)
         | 
| 10 | 
            -
            - [Built-in Validators](#built-in-validators)
         | 
| 11 | 
            -
              - [Common Options](#common-options)
         | 
| 12 | 
            -
              - [Exclusion](#exclusion)
         | 
| 13 | 
            -
              - [Format](#format)
         | 
| 14 | 
            -
              - [Inclusion](#inclusion)
         | 
| 15 | 
            -
              - [Length](#length)
         | 
| 16 | 
            -
              - [Numeric](#numeric)
         | 
| 17 | 
            -
              - [Presence](#presence)
         | 
| 18 | 
            -
            - [Declarations](#declarations)
         | 
| 19 | 
            -
              - [Proc or Lambda](#proc-or-lambda)
         | 
| 20 | 
            -
              - [Class or Module](#class-or-module)
         | 
| 21 | 
            -
            - [Removals](#removals)
         | 
| 22 | 
            -
            - [Error Handling](#error-handling)
         | 
| 3 | 
            +
            Ensure inputs meet requirements before execution. Validations run after coercions, giving you declarative data integrity checks.
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            See [Global Configuration](../getting_started.md#validators) for custom validator setup.
         | 
| 23 6 |  | 
| 24 7 | 
             
            ## Usage
         | 
| 25 8 |  | 
| @@ -55,8 +38,9 @@ ProcessSubscription.execute( | |
| 55 38 | 
             
            )
         | 
| 56 39 | 
             
            ```
         | 
| 57 40 |  | 
| 58 | 
            -
             | 
| 59 | 
            -
             | 
| 41 | 
            +
            !!! tip
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                Validations run after coercions, so you can validate the final coerced values rather than raw input.
         | 
| 60 44 |  | 
| 61 45 | 
             
            ## Built-in Validators
         | 
| 62 46 |  | 
| @@ -211,8 +195,9 @@ end | |
| 211 195 |  | 
| 212 196 | 
             
            ## Declarations
         | 
| 213 197 |  | 
| 214 | 
            -
             | 
| 215 | 
            -
             | 
| 198 | 
            +
            !!! warning "Important"
         | 
| 199 | 
            +
             | 
| 200 | 
            +
                Custom validators must raise `CMDx::ValidationError` with a descriptive message.
         | 
| 216 201 |  | 
| 217 202 | 
             
            ### Proc or Lambda
         | 
| 218 203 |  | 
| @@ -258,10 +243,11 @@ end | |
| 258 243 |  | 
| 259 244 | 
             
            ## Removals
         | 
| 260 245 |  | 
| 261 | 
            -
            Remove  | 
| 246 | 
            +
            Remove unwanted validators:
         | 
| 262 247 |  | 
| 263 | 
            -
             | 
| 264 | 
            -
             | 
| 248 | 
            +
            !!! warning
         | 
| 249 | 
            +
             | 
| 250 | 
            +
                Each `deregister` call removes one validator. Use multiple calls for batch removals.
         | 
| 265 251 |  | 
| 266 252 | 
             
            ```ruby
         | 
| 267 253 | 
             
            class SetupApplication < CMDx::Task
         | 
| @@ -271,7 +257,7 @@ end | |
| 271 257 |  | 
| 272 258 | 
             
            ## Error Handling
         | 
| 273 259 |  | 
| 274 | 
            -
            Validation failures provide detailed | 
| 260 | 
            +
            Validation failures provide detailed, structured error messages:
         | 
| 275 261 |  | 
| 276 262 | 
             
            ```ruby
         | 
| 277 263 | 
             
            class CreateProject < CMDx::Task
         | 
| @@ -294,19 +280,16 @@ result = CreateProject.execute( | |
| 294 280 |  | 
| 295 281 | 
             
            result.state    #=> "interrupted"
         | 
| 296 282 | 
             
            result.status   #=> "failed"
         | 
| 297 | 
            -
            result.reason   #=> "Invalid | 
| 283 | 
            +
            result.reason   #=> "Invalid"
         | 
| 298 284 | 
             
            result.metadata #=> {
         | 
| 299 | 
            -
                            #      | 
| 300 | 
            -
                            # | 
| 301 | 
            -
                            #        | 
| 302 | 
            -
                            # | 
| 303 | 
            -
                            # | 
| 304 | 
            -
                            # | 
| 285 | 
            +
                            #     errors: {
         | 
| 286 | 
            +
                            #       full_message: "project_name is too short (minimum is 3 characters). budget must be greater than 1000. priority is not included in the list. contact_email is invalid.",
         | 
| 287 | 
            +
                            #       messages: {
         | 
| 288 | 
            +
                            #         project_name: ["is too short (minimum is 3 characters)"],
         | 
| 289 | 
            +
                            #         budget: ["must be greater than 1000"],
         | 
| 290 | 
            +
                            #         priority: ["is not included in the list"],
         | 
| 291 | 
            +
                            #         contact_email: ["is invalid"]
         | 
| 292 | 
            +
                            #       }
         | 
| 305 293 | 
             
                            #     }
         | 
| 306 294 | 
             
                            #   }
         | 
| 307 295 | 
             
            ```
         | 
| 308 | 
            -
             | 
| 309 | 
            -
            ---
         | 
| 310 | 
            -
             | 
| 311 | 
            -
            - **Prev:** [Attributes - Coercions](coercions.md)
         | 
| 312 | 
            -
            - **Next:** [Attributes - Defaults](defaults.md)
         |