cuprum 0.8.0 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/CHANGELOG.md +85 -9
- data/DEVELOPMENT.md +58 -42
- data/README.md +414 -754
- data/lib/cuprum.rb +1 -25
- data/lib/cuprum/built_in/identity_command.rb +4 -4
- data/lib/cuprum/chaining.rb +139 -148
- data/lib/cuprum/command.rb +24 -15
- 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/error.rb +37 -0
- data/lib/cuprum/errors/command_not_implemented.rb +35 -0
- data/lib/cuprum/errors/operation_not_called.rb +35 -0
- data/lib/cuprum/operation.rb +37 -28
- data/lib/cuprum/processing.rb +45 -82
- data/lib/cuprum/result.rb +52 -127
- data/lib/cuprum/result_helpers.rb +14 -105
- data/lib/cuprum/rspec.rb +8 -0
- data/lib/cuprum/rspec/be_a_result.rb +19 -0
- data/lib/cuprum/rspec/be_a_result_matcher.rb +286 -0
- data/lib/cuprum/steps.rb +275 -0
- data/lib/cuprum/utils/instance_spy.rb +9 -2
- data/lib/cuprum/version.rb +1 -1
- metadata +14 -8
- data/lib/cuprum/errors/process_not_implemented_error.rb +0 -14
- data/lib/cuprum/utils/result_not_empty_warning.rb +0 -72
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 87866e465634782fef9cd2ce7bebf42f56eeb789d235874a280134e6d2431104
|
4
|
+
data.tar.gz: e60264b79e82f77f5cbc2149c6522f0ef74fad2c198238ea6db3338ba3e35ce3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 89aa00632c89bfddcc3063d0aff7344af81e36e4568d6899ef16c5a80b464ae43f7b530f689fb6a8e8f7e2c3987ee4463014744ab242b20828959483e070d968
|
7
|
+
data.tar.gz: 05cf7814eeff05e179bc006d1a9c80f8bc3ca7b9e4ca12256b34f65bc6aa1ba253e12a2c00fe411e38162b65d9d1a5a1ac6a3df3e820af40cec2944291b6e3a5
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,81 @@
|
|
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
|
+
|
23
|
+
## 0.9.1
|
24
|
+
|
25
|
+
### Operations
|
26
|
+
|
27
|
+
Delegate Operation#status to the most recent result.
|
28
|
+
|
29
|
+
### RSpec
|
30
|
+
|
31
|
+
The #be_a_passing_result macro now automatically adds a `with_error(nil)` expectation.
|
32
|
+
|
33
|
+
- This causes the error (if any) to be displayed when matching a failing result.
|
34
|
+
- This may break some cases when expecting a passing result that still has an error; in these cases, add an error expectation to the call, e.g. `expect().to be_a_passing_result.with_error(some_error)`.
|
35
|
+
|
36
|
+
Improve failure message of BeAResultMatcher under some circumstances.
|
37
|
+
|
38
|
+
## 0.9.0
|
39
|
+
|
40
|
+
The "'Tis Not Too Late To Seek A Newer World" Update
|
41
|
+
|
42
|
+
Major refactoring of Command processing and the Result object. This update is **not** backwards compatible.
|
43
|
+
|
44
|
+
### Commands
|
45
|
+
|
46
|
+
Removed the `#success` and `#failure` chaining helpers.
|
47
|
+
|
48
|
+
Permanently removed the deprecated ResultHelpers mixin.
|
49
|
+
|
50
|
+
### Errors
|
51
|
+
|
52
|
+
Added `Cuprum::Error`, which encapsulates the failure state of a result. It is *recommended*, but not required, that when creating a failing Result, the `:error` property be set to an instance of `Cuprum::Error`.
|
53
|
+
|
54
|
+
### Results
|
55
|
+
|
56
|
+
Results are now nominally immutable objects. All mutator methods have been removed, including `#failure!`, `#success!`, and `#update`. The `#empty?` predicate has also been removed.
|
57
|
+
|
58
|
+
Updated the constructor to take the following keyword arguments: `:value`, `:error`, and `:status`.
|
59
|
+
|
60
|
+
- The status can now be overridden on a new Result by passing in the `:status`.
|
61
|
+
- Resolves an issue when attempting to instantiate a result with a Hash value.
|
62
|
+
- *Note:* The value must now be passed in as a keyword.
|
63
|
+
- *Note:* The `:errors` keyword has been renamed to `:error`.
|
64
|
+
|
65
|
+
Removed the `:halted` status.
|
66
|
+
|
67
|
+
### Other Changes
|
68
|
+
|
69
|
+
Removed the `Cuprum#warn` functionality.
|
70
|
+
|
71
|
+
### Upgrade Notes
|
72
|
+
|
73
|
+
Anywhere a new `Cuprum::Result` is created directly, update the arguments to match the new `value:` and `error:` keywords.
|
74
|
+
|
75
|
+
Anywhere the `#result` is referenced inside a command, instead return the desired value directly, or return a result with the desired error.
|
76
|
+
|
77
|
+
Anywhere a command is chained with the `#success` or `#failure` shorthand, use the full `chain(on: :success)` or `chain(on: :failure)` format.
|
78
|
+
|
3
79
|
## 0.8.0
|
4
80
|
|
5
81
|
The "We Have The Technology" Update.
|
@@ -60,21 +136,21 @@ The "Name Not Found For NullFunction" Update.
|
|
60
136
|
|
61
137
|
Added the `Cuprum::warn` helper, which prints a warning message. By default, `::warn` delegates to `Kernel#warn`, but can be configured (e.g. to call a Logger) by setting `Cuprum::warning_proc=` with a Proc that accepts one argument (the message to display).
|
62
138
|
|
63
|
-
|
139
|
+
### Operations
|
64
140
|
|
65
141
|
The implementation of `Cuprum::Operation` has been extracted to a module at `Cuprum::Operation::Mixin`, allowing users to easily convert an existing function class or instance to an operation.
|
66
142
|
|
67
|
-
|
143
|
+
### Results
|
68
144
|
|
69
145
|
Implemented `Cuprum::Result#==` as a fuzzy comparison, allowing a result to be equal to any object with the same value and status.
|
70
146
|
|
71
147
|
Implemented `Cuprum::Result#empty?`, which returns true for a new result and false for a result with a value, with non-empty errors, a result with set status, or a halted result.
|
72
148
|
|
73
|
-
|
149
|
+
### Utilities
|
74
150
|
|
75
151
|
Added the `Cuprum::Utils::InstanceSpy` module to empower testing of code that calls a function without providing a reference, such as some chained functions.
|
76
152
|
|
77
|
-
|
153
|
+
### Built In Functions
|
78
154
|
|
79
155
|
Added the `NullFunction` and `NullOperation` predefined classes, which do nothing when called and return a result with no errors and a value of nil.
|
80
156
|
|
@@ -84,7 +160,7 @@ Added the `IdentityFunction` and `IdentityOperation` predefined classes, which r
|
|
84
160
|
|
85
161
|
The "Halt And Catch Fire" Update.
|
86
162
|
|
87
|
-
|
163
|
+
### Functions
|
88
164
|
|
89
165
|
Can now call `#success!` or `#failure!` in a function block or `#process` method to override the default, error-based status for the result. This allows for a passing result that still has errors, or a failing result that does not have explicit errors.
|
90
166
|
|
@@ -94,11 +170,11 @@ Can now generate results with custom error objects by overriding the `#build_err
|
|
94
170
|
|
95
171
|
Fixed an inconsistency issue when a function block or `#process` method returned an instance of `Cuprum::Result`.
|
96
172
|
|
97
|
-
|
173
|
+
### Operations
|
98
174
|
|
99
175
|
Calling `#call` on an operation now returns the operation instance.
|
100
176
|
|
101
|
-
|
177
|
+
### Results
|
102
178
|
|
103
179
|
Can now call `#success!` or `#failure!` to override the default, error-based status.
|
104
180
|
|
@@ -108,11 +184,11 @@ Can now call `#halt!` and check the `#halted?` status. A halted result will prev
|
|
108
184
|
|
109
185
|
The "Nothing To Lose But Your Chains" Update.
|
110
186
|
|
111
|
-
|
187
|
+
### Functions
|
112
188
|
|
113
189
|
Now support chaining via the `#chain`, `#then`, and `#else` methods.
|
114
190
|
|
115
|
-
|
191
|
+
### Results
|
116
192
|
|
117
193
|
Can pass a value and/or an errors object to the constructor.
|
118
194
|
|
data/DEVELOPMENT.md
CHANGED
@@ -1,46 +1,14 @@
|
|
1
1
|
# Development
|
2
2
|
|
3
|
-
## Version 0.9.0
|
4
|
-
|
5
|
-
The "Second Star To The Right" Update
|
6
|
-
|
7
|
-
### Actions
|
8
|
-
|
9
|
-
#### LifecycleHooks
|
10
|
-
|
11
|
-
- :before, :after hooks
|
12
|
-
- NOT included in Command by default
|
13
|
-
|
14
|
-
## Version 0.10.0
|
15
|
-
|
16
|
-
'The "Out Of Context Problem" Update'
|
17
|
-
|
18
|
-
### Commands
|
19
|
-
|
20
|
-
- #context object
|
21
|
-
|
22
3
|
## Version 1.0.0
|
23
4
|
|
24
|
-
|
5
|
+
The "Look On My Works, Ye Mighty, and Despair" Update
|
25
6
|
|
26
7
|
- Integration specs.
|
27
|
-
- Configuration option to raise, warn, ignore discarded results.
|
28
8
|
- Code cleanup: Hash syntax, remove end comments, remove file headers
|
9
|
+
- Status Badges!
|
29
10
|
|
30
|
-
|
31
|
-
|
32
|
-
- Command#to_proc
|
33
|
-
- :clear_errors => true option on #chain
|
34
|
-
|
35
|
-
### Commands - Built In
|
36
|
-
|
37
|
-
- MapCommand - wraps a command (or proc) and returns Result with value, errors
|
38
|
-
as array
|
39
|
-
- RetryCommand
|
40
|
-
|
41
|
-
### Documentation
|
42
|
-
|
43
|
-
Chaining Case Study: |
|
11
|
+
Steps Case Study: |
|
44
12
|
|
45
13
|
CMS application - creating a new post.
|
46
14
|
Directory has many Posts
|
@@ -54,20 +22,23 @@ Chaining Case Study: |
|
|
54
22
|
Create ContentVersion
|
55
23
|
Tags.each { FindOrCreate Tag }
|
56
24
|
|
25
|
+
### Commands
|
26
|
+
|
27
|
+
- Remove `Cuprum::Chaining`.
|
28
|
+
- Implement `Command#to_proc`.
|
29
|
+
|
57
30
|
## Future Versions
|
58
31
|
|
59
32
|
### Commands
|
60
33
|
|
61
|
-
-
|
34
|
+
- Implement #<<, #>> composition methods.
|
35
|
+
- Calls commands in order passing values.
|
36
|
+
- Return Result early on Failure (or not Success), otherwise final Result.
|
62
37
|
|
63
|
-
####
|
38
|
+
#### DSL
|
64
39
|
|
65
40
|
- ::process - shortcut for defining #process
|
66
41
|
- ::rescue - `rescue StandardError do ... end`, rescues matched errors in #process
|
67
|
-
- chaining methods:
|
68
|
-
- ::chain (::success, ::failure):
|
69
|
-
on #initialize, chains the given command. Can be given a command class
|
70
|
-
(if ::new takes no arguments) or a block that returns a command.
|
71
42
|
- constructor methods:
|
72
43
|
- Programmatically generate a constructor method. Raises an error if
|
73
44
|
#initialize is defined. Automatically sets instance variables on initialize,
|
@@ -78,4 +49,49 @@ Chaining Case Study: |
|
|
78
49
|
optional arguments and their default values.
|
79
50
|
- ::keywords - sets keyword arguments; same arguments as ::arguments.
|
80
51
|
|
81
|
-
####
|
52
|
+
#### Dependency Injection
|
53
|
+
|
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
@@ -6,7 +6,8 @@ It defines the following concepts:
|
|
6
6
|
|
7
7
|
- [Commands](#label-Commands) - A function-like object that responds to `#call` and returns a `Result`.
|
8
8
|
- [Operations](#label-Operations) - A stateful `Command` that wraps and delegates to its most recent `Result`.
|
9
|
-
- [Results](#label-Results) -
|
9
|
+
- [Results](#label-Results) - An immutable data object with a status (either `:success` or `:failure`), and either a `#value` or an `#error` object.
|
10
|
+
- [Errors](#label-Errors) - Encapsulates a failure state of a command.
|
10
11
|
|
11
12
|
## About
|
12
13
|
|
@@ -22,12 +23,12 @@ Traditional frameworks such as Rails focus on the objects of your application -
|
|
22
23
|
|
23
24
|
### Alternatives
|
24
25
|
|
25
|
-
If you want to extract your logic but Cuprum is not the right solution for you,
|
26
|
-
|
27
|
-
-
|
28
|
-
|
29
|
-
|
30
|
-
|
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).
|
31
32
|
|
32
33
|
### Compatibility
|
33
34
|
|
@@ -41,7 +42,7 @@ Documentation is generated using [YARD](https://yardoc.org/), and can be generat
|
|
41
42
|
|
42
43
|
### License
|
43
44
|
|
44
|
-
Copyright (c)
|
45
|
+
Copyright (c) 2019 Rob Smith
|
45
46
|
|
46
47
|
Cuprum is released under the [MIT License](https://opensource.org/licenses/MIT).
|
47
48
|
|
@@ -63,9 +64,9 @@ Hi, I'm Rob Smith, a Ruby Engineer and the developer of this library. I use thes
|
|
63
64
|
|
64
65
|
require 'cuprum'
|
65
66
|
|
66
|
-
Commands are the core feature of Cuprum. In a nutshell, each Cuprum::Command is a functional object that encapsulates a business logic operation. A Command provides a consistent interface and tracking of result value and status. This minimizes boilerplate and allows for interchangeability between different implementations or strategies for managing your data and processes.
|
67
|
+
Commands are the core feature of Cuprum. In a nutshell, each `Cuprum::Command` is a functional object that encapsulates a business logic operation. A Command provides a consistent interface and tracking of result value and status. This minimizes boilerplate and allows for interchangeability between different implementations or strategies for managing your data and processes.
|
67
68
|
|
68
|
-
Each Command implements a `#call` method that wraps your defined business logic and returns an instance of Cuprum::Result
|
69
|
+
Each Command implements a `#call` method that wraps your defined business logic and returns an instance of `Cuprum::Result`. The result has a status (either `:success` or `:failure`), and may have a `#value` and/or an `#error` object. For more details about Cuprum::Result, [see below](#label-Results).
|
69
70
|
|
70
71
|
[Class Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum%2FCommand)
|
71
72
|
|
@@ -77,12 +78,13 @@ The recommended way to define commands is to create a subclass of `Cuprum::Comma
|
|
77
78
|
class BuildBookCommand < Cuprum::Command
|
78
79
|
def process attributes
|
79
80
|
Book.new(attributes)
|
80
|
-
end
|
81
|
-
end
|
81
|
+
end
|
82
|
+
end
|
82
83
|
|
83
84
|
command = BuildPostCommand.new
|
84
|
-
result = command.call(:
|
85
|
-
result.class
|
85
|
+
result = command.call(title: 'The Hobbit')
|
86
|
+
result.class #=> Cuprum::Result
|
87
|
+
result.success? #=> true
|
86
88
|
|
87
89
|
book = result.value
|
88
90
|
book.class #=> Book
|
@@ -97,23 +99,30 @@ Because a command is just a Ruby object, we can also pass values to the construc
|
|
97
99
|
class SaveBookCommand < Cuprum::Command
|
98
100
|
def initialize repository
|
99
101
|
@repository = repository
|
100
|
-
end
|
102
|
+
end
|
101
103
|
|
102
104
|
def process book
|
103
|
-
@repository.persist(book)
|
104
|
-
|
105
|
-
|
105
|
+
if @repository.persist(book)
|
106
|
+
success(book)
|
107
|
+
else
|
108
|
+
failure('unable to save book')
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
106
112
|
|
107
113
|
books = [
|
108
|
-
Book.new(:
|
109
|
-
Book.new(:
|
110
|
-
Book.new(:
|
114
|
+
Book.new(title: 'The Fellowship of the Ring'),
|
115
|
+
Book.new(title: 'The Two Towers'),
|
116
|
+
Book.new(title: 'The Return of the King')
|
111
117
|
]
|
112
118
|
command = SaveBookCommand.new(books_repository)
|
113
119
|
books.each { |book| command.call(book) }
|
114
120
|
```
|
115
121
|
|
116
|
-
Here, we are
|
122
|
+
Here, we are defining a command that might fail - maybe the database is unavailable, or there's a constraint that is violated by the inserted attributes. If the call to `#persist` succeeds, we're returning a Result with a status of `:success` and the value set to the persisted book.
|
123
|
+
Conversely, if the call to `#persist` fails, we're returning a Result with a status of `:failure` and a custom error message. Since the `#process` method returns a Result, it is returned directly by `#call`.
|
124
|
+
|
125
|
+
Note also that we are reusing the same command three times, rather than creating a new save command for each book. Each book is persisted to the `books_repository`. This is also an example of how using commands can simplify code - notice that nothing about the `SaveBookCommand` is specific to the `Book` model. Thus, we could refactor this into a generic `SaveModelCommand`.
|
117
126
|
|
118
127
|
A command can also be defined by passing block to `Cuprum::Command.new`.
|
119
128
|
|
@@ -136,9 +145,9 @@ Commands defined using `Cuprum::Command.new` are quick to use, but more difficul
|
|
136
145
|
|
137
146
|
Calling the `#call` method on a `Cuprum::Command` instance will always return an instance of `Cuprum::Result`. The result's `#value` property is determined by the object returned by the `#process` method (if the command is defined as a class) or the block (if the command is defined by passing a block to `Cuprum::Command.new`).
|
138
147
|
|
139
|
-
The `#value` depends on whether or not the returned object is a result or is compatible with the result interface. Specifically, any object that responds to the
|
148
|
+
The `#value` depends on whether or not the returned object is a result or is compatible with the result interface. Specifically, any object that responds to the method `#to_cuprum_result` is considered to be a result.
|
140
149
|
|
141
|
-
If the object returned is **not** a result, then the `#value` of the returned result is set to the object.
|
150
|
+
If the object returned by `#process` is **not** a result, then the `#value` of the returned result is set to the object.
|
142
151
|
|
143
152
|
```ruby
|
144
153
|
command = Cuprum::Command.new { 'Greetings, programs!' }
|
@@ -147,46 +156,18 @@ result.class #=> Cuprum::Result
|
|
147
156
|
result.value #=> 'Greetings, programs!'
|
148
157
|
```
|
149
158
|
|
150
|
-
If the object returned
|
159
|
+
If the object returned by `#process` is a result object, then result is returned directly.
|
151
160
|
|
152
161
|
```ruby
|
153
|
-
command = Cuprum::Command.new {
|
162
|
+
command = Cuprum::Command.new { Cuprum::Result.new(value: 'Greetings, programs!') }
|
154
163
|
result = command.call
|
155
|
-
result.class #=> Cuprum::Result
|
156
|
-
result.value #=> nil
|
157
|
-
result.success? #=> false
|
158
|
-
```
|
159
|
-
|
160
|
-
If the object returned is another result or compatible object, then `#call` will call the `#to_result` method on the result and return the resulting object.
|
161
|
-
|
162
|
-
```ruby
|
163
|
-
command = Cuprum::Command.new { |value| Cuprum::Result.new(value) }
|
164
|
-
result = command.call('Greetings, starfighter!')
|
165
164
|
result.class #=> Cuprum::Result
|
166
|
-
result.value #=> 'Greetings,
|
167
|
-
```
|
168
|
-
|
169
|
-
In some cases, returning a result directly will discard information on the command's own result object. When this occurs, Cuprum will display a warning.
|
170
|
-
|
171
|
-
```ruby
|
172
|
-
command =
|
173
|
-
Cuprum::Command.new do
|
174
|
-
result.errors << 'Oops! We are throwing away this result.'
|
175
|
-
|
176
|
-
Cuprum::Result.new
|
177
|
-
end
|
178
|
-
|
179
|
-
#=> This calls Kernel#warn with a warning message.
|
180
|
-
result = command.call
|
181
|
-
result.class #=> Cuprum::Result
|
182
|
-
result.value #=> nil
|
183
|
-
result.success? #=> true
|
184
|
-
result.errors #=> []
|
165
|
+
result.value #=> 'Greetings, programs!'
|
185
166
|
```
|
186
167
|
|
187
168
|
#### Success, Failure, and Errors
|
188
169
|
|
189
|
-
|
170
|
+
Each Result has a status, either `:success` or `:failure`. A Result will have a status of `:failure` when it was created with an error object. Otherwise, a Result will have a status of `:success`. Returning a failing Result from a Command indicates that something went wrong while executing the Command.
|
190
171
|
|
191
172
|
```ruby
|
192
173
|
class PublishBookCommand < Cuprum::Command
|
@@ -194,47 +175,99 @@ class PublishBookCommand < Cuprum::Command
|
|
194
175
|
|
195
176
|
def process book
|
196
177
|
if book.cover.nil?
|
197
|
-
|
198
|
-
|
199
|
-
return
|
200
|
-
end # if
|
178
|
+
return Cuprum::Result.new(error: 'This book does not have a cover.')
|
179
|
+
end
|
201
180
|
|
202
181
|
book.published = true
|
203
182
|
|
204
183
|
book
|
205
|
-
end
|
206
|
-
end
|
184
|
+
end
|
185
|
+
end
|
207
186
|
```
|
208
187
|
|
209
|
-
In addition, the result object defines `#success?` and `#failure?` predicates.
|
188
|
+
In addition, the result object defines `#success?` and `#failure?` predicates.
|
210
189
|
|
211
190
|
```ruby
|
212
|
-
book = Book.new(:
|
191
|
+
book = Book.new(title: 'The Silmarillion', cover: Cover.new)
|
213
192
|
book.published? #=> false
|
214
193
|
|
215
194
|
result = PublishBookCommand.new.call(book)
|
216
|
-
result.
|
195
|
+
result.error #=> nil
|
217
196
|
result.success? #=> true
|
218
197
|
result.failure? #=> false
|
219
198
|
result.value #=> book
|
220
199
|
book.published? #=> true
|
221
200
|
```
|
222
201
|
|
223
|
-
If the result does have
|
202
|
+
If the result does have an error, `#success?` will return false and `#failure?` will return true.
|
224
203
|
|
225
204
|
```ruby
|
226
|
-
book = Book.new(:
|
205
|
+
book = Book.new(title: 'The Silmarillion', cover: nil)
|
227
206
|
book.published? #=> false
|
228
207
|
|
229
208
|
result = PublishBookCommand.new.call(book)
|
230
|
-
result.
|
209
|
+
result.error #=> 'This book does not have a cover.'
|
231
210
|
result.success? #=> false
|
232
211
|
result.failure? #=> true
|
233
212
|
result.value #=> book
|
234
213
|
book.published? #=> false
|
235
214
|
```
|
236
215
|
|
237
|
-
####
|
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
|
238
271
|
|
239
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:
|
240
273
|
|
@@ -245,10 +278,12 @@ increment_command.call(2).value #=> 3
|
|
245
278
|
increment_command.call(3).value #=> 4
|
246
279
|
|
247
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.
|
248
283
|
addend.times { i = increment_command(i).value }
|
249
284
|
|
250
285
|
i
|
251
|
-
end
|
286
|
+
end
|
252
287
|
|
253
288
|
add_command.call(1, 1).value #=> 2
|
254
289
|
add_command.call(1, 2).value #=> 3
|
@@ -274,8 +309,12 @@ class AddCommand < Cuprum::Command
|
|
274
309
|
|
275
310
|
private
|
276
311
|
|
312
|
+
def increment_command
|
313
|
+
@increment_command ||= IncrementCommand.new
|
314
|
+
end
|
315
|
+
|
277
316
|
def process i
|
278
|
-
addend.times { i =
|
317
|
+
addend.times { i = increment_command.call(i).value }
|
279
318
|
|
280
319
|
i
|
281
320
|
end
|
@@ -287,442 +326,420 @@ add_two_command.call(1).value #=> 3
|
|
287
326
|
add_two_command.call(8).value #=> 10
|
288
327
|
```
|
289
328
|
|
290
|
-
|
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.
|
291
330
|
|
292
|
-
|
331
|
+
#### Commands As Arguments
|
332
|
+
|
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:
|
293
334
|
|
294
335
|
```ruby
|
295
|
-
|
296
|
-
|
297
|
-
|
336
|
+
class RepeatCommand
|
337
|
+
def initialize(count)
|
338
|
+
@count = count
|
339
|
+
end
|
298
340
|
|
299
|
-
|
300
|
-
name_command
|
301
|
-
.chain(pluralize_command)
|
302
|
-
.chain(underscore_command)
|
341
|
+
private
|
303
342
|
|
304
|
-
|
305
|
-
|
306
|
-
|
343
|
+
def process(command)
|
344
|
+
@count.times { command.call }
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
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
|
307
351
|
```
|
308
352
|
|
309
|
-
|
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.
|
310
354
|
|
311
|
-
|
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.
|
312
356
|
|
313
|
-
|
314
|
-
table_name_command =
|
315
|
-
Cuprum::Command.new { |klass| klass.name }
|
316
|
-
.chain { |str| str.pluralize }
|
317
|
-
.chain { |str| str.underscore }
|
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.
|
318
358
|
|
319
|
-
|
320
|
-
|
321
|
-
```
|
359
|
+
```ruby
|
360
|
+
class DeliverEbook < Cuprum::Command; end
|
322
361
|
|
323
|
-
|
362
|
+
class ShipDomestic < Cuprum::Command; end
|
324
363
|
|
325
|
-
|
364
|
+
class ShipInternational < Cuprum::Command; end
|
326
365
|
|
327
|
-
|
328
|
-
|
329
|
-
|
366
|
+
class FulfillOrder < Cuprum::Command
|
367
|
+
def initialize(delivery_command)
|
368
|
+
@delivery_command = delivery_command
|
369
|
+
end
|
330
370
|
|
331
|
-
|
332
|
-
second_command.call #=> Outputs 'First command!' then 'Second command!'.
|
371
|
+
private
|
333
372
|
|
334
|
-
|
335
|
-
|
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
|
336
379
|
```
|
337
380
|
|
338
|
-
|
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).
|
339
382
|
|
340
|
-
|
383
|
+
#### Commands As Returned Values
|
341
384
|
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
square_command = Cuprum::Command.new { |i| i * i }
|
346
|
-
chained_command =
|
347
|
-
double_command
|
348
|
-
.chain(increment_command)
|
349
|
-
.chain(square_command)
|
350
|
-
|
351
|
-
# First, the double_commmand is called with 2. This returns a Cuprum::Result
|
352
|
-
# with a value of 4.
|
353
|
-
#
|
354
|
-
# Next, the increment_command is called with 4, returning a result with value 5.
|
355
|
-
#
|
356
|
-
# Finally, the square_command is called with 5, returning a result with a value
|
357
|
-
# of 25. This final result is returned by #call.
|
358
|
-
result = chained_command.call(2)
|
359
|
-
result.class #=> Cuprum::Result
|
360
|
-
result.value #=> 25
|
361
|
-
```
|
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.
|
362
388
|
|
363
|
-
|
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.
|
364
390
|
|
365
391
|
```ruby
|
366
|
-
|
367
|
-
|
368
|
-
result.errors << 'Object is invalid!' unless object.valid?
|
392
|
+
class ShippingMethod < Cuprum::Command
|
393
|
+
private
|
369
394
|
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
395
|
+
def process(book:, user:)
|
396
|
+
return DeliverEbook.new(user.email) if book.ebook?
|
397
|
+
|
398
|
+
return ShipDomestic.new(user.address) if user.address&.domestic?
|
399
|
+
|
400
|
+
return ShipInternational.new(user.address) if user.address&.international?
|
375
401
|
|
376
|
-
|
402
|
+
err = Cuprum::Error.new(message: 'user does not have a valid address')
|
403
|
+
|
404
|
+
failure(err)
|
377
405
|
end
|
378
|
-
|
379
|
-
|
380
|
-
# First, validate_command is called with a valid object. This creates a result
|
381
|
-
# with no errors and whose value is the valid object.
|
382
|
-
#
|
383
|
-
# Then, persist_command is called with the object, and its result is assigned to
|
384
|
-
# the previous result. Since there are no errors on the result, the object is
|
385
|
-
# saved. Finally, the value of the result is set to the object, and the result
|
386
|
-
# is returned.
|
387
|
-
result = chained_command.call(a_valid_object) #=> Saves the object.
|
388
|
-
result.value #=> a_valid_object
|
389
|
-
result.errors #=> []
|
390
|
-
a_valid_object.persisted? #=> true
|
391
|
-
|
392
|
-
# First, validate_command is called with an invalid object. This creates a
|
393
|
-
# result whose value is the invalid object, and with errors 'Object is
|
394
|
-
# invalid!'.
|
395
|
-
#
|
396
|
-
# Then, persist_command is called with the object, and its result is assigned to
|
397
|
-
# the previous result. Since the result has an error, the object is not saved.
|
398
|
-
# Finally, the value of the result is set to the object, and the result
|
399
|
-
# is returned.
|
400
|
-
result = chained_command.call(an_invalid_object) #=> Does not save the object.
|
401
|
-
result.value #=> an_invalid_object
|
402
|
-
result.errors #=> ['Object is invalid!']
|
403
|
-
an_invalid_object.persisted? #=> false
|
406
|
+
end
|
404
407
|
```
|
405
408
|
|
406
|
-
|
407
|
-
|
408
|
-
The `#chain` method can be passed an optional `:on => value` keyword. This keyword determines whether or not the chained command will execute, based on the previous result status. Possible values are `:success`, `:failure`, `:always`, or `nil`. The default value is `nil`.
|
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.
|
409
410
|
|
410
|
-
|
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.
|
411
412
|
|
412
|
-
|
413
|
+
### Command Steps
|
413
414
|
|
414
|
-
|
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.
|
415
416
|
|
416
|
-
|
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.
|
417
418
|
|
418
419
|
```ruby
|
419
|
-
|
420
|
-
Cuprum::Command.new do |attributes|
|
421
|
-
book = Book.where(:id => attributes[:id]).first
|
420
|
+
triple_command = Cuprum::Command.new { |i| success(3 * i) }
|
422
421
|
|
423
|
-
|
422
|
+
int = 2
|
423
|
+
int = step { triple_command.call(int) } #=> returns 6
|
424
|
+
int = step { triple_command.call(int) } #=> returns 18
|
425
|
+
```
|
424
426
|
|
425
|
-
|
426
|
-
end
|
427
|
-
create_command =
|
428
|
-
Cuprum::Command.new do |attributes|
|
429
|
-
book = Book.new(attributes)
|
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.
|
430
428
|
|
431
|
-
|
432
|
-
result.success!
|
433
|
-
else
|
434
|
-
book.errors.full_messages.each { |message| result.errors << message }
|
435
|
-
end
|
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`.
|
436
430
|
|
437
|
-
|
438
|
-
|
431
|
+
```ruby
|
432
|
+
divide_command = Cuprum::Command.new do |dividend, divisor|
|
433
|
+
return failure('divide by zero') if divisor.zero?
|
439
434
|
|
440
|
-
|
435
|
+
success(dividend / divisor)
|
436
|
+
end
|
441
437
|
|
442
|
-
|
443
|
-
|
444
|
-
# create_command is not called.
|
445
|
-
hsh = { id: 0, title: 'Journey to the West' }
|
446
|
-
result = find_or_create_command.call(hsh)
|
447
|
-
book = result.value
|
448
|
-
book.id #=> 0
|
449
|
-
book.title #=> 'Journey to the West'
|
450
|
-
result.success? #=> true
|
451
|
-
result.errors #=> []
|
452
|
-
|
453
|
-
# With a book that does not exist but with valid attributes, the find command
|
454
|
-
# returns a failing result with a value of nil. The create_command is called and
|
455
|
-
# creates a new book with the attributes, returning a passing result but
|
456
|
-
# preserving the errors.
|
457
|
-
hsh = { id: 1, title: 'The Ramayana' }
|
458
|
-
result = find_or_create_command.call(hsh)
|
459
|
-
book = result.value
|
460
|
-
book.id #=> 1
|
461
|
-
book.title #=> 'The Ramayana'
|
462
|
-
result.success? #=> true
|
463
|
-
result.errors #=> ['Book not found']
|
464
|
-
|
465
|
-
# With a book that does not exist and with invalid attributes, the find command
|
466
|
-
# returns a failing result with a value of nil. The create_command is called and
|
467
|
-
# is unable to create a new book with the attributes, returning the
|
468
|
-
# (non-persisted) book and adding the validation errors.
|
469
|
-
hsh = { id: 2, title: nil }
|
470
|
-
result = find_or_create_command.call(hsh)
|
471
|
-
book = result.value
|
472
|
-
book.id #=> 2
|
473
|
-
book.title #=> nil
|
474
|
-
result.success? #=> false
|
475
|
-
result.errors #=> ['Book not found', "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
|
476
440
|
```
|
477
441
|
|
478
|
-
|
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.
|
479
443
|
|
480
|
-
|
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:
|
481
445
|
|
482
|
-
|
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.
|
483
449
|
|
484
|
-
|
485
|
-
double_command = Cuprum::Command.new { |i| 2 * i }
|
486
|
-
halt_command = Cuprum::Command.new { |value| value.tap { result.halt! } }
|
487
|
-
add_one_command = Cuprum::Command.new { |i| i + 1 }
|
488
|
-
|
489
|
-
chained_command =
|
490
|
-
double_command
|
491
|
-
.chain(halt_command)
|
492
|
-
.chain(add_one_command)
|
493
|
-
.chain(:on => :always) { |count| "There are #{count} lights!" }
|
494
|
-
|
495
|
-
# First, double_command is called with 2, returning a result with no errors and
|
496
|
-
# a value of 4. Then, halt_command is called, which marks the result as halted.
|
497
|
-
# Because the result is now halted, the add_one_command is not called. Finally,
|
498
|
-
# the last command is called (even though the result is halted, the command is
|
499
|
-
# chained :on => :always), which returns a result with the string value. The
|
500
|
-
# result is passing, but is still halted.
|
501
|
-
result = chained_command.call(2)
|
502
|
-
result.value #=> 'There are 4 lights!'
|
503
|
-
result.success? #=> true
|
504
|
-
result.halted? #=> true
|
505
|
-
```
|
506
|
-
|
507
|
-
### Advanced Chaining
|
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.
|
508
451
|
|
509
|
-
|
452
|
+
```ruby
|
453
|
+
class CheckUserStatus < Cuprum::Command; end
|
510
454
|
|
511
|
-
|
455
|
+
class CreateBookReservation < Cuprum::Command; end
|
512
456
|
|
513
|
-
|
457
|
+
class FindBookByTitle < Cuprum::Command; end
|
514
458
|
|
515
|
-
|
516
|
-
|
517
|
-
Cuprum::Command.new do
|
518
|
-
result.errors << 'Example error'
|
459
|
+
class ReserveBookByTitle < Cuprum::Command
|
460
|
+
private
|
519
461
|
|
520
|
-
|
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) }
|
521
477
|
end
|
522
|
-
|
523
|
-
|
524
|
-
.tap_result do |result|
|
525
|
-
puts "The result value was #{result.inspect}"
|
526
|
-
end
|
478
|
+
end
|
479
|
+
```
|
527
480
|
|
528
|
-
|
529
|
-
|
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
|
+
)
|
530
488
|
result.class #=> Cuprum::Result
|
531
|
-
result.value #=> 'Example value'
|
532
489
|
result.success? #=> false
|
533
|
-
result.
|
490
|
+
result.error #=> 'not authorized to reserve book'
|
534
491
|
```
|
535
492
|
|
536
|
-
|
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`.
|
537
494
|
|
538
495
|
```ruby
|
539
|
-
|
540
|
-
|
541
|
-
|
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
|
+
```
|
542
504
|
|
543
|
-
|
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`.
|
544
506
|
|
545
|
-
|
546
|
-
|
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
|
+
```
|
547
516
|
|
548
|
-
|
549
|
-
find_book_command
|
550
|
-
.tap_result(:on => :success) do |result|
|
551
|
-
render :show, :locals => { :book => result.value }
|
552
|
-
end
|
553
|
-
.tap_result(:on => :failure) do
|
554
|
-
redirect_to books_path
|
555
|
-
end
|
517
|
+
Finally, if each of the steps succeeds, the `#call` method will return the result of the final step.
|
556
518
|
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
result = chained_command.call(valid_id)
|
519
|
+
```ruby
|
520
|
+
result = ReserveBookByTitle.new.call(
|
521
|
+
title: 'The C Programming Language',
|
522
|
+
user: 'Alan Bradley'
|
523
|
+
)
|
563
524
|
result.class #=> Cuprum::Result
|
564
|
-
result.value #=> an instance of Book
|
565
525
|
result.success? #=> true
|
566
|
-
result.
|
567
|
-
|
568
|
-
# Calls find_book_command with the id, which queries for the book and returns a
|
569
|
-
# result with a value of nil and the error message. Because the result is
|
570
|
-
# failing, the first block is skipped. The second block is then evaluated,
|
571
|
-
# calling the redirect_to method. Finally, the result of find_book_command is
|
572
|
-
# returned unchanged.
|
573
|
-
result = chained_command.call(invalid_id)
|
574
|
-
result.class #=> Cuprum::Result
|
575
|
-
result.value #=> nil
|
576
|
-
result.success? #=> false
|
577
|
-
result.errors #=> ['Unable to find book with id invalid_id']
|
526
|
+
result.value #=> an instance of BookReservation
|
578
527
|
```
|
579
528
|
|
580
|
-
####
|
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.
|
581
532
|
|
582
|
-
|
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.
|
583
534
|
|
584
|
-
|
535
|
+
We can use this to rewrite our `ReserveBookByTitle` command to use methods:
|
585
536
|
|
586
537
|
```ruby
|
587
|
-
|
588
|
-
|
589
|
-
|
538
|
+
class ReserveBookByTitle < Cuprum::Result
|
539
|
+
private
|
540
|
+
|
541
|
+
def check_user_status(user)
|
542
|
+
CheckUserStatus.new(user)
|
590
543
|
end
|
591
|
-
.yield_result do |result|
|
592
|
-
result.errors << 'Example error'
|
593
544
|
|
594
|
-
|
545
|
+
def create_book_reservation(book:, user:)
|
546
|
+
CreateBookReservation.new(book: book, user: user)
|
595
547
|
end
|
596
|
-
|
597
|
-
|
548
|
+
|
549
|
+
def find_book_by_title(title)
|
550
|
+
FindBookByTitle.new.call(title)
|
598
551
|
end
|
599
552
|
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
# the passed result. Since the second block does not return the previous result,
|
609
|
-
# the previous result is discarded and a new result is created with the string
|
610
|
-
# value starting with 'The last result was ...'.
|
611
|
-
result = chained_command.call
|
612
|
-
result.class #=> Cuprum::Result
|
613
|
-
result.value #=> 'The last result was a failure.'
|
614
|
-
result.success? #=> true
|
615
|
-
result.errors #=> []
|
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
|
616
561
|
```
|
617
562
|
|
618
|
-
|
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.
|
564
|
+
|
565
|
+
#### Using Steps Outside Of Commands
|
566
|
+
|
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.
|
568
|
+
|
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:
|
619
570
|
|
620
571
|
```ruby
|
621
|
-
|
622
|
-
|
623
|
-
# divide the number by two. If the number is odd, multiply by three and add one.
|
624
|
-
# These steps are repeated until the value is one or the numbers loop.
|
625
|
-
step_command =
|
626
|
-
Cuprum::Command.new do |i|
|
627
|
-
result.failure! unless i.even?
|
572
|
+
steps do
|
573
|
+
step { check_something }
|
628
574
|
|
629
|
-
|
630
|
-
end
|
631
|
-
.yield_result(:on => :success) { |result| result.value / 2 }
|
632
|
-
.yield_result(:on => :failure) { |result| 1 + 3 * result.value }
|
633
|
-
|
634
|
-
# Because the value is even, the first command returns a passing result with a
|
635
|
-
# value of 8. The first yield_result block is then called with that result,
|
636
|
-
# returning a new result with a value of 4. The result is passing, so the second
|
637
|
-
# yield_result block is skipped and the result is returned.
|
638
|
-
result = collatz_command.call(8)
|
639
|
-
result.value #=> 4
|
640
|
-
result.success? #=> true
|
575
|
+
obj = step { find_something }
|
641
576
|
|
642
|
-
|
643
|
-
|
644
|
-
# skipped. The second yield_result block is then called with the result,
|
645
|
-
# returning a new (passing) result with a value of 16.
|
646
|
-
result = collatz_command.call(5)
|
647
|
-
result.value #=> 16
|
648
|
-
result.success? #=> true
|
577
|
+
step :do_something, with: obj
|
578
|
+
end
|
649
579
|
```
|
650
580
|
|
651
|
-
|
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`.
|
652
582
|
|
653
|
-
|
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:
|
654
584
|
|
655
|
-
|
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.
|
656
588
|
|
657
589
|
```ruby
|
658
|
-
|
659
|
-
|
660
|
-
include Cuprum::Chaining
|
661
|
-
include Cuprum::Processing
|
662
|
-
#
|
663
|
-
def initialize
|
664
|
-
# After the build step is run, we validate the comment.
|
665
|
-
chain!(ValidateCommentCommand.new)
|
666
|
-
#
|
667
|
-
# If the validation passes, we then save the comment.
|
668
|
-
chain!(SaveCommentCommand.new, on: :success)
|
669
|
-
end
|
670
|
-
end
|
590
|
+
class BooksController
|
591
|
+
include Cuprum::Steps
|
671
592
|
|
672
|
-
|
593
|
+
def create
|
594
|
+
attributes = params[:books]
|
595
|
+
result = steps do
|
596
|
+
@book = step :build_book, attributes
|
673
597
|
|
674
|
-
|
675
|
-
'packages of eight?'
|
676
|
-
result = CreateCommentCommand.new.call({ user_id: '12345', body: body })
|
598
|
+
step :run_validations, @book
|
677
599
|
|
678
|
-
|
679
|
-
|
680
|
-
Comment.count #=> 1; the comment was added to the database
|
600
|
+
step :persist_book, book
|
601
|
+
end
|
681
602
|
|
682
|
-
result
|
603
|
+
result.success ? redirect_to(@book) : render(:edit)
|
604
|
+
end
|
683
605
|
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
|
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
|
688
622
|
```
|
689
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
|
+
|
690
628
|
### Results
|
691
629
|
|
692
630
|
require 'cuprum'
|
693
631
|
|
694
632
|
[Class Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum%2FResult)
|
695
633
|
|
696
|
-
A Cuprum::Result is a data object that encapsulates the result of calling a Cuprum command. Each result has a `#value`, an `#
|
634
|
+
A `Cuprum::Result` is a data object that encapsulates the result of calling a Cuprum command. Each result has a `#value`, an `#error` object (defaults to `nil`), and a `#status` (either `:success` or `:failure`, and accessible via the `#success?` and `#failure?` predicates).
|
635
|
+
|
636
|
+
```ruby
|
637
|
+
result = Cuprum::Result.new
|
638
|
+
|
639
|
+
result.value #=> nil
|
640
|
+
result.error #=> nil
|
641
|
+
result.status #=> :success
|
642
|
+
result.success? #=> true
|
643
|
+
result.failure? #=> true
|
644
|
+
```
|
645
|
+
|
646
|
+
Creating a result with a value stores the value.
|
697
647
|
|
698
648
|
```ruby
|
699
649
|
value = 'A result value'.freeze
|
700
|
-
result = Cuprum::Result.new(value)
|
650
|
+
result = Cuprum::Result.new(value: value)
|
701
651
|
|
702
652
|
result.value #=> 'A result value'
|
703
|
-
result.
|
653
|
+
result.error #=> nil
|
654
|
+
result.status #=> :success
|
704
655
|
result.success? #=> true
|
705
656
|
result.failure? #=> false
|
706
|
-
result.halted? #=> false
|
707
657
|
```
|
708
658
|
|
709
|
-
|
659
|
+
Creating a Result with an error stores the error and sets the status to `:failure`.
|
660
|
+
|
661
|
+
```ruby
|
662
|
+
error = Cuprum::Error.new(message: "I'm sorry, something went wrong.")
|
663
|
+
result = Cuprum::Result.new(error: error)
|
664
|
+
result.value #=> nil
|
665
|
+
result.error #=> Error with message "I'm sorry, something went wrong."
|
666
|
+
result.status #=> :failure
|
667
|
+
result.success? #=> false
|
668
|
+
result.failure? #=> true
|
669
|
+
```
|
670
|
+
|
671
|
+
Although using a `Cuprum::Error` instance as the `:error` is recommended, it is not required. You can use a custom error object, or just a string message.
|
710
672
|
|
711
673
|
```ruby
|
712
|
-
result.
|
674
|
+
result = Cuprum::Result.new(error: "I'm sorry, something went wrong.")
|
675
|
+
result.value #=> nil
|
676
|
+
result.error #=> "I'm sorry, something went wrong."
|
677
|
+
result.status #=> :failure
|
713
678
|
result.success? #=> false
|
714
679
|
result.failure? #=> true
|
715
680
|
```
|
716
681
|
|
717
|
-
|
682
|
+
Finally, the status can be overridden via the `:status` keyword.
|
718
683
|
|
719
684
|
```ruby
|
720
|
-
result.
|
721
|
-
result.
|
685
|
+
result = Cuprum::Result.new(status: :failure)
|
686
|
+
result.error #=> nil
|
687
|
+
result.status #=> :failure
|
688
|
+
result.success? #=> false
|
689
|
+
result.failure? #=> true
|
690
|
+
|
691
|
+
error = Cuprum::Error.new(message: "I'm sorry, something went wrong.")
|
692
|
+
result = Cuprum::Result.new(error: error, status: :success)
|
693
|
+
result.error #=> Error with message "I'm sorry, something went wrong."
|
694
|
+
result.status #=> :success
|
722
695
|
result.success? #=> true
|
723
696
|
result.failure? #=> false
|
724
697
|
```
|
725
698
|
|
699
|
+
### Errors
|
700
|
+
|
701
|
+
require 'cuprum/error'
|
702
|
+
|
703
|
+
[Class Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum%2FError)
|
704
|
+
|
705
|
+
A `Cuprum::Error` encapsulates a specific failure state of a Command. Each Error has a `#message` property, which defaults to nil.
|
706
|
+
|
707
|
+
```ruby
|
708
|
+
error = Cuprum::Error.new
|
709
|
+
error.message => # nil
|
710
|
+
|
711
|
+
error = Cuprum::Error.new(message: 'Something went wrong.')
|
712
|
+
error.message => # 'Something went wrong.'
|
713
|
+
```
|
714
|
+
|
715
|
+
Each application should define its own failure states as errors. For example, a typical web application might define the following errors:
|
716
|
+
|
717
|
+
```ruby
|
718
|
+
class NotFoundError < Cuprum::Error
|
719
|
+
def initialize(resource:, resource_id:)
|
720
|
+
@resource = resource
|
721
|
+
@resource_id = resource_id
|
722
|
+
|
723
|
+
super(message: "#{resource} not found with id #{resource_id}")
|
724
|
+
end
|
725
|
+
|
726
|
+
attr_reader :resource, :resource_id
|
727
|
+
end
|
728
|
+
|
729
|
+
class ValidationError < Cuprum::Error
|
730
|
+
def initialize(resource:, errors:)
|
731
|
+
@resource = resource
|
732
|
+
@errors = errors
|
733
|
+
|
734
|
+
super(message: "#{resource} was invalid")
|
735
|
+
end
|
736
|
+
|
737
|
+
attr_reader :resource, :errors
|
738
|
+
end
|
739
|
+
```
|
740
|
+
|
741
|
+
It is optional but recommended to use a `Cuprum::Error` when returning a failed result from a command.
|
742
|
+
|
726
743
|
### Operations
|
727
744
|
|
728
745
|
require 'cuprum'
|
@@ -737,8 +754,8 @@ These two features allow developers to simplify logic around calling and using t
|
|
737
754
|
class CreateBookOperation < Cuprum::Operation
|
738
755
|
def process
|
739
756
|
# Implementation here.
|
740
|
-
end
|
741
|
-
end
|
757
|
+
end
|
758
|
+
end
|
742
759
|
|
743
760
|
# Defining a controller action using an operation.
|
744
761
|
def create
|
@@ -750,13 +767,13 @@ def create
|
|
750
767
|
@book = operation.value
|
751
768
|
|
752
769
|
render :new
|
753
|
-
end
|
754
|
-
end
|
770
|
+
end
|
771
|
+
end
|
755
772
|
```
|
756
773
|
|
757
774
|
Like a Command, an Operation can be defined directly by passing an implementation block to the constructor or by creating a subclass that overwrites the #process method.
|
758
775
|
|
759
|
-
An operation inherits the `#call` method from Cuprum::Command (see above), and delegates the `#value`, `#
|
776
|
+
An operation inherits the `#call` method from Cuprum::Command (see above), and delegates the `#value`, `#error`, `#success?`, and `#failure` methods to the most recent result. If the operation has not been called, these methods will return default values.
|
760
777
|
|
761
778
|
#### The Operation Mixin
|
762
779
|
|
@@ -764,13 +781,6 @@ An operation inherits the `#call` method from Cuprum::Command (see above), and d
|
|
764
781
|
|
765
782
|
The implementation of `Cuprum::Operation` is defined by the `Cuprum::Operation::Mixin` module, which provides the methods defined above. Any command class or instance can be converted to an operation by including (for a class) or extending (for an instance) the operation mixin.
|
766
783
|
|
767
|
-
Finally, the result can be halted using the `#halt` method, which indicates that further chained commands should not be executed.
|
768
|
-
|
769
|
-
```ruby
|
770
|
-
result.halt!
|
771
|
-
result.halted? #=> true
|
772
|
-
```
|
773
|
-
|
774
784
|
### Command Factories
|
775
785
|
|
776
786
|
[Class Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum%2FCommandFactory)
|
@@ -898,15 +908,15 @@ class PublishedBooksCommand < Cuprum::Command
|
|
898
908
|
end
|
899
909
|
|
900
910
|
class BookFactory < Cuprum::CommandFactory
|
901
|
-
command :published do
|
902
|
-
PublishedBooksCommand.new(books_collection)
|
903
|
-
end
|
904
|
-
|
905
911
|
def initialize(books)
|
906
912
|
@books_collection = books
|
907
913
|
end
|
908
914
|
|
909
915
|
attr_reader :books_collection
|
916
|
+
|
917
|
+
command :published do
|
918
|
+
PublishedBooksCommand.new(books_collection)
|
919
|
+
end
|
910
920
|
end
|
911
921
|
```
|
912
922
|
|
@@ -1135,354 +1145,4 @@ operation.success? #=> true
|
|
1135
1145
|
|
1136
1146
|
## Reference
|
1137
1147
|
|
1138
|
-
|
1139
|
-
|
1140
|
-
require 'cuprum/built_in/identity_command'
|
1141
|
-
|
1142
|
-
[Class Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum%2FBuiltIn%2FIdentityCommand)
|
1143
|
-
|
1144
|
-
Cuprum::BuiltIn::IdentityCommand defines the following methods:
|
1145
|
-
|
1146
|
-
#### `#call`
|
1147
|
-
|
1148
|
-
call(value) #=> Cuprum::Result
|
1149
|
-
|
1150
|
-
Returns a result, whose `#value` is equal to the given value.
|
1151
|
-
|
1152
|
-
### Cuprum::BuiltIn::IdentityOperation
|
1153
|
-
|
1154
|
-
require 'cuprum/built_in/identity_operation'
|
1155
|
-
|
1156
|
-
[Class Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum%2FBuiltIn%2FIdentityOperation)
|
1157
|
-
|
1158
|
-
Cuprum::BuiltIn::IdentityOperation defines the following methods:
|
1159
|
-
|
1160
|
-
#### `#call`
|
1161
|
-
|
1162
|
-
call(value) #=> Cuprum::BuiltIn::IdentityOperation
|
1163
|
-
|
1164
|
-
Sets the last result to a new result, whose `#value` is equal to the given value.
|
1165
|
-
|
1166
|
-
### Cuprum::BuiltIn::NullCommand
|
1167
|
-
|
1168
|
-
require 'cuprum/built_in/null_command'
|
1169
|
-
|
1170
|
-
[Class Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum%2FBuiltIn%2FNullCommand)
|
1171
|
-
|
1172
|
-
Cuprum::BuiltIn::NullCommand defines the following methods:
|
1173
|
-
|
1174
|
-
#### `#call`
|
1175
|
-
|
1176
|
-
call(*args, **keywords) { ... } #=> Cuprum::Result
|
1177
|
-
|
1178
|
-
Returns a result with nil value. Any arguments or keywords are ignored.
|
1179
|
-
|
1180
|
-
### Cuprum::BuiltIn::NullOperation
|
1181
|
-
|
1182
|
-
require 'cuprum/built_in/null_operation'
|
1183
|
-
|
1184
|
-
[Class Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum%2FBuiltIn%2FNullOperation)
|
1185
|
-
|
1186
|
-
Cuprum::BuiltIn::NullOperation defines the following methods:
|
1187
|
-
|
1188
|
-
#### `#call`
|
1189
|
-
|
1190
|
-
call(*args, **keywords) { ... } #=> Cuprum::BuiltIn::NullOperation
|
1191
|
-
|
1192
|
-
Sets the last result to a result with nil value. Any arguments or keywords are ignored.
|
1193
|
-
|
1194
|
-
### Cuprum::Command
|
1195
|
-
|
1196
|
-
require 'cuprum'
|
1197
|
-
|
1198
|
-
[Class Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum%2FCommand)
|
1199
|
-
|
1200
|
-
A Cuprum::Command defines the following methods:
|
1201
|
-
|
1202
|
-
#### `#initialize`
|
1203
|
-
|
1204
|
-
initialize { |*arguments, **keywords, &block| ... } #=> Cuprum::Command
|
1205
|
-
|
1206
|
-
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.
|
1207
|
-
|
1208
|
-
[Method Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum/Command#initialize-instance_method)
|
1209
|
-
|
1210
|
-
#### `#build_errors`
|
1211
|
-
|
1212
|
-
*(Private Method)*
|
1213
|
-
|
1214
|
-
build_errors() #=> Array
|
1215
|
-
|
1216
|
-
Generates an empty errors object. When the command is called, the result will have its `#errors` property initialized to the value returned by `#build_errors`. By default, this is an array. If you want to use a custom errors object type, override this method in a subclass.
|
1217
|
-
|
1218
|
-
[Method Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum/Command#build_errors-instance_method)
|
1219
|
-
|
1220
|
-
#### `#call`
|
1221
|
-
|
1222
|
-
call(*arguments, **keywords) { ... } #=> Cuprum::Result
|
1223
|
-
|
1224
|
-
Executes the logic encoded in the constructor block, or the #process method if no block was passed to the constructor.
|
1225
|
-
|
1226
|
-
[Method Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum/Command#call-instance_method)
|
1227
|
-
|
1228
|
-
#### `#chain`
|
1229
|
-
|
1230
|
-
chain(on: nil) { |result| ... } #=> Cuprum::Command
|
1231
|
-
|
1232
|
-
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.
|
1233
|
-
|
1234
|
-
chain(command, on: nil) #=> Cuprum::Command
|
1235
|
-
|
1236
|
-
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).
|
1237
|
-
|
1238
|
-
The block will be passed the #result of the previous command as its parameter. If your use case depends on the status of the previous command or on any errors generated, use the block form of #chain.
|
1239
|
-
|
1240
|
-
If the block returns a Cuprum::Result (or an object responding to #value and #success?), 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.
|
1241
|
-
|
1242
|
-
[Method Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum/Command#chain-instance_method)
|
1243
|
-
|
1244
|
-
#### `#chain!`
|
1245
|
-
|
1246
|
-
*(Protected Method)*
|
1247
|
-
|
1248
|
-
chain!(on: nil) { |result| ... } #=> Cuprum::Command
|
1249
|
-
|
1250
|
-
chain!(command, on: nil) #=> Cuprum::Command
|
1251
|
-
|
1252
|
-
As `#chain`, but modifies the current command instead of creating a clone.
|
1253
|
-
|
1254
|
-
[Method Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum/Command#chain!-instance_method)
|
1255
|
-
|
1256
|
-
#### `#else`
|
1257
|
-
|
1258
|
-
else(command) #=> Cuprum::Command
|
1259
|
-
|
1260
|
-
Shorthand for `command.chain(:on => :failure)`. Registers a command or block to run after the current command. The chained command will only run if the previous command was unsuccessfully run.
|
1261
|
-
|
1262
|
-
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).
|
1263
|
-
|
1264
|
-
else() { |result| ... } #=> Cuprum::Command
|
1265
|
-
|
1266
|
-
The block will be passed the #result of the previous command as its parameter. If your use case depends on the status of the previous command or on any errors generated, use the block form of #chain.
|
1267
|
-
|
1268
|
-
If the block returns a Cuprum::Result (or an object responding to #value and #success?), 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.
|
1269
|
-
|
1270
|
-
[Method Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum/Command#else-instance_method)
|
1271
|
-
|
1272
|
-
#### `#tap_result`
|
1273
|
-
|
1274
|
-
tap_result(on: nil) { |previous_result| } #=> Cuprum::Result
|
1275
|
-
|
1276
|
-
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.
|
1277
|
-
|
1278
|
-
If the `on` parameter is omitted, the block will be called if the last result is not halted. If the `on` parameter is set to `:success`, the block will be called if the last result is successful and not halted. If the `on` parameter is set to `:failure`, the block will be called if the last result is failing and not halted. Finally, if the `on` parameter is set to `:always`, the block will always be called, even if the last result is halted.
|
1279
|
-
|
1280
|
-
[Method Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum/Command#tap_result-instance_method)
|
1281
|
-
|
1282
|
-
#### `#tap_result!`
|
1283
|
-
|
1284
|
-
*(Protected Method)*
|
1285
|
-
|
1286
|
-
tap_result!(on: nil) { |previous_result| } #=> Cuprum::Result
|
1287
|
-
|
1288
|
-
As `#tap_result`, but modifies the current command instead of creating a clone.
|
1289
|
-
|
1290
|
-
[Method Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum/Command#tap_result!-instance_method)
|
1291
|
-
|
1292
|
-
#### `#then`
|
1293
|
-
|
1294
|
-
then(command) #=> Cuprum::Command
|
1295
|
-
|
1296
|
-
Shorthand for `command.chain(:on => :success)`. Registers a command or block to run after the current command. The chained command will only run if the previous command was successfully run.
|
1297
|
-
|
1298
|
-
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).
|
1299
|
-
|
1300
|
-
then() { |result| ... } #=> Cuprum::Command
|
1301
|
-
|
1302
|
-
The block will be passed the #result of the previous command as its parameter. If your use case depends on the status of the previous command or on any errors generated, use the block form of #chain.
|
1303
|
-
|
1304
|
-
If the block returns a Cuprum::Result (or an object responding to #value and #success?), 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.
|
1305
|
-
|
1306
|
-
[Method Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum/Command#then-instance_method)
|
1307
|
-
|
1308
|
-
#### `#yield_result`
|
1309
|
-
|
1310
|
-
yield_result(on: nil) { |previous_result| } #=> Cuprum::Result
|
1311
|
-
|
1312
|
-
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.
|
1313
|
-
|
1314
|
-
If the `on` parameter is omitted, the block will be called if the last result is not halted. If the `on` parameter is set to `:success`, the block will be called if the last result is successful and not halted. If the `on` parameter is set to `:failure`, the block will be called if the last result is failing and not halted. Finally, if the `on` parameter is set to `:always`, the block will always be called, even if the last result is halted.
|
1315
|
-
|
1316
|
-
[Method Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum/Command#yield_result-instance_method)
|
1317
|
-
|
1318
|
-
#### `#yield_result!`
|
1319
|
-
|
1320
|
-
*(Protected Method)*
|
1321
|
-
|
1322
|
-
yield_result!(on: nil) { |previous_result| } #=> Cuprum::Result
|
1323
|
-
|
1324
|
-
As `#yield_result`, but modifies the current command instead of creating a clone.
|
1325
|
-
|
1326
|
-
[Method Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum/Command#yield_result!-instance_method)
|
1327
|
-
|
1328
|
-
### Cuprum::Operation
|
1329
|
-
|
1330
|
-
require 'cuprum'
|
1331
|
-
|
1332
|
-
[Class Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum%2FOperation)
|
1333
|
-
|
1334
|
-
A Cuprum::Operation inherits the methods from Cuprum::Command (see above), and defines the following additional methods:
|
1335
|
-
|
1336
|
-
#### `#called?`
|
1337
|
-
|
1338
|
-
called?() #=> true, false
|
1339
|
-
|
1340
|
-
True if the operation has been called and there is a result available by calling `#result` or one of the delegated methods, otherwise false.
|
1341
|
-
|
1342
|
-
[Method Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum/Operation#called%3F-instance_method)
|
1343
|
-
|
1344
|
-
#### `#reset!`
|
1345
|
-
|
1346
|
-
reset!()
|
1347
|
-
|
1348
|
-
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.
|
1349
|
-
|
1350
|
-
[Method Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum/Operation#reset!-instance_method)
|
1351
|
-
|
1352
|
-
#### `#result`
|
1353
|
-
|
1354
|
-
result() #=> Cuprum::Result
|
1355
|
-
|
1356
|
-
The most recent result, from the previous time `#call` was executed for the operation.
|
1357
|
-
|
1358
|
-
[Method Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum/Operation#result-instance_method)
|
1359
|
-
|
1360
|
-
### Cuprum::Result
|
1361
|
-
|
1362
|
-
[Class Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum%2FResult)
|
1363
|
-
|
1364
|
-
A Cuprum::Result defines the following methods:
|
1365
|
-
|
1366
|
-
#### `#==`
|
1367
|
-
|
1368
|
-
==(other) #=> true, false
|
1369
|
-
|
1370
|
-
Performs a fuzzy comparison with the other object. At a minimum, the other object must respond to `#value` and `#success?`, and the values of `other.value` and `other.success?` must be equal to the corresponding value on the result. In addition, if the `#failure?`, `#errors`, or `#halted?` methods are defined on the other object, then the value of each defined method is compared to the value on the result. Returns true if all values match, otherwise returns false.
|
1371
|
-
|
1372
|
-
#### `#empty?`
|
1373
|
-
|
1374
|
-
empty?() #=> true, false
|
1375
|
-
|
1376
|
-
Helper method that returns true for a new result. The method returns false if `result.value` is not nil, if `result.errors` is not empty, if the status has been manually set with `#success!` or `#failure!`, or if the result has been halted.
|
1377
|
-
|
1378
|
-
#### `#errors`
|
1379
|
-
|
1380
|
-
errors() #=> Array
|
1381
|
-
|
1382
|
-
The errors generated by the command, or an empty array if no errors were generated.
|
1383
|
-
|
1384
|
-
[Method Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum/Result#errors-instance_method)
|
1385
|
-
|
1386
|
-
#### `#failure!`
|
1387
|
-
|
1388
|
-
failure!() #=> Cuprum::Result
|
1389
|
-
|
1390
|
-
Marks the result as failing and returns the result. Calling `#failure?` will return true, even if the result has no errors.
|
1391
|
-
|
1392
|
-
[Method Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum/Result#failure!-instance_method)
|
1393
|
-
|
1394
|
-
#### `#failure?`
|
1395
|
-
|
1396
|
-
failure?() #=> true, false
|
1397
|
-
|
1398
|
-
True if the command generated one or more errors or was marked as failing. Otherwise false.
|
1399
|
-
|
1400
|
-
[Method Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum/Result#failure%3F-instance_method)
|
1401
|
-
|
1402
|
-
#### `#halt!`
|
1403
|
-
|
1404
|
-
halt!() #=> Cuprum::Result
|
1405
|
-
|
1406
|
-
Marks the result as halted and returns the result. Calling `#halted?` will return true.
|
1407
|
-
|
1408
|
-
[Method Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum/Result#halt!-instance_method)
|
1409
|
-
|
1410
|
-
#### `#halted?`
|
1411
|
-
|
1412
|
-
halted?() #=> true, false
|
1413
|
-
|
1414
|
-
True if the result is halted, which prevents chained commands from executing.
|
1415
|
-
|
1416
|
-
#### `#success!`
|
1417
|
-
|
1418
|
-
success!() #=> Cuprum::Result
|
1419
|
-
|
1420
|
-
Marks the result as passing and returns the result. Calling `#success?` will return true, even if the result has errors.
|
1421
|
-
|
1422
|
-
[Method Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum/Result#success!-instance_method)
|
1423
|
-
|
1424
|
-
#### `#success?`
|
1425
|
-
|
1426
|
-
success?() #=> true, false
|
1427
|
-
|
1428
|
-
True if the command did not generate any errors, or the result has errors but was marked as passing. Otherwise false.
|
1429
|
-
|
1430
|
-
[Method Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum/Result#success%3F-instance_method)
|
1431
|
-
|
1432
|
-
#### `#value`
|
1433
|
-
|
1434
|
-
value() #=> Object
|
1435
|
-
|
1436
|
-
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.
|
1437
|
-
|
1438
|
-
[Method Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum/Result#value-instance_method)
|
1439
|
-
|
1440
|
-
### Cuprum::Utilities::InstanceSpy
|
1441
|
-
|
1442
|
-
require 'cuprum/utils/instance_spy'
|
1443
|
-
|
1444
|
-
[Class Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum%2FUtils%2FInstanceSpy)
|
1445
|
-
|
1446
|
-
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.
|
1447
|
-
|
1448
|
-
#### `::clear_spies`
|
1449
|
-
|
1450
|
-
clear_spies() #=> nil
|
1451
|
-
|
1452
|
-
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.
|
1453
|
-
|
1454
|
-
after(:example) { Cuprum::Utils::InstanceSpy.clear_spies }
|
1455
|
-
|
1456
|
-
[Method Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum/Utils/InstanceSpy#clear_spies%3F-instance_method)
|
1457
|
-
|
1458
|
-
#### `::spy_on`
|
1459
|
-
|
1460
|
-
spy_on(command_class) #=> InstanceSpy
|
1461
|
-
spy_on(command_class) { |spy| ... } #=> nil
|
1462
|
-
|
1463
|
-
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.
|
1464
|
-
|
1465
|
-
# Observing calls to instances of a command.
|
1466
|
-
spy = Cuprum::Utils::InstanceSpy.spy_on(CustomCommand)
|
1467
|
-
|
1468
|
-
expect(spy).to receive(:call).with(1, 2, 3, :four => '4')
|
1469
|
-
|
1470
|
-
CustomCommand.new.call(1, 2, 3, :four => '4')
|
1471
|
-
|
1472
|
-
# Observing calls to a chained command.
|
1473
|
-
spy = Cuprum::Utils::InstanceSpy.spy_on(ChainedCommand)
|
1474
|
-
|
1475
|
-
expect(spy).to receive(:call)
|
1476
|
-
|
1477
|
-
Cuprum::Command.new {}.
|
1478
|
-
chain { |result| ChainedCommand.new.call(result) }.
|
1479
|
-
call
|
1480
|
-
|
1481
|
-
# Block syntax
|
1482
|
-
Cuprum::Utils::InstanceSpy.spy_on(CustomCommand) do |spy|
|
1483
|
-
expect(spy).to receive(:call)
|
1484
|
-
|
1485
|
-
CustomCommand.new.call
|
1486
|
-
end # spy_on
|
1487
|
-
|
1488
|
-
[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).
|