lite-command 2.0.3 → 2.1.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/.rubocop.yml +6 -0
- data/CHANGELOG.md +22 -0
- data/Gemfile.lock +3 -3
- data/README.md +499 -58
- data/lib/lite/command/attribute.rb +93 -0
- data/lib/lite/command/attribute_validator.rb +35 -0
- data/lib/lite/command/base.rb +8 -16
- data/lib/lite/command/fault.rb +19 -5
- data/lib/lite/command/fault_streamer.rb +40 -0
- data/lib/lite/command/internals/call.rb +99 -0
- data/lib/lite/command/internals/context.rb +46 -0
- data/lib/lite/command/internals/{executable.rb → execute.rb} +30 -22
- data/lib/lite/command/internals/fault.rb +41 -0
- data/lib/lite/command/internals/{resultable.rb → result.rb} +7 -6
- data/lib/lite/command/sequence.rb +28 -0
- data/lib/lite/command/step.rb +26 -0
- data/lib/lite/command/utils.rb +31 -0
- data/lib/lite/command/version.rb +1 -1
- data/lib/lite/command.rb +12 -5
- metadata +14 -7
- data/lib/lite/command/internals/callable.rb +0 -85
- data/lib/lite/command/internals/faultable.rb +0 -83
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 67e91e41adc24a79e5b834c8633ab65645f86e208bbd0d23dbd2e49a4462a17c
|
4
|
+
data.tar.gz: 699f3dbdef6befb271880d31d30063f10ed97517bf14ea3418574494ee2a2878
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: aad63061e1145000153053df04cac575065f1220bb77e09db6521e8a0f85a99c7347d049c0b941bd77c97d34c95495877dfc9066798ea278459a6df4111e4e91
|
7
|
+
data.tar.gz: 1da794d70ca0c441cc0de1b37161b02774035a4e9bff07a56f908551dc7bac751689da47e8903326c61bfc662d52d8a0036ba06ce7a0155294a9b66c294ce921
|
data/.rubocop.yml
CHANGED
@@ -51,6 +51,12 @@ RSpec/MultipleExpectations:
|
|
51
51
|
Enabled: false
|
52
52
|
RSpec/MultipleMemoizedHelpers:
|
53
53
|
Enabled: false
|
54
|
+
RSpec/NestedGroups:
|
55
|
+
Enabled: false
|
56
|
+
RSpec/StringAsInstanceDoubleConstant:
|
57
|
+
Enabled: false
|
58
|
+
RSpec/VerifiedDoubleReference:
|
59
|
+
EnforcedStyle: string
|
54
60
|
Style/ArgumentsForwarding:
|
55
61
|
Enabled: false
|
56
62
|
Style/Documentation:
|
data/CHANGELOG.md
CHANGED
@@ -6,6 +6,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
6
6
|
|
7
7
|
## [Unreleased]
|
8
8
|
|
9
|
+
## [2.1.1] - 2024-10-06
|
10
|
+
### Added
|
11
|
+
- Added on_status hook to `execute!`
|
12
|
+
### Changed
|
13
|
+
- Reuse same attribute instance
|
14
|
+
|
15
|
+
## [2.1.0] - 2024-10-05
|
16
|
+
### Added
|
17
|
+
- Added passing metadata to faults
|
18
|
+
- Added `on_success` callback
|
19
|
+
- Added `on_pending`, `on_executing`, `on_complete`, and `on_interrupted` callbacks
|
20
|
+
- Added attributes and attribute validations
|
21
|
+
- Added steps and sequences
|
22
|
+
- Added fault streamer
|
23
|
+
### Changed
|
24
|
+
- Check error descendency instead of type
|
25
|
+
- Rename internal modules
|
26
|
+
- Make execute(!) methods private
|
27
|
+
### Removed
|
28
|
+
- Remove predefined callback methods
|
29
|
+
- Remove non-bang fault methods
|
30
|
+
|
9
31
|
## [2.0.3] - 2024-09-30
|
10
32
|
### Changed
|
11
33
|
- Simplify error building
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
lite-command (2.
|
4
|
+
lite-command (2.1.1)
|
5
5
|
ostruct
|
6
6
|
|
7
7
|
GEM
|
@@ -114,7 +114,7 @@ GEM
|
|
114
114
|
rspec-expectations (3.13.3)
|
115
115
|
diff-lcs (>= 1.2.0, < 2.0)
|
116
116
|
rspec-support (~> 3.13.0)
|
117
|
-
rspec-mocks (3.13.
|
117
|
+
rspec-mocks (3.13.2)
|
118
118
|
diff-lcs (>= 1.2.0, < 2.0)
|
119
119
|
rspec-support (~> 3.13.0)
|
120
120
|
rspec-support (3.13.1)
|
@@ -135,7 +135,7 @@ GEM
|
|
135
135
|
rubocop-ast (>= 1.31.1, < 2.0)
|
136
136
|
rubocop-rake (0.6.0)
|
137
137
|
rubocop (~> 1.0)
|
138
|
-
rubocop-rspec (3.0
|
138
|
+
rubocop-rspec (3.1.0)
|
139
139
|
rubocop (~> 1.61)
|
140
140
|
ruby-progressbar (1.13.0)
|
141
141
|
ruby_parser (3.21.1)
|
data/README.md
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
# Lite::Command
|
2
2
|
|
3
3
|
[](http://badge.fury.io/rb/lite-command)
|
4
|
-
[](https://travis-ci.org/drexed/lite-command)
|
5
4
|
|
6
5
|
Lite::Command provides an API for building simple and complex command based service objects.
|
7
6
|
|
@@ -9,6 +8,10 @@ Lite::Command provides an API for building simple and complex command based serv
|
|
9
8
|
|
10
9
|
Add this line to your application's Gemfile:
|
11
10
|
|
11
|
+
> [!NOTE]
|
12
|
+
> Gem versions `2.0.0`, `2.0.1`, `2.0.2`, and `2.0.3` are borked.
|
13
|
+
> Version `~> 2.1.0` is the suggested working version.
|
14
|
+
|
12
15
|
```ruby
|
13
16
|
gem 'lite-command'
|
14
17
|
```
|
@@ -25,50 +28,112 @@ Or install it yourself as:
|
|
25
28
|
|
26
29
|
* [Setup](#setup)
|
27
30
|
* [Execution](#execution)
|
31
|
+
* [Dynamic Faults](#dynamic-faults)
|
28
32
|
* [Context](#context)
|
29
|
-
* [
|
33
|
+
* [Attributes](#attributes)
|
34
|
+
* [States](#states)
|
35
|
+
* [Statuses](#statuses)
|
36
|
+
* [Callbacks](#callbacks)
|
37
|
+
* [State Hooks](#status-hooks)
|
38
|
+
* [Execution Hooks](#execution-hooks)
|
39
|
+
* [Status Hooks](#status-hooks)
|
40
|
+
* [Children](#children)
|
41
|
+
* [Throwing Faults](#throwing-faults)
|
42
|
+
* [Sequences](#sequences)
|
43
|
+
* [Results](#results)
|
44
|
+
* [Examples](#examples)
|
45
|
+
* [Disable Instance Calls](#disable-instance-calls)
|
46
|
+
* [ActiveModel Validations](#activemodel-validations)
|
30
47
|
* [Generator](#generator)
|
31
48
|
|
32
49
|
## Setup
|
33
50
|
|
34
|
-
Defining a command is as simple as adding a call method.
|
51
|
+
Defining a command is as simple as adding a call method to a command object (required).
|
35
52
|
|
36
53
|
```ruby
|
37
54
|
class CalculatePower < Lite::Command::Base
|
38
55
|
|
39
56
|
def call
|
40
|
-
|
57
|
+
if all_even_numbers?
|
58
|
+
context.result = ctx.a ** ctx.b
|
59
|
+
else
|
60
|
+
invalid!("All values must be even")
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def all_even_numbers?
|
67
|
+
# Some logic...
|
41
68
|
end
|
42
69
|
|
43
70
|
end
|
44
71
|
```
|
45
72
|
|
73
|
+
> [!TIP]
|
74
|
+
> You should make all of your domain logic private so that only the command API is exposed.
|
75
|
+
|
46
76
|
## Execution
|
47
77
|
|
48
78
|
Executing a command can be done as an instance or class call.
|
49
|
-
It returns the command instance in a
|
79
|
+
It returns the command instance in a frozen state.
|
50
80
|
These will never call will never raise an execption, but will
|
51
81
|
be kept track of in its internal state.
|
52
82
|
|
53
|
-
**NOTE:** Class calls is the prefered format due to its readability.
|
54
|
-
|
55
83
|
```ruby
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
# Instance call
|
60
|
-
caculator = CalculatePower.new(..args).call
|
84
|
+
CalculatePower.call(...)
|
85
|
+
# - or -
|
86
|
+
CalculatePower.new(...).call
|
61
87
|
|
88
|
+
# On success, fault and exception:
|
62
89
|
#=> <CalculatePower ...>
|
63
90
|
```
|
64
91
|
|
92
|
+
> [!TIP]
|
93
|
+
> Class calls is the prefered format due to its readability.
|
94
|
+
|
65
95
|
Commands can be called with a `!` bang method to raise a
|
66
96
|
`Lite::Command::Fault` based exception or the original
|
67
97
|
`StandardError` based exception.
|
68
98
|
|
69
99
|
```ruby
|
70
|
-
CalculatePower.call!(
|
100
|
+
CalculatePower.call!(...)
|
101
|
+
# - or -
|
102
|
+
CalculatePower.new(...).call!
|
103
|
+
|
104
|
+
# On success:
|
105
|
+
#=> <CalculatePower ...>
|
106
|
+
|
107
|
+
# On fault:
|
71
108
|
#=> raises Lite::Command::Fault
|
109
|
+
|
110
|
+
# On exception:
|
111
|
+
#=> raises StandardError
|
112
|
+
```
|
113
|
+
|
114
|
+
### Dynamic Faults
|
115
|
+
|
116
|
+
You can enable dynamic faults named after your command. This is
|
117
|
+
especially helpful for catching + running custom logic or filtering
|
118
|
+
out specific errors from you APM service.
|
119
|
+
|
120
|
+
```ruby
|
121
|
+
class CalculatePower < Lite::Command::Base
|
122
|
+
|
123
|
+
def call
|
124
|
+
fail!("Some failure")
|
125
|
+
end
|
126
|
+
|
127
|
+
private
|
128
|
+
|
129
|
+
def raise_dynamic_faults?
|
130
|
+
true
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
134
|
+
|
135
|
+
CalculatePower.call!(...)
|
136
|
+
#=> raises CalculatePower::Fault
|
72
137
|
```
|
73
138
|
|
74
139
|
## Context
|
@@ -81,7 +146,8 @@ of its children commands.
|
|
81
146
|
class CalculatePower < Lite::Command::Base
|
82
147
|
|
83
148
|
def call
|
84
|
-
|
149
|
+
# `ctx` is an alias to `context`
|
150
|
+
context.result = ctx.a ** ctx.b
|
85
151
|
end
|
86
152
|
|
87
153
|
end
|
@@ -90,50 +156,425 @@ command = CalculatePower.call(a: 2, b: 3)
|
|
90
156
|
command.context.result #=> 8
|
91
157
|
```
|
92
158
|
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
159
|
+
### Attributes
|
160
|
+
|
161
|
+
Delegate methods for a cleaner command setup, type checking and
|
162
|
+
argument requirements. Setup a contract by using the `attribute`
|
163
|
+
method which automatically delegates to `context`.
|
164
|
+
|
165
|
+
| Options | Values | Default | Description |
|
166
|
+
| ---------- | ------ | ------- | ----------- |
|
167
|
+
| `from` | Symbol, String | `:context` | The object containing the attribute. |
|
168
|
+
| `types` | Symbol, String, Array, Proc | | The allowed class types of the attribute value. |
|
169
|
+
| `required` | Symbol, String, Boolean, Proc | `false` | The attribute must be passed to the context or delegatable (no matter the value). |
|
170
|
+
| `filled` | Symbol, String, Boolean, Proc | `false` | The attribute value must be not be `nil`. |
|
171
|
+
|
172
|
+
> [!NOTE]
|
173
|
+
> If optioned with some similar to `filled: true, types: [String, NilClass]`
|
174
|
+
> then `NilClass` for the `types` option will be removed automatically.
|
175
|
+
|
176
|
+
```ruby
|
177
|
+
class CalculatePower < Lite::Command::Base
|
178
|
+
|
179
|
+
attribute :remote_storage, required: true, filled: true, types: RemoteStorage
|
180
|
+
|
181
|
+
attribute :a, :b
|
182
|
+
attribute :c, :d, from: :remote_storage, types: [Integer, Float]
|
183
|
+
attribute :x, :y, from: :local_storage, if: :signed_in?
|
184
|
+
|
185
|
+
def call
|
186
|
+
context.result =
|
187
|
+
(a.to_i ** b.to_i) +
|
188
|
+
(c.to_i + d.to_i) -
|
189
|
+
(x.to_i + y.to_i)
|
190
|
+
end
|
191
|
+
|
192
|
+
private
|
193
|
+
|
194
|
+
def local_storage
|
195
|
+
@local_storage ||= LocalStorage.new(x: 1, y: 1, z: 99)
|
196
|
+
end
|
197
|
+
|
198
|
+
def signed_in?
|
199
|
+
ctx.user.signed_in?
|
200
|
+
end
|
201
|
+
|
202
|
+
end
|
203
|
+
|
204
|
+
# With valid options:
|
205
|
+
storage = RemoteStorage.new(c: 2, d: 2, j: 99)
|
206
|
+
command = CalculatePower.call(a: 2, b: 2, remote_storage: storage)
|
207
|
+
command.status #=> "success"
|
208
|
+
command.context.result #=> 6
|
209
|
+
|
210
|
+
# With invalid options
|
211
|
+
command = CalculatePower.call
|
212
|
+
command.status #=> "invalid"
|
213
|
+
command.reason #=> "Invalid context attributes"
|
214
|
+
command.metadata #=> {
|
215
|
+
#=> context: ["a is required", "remote_storage must be filled"],
|
216
|
+
#=> remote_storage: ["d type invalid"]
|
217
|
+
#=> local_storage: ["is not defined or an attribute"]
|
218
|
+
#=> }
|
219
|
+
```
|
220
|
+
|
221
|
+
## States
|
222
|
+
`state` represents the condition of all the code command should execute.
|
223
|
+
|
224
|
+
| Status | Description |
|
225
|
+
| ------------- | ----------- |
|
226
|
+
| `pending` | Command objects that have been initialized. |
|
227
|
+
| `executing` | Command objects that are actively executing code. |
|
228
|
+
| `complete` | Command objects that executed to completion without fault/exception. |
|
229
|
+
| `interrupted` | Command objects that could **NOT** be executed to completion due to a fault/exception. |
|
230
|
+
|
231
|
+
> [!CAUTION]
|
232
|
+
> States are automatically transitioned and should **NEVER** be altered manually.
|
233
|
+
|
234
|
+
```ruby
|
235
|
+
class CalculatePower < Lite::Command::Base
|
236
|
+
|
237
|
+
def call
|
238
|
+
# ...
|
239
|
+
end
|
240
|
+
|
241
|
+
end
|
242
|
+
|
243
|
+
command = CalculatePower.call(a: 1, b: 3)
|
244
|
+
command.state #=> "executed"
|
245
|
+
command.pending? #=> false
|
246
|
+
command.executed? #=> false
|
247
|
+
```
|
248
|
+
|
249
|
+
## Statuses
|
250
|
+
|
251
|
+
`status` represents the state of the domain logic executed via the `call` method.
|
252
|
+
A status of `success` is returned even if the command has **NOT** been executed.
|
253
|
+
|
254
|
+
| Status | Description |
|
255
|
+
| --------- | ----------- |
|
256
|
+
| `success` | Call execution completed without fault/exception. |
|
257
|
+
| `noop` | **Fault** to skip completion of call execution early for an unsatisfied condition where proceeding is pointless. |
|
258
|
+
| `invalid` | **Fault** to stop call execution due to missing, bad, or corrupt data. |
|
259
|
+
| `failure` | **Fault** to stop call execution due to an unsatisfied condition where it blocks proceeding any further. |
|
260
|
+
| `error` | **Fault** to stop call execution due to a thrown `StandardError` based exception. |
|
261
|
+
|
262
|
+
> [!IMPORTANT]
|
263
|
+
> Each **fault** status has a setter method ending in `!` that invokes a matching fault procedure.
|
264
|
+
> Metadata may also be passed to enrich your fault response.
|
265
|
+
|
266
|
+
```ruby
|
267
|
+
class CalculatePower < Lite::Command::Base
|
268
|
+
|
269
|
+
def call
|
270
|
+
if ctx.a.nil? || ctx.b.nil?
|
271
|
+
invalid!("An a and b parameter must be passed")
|
272
|
+
elsif ctx.a < 1 || ctx.b < 1
|
273
|
+
failure!("Parameters must be >= 1")
|
274
|
+
elsif ctx.a == 1 || ctx.b == 1
|
275
|
+
noop!(
|
276
|
+
"Anything to the power of 1 is 1",
|
277
|
+
{ i18n: "some.key" }
|
278
|
+
)
|
279
|
+
else
|
280
|
+
ctx.result = ctx.a ** ctx.b
|
281
|
+
end
|
282
|
+
rescue DivisionError => e
|
283
|
+
error!("Cathcing it myself")
|
284
|
+
end
|
285
|
+
|
286
|
+
end
|
287
|
+
|
288
|
+
command = CalculatePower.call(a: 1, b: 3)
|
289
|
+
command.ctx.result #=> nil
|
290
|
+
command.status #=> "noop"
|
291
|
+
command.reason #=> "Anything to the power of 1 is 1"
|
292
|
+
command.metadata #=> { i18n: "some.key" }
|
293
|
+
command.invalid? #=> false
|
294
|
+
command.noop? #=> true
|
295
|
+
command.noop?("Anything to the power of 1 is 1") #=> true
|
296
|
+
```
|
297
|
+
|
298
|
+
## Callbacks
|
299
|
+
|
300
|
+
Use callbacks to run arbituary code at transition points and
|
301
|
+
on finalized internals. The following is an example of the hooks
|
302
|
+
called for a failed command with a successful child command.
|
303
|
+
|
304
|
+
```ruby
|
305
|
+
-> 1. FooCommand.on_pending
|
306
|
+
-> 2. FooCommand.on_before_execution
|
307
|
+
-> 3. FooCommand.on_executing
|
308
|
+
---> 3a. BarCommand.on_pending
|
309
|
+
---> 3b. BarCommand.on_before_execution
|
310
|
+
---> 3c. BarCommand.on_executing
|
311
|
+
---> 3d. BarCommand.on_after_execution
|
312
|
+
---> 3e. BarCommand.on_success
|
313
|
+
---> 3f. BarCommand.on_complete
|
314
|
+
-> 4. FooCommand.on_after_execution
|
315
|
+
-> 5. FooCommand.on_failure
|
316
|
+
-> 6. FooCommand.on_interrupted
|
317
|
+
```
|
318
|
+
|
319
|
+
### Status Hooks
|
320
|
+
|
321
|
+
Define one or more callbacks that are called during transitions
|
322
|
+
between states.
|
323
|
+
|
324
|
+
```ruby
|
325
|
+
class CalculatePower < Lite::Command::Base
|
326
|
+
|
327
|
+
def call
|
328
|
+
# ...
|
329
|
+
end
|
330
|
+
|
331
|
+
private
|
332
|
+
|
333
|
+
def on_pending
|
334
|
+
# eg: Append additional contextual data
|
335
|
+
end
|
336
|
+
|
337
|
+
def on_executing
|
338
|
+
# eg: Insert inspection debugger
|
339
|
+
end
|
340
|
+
|
341
|
+
def on_complete
|
342
|
+
# eg: Log message for posterity
|
343
|
+
end
|
344
|
+
|
345
|
+
def on_interrupted
|
346
|
+
# eg: Report to APM with tags and metadata
|
347
|
+
end
|
348
|
+
|
349
|
+
end
|
350
|
+
```
|
351
|
+
|
352
|
+
### Execution Hooks
|
353
|
+
|
354
|
+
Define before and after callbacks to call around execution.
|
355
|
+
|
356
|
+
```ruby
|
357
|
+
class CalculatePower < Lite::Command::Base
|
358
|
+
|
359
|
+
def call
|
360
|
+
# ...
|
361
|
+
end
|
362
|
+
|
363
|
+
private
|
364
|
+
|
365
|
+
def on_before_execution
|
366
|
+
# eg: Append additional contextual data
|
367
|
+
end
|
368
|
+
|
369
|
+
def on_after_execution
|
370
|
+
# eg: Store results to database
|
371
|
+
end
|
372
|
+
|
373
|
+
end
|
374
|
+
```
|
375
|
+
|
376
|
+
### Status Hooks
|
377
|
+
|
378
|
+
Define one or more callbacks that are called after execution for
|
379
|
+
specific statuses.
|
380
|
+
|
381
|
+
```ruby
|
382
|
+
class CalculatePower < Lite::Command::Base
|
383
|
+
|
384
|
+
def call
|
385
|
+
# ...
|
386
|
+
end
|
387
|
+
|
388
|
+
private
|
389
|
+
|
390
|
+
def on_success
|
391
|
+
# eg: Increment KPI counter
|
392
|
+
end
|
393
|
+
|
394
|
+
def on_noop(fault)
|
395
|
+
# eg: Log message for posterity
|
396
|
+
end
|
397
|
+
|
398
|
+
def on_invalid(fault)
|
399
|
+
# eg: Send metadata errors to frontend
|
400
|
+
end
|
401
|
+
|
402
|
+
def on_failure(fault)
|
403
|
+
# eg: Rollback record changes
|
404
|
+
end
|
405
|
+
|
406
|
+
def on_error(fault_or_exception)
|
407
|
+
# eg: Report to APM with tags and metadata
|
408
|
+
end
|
409
|
+
|
410
|
+
end
|
411
|
+
```
|
412
|
+
|
413
|
+
> [!NOTE]
|
414
|
+
> The `on_success` callback does **NOT** take any arguments.
|
415
|
+
|
416
|
+
## Children
|
417
|
+
|
418
|
+
When building complex commands, its best that you pass the
|
419
|
+
parents context to the child command (unless neccessary) so
|
420
|
+
that it gains automated indexing and the parents `cmd_id`.
|
421
|
+
|
422
|
+
```ruby
|
423
|
+
class CalculatePower < Lite::Command::Base
|
424
|
+
|
425
|
+
def call
|
426
|
+
CalculateSqrt.call(context.merge!(some_other: "required value"))
|
427
|
+
end
|
428
|
+
|
429
|
+
end
|
430
|
+
```
|
431
|
+
|
432
|
+
### Throwing Faults
|
433
|
+
|
434
|
+
Throwing faults allows you to bubble up child faults up to the parent.
|
435
|
+
Use it to create branches within your logic and create clean tracing
|
436
|
+
of your command results. You can use `throw!` as a catch-all or any
|
437
|
+
of the bang status method `failure!`.
|
438
|
+
|
439
|
+
```ruby
|
440
|
+
class CalculatePower < Lite::Command::Base
|
441
|
+
|
442
|
+
def call
|
443
|
+
command = CalculateSqrt.call(context.merge!(some_other: "required value"))
|
444
|
+
|
445
|
+
if command.noop?("Sqrt of 1 is 1")
|
446
|
+
# Manually throw any fault you want
|
447
|
+
invalid!(command)
|
448
|
+
elsif command.fault?
|
449
|
+
# Automatically throws a matching fault type
|
450
|
+
throw!(command)
|
451
|
+
else
|
452
|
+
# Success, do nothing
|
453
|
+
end
|
454
|
+
end
|
455
|
+
|
456
|
+
end
|
457
|
+
```
|
458
|
+
|
459
|
+
## Sequences
|
460
|
+
|
461
|
+
A sequence is a command that calls commands in a linear fashion.
|
462
|
+
This is useful for composing multiple steps into one call.
|
463
|
+
|
464
|
+
> [!NOTE]
|
465
|
+
> Sequences only stop processing on `invalid`, `failure`, and `error`
|
466
|
+
> faults. This is due to the the idea the `noop` performs no work,
|
467
|
+
> so its no different than just passing the context forward. To change
|
468
|
+
> this behavior, just override the `ok?` method with you logic, eg: just `success`
|
469
|
+
|
470
|
+
```ruby
|
471
|
+
class ProcessCheckout < Lite::Command::Sequence
|
472
|
+
|
473
|
+
attribute :user, required: true, filled: true
|
474
|
+
|
475
|
+
step FinalizeInvoice
|
476
|
+
step ChargeCard, if: :card_available?
|
477
|
+
step SendConfirmationEmail, SendConfirmationText
|
478
|
+
step NotifyWarehouse, unless: proc { ctx.invoice.fullfilled_by_amazon? }
|
479
|
+
|
480
|
+
# Do NOT set a call method.
|
481
|
+
# Its defined by Lite::Command::Sequence
|
482
|
+
|
483
|
+
private
|
484
|
+
|
485
|
+
def card_available?
|
486
|
+
user.has_card?
|
487
|
+
end
|
488
|
+
|
489
|
+
end
|
490
|
+
|
491
|
+
sequence = ProcessCheckout.call(...)
|
492
|
+
# <ProcessCheckout ...>
|
493
|
+
```
|
494
|
+
|
495
|
+
## Results
|
496
|
+
|
497
|
+
During any point in the lifecyle of a command, `to_hash` can be
|
498
|
+
called to dump out the current values. The `index` value is
|
499
|
+
auto-incremented and the `cmd_id` is static when its passed to
|
500
|
+
child commands. This helps with debugging and logging.
|
501
|
+
|
502
|
+
```ruby
|
503
|
+
command = CalculatePower.call(...)
|
504
|
+
command.to_hash #=> {
|
505
|
+
#=> index: 1,
|
506
|
+
#=> cmd_id: "018c2b95-b764-7615-a924-cc5b910ed1e5",
|
507
|
+
#=> command: "FailureCommand",
|
508
|
+
#=> outcome: "failure",
|
509
|
+
#=> state: "interrupted",
|
510
|
+
#=> status: "failure",
|
511
|
+
#=> reason: "[!] command stopped due to failure",
|
512
|
+
#=> metadata: {
|
513
|
+
#=> errors: { name: ["is too short"] },
|
514
|
+
#=> i18n_key: "command.failure"
|
515
|
+
#=> },
|
516
|
+
#=> caused_by: 1,
|
517
|
+
#=> thrown_by: 1,
|
518
|
+
#=> runtime: 0.0123
|
519
|
+
#=> }
|
520
|
+
```
|
521
|
+
|
522
|
+
## Examples
|
523
|
+
|
524
|
+
### Disable Instance Calls
|
525
|
+
|
526
|
+
```ruby
|
527
|
+
class CalculatePower < Lite::Command::Base
|
528
|
+
|
529
|
+
private_class_method :new
|
530
|
+
|
531
|
+
def call
|
532
|
+
# ...
|
533
|
+
end
|
534
|
+
|
535
|
+
end
|
536
|
+
|
537
|
+
CalculatePower.new(...).call
|
538
|
+
#=> raise NoMethodError
|
539
|
+
```
|
540
|
+
|
541
|
+
### ActiveModel Validations
|
542
|
+
|
543
|
+
```ruby
|
544
|
+
class CalculatePower < Lite::Command::Base
|
545
|
+
include ActiveModel::Validations
|
546
|
+
|
547
|
+
validates :a, :b, presence: true
|
548
|
+
|
549
|
+
def call
|
550
|
+
# ...
|
551
|
+
end
|
552
|
+
|
553
|
+
def read_attribute_for_validation(key)
|
554
|
+
context.public_send(key)
|
555
|
+
end
|
556
|
+
|
557
|
+
private
|
558
|
+
|
559
|
+
def on_before_execution
|
560
|
+
return if valid?
|
561
|
+
|
562
|
+
invalid!(
|
563
|
+
errors.full_messages.to_sentence,
|
564
|
+
errors.to_hash
|
565
|
+
)
|
566
|
+
end
|
567
|
+
|
568
|
+
end
|
569
|
+
|
570
|
+
CalculatePower.call!
|
571
|
+
|
572
|
+
# With `validate!`
|
573
|
+
#=> raise ActiveRecord::RecordInvalid
|
574
|
+
|
575
|
+
# With `valid?`
|
576
|
+
#=> raise Lite::Command::Invalid
|
577
|
+
```
|
137
578
|
|
138
579
|
## Generator
|
139
580
|
|