cmdx 1.12.0 → 1.14.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.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +88 -71
  3. data/LICENSE.txt +3 -20
  4. data/README.md +8 -7
  5. data/lib/cmdx/attribute.rb +21 -5
  6. data/lib/cmdx/chain.rb +18 -4
  7. data/lib/cmdx/context.rb +18 -0
  8. data/lib/cmdx/executor.rb +35 -30
  9. data/lib/cmdx/result.rb +45 -2
  10. data/lib/cmdx/task.rb +22 -1
  11. data/lib/cmdx/version.rb +1 -1
  12. data/mkdocs.yml +67 -37
  13. metadata +3 -57
  14. data/.cursor/prompts/docs.md +0 -12
  15. data/.cursor/prompts/llms.md +0 -8
  16. data/.cursor/prompts/rspec.md +0 -24
  17. data/.cursor/prompts/yardoc.md +0 -15
  18. data/.cursor/rules/cursor-instructions.mdc +0 -68
  19. data/.irbrc +0 -18
  20. data/.rspec +0 -4
  21. data/.rubocop.yml +0 -95
  22. data/.ruby-version +0 -1
  23. data/.yard-lint.yml +0 -174
  24. data/.yardopts +0 -7
  25. data/docs/.DS_Store +0 -0
  26. data/docs/assets/favicon.ico +0 -0
  27. data/docs/assets/favicon.svg +0 -1
  28. data/docs/attributes/coercions.md +0 -155
  29. data/docs/attributes/defaults.md +0 -77
  30. data/docs/attributes/definitions.md +0 -283
  31. data/docs/attributes/naming.md +0 -68
  32. data/docs/attributes/transformations.md +0 -63
  33. data/docs/attributes/validations.md +0 -336
  34. data/docs/basics/chain.md +0 -108
  35. data/docs/basics/context.md +0 -121
  36. data/docs/basics/execution.md +0 -96
  37. data/docs/basics/setup.md +0 -84
  38. data/docs/callbacks.md +0 -157
  39. data/docs/configuration.md +0 -314
  40. data/docs/deprecation.md +0 -145
  41. data/docs/getting_started.md +0 -126
  42. data/docs/index.md +0 -134
  43. data/docs/internationalization.md +0 -126
  44. data/docs/interruptions/exceptions.md +0 -52
  45. data/docs/interruptions/faults.md +0 -169
  46. data/docs/interruptions/halt.md +0 -216
  47. data/docs/logging.md +0 -94
  48. data/docs/middlewares.md +0 -191
  49. data/docs/outcomes/result.md +0 -194
  50. data/docs/outcomes/states.md +0 -66
  51. data/docs/outcomes/statuses.md +0 -65
  52. data/docs/retries.md +0 -121
  53. data/docs/stylesheets/extra.css +0 -42
  54. data/docs/tips_and_tricks.md +0 -157
  55. data/docs/workflows.md +0 -226
  56. data/examples/active_record_database_transaction.md +0 -27
  57. data/examples/active_record_query_tagging.md +0 -46
  58. data/examples/flipper_feature_flags.md +0 -50
  59. data/examples/paper_trail_whatdunnit.md +0 -39
  60. data/examples/redis_idempotency.md +0 -71
  61. data/examples/sentry_error_tracking.md +0 -46
  62. data/examples/sidekiq_async_execution.md +0 -29
  63. data/examples/stoplight_circuit_breaker.md +0 -36
  64. data/src/cmdx-dark-logo.png +0 -0
  65. data/src/cmdx-favicon.svg +0 -1
  66. data/src/cmdx-light-logo.png +0 -0
  67. data/src/cmdx-logo.svg +0 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5dffef0df3a8abb190fc0ca6a194383614fdd81f3eebf22ecab59d8ec047d1d7
