cmdx 1.8.0 → 1.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. checksums.yaml +4 -4
  2. data/.DS_Store +0 -0
  3. data/.cursor/prompts/docs.md +3 -3
  4. data/.cursor/prompts/llms.md +1 -3
  5. data/.cursor/prompts/yardoc.md +1 -0
  6. data/.irbrc +14 -2
  7. data/CHANGELOG.md +64 -45
  8. data/LLM.md +159 -53
  9. data/README.md +26 -83
  10. data/docs/.DS_Store +0 -0
  11. data/docs/assets/favicon.ico +0 -0
  12. data/docs/assets/favicon.svg +1 -0
  13. data/docs/attributes/coercions.md +12 -24
  14. data/docs/attributes/defaults.md +3 -16
  15. data/docs/attributes/definitions.md +16 -30
  16. data/docs/attributes/naming.md +3 -13
  17. data/docs/attributes/transformations.md +63 -0
  18. data/docs/attributes/validations.md +14 -33
  19. data/docs/basics/chain.md +14 -23
  20. data/docs/basics/context.md +13 -22
  21. data/docs/basics/execution.md +8 -26
  22. data/docs/basics/setup.md +8 -19
  23. data/docs/callbacks.md +19 -32
  24. data/docs/deprecation.md +8 -25
  25. data/docs/getting_started.md +109 -76
  26. data/docs/index.md +132 -0
  27. data/docs/internationalization.md +6 -18
  28. data/docs/interruptions/exceptions.md +10 -16
  29. data/docs/interruptions/faults.md +8 -25
  30. data/docs/interruptions/halt.md +12 -27
  31. data/docs/logging.md +7 -17
  32. data/docs/middlewares.md +13 -29
  33. data/docs/outcomes/result.md +21 -38
  34. data/docs/outcomes/states.md +8 -22
  35. data/docs/outcomes/statuses.md +10 -21
  36. data/docs/stylesheets/extra.css +42 -0
  37. data/docs/tips_and_tricks.md +7 -46
  38. data/docs/workflows.md +23 -38
  39. data/examples/active_record_query_tagging.md +46 -0
  40. data/examples/paper_trail_whatdunnit.md +39 -0
  41. data/lib/cmdx/attribute.rb +88 -6
  42. data/lib/cmdx/attribute_registry.rb +20 -0
  43. data/lib/cmdx/attribute_value.rb +56 -10
  44. data/lib/cmdx/callback_registry.rb +31 -2
  45. data/lib/cmdx/chain.rb +34 -1
  46. data/lib/cmdx/coercion_registry.rb +18 -0
  47. data/lib/cmdx/coercions/array.rb +2 -0
  48. data/lib/cmdx/coercions/big_decimal.rb +3 -0
  49. data/lib/cmdx/coercions/boolean.rb +5 -0
  50. data/lib/cmdx/coercions/complex.rb +2 -0
  51. data/lib/cmdx/coercions/date.rb +4 -0
  52. data/lib/cmdx/coercions/date_time.rb +5 -0
  53. data/lib/cmdx/coercions/float.rb +2 -0
  54. data/lib/cmdx/coercions/hash.rb +4 -0
  55. data/lib/cmdx/coercions/integer.rb +2 -0
  56. data/lib/cmdx/coercions/rational.rb +2 -0
  57. data/lib/cmdx/coercions/string.rb +2 -0
  58. data/lib/cmdx/coercions/symbol.rb +2 -0
  59. data/lib/cmdx/coercions/time.rb +5 -0
  60. data/lib/cmdx/configuration.rb +119 -3
  61. data/lib/cmdx/context.rb +36 -0
  62. data/lib/cmdx/deprecator.rb +6 -3
  63. data/lib/cmdx/errors.rb +22 -0
  64. data/lib/cmdx/executor.rb +136 -7
  65. data/lib/cmdx/faults.rb +14 -0
  66. data/lib/cmdx/identifier.rb +2 -0
  67. data/lib/cmdx/locale.rb +3 -0
  68. data/lib/cmdx/log_formatters/json.rb +2 -0
  69. data/lib/cmdx/log_formatters/key_value.rb +2 -0
  70. data/lib/cmdx/log_formatters/line.rb +2 -0
  71. data/lib/cmdx/log_formatters/logstash.rb +2 -0
  72. data/lib/cmdx/log_formatters/raw.rb +2 -0
  73. data/lib/cmdx/middleware_registry.rb +20 -0
  74. data/lib/cmdx/middlewares/correlate.rb +11 -0
  75. data/lib/cmdx/middlewares/runtime.rb +4 -0
  76. data/lib/cmdx/middlewares/timeout.rb +4 -0
  77. data/lib/cmdx/pipeline.rb +24 -5
  78. data/lib/cmdx/railtie.rb +13 -0
  79. data/lib/cmdx/result.rb +133 -2
  80. data/lib/cmdx/task.rb +103 -8
  81. data/lib/cmdx/utils/call.rb +2 -0
  82. data/lib/cmdx/utils/condition.rb +3 -0
  83. data/lib/cmdx/utils/format.rb +5 -0
  84. data/lib/cmdx/validator_registry.rb +18 -0
  85. data/lib/cmdx/validators/exclusion.rb +2 -0
  86. data/lib/cmdx/validators/format.rb +2 -0
  87. data/lib/cmdx/validators/inclusion.rb +2 -0
  88. data/lib/cmdx/validators/length.rb +14 -0
  89. data/lib/cmdx/validators/numeric.rb +14 -0
  90. data/lib/cmdx/validators/presence.rb +2 -0
  91. data/lib/cmdx/version.rb +4 -1
  92. data/lib/cmdx/workflow.rb +10 -0
  93. data/lib/cmdx.rb +9 -0
  94. data/lib/generators/cmdx/locale_generator.rb +0 -1
  95. data/lib/generators/cmdx/templates/install.rb +9 -0
  96. data/mkdocs.yml +122 -0
  97. data/src/cmdx-dark-logo.png +0 -0
  98. data/src/cmdx-favicon.svg +1 -0
  99. data/src/cmdx-light-logo.png +0 -0
  100. data/src/cmdx-logo.svg +1 -0
  101. metadata +14 -3
  102. data/lib/cmdx/freezer.rb +0 -51
  103. data/src/cmdx-logo.png +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 85aac46372133bca5764f5bfe14b1809e5a910aed2c449660d2d1123d79540f1
