cmdx 1.0.0 → 1.1.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/.cursor/prompts/rspec.md +20 -0
- data/.cursor/prompts/yardoc.md +8 -0
- data/.rubocop.yml +5 -0
- data/CHANGELOG.md +101 -49
- data/README.md +2 -1
- data/docs/ai_prompts.md +10 -0
- data/docs/basics/call.md +11 -2
- data/docs/basics/chain.md +10 -1
- data/docs/basics/context.md +9 -0
- data/docs/basics/setup.md +9 -0
- data/docs/callbacks.md +14 -37
- data/docs/configuration.md +68 -27
- data/docs/getting_started.md +11 -0
- data/docs/internationalization.md +148 -0
- data/docs/interruptions/exceptions.md +10 -1
- data/docs/interruptions/faults.md +11 -2
- data/docs/interruptions/halt.md +9 -0
- data/docs/logging.md +14 -4
- data/docs/middlewares.md +53 -43
- data/docs/outcomes/result.md +9 -0
- data/docs/outcomes/states.md +9 -0
- data/docs/outcomes/statuses.md +9 -0
- data/docs/parameters/coercions.md +58 -38
- data/docs/parameters/defaults.md +10 -1
- data/docs/parameters/definitions.md +9 -0
- data/docs/parameters/namespacing.md +9 -0
- data/docs/parameters/validations.md +8 -67
- data/docs/testing.md +22 -13
- data/docs/tips_and_tricks.md +9 -0
- data/docs/workflows.md +14 -4
- data/lib/cmdx/.DS_Store +0 -0
- data/lib/cmdx/callback.rb +36 -56
- data/lib/cmdx/callback_registry.rb +82 -73
- data/lib/cmdx/chain.rb +65 -122
- data/lib/cmdx/chain_inspector.rb +22 -115
- data/lib/cmdx/chain_serializer.rb +17 -148
- data/lib/cmdx/coercion.rb +49 -0
- data/lib/cmdx/coercion_registry.rb +94 -0
- data/lib/cmdx/coercions/array.rb +18 -36
- data/lib/cmdx/coercions/big_decimal.rb +21 -33
- data/lib/cmdx/coercions/boolean.rb +21 -40
- data/lib/cmdx/coercions/complex.rb +18 -31
- data/lib/cmdx/coercions/date.rb +20 -39
- data/lib/cmdx/coercions/date_time.rb +22 -39
- data/lib/cmdx/coercions/float.rb +19 -32
- data/lib/cmdx/coercions/hash.rb +22 -41
- data/lib/cmdx/coercions/integer.rb +20 -33
- data/lib/cmdx/coercions/rational.rb +20 -32
- data/lib/cmdx/coercions/string.rb +23 -31
- data/lib/cmdx/coercions/time.rb +24 -40
- data/lib/cmdx/coercions/virtual.rb +14 -31
- data/lib/cmdx/configuration.rb +57 -171
- data/lib/cmdx/context.rb +22 -165
- data/lib/cmdx/core_ext/hash.rb +42 -67
- data/lib/cmdx/core_ext/module.rb +35 -79
- data/lib/cmdx/core_ext/object.rb +63 -98
- data/lib/cmdx/correlator.rb +40 -156
- data/lib/cmdx/error.rb +37 -202
- data/lib/cmdx/errors.rb +165 -202
- data/lib/cmdx/fault.rb +55 -158
- data/lib/cmdx/faults.rb +26 -137
- data/lib/cmdx/immutator.rb +22 -109
- data/lib/cmdx/lazy_struct.rb +103 -187
- data/lib/cmdx/log_formatters/json.rb +14 -40
- data/lib/cmdx/log_formatters/key_value.rb +14 -40
- data/lib/cmdx/log_formatters/line.rb +14 -48
- data/lib/cmdx/log_formatters/logstash.rb +14 -57
- data/lib/cmdx/log_formatters/pretty_json.rb +14 -50
- data/lib/cmdx/log_formatters/pretty_key_value.rb +13 -46
- data/lib/cmdx/log_formatters/pretty_line.rb +16 -54
- data/lib/cmdx/log_formatters/raw.rb +19 -49
- data/lib/cmdx/logger.rb +20 -82
- data/lib/cmdx/logger_ansi.rb +18 -75
- data/lib/cmdx/logger_serializer.rb +24 -114
- data/lib/cmdx/middleware.rb +38 -60
- data/lib/cmdx/middleware_registry.rb +81 -77
- data/lib/cmdx/middlewares/correlate.rb +41 -226
- data/lib/cmdx/middlewares/timeout.rb +46 -185
- data/lib/cmdx/parameter.rb +120 -198
- data/lib/cmdx/parameter_evaluator.rb +231 -0
- data/lib/cmdx/parameter_inspector.rb +25 -56
- data/lib/cmdx/parameter_registry.rb +59 -84
- data/lib/cmdx/parameter_serializer.rb +23 -74
- data/lib/cmdx/railtie.rb +24 -107
- data/lib/cmdx/result.rb +254 -260
- data/lib/cmdx/result_ansi.rb +19 -85
- data/lib/cmdx/result_inspector.rb +27 -68
- data/lib/cmdx/result_logger.rb +18 -81
- data/lib/cmdx/result_serializer.rb +28 -132
- data/lib/cmdx/rspec/matchers.rb +28 -0
- data/lib/cmdx/rspec/result_matchers/be_executed.rb +42 -0
- data/lib/cmdx/rspec/result_matchers/be_failed_task.rb +94 -0
- data/lib/cmdx/rspec/result_matchers/be_skipped_task.rb +94 -0
- data/lib/cmdx/rspec/result_matchers/be_state_matchers.rb +59 -0
- data/lib/cmdx/rspec/result_matchers/be_status_matchers.rb +57 -0
- data/lib/cmdx/rspec/result_matchers/be_successful_task.rb +87 -0
- data/lib/cmdx/rspec/result_matchers/have_bad_outcome.rb +51 -0
- data/lib/cmdx/rspec/result_matchers/have_caused_failure.rb +58 -0
- data/lib/cmdx/rspec/result_matchers/have_chain_index.rb +59 -0
- data/lib/cmdx/rspec/result_matchers/have_context.rb +86 -0
- data/lib/cmdx/rspec/result_matchers/have_empty_metadata.rb +54 -0
- data/lib/cmdx/rspec/result_matchers/have_good_outcome.rb +52 -0
- data/lib/cmdx/rspec/result_matchers/have_metadata.rb +114 -0
- data/lib/cmdx/rspec/result_matchers/have_preserved_context.rb +66 -0
- data/lib/cmdx/rspec/result_matchers/have_received_thrown_failure.rb +64 -0
- data/lib/cmdx/rspec/result_matchers/have_runtime.rb +78 -0
- data/lib/cmdx/rspec/result_matchers/have_thrown_failure.rb +76 -0
- data/lib/cmdx/rspec/task_matchers/be_well_formed_task.rb +62 -0
- data/lib/cmdx/rspec/task_matchers/have_callback.rb +85 -0
- data/lib/cmdx/rspec/task_matchers/have_cmd_setting.rb +68 -0
- data/lib/cmdx/rspec/task_matchers/have_executed_callbacks.rb +92 -0
- data/lib/cmdx/rspec/task_matchers/have_middleware.rb +46 -0
- data/lib/cmdx/rspec/task_matchers/have_parameter.rb +181 -0
- data/lib/cmdx/task.rb +213 -425
- data/lib/cmdx/task_deprecator.rb +55 -0
- data/lib/cmdx/task_processor.rb +245 -0
- data/lib/cmdx/task_serializer.rb +22 -70
- data/lib/cmdx/utils/ansi_color.rb +13 -89
- data/lib/cmdx/utils/log_timestamp.rb +13 -42
- data/lib/cmdx/utils/monotonic_runtime.rb +13 -63
- data/lib/cmdx/utils/name_affix.rb +21 -71
- data/lib/cmdx/validator.rb +48 -0
- data/lib/cmdx/validator_registry.rb +86 -0
- data/lib/cmdx/validators/exclusion.rb +55 -94
- data/lib/cmdx/validators/format.rb +31 -85
- data/lib/cmdx/validators/inclusion.rb +65 -110
- data/lib/cmdx/validators/length.rb +117 -133
- data/lib/cmdx/validators/numeric.rb +123 -130
- data/lib/cmdx/validators/presence.rb +38 -79
- data/lib/cmdx/version.rb +1 -7
- data/lib/cmdx/workflow.rb +46 -339
- data/lib/cmdx.rb +1 -1
- data/lib/generators/cmdx/install_generator.rb +14 -31
- data/lib/generators/cmdx/task_generator.rb +39 -55
- data/lib/generators/cmdx/templates/install.rb +61 -11
- data/lib/generators/cmdx/workflow_generator.rb +41 -66
- data/lib/locales/ar.yml +35 -0
- data/lib/locales/cs.yml +35 -0
- data/lib/locales/da.yml +35 -0
- data/lib/locales/de.yml +35 -0
- data/lib/locales/el.yml +35 -0
- data/lib/locales/en.yml +19 -20
- data/lib/locales/es.yml +19 -20
- data/lib/locales/fi.yml +35 -0
- data/lib/locales/fr.yml +35 -0
- data/lib/locales/he.yml +35 -0
- data/lib/locales/hi.yml +35 -0
- data/lib/locales/it.yml +35 -0
- data/lib/locales/ja.yml +35 -0
- data/lib/locales/ko.yml +35 -0
- data/lib/locales/nl.yml +35 -0
- data/lib/locales/no.yml +35 -0
- data/lib/locales/pl.yml +35 -0
- data/lib/locales/pt.yml +35 -0
- data/lib/locales/ru.yml +35 -0
- data/lib/locales/sv.yml +35 -0
- data/lib/locales/th.yml +35 -0
- data/lib/locales/tr.yml +35 -0
- data/lib/locales/vi.yml +35 -0
- data/lib/locales/zh.yml +35 -0
- metadata +57 -8
- data/lib/cmdx/parameter_validator.rb +0 -81
- data/lib/cmdx/parameter_value.rb +0 -244
- data/lib/cmdx/parameters_inspector.rb +0 -72
- data/lib/cmdx/parameters_serializer.rb +0 -115
- data/lib/cmdx/rspec/result_matchers.rb +0 -917
- data/lib/cmdx/rspec/task_matchers.rb +0 -570
- data/lib/cmdx/validators/custom.rb +0 -102
data/lib/cmdx/task.rb
CHANGED
@@ -1,157 +1,42 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module CMDx
|
4
|
-
|
5
|
-
# Task is the base class for all command objects in CMDx, providing a framework
|
6
|
-
# for encapsulating business logic with parameter validation, callbacks, and result tracking.
|
4
|
+
# Core task execution system for CMDx framework.
|
7
5
|
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
#
|
11
|
-
#
|
12
|
-
# @example Basic task definition
|
13
|
-
# class ProcessOrderTask < CMDx::Task
|
14
|
-
# required :order_id, type: :integer
|
15
|
-
# optional :notify_user, type: :boolean, default: true
|
16
|
-
#
|
17
|
-
# def call
|
18
|
-
# # Business logic here
|
19
|
-
# context.order = Order.find(order_id)
|
20
|
-
# skip!(reason: "Order already processed") if context.order.processed?
|
21
|
-
#
|
22
|
-
# context.order.process!
|
23
|
-
# NotificationService.call(order_id: order_id) if notify_user
|
24
|
-
# end
|
25
|
-
# end
|
26
|
-
#
|
27
|
-
# @example Task execution
|
28
|
-
# result = ProcessOrderTask.call(order_id: 123, notify_user: false)
|
29
|
-
# result.success? #=> true
|
30
|
-
# result.context.order #=> <Order id: 123>
|
31
|
-
#
|
32
|
-
# @example Task chaining with Result objects
|
33
|
-
# # First task extracts data
|
34
|
-
# class ExtractDataTask < CMDx::Task
|
35
|
-
# required :source_id, type: :integer
|
36
|
-
#
|
37
|
-
# def call
|
38
|
-
# context.extracted_data = DataSource.extract(source_id)
|
39
|
-
# context.extraction_time = Time.now
|
40
|
-
# end
|
41
|
-
# end
|
42
|
-
#
|
43
|
-
# # Second task processes the extracted data
|
44
|
-
# class ProcessDataTask < CMDx::Task
|
45
|
-
# def call
|
46
|
-
# # Access data from previous task's context
|
47
|
-
# fail!(reason: "No data to process") unless context.extracted_data
|
48
|
-
#
|
49
|
-
# context.processed_data = DataProcessor.process(context.extracted_data)
|
50
|
-
# context.processing_time = Time.now
|
51
|
-
# end
|
52
|
-
# end
|
53
|
-
#
|
54
|
-
# # Chain tasks by passing Result objects
|
55
|
-
# extraction_result = ExtractDataTask.call(source_id: 123)
|
56
|
-
# processing_result = ProcessDataTask.call(extraction_result)
|
57
|
-
#
|
58
|
-
# # Result object context is automatically extracted
|
59
|
-
# processing_result.context.extracted_data #=> data from first task
|
60
|
-
# processing_result.context.processed_data #=> data from second task
|
61
|
-
#
|
62
|
-
# @example Using callbacks
|
63
|
-
# class ProcessOrderTask < CMDx::Task
|
64
|
-
# before_validation :log_start
|
65
|
-
# after_execution :cleanup_resources
|
66
|
-
# on_success :send_confirmation
|
67
|
-
# on_failure :alert_support, if: :critical_order?
|
68
|
-
#
|
69
|
-
# def call
|
70
|
-
# # Implementation
|
71
|
-
# end
|
72
|
-
#
|
73
|
-
# private
|
74
|
-
#
|
75
|
-
# def critical_order?
|
76
|
-
# context.order.value > 10_000
|
77
|
-
# end
|
78
|
-
# end
|
79
|
-
#
|
80
|
-
# == Task Chaining and Data Flow
|
81
|
-
#
|
82
|
-
# Tasks can be seamlessly chained by passing Result objects between them.
|
83
|
-
# This enables powerful workflows where the output of one task becomes the
|
84
|
-
# input for the next, maintaining data consistency and enabling complex
|
85
|
-
# business logic composition.
|
86
|
-
#
|
87
|
-
# Benefits of Result object chaining:
|
88
|
-
# - Automatic context extraction and data flow
|
89
|
-
# - Preserves all context data including custom attributes
|
90
|
-
# - Maintains execution chain relationships
|
91
|
-
# - Enables conditional task execution based on previous results
|
92
|
-
# - Simplifies error handling and rollback scenarios
|
93
|
-
#
|
94
|
-
# @see Result Result object for execution outcomes
|
95
|
-
# @see Context Context object for parameter access
|
96
|
-
# @see ParameterRegistry Parameter definition and validation
|
97
|
-
# @see Workflow Workflow for executing multiple tasks
|
98
|
-
# @since 1.0.0
|
6
|
+
# Task provides the foundational functionality for executing business logic
|
7
|
+
# with parameter validation, middleware support, callback execution, and
|
8
|
+
# result tracking. Tasks encapsulate reusable business operations with
|
9
|
+
# comprehensive error handling, logging, and execution state management.
|
99
10
|
class Task
|
100
11
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
]
|
117
|
-
|
118
|
-
__cmdx_attr_setting :task_settings,
|
119
|
-
default: -> { CMDx.configuration.to_h.merge(tags: []) }
|
120
|
-
__cmdx_attr_setting :cmd_middlewares,
|
121
|
-
default: -> { MiddlewareRegistry.new(CMDx.configuration.middlewares) }
|
122
|
-
__cmdx_attr_setting :cmd_callbacks,
|
123
|
-
default: -> { CallbackRegistry.new(CMDx.configuration.callbacks) }
|
124
|
-
__cmdx_attr_setting :cmd_parameters,
|
125
|
-
default: -> { ParameterRegistry.new }
|
126
|
-
|
127
|
-
__cmdx_attr_delegator :cmd_middlewares, :cmd_callbacks, :cmd_parameters, :task_setting, :task_setting?,
|
128
|
-
to: :class
|
129
|
-
__cmdx_attr_delegator :skip!, :fail!, :throw!,
|
130
|
-
to: :result
|
131
|
-
|
132
|
-
##
|
133
|
-
# @!attribute [r] context
|
134
|
-
# @return [Context] parameter context for this task execution
|
12
|
+
cmdx_attr_setting :cmd_settings,
|
13
|
+
default: -> { CMDx.configuration.to_h.slice(:logger, :task_halt, :workflow_halt).merge(tags: []) }
|
14
|
+
cmdx_attr_setting :cmd_middlewares,
|
15
|
+
default: -> { MiddlewareRegistry.new(CMDx.configuration.middlewares) }
|
16
|
+
cmdx_attr_setting :cmd_callbacks,
|
17
|
+
default: -> { CallbackRegistry.new(CMDx.configuration.callbacks) }
|
18
|
+
cmdx_attr_setting :cmd_parameters,
|
19
|
+
default: -> { ParameterRegistry.new }
|
20
|
+
|
21
|
+
cmdx_attr_delegator :cmd_middlewares, :cmd_callbacks, :cmd_parameters,
|
22
|
+
:cmd_settings, :cmd_setting, :cmd_setting?,
|
23
|
+
to: :class
|
24
|
+
cmdx_attr_delegator :skip!, :fail!, :throw!,
|
25
|
+
to: :result
|
26
|
+
|
27
|
+
# @return [Context] parameter context for this task execution
|
135
28
|
attr_reader :context
|
136
29
|
|
137
|
-
|
138
|
-
# @!attribute [r] errors
|
139
|
-
# @return [Errors] collection of validation and execution errors
|
30
|
+
# @return [Errors] collection of validation and execution errors
|
140
31
|
attr_reader :errors
|
141
32
|
|
142
|
-
|
143
|
-
# @!attribute [r] id
|
144
|
-
# @return [String] unique identifier for this task instance
|
33
|
+
# @return [String] unique identifier for this task instance
|
145
34
|
attr_reader :id
|
146
35
|
|
147
|
-
|
148
|
-
# @!attribute [r] result
|
149
|
-
# @return [Result] execution result tracking state and status
|
36
|
+
# @return [Result] execution result tracking state and status
|
150
37
|
attr_reader :result
|
151
38
|
|
152
|
-
|
153
|
-
# @!attribute [r] chain
|
154
|
-
# @return [Chain] execution chain containing this task and related executions
|
39
|
+
# @return [Chain] execution chain containing this task and related executions
|
155
40
|
attr_reader :chain
|
156
41
|
|
157
42
|
# @return [Context] alias for context
|
@@ -160,21 +45,20 @@ module CMDx
|
|
160
45
|
# @return [Result] alias for result
|
161
46
|
alias res result
|
162
47
|
|
163
|
-
|
164
|
-
#
|
48
|
+
# Creates a new task instance with the provided execution context.
|
49
|
+
#
|
50
|
+
# @param context [Hash, Context] execution context data or Context instance
|
165
51
|
#
|
166
|
-
#
|
167
|
-
# When a Result object is passed, its context is automatically extracted,
|
168
|
-
# enabling seamless task chaining and data flow between tasks.
|
52
|
+
# @return [Task] a new task instance ready for execution
|
169
53
|
#
|
170
|
-
# @
|
171
|
-
#
|
172
|
-
# task
|
54
|
+
# @example Create a task with hash context
|
55
|
+
# task = MyTask.new(user_id: 123, action: "process")
|
56
|
+
# task.context.user_id #=> 123
|
173
57
|
#
|
174
|
-
# @example
|
175
|
-
#
|
176
|
-
#
|
177
|
-
#
|
58
|
+
# @example Create a task with existing context
|
59
|
+
# existing_context = CMDx::Context.build(name: "John")
|
60
|
+
# task = MyTask.new(existing_context)
|
61
|
+
# task.context.name #=> "John"
|
178
62
|
def initialize(context = {})
|
179
63
|
context = context.context if context.respond_to?(:context)
|
180
64
|
|
@@ -183,347 +67,251 @@ module CMDx
|
|
183
67
|
@id = CMDx::Correlator.generate
|
184
68
|
@result = Result.new(self)
|
185
69
|
@chain = Chain.build(@result)
|
70
|
+
|
71
|
+
TaskDeprecator.call(self)
|
186
72
|
end
|
187
73
|
|
188
74
|
class << self
|
189
75
|
|
190
|
-
|
191
|
-
#
|
192
|
-
#
|
193
|
-
#
|
194
|
-
#
|
195
|
-
#
|
196
|
-
#
|
197
|
-
# @
|
198
|
-
#
|
199
|
-
#
|
200
|
-
# @
|
201
|
-
#
|
202
|
-
#
|
203
|
-
#
|
204
|
-
#
|
205
|
-
# @
|
206
|
-
#
|
207
|
-
#
|
208
|
-
# @
|
209
|
-
#
|
210
|
-
|
76
|
+
# Registers callbacks for task execution lifecycle events.
|
77
|
+
#
|
78
|
+
# These methods are dynamically defined for each callback type and provide
|
79
|
+
# a clean DSL for registering callbacks that will be executed at specific
|
80
|
+
# points during task execution.
|
81
|
+
#
|
82
|
+
# @param callables [Array<Object>] callback objects to register (symbols, procs, classes)
|
83
|
+
# @param options [Hash] conditional execution options
|
84
|
+
# @param block [Proc] optional block to register as a callback
|
85
|
+
#
|
86
|
+
# @return [void]
|
87
|
+
#
|
88
|
+
# @example Register before_execution callback with symbol
|
89
|
+
# MyTask.before_execution :setup_database
|
90
|
+
#
|
91
|
+
# @example Register before_execution callback with proc
|
92
|
+
# MyTask.before_execution -> { puts "Starting task execution" }
|
93
|
+
#
|
94
|
+
# @example Register before_execution callback with class
|
95
|
+
# MyTask.before_execution SetupCallback
|
96
|
+
#
|
97
|
+
# @example Register before_execution callback with block
|
98
|
+
# MyTask.before_execution { |task| task.context.started_at = Time.now }
|
99
|
+
#
|
100
|
+
# @example Register on_success callback with conditional options
|
101
|
+
# MyTask.on_success :send_notification, if: -> { Rails.env.production? }
|
102
|
+
#
|
103
|
+
# @example Register on_success callback with multiple callables
|
104
|
+
# MyTask.on_success :log_success, :send_email, :update_metrics
|
105
|
+
CallbackRegistry::TYPES.each do |callback|
|
211
106
|
define_method(callback) do |*callables, **options, &block|
|
212
107
|
cmd_callbacks.register(callback, *callables, **options, &block)
|
213
108
|
end
|
214
109
|
end
|
215
110
|
|
216
|
-
|
217
|
-
#
|
218
|
-
#
|
219
|
-
# @param key [Symbol, String] setting key to retrieve
|
220
|
-
# @return [Object] the setting value
|
221
|
-
# @example
|
222
|
-
# task_setting(:timeout) #=> 30
|
223
|
-
def task_setting(key)
|
224
|
-
__cmdx_yield(task_settings[key])
|
225
|
-
end
|
226
|
-
|
227
|
-
##
|
228
|
-
# Checks if a task setting exists.
|
111
|
+
# Retrieves the value of a task setting.
|
112
|
+
#
|
113
|
+
# @param key [Symbol] the setting key to retrieve
|
229
114
|
#
|
230
|
-
# @
|
231
|
-
#
|
232
|
-
|
233
|
-
|
115
|
+
# @return [Object] the setting value, processed through cmdx_yield
|
116
|
+
#
|
117
|
+
# @example Get logger setting
|
118
|
+
# MyTask.cmd_setting(:logger) #=> #<Logger:...>
|
119
|
+
#
|
120
|
+
# @example Get halt setting
|
121
|
+
# MyTask.cmd_setting(:task_halt) #=> "failed"
|
122
|
+
def cmd_setting(key)
|
123
|
+
cmdx_yield(cmd_settings[key])
|
234
124
|
end
|
235
125
|
|
236
|
-
|
237
|
-
#
|
238
|
-
#
|
239
|
-
#
|
240
|
-
# @return [
|
241
|
-
#
|
242
|
-
#
|
243
|
-
|
244
|
-
|
126
|
+
# Checks if a task setting key exists.
|
127
|
+
#
|
128
|
+
# @param key [Symbol] the setting key to check
|
129
|
+
#
|
130
|
+
# @return [Boolean] true if the setting exists, false otherwise
|
131
|
+
#
|
132
|
+
# @example Check if setting exists
|
133
|
+
# MyTask.cmd_setting?(:logger) #=> true
|
134
|
+
# MyTask.cmd_setting?(:invalid) #=> false
|
135
|
+
def cmd_setting?(key)
|
136
|
+
cmd_settings.key?(key)
|
245
137
|
end
|
246
138
|
|
247
|
-
|
248
|
-
#
|
249
|
-
#
|
250
|
-
#
|
251
|
-
#
|
252
|
-
#
|
253
|
-
# @
|
254
|
-
#
|
255
|
-
#
|
256
|
-
|
257
|
-
|
258
|
-
# use LoggingMiddleware
|
259
|
-
# use AuthenticationMiddleware, "admin"
|
260
|
-
# use CachingMiddleware.new(ttl: 300)
|
261
|
-
def use(middleware, ...)
|
262
|
-
cmd_middlewares.use(middleware, ...)
|
139
|
+
# Updates task settings with new values.
|
140
|
+
#
|
141
|
+
# @param options [Hash] hash of setting keys and values to merge
|
142
|
+
#
|
143
|
+
# @return [Hash] the updated task settings hash
|
144
|
+
#
|
145
|
+
# @example Update task settings
|
146
|
+
# MyTask.cmd_settings!(task_halt: ["failed", "error"])
|
147
|
+
# MyTask.cmd_setting(:task_halt) #=> ["failed", "error"]
|
148
|
+
def cmd_settings!(**options)
|
149
|
+
cmd_settings.merge!(options)
|
263
150
|
end
|
264
151
|
|
265
|
-
|
266
|
-
#
|
267
|
-
#
|
268
|
-
#
|
269
|
-
#
|
270
|
-
#
|
271
|
-
# @
|
272
|
-
#
|
273
|
-
# @
|
274
|
-
#
|
275
|
-
#
|
276
|
-
# @
|
277
|
-
#
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
152
|
+
# Registers middleware, callbacks, validators, or coercions with the task.
|
153
|
+
#
|
154
|
+
# @param type [Symbol] the type of registration (:middleware, :callback, :validator, :coercion)
|
155
|
+
# @param object [Object] the object to register
|
156
|
+
# @param args [Array] additional arguments passed to the registration method
|
157
|
+
#
|
158
|
+
# @return [void]
|
159
|
+
#
|
160
|
+
# @example Register middleware
|
161
|
+
# MyTask.use(:middleware, TimeoutMiddleware, timeout: 30)
|
162
|
+
#
|
163
|
+
# @example Register callback
|
164
|
+
# MyTask.use(:callback, :before_execution, MyCallback)
|
165
|
+
def use(type, object, ...)
|
166
|
+
case type
|
167
|
+
when :middleware
|
168
|
+
cmd_middlewares.register(object, ...)
|
169
|
+
when :callback
|
170
|
+
cmd_callbacks.register(type, object, ...)
|
171
|
+
when :validator
|
172
|
+
cmd_validators.register(type, object, ...)
|
173
|
+
when :coercion
|
174
|
+
cmd_coercions.register(type, object, ...)
|
175
|
+
end
|
284
176
|
end
|
285
177
|
|
286
|
-
##
|
287
178
|
# Defines optional parameters for the task.
|
288
179
|
#
|
289
|
-
# @param attributes [Array<Symbol>] parameter names
|
290
|
-
# @param options [Hash] parameter options
|
291
|
-
# @param block [Proc] block for nested
|
292
|
-
#
|
293
|
-
# @
|
294
|
-
#
|
295
|
-
#
|
296
|
-
#
|
297
|
-
#
|
180
|
+
# @param attributes [Array<Symbol>] parameter names to define as optional
|
181
|
+
# @param options [Hash] parameter configuration options
|
182
|
+
# @param block [Proc] optional block for defining nested parameters
|
183
|
+
#
|
184
|
+
# @return [void]
|
185
|
+
#
|
186
|
+
# @example Define optional parameters
|
187
|
+
# MyTask.optional :name, :email, type: :string
|
188
|
+
#
|
189
|
+
# @example Define optional parameter with validation
|
190
|
+
# MyTask.optional :age, type: :integer, validate: { numeric: { greater_than: 0 } }
|
298
191
|
def optional(*attributes, **options, &)
|
299
192
|
parameters = Parameter.optional(*attributes, **options.merge(klass: self), &)
|
300
|
-
cmd_parameters.concat(parameters)
|
193
|
+
cmd_parameters.registry.concat(parameters)
|
301
194
|
end
|
302
195
|
|
303
|
-
##
|
304
196
|
# Defines required parameters for the task.
|
305
197
|
#
|
306
|
-
# @param attributes [Array<Symbol>] parameter names
|
307
|
-
# @param options [Hash] parameter options
|
308
|
-
# @param block [Proc] block for nested
|
309
|
-
#
|
310
|
-
# @
|
311
|
-
#
|
312
|
-
#
|
198
|
+
# @param attributes [Array<Symbol>] parameter names to define as required
|
199
|
+
# @param options [Hash] parameter configuration options
|
200
|
+
# @param block [Proc] optional block for defining nested parameters
|
201
|
+
#
|
202
|
+
# @return [void]
|
203
|
+
#
|
204
|
+
# @example Define required parameters
|
205
|
+
# MyTask.required :user_id, :action, type: :string
|
206
|
+
#
|
207
|
+
# @example Define required parameter with nested structure
|
208
|
+
# MyTask.required :user, type: :hash do
|
313
209
|
# required :name, type: :string
|
314
|
-
# optional :
|
210
|
+
# optional :email, type: :string
|
315
211
|
# end
|
316
212
|
def required(*attributes, **options, &)
|
317
213
|
parameters = Parameter.required(*attributes, **options.merge(klass: self), &)
|
318
|
-
cmd_parameters.concat(parameters)
|
214
|
+
cmd_parameters.registry.concat(parameters)
|
319
215
|
end
|
320
216
|
|
321
|
-
|
322
|
-
#
|
323
|
-
#
|
324
|
-
#
|
325
|
-
#
|
326
|
-
#
|
327
|
-
#
|
328
|
-
#
|
329
|
-
#
|
330
|
-
#
|
331
|
-
# @example With hash parameters
|
332
|
-
# result = ProcessOrderTask.call(order_id: 123)
|
333
|
-
# result.success? #=> true or false
|
334
|
-
# result.context.order #=> processed order
|
335
|
-
#
|
336
|
-
# @example With Result object (task chaining)
|
337
|
-
# extraction_result = ExtractDataTask.call(source_id: 456)
|
338
|
-
# processing_result = ProcessDataTask.call(extraction_result)
|
339
|
-
# # Context from extraction_result is automatically used
|
340
|
-
# processing_result.context.source_id #=> 456
|
217
|
+
# Executes the task with fault tolerance and returns the result.
|
218
|
+
#
|
219
|
+
# @param args [Array] arguments passed to the task constructor
|
220
|
+
#
|
221
|
+
# @return [Result] the task execution result
|
222
|
+
#
|
223
|
+
# @example Execute task with fault tolerance
|
224
|
+
# result = MyTask.call(user_id: 123)
|
225
|
+
# result.success? #=> true
|
226
|
+
# result.context.user_id #=> 123
|
341
227
|
def call(...)
|
342
228
|
instance = new(...)
|
343
|
-
instance.
|
229
|
+
instance.process
|
344
230
|
instance.result
|
345
231
|
end
|
346
232
|
|
347
|
-
|
348
|
-
#
|
349
|
-
#
|
350
|
-
#
|
351
|
-
#
|
352
|
-
#
|
353
|
-
#
|
354
|
-
#
|
355
|
-
# @param args [Array] arguments passed to task initialization
|
356
|
-
# @return [Result] execution result if successful
|
357
|
-
# @raise [Fault] if task fails and task_halt includes the failure status
|
358
|
-
# @example With hash parameters
|
359
|
-
# begin
|
360
|
-
# result = ProcessOrderTask.call!(order_id: 123)
|
361
|
-
# rescue CMDx::Failed => e
|
362
|
-
# # Handle failure
|
363
|
-
# end
|
233
|
+
# Executes the task with strict fault handling and returns the result.
|
234
|
+
#
|
235
|
+
# @param args [Array] arguments passed to the task constructor
|
236
|
+
#
|
237
|
+
# @return [Result] the task execution result
|
238
|
+
#
|
239
|
+
# @raise [Fault] if the task fails and task_halt setting includes the failure status
|
364
240
|
#
|
365
|
-
# @example
|
241
|
+
# @example Execute task with strict fault handling
|
242
|
+
# result = MyTask.call!(user_id: 123)
|
243
|
+
# result.success? #=> true
|
244
|
+
#
|
245
|
+
# @example Handling fault on failure
|
366
246
|
# begin
|
367
|
-
#
|
368
|
-
#
|
369
|
-
#
|
370
|
-
# # Handle failure from either task
|
247
|
+
# MyTask.call!(invalid_data: true)
|
248
|
+
# rescue CMDx::Fault => e
|
249
|
+
# puts "Task failed: #{e.message}"
|
371
250
|
# end
|
372
251
|
def call!(...)
|
373
252
|
instance = new(...)
|
374
|
-
instance.
|
253
|
+
instance.process!
|
375
254
|
instance.result
|
376
255
|
end
|
377
256
|
|
378
257
|
end
|
379
258
|
|
380
|
-
|
381
|
-
#
|
382
|
-
# This method contains the core business logic
|
259
|
+
# Abstract method that must be implemented by task subclasses.
|
260
|
+
#
|
261
|
+
# This method contains the core business logic to be executed by the task.
|
262
|
+
# Subclasses must override this method to provide their specific implementation.
|
383
263
|
#
|
384
|
-
# @abstract Subclasses must implement this method
|
385
264
|
# @return [void]
|
386
|
-
# @raise [UndefinedCallError] if not implemented in subclass
|
387
|
-
# @example
|
388
|
-
# def call
|
389
|
-
# context.user = User.find(user_id)
|
390
|
-
# fail!(reason: "User not found") unless context.user
|
391
265
|
#
|
392
|
-
#
|
393
|
-
#
|
266
|
+
# @raise [UndefinedCallError] if not implemented by subclass
|
267
|
+
#
|
268
|
+
# @example Implement in a subclass
|
269
|
+
# class ProcessUserTask < CMDx::Task
|
270
|
+
# def call
|
271
|
+
# # Business logic here
|
272
|
+
# context.processed = true
|
273
|
+
# end
|
394
274
|
# end
|
395
275
|
def call
|
396
276
|
raise UndefinedCallError, "call method not defined in #{self.class.name}"
|
397
277
|
end
|
398
278
|
|
399
|
-
|
400
|
-
# Executes the task with full exception handling for the non-bang call method.
|
401
|
-
# Captures all exceptions and converts them to appropriate result states.
|
402
|
-
#
|
403
|
-
# @return [void]
|
404
|
-
def perform
|
405
|
-
return execute_call if cmd_middlewares.empty?
|
406
|
-
|
407
|
-
cmd_middlewares.call(self) { |task| task.send(:execute_call) }
|
408
|
-
end
|
409
|
-
|
410
|
-
##
|
411
|
-
# Executes the task with exception propagation for the bang call method.
|
412
|
-
# Allows exceptions to bubble up for external handling.
|
279
|
+
# Performs task execution with middleware support and fault tolerance.
|
413
280
|
#
|
414
281
|
# @return [void]
|
415
|
-
# @raise [Fault] if task fails and task_halt includes the failure status
|
416
|
-
def perform!
|
417
|
-
return execute_call! if cmd_middlewares.empty?
|
418
|
-
|
419
|
-
cmd_middlewares.call(self) { |task| task.send(:execute_call!) }
|
420
|
-
end
|
421
|
-
|
422
|
-
private
|
423
|
-
|
424
|
-
##
|
425
|
-
# Returns the logger instance for this task.
|
426
282
|
#
|
427
|
-
# @
|
428
|
-
#
|
429
|
-
|
430
|
-
|
283
|
+
# @example Task execution with middleware
|
284
|
+
# task = MyTask.new(user_id: 123)
|
285
|
+
# task.process
|
286
|
+
# task.result.success? #=> true
|
287
|
+
def process
|
288
|
+
cmd_middlewares.call(self) { |task| TaskProcessor.call(task) }
|
431
289
|
end
|
432
290
|
|
433
|
-
|
434
|
-
# Executes before-call callbacks and validations.
|
435
|
-
# Sets up the execution context and validates parameters.
|
291
|
+
# Performs task execution with middleware support and strict fault handling.
|
436
292
|
#
|
437
293
|
# @return [void]
|
438
|
-
# @api private
|
439
|
-
def before_call
|
440
|
-
cmd_callbacks.call(self, :before_execution)
|
441
|
-
|
442
|
-
result.executing!
|
443
|
-
cmd_callbacks.call(self, :on_executing)
|
444
|
-
|
445
|
-
cmd_callbacks.call(self, :before_validation)
|
446
|
-
ParameterValidator.call(self)
|
447
|
-
cmd_callbacks.call(self, :after_validation)
|
448
|
-
end
|
449
|
-
|
450
|
-
##
|
451
|
-
# Executes after-call callbacks based on execution results.
|
452
|
-
# Handles state and status transitions with appropriate callbacks.
|
453
294
|
#
|
454
|
-
# @
|
455
|
-
# @api private
|
456
|
-
def after_call
|
457
|
-
cmd_callbacks.call(self, :"on_#{result.state}")
|
458
|
-
cmd_callbacks.call(self, :on_executed) if result.executed?
|
459
|
-
|
460
|
-
cmd_callbacks.call(self, :"on_#{result.status}")
|
461
|
-
cmd_callbacks.call(self, :on_good) if result.good?
|
462
|
-
cmd_callbacks.call(self, :on_bad) if result.bad?
|
463
|
-
|
464
|
-
cmd_callbacks.call(self, :after_execution)
|
465
|
-
end
|
466
|
-
|
467
|
-
##
|
468
|
-
# Finalizes task execution by freezing the task and logging results.
|
295
|
+
# @raise [Fault] if task fails and task_halt setting includes the failure status
|
469
296
|
#
|
470
|
-
# @
|
471
|
-
#
|
472
|
-
|
473
|
-
|
474
|
-
|
297
|
+
# @example Task execution with strict fault handling
|
298
|
+
# task = MyTask.new(user_id: 123)
|
299
|
+
# task.process!
|
300
|
+
# task.result.success? #=> true
|
301
|
+
def process!
|
302
|
+
cmd_middlewares.call(self) { |task| TaskProcessor.call!(task) }
|
475
303
|
end
|
476
304
|
|
477
|
-
|
478
|
-
# Executes the task directly without middleware for the non-bang call method.
|
305
|
+
# Creates a logger instance for this task.
|
479
306
|
#
|
480
|
-
# @return [
|
481
|
-
# @api private
|
482
|
-
def execute_call
|
483
|
-
result.runtime do
|
484
|
-
before_call
|
485
|
-
call
|
486
|
-
rescue UndefinedCallError => e
|
487
|
-
raise(e)
|
488
|
-
rescue Fault => e
|
489
|
-
throw!(e.result, original_exception: e) if Array(task_setting(:task_halt)).include?(e.result.status)
|
490
|
-
rescue StandardError => e
|
491
|
-
fail!(reason: "[#{e.class}] #{e.message}", original_exception: e)
|
492
|
-
ensure
|
493
|
-
result.executed!
|
494
|
-
after_call
|
495
|
-
end
|
496
|
-
|
497
|
-
terminate_call
|
498
|
-
end
|
499
|
-
|
500
|
-
##
|
501
|
-
# Executes the task directly without middleware for the bang call method.
|
307
|
+
# @return [Logger] logger instance configured for this task
|
502
308
|
#
|
503
|
-
# @
|
504
|
-
#
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
rescue UndefinedCallError => e
|
510
|
-
Chain.clear
|
511
|
-
raise(e)
|
512
|
-
rescue Fault => e
|
513
|
-
result.executed!
|
514
|
-
|
515
|
-
if Array(task_setting(:task_halt)).include?(e.result.status)
|
516
|
-
Chain.clear
|
517
|
-
raise(e)
|
518
|
-
end
|
519
|
-
|
520
|
-
after_call # HACK: treat as NO-OP
|
521
|
-
else
|
522
|
-
result.executed!
|
523
|
-
after_call # ELSE: treat as success
|
524
|
-
end
|
525
|
-
|
526
|
-
terminate_call
|
309
|
+
# @example Getting task logger
|
310
|
+
# task = MyTask.new
|
311
|
+
# logger = task.send(:logger)
|
312
|
+
# logger.info("Task started")
|
313
|
+
def logger
|
314
|
+
Logger.call(self)
|
527
315
|
end
|
528
316
|
|
529
317
|
end
|