4
- data.tar.gz: 0f38ccaa12a07c41b64fb4fd3f0685bafb45b7de0b3830243f85a53de4d331c4
3
+ metadata.gz: 620c2c00dd483c6a4122c7b5acbbe6cf7a07d22260d0331d6f1d54b571ef7648
4
+ data.tar.gz: aff47b340a77b052bc4cd2e201068044a443c1d8f4609163fa45025bdba25d00
5
5
  SHA512:
6
- metadata.gz: 8649dadb0908d88f7441b80d6e3dce03e7f0d20095216d8ac24cc0bf8b1446f57b4552f021a324f47d860112e431b531695a5cd78e84a4b2cf0c3be0dff1224e
7
- data.tar.gz: 5e88be6d5a3070d75abbbcb20e25d900db3a44f011f71ed55c61f748cda77e9be4297f12829ab9f28daac66a4b2c511725318ae4e1455793eef8b8f806c3864c
6
+ metadata.gz: a8f6fec277ff0198937013c25a1a1c753d4cf5100ff4920c834dc92fba8df7d2adc1ef3fbcff91c999f1af8c4676c3e8702c71a29a3402efcf36d4a0236163ba
7
+ data.tar.gz: 2233517633c8d9afaafb7a44878b6b3fe0939dabcbe647ceafd9e7ca04450ee1e5e9f6b6f5adb2de58c305b0c2c0940e74ee2d7290f0eb202bda429d76b66992
data/CHANGELOG.md CHANGED
@@ -4,166 +4,183 @@ 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
- ## [UNRELEASED]
7
+ ## [Unreleased]
8
8
 
9
- ## [1.12.0] - 2025-12-18
9
+ ### Added
10
+ - Add Ruby 4.0 compatibility
11
+ - Add `Context#clear!` method to remove all context data
12
+ - Add `Task.attribute_schema` class method for attribute introspection
13
+ - Add `#to_h` method for attribute serialization
14
+
15
+ ### Changed
16
+ - **BREAKING**: Switch license from MIT to LGPLv3
17
+ - Replace `instance_eval` with `define_singleton_method` for attribute method definitions
18
+ - Move retry count from metadata to result object
19
+ - Exclude non-essential files from gem package
20
+
21
+ ### Removed
22
+ - Remove public `Result#rolled_back!` method to hide internal implementation
23
+
24
+ ## [1.13.0] - 2025-12-23
10
25
 
11
26
  ### Added
12
- - Added active record database transaction example
13
- - Added Sentry error tracking example
14
- - Added Redis idempotency example
15
- - Added Flipper feature flag example
16
-
17
- ### Updated
18
- - Remove `handle_*` methods and provide `on(*states_or_statuses)` method for more flexibility
19
- - Optimize logging ancestor lookup
20
- - Use chop instead of range for better string performance
21
- - Update boolean coercion `TRUTHY` and `FALSEY` regexp to be case insensitive
22
- - Improve YARD documentation with `yard-lint`
27
+ - Add rollback tracking and logging for task execution
28
+ - Add `dry_run` execution option with inheritance support for nested tasks
29
+ - Add `Context#delete` alias for `Context#delete!`
30
+ - Add `Context#merge` alias for `Context#merge!`
31
+
32
+ ## [1.12.0] - 2025-12-18
33
+
34
+ ### Changed
35
+ - Optimize logging ancestor chain lookup performance
36
+ - Use `String#chop` instead of range indexing for improved string performance
37
+ - Make boolean coercion `TRUTHY` and `FALSEY` patterns case-insensitive
38
+ - Enhance YARD documentation using `yard-lint` validation
39
+
40
+ ### Removed
41
+ - Remove `handle_*` callback methods in favor of `on(*states_or_statuses)` for flexible state handling
23
42
 
24
43
  ## [1.11.0] - 2025-11-08
25
44
 
26
- ### Updated
27
- - Added conditionally required attributes options
28
- - Update specs to use new `cmdx-rspec` matcher names
45
+ ### Changed
46
+ - Add conditional requirement support for attribute validation
47
+ - Update specs to use new `cmdx-rspec` matcher naming conventions
29
48
 