4
- data.tar.gz: 4e377e2bd36936a83f8f244ddea45e2af05277daecbf5e49db76f4cb38fe46fd
3
+ metadata.gz: 9220688eed061c4c48562665fe9c2c780a2ecc08642eeaedb095d6bfc312e643
4
+ data.tar.gz: f470f2ccebce524942277865f2967979244dde07ba1a43095c7f95209127c635
5
5
  SHA512:
6
- metadata.gz: 38fec1c99ea4cb1cdd7639f65d85c76aeeac37fa1be3410344a2dda709e2a1f34cdf26d3b50cc7c7309a7234c756def0db8e2feb083972783018f6f4fc2fade1
7
- data.tar.gz: d08526598534fddb102297b1312304696bb4ffd6783d3549f7b4332cd83c8895cda84faeb1d427b14c8f60ae8a30839e983c1a649dcace3da1ec6a6978ab079d
6
+ metadata.gz: 155e2d09a4ca6147a7df578b0f2952ab81e781d5c492d821a4b71b08b41ef510cfccade001cc2e593f08ea1876ba6e5b658595a2bddaf6919f243c8df07d0569
7
+ data.tar.gz: fd0dba94495d6d139dc13507169143ee83f13dfac521d6ef607800c6d6e4cfd13e47cc03e2f27e047b185a0b9ebfc0a9d080143f7c6c2ec61aec139f38ea17cd
data/.DS_Store CHANGED
Binary file
@@ -3,10 +3,10 @@ You are a senior Ruby developer with expert knowledge of CMDx and writing docume
3
3
  Update the active tab using the following guidelines:
4
4
 
5
5
  - Follow best practices and implementation
6
- - Use a consistent professional voice
6
+ - Use a consistent warm, friendly and professional voice
7
7
  - Examples should be concise, non-repetitive, and realistic
