cmdx 1.9.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.
- checksums.yaml +4 -4
- data/.cursor/prompts/yardoc.md +1 -0
- data/CHANGELOG.md +6 -0
- data/LLM.md +9 -0
- data/README.md +6 -1
- data/docs/getting_started.md +9 -0
- data/docs/index.md +13 -1
- data/lib/cmdx/attribute.rb +82 -1
- data/lib/cmdx/attribute_registry.rb +20 -0
- data/lib/cmdx/attribute_value.rb +25 -0
- data/lib/cmdx/callback_registry.rb +19 -0
- data/lib/cmdx/chain.rb +34 -1
- data/lib/cmdx/coercion_registry.rb +18 -0
- data/lib/cmdx/coercions/array.rb +2 -0
- data/lib/cmdx/coercions/big_decimal.rb +3 -0
- data/lib/cmdx/coercions/boolean.rb +5 -0
- data/lib/cmdx/coercions/complex.rb +2 -0
- data/lib/cmdx/coercions/date.rb +4 -0
- data/lib/cmdx/coercions/date_time.rb +5 -0
- data/lib/cmdx/coercions/float.rb +2 -0
- data/lib/cmdx/coercions/hash.rb +2 -0
- data/lib/cmdx/coercions/integer.rb +2 -0
- data/lib/cmdx/coercions/rational.rb +2 -0
- data/lib/cmdx/coercions/string.rb +2 -0
- data/lib/cmdx/coercions/symbol.rb +2 -0
- data/lib/cmdx/coercions/time.rb +5 -0
- data/lib/cmdx/configuration.rb +111 -3
- data/lib/cmdx/context.rb +36 -0
- data/lib/cmdx/deprecator.rb +3 -0
- data/lib/cmdx/errors.rb +22 -0
- data/lib/cmdx/executor.rb +43 -0
- data/lib/cmdx/faults.rb +14 -0
- data/lib/cmdx/identifier.rb +2 -0
- data/lib/cmdx/locale.rb +3 -0
- data/lib/cmdx/log_formatters/json.rb +2 -0
- data/lib/cmdx/log_formatters/key_value.rb +2 -0
- data/lib/cmdx/log_formatters/line.rb +2 -0
- data/lib/cmdx/log_formatters/logstash.rb +2 -0
- data/lib/cmdx/log_formatters/raw.rb +2 -0
- data/lib/cmdx/middleware_registry.rb +20 -0
- data/lib/cmdx/middlewares/correlate.rb +11 -0
- data/lib/cmdx/middlewares/runtime.rb +4 -0
- data/lib/cmdx/middlewares/timeout.rb +4 -0
- data/lib/cmdx/pipeline.rb +20 -1
- data/lib/cmdx/railtie.rb +4 -0
- data/lib/cmdx/result.rb +123 -1
- data/lib/cmdx/task.rb +91 -1
- data/lib/cmdx/utils/call.rb +2 -0
- data/lib/cmdx/utils/condition.rb +3 -0
- data/lib/cmdx/utils/format.rb +5 -0
- data/lib/cmdx/validator_registry.rb +18 -0
- data/lib/cmdx/validators/exclusion.rb +2 -0
- data/lib/cmdx/validators/format.rb +2 -0
- data/lib/cmdx/validators/inclusion.rb +2 -0
- data/lib/cmdx/validators/length.rb +14 -0
- data/lib/cmdx/validators/numeric.rb +14 -0
- data/lib/cmdx/validators/presence.rb +2 -0
- data/lib/cmdx/version.rb +4 -1
- data/lib/cmdx/workflow.rb +10 -0
- data/lib/cmdx.rb +8 -0
- data/lib/generators/cmdx/locale_generator.rb +0 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9220688eed061c4c48562665fe9c2c780a2ecc08642eeaedb095d6bfc312e643
|
|
4
|
+
data.tar.gz: f470f2ccebce524942277865f2967979244dde07ba1a43095c7f95209127c635
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 155e2d09a4ca6147a7df578b0f2952ab81e781d5c492d821a4b71b08b41ef510cfccade001cc2e593f08ea1876ba6e5b658595a2bddaf6919f243c8df07d0569
|
|
7
|
+
data.tar.gz: fd0dba94495d6d139dc13507169143ee83f13dfac521d6ef607800c6d6e4cfd13e47cc03e2f27e047b185a0b9ebfc0a9d080143f7c6c2ec61aec139f38ea17cd
|
data/.cursor/prompts/yardoc.md
CHANGED
|
@@ -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/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
|
6
6
|
|
|
7
7
|
## [UNRELEASED]
|
|
8
8
|
|
|
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
|
+
|
|
9
15
|
## [1.9.0] - 2025-10-21
|
|
10
16
|
|
|
11
17
|
### Added
|
data/LLM.md
CHANGED
|
@@ -372,6 +372,15 @@ end
|
|
|
372
372
|
> [!TIP]
|
|
373
373
|
> Use **present tense verbs + noun** for task names, eg: `ModerateBlogPost`, `ScheduleAppointment`, `ValidateDocument`
|
|
374
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
|
+
|
|
375
384
|
---
|
|
376
385
|
|
|
377
386
|
url: https://github.com/drexed/cmdx/blob/main/docs/basics/setup.md
|
data/README.md
CHANGED
|
@@ -17,6 +17,9 @@
|
|
|
17
17
|
|
|
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
|
+
> [!NOTE]
|
|
21
|
+
> Documentation reflects the latest code on `main`. For version-specific documentation, please refer to the `docs/` directory within that version's tag.
|
|
22
|
+
|
|
20
23
|
## Requirements
|
|
21
24
|
|
|
22
25
|
- Ruby: MRI 3.1+ or JRuby 9.4+.
|
|
@@ -105,6 +108,8 @@ I, [2022-07-17T18:43:15.000000 #3784] INFO -- CMDx:
|
|
|
105
108
|
index=0 chain_id="018c2b95-b764-7615-a924-cc5b910ed1e5" type="Task" class="AnalyzeMetrics" state="complete" status="success" metadata={runtime: 187}
|
|
106
109
|
```
|
|
107
110
|
|
|
111
|
+
Ready to dive in? Check out the [Getting Started](https://drexed.github.io/cmdx/getting_started/) guide to learn more.
|
|
112
|
+
|
|
108
113
|
## Ecosystem
|
|
109
114
|
|
|
110
115
|
- [cmdx-rspec](https://github.com/drexed/cmdx-rspec) - RSpec test matchers
|
|
@@ -116,7 +121,7 @@ For backwards compatibility of certain functionality:
|
|
|
116
121
|
|
|
117
122
|
## Contributing
|
|
118
123
|
|
|
119
|
-
Bug reports and pull requests are welcome at https://github.com/drexed/cmdx
|
|
124
|
+
Bug reports and pull requests are welcome at <https://github.com/drexed/cmdx>. We're committed to fostering a welcoming, collaborative community. Please follow our [code of conduct](CODE_OF_CONDUCT.md).
|
|
120
125
|
|
|
121
126
|
## License
|
|
122
127
|
|
data/docs/getting_started.md
CHANGED
|
@@ -367,3 +367,12 @@ end
|
|
|
367
367
|
!!! tip
|
|
368
368
|
|
|
369
369
|
Use **present tense verbs + noun** for task names, eg: `ModerateBlogPost`, `ScheduleAppointment`, `ValidateDocument`
|
|
370
|
+
|
|
371
|
+
## Type safety
|
|
372
|
+
|
|
373
|
+
CMDx includes built-in RBS (Ruby Type Signature) inline annotations throughout the codebase, providing type information for static analysis and editor support.
|
|
374
|
+
|
|
375
|
+
- **Type checking** — Catch type errors before runtime using tools like Steep or TypeProf
|
|
376
|
+
- **Better IDE support** — Enhanced autocomplete, navigation, and inline documentation
|
|
377
|
+
- **Self-documenting code** — Clear method signatures and return types
|
|
378
|
+
- **Refactoring confidence** — Type-aware refactoring reduces bugs
|
data/docs/index.md
CHANGED
|
@@ -6,8 +6,20 @@ Build business logic that's powerful, predictable, and maintainable.
|
|
|
6
6
|
[](https://github.com/drexed/cmdx/actions/workflows/ci.yml)
|
|
7
7
|
[](https://github.com/drexed/cmdx/blob/main/LICENSE.txt)
|
|
8
8
|
|
|
9
|
+
---
|
|
10
|
+
|
|
9
11
|
Say goodbye to messy service objects. CMDx helps you design business logic with clarity and consistency—build faster, debug easier, and ship with confidence.
|
|
10
12
|
|
|
13
|
+
!!! note
|
|
14
|
+
|
|
15
|
+
Documentation reflects the latest code on `main`. For version-specific documentation, please refer to the `docs/` directory within that version's tag.
|
|
16
|
+
|
|
17
|
+
## Requirements
|
|
18
|
+
|
|
19
|
+
- Ruby: MRI 3.1+ or JRuby 9.4+.
|
|
20
|
+
|
|
21
|
+
CMDx works with any Ruby framework. Rails support is built-in, but it's framework-agnostic at its core.
|
|
22
|
+
|
|
11
23
|
## Installation
|
|
12
24
|
|
|
13
25
|
```sh
|
|
@@ -113,7 +125,7 @@ For backwards compatibility of certain functionality:
|
|
|
113
125
|
|
|
114
126
|
## Contributing
|
|
115
127
|
|
|
116
|
-
Bug reports and pull requests are welcome at https://github.com/drexed/cmdx
|
|
128
|
+
Bug reports and pull requests are welcome at <https://github.com/drexed/cmdx>. We're committed to fostering a welcoming, collaborative community. Please follow our [code of conduct](CODE_OF_CONDUCT.md).
|
|
117
129
|
|
|
118
130
|
## License
|
|
119
131
|
|
data/lib/cmdx/attribute.rb
CHANGED
|
@@ -6,14 +6,71 @@ module CMDx
|
|
|
6
6
|
# They can be nested to create complex hierarchical data structures.
|
|
7
7
|
class Attribute
|
|
8
8
|
|
|
9
|
+
# @rbs AFFIX: Proc
|
|
9
10
|
AFFIX = proc do |value, &block|
|
|
10
11
|
value == true ? block.call : value
|
|
11
12
|
end.freeze
|
|
12
13
|
private_constant :AFFIX
|
|
13
14
|
|
|
15
|
+
# Returns the task instance associated with this attribute.
|
|
16
|
+
#
|
|
17
|
+
# @return [CMDx::Task] The task instance
|
|
18
|
+
#
|
|
19
|
+
# @example
|
|
20
|
+
# attribute.task.context[:user_id] # => 42
|
|
21
|
+
#
|
|
22
|
+
# @rbs @task: Task
|
|
14
23
|
attr_accessor :task
|
|
15
24
|
|
|
16
|
-
|
|
25
|
+
# Returns the name of this attribute.
|
|
26
|
+
#
|
|
27
|
+
# @return [Symbol] The attribute name
|
|
28
|
+
#
|
|
29
|
+
# @example
|
|
30
|
+
# attribute.name # => :user_id
|
|
31
|
+
#
|
|
32
|
+
# @rbs @name: Symbol
|
|
33
|
+
attr_reader :name
|
|
34
|
+
|
|
35
|
+
# Returns the configuration options for this attribute.
|
|
36
|
+
#
|
|
37
|
+
# @return [Hash{Symbol => Object}] Configuration options hash
|
|
38
|
+
#
|
|
39
|
+
# @example
|
|
40
|
+
# attribute.options # => { required: true, default: 0 }
|
|
41
|
+
#
|
|
42
|
+
# @rbs @options: Hash[Symbol, untyped]
|
|
43
|
+
attr_reader :options
|
|
44
|
+
|
|
45
|
+
# Returns the child attributes for nested structures.
|
|
46
|
+
#
|
|
47
|
+
# @return [Array<Attribute>] Array of child attributes
|
|
48
|
+
#
|
|
49
|
+
# @example
|
|
50
|
+
# attribute.children # => [#<Attribute @name=:street>, #<Attribute @name=:city>]
|
|
51
|
+
#
|
|
52
|
+
# @rbs @children: Array[Attribute]
|
|
53
|
+
attr_reader :children
|
|
54
|
+
|
|
55
|
+
# Returns the parent attribute if this is a nested attribute.
|
|
56
|
+
#
|
|
57
|
+
# @return [Attribute, nil] The parent attribute, or nil if root-level
|
|
58
|
+
#
|
|
59
|
+
# @example
|
|
60
|
+
# attribute.parent # => #<Attribute @name=:address>
|
|
61
|
+
#
|
|
62
|
+
# @rbs @parent: (Attribute | nil)
|
|
63
|
+
attr_reader :parent
|
|
64
|
+
|
|
65
|
+
# Returns the expected type(s) for this attribute's value.
|
|
66
|
+
#
|
|
67
|
+
# @return [Array<Class>] Array of expected type classes
|
|
68
|
+
#
|
|
69
|
+
# @example
|
|
70
|
+
# attribute.types # => [Integer, String]
|
|
71
|
+
#
|
|
72
|
+
# @rbs @types: Array[Class]
|
|
73
|
+
attr_reader :types
|
|
17
74
|
|
|
18
75
|
# Creates a new attribute with the specified name and configuration.
|
|
19
76
|
#
|
|
@@ -35,6 +92,8 @@ module CMDx
|
|
|
35
92
|
# required :name, types: String
|
|
36
93
|
# optional :email, types: String
|
|
37
94
|
# end
|
|
95
|
+
#
|
|
96
|
+
# @rbs ((Symbol | String) name, ?Hash[Symbol, untyped] options) ?{ () -> void } -> void
|
|
38
97
|
def initialize(name, options = {}, &)
|
|
39
98
|
@parent = options.delete(:parent)
|
|
40
99
|
@required = options.delete(:required) || false
|
|
@@ -62,6 +121,8 @@ module CMDx
|
|
|
62
121
|
#
|
|
63
122
|
# @example
|
|
64
123
|
# Attribute.build(:first_name, :last_name, required: true, types: String)
|
|
124
|
+
#
|
|
125
|
+
# @rbs (*untyped names, **untyped options) ?{ () -> void } -> Array[Attribute]
|
|
65
126
|
def build(*names, **options, &)
|
|
66
127
|
if names.none?
|
|
67
128
|
raise ArgumentError, "no attributes given"
|
|
@@ -83,6 +144,8 @@ module CMDx
|
|
|
83
144
|
#
|
|
84
145
|
# @example
|
|
85
146
|
# Attribute.optional(:description, :tags, types: String)
|
|
147
|
+
#
|
|
148
|
+
# @rbs (*untyped names, **untyped options) ?{ () -> void } -> Array[Attribute]
|
|
86
149
|
def optional(*names, **options, &)
|
|
87
150
|
build(*names, **options.merge(required: false), &)
|
|
88
151
|
end
|
|
@@ -98,6 +161,8 @@ module CMDx
|
|
|
98
161
|
#
|
|
99
162
|
# @example
|
|
100
163
|
# Attribute.required(:id, :name, types: [Integer, String])
|
|
164
|
+
#
|
|
165
|
+
# @rbs (*untyped names, **untyped options) ?{ () -> void } -> Array[Attribute]
|
|
101
166
|
def required(*names, **options, &)
|
|
102
167
|
build(*names, **options.merge(required: true), &)
|
|
103
168
|
end
|
|
@@ -110,6 +175,8 @@ module CMDx
|
|
|
110
175
|
#
|
|
111
176
|
# @example
|
|
112
177
|
# attribute.required? # => true
|
|
178
|
+
#
|
|
179
|
+
# @rbs () -> bool
|
|
113
180
|
def required?
|
|
114
181
|
!!@required
|
|
115
182
|
end
|
|
@@ -120,6 +187,8 @@ module CMDx
|
|
|
120
187
|
#
|
|
121
188
|
# @example
|
|
122
189
|
# attribute.source # => :context
|
|
190
|
+
#
|
|
191
|
+
# @rbs () -> untyped
|
|
123
192
|
def source
|
|
124
193
|
@source ||= parent&.method_name || begin
|
|
125
194
|
value = options[:source]
|
|
@@ -140,6 +209,8 @@ module CMDx
|
|
|
140
209
|
#
|
|
141
210
|
# @example
|
|
142
211
|
# attribute.method_name # => :user_name
|
|
212
|
+
#
|
|
213
|
+
# @rbs () -> Symbol
|
|
143
214
|
def method_name
|
|
144
215
|
@method_name ||= options[:as] || begin
|
|
145
216
|
prefix = AFFIX.call(options[:prefix]) { "#{source}_" }
|
|
@@ -150,6 +221,8 @@ module CMDx
|
|
|
150
221
|
end
|
|
151
222
|
|
|
152
223
|
# Defines and verifies the entire attribute tree including nested children.
|
|
224
|
+
#
|
|
225
|
+
# @rbs () -> void
|
|
153
226
|
def define_and_verify_tree
|
|
154
227
|
define_and_verify
|
|
155
228
|
|
|
@@ -172,6 +245,8 @@ module CMDx
|
|
|
172
245
|
#
|
|
173
246
|
# @example
|
|
174
247
|
# attributes :street, :city, :zip, types: String
|
|
248
|
+
#
|
|
249
|
+
# @rbs (*untyped names, **untyped options) ?{ () -> void } -> Array[Attribute]
|
|
175
250
|
def attributes(*names, **options, &)
|
|
176
251
|
attrs = self.class.build(*names, **options.merge(parent: self), &)
|
|
177
252
|
children.concat(attrs)
|
|
@@ -189,6 +264,8 @@ module CMDx
|
|
|
189
264
|
#
|
|
190
265
|
# @example
|
|
191
266
|
# optional :middle_name, :nickname, types: String
|
|
267
|
+
#
|
|
268
|
+
# @rbs (*untyped names, **untyped options) ?{ () -> void } -> Array[Attribute]
|
|
192
269
|
def optional(*names, **options, &)
|
|
193
270
|
attributes(*names, **options.merge(required: false), &)
|
|
194
271
|
end
|
|
@@ -204,6 +281,8 @@ module CMDx
|
|
|
204
281
|
#
|
|
205
282
|
# @example
|
|
206
283
|
# required :first_name, :last_name, types: String
|
|
284
|
+
#
|
|
285
|
+
# @rbs (*untyped names, **untyped options) ?{ () -> void } -> Array[Attribute]
|
|
207
286
|
def required(*names, **options, &)
|
|
208
287
|
attributes(*names, **options.merge(required: true), &)
|
|
209
288
|
end
|
|
@@ -211,6 +290,8 @@ module CMDx
|
|
|
211
290
|
# Defines the attribute method on the task and validates the configuration.
|
|
212
291
|
#
|
|
213
292
|
# @raise [RuntimeError] When the method name is already defined on the task
|
|
293
|
+
#
|
|
294
|
+
# @rbs () -> void
|
|
214
295
|
def define_and_verify
|
|
215
296
|
if task.respond_to?(method_name, true)
|
|
216
297
|
raise <<~MESSAGE
|
|
@@ -6,6 +6,14 @@ module CMDx
|
|
|
6
6
|
# in a hierarchical structure, supporting nested attribute definitions.
|
|
7
7
|
class AttributeRegistry
|
|
8
8
|
|
|
9
|
+
# Returns the collection of registered attributes.
|
|
10
|
+
#
|
|
11
|
+
# @return [Array<Attribute>] Array of registered attributes
|
|
12
|
+
#
|
|
13
|
+
# @example
|
|
14
|
+
# registry.registry # => [#<Attribute @name=:name>, #<Attribute @name=:email>]
|
|
15
|
+
#
|
|
16
|
+
# @rbs @registry: Array[Attribute]
|
|
9
17
|
attr_reader :registry
|
|
10
18
|
alias to_a registry
|
|
11
19
|
|
|
@@ -18,6 +26,8 @@ module CMDx
|
|
|
18
26
|
# @example
|
|
19
27
|
# registry = AttributeRegistry.new
|
|
20
28
|
# registry = AttributeRegistry.new([attr1, attr2])
|
|
29
|
+
#
|
|
30
|
+
# @rbs (?Array[Attribute] registry) -> void
|
|
21
31
|
def initialize(registry = [])
|
|
22
32
|
@registry = registry
|
|
23
33
|
end
|
|
@@ -28,6 +38,8 @@ module CMDx
|
|
|
28
38
|
#
|
|
29
39
|
# @example
|
|
30
40
|
# new_registry = registry.dup
|
|
41
|
+
#
|
|
42
|
+
# @rbs () -> AttributeRegistry
|
|
31
43
|
def dup
|
|
32
44
|
self.class.new(registry.dup)
|
|
33
45
|
end
|
|
@@ -41,6 +53,8 @@ module CMDx
|
|
|
41
53
|
# @example
|
|
42
54
|
# registry.register(attribute)
|
|
43
55
|
# registry.register([attr1, attr2])
|
|
56
|
+
#
|
|
57
|
+
# @rbs (Attribute | Array[Attribute] attributes) -> self
|
|
44
58
|
def register(attributes)
|
|
45
59
|
@registry.concat(Array(attributes))
|
|
46
60
|
self
|
|
@@ -56,6 +70,8 @@ module CMDx
|
|
|
56
70
|
# @example
|
|
57
71
|
# registry.deregister(:name)
|
|
58
72
|
# registry.deregister(['name1', 'name2'])
|
|
73
|
+
#
|
|
74
|
+
# @rbs ((Symbol | String | Array[Symbol | String]) names) -> self
|
|
59
75
|
def deregister(names)
|
|
60
76
|
Array(names).each do |name|
|
|
61
77
|
@registry.reject! { |attribute| matches_attribute_tree?(attribute, name.to_sym) }
|
|
@@ -69,6 +85,8 @@ module CMDx
|
|
|
69
85
|
# and validate the attribute hierarchy.
|
|
70
86
|
#
|
|
71
87
|
# @param task [Task] The task to associate with all attributes
|
|
88
|
+
#
|
|
89
|
+
# @rbs (Task task) -> void
|
|
72
90
|
def define_and_verify(task)
|
|
73
91
|
registry.each do |attribute|
|
|
74
92
|
attribute.task = task
|
|
@@ -84,6 +102,8 @@ module CMDx
|
|
|
84
102
|
# @param name [Symbol] The name to match against
|
|
85
103
|
#
|
|
86
104
|
# @return [Boolean] True if the attribute or any child matches the name
|
|
105
|
+
#
|
|
106
|
+
# @rbs (Attribute attribute, Symbol name) -> bool
|
|
87
107
|
def matches_attribute_tree?(attribute, name)
|
|
88
108
|
return true if attribute.method_name == name
|
|
89
109
|
|
data/lib/cmdx/attribute_value.rb
CHANGED
|
@@ -8,6 +8,14 @@ module CMDx
|
|
|
8
8
|
|
|
9
9
|
extend Forwardable
|
|
10
10
|
|
|
11
|
+
# Returns the attribute managed by this value handler.
|
|
12
|
+
#
|
|
13
|
+
# @return [Attribute] The attribute instance
|
|
14
|
+
#
|
|
15
|
+
# @example
|
|
16
|
+
# attr_value.attribute.name # => :user_id
|
|
17
|
+
#
|
|
18
|
+
# @rbs @attribute: Attribute
|
|
11
19
|
attr_reader :attribute
|
|
12
20
|
|
|
13
21
|
def_delegators :attribute, :task, :parent, :name, :options, :types, :source, :method_name, :required?
|
|
@@ -20,6 +28,8 @@ module CMDx
|
|
|
20
28
|
# @example
|
|
21
29
|
# attr = Attribute.new(:user_id, required: true)
|
|
22
30
|
# attr_value = AttributeValue.new(attr)
|
|
31
|
+
#
|
|
32
|
+
# @rbs (Attribute attribute) -> void
|
|
23
33
|
def initialize(attribute)
|
|
24
34
|
@attribute = attribute
|
|
25
35
|
end
|
|
@@ -30,6 +40,8 @@ module CMDx
|
|
|
30
40
|
#
|
|
31
41
|
# @example
|
|
32
42
|
# attr_value.value # => "john_doe"
|
|
43
|
+
#
|
|
44
|
+
# @rbs () -> untyped
|
|
33
45
|
def value
|
|
34
46
|
attributes[method_name]
|
|
35
47
|
end
|
|
@@ -41,6 +53,8 @@ module CMDx
|
|
|
41
53
|
#
|
|
42
54
|
# @example
|
|
43
55
|
# attr_value.generate # => 42
|
|
56
|
+
#
|
|
57
|
+
# @rbs () -> untyped
|
|
44
58
|
def generate
|
|
45
59
|
return value if attributes.key?(method_name)
|
|
46
60
|
|
|
@@ -64,6 +78,8 @@ module CMDx
|
|
|
64
78
|
# @example
|
|
65
79
|
# attr_value.validate
|
|
66
80
|
# # Validates value against :presence, :format, etc.
|
|
81
|
+
#
|
|
82
|
+
# @rbs () -> void
|
|
67
83
|
def validate
|
|
68
84
|
registry = task.class.settings[:validators]
|
|
69
85
|
|
|
@@ -86,6 +102,7 @@ module CMDx
|
|
|
86
102
|
# @example
|
|
87
103
|
# # Sources from task method, proc, or direct value
|
|
88
104
|
# source_value # => "raw_value"
|
|
105
|
+
# @rbs () -> untyped
|
|
89
106
|
def source_value
|
|
90
107
|
sourced_value =
|
|
91
108
|
case source
|
|
@@ -115,6 +132,8 @@ module CMDx
|
|
|
115
132
|
# @example
|
|
116
133
|
# # Default can be symbol, proc, or direct value
|
|
117
134
|
# -> { rand(100) } # => 23
|
|
135
|
+
#
|
|
136
|
+
# @rbs () -> untyped
|
|
118
137
|
def default_value
|
|
119
138
|
default = options[:default]
|
|
120
139
|
|
|
@@ -140,6 +159,8 @@ module CMDx
|
|
|
140
159
|
# @example
|
|
141
160
|
# # Derives from hash key, method call, or proc execution
|
|
142
161
|
# context.user_id # => 42
|
|
162
|
+
#
|
|
163
|
+
# @rbs (untyped source_value) -> untyped
|
|
143
164
|
def derive_value(source_value)
|
|
144
165
|
derived_value =
|
|
145
166
|
case source_value
|
|
@@ -163,6 +184,8 @@ module CMDx
|
|
|
163
184
|
#
|
|
164
185
|
# @example
|
|
165
186
|
# :downcase # => "hello"
|
|
187
|
+
#
|
|
188
|
+
# @rbs (untyped derived_value) -> untyped
|
|
166
189
|
def transform_value(derived_value)
|
|
167
190
|
transform = options[:transform]
|
|
168
191
|
|
|
@@ -186,6 +209,8 @@ module CMDx
|
|
|
186
209
|
# @example
|
|
187
210
|
# # Coerces "42" to Integer, "true" to Boolean, etc.
|
|
188
211
|
# coerce_value("42") # => 42
|
|
212
|
+
#
|
|
213
|
+
# @rbs (untyped transformed_value) -> untyped
|
|
189
214
|
def coerce_value(transformed_value)
|
|
190
215
|
return transformed_value if types.empty?
|
|
191
216
|
|
|
@@ -7,6 +7,7 @@ module CMDx
|
|
|
7
7
|
# Each callback type represents a specific execution phase or outcome.
|
|
8
8
|
class CallbackRegistry
|
|
9
9
|
|
|
10
|
+
# @rbs TYPES: Array[Symbol]
|
|
10
11
|
TYPES = %i[
|
|
11
12
|
before_validation
|
|
12
13
|
before_execution
|
|
@@ -20,10 +21,20 @@ module CMDx
|
|
|
20
21
|
on_bad
|
|
21
22
|
].freeze
|
|
22
23
|
|
|
24
|
+
# Returns the internal registry of callbacks organized by type.
|
|
25
|
+
#
|
|
26
|
+
# @return [Hash{Symbol => Set<Array>}] Hash mapping callback types to their registered callables
|
|
27
|
+
#
|
|
28
|
+
# @example
|
|
29
|
+
# registry.registry # => { before_execution: #<Set: [[[:validate], {}]]> }
|
|
30
|
+
#
|
|
31
|
+
# @rbs @registry: Hash[Symbol, Set[Array[untyped]]]
|
|
23
32
|
attr_reader :registry
|
|
24
33
|
alias to_h registry
|
|
25
34
|
|
|
26
35
|
# @param registry [Hash] Initial registry hash, defaults to empty
|
|
36
|
+
#
|
|
37
|
+
# @rbs (?Hash[Symbol, Set[Array[untyped]]] registry) -> void
|
|
27
38
|
def initialize(registry = {})
|
|
28
39
|
@registry = registry
|
|
29
40
|
end
|
|
@@ -31,6 +42,8 @@ module CMDx
|
|
|
31
42
|
# Creates a deep copy of the registry with duplicated callable sets
|
|
32
43
|
#
|
|
33
44
|
# @return [CallbackRegistry] A new instance with duplicated registry contents
|
|
45
|
+
#
|
|
46
|
+
# @rbs () -> CallbackRegistry
|
|
34
47
|
def dup
|
|
35
48
|
self.class.new(registry.transform_values(&:dup))
|
|
36
49
|
end
|
|
@@ -54,6 +67,8 @@ module CMDx
|
|
|
54
67
|
# registry.register(:on_success, if: { status: :completed }) do |task|
|
|
55
68
|
# task.log("Success callback executed")
|
|
56
69
|
# end
|
|
70
|
+
#
|
|
71
|
+
# @rbs (Symbol type, *untyped callables, **untyped options) ?{ (Task) -> void } -> self
|
|
57
72
|
def register(type, *callables, **options, &block)
|
|
58
73
|
callables << block if block_given?
|
|
59
74
|
|
|
@@ -73,6 +88,8 @@ module CMDx
|
|
|
73
88
|
#
|
|
74
89
|
# @example Remove a specific callback
|
|
75
90
|
# registry.deregister(:before_execution, :validate_inputs)
|
|
91
|
+
#
|
|
92
|
+
# @rbs (Symbol type, *untyped callables, **untyped options) ?{ (Task) -> void } -> self
|
|
76
93
|
def deregister(type, *callables, **options, &block)
|
|
77
94
|
callables << block if block_given?
|
|
78
95
|
return self unless registry[type]
|
|
@@ -91,6 +108,8 @@ module CMDx
|
|
|
91
108
|
#
|
|
92
109
|
# @example Invoke all before_execution callbacks
|
|
93
110
|
# registry.invoke(:before_execution, task)
|
|
111
|
+
#
|
|
112
|
+
# @rbs (Symbol type, Task task) -> void
|
|
94
113
|
def invoke(type, task)
|
|
95
114
|
raise TypeError, "unknown callback type #{type.inspect}" unless TYPES.include?(type)
|
|
96
115
|
|
data/lib/cmdx/chain.rb
CHANGED
|
@@ -8,9 +8,28 @@ module CMDx
|
|
|
8
8
|
|
|
9
9
|
extend Forwardable
|
|
10
10
|
|
|
11
|
+
# @rbs THREAD_KEY: Symbol
|
|
11
12
|
THREAD_KEY = :cmdx_chain
|
|
12
13
|
|
|
13
|
-
|
|
14
|
+
# Returns the unique identifier for this chain.
|
|
15
|
+
#
|
|
16
|
+
# @return [String] The chain identifier
|
|
17
|
+
#
|
|
18
|
+
# @example
|
|
19
|
+
# chain.id # => "abc123xyz"
|
|
20
|
+
#
|
|
21
|
+
# @rbs @id: String
|
|
22
|
+
attr_reader :id
|
|
23
|
+
|
|
24
|
+
# Returns the collection of execution results in this chain.
|
|
25
|
+
#
|
|
26
|
+
# @return [Array<Result>] Array of task results
|
|
27
|
+
#
|
|
28
|
+
# @example
|
|
29
|
+
# chain.results # => [#<Result>, #<Result>]
|
|
30
|
+
#
|
|
31
|
+
# @rbs @results: Array[Result]
|
|
32
|
+
attr_reader :results
|
|
14
33
|
|
|
15
34
|
def_delegators :results, :index, :first, :last, :size
|
|
16
35
|
def_delegators :first, :state, :status, :outcome, :runtime
|
|
@@ -18,6 +37,8 @@ module CMDx
|
|
|
18
37
|
# Creates a new chain with a unique identifier and empty results collection.
|
|
19
38
|
#
|
|
20
39
|
# @return [Chain] A new chain instance
|
|
40
|
+
#
|
|
41
|
+
# @rbs () -> void
|
|
21
42
|
def initialize
|
|
22
43
|
@id = Identifier.generate
|
|
23
44
|
@results = []
|
|
@@ -34,6 +55,8 @@ module CMDx
|
|
|
34
55
|
# if chain
|
|
35
56
|
# puts "Current chain: #{chain.id}"
|
|
36
57
|
# end
|
|
58
|
+
#
|
|
59
|
+
# @rbs () -> Chain?
|
|
37
60
|
def current
|
|
38
61
|
Thread.current[THREAD_KEY]
|
|
39
62
|
end
|
|
@@ -46,6 +69,8 @@ module CMDx
|
|
|
46
69
|
#
|
|
47
70
|
# @example
|
|
48
71
|
# Chain.current = my_chain
|
|
72
|
+
#
|
|
73
|
+
# @rbs (Chain chain) -> Chain
|
|
49
74
|
def current=(chain)
|
|
50
75
|
Thread.current[THREAD_KEY] = chain
|
|
51
76
|
end
|
|
@@ -56,6 +81,8 @@ module CMDx
|
|
|
56
81
|
#
|
|
57
82
|
# @example
|
|
58
83
|
# Chain.clear
|
|
84
|
+
#
|
|
85
|
+
# @rbs () -> nil
|
|
59
86
|
def clear
|
|
60
87
|
Thread.current[THREAD_KEY] = nil
|
|
61
88
|
end
|
|
@@ -73,6 +100,8 @@ module CMDx
|
|
|
73
100
|
# result = task.execute
|
|
74
101
|
# chain = Chain.build(result)
|
|
75
102
|
# puts "Chain size: #{chain.size}"
|
|
103
|
+
#
|
|
104
|
+
# @rbs (Result result) -> Chain
|
|
76
105
|
def build(result)
|
|
77
106
|
raise TypeError, "must be a CMDx::Result" unless result.is_a?(Result)
|
|
78
107
|
|
|
@@ -95,6 +124,8 @@ module CMDx
|
|
|
95
124
|
# chain_hash = chain.to_h
|
|
96
125
|
# puts chain_hash[:id]
|
|
97
126
|
# puts chain_hash[:results].size
|
|
127
|
+
#
|
|
128
|
+
# @rbs () -> Hash[Symbol, untyped]
|
|
98
129
|
def to_h
|
|
99
130
|
{
|
|
100
131
|
id: id,
|
|
@@ -108,6 +139,8 @@ module CMDx
|
|
|
108
139
|
#
|
|
109
140
|
# @example
|
|
110
141
|
# puts chain.to_s
|
|
142
|
+
#
|
|
143
|
+
# @rbs () -> String
|
|
111
144
|
def to_s
|
|
112
145
|
Utils::Format.to_str(to_h)
|
|
113
146
|
end
|
|
@@ -7,6 +7,14 @@ module CMDx
|
|
|
7
7
|
# for various data types including arrays, numbers, dates, and other primitives.
|
|
8
8
|
class CoercionRegistry
|
|
9
9
|
|
|
10
|
+
# Returns the internal registry mapping coercion types to handler classes.
|
|
11
|
+
#
|
|
12
|
+
# @return [Hash{Symbol => Class}] Hash of coercion type names to coercion classes
|
|
13
|
+
#
|
|
14
|
+
# @example
|
|
15
|
+
# registry.registry # => { integer: Coercions::Integer, boolean: Coercions::Boolean }
|
|
16
|
+
#
|
|
17
|
+
# @rbs @registry: Hash[Symbol, Class]
|
|
10
18
|
attr_reader :registry
|
|
11
19
|
alias to_h registry
|
|
12
20
|
|
|
@@ -17,6 +25,8 @@ module CMDx
|
|
|
17
25
|
# @example
|
|
18
26
|
# registry = CoercionRegistry.new
|
|
19
27
|
# registry = CoercionRegistry.new(custom: CustomCoercion)
|
|
28
|
+
#
|
|
29
|
+
# @rbs (?Hash[Symbol, Class]? registry) -> void
|
|
20
30
|
def initialize(registry = nil)
|
|
21
31
|
@registry = registry || {
|
|
22
32
|
array: Coercions::Array,
|
|
@@ -40,6 +50,8 @@ module CMDx
|
|
|
40
50
|
#
|
|
41
51
|
# @example
|
|
42
52
|
# new_registry = registry.dup
|
|
53
|
+
#
|
|
54
|
+
# @rbs () -> CoercionRegistry
|
|
43
55
|
def dup
|
|
44
56
|
self.class.new(registry.dup)
|
|
45
57
|
end
|
|
@@ -54,6 +66,8 @@ module CMDx
|
|
|
54
66
|
# @example
|
|
55
67
|
# registry.register(:custom_type, CustomCoercion)
|
|
56
68
|
# registry.register("another_type", AnotherCoercion)
|
|
69
|
+
#
|
|
70
|
+
# @rbs ((Symbol | String) name, Class coercion) -> self
|
|
57
71
|
def register(name, coercion)
|
|
58
72
|
registry[name.to_sym] = coercion
|
|
59
73
|
self
|
|
@@ -68,6 +82,8 @@ module CMDx
|
|
|
68
82
|
# @example
|
|
69
83
|
# registry.deregister(:custom_type)
|
|
70
84
|
# registry.deregister("another_type")
|
|
85
|
+
#
|
|
86
|
+
# @rbs ((Symbol | String) name) -> self
|
|
71
87
|
def deregister(name)
|
|
72
88
|
registry.delete(name.to_sym)
|
|
73
89
|
self
|
|
@@ -87,6 +103,8 @@ module CMDx
|
|
|
87
103
|
# @example
|
|
88
104
|
# result = registry.coerce(:integer, task, "42")
|
|
89
105
|
# result = registry.coerce(:boolean, task, "true", strict: true)
|
|
106
|
+
#
|
|
107
|
+
# @rbs (Symbol type, untyped task, untyped value, ?Hash[Symbol, untyped] options) -> untyped
|
|
90
108
|
def coerce(type, task, value, options = {})
|
|
91
109
|
raise TypeError, "unknown coercion type #{type.inspect}" unless registry.key?(type)
|
|
92
110
|
|
data/lib/cmdx/coercions/array.rb
CHANGED
|
@@ -26,6 +26,8 @@ module CMDx
|
|
|
26
26
|
# Array.call("hello") # => ["hello"]
|
|
27
27
|
# Array.call(42) # => [42]
|
|
28
28
|
# Array.call(nil) # => []
|
|
29
|
+
#
|
|
30
|
+
# @rbs (untyped value, ?Hash[Symbol, untyped] options) -> Array[untyped]
|
|
29
31
|
def call(value, options = {})
|
|
30
32
|
if value.is_a?(::String) && value.start_with?("[")
|
|
31
33
|
JSON.parse(value)
|