30
49
  ## [1.10.1] - 2025-11-06
31
50
 
32
51
  ### Added
33
- - Added Sidekiq async integration example
34
- - Added YARDoc documentation to docs site
52
+ - Add YARDoc documentation to documentation site
35
53
 
36
54
  ### Removed
37
- - Removed `Executor#repeator` method (never used)
55
+ - Remove unused `Executor#repeator` method
38
56
 
39
57
  ## [1.10.0] - 2025-10-26
40
58
 
41
59
  ### Added
42
- - Added `rollback` ability to undo on matching status
43
- - Added documentation for retries
44
- - Added Stoplight circuit breaker integration example
60
+ - Add `rollback` capability to undo operations based on status
61
+ - Add retry mechanism documentation
45
62
 
46
- ### Updated
47
- - Updated `retry_jitter` option to support symbol, proc, and callables on top of fixed values
63
+ ### Changed
64
+ - Extend `retry_jitter` option to accept symbols, procs, and callable objects
48
65
 
49
66
  ## [1.9.1] - 2025-10-22
50
67
 
51
68
  ### Added
52
- - Added RBS inlines type signatures
53
- - Added YARDocs for `attr_reader` and `attr_accessor` methods
69
+ - Add RBS inline type signatures
70
+ - Add YARDocs for `attr_reader` and `attr_accessor` methods
54
71
 
55
72
  ## [1.9.0] - 2025-10-21
56
73
 
57
74
  ### Added
58
- - Added `transform` option to attributes
59
- - Added option to output failure backtraces
60
- - Added exception handling for non-bang methods
61
- - Added durability with automatic retries to execution
62
- - Added `to_h` hash coercion support
63
- - Added comprehensive MkDocs configuration with material theme
75
+ - Add `transform` option for attribute value transformations
76
+ - Add optional failure backtrace output
77
+ - Add exception handling for non-bang execution methods
78
+ - Add automatic retry mechanism for execution durability
79
+ - Add `to_h` hash coercion support
80
+ - Add MkDocs configuration with Material theme
64
81
 
65
82
  ### Changed
66
- - Improved performance of task settings setup
67
- - Improved error messages for raised exceptions
68
- - Improved inheritance of parent settings
69
- - Cleaned halt backtrace frames for better readability
83
+ - Improve task settings initialization performance
84
+ - Improve exception error message clarity
85
+ - Improve parent settings inheritance behavior
86
+ - Clean halt backtrace frames for better readability
70
87
 
71
88
  ### Removed
72
- - Removed `Freezer` module and moved logic into executor `freeze_execution!` method
73
- - Removed task parameter from callback signature
74
- - Removed task and workflow arguments from conditional checks
75
- - Removed chain persistence after execution in specs
89
+ - Remove `Freezer` module; consolidate logic into `Executor#freeze_execution!`
90
+ - Remove `task` parameter from callback method signatures
91
+ - Remove `task` and `workflow` arguments from conditional checks
92
+ - Remove chain persistence after execution in specs
76
93
 
77
94
  ## [1.8.0] - 2025-09-22
78
95
 
79
96
  ### Changed
80
- - Generalized locale values for fault `invalid` and `unspecified`
81
- - Nested attribute error messages under `error` key within metadata
82
- - Reordered logstash formatter keys for consistency
83
- - Improved error message for already defined items
84
- - Changed hash coercion for `nil` to return `{}`
97
+ - Generalize locale values for `invalid` and `unspecified` faults
98
+ - Nest attribute error messages under `error` key in metadata
99
+ - Reorder Logstash formatter keys for consistency
100
+ - Improve error messaging for duplicate item definitions
101
+ - Return empty hash `{}` for `nil` hash coercion
85
102
 
86
103
  ## [1.7.5] - 2025-09-10
87
104
 
88
105
  ### Added