8
8
  - Update any pre-existing documentation to match stated rules
9
9
  - Examples should not cross boundaries or focus
10
- - Docs must cover both typical use cases, including Invalid and error conditions
11
- - Use GitHub flavored markdown, including alerts to emphasize critical information (https://github.com/orgs/community/discussions/16925)
10
+ - Docs must cover both typical use cases, including invalid and error conditions
11
+ - Use mkdocs Admonitions to emphasize critical information (https://squidfunk.github.io/mkdocs-material/reference/admonitions/)
12
12
  - Optimize for LLM's including coding and AI agents
@@ -4,9 +4,7 @@ Process the following instructions in the order given:
4
4
  2. Append all files within `docs/**/*.md` into @LLM.md
5
5
  2a. Use order outlined in the table of contents of @README.md
6
6
  2b. Process one file at a time faster performance and improved accuracy
7
- 2c. Remove the table of contents from the chunk
8
- 2c. Remove the navigations below `---` from the chunk
9
- 2d. Wrap the chunk the files GitHub url the top and a spacer at the bottom like so:
7
+ 2c. Wrap the chunk the files GitHub url the top and a spacer at the bottom like so:
10
8
  ```
11
9
 
12
10
  ---
@@ -12,3 +12,4 @@ Add yardoc to the active tab using the following guidelines:
12
12
  - Method level docs should include `@example`, `param`, `@options`, `@return`, and any `@raise`
13
13
  - Hash `@params` should expand with possible `@option`
14
14
  - Module and method level docs should NOT include `@since`
15
+ - Add RBS inline comments after YARDoc block
data/.irbrc CHANGED
@@ -2,5 +2,17 @@
2
2
 
3
3
  require "pp"
4
4
 
5
- # To reload the gem, you must exit and restart the IRB session
6
- require_relative "lib/cmdx" unless defined?(CMDx)
5
+ # rubocop:disable Style/MixinUsage
6
+ unless defined?(CMDx)
7
+ require_relative "lib/cmdx"
8
+
9
+ require_relative "spec/support/helpers/task_builders"
10
+ require_relative "spec/support/helpers/workflow_builders"
11
+ include CMDx::Testing::TaskBuilders
12
+ include CMDx::Testing::WorkflowBuilders
13
+ end
14
+ # rubocop:enable Style/MixinUsage
15
+
16
+ def reload!
17
+ exec("irb")
18
+ end
data/CHANGELOG.md CHANGED
@@ -4,27 +4,44 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
- ## [TODO]
8
- - Update exceptions with more info on how to fix the issue
9
- - Add trnasform option to attributes
10
- - Add option to output failure backtraces
11
- - Add durability (retries) to execution
12
- - N-retries (3 default)
13
- - Backoff strategy
14
- - On specific errors
15
-
16
7
  ## [UNRELEASED]
17
8
 
18
- - N/A
9
+ ## [1.9.1] - 2025-10-22
10
+
11
+ ### Added
12
+ - Added RBS inlines type signatures
13
+ - Added YARDocs for `attr_reader` and `attr_accessor` methods
14
+
15
+ ## [1.9.0] - 2025-10-21
16
+
17
+ ### Added
18
+ - Added `transform` option to attributes
19
+ - Added option to output failure backtraces
20
+ - Added exception handling for non-bang methods
21
+ - Added durability with automatic retries to execution
22
+ - Added `to_h` hash coercion support
23
+ - Added comprehensive MkDocs configuration with material theme
24
+
25
+ ### Changed
26
+ - Improved performance of task settings setup
27
+ - Improved error messages for raised exceptions
28
+ - Improved inheritance of parent settings
29
+ - Cleaned halt backtrace frames for better readability
30
+
31
+ ### Removed
32
+ - Removed `Freezer` module and moved logic into executor `freeze_execution!` method
33
+ - Removed task parameter from callback signature
34
+ - Removed task and workflow arguments from conditional checks
35
+ - Removed chain persistence after execution in specs
19
36
 
20
37
  ## [1.8.0] - 2025-09-22
21
38
 
22
- ### Changes
23
- - Generalize locale values for fault `invalid` and `unspecified`
24
- - Nest attribute error messages under `error` key within metadata
25
- - Reordered logstash formatter keys
26
- - Improved already defined error message
27
- - Hash coercion for `nil` returns `{}`
39
+ ### Changed
40
+ - Generalized locale values for fault `invalid` and `unspecified`
41
+ - Nested attribute error messages under `error` key within metadata
42
+ - Reordered logstash formatter keys for consistency
43
+ - Improved error message for already defined items
44
+ - Changed hash coercion for `nil` to return `{}`
28
45
 
29
46
  ## [1.7.5] - 2025-09-10
30
47
 
@@ -42,69 +59,71 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
42
59
 
43
60
  ## [1.7.3] - 2025-09-03
44
61
 
45
- ### Changes
46
- - Return generic validation reason
47
- - Move validation full message string to `:full_message` within metadata
62
+ ### Changed
63
+ - Changed validation reasons to use generic values
64
+ - Moved validation full message string to `:full_message` key within metadata
48
65
 
49
66
  ## [1.7.2] - 2025-09-03
50
67
 
51
- ### Changes
52
- - Correlation ID is set before continuing to further steps
68
+ ### Changed
69
+ - Changed correlation ID to be set before continuing to further steps
53
70
 
54
71
  ## [1.7.1] - 2025-08-26
55
72
 
56
73
  ### Added
57
- - Yield result if block given to `execute` and `execute!` methods
74
+ - Added result yielding when block is given to `execute` and `execute!` methods
58
75
 
59
76
  ## [1.7.0] - 2025-08-25
60
77
 
61
78
  ### Added
62
- - Workflow generator
79
+ - Added workflow generator
63
80
 
64
- ### Changes
65
- - Port over `cmdx-parallel` changes
66
- - Port over `cmdx-i18n` changes
81
+ ### Changed
82
+ - Ported `cmdx-parallel` changes into core
83
+ - Ported `cmdx-i18n` changes into core
67
84
 
68
85
  ## [1.6.2] - 2025-08-24
69
86
 
70
- ### Changes
71
- - Prefix railtie I18n with `::` to play nice with `CMDx::I18n`
72
- - Use `cmdx-rspec` for matchers support
87
+ ### Changed
88
+ - Prefixed railtie I18n with `::` for compatibility with `CMDx::I18n`
89
+ - Changed to use `cmdx-rspec` for matchers support
73
90
 
74
91
  ## [1.6.1] - 2025-08-23
75
92
 
76
- ### Changes
77
- - Log task results before freezing
78
- - Rename `execute_tasks_sequentially` to `execute_tasks_in_sequence`
93
+ ### Changed
94
+ - Changed task results to be logged before freezing
95
+ - Renamed `execute_tasks_sequentially` to `execute_tasks_in_sequence`
79
96
 
80
97
  ## [1.6.0] - 2025-08-22
81
98
 
82
- ### Changes
83
- - Rename `Worker` class to `Executor`
84
- - Move workflow `work` logic into `Pipeline`
85
- - Add workflow task `:breakpoints`
99
+ ### Added
100
+ - Added workflow task `:breakpoints` support
101
+
102
+ ### Changed
103
+ - Renamed `Worker` class to `Executor`
104
+ - Moved workflow `work` logic into `Pipeline`
86
105
 
87
106
  ## [1.5.2] - 2025-08-22
88
107
 
89
- ### Changes
90
- - Rename workflow `execution_groups` attribute to `pipeline`
108
+ ### Changed
109
+ - Renamed workflow `execution_groups` attribute to `pipeline`
91
110
 
92
111
  ## [1.5.1] - 2025-08-21
93
112
 
94
- ### Changes
95
- - Prefix locale I18n with `::` to play nice with `CMDx::I18n`
96
- - Safe navigate length and numeric validators
97
- - Update railtie file path points to correct directory
113
+ ### Changed
114
+ - Prefixed locale I18n with `::` for compatibility with `CMDx::I18n`
115
+ - Added safe navigation to length and numeric validators
116
+ - Updated railtie file path to point to correct directory
98
117
 
99
118
  ## [1.5.0] - 2025-08-21
100
119
 
101
- ### Changes
102
- - BREAKING - Revamp CMDx for clarity, transparency, and higher performance
120
+ ### Changed
121
+ - **BREAKING**: Revamped CMDx for improved clarity, transparency, and higher performance
103
122
 
104
123
  ## [1.1.2] - 2025-07-20
105
124
 
106
125
  ### Changed
107
- - All items between versions `0.1.0` and `1.1.2` should be reviewed within its own tag
126
+ - All changes between versions `0.1.0` and `1.1.2` should be reviewed within their respective git tags
108
127
 
109
128
  ## [0.1.0] - 2025-03-07
110
129
 
data/LLM.md CHANGED
@@ -87,6 +87,45 @@ CMDx.configure do |config|
87
87
  end
88
88
  ```
89
89
 
90
+ ### Backtraces
91
+
92
+ Enable backtraces to be logged on any non-fault exceptions for improved debugging context. Run them through a cleaner to remove unwanted stack trace noise.
93
+
94
+ > [!NOTE]
95
+ > The `backtrace_cleaner` is set to `Rails.backtrace_cleaner.clean` in a Rails env by default.
96
+
97
+ ```ruby
98
+ CMDx.configure do |config|
99
+ # Truthy
100
+ config.backtrace = true
101
+
102
+ # Via callable (must respond to `call(backtrace)`)
103
+ config.backtrace_cleaner = AdvanceCleaner.new
104
+
105
+ # Via proc or lambda
106
+ config.backtrace_cleaner = ->(backtrace) { backtrace[0..5] }
107
+ end
108
+ ```
109
+
110
+ ### Exception Handlers
111
+
112
+ Use exception handlers are called on non-fault standard error based exceptions.
113
+
114
+ > [!TIP]
115
+ > Use exception handlers to send errors to your APM of choice.
116
+
117
+ ```ruby
118
+ CMDx.configure do |config|
119
+ # Via callable (must respond to `call(task, exception)`)
120
+ config.exception_handler = NewRelicReporter
121
+
122
+ # Via proc or lambda
123
+ config.exception_handler = proc do |task, exception|
124
+ APMService.report(exception, extra_data: { task: task.name, id: task.id })
125
+ end
126
+ end
127
+ ```
128
+
90
129
  ### Logging
91
130
 
92
131
  ```ruby
@@ -213,6 +252,8 @@ class GenerateInvoice < CMDx::Task
213
252
  # Global configuration overrides
214
253
  task_breakpoints: ["failed"], # Breakpoint override
215
254
  workflow_breakpoints: [], # Breakpoint override
255
+ backtrace: true, # Toggle backtrace
256
+ backtrace_cleaner: ->(bt) { bt[0..5] }, # Backtrace cleaner
216
257
  logger: CustomLogger.new($stdout), # Custom logger
217
258
 
218
259
  # Task configuration settings
@@ -220,7 +261,10 @@ class GenerateInvoice < CMDx::Task
220
261
  log_level: :info, # Log level override
221
262
  log_formatter: CMDx::LogFormatters::Json.new # Log formatter override
222
263
  tags: ["billing", "financial"], # Logging tags
223
- deprecated: true # Task deprecations
264
+ deprecated: true, # Task deprecations
265
+ retries: 3, # Non-fault exception retries
266
+ retry_on: [External::ApiError], # List of exceptions to retry on
267
+ retry_jitter: 1 # Space between retry iteration, eg: current retry num + 1
224
268
  )
225
269
 
226
270
  def work
@@ -229,8 +273,8 @@ class GenerateInvoice < CMDx::Task
229
273
  end
230
274
  ```
231
275
 
232
- > [!TIP]
233
- > Use task-level settings for tasks that require special handling, such as financial reporting, external API integrations, or critical system operations.
276
+ > [!IMPORTANT]
277
+ > Retries reuse the same context when executing its work. By default all `StandardErrors` will be retried if no `retry_on` option is passed.
234
278
 
235
279
  ### Registrations
236
280
 
@@ -328,6 +372,15 @@ end
328
372
  > [!TIP]
329
373
  > Use **present tense verbs + noun** for task names, eg: `ModerateBlogPost`, `ScheduleAppointment`, `ValidateDocument`
330
374
 
375
+ ## Type safety
376
+
377
+ CMDx includes built-in RBS (Ruby Type Signature) inline annotations throughout the codebase, providing type information for static analysis and editor support.
378
+
379
+ - **Type checking** — Catch type errors before runtime using tools like Steep or TypeProf
380
+ - **Better IDE support** — Enhanced autocomplete, navigation, and inline documentation
381
+ - **Self-documenting code** — Clear method signatures and return types
382
+ - **Refactoring confidence** — Type-aware refactoring reduces bugs
383
+
331
384
  ---
332
385
 
333
386
  url: https://github.com/drexed/cmdx/blob/main/docs/basics/setup.md
@@ -1170,6 +1223,9 @@ result.reason #=> "[ActiveRecord::NotFoundError] record not found"
1170
1223
  result.cause #=> <ActiveRecord::NotFoundError>
1171
1224
  ```
1172
1225
 
1226
+ > [!NOTE]
1227
+ > The `exception_handler` setting only works with non-bang execution as it catches all exceptions preventing them from reaching your apps global error handler.
1228
+
1173
1229
  ### Bang execution
1174
1230
 
1175
1231
  The `execute!` method allows unhandled exceptions to propagate, enabling standard Ruby exception handling while respecting CMDx fault configuration.
@@ -1321,19 +1377,19 @@ result = BuildApplication.execute(version: "1.2.3")
1321
1377
 
1322
1378
  # Status-based handlers
1323
1379
  result
1324
- .on_success { |result| notify_deployment_ready(result) }
1325
- .on_failed { |result| handle_build_failure(result) }
1326
- .on_skipped { |result| log_skip_reason(result) }
1380
+ .handle_success { |result| notify_deployment_ready(result) }
1381
+ .handle_failed { |result| handle_build_failure(result) }
1382
+ .handle_skipped { |result| log_skip_reason(result) }
1327
1383
 
1328
1384
  # State-based handlers
1329
1385
  result
1330
- .on_complete { |result| update_build_status(result) }
1331
- .on_interrupted { |result| cleanup_partial_artifacts(result) }
1386
+ .handle_complete { |result| update_build_status(result) }
1387
+ .handle_interrupted { |result| cleanup_partial_artifacts(result) }
1332
1388
 
1333
1389
  # Outcome-based handlers
1334
1390
  result
1335
- .on_good { |result| increment_success_counter(result) }
1336
- .on_bad { |result| alert_operations_team(result) }
1391
+ .handle_good { |result| increment_success_counter(result) }
1392
+ .handle_bad { |result| alert_operations_team(result) }
1337
1393
  ```
1338
1394
 
1339
1395
  ## Pattern Matching
@@ -1455,9 +1511,9 @@ result = ProcessVideoUpload.execute
1455
1511
 
1456
1512
  # Individual state handlers
1457
1513
  result
1458
- .on_complete { |result| send_upload_notification(result) }
1459
- .on_interrupted { |result| cleanup_temp_files(result) }
1460
- .on_executed { |result| log_upload_metrics(result) }
1514
+ .handle_complete { |result| send_upload_notification(result) }
1515
+ .handle_interrupted { |result| cleanup_temp_files(result) }
1516
+ .handle_executed { |result| log_upload_metrics(result) }
1461
1517
  ```
1462
1518
 
1463
1519
  ---
@@ -1520,14 +1576,14 @@ result = ProcessNotification.execute
1520
1576
 
1521
1577
  # Individual status handlers
1522
1578
  result
1523
- .on_success { |result| mark_notification_sent(result) }
1524
- .on_skipped { |result| log_notification_skipped(result) }
1525
- .on_failed { |result| queue_retry_notification(result) }
1579
+ .handle_success { |result| mark_notification_sent(result) }
1580
+ .handle_skipped { |result| log_notification_skipped(result) }
1581
+ .handle_failed { |result| queue_retry_notification(result) }
1526
1582
 
1527
1583
  # Outcome-based handlers
1528
1584
  result
1529
- .on_good { |result| update_message_stats(result) }
1530
- .on_bad { |result| track_delivery_failure(result) }
1585
+ .handle_good { |result| update_message_stats(result) }
1586
+ .handle_bad { |result| track_delivery_failure(result) }
1531
1587
  ```
1532
1588
 
1533
1589
  ---
@@ -2419,6 +2475,80 @@ end
2419
2475
 
2420
2476
  ---
2421
2477
 
2478
+ url: https://github.com/drexed/cmdx/blob/main/docs/attributes/transformations.md
2479
+ ---
2480
+
2481
+ # Attributes - Transformations
2482
+
2483
+ Transformations allow you to modify attribute values after they are derived and coerced from their source but before any validations. This enables data normalization, formatting, and conditional processing within the attribute pipeline.
2484
+
2485
+ ## Declarations
2486
+
2487
+ ### Symbol References
2488
+
2489
+ Reference instance methods by symbol for dynamic value transformations:
2490
+
2491
+ ```ruby
2492
+ class ProcessAnalytics < CMDx::Task
2493
+ attribute :options, transform: :compact_blank
2494
+ end
2495
+ ```
2496
+
2497
+ ### Proc or Lambda
2498
+
2499
+ Use anonymous functions for dynamic value transformations:
2500
+
2501
+ ```ruby
2502
+ class CacheContent < CMDx::Task
2503
+ # Proc
2504
+ attribute :expire_hours, transform: proc { |v| v * 2 }
2505
+
2506
+ # Lambda
2507
+ attribute :compression, transform: ->(v) { v.to_s.upcase.strip[0..2] }
2508
+ end
2509
+ ```
2510
+
2511
+ ### Class or Module
2512
+
2513
+ Use any object that responds to `call` for reusable transformation logic:
2514
+
2515
+ ```ruby
2516
+ class EmailNormalizer
2517
+ def call(value)
2518
+ value.to_s.downcase.strip
2519
+ end
2520
+ end
2521
+
2522
+ class ProcessContacts < CMDx::Task
2523
+ # Class or Module
2524
+ attribute :email, transform: EmailNormalizer
2525
+
2526
+ # Instance
2527
+ attribute :email, transform: EmailNormalizer.new
2528
+ end
2529
+ ```
2530
+
2531
+ ## Validations
2532
+
2533
+ Transformed values are subject to the same validation rules as untransformed values, ensuring consistency and catching configuration errors early.
2534
+
2535
+ ```ruby
2536
+ class ScheduleBackup < CMDx::Task
2537
+ # Coercions
2538
+ attribute :retention_days, type: :integer, transform: proc { |v| v.clamp(1, 5) }
2539
+
2540
+ # Validations
2541
+ optional :frequency, transform: :downcase, inclusion: { in: %w[hourly daily weekly monthly] }
2542
+ end
2543
+ ```
2544
+
2545
+ ---
2546
+
2547
+ - **Prev:** [Attributes - Defaults](defaults.md)
2548
+ - **Next:** [Callbacks](../callbacks.md)
2549
+
2550
+ ---
2551
+
2422
2552
  url: https://github.com/drexed/cmdx/blob/main/docs/callbacks.md
2423
2553
  ---
2424
2554
 
@@ -2487,7 +2617,7 @@ Use anonymous functions for inline callback logic:
2487
2617
  ```ruby
2488
2618
  class ProcessBooking < CMDx::Task
2489
2619
  # Proc
2490
- on_interrupted proc { |task| ReservationSystem.pause! }
2620
+ on_interrupted proc { ReservationSystem.pause! }
2491
2621
 
2492
2622
  # Lambda
2493
2623
  on_complete -> { ReservationSystem.resume! }
@@ -2534,10 +2664,10 @@ class ProcessBooking < CMDx::Task
2534
2664
  before_execution :notify_guest, if: :messaging_enabled?, unless: :messaging_blocked?
2535
2665
 
2536
2666
  # Proc
2537
- on_failure :increment_failure, if: ->(task) { Rails.env.production? && task.class.name.include?("Legacy") }
2667
+ on_failure :increment_failure, if: -> { Rails.env.production? && self.class.name.include?("Legacy") }
2538
2668
 
2539
2669
  # Lambda
2540
- on_success :ping_housekeeping, if: proc { |task| task.context.rooms_need_cleaning? }
2670
+ on_success :ping_housekeeping, if: proc { context.rooms_need_cleaning? }
2541
2671
 
2542
2672
  # Class or Module
2543
2673
  on_complete :send_confirmation, unless: MessagingPermissionCheck
@@ -2552,7 +2682,7 @@ class ProcessBooking < CMDx::Task
2552
2682
  private
2553
2683
 
2554
2684
  def messaging_enabled?
2555
- context.guest.messaging_preference.present?
2685
+ context.guest.messaging_preference == true
2556
2686
  end
2557
2687
 
2558
2688
  def messaging_blocked?
@@ -3076,7 +3206,7 @@ result = ProcessOldData.execute
3076
3206
  result.successful? #=> true
3077
3207
 
3078
3208
  # Ruby warning appears in stderr:
3079
- # [ProcessOldData] DEPRECATED: migrate to replacement or discontinue use
3209
+ # [ProcessOldData] DEPRECATED: migrate to a replacement or discontinue use
3080
3210
  ```
3081
3211
 
3082
3212
  ## Declarations
@@ -3227,10 +3357,10 @@ class OnboardingWorkflow < CMDx::Task
3227
3357
  task SendWelcomeEmail, if: :email_configured?, unless: :email_disabled?
3228
3358
 
3229
3359
  # Proc
3230
- task SendWelcomeEmail, if: ->(workflow) { Rails.env.production? && workflow.class.name.include?("Premium") }
3360
+ task SendWelcomeEmail, if: -> { Rails.env.production? && self.class.name.include?("Premium") }
3231
3361
 
3232
3362
  # Lambda
3233
- task SendWelcomeEmail, if: proc { |workflow| workflow.context.features_enabled? }
3363
+ task SendWelcomeEmail, if: proc { context.features_enabled? }
3234
3364
 
3235
3365
  # Class or Module
3236
3366
  task SendWelcomeEmail, unless: ContentAccessCheck
@@ -3244,7 +3374,7 @@ class OnboardingWorkflow < CMDx::Task
3244
3374
  private
3245
3375
 
3246
3376
  def email_configured?
3247
- context.user.email_address.present?
3377
+ context.user.email_address == true
3248
3378
  end
3249
3379
 
3250
3380
  def email_disabled?
@@ -3536,33 +3666,9 @@ class ConfigureCompany < CMDx::Task
3536
3666
  end
3537
3667
  ```
3538
3668
 
3539
- ## ActiveRecord Query Tagging
3669
+ ## Advance Examples
3540
3670
 
3541
- Automatically tag SQL queries for better debugging:
3542
-
3543
- ```ruby
3544
- # config/application.rb
3545
- config.active_record.query_log_tags_enabled = true
3546
- config.active_record.query_log_tags << :cmdx_task_class
3547
- config.active_record.query_log_tags << :cmdx_chain_id
3548
-
3549
- # app/tasks/application_task.rb
3550
- class ApplicationTask < CMDx::Task
3551
- before_execution :set_execution_context
3552
-
3553
- private
3554
-
3555
- def set_execution_context
3556
- # NOTE: This could easily be made into a middleware
3557
- ActiveSupport::ExecutionContext.set(
3558
- cmdx_task_class: self.class.name,
3559
- cmdx_chain_id: chain.id
3560
- )
3561
- end
3562
- end
3563
-
3564
- # SQL queries will now include comments like:
3565
- # /*cmdx_task_class:ExportReportTask,cmdx_chain_id:018c2b95-b764-7615*/ SELECT * FROM reports WHERE id = 1
3566
- ```
3671
+ - [Active Record Query Tagging](https://github.com/drexed/cmdx/blob/main/examples/active_record_query_tagging.md)
3672
+ - [Paper Trail Whatdunnit](https://github.com/drexed/cmdx/blob/main/examples/paper_trail_whatdunnit.md)
3567
3673
 
3568
3674
  ---