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
@@ -4,6 +4,7 @@ Parameter values can be validated using built-in validators or custom validation
|
|
4
4
|
|
5
5
|
## Table of Contents
|
6
6
|
|
7
|
+
- [TLDR](#tldr)
|
7
8
|
- [Common Options](#common-options)
|
8
9
|
- [Presence](#presence)
|
9
10
|
- [Format](#format)
|
@@ -11,9 +12,14 @@ Parameter values can be validated using built-in validators or custom validation
|
|
11
12
|
- [Inclusion](#inclusion)
|
12
13
|
- [Length](#length)
|
13
14
|
- [Numeric](#numeric)
|
14
|
-
- [Custom](#custom)
|
15
15
|
- [Validation Results](#validation-results)
|
16
|
-
|
16
|
+
|
17
|
+
## TLDR
|
18
|
+
|
19
|
+
- **Built-in validators** - `presence`, `format`, `inclusion`, `exclusion`, `length`, `numeric`
|
20
|
+
- **Common options** - All support `:allow_nil`, `:if`, `:unless`, `:message`
|
21
|
+
- **Usage** - Add to parameter definitions: `required :email, presence: true, format: { with: /@/ }`
|
22
|
+
- **Conditional** - Use `:if` and `:unless` for conditional validation
|
17
23
|
|
18
24
|
## Common Options
|
19
25
|
|
@@ -239,43 +245,6 @@ end
|
|
239
245
|
| `:is_message` | "must be %{is}" |
|
240
246
|
| `:is_not_message` | "must not be %{is_not}" |
|
241
247
|
|
242
|
-
## Custom
|
243
|
-
|
244
|
-
Validates using custom logic. Accepts any callable object (class, proc, lambda) implementing a `call` method that returns truthy for valid values.
|
245
|
-
|
246
|
-
```ruby
|
247
|
-
class EmailDomainValidator
|
248
|
-
def self.call(value, options)
|
249
|
-
allowed_domains = options.dig(:custom, :allowed_domains) || ['example.com']
|
250
|
-
domain = value.split('@').last
|
251
|
-
allowed_domains.include?(domain)
|
252
|
-
end
|
253
|
-
end
|
254
|
-
|
255
|
-
class CreateAccountTask < CMDx::Task
|
256
|
-
required :work_email, custom: {
|
257
|
-
validator: EmailDomainValidator,
|
258
|
-
allowed_domains: ['company.com', 'partner.org'],
|
259
|
-
message: "must be from an approved domain"
|
260
|
-
}
|
261
|
-
|
262
|
-
required :age, custom: {
|
263
|
-
validator: ->(value, options) { value.between?(18, 120) },
|
264
|
-
message: "must be a valid age"
|
265
|
-
}
|
266
|
-
|
267
|
-
def call
|
268
|
-
create_user_account
|
269
|
-
end
|
270
|
-
end
|
271
|
-
```
|
272
|
-
|
273
|
-
**Options:**
|
274
|
-
|
275
|
-
| Option | Description |
|
276
|
-
| ------------ | ----------- |
|
277
|
-
| `:validator` | Callable object returning true/false. Receives value and options as parameters |
|
278
|
-
|
279
248
|
## Validation Results
|
280
249
|
|
281
250
|
When validation fails, tasks enter a failed state with detailed error information:
|
@@ -303,34 +272,6 @@ result.metadata[:messages][:email] #=> ["format is invalid"]
|
|
303
272
|
result.metadata[:messages][:username] #=> ["cannot be empty"]
|
304
273
|
```
|
305
274
|
|
306
|
-
## Internationalization (i18n)
|
307
|
-
|
308
|
-
All validators support internationalization through Rails i18n. Customize error messages in your locale files:
|
309
|
-
|
310
|
-
```yaml
|
311
|
-
# config/locales/en.yml
|
312
|
-
en:
|
313
|
-
cmdx:
|
314
|
-
validators:
|
315
|
-
presence: "is required"
|
316
|
-
format: "has invalid format"
|
317
|
-
inclusion:
|
318
|
-
of: "must be one of: %{values}"
|
319
|
-
in: "must be within %{min} and %{max}"
|
320
|
-
exclusion:
|
321
|
-
of: "must not be one of: %{values}"
|
322
|
-
in: "must not be within %{min} and %{max}"
|
323
|
-
length:
|
324
|
-
within: "must be between %{min} and %{max} characters"
|
325
|
-
min: "must be at least %{min} characters"
|
326
|
-
max: "must be at most %{max} characters"
|
327
|
-
numeric:
|
328
|
-
within: "must be between %{min} and %{max}"
|
329
|
-
min: "must be at least %{min}"
|
330
|
-
max: "must be at most %{max}"
|
331
|
-
custom: "is invalid"
|
332
|
-
```
|
333
|
-
|
334
275
|
---
|
335
276
|
|
336
277
|
- **Prev:** [Parameters - Coercions](coercions.md)
|
data/docs/testing.md
CHANGED
@@ -4,6 +4,7 @@ CMDx provides a comprehensive suite of custom RSpec matchers designed for expres
|
|
4
4
|
|
5
5
|
## Table of Contents
|
6
6
|
|
7
|
+
- [TLDR](#tldr)
|
7
8
|
- [External Project Setup](#external-project-setup)
|
8
9
|
- [Matcher Organization](#matcher-organization)
|
9
10
|
- [Result Matchers](#result-matchers)
|
@@ -20,23 +21,31 @@ CMDx provides a comprehensive suite of custom RSpec matchers designed for expres
|
|
20
21
|
- [Composable Testing](#composable-testing)
|
21
22
|
- [Best Practices](#best-practices)
|
22
23
|
|
23
|
-
##
|
24
|
+
## TLDR
|
25
|
+
|
26
|
+
- **Custom matchers** - 40+ specialized RSpec matchers for testing CMDx tasks and results
|
27
|
+
- **Setup** - Require `cmdx/rspec/matchers`
|
28
|
+
- **Result matchers** - `be_successful_task`, `be_failed_task`, `be_skipped_task` with chainable metadata
|
29
|
+
- **Task matchers** - Parameter validation, lifecycle, exception handling, and configuration testing
|
30
|
+
- **Composable** - Chain matchers for complex validation scenarios
|
31
|
+
- **YARD documented** - Complete documentation with examples for all matchers
|
32
|
+
|
33
|
+
## External Project Setup
|
24
34
|
|
25
35
|
To use CMDx's custom matchers in an external RSpec-based project update your `spec/spec_helper.rb` or `spec/rails_helper.rb`:
|
26
36
|
|
27
37
|
```ruby
|
28
|
-
require "cmdx/rspec/
|
29
|
-
require "cmdx/rspec/task_matchers"
|
38
|
+
require "cmdx/rspec/matchers"
|
30
39
|
```
|
31
40
|
|
32
41
|
## Matcher Organization
|
33
42
|
|
34
43
|
CMDx matchers are organized into two primary files with comprehensive YARD documentation:
|
35
44
|
|
36
|
-
|
|
37
|
-
|
38
|
-
|
|
39
|
-
|
|
45
|
+
| Purpose | Matcher Count |
|
46
|
+
|---------|---------------|
|
47
|
+
| Task execution outcomes and side effects | 15+ matchers |
|
48
|
+
| Task behavior, validation, and lifecycle | 5+ matchers |
|
40
49
|
|
41
50
|
All matchers include:
|
42
51
|
- Complete parameter descriptions
|
@@ -427,15 +436,15 @@ expect(SimpleTask).not_to have_middleware(ComplexMiddleware)
|
|
427
436
|
|
428
437
|
```ruby
|
429
438
|
# Test setting presence
|
430
|
-
expect(ConfiguredTask).to
|
431
|
-
expect(CustomTask).to
|
439
|
+
expect(ConfiguredTask).to have_cmd_setting(:timeout)
|
440
|
+
expect(CustomTask).to have_cmd_setting(:priority)
|
432
441
|
|
433
442
|
# Test setting with specific value
|
434
|
-
expect(TimedTask).to
|
435
|
-
expect(PriorityTask).to
|
443
|
+
expect(TimedTask).to have_cmd_setting(:timeout, 30)
|
444
|
+
expect(PriorityTask).to have_cmd_setting(:priority, "high")
|
436
445
|
|
437
446
|
# Negated usage
|
438
|
-
expect(SimpleTask).not_to
|
447
|
+
expect(SimpleTask).not_to have_cmd_setting(:complex_setting)
|
439
448
|
```
|
440
449
|
|
441
450
|
## Composable Testing
|
@@ -546,5 +555,5 @@ end
|
|
546
555
|
|
547
556
|
---
|
548
557
|
|
549
|
-
- **Prev:** [
|
558
|
+
- **Prev:** [Internationalization (i18n)](internationalization.md)
|
550
559
|
- **Next:** [AI Prompts](ai_prompts.md)
|
data/docs/tips_and_tricks.md
CHANGED
@@ -4,6 +4,7 @@ This guide covers advanced patterns and optimization techniques for getting the
|
|
4
4
|
|
5
5
|
## Table of Contents
|
6
6
|
|
7
|
+
- [TLDR](#tldr)
|
7
8
|
- [Project Organization](#project-organization)
|
8
9
|
- [Directory Structure](#directory-structure)
|
9
10
|
- [Naming Conventions](#naming-conventions)
|
@@ -12,6 +13,14 @@ This guide covers advanced patterns and optimization techniques for getting the
|
|
12
13
|
- [Monitoring and Observability](#monitoring-and-observability)
|
13
14
|
- [ActiveRecord Query Tagging](#activerecord-query-tagging)
|
14
15
|
|
16
|
+
## TLDR
|
17
|
+
|
18
|
+
- **Organization** - Group commands by domain in `/app/commands` with descriptive subdirectories
|
19
|
+
- **Naming** - Tasks use "Verb + Noun + Task", workflows use "Noun + Verb + Workflow"
|
20
|
+
- **Parameter optimization** - Use `with_options` to reduce duplication in parameter definitions
|
21
|
+
- **Monitoring** - Enable ActiveRecord query tagging for better debugging and observability
|
22
|
+
- **Base classes** - Create `ApplicationTask` and `ApplicationWorkflow` for shared configuration
|
23
|
+
|
15
24
|
## Project Organization
|
16
25
|
|
17
26
|
### Directory Structure
|
data/docs/workflows.md
CHANGED
@@ -6,6 +6,7 @@ Workflows inherit from Task, gaining all task capabilities including callbacks,
|
|
6
6
|
|
7
7
|
## Table of Contents
|
8
8
|
|
9
|
+
- [TLDR](#tldr)
|
9
10
|
- [Basic Usage](#basic-usage)
|
10
11
|
- [Task Declaration](#task-declaration)
|
11
12
|
- [Context Propagation](#context-propagation)
|
@@ -21,6 +22,15 @@ Workflows inherit from Task, gaining all task capabilities including callbacks,
|
|
21
22
|
- [Task Settings Integration](#task-settings-integration)
|
22
23
|
- [Generator](#generator)
|
23
24
|
|
25
|
+
## TLDR
|
26
|
+
|
27
|
+
- **Purpose** - Orchestrate sequential execution of multiple tasks in linear pipeline
|
28
|
+
- **Declaration** - Use `process` method to declare tasks in execution order
|
29
|
+
- **Context sharing** - Context object shared across all tasks for data pipeline
|
30
|
+
- **Conditional execution** - Support `:if` and `:unless` options for conditional tasks
|
31
|
+
- **Halt behavior** - Configurable stopping on failed/skipped results (default: halt on failed only)
|
32
|
+
- **No call method** - Workflows automatically provide execution logic, don't define `call`
|
33
|
+
|
24
34
|
## Basic Usage
|
25
35
|
|
26
36
|
> [!WARNING]
|
@@ -143,12 +153,12 @@ end
|
|
143
153
|
|
144
154
|
### Class-Level Configuration
|
145
155
|
|
146
|
-
Configure halt behavior for the entire workflow using `
|
156
|
+
Configure halt behavior for the entire workflow using `cmd_settings!`:
|
147
157
|
|
148
158
|
```ruby
|
149
159
|
class CriticalDataProcessingWorkflow < CMDx::Workflow
|
150
160
|
# Halt on both failed and skipped results
|
151
|
-
|
161
|
+
cmd_settings!(workflow_halt: [CMDx::Result::FAILED, CMDx::Result::SKIPPED])
|
152
162
|
|
153
163
|
process LoadCriticalDataTask
|
154
164
|
process ValidateCriticalDataTask
|
@@ -156,7 +166,7 @@ end
|
|
156
166
|
|
157
167
|
class OptionalDataProcessingWorkflow < CMDx::Workflow
|
158
168
|
# Never halt, always continue
|
159
|
-
|
169
|
+
cmd_settings!(workflow_halt: [])
|
160
170
|
|
161
171
|
process TryLoadDataTask
|
162
172
|
process TryValidateDataTask
|
@@ -264,7 +274,7 @@ Workflows support all task settings and can be configured like regular tasks:
|
|
264
274
|
```ruby
|
265
275
|
class PaymentProcessingWorkflow < CMDx::Workflow
|
266
276
|
# Configure workflow-specific settings
|
267
|
-
|
277
|
+
cmd_settings!(
|
268
278
|
workflow_halt: [CMDx::Result::FAILED],
|
269
279
|
log_level: :debug,
|
270
280
|
tags: [:critical, :payment]
|
data/lib/cmdx/.DS_Store
CHANGED
Binary file
|
data/lib/cmdx/callback.rb
CHANGED
@@ -1,67 +1,47 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module CMDx
|
4
|
-
|
5
|
-
# Base class for CMDx callbacks that provides lifecycle execution points.
|
4
|
+
# Base class for implementing callback functionality in task execution.
|
6
5
|
#
|
7
|
-
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
#
|
11
|
-
#
|
12
|
-
# @example Basic callback implementation
|
13
|
-
# class LoggingCallback < CMDx::Callback
|
14
|
-
# def call(task, callback_type)
|
15
|
-
# puts "Executing #{callback_type} callback for #{task.class.name}"
|
16
|
-
# task.logger.info("Callback executed: #{callback_type}")
|
17
|
-
# end
|
18
|
-
# end
|
19
|
-
#
|
20
|
-
# @example Callback with initialization parameters
|
21
|
-
# class NotificationCallback < CMDx::Callback
|
22
|
-
# def initialize(channels)
|
23
|
-
# @channels = channels
|
24
|
-
# end
|
25
|
-
#
|
26
|
-
# def call(task, callback_type)
|
27
|
-
# return unless callback_type == :on_success
|
28
|
-
#
|
29
|
-
# @channels.each do |channel|
|
30
|
-
# NotificationService.send(channel, "Task #{task.class.name} completed")
|
31
|
-
# end
|
32
|
-
# end
|
33
|
-
# end
|
34
|
-
#
|
35
|
-
# @example Conditional callback execution
|
36
|
-
# class ErrorReportingCallback < CMDx::Callback
|
37
|
-
# def call(task, callback_type)
|
38
|
-
# return unless callback_type == :on_failure
|
39
|
-
# return unless task.result.failed?
|
40
|
-
#
|
41
|
-
# ErrorReporter.notify(
|
42
|
-
# task.errors.full_messages.join(", "),
|
43
|
-
# context: task.context.to_h
|
44
|
-
# )
|
45
|
-
# end
|
46
|
-
# end
|
47
|
-
#
|
48
|
-
# @see CallbackRegistry Callback management
|
49
|
-
# @see Task Callback integration
|
50
|
-
# @since 1.0.0
|
6
|
+
# Callbacks are executed at specific points during task lifecycle to
|
7
|
+
# provide hooks for custom behavior, logging, validation, or cleanup.
|
8
|
+
# All callback implementations must inherit from this class and implement
|
9
|
+
# the abstract call method.
|
51
10
|
class Callback
|
52
11
|
|
53
|
-
|
54
|
-
#
|
12
|
+
# Executes a callback by creating a new instance and calling it.
|
13
|
+
#
|
14
|
+
# @param task [Task] the task instance executing the callback
|
15
|
+
# @param type [Symbol] the callback type identifier
|
16
|
+
#
|
17
|
+
# @return [Object] the result of the callback execution
|
18
|
+
#
|
19
|
+
# @raise [UndefinedCallError] when the callback subclass doesn't implement call
|
20
|
+
#
|
21
|
+
# @example Execute a callback on a task
|
22
|
+
# MyCallback.call(task, :before)
|
23
|
+
def self.call(task, type)
|
24
|
+
new.call(task, type)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Abstract method that must be implemented by callback subclasses.
|
28
|
+
#
|
29
|
+
# This method contains the actual callback logic to be executed.
|
30
|
+
# Subclasses must override this method to provide their specific
|
31
|
+
# callback implementation.
|
32
|
+
#
|
33
|
+
# @param _task [Task] the task instance executing the callback
|
34
|
+
# @param _type [Symbol] the callback type identifier
|
35
|
+
#
|
36
|
+
# @return [Object] the result of the callback execution
|
55
37
|
#
|
56
|
-
#
|
57
|
-
# behavior. The method receives the task instance and the callback type
|
58
|
-
# being executed.
|
38
|
+
# @raise [UndefinedCallError] always raised in the base class
|
59
39
|
#
|
60
|
-
# @
|
61
|
-
#
|
62
|
-
#
|
63
|
-
#
|
64
|
-
def call(_task,
|
40
|
+
# @example Implement in a subclass
|
41
|
+
# def call(task, type)
|
42
|
+
# puts "Executing #{type} callback for #{task.class.name}"
|
43
|
+
# end
|
44
|
+
def call(_task, _type)
|
65
45
|
raise UndefinedCallError, "call method not defined in #{self.class.name}"
|
66
46
|
end
|
67
47
|
|
@@ -1,106 +1,115 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module CMDx
|
4
|
-
|
5
|
-
# The CallbackRegistry collection provides a lifecycle callback system that executes
|
6
|
-
# registered callbacks at specific points during task execution. Callbacks can be
|
7
|
-
# conditionally executed based on task state and support both method references
|
8
|
-
# and callable objects.
|
4
|
+
# Registry for managing callback definitions and execution within tasks.
|
9
5
|
#
|
10
|
-
#
|
11
|
-
#
|
12
|
-
#
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
# callback_registry.each { |callback_name, callbacks| puts "#{callback_name}: #{callbacks}" }
|
27
|
-
#
|
28
|
-
# @see Callback Base callback execution class
|
29
|
-
# @see Task Task lifecycle callbacks
|
30
|
-
# @since 1.0.0
|
31
|
-
class CallbackRegistry < Hash
|
6
|
+
# This registry handles the registration and execution of callbacks at various
|
7
|
+
# points in the task lifecycle, including validation, execution, and outcome
|
8
|
+
# handling phases.
|
9
|
+
class CallbackRegistry
|
10
|
+
|
11
|
+
TYPES = [
|
12
|
+
:before_validation,
|
13
|
+
:after_validation,
|
14
|
+
:before_execution,
|
15
|
+
:after_execution,
|
16
|
+
:on_executed,
|
17
|
+
:on_good,
|
18
|
+
:on_bad,
|
19
|
+
*Result::STATUSES.map { |s| :"on_#{s}" },
|
20
|
+
*Result::STATES.map { |s| :"on_#{s}" }
|
21
|
+
].freeze
|
32
22
|
|
33
|
-
|
34
|
-
# Initializes a new CallbackRegistry.
|
23
|
+
# The internal hash storing callback definitions.
|
35
24
|
#
|
36
|
-
# @
|
25
|
+
# @return [Hash] hash containing callback type keys and callback definition arrays
|
26
|
+
attr_reader :registry
|
27
|
+
|
28
|
+
# Initializes a new callback registry.
|
37
29
|
#
|
38
|
-
# @
|
39
|
-
# registry = CallbackRegistry.new
|
30
|
+
# @param registry [Hash] initial registry hash with callback definitions
|
40
31
|
#
|
41
|
-
# @
|
42
|
-
#
|
43
|
-
#
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
32
|
+
# @return [CallbackRegistry] a new callback registry instance
|
33
|
+
#
|
34
|
+
# @example Creating an empty registry
|
35
|
+
# CallbackRegistry.new
|
36
|
+
#
|
37
|
+
# @example Creating a registry with initial callbacks
|
38
|
+
# CallbackRegistry.new(before_execution: [[:my_callback, {}]])
|
39
|
+
def initialize(registry = {})
|
40
|
+
@registry = registry.to_h
|
50
41
|
end
|
51
42
|
|
52
|
-
# Registers
|
43
|
+
# Registers one or more callbacks for a specific type.
|
53
44
|
#
|
54
|
-
# @param
|
55
|
-
# @param callables [Array<
|
56
|
-
# @param options [Hash]
|
57
|
-
# @
|
58
|
-
# @option options [Symbol, Proc, #call] :unless condition that must be falsy
|
59
|
-
# @param block [Proc] Block to execute as part of the callback
|
60
|
-
# @return [CallbackRegistry] self for method chaining
|
45
|
+
# @param type [Symbol] the callback type to register
|
46
|
+
# @param callables [Array<Object>] callable objects to register
|
47
|
+
# @param options [Hash] options for conditional callback execution
|
48
|
+
# @param block [Proc] optional block to register as a callback
|
61
49
|
#
|
62
|
-
# @
|
63
|
-
# registry.register(:before_validation, :check_permissions)
|
50
|
+
# @return [CallbackRegistry] returns self for method chaining
|
64
51
|
#
|
65
|
-
# @example
|
66
|
-
# registry.register(:
|
52
|
+
# @example Registering a symbol callback
|
53
|
+
# registry.register(:before_execution, :setup_database)
|
67
54
|
#
|
68
|
-
# @example
|
69
|
-
# registry.register(:
|
70
|
-
|
55
|
+
# @example Registering a Proc callback
|
56
|
+
# registry.register(:on_good, ->(task) { puts "Task completed: #{task.name}" })
|
57
|
+
#
|
58
|
+
# @example Registering a Callback class
|
59
|
+
# registry.register(:after_validation, NotificationCallback)
|
60
|
+
#
|
61
|
+
# @example Registering multiple callbacks with options
|
62
|
+
# registry.register(:on_good, :send_notification, :log_success, if: -> { Rails.env.production? })
|
63
|
+
#
|
64
|
+
# @example Registering a block callback
|
65
|
+
# registry.register(:after_validation) { |task| puts "Validation complete" }
|
66
|
+
def register(type, *callables, **options, &block)
|
71
67
|
callables << block if block_given?
|
72
|
-
(
|
68
|
+
(registry[type] ||= []).push([callables, options]).uniq!
|
73
69
|
self
|
74
70
|
end
|
75
71
|
|
76
|
-
# Executes all callbacks
|
77
|
-
#
|
72
|
+
# Executes all registered callbacks for a specific type.
|
73
|
+
#
|
74
|
+
# @param task [Task] the task instance to execute callbacks on
|
75
|
+
# @param type [Symbol] the callback type to execute
|
78
76
|
#
|
79
|
-
# @param task [Task] The task instance to execute callbacks on
|
80
|
-
# @param callback [Symbol] The callback type to execute (e.g., :before_validation, :on_success)
|
81
77
|
# @return [void]
|
82
78
|
#
|
83
|
-
# @
|
79
|
+
# @raise [UnknownCallbackError] when the callback type is not recognized
|
80
|
+
#
|
81
|
+
# @example Executing before_validation callbacks
|
84
82
|
# registry.call(task, :before_validation)
|
85
83
|
#
|
86
|
-
# @example
|
87
|
-
#
|
88
|
-
|
89
|
-
|
90
|
-
return unless key?(callback)
|
84
|
+
# @example Executing outcome callbacks
|
85
|
+
# registry.call(task, :on_good)
|
86
|
+
def call(task, type)
|
87
|
+
raise UnknownCallbackError, "unknown callback #{type}" unless TYPES.include?(type)
|
91
88
|
|
92
|
-
Array(
|
93
|
-
next unless task.
|
89
|
+
Array(registry[type]).each do |callables, options|
|
90
|
+
next unless task.cmdx_eval(options)
|
94
91
|
|
95
|
-
Array(callables).each do |
|
96
|
-
|
97
|
-
|
92
|
+
Array(callables).each do |callable|
|
93
|
+
case callable
|
94
|
+
when Symbol, String, Proc
|
95
|
+
task.cmdx_try(callable)
|
98
96
|
else
|
99
|
-
|
97
|
+
callable.call(task)
|
100
98
|
end
|
101
99
|
end
|
102
100
|
end
|
103
101
|
end
|
104
102
|
|
103
|
+
# Returns a hash representation of the registry.
|
104
|
+
#
|
105
|
+
# @return [Hash] a deep copy of the registry hash
|
106
|
+
#
|
107
|
+
# @example Getting registry contents
|
108
|
+
# registry.to_h
|
109
|
+
# # => { before_execution: [[:setup, {}]], on_good: [[:notify, { if: -> { true } }]] }
|
110
|
+
def to_h
|
111
|
+
registry.transform_values(&:dup)
|
112
|
+
end
|
113
|
+
|
105
114
|
end
|
106
115
|
end
|