89
- - Added `fetch_or_store` method to context
90
- - Added `ctx` alias for context in result
91
- - Added `ok?` alias for `good?` in result
92
- - Added deconstruction values in result
106
+ - Add `Context#fetch_or_store` method for atomic get-or-set operations
107
+ - Add `Result#ctx` alias for `Result#context`
108
+ - Add `Result#ok?` alias for `Result#good?`
109
+ - Add result deconstruction support for pattern matching
93
110
 
94
111
  ## [1.7.4] - 2025-09-03
95
112
 
96
113
  ### Added
97
- - Added errors delegation from result object
98
- - Added `full_messages` and `to_hash` methods to errors
114
+ - Add errors delegation from result object
115
+ - Add `Errors#full_messages` and `Errors#to_hash` methods
99
116
 
100
117
  ## [1.7.3] - 2025-09-03
101
118
 
102
119
  ### Changed
103
- - Changed validation reasons to use generic values
104
- - Moved validation full message string to `:full_message` key within metadata
120
+ - Use generic validation reason values
121
+ - Move validation full message to `:full_message` key in metadata
105
122
 
106
123
  ## [1.7.2] - 2025-09-03
107
124
 
108
125
  ### Changed
109
- - Changed correlation ID to be set before continuing to further steps
126
+ - Set correlation ID before proceeding to subsequent execution steps
110
127
 
111
128
  ## [1.7.1] - 2025-08-26
112
129
 
113
130
  ### Added
114
- - Added result yielding when block is given to `execute` and `execute!` methods
131
+ - Add block yielding support to `execute` and `execute!` methods
115
132
 
116
133
  ## [1.7.0] - 2025-08-25
117
134
 
118
135
  ### Added
119
- - Added workflow generator
136
+ - Add workflow generator
120
137
 
121
138
  ### Changed
122
- - Ported `cmdx-parallel` changes into core
123
- - Ported `cmdx-i18n` changes into core
139
+ - Integrate `cmdx-parallel` functionality into core
140
+ - Integrate `cmdx-i18n` functionality into core
124
141
 
125
142
  ## [1.6.2] - 2025-08-24
126
143
 
127
144
  ### Changed
128
- - Prefixed railtie I18n with `::` for compatibility with `CMDx::I18n`
129
- - Changed to use `cmdx-rspec` for matchers support
145
+ - Prefix railtie I18n with `::` for `CMDx::I18n` compatibility
146
+ - Switch to `cmdx-rspec` for matcher support
130
147
 
131
148
  ## [1.6.1] - 2025-08-23
132
149
 
133
150
  ### Changed
134
- - Changed task results to be logged before freezing
135
- - Renamed `execute_tasks_sequentially` to `execute_tasks_in_sequence`
151
+ - Log task results before freezing execution state
152
+ - Rename `execute_tasks_sequentially` to `execute_tasks_in_sequence`
136
153
 
137
154
  ## [1.6.0] - 2025-08-22
138
155
 
139
156
  ### Added
140
- - Added workflow task `:breakpoints` support
157
+ - Add workflow task `:breakpoints` support
141
158
 
142
159
  ### Changed
143
- - Renamed `Worker` class to `Executor`
144
- - Moved workflow `work` logic into `Pipeline`
160
+ - Rename `Worker` class to `Executor`
161
+ - Extract workflow execution logic into `Pipeline` class
145
162
 
146
163
  ## [1.5.2] - 2025-08-22
147
164
 
148
165
  ### Changed
149
- - Renamed workflow `execution_groups` attribute to `pipeline`
166
+ - Rename workflow `execution_groups` attribute to `pipeline`
150
167
 
151
168
  ## [1.5.1] - 2025-08-21
152
169
 
153
170
  ### Changed
