lite-command 2.0.3 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +6 -0
- data/CHANGELOG.md +15 -0
- data/Gemfile.lock +3 -3
- data/README.md +499 -58
- data/lib/lite/command/attribute.rb +96 -0
- data/lib/lite/command/attribute_validator.rb +36 -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 +100 -0
- data/lib/lite/command/internals/context.rb +46 -0
- data/lib/lite/command/internals/{executable.rb → execute.rb} +29 -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 +13 -6
- 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: efd37a3a2fcbb8d90fef73088313f18bd281922d46594a4a57ee8dd34fcaf801
|
4
|
+
data.tar.gz: 4e88ca10b986095be52c3e454e70a6c8d7b3556bb4770ed51aca80d0b9beb894
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4123ef0a3315a8b00796a58ca65b10bc2a51905dc59e663b1ffef2ec7f9becc700ae64216cb896024564993749979cd824087a68bac1e95532311dba29cd7aa0
|
7
|
+
data.tar.gz: dd4e22ead3d68f981319a372a0ce305f734f1745a31223daf725d93ff71cccbe1b4708e7b3d7ef4921f98f2e8df6c04939855b8a7e8d0e3292f5e7f3e0ec6c1a
|
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,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
6
6
|
|
7
7
|
## [Unreleased]
|
8
8
|
|
9
|
+
## [2.1.0] - 2024-10-05
|
10
|
+
### Added
|
11
|
+
- Added passing metadata to faults
|
12
|
+
- Added `on_success` callback
|
13
|
+
- Added `on_pending`, `on_executing`, `on_complete`, and `on_interrupted` callbacks
|
14
|
+
- Added attributes and attribute validations
|
15
|
+
- Added sequences
|
16
|
+
### Changed
|
17
|
+
- Check error descendency instead of type
|
18
|
+
- Rename internal modules
|
19
|
+
- Make execute(!) methods private
|
20
|
+
### Removed
|
21
|
+
- Remove predefined callback methods
|
22
|
+
- Remove non-bang fault methods
|
23
|
+
|
9
24
|
## [2.0.3] - 2024-09-30
|
10
25
|
### Changed
|
11
26
|
- Simplify error building
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
lite-command (2.0
|
4
|
+
lite-command (2.1.0)
|
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
|
[![Gem Version](https://badge.fury.io/rb/lite-command.svg)](http://badge.fury.io/rb/lite-command)
|
4
|
-
[![Build Status](https://travis-ci.org/drexed/lite-command.svg?branch=master)](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 latest 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
|
|