cuprum 0.9.1 → 0.10.0.rc.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +20 -0
- data/DEVELOPMENT.md +53 -47
- data/README.md +283 -483
- data/lib/cuprum.rb +1 -0
- data/lib/cuprum/chaining.rb +22 -1
- data/lib/cuprum/command.rb +8 -1
- data/lib/cuprum/command_factory.rb +43 -19
- data/lib/cuprum/currying.rb +78 -0
- data/lib/cuprum/currying/curried_command.rb +109 -0
- data/lib/cuprum/operation.rb +1 -1
- data/lib/cuprum/processing.rb +10 -14
- data/lib/cuprum/result.rb +1 -1
- data/lib/cuprum/result_helpers.rb +22 -0
- data/lib/cuprum/rspec/be_a_result_matcher.rb +3 -3
- data/lib/cuprum/steps.rb +275 -0
- data/lib/cuprum/utils/instance_spy.rb +9 -2
- data/lib/cuprum/version.rb +4 -4
- metadata +11 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8ca65deb82f58ec6a0b9706a7eb81f6edc191ecf54d22a9e3155a82370aec83e
|
4
|
+
data.tar.gz: e259228f7b6128c24a0974666165a0655c4188f1011519c8d9dea498c5d59008
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: eafd3d6b85ca035cec50fdbbaacf3f3238cfa1a624de85f0663fdfcef4aeb91298f08ee11915631d3f82069222ca641279c243a6d2087a8b29cc0cf1bd01bd01
|
7
|
+
data.tar.gz: '080089f9d312a1019e934e7ed84e6df92b566c2bff83c9472671c0bed9f3738762345082d99f298af70eab33f1ec8af6809310ee2a3f90cd65544a618e812fb8'
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,25 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## 0.10
|
4
|
+
|
5
|
+
The "One Small Step" Update
|
6
|
+
|
7
|
+
**Note:** This update may have backwards incompatible changes for versions of Ruby before 2.7 when creating commands whose last parameter is an arguments Hash. See [separation of positional and keyword arguments](https://www.ruby-lang.org/en/news/2019/12/12/separation-of-positional-and-keyword-arguments-in-ruby-3-0/) for more information.
|
8
|
+
|
9
|
+
### Commands
|
10
|
+
|
11
|
+
Implemented the `#curry` method, which performs partial application of arguments or keywords.
|
12
|
+
|
13
|
+
#### Chaining
|
14
|
+
|
15
|
+
Added deprecation warnings to all chaining methods, and `Cuprum::Command` no longer includes `Cuprum::Chaining` by default. The `Cuprum::Chaining` module will be removed in version 1.0.
|
16
|
+
|
17
|
+
#### Steps
|
18
|
+
|
19
|
+
Implemented the `#step` method, which extracts the value of the called command (on a success) or halts execution (on a failure).
|
20
|
+
|
21
|
+
Implemented the `#steps` method, which wraps a series of steps and returns first failing result, or the the last result if all steps are passing.
|
22
|
+
|
3
23
|
## 0.9.1
|
4
24
|
|
5
25
|
### Operations
|
data/DEVELOPMENT.md
CHANGED
@@ -1,25 +1,13 @@
|
|
1
1
|
# Development
|
2
2
|
|
3
|
-
## Version 0.
|
4
|
-
|
5
|
-
The "'Tis Not Too Late To Seek A Newer World" Update
|
6
|
-
|
7
|
-
## Version 0.10.0
|
8
|
-
|
9
|
-
The "One Small Step" Update
|
3
|
+
## Version 1.0.0
|
10
4
|
|
11
|
-
|
5
|
+
The "Look On My Works, Ye Mighty, and Despair" Update
|
12
6
|
|
13
|
-
-
|
14
|
-
|
15
|
-
|
16
|
-
-
|
17
|
-
- Called with command (block? method?) that returns a Result.
|
18
|
-
- Raise (and catch) exception on non-success Result (test custom status?)
|
19
|
-
- Otherwise return Result#value.
|
20
|
-
- Deprecate #chain and its related methods
|
21
|
-
|
22
|
-
### Documentation
|
7
|
+
- Integration specs.
|
8
|
+
- Configuration option to raise, warn, ignore discarded results.
|
9
|
+
- Code cleanup: Hash syntax, remove end comments, remove file headers
|
10
|
+
- Status Badges!
|
23
11
|
|
24
12
|
Steps Case Study: |
|
25
13
|
|
@@ -35,44 +23,19 @@ Steps Case Study: |
|
|
35
23
|
Create ContentVersion
|
36
24
|
Tags.each { FindOrCreate Tag }
|
37
25
|
|
38
|
-
### Matcher
|
39
|
-
|
40
|
-
- Handle success(), failure(), failure(SomeError) cases.
|
41
|
-
- Custom matcher to handle additional cases - halted, pending, etc?
|
42
|
-
|
43
|
-
### RSpec
|
44
|
-
|
45
|
-
- be_callable matcher - delegates to respond_to(), but check arguments of
|
46
|
-
private #process method
|
47
|
-
- call_command_step matcher
|
48
|
-
- (optionally) alias be_a_result family as have_result for operations
|
49
|
-
|
50
|
-
## Version 1.0.0
|
51
|
-
|
52
|
-
'The "Look On My Works, Ye Mighty, and Despair" Update'
|
53
|
-
|
54
|
-
- Integration specs.
|
55
|
-
- Configuration option to raise, warn, ignore discarded results.
|
56
|
-
- Code cleanup: Hash syntax, remove end comments, remove file headers
|
57
|
-
|
58
26
|
### Commands
|
59
27
|
|
60
28
|
- Command#to_proc
|
61
|
-
- Remove #chain and its related methods
|
62
|
-
|
63
|
-
### Commands - Built In
|
64
|
-
|
65
|
-
- MapCommand - wraps a command (or proc) and returns Result with value, errors
|
66
|
-
as array
|
67
|
-
- RetryCommand
|
68
29
|
|
69
30
|
## Future Versions
|
70
31
|
|
71
32
|
### Commands
|
72
33
|
|
73
|
-
-
|
34
|
+
- Implement #<<, #>> composition methods.
|
35
|
+
- Calls commands in order passing values.
|
36
|
+
- Return Result early on Failure (or not Success), otherwise final Result.
|
74
37
|
|
75
|
-
####
|
38
|
+
#### DSL
|
76
39
|
|
77
40
|
- ::process - shortcut for defining #process
|
78
41
|
- ::rescue - `rescue StandardError do ... end`, rescues matched errors in #process
|
@@ -89,3 +52,46 @@ Steps Case Study: |
|
|
89
52
|
#### Dependency Injection
|
90
53
|
|
91
54
|
- shorthand for referencing a sequence of operations
|
55
|
+
|
56
|
+
### Commands - Built In
|
57
|
+
|
58
|
+
- MapCommand - wraps a command (or proc) and returns Result with value, errors
|
59
|
+
as array
|
60
|
+
- RetryCommand - takes command, retry count
|
61
|
+
- optional only:, except: - restrict what errors are retried
|
62
|
+
|
63
|
+
### Matcher
|
64
|
+
|
65
|
+
- Handle success(), failure(), failure(SomeError) cases.
|
66
|
+
- Custom matcher to handle additional cases - halted, pending, etc?
|
67
|
+
|
68
|
+
### Middleware
|
69
|
+
|
70
|
+
- Implement Command.subclass
|
71
|
+
- Curries constructor arguments
|
72
|
+
- Implement Cuprum::Middleware
|
73
|
+
- #process takes next command, \*args, \*\*kwargs
|
74
|
+
- calls next command with \*args, \*\*kwargs
|
75
|
+
- .apply takes middleware: array, root: command
|
76
|
+
- Implement Cuprum::AppliedMiddleware < Cuprum::Command
|
77
|
+
- has readers #root (Class), #middleware (Array<Class>)
|
78
|
+
- #initialize
|
79
|
+
- initializes root command (passing constructor parameters)
|
80
|
+
- initializes each middleware command
|
81
|
+
- if Class defining .instance, call .instance
|
82
|
+
- if Class, call .new
|
83
|
+
- if Proc, call #call with constructor parameters
|
84
|
+
- calls Middleware.apply and caches as private #applied
|
85
|
+
- #call
|
86
|
+
- delegates to #applied
|
87
|
+
|
88
|
+
### RSpec
|
89
|
+
|
90
|
+
- be_callable matcher - delegates to respond_to(), but check arguments of
|
91
|
+
private #process method
|
92
|
+
- call_command_step matcher
|
93
|
+
- (optionally) alias be_a_result family as have_result for operations
|
94
|
+
|
95
|
+
### Steps::Strict
|
96
|
+
|
97
|
+
- #step raises exception unless block or method returns a result
|
data/README.md
CHANGED
@@ -23,12 +23,12 @@ Traditional frameworks such as Rails focus on the objects of your application -
|
|
23
23
|
|
24
24
|
### Alternatives
|
25
25
|
|
26
|
-
If you want to extract your logic but Cuprum is not the right solution for you,
|
27
|
-
|
28
|
-
-
|
29
|
-
|
30
|
-
|
31
|
-
|
26
|
+
If you want to extract your logic but Cuprum is not the right solution for you, there are a number of alternatives, including
|
27
|
+
[ActiveInteraction](https://github.com/AaronLasseigne/active_interaction),
|
28
|
+
[Dry::Monads](https://dry-rb.org/gems/dry-monads/),
|
29
|
+
[Interactor](https://github.com/collectiveidea/interactor),
|
30
|
+
[Trailblazer](http://trailblazer.to/) Operations,
|
31
|
+
and [Waterfall](https://github.com/apneadiving/waterfall).
|
32
32
|
|
33
33
|
### Compatibility
|
34
34
|
|
@@ -213,7 +213,61 @@ result.value #=> book
|
|
213
213
|
book.published? #=> false
|
214
214
|
```
|
215
215
|
|
216
|
-
####
|
216
|
+
#### Command Currying
|
217
|
+
|
218
|
+
Cuprum::Command defines the `#curry` method, which allows for partial application of command objects. Partial application (more commonly referred to, if imprecisely, as currying) refers to fixing some number of arguments to a function, resulting in a function with a smaller number of arguments.
|
219
|
+
|
220
|
+
In Cuprum's case, a curried (partially applied) command takes an original command and pre-defines some of its arguments. When the curried command is called, the predefined arguments and/or keywords will be combined with the arguments passed to #call.
|
221
|
+
|
222
|
+
##### Currying Arguments
|
223
|
+
|
224
|
+
We start by defining the base command. In this case, our base command takes two string arguments - a greeting and a person to be greeted.
|
225
|
+
|
226
|
+
```ruby
|
227
|
+
say_command = Cuprum::Command.new do |greeting, person|
|
228
|
+
"#{greeting}, #{person}!"
|
229
|
+
end
|
230
|
+
say_command.call('Hello', 'world')
|
231
|
+
#=> returns a result with value 'Hello, world!'
|
232
|
+
```
|
233
|
+
|
234
|
+
Next, we create a curried command. Here, we pass in one argument. This will set the first argument to always be "Greetings"; therefore, our curried command only takes one argument, the name of the person being greeted.
|
235
|
+
|
236
|
+
```ruby
|
237
|
+
greet_command = say_command.curry('Greetings')
|
238
|
+
greet_command.call('programs')
|
239
|
+
#=> returns a result with value 'Greetings, programs!'
|
240
|
+
```
|
241
|
+
|
242
|
+
Alternatively, we could pass both arguments to `#curry`. In this case, our curried argument does not take any arguments, and will always return the same string.
|
243
|
+
|
244
|
+
```ruby
|
245
|
+
recruit_command = say_command.curry('Greetings', 'starfighter')
|
246
|
+
recruit_command.call
|
247
|
+
#=> returns a result with value 'Greetings, starfighter!'
|
248
|
+
```
|
249
|
+
|
250
|
+
##### Currying Keywords
|
251
|
+
|
252
|
+
We can also pass keywords to `#curry`. Again, we start by defining our base command. In this case, our base command takes a mathematical operation (addition, subtraction, multiplication, etc) and a list of operands.
|
253
|
+
|
254
|
+
```ruby
|
255
|
+
math_command = Cuprum::Command.new do |operands:, operation:|
|
256
|
+
operations.reduce(&operation)
|
257
|
+
end
|
258
|
+
math_command.call(operands: [2, 2], operation: :+)
|
259
|
+
#=> returns a result with value 4
|
260
|
+
```
|
261
|
+
|
262
|
+
Our curried command still takes two keywords, but now the operation keyword is optional. It now defaults to :\*, for multiplication.
|
263
|
+
|
264
|
+
```ruby
|
265
|
+
multiply_command = math_command.curry(operation: :*)
|
266
|
+
multiply_command.call(operands: [3, 3])
|
267
|
+
#=> returns a result with value 9
|
268
|
+
```
|
269
|
+
|
270
|
+
### Composing Commands
|
217
271
|
|
218
272
|
Because Cuprum::Command instances are proper objects, they can be composed like any other object. For example, we could define some basic mathematical operations by composing commands:
|
219
273
|
|
@@ -224,6 +278,8 @@ increment_command.call(2).value #=> 3
|
|
224
278
|
increment_command.call(3).value #=> 4
|
225
279
|
|
226
280
|
add_command = Cuprum::Command.new do |addend, i|
|
281
|
+
# Here, we are composing commands together by calling the increment_command
|
282
|
+
# instance from inside the add_command definition.
|
227
283
|
addend.times { i = increment_command(i).value }
|
228
284
|
|
229
285
|
i
|
@@ -270,289 +326,305 @@ add_two_command.call(1).value #=> 3
|
|
270
326
|
add_two_command.call(8).value #=> 10
|
271
327
|
```
|
272
328
|
|
273
|
-
|
329
|
+
You can achieve even more powerful composition by passing in a command as an argument to a method, or by creating a method that returns a command.
|
330
|
+
|
331
|
+
#### Commands As Arguments
|
274
332
|
|
275
|
-
|
333
|
+
Since commands are objects, they can be passed in as arguments to a method or to another command. For example, consider a command that calls another command a given number of times:
|
276
334
|
|
277
335
|
```ruby
|
278
|
-
|
279
|
-
|
280
|
-
|
336
|
+
class RepeatCommand
|
337
|
+
def initialize(count)
|
338
|
+
@count = count
|
339
|
+
end
|
340
|
+
|
341
|
+
private
|
281
342
|
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
343
|
+
def process(command)
|
344
|
+
@count.times { command.call }
|
345
|
+
end
|
346
|
+
end
|
286
347
|
|
287
|
-
|
288
|
-
|
289
|
-
|
348
|
+
greet_command = Cuprum::Command.new { puts 'Greetings, programs!' }
|
349
|
+
repeat_command = RepeatCommand.new(3)
|
350
|
+
repeat_command.call(greet_command) #=> prints 'Greetings, programs!' 3 times
|
290
351
|
```
|
291
352
|
|
292
|
-
|
353
|
+
This is an implementation of the Strategy pattern, which allows us to customize the behavior of a part of our system by passing in implementation code rather than burying conditionals in our logic.
|
293
354
|
|
294
|
-
|
355
|
+
Consider a more concrete example. Suppose we are running an online bookstore that sells both physuical and electronic books, and serves both domestic and international customers. Depending on what the customer ordered and where they live, our business logic for fulfilling an order will have different shipping instructions.
|
356
|
+
|
357
|
+
Traditionally this would be handled with a conditional inside the order fulfillment code, which adds complexity. However, we can use the Strategy pattern and pass in our shipping code as a command.
|
295
358
|
|
296
359
|
```ruby
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
360
|
+
class DeliverEbook < Cuprum::Command; end
|
361
|
+
|
362
|
+
class ShipDomestic < Cuprum::Command; end
|
363
|
+
|
364
|
+
class ShipInternational < Cuprum::Command; end
|
365
|
+
|
366
|
+
class FulfillOrder < Cuprum::Command
|
367
|
+
def initialize(delivery_command)
|
368
|
+
@delivery_command = delivery_command
|
369
|
+
end
|
301
370
|
|
302
|
-
|
303
|
-
|
371
|
+
private
|
372
|
+
|
373
|
+
def process(book:, user:)
|
374
|
+
# Here we will check inventory, process payments, and so on. The final step
|
375
|
+
# is actually delivering the book to the user:
|
376
|
+
delivery_command.call(book: book, user: user)
|
377
|
+
end
|
378
|
+
end
|
304
379
|
```
|
305
380
|
|
306
|
-
|
381
|
+
This pattern is also useful for testing. When writing specs for the FulfillOrder command, simply pass in a mock double as the delivery command. This removes any need to stub out the implementation of whatever shipping method is used (or worse, calls to external services).
|
307
382
|
|
308
|
-
|
383
|
+
#### Commands As Returned Values
|
384
|
+
|
385
|
+
We can also return commands as an object from a method call or from another command. One use case for this is the Abstract Factory pattern.
|
386
|
+
|
387
|
+
Consider our shipping example, above. The traditional way to generate a shipping command is to use an `if-then-else` or `case` construct, which would be embedded in whatever code is calling `FulfillOrder`. This adds complexity and increases the testing burden.
|
388
|
+
|
389
|
+
Instead, let's create a factory command. This command will take a user and a book, and will return the command used to ship that item.
|
309
390
|
|
310
391
|
```ruby
|
311
|
-
|
312
|
-
|
392
|
+
class ShippingMethod < Cuprum::Command
|
393
|
+
private
|
313
394
|
|
314
|
-
|
315
|
-
|
395
|
+
def process(book:, user:)
|
396
|
+
return DeliverEbook.new(user.email) if book.ebook?
|
316
397
|
|
317
|
-
|
318
|
-
first_command.call #=> Outputs 'First command!' to STDOUT.
|
319
|
-
```
|
398
|
+
return ShipDomestic.new(user.address) if user.address&.domestic?
|
320
399
|
|
321
|
-
|
400
|
+
return ShipInternational.new(user.address) if user.address&.international?
|
322
401
|
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
double_command
|
329
|
-
.chain(increment_command)
|
330
|
-
.chain(square_command)
|
331
|
-
|
332
|
-
# First, the double_commmand is called with 2. This returns a Cuprum::Result
|
333
|
-
# with a value of 4.
|
334
|
-
#
|
335
|
-
# Next, the increment_command is called with 4, returning a result with value 5.
|
336
|
-
#
|
337
|
-
# Finally, the square_command is called with 5, returning a result with a value
|
338
|
-
# of 25. This final result is returned by #call.
|
339
|
-
result = chained_command.call(2)
|
340
|
-
result.class #=> Cuprum::Result
|
341
|
-
result.value #=> 25
|
402
|
+
err = Cuprum::Error.new(message: 'user does not have a valid address')
|
403
|
+
|
404
|
+
failure(err)
|
405
|
+
end
|
406
|
+
end
|
342
407
|
```
|
343
408
|
|
344
|
-
|
409
|
+
Notice that our factory includes error handling - if the user does not have a valid address, that is handled immediately rather than when trying to ship the item.
|
345
410
|
|
346
|
-
The
|
411
|
+
The [Command Factory](#label-Command+Factories) defined by Cuprum is another example of using the Abstract Factory pattern to return command instances. One use case for a command factory would be defining CRUD operations for data records. Depending on the class or the type of record passed in, the factory could return a generic command or a specific command tied to that specific record type.
|
347
412
|
|
348
|
-
|
413
|
+
### Command Steps
|
349
414
|
|
350
|
-
|
415
|
+
Separating out business logic into commands is a powerful tool, but it does come with some overhead, particularly when checking whether a result is passing, or when converting between results and values. When a process has many steps, each of which can fail or return a value, this can result in a lot of boilerplate.
|
351
416
|
|
352
|
-
|
417
|
+
The solution Cuprum provides is the `#step` method, which calls either a named method or a given block. If the result of the block or method is passing, then the `#step` method returns the value of the result.
|
353
418
|
|
354
419
|
```ruby
|
355
|
-
|
356
|
-
Cuprum::Command.new do |attributes|
|
357
|
-
book = Book.where(id: attributes[:id]).first
|
358
|
-
|
359
|
-
return book if book
|
360
|
-
|
361
|
-
Cuprum::Result.new(error: 'Book not found')
|
362
|
-
end
|
363
|
-
create_command =
|
364
|
-
Cuprum::Command.new do |attributes|
|
365
|
-
book = Book.new(attributes)
|
420
|
+
triple_command = Cuprum::Command.new { |i| success(3 * i) }
|
366
421
|
|
367
|
-
|
422
|
+
int = 2
|
423
|
+
int = step { triple_command.call(int) } #=> returns 6
|
424
|
+
int = step { triple_command.call(int) } #=> returns 18
|
425
|
+
```
|
368
426
|
|
369
|
-
|
370
|
-
end
|
427
|
+
Notice that in each step, we are returning the *value* of the result from `#step`, not the result itself. This means we do not need explicit calls to the `#value` method.
|
371
428
|
|
372
|
-
|
429
|
+
Of course, not all commands return a passing result. If the result of the block or method is failing, then `#step` will throw `:cuprum_failed_result` and the result, immediately halting the execution chain. If the `#step` method is used inside a command definition (or inside a `#steps` block; [see below](#label-Using+Steps+Outside+Of+Commands)), that symbol will be caught and the failing result returned by `#call`.
|
373
430
|
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
hsh = { id: 0, title: 'Journey to the West' }
|
378
|
-
result = find_or_create_command.call(hsh)
|
379
|
-
book = result.value
|
380
|
-
book.id #=> 0
|
381
|
-
book.title #=> 'Journey to the West'
|
382
|
-
result.success? #=> true
|
383
|
-
result.error #=> nil
|
431
|
+
```ruby
|
432
|
+
divide_command = Cuprum::Command.new do |dividend, divisor|
|
433
|
+
return failure('divide by zero') if divisor.zero?
|
384
434
|
|
385
|
-
|
386
|
-
|
387
|
-
# creates a new book with the attributes, returning a passing result.
|
388
|
-
hsh = { id: 1, title: 'The Ramayana' }
|
389
|
-
result = find_or_create_command.call(hsh)
|
390
|
-
book = result.value
|
391
|
-
book.id #=> 1
|
392
|
-
book.title #=> 'The Ramayana'
|
393
|
-
result.success? #=> true
|
394
|
-
result.error #=> nil
|
435
|
+
success(dividend / divisor)
|
436
|
+
end
|
395
437
|
|
396
|
-
|
397
|
-
|
398
|
-
# is unable to create a new book with the attributes, returning the
|
399
|
-
# (non-persisted) book and adding the validation errors.
|
400
|
-
hsh = { id: 2, title: nil }
|
401
|
-
result = find_or_create_command.call(hsh)
|
402
|
-
book = result.value
|
403
|
-
book.id #=> 2
|
404
|
-
book.title #=> nil
|
405
|
-
result.success? #=> false
|
406
|
-
result.error #=> ["Title can't be blank"]
|
438
|
+
value = step { divide_command.call(10, 5) } #=> returns 2
|
439
|
+
value = step { divide_command.call(2, 0) } #=> throws :cuprum_failed_result
|
407
440
|
```
|
408
441
|
|
409
|
-
|
442
|
+
Here, the `divide_command` can either return a passing result (if the divisor is not zero) or a failing result (if the divisor is zero). When wrapped in a `#step`, the failing result is then thrown, halting execution.
|
410
443
|
|
411
|
-
|
444
|
+
This is important when using a sequence of steps. Let's consider a case study - reserving a book from the library. This entails several steps, each of which could potentially fail:
|
412
445
|
|
413
|
-
|
446
|
+
- Validating that the user can reserve books. Maybe the user has too many unpaid fines.
|
447
|
+
- Finding the requested book in the library system. Maybe the requested title isn't in the system.
|
448
|
+
- Placing a reservation on the book. Maybe there are no copies of the book available to reserve.
|
414
449
|
|
415
|
-
|
450
|
+
Using `#step`, as soon as one of the subtasks fails then the command will immediately return the failed value. This prevents us from hitting later subtasks with invalid data, it returns the actual failing result for analytics and for displaying a useful error message to the user, and it avoids the overhead (and the boilerplate) of exception-based failure handling.
|
416
451
|
|
417
452
|
```ruby
|
418
|
-
|
419
|
-
|
420
|
-
|
453
|
+
class CheckUserStatus < Cuprum::Command; end
|
454
|
+
|
455
|
+
class CreateBookReservation < Cuprum::Command; end
|
456
|
+
|
457
|
+
class FindBookByTitle < Cuprum::Command; end
|
458
|
+
|
459
|
+
class ReserveBookByTitle < Cuprum::Command
|
460
|
+
private
|
461
|
+
|
462
|
+
def process(title:, user:)
|
463
|
+
# If CheckUserStatus fails, #process will immediately return that result.
|
464
|
+
# For this step, we already have the user, so we don't need to use the
|
465
|
+
# result value.
|
466
|
+
step { CheckUserStatus.new.call(user) }
|
467
|
+
|
468
|
+
# Here, we are looking up the requested title. In this case, we will need
|
469
|
+
# the book object, so we save it as a variable. Notice that we don't need
|
470
|
+
# an explicit #value call - #step handles that for us.
|
471
|
+
book = step { FindBookByTitle.new.call(title) }
|
472
|
+
|
473
|
+
# Finally, we want to reserve the book. Since this is the last subtask, we
|
474
|
+
# don't strictly need to use #step. However, it's good practice, especially
|
475
|
+
# if we might need to add more steps to the command in the future.
|
476
|
+
step { CreateBookReservation.new.call(book: book, user: user) }
|
421
477
|
end
|
422
|
-
|
423
|
-
|
424
|
-
.tap_result do |result|
|
425
|
-
puts "The result value was #{result.inspect}"
|
426
|
-
end
|
478
|
+
end
|
479
|
+
```
|
427
480
|
|
428
|
-
|
429
|
-
|
481
|
+
First, our user may not have borrowing privileges. In this case, `CheckUserStatus` will fail, and neither of the subsequent steps will be called. The `#call` method will return the failing result from `CheckUserStatus`.
|
482
|
+
|
483
|
+
```ruby
|
484
|
+
result = ReserveBookByTitle.new.call(
|
485
|
+
title: 'The C Programming Language',
|
486
|
+
user: 'Ed Dillinger'
|
487
|
+
)
|
430
488
|
result.class #=> Cuprum::Result
|
431
|
-
result.value #=> 'Example value'
|
432
489
|
result.success? #=> false
|
433
|
-
result.error #=> '
|
490
|
+
result.error #=> 'not authorized to reserve book'
|
434
491
|
```
|
435
492
|
|
436
|
-
|
493
|
+
Second, our user may be valid but our requested title may not exist in the system. In this case, `FindBookByTitle` will fail, and the final step will not be called. The `#call` method will return the failing result from `FindBookByTitle`.
|
437
494
|
|
438
495
|
```ruby
|
439
|
-
|
440
|
-
|
441
|
-
|
496
|
+
result = ReserveBookByTitle.new.call(
|
497
|
+
title: 'Using GOTO For Fun And Profit',
|
498
|
+
user: 'Alan Bradley'
|
499
|
+
)
|
500
|
+
result.class #=> Cuprum::Result
|
501
|
+
result.success? #=> false
|
502
|
+
result.error #=> 'title not found'
|
503
|
+
```
|
442
504
|
|
443
|
-
|
505
|
+
Third, our user and book may be valid, but all of the copies are checked out. In this case, each of the steps will be called, and the `#call` method will return the failing result from `CreateBookReservation`.
|
444
506
|
|
445
|
-
|
446
|
-
|
507
|
+
```ruby
|
508
|
+
result = ReserveBookByTitle.new.call(
|
509
|
+
title: 'Design Patterns: Elements of Reusable Object-Oriented Software',
|
510
|
+
user: 'Alan Bradley'
|
511
|
+
)
|
512
|
+
result.class #=> Cuprum::Result
|
513
|
+
result.success? #=> false
|
514
|
+
result.error #=> 'no copies available'
|
515
|
+
```
|
447
516
|
|
448
|
-
|
449
|
-
find_book_command
|
450
|
-
.tap_result(on: :success) do |result|
|
451
|
-
render :show, locals: { book: result.value }
|
452
|
-
end
|
453
|
-
.tap_result(on: :failure) do
|
454
|
-
redirect_to books_path
|
455
|
-
end
|
517
|
+
Finally, if each of the steps succeeds, the `#call` method will return the result of the final step.
|
456
518
|
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
result = chained_command.call(valid_id)
|
519
|
+
```ruby
|
520
|
+
result = ReserveBookByTitle.new.call(
|
521
|
+
title: 'The C Programming Language',
|
522
|
+
user: 'Alan Bradley'
|
523
|
+
)
|
463
524
|
result.class #=> Cuprum::Result
|
464
|
-
result.value #=> an instance of Book
|
465
525
|
result.success? #=> true
|
466
|
-
result.
|
467
|
-
|
468
|
-
# Calls find_book_command with the id, which queries for the book and returns a
|
469
|
-
# result with a value of nil and the error message. Because the result is
|
470
|
-
# failing, the first block is skipped. The second block is then evaluated,
|
471
|
-
# calling the redirect_to method. Finally, the result of find_book_command is
|
472
|
-
# returned unchanged.
|
473
|
-
result = chained_command.call(invalid_id)
|
474
|
-
result.class #=> Cuprum::Result
|
475
|
-
result.value #=> nil
|
476
|
-
result.success? #=> false
|
477
|
-
result.error #=> ['Unable to find book with id invalid_id']
|
526
|
+
result.value #=> an instance of BookReservation
|
478
527
|
```
|
479
528
|
|
480
|
-
####
|
529
|
+
#### Using Methods As Steps
|
530
|
+
|
531
|
+
Steps can also be defined as method calls. Instead of providing a block to `#step`, provide the name of the method as the first argument, either as a symbol or as a string. Any subsequent arguments, keywords, or a block is passed to the method when it is called.
|
481
532
|
|
482
|
-
|
533
|
+
A step defined with a method behaves the same as a step defined with a block. If the method returns a successful result, then `#step` will return the value of the result. If the method returns a failing result, then `#step` will throw `:cuprum_failed_result` and the result, to be caught by the `#process` method or the containing `#steps` block.
|
483
534
|
|
484
|
-
|
535
|
+
We can use this to rewrite our `ReserveBookByTitle` command to use methods:
|
485
536
|
|
486
537
|
```ruby
|
487
|
-
|
488
|
-
|
489
|
-
|
538
|
+
class ReserveBookByTitle < Cuprum::Result
|
539
|
+
private
|
540
|
+
|
541
|
+
def check_user_status(user)
|
542
|
+
CheckUserStatus.new(user)
|
490
543
|
end
|
491
|
-
|
492
|
-
|
544
|
+
|
545
|
+
def create_book_reservation(book:, user:)
|
546
|
+
CreateBookReservation.new(book: book, user: user)
|
493
547
|
end
|
494
|
-
|
495
|
-
|
548
|
+
|
549
|
+
def find_book_by_title(title)
|
550
|
+
FindBookByTitle.new.call(title)
|
496
551
|
end
|
497
552
|
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
# the previous result is discarded and a new result is created with the string
|
507
|
-
# value starting with 'The last result was ...'.
|
508
|
-
result = chained_command.call
|
509
|
-
result.class #=> Cuprum::Result
|
510
|
-
result.value #=> 'The last result was a failure.'
|
511
|
-
result.success? #=> true
|
512
|
-
result.error #=> nil
|
553
|
+
def process(title:, user:)
|
554
|
+
step :check_user_status, user
|
555
|
+
|
556
|
+
book = step :find_book_by_title, title
|
557
|
+
|
558
|
+
create_book_reservation, book: book, user: user
|
559
|
+
end
|
560
|
+
end
|
513
561
|
```
|
514
562
|
|
515
|
-
|
563
|
+
In this case, our methods simply delegate to our previously defined commands. However, a more complex example could include other logic in each method, or even a sequence of steps defining subtasks for the method. The only requirement is that the method returns a result. You can use the `#success` helpers to wrap a non-result value, or the `#failure` helper to generate a failing result.
|
516
564
|
|
517
|
-
|
565
|
+
#### Using Steps Outside Of Commands
|
518
566
|
|
519
|
-
|
567
|
+
Steps can also be used outside of a command. For example, a controller action might define a sequence of steps to run when the corresponding endpoint is called.
|
520
568
|
|
521
|
-
|
569
|
+
To use steps outside of a command, include the `Cuprum::Steps` module. Then, each sequence of steps should be wrapped in a `#steps` block as follows:
|
522
570
|
|
523
571
|
```ruby
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
# After the build step is run, we validate the comment.
|
531
|
-
chain!(ValidateCommentCommand.new)
|
532
|
-
#
|
533
|
-
# If the validation passes, we then save the comment.
|
534
|
-
chain!(SaveCommentCommand.new, on: :success)
|
535
|
-
end
|
572
|
+
steps do
|
573
|
+
step { check_something }
|
574
|
+
|
575
|
+
obj = step { find_something }
|
576
|
+
|
577
|
+
step :do_something, with: obj
|
536
578
|
end
|
579
|
+
```
|
537
580
|
|
538
|
-
|
581
|
+
Each step will be executed in sequence until a failing result is returned by the block or method. The `#steps` block will return that failing result. If no step returns a failing result, then the return value of the block will be wrapped in a result and returned by `#steps`.
|
539
582
|
|
540
|
-
|
541
|
-
'packages of eight?'
|
542
|
-
result = CreateCommentCommand.new.call({ user_id: '12345', body: body })
|
583
|
+
Let's consider the example of a controller action for creating a new resource. This would have several steps, each of which can fail:
|
543
584
|
|
544
|
-
|
545
|
-
|
546
|
-
|
585
|
+
- First, we build a new instance of the resource with the provided attributes. This can fail if the attributes are incompatible with the resource, e.g. with extra attributes not included in the resource's table columns.
|
586
|
+
- Second, we run validations on the resource itself. This can fail if the attributes do not match the expected format.
|
587
|
+
- Finally, we persist the resource to the database. This can fail if the record violates any database constraints, or if the database itself is unavailable.
|
588
|
+
|
589
|
+
```ruby
|
590
|
+
class BooksController
|
591
|
+
include Cuprum::Steps
|
547
592
|
|
548
|
-
|
593
|
+
def create
|
594
|
+
attributes = params[:books]
|
595
|
+
result = steps do
|
596
|
+
@book = step :build_book, attributes
|
549
597
|
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
598
|
+
step :run_validations, @book
|
599
|
+
|
600
|
+
step :persist_book, book
|
601
|
+
end
|
602
|
+
|
603
|
+
result.success ? redirect_to(@book) : render(:edit)
|
604
|
+
end
|
605
|
+
|
606
|
+
private
|
607
|
+
|
608
|
+
def build_book(attributes)
|
609
|
+
success(Book.new(attributes))
|
610
|
+
rescue InvalidAttributes
|
611
|
+
failure('attributes are invalid')
|
612
|
+
end
|
613
|
+
|
614
|
+
def persist_book(book)
|
615
|
+
book.save ? success(book) : failure('unable to persist book')
|
616
|
+
end
|
617
|
+
|
618
|
+
def run_validations(book)
|
619
|
+
book.valid? ? success : failure('book is invalid')
|
620
|
+
end
|
621
|
+
end
|
554
622
|
```
|
555
623
|
|
624
|
+
A few things to note about this example. First, we have a couple of examples of wrapping existing code in a result, both by rescuing exceptions (in `#build_book`) or by checking a returned status (in `#persist_book`). Second, note that each of our helper methods can be reused in other controller actions. For even more encapsulation and reusability, the next step might be to convert those methods to commands of their own.
|
625
|
+
|
626
|
+
You can define even more complex logic by defining multiple `#steps` blocks. Each block represents a series of tasks that will terminate on the first failure. Steps blocks can even be nested in one another, or inside a `#process` method.
|
627
|
+
|
556
628
|
### Results
|
557
629
|
|
558
630
|
require 'cuprum'
|
@@ -1073,276 +1145,4 @@ operation.success? #=> true
|
|
1073
1145
|
|
1074
1146
|
## Reference
|
1075
1147
|
|
1076
|
-
|
1077
|
-
|
1078
|
-
require 'cuprum/built_in/identity_command'
|
1079
|
-
|
1080
|
-
[Class Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum%2FBuiltIn%2FIdentityCommand)
|
1081
|
-
|
1082
|
-
Cuprum::BuiltIn::IdentityCommand defines the following methods:
|
1083
|
-
|
1084
|
-
#### `#call`
|
1085
|
-
|
1086
|
-
call(value) #=> Cuprum::Result
|
1087
|
-
|
1088
|
-
Returns a result, whose `#value` is equal to the given value.
|
1089
|
-
|
1090
|
-
### Cuprum::BuiltIn::IdentityOperation
|
1091
|
-
|
1092
|
-
require 'cuprum/built_in/identity_operation'
|
1093
|
-
|
1094
|
-
[Class Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum%2FBuiltIn%2FIdentityOperation)
|
1095
|
-
|
1096
|
-
Cuprum::BuiltIn::IdentityOperation defines the following methods:
|
1097
|
-
|
1098
|
-
#### `#call`
|
1099
|
-
|
1100
|
-
call(value) #=> Cuprum::BuiltIn::IdentityOperation
|
1101
|
-
|
1102
|
-
Sets the last result to a new result, whose `#value` is equal to the given value.
|
1103
|
-
|
1104
|
-
### Cuprum::BuiltIn::NullCommand
|
1105
|
-
|
1106
|
-
require 'cuprum/built_in/null_command'
|
1107
|
-
|
1108
|
-
[Class Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum%2FBuiltIn%2FNullCommand)
|
1109
|
-
|
1110
|
-
Cuprum::BuiltIn::NullCommand defines the following methods:
|
1111
|
-
|
1112
|
-
#### `#call`
|
1113
|
-
|
1114
|
-
call(*args, **keywords) { ... } #=> Cuprum::Result
|
1115
|
-
|
1116
|
-
Returns a result with nil value. Any arguments or keywords are ignored.
|
1117
|
-
|
1118
|
-
### Cuprum::BuiltIn::NullOperation
|
1119
|
-
|
1120
|
-
require 'cuprum/built_in/null_operation'
|
1121
|
-
|
1122
|
-
[Class Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum%2FBuiltIn%2FNullOperation)
|
1123
|
-
|
1124
|
-
Cuprum::BuiltIn::NullOperation defines the following methods:
|
1125
|
-
|
1126
|
-
#### `#call`
|
1127
|
-
|
1128
|
-
call(*args, **keywords) { ... } #=> Cuprum::BuiltIn::NullOperation
|
1129
|
-
|
1130
|
-
Sets the last result to a result with nil value. Any arguments or keywords are ignored.
|
1131
|
-
|
1132
|
-
### Cuprum::Command
|
1133
|
-
|
1134
|
-
require 'cuprum'
|
1135
|
-
|
1136
|
-
[Class Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum%2FCommand)
|
1137
|
-
|
1138
|
-
A Cuprum::Command defines the following methods:
|
1139
|
-
|
1140
|
-
#### `#initialize`
|
1141
|
-
|
1142
|
-
initialize { |*arguments, **keywords, &block| ... } #=> Cuprum::Command
|
1143
|
-
|
1144
|
-
Returns a new instance of Cuprum::Command. If a block is given, the `#call` method will wrap the block and set the result `#value` to the return value of the block. This overrides the implementation in `#process`, if any.
|
1145
|
-
|
1146
|
-
[Method Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum/Command#initialize-instance_method)
|
1147
|
-
|
1148
|
-
#### `#call`
|
1149
|
-
|
1150
|
-
call(*arguments, **keywords) { ... } #=> Cuprum::Result
|
1151
|
-
|
1152
|
-
Executes the logic encoded in the constructor block, or the #process method if no block was passed to the constructor.
|
1153
|
-
|
1154
|
-
[Method Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum/Command#call-instance_method)
|
1155
|
-
|
1156
|
-
#### `#chain`
|
1157
|
-
|
1158
|
-
chain(on: nil) { |result| ... } #=> Cuprum::Command
|
1159
|
-
|
1160
|
-
Registers a command or block to run after the current command, or after the last chained command if the current command already has one or more chained command(s). This creates and modifies a copy of the current command. See Chaining Commands, below.
|
1161
|
-
|
1162
|
-
chain(command, on: nil) #=> Cuprum::Command
|
1163
|
-
|
1164
|
-
The command will be passed the `#value` of the previous command result as its parameter, and the result of the chained command will be returned (or passed to the next chained command, if any).
|
1165
|
-
|
1166
|
-
The block will be passed the #result of the previous command as its parameter.
|
1167
|
-
|
1168
|
-
If the block returns a Cuprum::Result (or an object responding to `#to_cuprum_result`), the block result will be returned (or passed to the next chained command, if any). If the block returns any other value (including `nil`), the `#result` of the previous command will be returned or passed to the next command.
|
1169
|
-
|
1170
|
-
[Method Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum/Command#chain-instance_method)
|
1171
|
-
|
1172
|
-
#### `#chain!`
|
1173
|
-
|
1174
|
-
*(Protected Method)*
|
1175
|
-
|
1176
|
-
chain!(on: nil) { |result| ... } #=> Cuprum::Command
|
1177
|
-
|
1178
|
-
chain!(command, on: nil) #=> Cuprum::Command
|
1179
|
-
|
1180
|
-
As `#chain`, but modifies the current command instead of creating a clone.
|
1181
|
-
|
1182
|
-
[Method Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum/Command#chain!-instance_method)
|
1183
|
-
|
1184
|
-
#### `#tap_result`
|
1185
|
-
|
1186
|
-
tap_result(on: nil) { |previous_result| } #=> Cuprum::Result
|
1187
|
-
|
1188
|
-
Creates a copy of the command, and then chains the block to execute after the command implementation. When #call is executed, each chained block will be yielded the previous result, and the previous result returned or yielded to the next block. The return value of the block is discarded.
|
1189
|
-
|
1190
|
-
If the `on` parameter is set to `:success`, the block will be called if the last result is successful. If the `on` parameter is set to `:failure`, the block will be called if the last result is failing. Finally, if the `on` parameter is set to `:always` or to `nil`, the block will always be called.
|
1191
|
-
|
1192
|
-
[Method Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum/Command#tap_result-instance_method)
|
1193
|
-
|
1194
|
-
#### `#tap_result!`
|
1195
|
-
|
1196
|
-
*(Protected Method)*
|
1197
|
-
|
1198
|
-
tap_result!(on: nil) { |previous_result| } #=> Cuprum::Result
|
1199
|
-
|
1200
|
-
As `#tap_result`, but modifies the current command instead of creating a clone.
|
1201
|
-
|
1202
|
-
[Method Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum/Command#tap_result!-instance_method)
|
1203
|
-
|
1204
|
-
#### `#yield_result`
|
1205
|
-
|
1206
|
-
yield_result(on: nil) { |previous_result| } #=> Cuprum::Result
|
1207
|
-
|
1208
|
-
Creates a copy of the command, and then chains the block to execute after the command implementation. When #call is executed, each chained block will be yielded the previous result, and the return value wrapped in a result and returned or yielded to the next block.
|
1209
|
-
|
1210
|
-
If the `on` parameter is set to `:success`, the block will be called if the last result is successful. If the `on` parameter is set to `:failure`, the block will be called if the last result is failing. Finally, if the `on` parameter is set to `:always` or to `nil`, the block will always be called.
|
1211
|
-
|
1212
|
-
[Method Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum/Command#yield_result-instance_method)
|
1213
|
-
|
1214
|
-
#### `#yield_result!`
|
1215
|
-
|
1216
|
-
*(Protected Method)*
|
1217
|
-
|
1218
|
-
yield_result!(on: nil) { |previous_result| } #=> Cuprum::Result
|
1219
|
-
|
1220
|
-
As `#yield_result`, but modifies the current command instead of creating a clone.
|
1221
|
-
|
1222
|
-
[Method Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum/Command#yield_result!-instance_method)
|
1223
|
-
|
1224
|
-
### Cuprum::Operation
|
1225
|
-
|
1226
|
-
require 'cuprum'
|
1227
|
-
|
1228
|
-
[Class Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum%2FOperation)
|
1229
|
-
|
1230
|
-
A Cuprum::Operation inherits the methods from Cuprum::Command (see above), and defines the following additional methods:
|
1231
|
-
|
1232
|
-
#### `#called?`
|
1233
|
-
|
1234
|
-
called?() #=> true, false
|
1235
|
-
|
1236
|
-
True if the operation has been called and there is a result available by calling `#result` or one of the delegated methods, otherwise false.
|
1237
|
-
|
1238
|
-
[Method Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum/Operation#called%3F-instance_method)
|
1239
|
-
|
1240
|
-
#### `#reset!`
|
1241
|
-
|
1242
|
-
reset!()
|
1243
|
-
|
1244
|
-
Clears the most recent result and resets `#called?` to false. This frees the result and any linked data for garbage collection. It also clears any internal state from the operation.
|
1245
|
-
|
1246
|
-
[Method Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum/Operation#reset!-instance_method)
|
1247
|
-
|
1248
|
-
#### `#result`
|
1249
|
-
|
1250
|
-
result() #=> Cuprum::Result
|
1251
|
-
|
1252
|
-
The most recent result, from the previous time `#call` was executed for the operation.
|
1253
|
-
|
1254
|
-
[Method Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum/Operation#result-instance_method)
|
1255
|
-
|
1256
|
-
### Cuprum::Result
|
1257
|
-
|
1258
|
-
[Class Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum%2FResult)
|
1259
|
-
|
1260
|
-
A Cuprum::Result defines the following methods:
|
1261
|
-
|
1262
|
-
#### `#==`
|
1263
|
-
|
1264
|
-
==(other) #=> true, false
|
1265
|
-
|
1266
|
-
Performs a fuzzy comparison with the other object. At a minimum, the other object must respond to `#value`, `#success?`, `#error` and the values of `other.value`, `other.success?`, and `other.error` must be equal to the corresponding value on the result. Returns true if all values match, otherwise returns false.
|
1267
|
-
|
1268
|
-
#### `#error`
|
1269
|
-
|
1270
|
-
error() #=> nil
|
1271
|
-
|
1272
|
-
The error generated by the command, or `nil` if no error was generated.
|
1273
|
-
|
1274
|
-
[Method Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum/Result#error-instance_method)
|
1275
|
-
|
1276
|
-
#### `#failure?`
|
1277
|
-
|
1278
|
-
failure?() #=> true, false
|
1279
|
-
|
1280
|
-
True if the command generated an error or was marked as failing. Otherwise false.
|
1281
|
-
|
1282
|
-
[Method Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum/Result#failure%3F-instance_method)
|
1283
|
-
|
1284
|
-
#### `#success?`
|
1285
|
-
|
1286
|
-
success?() #=> true, false
|
1287
|
-
|
1288
|
-
True if the command did not generate an error, or the result has an error but was marked as passing. Otherwise false.
|
1289
|
-
|
1290
|
-
[Method Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum/Result#success%3F-instance_method)
|
1291
|
-
|
1292
|
-
#### `#value`
|
1293
|
-
|
1294
|
-
value() #=> Object
|
1295
|
-
|
1296
|
-
The value returned by the command. For example, for an increment command that added 1 to a given integer, the `#value` of the result object would be the incremented integer.
|
1297
|
-
|
1298
|
-
[Method Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum/Result#value-instance_method)
|
1299
|
-
|
1300
|
-
### Cuprum::Utilities::InstanceSpy
|
1301
|
-
|
1302
|
-
require 'cuprum/utils/instance_spy'
|
1303
|
-
|
1304
|
-
[Class Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum%2FUtils%2FInstanceSpy)
|
1305
|
-
|
1306
|
-
Utility module for instrumenting calls to the #call method of any instance of a command class. This can be used to unobtrusively test the functionality of code that calls a command without providing a reference to the command instance, such as chained commands or methods that create and call a command instance.
|
1307
|
-
|
1308
|
-
#### `::clear_spies`
|
1309
|
-
|
1310
|
-
clear_spies() #=> nil
|
1311
|
-
|
1312
|
-
Retires all spies. Subsequent calls to the #call method on command instances will not be mirrored to existing spy objects. Calling this method after each test or example that uses an instance spy is recommended.
|
1313
|
-
|
1314
|
-
after(:example) { Cuprum::Utils::InstanceSpy.clear_spies }
|
1315
|
-
|
1316
|
-
[Method Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum/Utils/InstanceSpy#clear_spies%3F-instance_method)
|
1317
|
-
|
1318
|
-
#### `::spy_on`
|
1319
|
-
|
1320
|
-
spy_on(command_class) #=> InstanceSpy
|
1321
|
-
spy_on(command_class) { |spy| ... } #=> nil
|
1322
|
-
|
1323
|
-
Finds or creates a spy object for the given module or class. Each time that the #call method is called for an object of the given type, the spy's #call method will be invoked with the same arguments and block. If `#spy_on` is called with a block, the instance spy will be yielded to the block; otherwise, the spy will be returned.
|
1324
|
-
|
1325
|
-
# Observing calls to instances of a command.
|
1326
|
-
spy = Cuprum::Utils::InstanceSpy.spy_on(CustomCommand)
|
1327
|
-
|
1328
|
-
expect(spy).to receive(:call).with(1, 2, 3, four: '4')
|
1329
|
-
|
1330
|
-
CustomCommand.new.call(1, 2, 3, four: '4')
|
1331
|
-
|
1332
|
-
# Observing calls to a chained command.
|
1333
|
-
spy = Cuprum::Utils::InstanceSpy.spy_on(ChainedCommand)
|
1334
|
-
|
1335
|
-
expect(spy).to receive(:call)
|
1336
|
-
|
1337
|
-
Cuprum::Command.new {}.
|
1338
|
-
chain { |result| ChainedCommand.new.call(result) }.
|
1339
|
-
call
|
1340
|
-
|
1341
|
-
# Block syntax
|
1342
|
-
Cuprum::Utils::InstanceSpy.spy_on(CustomCommand) do |spy|
|
1343
|
-
expect(spy).to receive(:call)
|
1344
|
-
|
1345
|
-
CustomCommand.new.call
|
1346
|
-
end
|
1347
|
-
|
1348
|
-
[Method Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum/Utils/InstanceSpy#spy_on%3F-instance_method)
|
1148
|
+
Method and class documentation is available courtesy of [RubyDoc](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master).
|