154
- - Prefixed locale I18n with `::` for compatibility with `CMDx::I18n`
155
- - Added safe navigation to length and numeric validators
156
- - Updated railtie file path to point to correct directory
171
+ - Prefix locale I18n with `::` for `CMDx::I18n` compatibility
172
+ - Add safe navigation to length and numeric validators
173
+ - Fix railtie file path to reference correct directory
157
174
 
158
175
  ## [1.5.0] - 2025-08-21
159
176
 
160
177
  ### Changed
161
- - **BREAKING**: Revamped CMDx for improved clarity, transparency, and higher performance
178
+ - **BREAKING**: Complete architecture redesign for improved clarity, transparency, and performance
162
179
 
163
180
  ## [1.1.2] - 2025-07-20
164
181
 
165
182
  ### Changed
166
- - All changes between versions `0.1.0` and `1.1.2` should be reviewed within their respective git tags
183
+ - See git tags for changes between versions 0.1.0 and 1.1.2
167
184
 
168
185
  ## [0.1.0] - 2025-03-07
169
186
 
data/LICENSE.txt CHANGED
@@ -1,21 +1,4 @@
1
- The MIT License (MIT)
1
+ Copyright (c) Drexed
2
2
 
3
- Copyright (c) 2025 Juan Gomez
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in
13
- all copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
- THE SOFTWARE.
3
+ CMDx is an Open Source project licensed under the terms of the LGPLv3 license.
4
+ Please see <http://www.gnu.org/licenses/lgpl-3.0.html> for license text.
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
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">
2
+ <img src="./src/cmdx-light-logo.png#gh-light-mode-only" width="200" alt="CMDx Light Logo">
3
+ <img src="./src/cmdx-dark-logo.png#gh-dark-mode-only" width="200" alt="CMDx Dark Logo">
4
4
 
5
5
  ---
6
6
 
@@ -10,7 +10,7 @@
10
10
 
11
11
  <img alt="Version" src="https://img.shields.io/gem/v/cmdx">
12
12
  <img alt="Build" src="https://github.com/drexed/cmdx/actions/workflows/ci.yml/badge.svg">
13
- <img alt="License" src="https://img.shields.io/github/license/drexed/cmdx">
13
+ <img alt="License" src="https://img.shields.io/badge/license-LGPL%20v3-blue.svg">
14
14
  </div>
15
15
 
16
16
  # CMDx
@@ -18,13 +18,14 @@
18
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.
19
19
 
20
20
  > [!NOTE]
