lite-command 2.1.3 → 3.0.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 +4 -0
- data/CHANGELOG.md +17 -0
- data/Gemfile.lock +20 -17
- data/README.md +191 -174
- data/lib/generators/lite/command/install_generator.rb +15 -0
- data/lib/generators/lite/command/templates/install.rb +5 -0
- data/lib/lite/command/base.rb +20 -17
- data/lib/lite/command/configuration.rb +35 -0
- data/lib/lite/command/fault.rb +21 -8
- data/lib/lite/command/internals/attributes.rb +64 -0
- data/lib/lite/command/internals/{call.rb → calls.rb} +8 -4
- data/lib/lite/command/internals/{execute.rb → executions.rb} +11 -10
- data/lib/lite/command/internals/{fault.rb → faults.rb} +12 -6
- data/lib/lite/command/internals/{result.rb → results.rb} +1 -1
- data/lib/lite/command/sequence.rb +1 -1
- data/lib/lite/command/step.rb +1 -7
- data/lib/lite/command/utils.rb +11 -5
- data/lib/lite/command/version.rb +1 -1
- data/lib/lite/command.rb +10 -8
- data/lite-command.gemspec +1 -0
- metadata +24 -9
- data/lib/lite/command/attribute.rb +0 -102
- data/lib/lite/command/attribute_validator.rb +0 -35
- data/lib/lite/command/internals/context.rb +0 -46
data/README.md
CHANGED
@@ -8,10 +8,6 @@ Lite::Command provides an API for building simple and complex command based serv
|
|
8
8
|
|
9
9
|
Add this line to your application's Gemfile:
|
10
10
|
|
11
|
-
> [!WARNING]
|
12
|
-
> Gem versions `~> 2.0` are borked.
|
13
|
-
> Version `~> 2.1.0` is the suggested working version.
|
14
|
-
|
15
11
|
```ruby
|
16
12
|
gem 'lite-command'
|
17
13
|
```
|
@@ -26,15 +22,18 @@ Or install it yourself as:
|
|
26
22
|
|
27
23
|
## Table of Contents
|
28
24
|
|
29
|
-
* [
|
25
|
+
* [Configuration](#configuration)
|
26
|
+
* [Usage](#usage)
|
30
27
|
* [Execution](#execution)
|
31
28
|
* [Dynamic Faults](#dynamic-faults)
|
32
29
|
* [Context](#context)
|
33
30
|
* [Attributes](#attributes)
|
31
|
+
* [Validations](#validations)
|
34
32
|
* [States](#states)
|
35
33
|
* [Statuses](#statuses)
|
36
|
-
* [
|
34
|
+
* [Hooks](#hooks)
|
37
35
|
* [State Hooks](#status-hooks)
|
36
|
+
* [Attribute Hooks](#attribute-hooks)
|
38
37
|
* [Execution Hooks](#execution-hooks)
|
39
38
|
* [Status Hooks](#status-hooks)
|
40
39
|
* [Children](#children)
|
@@ -43,28 +42,38 @@ Or install it yourself as:
|
|
43
42
|
* [Results](#results)
|
44
43
|
* [Examples](#examples)
|
45
44
|
* [Disable Instance Calls](#disable-instance-calls)
|
46
|
-
* [ActiveModel Validations](#activemodel-validations)
|
47
45
|
* [Generator](#generator)
|
48
46
|
|
49
|
-
##
|
47
|
+
## Configuration
|
48
|
+
|
49
|
+
`rails g lite:command:install` will generate the following file in your application root:
|
50
|
+
`config/initalizers/lite_command.rb`
|
51
|
+
|
52
|
+
```ruby
|
53
|
+
Lite::Command.configure do |config|
|
54
|
+
config.raise_dynamic_faults = true
|
55
|
+
end
|
56
|
+
```
|
57
|
+
|
58
|
+
## Usage
|
50
59
|
|
51
|
-
Defining a command is as simple as inheriting the base class and
|
52
|
-
|
60
|
+
Defining a command is as simple as inheriting the base class and adding a `call` method
|
61
|
+
to a command object (required).
|
53
62
|
|
54
63
|
```ruby
|
55
|
-
class
|
64
|
+
class DecryptSecretMessage < Lite::Command::Base
|
56
65
|
|
57
66
|
def call
|
58
|
-
if
|
59
|
-
|
67
|
+
if invalid_magic_numbers?
|
68
|
+
invalid!("Invalid crypto message")
|
60
69
|
else
|
61
|
-
|
70
|
+
context.decrypted_message = SecretMessage.decrypt(context.encrypted_message)
|
62
71
|
end
|
63
72
|
end
|
64
73
|
|
65
74
|
private
|
66
75
|
|
67
|
-
def
|
76
|
+
def invalid_magic_numbers?
|
68
77
|
# Some logic...
|
69
78
|
end
|
70
79
|
|
@@ -72,38 +81,38 @@ end
|
|
72
81
|
```
|
73
82
|
|
74
83
|
> [!TIP]
|
75
|
-
> You should
|
84
|
+
> You should treat all command as emphemeral objects, so you should think about making
|
85
|
+
> all of your domain logic private and leaving the default command API is exposed.
|
76
86
|
|
77
87
|
## Execution
|
78
88
|
|
79
|
-
Executing a command can be done as an instance or class call.
|
80
|
-
|
81
|
-
|
82
|
-
be kept track of in its internal state.
|
89
|
+
Executing a command can be done as an instance or class call. It returns the command instance
|
90
|
+
in a frozen state. These will never call will never raise an execption, but will be kept track
|
91
|
+
of in its internal state.
|
83
92
|
|
84
93
|
```ruby
|
85
|
-
|
94
|
+
DecryptSecretMessage.call(...)
|
86
95
|
# - or -
|
87
|
-
|
96
|
+
DecryptSecretMessage.new(...).call
|
88
97
|
|
89
98
|
# On success, fault and exception:
|
90
|
-
#=> <
|
99
|
+
#=> <DecryptSecretMessage ...>
|
91
100
|
```
|
92
101
|
|
93
102
|
> [!TIP]
|
94
|
-
> Class calls is the prefered format due to its readability.
|
103
|
+
> Class calls is the prefered format due to its readability. Read the [Disable Instance Calls](#disable-instance-calls)
|
104
|
+
> section on how to prevent instance style calls.
|
95
105
|
|
96
|
-
Commands can be called with a `!` bang method to raise a
|
97
|
-
`
|
98
|
-
`StandardError` based exception.
|
106
|
+
Commands can be called with a `!` bang method to raise a `Lite::Command::Fault` or the
|
107
|
+
original `StandardError` based exceptions.
|
99
108
|
|
100
109
|
```ruby
|
101
|
-
|
110
|
+
DecryptSecretMessage.call!(...)
|
102
111
|
# - or -
|
103
|
-
|
112
|
+
DecryptSecretMessage.new(...).call!
|
104
113
|
|
105
114
|
# On success:
|
106
|
-
#=> <
|
115
|
+
#=> <DecryptSecretMessage ...>
|
107
116
|
|
108
117
|
# On fault:
|
109
118
|
#=> raises Lite::Command::Fault
|
@@ -114,12 +123,12 @@ CalculatePower.new(...).call!
|
|
114
123
|
|
115
124
|
### Dynamic Faults
|
116
125
|
|
117
|
-
|
118
|
-
|
119
|
-
|
126
|
+
Dynamic faults are custom faults named after your command. This is especially
|
127
|
+
helpful for catching + running custom logic or filtering out specific
|
128
|
+
exceptions from your APM service.
|
120
129
|
|
121
130
|
```ruby
|
122
|
-
class
|
131
|
+
class DecryptSecretMessage < Lite::Command::Base
|
123
132
|
|
124
133
|
def call
|
125
134
|
fail!("Some failure")
|
@@ -127,14 +136,17 @@ class CalculatePower < Lite::Command::Base
|
|
127
136
|
|
128
137
|
private
|
129
138
|
|
139
|
+
# Disable raising dynamic faults on a per command basis.
|
140
|
+
# The `raise_dynamic_faults` configuration option must be
|
141
|
+
# enabled for this method to have any affect.
|
130
142
|
def raise_dynamic_faults?
|
131
|
-
|
143
|
+
false
|
132
144
|
end
|
133
145
|
|
134
146
|
end
|
135
147
|
|
136
|
-
|
137
|
-
#=> raises
|
148
|
+
DecryptSecretMessage.call!(...)
|
149
|
+
#=> raises DecryptSecretMessage::Failure
|
138
150
|
```
|
139
151
|
|
140
152
|
## Context
|
@@ -147,57 +159,49 @@ of its children commands.
|
|
147
159
|
> Attributes that do **NOT** exist on the context will return `nil`.
|
148
160
|
|
149
161
|
```ruby
|
150
|
-
class
|
162
|
+
class DecryptSecretMessage < Lite::Command::Base
|
151
163
|
|
152
164
|
def call
|
153
165
|
# `ctx` is an alias to `context`
|
154
|
-
context.
|
166
|
+
context.decrypted_message = SecretMessage.decrypt(ctx.encrypted_message)
|
155
167
|
end
|
156
168
|
|
157
169
|
end
|
158
170
|
|
159
|
-
cmd =
|
160
|
-
cmd.context.
|
161
|
-
cmd.ctx.
|
171
|
+
cmd = DecryptSecretMessage.call(encrypted_message: "a22j3nkenjk2ne2")
|
172
|
+
cmd.context.decrypted_message #=> "Hello World"
|
173
|
+
cmd.ctx.fake_message #=> nil
|
162
174
|
```
|
163
175
|
|
164
176
|
### Attributes
|
165
177
|
|
166
|
-
Delegate methods for a cleaner command setup
|
167
|
-
|
168
|
-
method
|
169
|
-
|
170
|
-
|
171
|
-
| ---------- | ------ | ------- | ----------- |
|
172
|
-
| `from` | Symbol, String | `:context` | The object containing the attribute. |
|
173
|
-
| `types` | Symbol, String, Array, Proc | | The allowed class types of the attribute value. |
|
174
|
-
| `required` | Symbol, String, Boolean, Proc | `false` | The attribute must be passed to the context or delegatable (no matter the value). |
|
175
|
-
| `filled` | Symbol, String, Boolean, Proc, Hash | `false` | The attribute value must be not be `nil`. Prevent empty values using `{ empty: false }` |
|
176
|
-
|
177
|
-
> [!NOTE]
|
178
|
-
> If optioned with some similar to `filled: true, types: [String, NilClass]`
|
179
|
-
> then `NilClass` for the `types` option will be removed automatically.
|
178
|
+
Delegate methods for a cleaner command setup by declaring `required` and
|
179
|
+
`optional` arguments. `required` only verifies that argument was pass to the
|
180
|
+
context or can be called via defined method or another delegated method.
|
181
|
+
Is an `:if` or `:unless` callable option on a `required` delegation evaluates
|
182
|
+
to false, it will be delegated as an `optional` attribute.
|
180
183
|
|
181
184
|
```ruby
|
182
|
-
class
|
183
|
-
|
184
|
-
attribute :remote_storage, required: true, filled: true, types: RemoteStorage
|
185
|
+
class DecryptSecretMessage < Lite::Command::Base
|
185
186
|
|
186
|
-
|
187
|
-
|
188
|
-
|
187
|
+
required :user, :encrypted_message
|
188
|
+
required :secret_key, from: :user
|
189
|
+
required :algo, :algo_detector, if: :signed_in?
|
190
|
+
optional :version
|
189
191
|
|
190
192
|
def call
|
191
|
-
context.
|
192
|
-
|
193
|
-
|
194
|
-
|
193
|
+
context.decrypted_message = SecretMessage.decrypt(
|
194
|
+
encrypted_message,
|
195
|
+
decryption_key: ENV["DECRYPT_KEY"],
|
196
|
+
algo: algo,
|
197
|
+
version: version || 2
|
198
|
+
)
|
195
199
|
end
|
196
200
|
|
197
201
|
private
|
198
202
|
|
199
|
-
def
|
200
|
-
@
|
203
|
+
def algo_detector
|
204
|
+
@algo_detector ||= AlgoDetector.new(encrypted_message)
|
201
205
|
end
|
202
206
|
|
203
207
|
def signed_in?
|
@@ -207,19 +211,52 @@ class CalculatePower < Lite::Command::Base
|
|
207
211
|
end
|
208
212
|
|
209
213
|
# With valid options:
|
210
|
-
|
211
|
-
cmd
|
212
|
-
cmd.
|
213
|
-
cmd.context.result #=> 6
|
214
|
+
cmd = DecryptSecretMessage.call(user: user, encrypted_message: "ll23k2j3kcms", version: 9)
|
215
|
+
cmd.status #=> "success"
|
216
|
+
cmd.context.decrypted_message #=> "Hola Mundo"
|
214
217
|
|
215
218
|
# With invalid options:
|
216
|
-
cmd =
|
219
|
+
cmd = DecryptSecretMessage.call
|
217
220
|
cmd.status #=> "invalid"
|
218
|
-
cmd.reason #=> "
|
221
|
+
cmd.reason #=> "Encrypted message is a required argument. User is an undefined argument..."
|
219
222
|
cmd.metadata #=> {
|
220
|
-
#=>
|
221
|
-
#=>
|
222
|
-
#=>
|
223
|
+
#=> user: ["is a required argument", "is an undefined argument"],
|
224
|
+
#=> encrypted_message: ["is a required argument"]
|
225
|
+
#=> }
|
226
|
+
```
|
227
|
+
|
228
|
+
### Validations
|
229
|
+
|
230
|
+
The full power of active model valdations is available to validate
|
231
|
+
any and all delegated arguments.
|
232
|
+
|
233
|
+
```ruby
|
234
|
+
class DecryptSecretMessage < Lite::Command::Base
|
235
|
+
|
236
|
+
required :encrypted_message
|
237
|
+
optional :version
|
238
|
+
|
239
|
+
validates :encrypted_message, length: 10..999
|
240
|
+
validates :version, inclusion: { in: %w[v1 v3 v8], allow_blank: true }
|
241
|
+
|
242
|
+
def call
|
243
|
+
context.decrypted_message = SecretMessage.decrypt(ctx.encrypted_message)
|
244
|
+
end
|
245
|
+
|
246
|
+
end
|
247
|
+
|
248
|
+
# With valid options:
|
249
|
+
cmd = DecryptSecretMessage.call(encrypted_message: "ll23k2j3kcms", version: "v1")
|
250
|
+
cmd.status #=> "success"
|
251
|
+
cmd.context.decrypted_message #=> "Hola Mundo"
|
252
|
+
|
253
|
+
# With invalid options:
|
254
|
+
cmd = DecryptSecretMessage.call(encrypted_message: "idk", version: "v23")
|
255
|
+
cmd.status #=> "invalid"
|
256
|
+
cmd.reason #=> "Encrypted message is too short (minimum is 10 character). Version is not included in list..."
|
257
|
+
cmd.metadata #=> {
|
258
|
+
#=> user: ["is not included in list"],
|
259
|
+
#=> encrypted_message: ["is too short (minimum is 10 character)"]
|
223
260
|
#=> }
|
224
261
|
```
|
225
262
|
|
@@ -237,7 +274,7 @@ cmd.metadata #=> {
|
|
237
274
|
> States are automatically transitioned and should **NEVER** be altered manually.
|
238
275
|
|
239
276
|
```ruby
|
240
|
-
cmd =
|
277
|
+
cmd = DecryptSecretMessage.call
|
241
278
|
cmd.state #=> "complete"
|
242
279
|
|
243
280
|
cmd.pending? #=> false
|
@@ -267,53 +304,55 @@ A status of `success` is returned even if the command has **NOT** been executed.
|
|
267
304
|
> Metadata may also be passed to enrich your fault response.
|
268
305
|
|
269
306
|
```ruby
|
270
|
-
class
|
307
|
+
class DecryptSecretMessage < Lite::Command::Base
|
271
308
|
|
272
309
|
def call
|
273
|
-
if
|
274
|
-
|
275
|
-
elsif
|
276
|
-
|
277
|
-
elsif
|
278
|
-
|
279
|
-
"Anything to the power of 1 is 1",
|
280
|
-
{ i18n: "some.key" }
|
281
|
-
)
|
310
|
+
if context.encrypted_message.empty?
|
311
|
+
noop!("No message to decrypt")
|
312
|
+
elsif context.encrypted_message.start_with?("== womp")
|
313
|
+
invalid!("Invalid message start value", i18n: "gb.invalid_start_value")
|
314
|
+
elsif context.encrypted_message.algo?(OldAlgo)
|
315
|
+
failure!("Unsafe encryption algo detected")
|
282
316
|
else
|
283
|
-
|
317
|
+
context.decrypted_message = SecretMessage.decrypt(ctx.encrypted_message)
|
284
318
|
end
|
285
|
-
rescue
|
286
|
-
|
319
|
+
rescue CryptoError => e
|
320
|
+
Apm.report_error(e)
|
321
|
+
error!("Failed decryption due to: #{e}")
|
287
322
|
end
|
288
323
|
|
289
324
|
end
|
290
325
|
|
291
|
-
cmd =
|
292
|
-
cmd.status #=> "
|
293
|
-
cmd.reason #=> "
|
294
|
-
cmd.metadata #=> { i18n: "
|
326
|
+
cmd = DecryptSecretMessage.call(encrypted_message: "2jk3hjeh2hj2jh")
|
327
|
+
cmd.status #=> "invalid"
|
328
|
+
cmd.reason #=> "Invalid message start value"
|
329
|
+
cmd.metadata #=> { i18n: "gb.invalid_start_value" }
|
295
330
|
|
296
331
|
cmd.success? #=> false
|
297
|
-
cmd.noop? #=>
|
298
|
-
cmd.
|
299
|
-
cmd.invalid? #=> false
|
332
|
+
cmd.noop? #=> false
|
333
|
+
cmd.invalid? #=> true
|
334
|
+
cmd.invalid?("Other reason") #=> false
|
300
335
|
cmd.failure? #=> false
|
301
336
|
cmd.error? #=> false
|
302
337
|
|
303
338
|
# `success` or `noop`
|
304
|
-
cmd.ok? #=>
|
339
|
+
cmd.ok? #=> false
|
305
340
|
cmd.ok?("Other reason") #=> false
|
306
341
|
|
307
342
|
# NOT `success`
|
308
343
|
cmd.fault? #=> true
|
309
344
|
cmd.fault?("Other reason") #=> false
|
345
|
+
|
346
|
+
# `invalid` or `failure` or `error`
|
347
|
+
cmd.bad? #=> true
|
348
|
+
cmd.bad?("Other reason") #=> false
|
310
349
|
```
|
311
350
|
|
312
|
-
##
|
351
|
+
## Hooks
|
313
352
|
|
314
|
-
Use
|
315
|
-
|
316
|
-
|
353
|
+
Use hooks to run arbituary code at transition points and on finalized internals.
|
354
|
+
The following is an example of the hooks called for a failed command with a
|
355
|
+
successful child command.
|
317
356
|
|
318
357
|
```ruby
|
319
358
|
-> 1. FooCommand.on_pending
|
@@ -332,11 +371,10 @@ called for a failed command with a successful child command.
|
|
332
371
|
|
333
372
|
### Status Hooks
|
334
373
|
|
335
|
-
Define one or more callbacks that are called during transitions
|
336
|
-
between states.
|
374
|
+
Define one or more callbacks that are called during transitions between states.
|
337
375
|
|
338
376
|
```ruby
|
339
|
-
class
|
377
|
+
class DecryptSecretMessage < Lite::Command::Base
|
340
378
|
|
341
379
|
def call
|
342
380
|
# ...
|
@@ -363,12 +401,32 @@ class CalculatePower < Lite::Command::Base
|
|
363
401
|
end
|
364
402
|
```
|
365
403
|
|
404
|
+
### Attribute Hooks
|
405
|
+
|
406
|
+
Define before attribtue validation callbacks.
|
407
|
+
|
408
|
+
```ruby
|
409
|
+
class DecryptSecretMessage < Lite::Command::Base
|
410
|
+
|
411
|
+
def call
|
412
|
+
# ...
|
413
|
+
end
|
414
|
+
|
415
|
+
private
|
416
|
+
|
417
|
+
def on_before_validation
|
418
|
+
# eg: Normalize context data
|
419
|
+
end
|
420
|
+
|
421
|
+
end
|
422
|
+
```
|
423
|
+
|
366
424
|
### Execution Hooks
|
367
425
|
|
368
426
|
Define before and after callbacks to call around execution.
|
369
427
|
|
370
428
|
```ruby
|
371
|
-
class
|
429
|
+
class DecryptSecretMessage < Lite::Command::Base
|
372
430
|
|
373
431
|
def call
|
374
432
|
# ...
|
@@ -393,7 +451,7 @@ Define one or more callbacks that are called after execution for
|
|
393
451
|
specific statuses.
|
394
452
|
|
395
453
|
```ruby
|
396
|
-
class
|
454
|
+
class DecryptSecretMessage < Lite::Command::Base
|
397
455
|
|
398
456
|
def call
|
399
457
|
# ...
|
@@ -429,16 +487,16 @@ end
|
|
429
487
|
|
430
488
|
## Children
|
431
489
|
|
432
|
-
When building complex commands, its best that you pass the
|
433
|
-
|
434
|
-
|
490
|
+
When building complex commands, its best that you pass the parents context to the
|
491
|
+
child command (unless neccessary) so that it gains automated indexing and the
|
492
|
+
parents `cmd_id`.
|
435
493
|
|
436
494
|
```ruby
|
437
|
-
class
|
495
|
+
class DecryptSecretMessage < Lite::Command::Base
|
438
496
|
|
439
497
|
def call
|
440
|
-
context.merge!(
|
441
|
-
|
498
|
+
context.merge!(decryption_key: ENV["DECRYPT_KEY"])
|
499
|
+
ValidateSecretMessage.call(context)
|
442
500
|
end
|
443
501
|
|
444
502
|
end
|
@@ -446,26 +504,24 @@ end
|
|
446
504
|
|
447
505
|
### Throwing Faults
|
448
506
|
|
449
|
-
Throwing faults allows you to bubble up child faults up to the parent.
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
be bubbled up from the original fault.
|
507
|
+
Throwing faults allows you to bubble up child faults up to the parent. Use it to create
|
508
|
+
branches within your logic and create clean tracing of your command results. You can use
|
509
|
+
`throw!` as a catch-all or any of the bang status method `failure!`. Any `reason` and
|
510
|
+
`metadata` will be bubbled up from the original fault.
|
454
511
|
|
455
512
|
```ruby
|
456
|
-
class
|
513
|
+
class DecryptSecretMessage < Lite::Command::Base
|
457
514
|
|
458
515
|
def call
|
459
|
-
|
516
|
+
context.merge!(decryption_key: ENV["DECRYPT_KEY"])
|
517
|
+
cmd = ValidateSecretMessage.call(context)
|
460
518
|
|
461
|
-
if
|
462
|
-
# Manually throw a specific fault
|
463
|
-
invalid!(command)
|
519
|
+
if cmd.invalid?("Invalid magic numbers")
|
520
|
+
error!(cmd) # Manually throw a specific fault
|
464
521
|
elsif command.fault?
|
465
|
-
# Automatically throws a matching fault
|
466
|
-
throw!(command)
|
522
|
+
throw!(cmd) # Automatically throws a matching fault
|
467
523
|
else
|
468
|
-
|
524
|
+
context.decrypted_message = SecretMessage.decrypt(ctx.encrypted_message)
|
469
525
|
end
|
470
526
|
end
|
471
527
|
|
@@ -483,14 +539,14 @@ This is useful for composing multiple steps into one call.
|
|
483
539
|
> so its no different than just passing the context forward. To change
|
484
540
|
> this behavior, just override the `ok?` method with you logic, eg: just `success`
|
485
541
|
|
486
|
-
> [!
|
542
|
+
> [!WARNING]
|
487
543
|
> Do **NOT** define a call method in this class. The sequence logic is
|
488
544
|
> automatically defined by the sequence class.
|
489
545
|
|
490
546
|
```ruby
|
491
547
|
class ProcessCheckout < Lite::Command::Sequence
|
492
548
|
|
493
|
-
|
549
|
+
required :user
|
494
550
|
|
495
551
|
step FinalizeInvoice
|
496
552
|
step ChargeCard, if: :card_available?
|
@@ -513,13 +569,12 @@ seq = ProcessCheckout.call(...)
|
|
513
569
|
|
514
570
|
## Results
|
515
571
|
|
516
|
-
During any point in the lifecyle of a command, `to_hash` can be
|
517
|
-
|
518
|
-
|
519
|
-
child commands. This helps with debugging and logging.
|
572
|
+
During any point in the lifecyle of a command, `to_hash` can be called to dump out
|
573
|
+
the current values. The `index` value is auto-incremented and the `cmd_id` is static
|
574
|
+
when its passed to child commands. This helps with debugging and logging.
|
520
575
|
|
521
576
|
```ruby
|
522
|
-
command =
|
577
|
+
command = DecryptSecretMessage.call(...)
|
523
578
|
command.to_hash #=> {
|
524
579
|
#=> index: 1,
|
525
580
|
#=> cmd_id: "018c2b95-b764-7615-a924-cc5b910ed1e5",
|
@@ -543,7 +598,7 @@ command.to_hash #=> {
|
|
543
598
|
### Disable Instance Calls
|
544
599
|
|
545
600
|
```ruby
|
546
|
-
class
|
601
|
+
class DecryptSecretMessage < Lite::Command::Base
|
547
602
|
|
548
603
|
private_class_method :new
|
549
604
|
|
@@ -553,48 +608,10 @@ class CalculatePower < Lite::Command::Base
|
|
553
608
|
|
554
609
|
end
|
555
610
|
|
556
|
-
|
611
|
+
DecryptSecretMessage.new(...).call
|
557
612
|
#=> raise NoMethodError
|
558
613
|
```
|
559
614
|
|
560
|
-
### ActiveModel Validations
|
561
|
-
|
562
|
-
```ruby
|
563
|
-
class CalculatePower < Lite::Command::Base
|
564
|
-
include ActiveModel::Validations
|
565
|
-
|
566
|
-
validates :a, :b, presence: true
|
567
|
-
|
568
|
-
def call
|
569
|
-
# ...
|
570
|
-
end
|
571
|
-
|
572
|
-
def read_attribute_for_validation(key)
|
573
|
-
context.public_send(key)
|
574
|
-
end
|
575
|
-
|
576
|
-
private
|
577
|
-
|
578
|
-
def on_before_execution
|
579
|
-
return if valid?
|
580
|
-
|
581
|
-
invalid!(
|
582
|
-
errors.full_messages.to_sentence,
|
583
|
-
errors.to_hash
|
584
|
-
)
|
585
|
-
end
|
586
|
-
|
587
|
-
end
|
588
|
-
|
589
|
-
CalculatePower.call!
|
590
|
-
|
591
|
-
# With `validate!`
|
592
|
-
#=> raise ActiveRecord::RecordInvalid
|
593
|
-
|
594
|
-
# With `valid?`
|
595
|
-
#=> raise Lite::Command::Invalid
|
596
|
-
```
|
597
|
-
|
598
615
|
## Generator
|
599
616
|
|
600
617
|
`rails g command NAME` will generate the following file:
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Lite
|
4
|
+
module Command
|
5
|
+
class InstallGenerator < Rails::Generators::Base
|
6
|
+
|
7
|
+
source_root File.expand_path("../templates", __FILE__)
|
8
|
+
|
9
|
+
def copy_initializer_file
|
10
|
+
copy_file("install.rb", "config/initializers/lite_command.rb")
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|