lite-command 2.1.3 → 3.0.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 +2 -0
- data/CHANGELOG.md +13 -0
- data/Gemfile.lock +16 -13
- 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 +8 -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
|