21
- > Documentation reflects the latest code on `main`. For version-specific documentation, please refer to the `docs/` directory within that version's tag.
21
+ > [Documentation](https://drexed.github.io/cmdx/getting_started/) reflects the latest code on `main`. For version-specific documentation, please refer to the `docs/` directory within that version's tag.
22
22
 
23
23
  ## Requirements
24
24
 
25
- - Ruby: MRI 3.1+ or JRuby 9.4+.
25
+ - Ruby: MRI 3.1+ or JRuby 9.4+
26
+ - Dependencies: None
26
27
 
27
- CMDx works with any Ruby framework. Rails support is built-in, but it's framework-agnostic at its core.
28
+ Rails support is built-in, but it's framework-agnostic at its core.
28
29
 
29
30
  ## Installation
30
31
 
@@ -125,4 +126,4 @@ Bug reports and pull requests are welcome at <https://github.com/drexed/cmdx>. W
125
126
 
126
127
  ## License
127
128
 
128
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
129
+ The gem is available as open source under the terms of the [LGPLv3 License](https://www.gnu.org/licenses/lgpl-3.0.html).
@@ -235,6 +235,23 @@ module CMDx
235
235
  end
236
236
  end
237
237
 
238
+ # @return [Hash] A hash representation of the attribute
239
+ #
240
+ # @example
241
+ # attribute.to_h # => { name: :user_id, method_name: :user_id, required: true, types: [:integer], options: {}, children: [] }
242
+ #
243
+ # @rbs () -> Hash[Symbol, untyped]
244
+ def to_h
245
+ {
246
+ name: name,
247
+ method_name: method_name,
248
+ required: required?,
249
+ types: types,
250
+ options: options.except(:if, :unless),
251
+ children: children.map(&:to_h)
252
+ }
253
+ end
254
+
238
255
  private
239
256
 
240
257
  # Creates nested attributes as children of this attribute.
@@ -312,11 +329,10 @@ module CMDx
312
329
  attribute_value.generate
313
330
  attribute_value.validate
314
331
 
315
- task.instance_eval(<<~RUBY, __FILE__, __LINE__ + 1)
316
- def #{method_name}
317
- attributes[:#{method_name}]
318
- end
319
- RUBY
332
+ name_of_method = method_name
333
+ task.define_singleton_method(name_of_method) do
334
+ attributes[name_of_method]
335
+ end
320
336
  end
321
337
 
322
338
  end
data/lib/cmdx/chain.rb CHANGED
@@ -39,9 +39,10 @@ module CMDx
39
39
  # @return [Chain] A new chain instance
40
40
  #
41
41
  # @rbs () -> void
42
- def initialize
42
+ def initialize(dry_run: false)
43
43
  @id = Identifier.generate
44
44
  @results = []
45
+ @dry_run = !!dry_run
45
46
  end
46
47
 
47
48
  class << self
@@ -102,16 +103,28 @@ module CMDx
102
103
  # puts "Chain size: #{chain.size}"
103
104
  #
104
105
  # @rbs (Result result) -> Chain
105
- def build(result)
106
+ def build(result, dry_run: false)
106
107
  raise TypeError, "must be a CMDx::Result" unless result.is_a?(Result)
107
108
 
108
- self.current ||= new
109
+ self.current ||= new(dry_run:)
109
110
  current.results << result
110
111
  current
111
112
  end
112
113
 
113
114
  end
114
115
 
116
+ # Returns whether the chain is running in dry-run mode.
117
+ #
118
+ # @return [Boolean] Whether the chain is running in dry-run mode
119
+ #
120
+ # @example
121
+ # chain.dry_run? # => true
122
+ #
123
+ # @rbs () -> bool
124
+ def dry_run?
125
+ !!@dry_run
126
+ end
127
+
115
128
  # Converts the chain to a hash representation.
116
129
  #
117
130
  # @option return [String] :id The chain identifier
@@ -127,7 +140,8 @@ module CMDx
127
140
  # @rbs () -> Hash[Symbol, untyped]
128
141
  def to_h
129
142
  {
130
- id: id,
143
+ id:,
144
+ dry_run: dry_run?,
131
145
  results: results.map(&:to_h)
132
146
  }
133
147
  end
data/lib/cmdx/context.rb CHANGED
@@ -164,6 +164,7 @@ module CMDx
164
164
  args.to_h.each { |key, value| self[key.to_sym] = value }
165
165
  self
166
166
  end
167
+ alias merge merge!
167
168
 
168
169
  # Deletes a key-value pair from the context.
169
170
  #
@@ -182,6 +183,23 @@ module CMDx
182
183
  def delete!(key, &)
183
184
  table.delete(key.to_sym, &)
184
185
  end
186
+ alias delete delete!
187
+
188
+ # Clears all key-value pairs from the context.
189
+ #
190
+ # @return [Context] self for method chaining
191
+ #
192
+ # @example
193
+ # context = Context.new(name: "John")
194
+ # context.clear!
195
+ # context.to_h # => {}
196
+ #
197
+ # @rbs () -> self
198
+ def clear!
199
+ table.clear
200
+ self
201
+ end
202
+ alias clear clear!
185
203
 
186
204
  # Compares this context with another object for equality.
187
205
  #
data/lib/cmdx/executor.rb CHANGED
@@ -8,6 +8,8 @@ module CMDx
8
8
  # and proper error handling for different types of failures.
9
9
  class Executor
10
10
 
11
+ extend Forwardable
12
+
11
13
  # Returns the task being executed.
12
14
  #
13
15
  # @return [Task] The task instance
@@ -18,6 +20,8 @@ module CMDx
18
20
  # @rbs @task: Task
19
21
  attr_reader :task
20
22
 
23
+ def_delegators :task, :result
24
+
21
25
  # @param task [CMDx::Task] The task to execute
22
26
  #
23
27
  # @return [CMDx::Executor] A new executor instance
@@ -65,13 +69,13 @@ module CMDx
65
69
  rescue UndefinedMethodError => e
66
70
  raise(e) # No need to clear the Chain since exception is not being re-raised
67
71
  rescue Fault => e
68
- task.result.throw!(e.result, halt: false, cause: e)
72
+ result.throw!(e.result, halt: false, cause: e)
69
73
  rescue StandardError => e
70
74
  retry if retry_execution?(e)
71
- task.result.fail!("[#{e.class}] #{e.message}", halt: false, cause: e)
75
+ result.fail!("[#{e.class}] #{e.message}", halt: false, cause: e)
72
76
  task.class.settings[:exception_handler]&.call(task, e)
73
77
  ensure
74
- task.result.executed!
78
+ result.executed!
75
79
  post_execution!
76
80
  end
77
81
 
@@ -96,14 +100,14 @@ module CMDx
96
100
  rescue UndefinedMethodError => e
97
101
  raise_exception(e)
98
102
  rescue Fault => e
99
- task.result.throw!(e.result, halt: false, cause: e)
103
+ result.throw!(e.result, halt: false, cause: e)
100
104
  halt_execution?(e) ? raise_exception(e) : post_execution!
101
105
  rescue StandardError => e
102
106
  retry if retry_execution?(e)
103
- task.result.fail!("[#{e.class}] #{e.message}", halt: false, cause: e)
107
+ result.fail!("[#{e.class}] #{e.message}", halt: false, cause: e)
104
108
  raise_exception(e)
105
109
  else
106
- task.result.executed!
110
+ result.executed!
107
111
  post_execution!
108
112
  end
109
113
 
@@ -134,17 +138,17 @@ module CMDx
134
138
  #
135
139
  # @rbs (Exception exception) -> bool
136
140
  def retry_execution?(exception)
137
- available_retries = (task.class.settings[:retries] || 0).to_i
141
+ available_retries = Integer(task.class.settings[:retries] || 0)
138
142
  return false unless available_retries.positive?
139
143
 
140
- current_retries = (task.result.metadata[:retries] ||= 0).to_i
141
- remaining_retries = available_retries - current_retries
144
+ current_retry = result.retries
145
+ remaining_retries = available_retries - current_retry
142
146
  return false unless remaining_retries.positive?
143
147
 
144
148
  exceptions = Array(task.class.settings[:retry_on] || StandardError)
145
149
  return false unless exceptions.any? { |e| exception.class <= e }
146
150
 
147
- task.result.metadata[:retries] += 1
151
+ result.retries += 1
148
152
 
149
153
  task.logger.warn do
150
154
  reason = "[#{exception.class}] #{exception.message}"
@@ -154,13 +158,13 @@ module CMDx
154
158
  jitter = task.class.settings[:retry_jitter]
155
159
  jitter =
156
160
  if jitter.is_a?(Symbol)
157
- task.send(jitter, current_retries)
161
+ task.send(jitter, current_retry)
158
162
  elsif jitter.is_a?(Proc)
159
- task.instance_exec(current_retries, &jitter)
163
+ task.instance_exec(current_retry, &jitter)
160
164
  elsif jitter.respond_to?(:call)
161
- jitter.call(task, current_retries)
165
+ jitter.call(task, current_retry)
162
166
  else
163
- jitter.to_f * current_retries
167
+ jitter.to_f * current_retry
164
168
  end
165
169
 
166
170
  sleep(jitter) if jitter.positive?
@@ -208,7 +212,7 @@ module CMDx
208
212
  task.class.settings[:attributes].define_and_verify(task)
209
213
  return if task.errors.empty?
210
214
 
211
- task.result.fail!(
215
+ result.fail!(
212
216
  Locale.t("cmdx.faults.invalid"),
213
217
  errors: {
214
218
  full_message: task.errors.to_s,
@@ -223,7 +227,7 @@ module CMDx
223
227
  def execution!
224
228
  invoke_callbacks(:before_execution)
225
229
 
226
- task.result.executing!
230
+ result.executing!
227
231
  task.work
228
232
  end
229
233
 
@@ -231,12 +235,12 @@ module CMDx
231
235
  #
232
236
  # @rbs () -> void
233
237
  def post_execution!
234
- invoke_callbacks(:"on_#{task.result.state}")
235
- invoke_callbacks(:on_executed) if task.result.executed?
238
+ invoke_callbacks(:"on_#{result.state}")
239
+ invoke_callbacks(:on_executed) if result.executed?
236
240
 
237
- invoke_callbacks(:"on_#{task.result.status}")
238
- invoke_callbacks(:on_good) if task.result.good?
239
- invoke_callbacks(:on_bad) if task.result.bad?
241
+ invoke_callbacks(:"on_#{result.status}")
242
+ invoke_callbacks(:on_good) if result.good?
243
+ invoke_callbacks(:on_bad) if result.bad?
240
244
  end
241
245
 
242
246
  # Finalizes execution by freezing the task, logging results, and rolling back work.
@@ -246,26 +250,25 @@ module CMDx
246
250
  log_execution!
247
251
  log_backtrace! if task.class.settings[:backtrace]
248
252
 
253
+ rollback_execution!
249
254
  freeze_execution!
250
255
  clear_chain!
251
-
252
- rollback_execution!
253
256
  end
254
257
 
255
258
  # Logs the execution result at the configured log level.
256
259
  #
257
260
  # @rbs () -> void
258
261
  def log_execution!
259
- task.logger.info { task.result.to_h }
262
+ task.logger.info { result.to_h }
260
263
  end
261
264
 
262
265
  # Logs the backtrace of the exception if the task failed.
263
266
  #
264
267
  # @rbs () -> void
265
268
  def log_backtrace!
266
- return unless task.result.failed?
269
+ return unless result.failed?
267
270
 
268
- exception = task.result.caused_failure.cause
271
+ exception = result.caused_failure.cause
269
272
  return if exception.is_a?(Fault)
270
273
 
271
274
  task.logger.error do
@@ -287,11 +290,11 @@ module CMDx
287
290
  return if Coercions::Boolean.call(skip_freezing)
288
291
 
289
292
  task.freeze
290
- task.result.freeze
293
+ result.freeze
291
294
 
292
295
  # Freezing the context and chain can only be done
293
296
  # once the outer-most task has completed.
294
- return unless task.result.index.zero?
297
+ return unless result.index.zero?
295
298
 
296
299
  task.context.freeze
297
300
  task.chain.freeze
@@ -301,7 +304,7 @@ module CMDx
301
304
  #
302
305
  # @rbs () -> void
303
306
  def clear_chain!
304
- return unless task.result.index.zero?
307
+ return unless result.index.zero?
305
308
 
306
309
  Chain.clear
307
310
  end
@@ -310,12 +313,14 @@ module CMDx
310
313
  #
311
314
  # @rbs () -> void
312
315
  def rollback_execution!
316
+ return if result.rolled_back?
313
317
  return unless task.respond_to?(:rollback)
314
318
 
315
319
  statuses = task.class.settings[:rollback_on]
316
320
  statuses = Array(statuses).map(&:to_s).uniq
317
- return unless statuses.include?(task.result.status)
321
+ return unless statuses.include?(result.status)
318
322
 
323
+ result.rolled_back = true
319
324
  task.rollback
320
325
  end
321
326