cuprum 0.10.0 → 0.11.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 +52 -2
- data/DEVELOPMENT.md +4 -21
- data/README.md +487 -95
- data/lib/cuprum.rb +12 -7
- data/lib/cuprum/built_in.rb +3 -1
- data/lib/cuprum/built_in/identity_command.rb +6 -4
- data/lib/cuprum/built_in/identity_operation.rb +4 -2
- data/lib/cuprum/built_in/null_command.rb +5 -3
- data/lib/cuprum/built_in/null_operation.rb +4 -2
- data/lib/cuprum/command.rb +29 -58
- data/lib/cuprum/command_factory.rb +7 -5
- data/lib/cuprum/currying.rb +3 -2
- data/lib/cuprum/currying/curried_command.rb +11 -4
- data/lib/cuprum/error.rb +44 -10
- data/lib/cuprum/errors.rb +2 -0
- data/lib/cuprum/errors/command_not_implemented.rb +6 -3
- data/lib/cuprum/errors/operation_not_called.rb +6 -6
- data/lib/cuprum/errors/uncaught_exception.rb +55 -0
- data/lib/cuprum/exception_handling.rb +50 -0
- data/lib/cuprum/matcher.rb +90 -0
- data/lib/cuprum/matcher_list.rb +150 -0
- data/lib/cuprum/matching.rb +232 -0
- data/lib/cuprum/matching/match_clause.rb +65 -0
- data/lib/cuprum/middleware.rb +210 -0
- data/lib/cuprum/operation.rb +17 -15
- data/lib/cuprum/result.rb +1 -3
- data/lib/cuprum/rspec/be_a_result.rb +10 -1
- data/lib/cuprum/rspec/be_a_result_matcher.rb +2 -4
- data/lib/cuprum/rspec/be_callable.rb +14 -0
- data/lib/cuprum/steps.rb +47 -89
- data/lib/cuprum/utils.rb +3 -1
- data/lib/cuprum/utils/instance_spy.rb +28 -28
- data/lib/cuprum/version.rb +14 -11
- metadata +32 -21
- data/lib/cuprum/chaining.rb +0 -441
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3938b7e4b35db9560c46f0c4233c4ee50972e34e66145a238ba8415af3998b80
|
4
|
+
data.tar.gz: b78ca19367cfb764110b32849a191bcd0519fbf4668c554b3af15b81313990b2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e5593d6900fb03df0c53681922e180722491d56d3b60281b881e7e9e04118d8cbea5b8e590df80b205f44fd21bcdfacdc2a79c83f8657e09b3e0fa2128e9f742
|
7
|
+
data.tar.gz: 18a2d36932b71428dc3cf93ffd767844c87eddf105666838907372d1276e873aaffc988d1d6543e413896670d0bde4a51ca7f9a4cd3bfa6088f565faca85db1d
|
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,56 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
-
## 0.
|
3
|
+
## 0.11.0
|
4
|
+
|
5
|
+
The "One Giant Leap" Update
|
6
|
+
|
7
|
+
**Note:** This will be the last feature update before 1.0.
|
8
|
+
|
9
|
+
### Commands
|
10
|
+
|
11
|
+
Implemented the `#to_proc` method, which allows for constructs such as `array.map(&command)`.
|
12
|
+
|
13
|
+
Removed the deprecated chaining mechanic.
|
14
|
+
|
15
|
+
#### Currying
|
16
|
+
|
17
|
+
Added support for currying block parameters.
|
18
|
+
|
19
|
+
#### Exception Handling
|
20
|
+
|
21
|
+
Defined `Cuprum::ExceptionHandling` to rescue uncaught errors in commands.
|
22
|
+
|
23
|
+
Exception handling is *not* included by default - add `include Cuprum::ExceptionHandling` to your command classes to use this feature.
|
24
|
+
|
25
|
+
#### Middleware
|
26
|
+
|
27
|
+
Defined `Cuprum::Middleware` to define a wrapper that calls other commands.
|
28
|
+
|
29
|
+
#### Steps
|
30
|
+
|
31
|
+
Deprecated calling `#step` with a method name.
|
32
|
+
|
33
|
+
The error type and message when calling `#steps` without a block has changed.
|
34
|
+
|
35
|
+
### Errors
|
36
|
+
|
37
|
+
Errors can now define their comparable properties by passing additional keywords to the constructor (or `super` for error subclasses).
|
38
|
+
|
39
|
+
Added the `#type` method and property.
|
40
|
+
|
41
|
+
Added serialization via the `#as_json` method.
|
42
|
+
|
43
|
+
### Matchers
|
44
|
+
|
45
|
+
Implemented `Cuprum::Matcher`, which provides a way to handle different result cases.
|
46
|
+
|
47
|
+
### RSpec
|
48
|
+
|
49
|
+
Added the `#be_callable` macro, which is a wrapper for `#respond_to` that references the `#process` method.
|
50
|
+
|
51
|
+
RSpec matchers are no longer automatically included when the macro is required. To use the Cuprum matchers, add `config.include Cuprum::RSpec::Matchers` to your RSpec configuration, or add `include Cuprum::RSpec::Matchers` to your example groups.
|
52
|
+
|
53
|
+
## 0.10.0
|
4
54
|
|
5
55
|
The "One Small Step" Update
|
6
56
|
|
@@ -12,7 +62,7 @@ Implemented the `#curry` method, which performs partial application of arguments
|
|
12
62
|
|
13
63
|
#### Chaining
|
14
64
|
|
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
|
65
|
+
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 0.11.
|
16
66
|
|
17
67
|
#### Steps
|
18
68
|
|
data/DEVELOPMENT.md
CHANGED
@@ -4,31 +4,25 @@
|
|
4
4
|
|
5
5
|
The "Look On My Works, Ye Mighty, and Despair" Update
|
6
6
|
|
7
|
-
-
|
8
|
-
- Code cleanup: Hash syntax, remove end comments, remove file headers
|
9
|
-
- Status Badges!
|
7
|
+
- Documentation pass.
|
10
8
|
|
11
9
|
Steps Case Study: |
|
12
10
|
|
13
11
|
CMS application - creating a new post.
|
14
12
|
Directory has many Posts
|
15
13
|
Post has a Content
|
16
|
-
Content has many ContentVersions
|
17
14
|
Post has many Tags
|
18
15
|
|
19
16
|
Find Directory
|
20
17
|
Create Post
|
21
18
|
Create Content
|
22
|
-
Create ContentVersion
|
23
19
|
Tags.each { FindOrCreate Tag }
|
24
|
-
|
25
|
-
### Commands
|
26
|
-
|
27
|
-
- Remove `Cuprum::Chaining`.
|
28
|
-
- Implement `Command#to_proc`.
|
20
|
+
Publish Post # Requires that post have content
|
29
21
|
|
30
22
|
## Future Versions
|
31
23
|
|
24
|
+
Add `.rbs` files
|
25
|
+
|
32
26
|
### Commands
|
33
27
|
|
34
28
|
- Implement #<<, #>> composition methods.
|
@@ -60,19 +54,10 @@ Steps Case Study: |
|
|
60
54
|
- RetryCommand - takes command, retry count
|
61
55
|
- optional only:, except: - restrict what errors are retried
|
62
56
|
|
63
|
-
### Matcher
|
64
|
-
|
65
|
-
- Handle success(), failure(), failure(SomeError) cases.
|
66
|
-
- Custom matcher to handle additional cases - halted, pending, etc?
|
67
|
-
|
68
57
|
### Middleware
|
69
58
|
|
70
59
|
- Implement Command.subclass
|
71
60
|
- 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
61
|
- Implement Cuprum::AppliedMiddleware < Cuprum::Command
|
77
62
|
- has readers #root (Class), #middleware (Array<Class>)
|
78
63
|
- #initialize
|
@@ -87,8 +72,6 @@ Steps Case Study: |
|
|
87
72
|
|
88
73
|
### RSpec
|
89
74
|
|
90
|
-
- be_callable matcher - delegates to respond_to(), but check arguments of
|
91
|
-
private #process method
|
92
75
|
- call_command_step matcher
|
93
76
|
- (optionally) alias be_a_result family as have_result for operations
|
94
77
|
|
data/README.md
CHANGED
@@ -4,10 +4,11 @@ An opinionated implementation of the Command pattern for Ruby applications. Cupr
|
|
4
4
|
|
5
5
|
It defines the following concepts:
|
6
6
|
|
7
|
-
- [Commands](#
|
8
|
-
- [Operations](#
|
9
|
-
- [Results](#
|
10
|
-
- [Errors](#
|
7
|
+
- [Commands](#Commands) - A function-like object that responds to `#call` and returns a `Result`.
|
8
|
+
- [Operations](#Operations) - A stateful `Command` that wraps and delegates to its most recent `Result`.
|
9
|
+
- [Results](#Results) - An immutable data object with a status (either `:success` or `:failure`), and optional `#value` and/or `#error` objects.
|
10
|
+
- [Errors](#Errors) - Encapsulates a failure state of a command.
|
11
|
+
- [Matchers](#Matchers) - Define handling for results based on status, error, and value.
|
11
12
|
|
12
13
|
## About
|
13
14
|
|
@@ -18,7 +19,7 @@ Traditional frameworks such as Rails focus on the objects of your application -
|
|
18
19
|
- **Consistency:** Use the same Commands to underlie controller actions, worker processes and test factories.
|
19
20
|
- **Encapsulation:** Each Command is defined and run in isolation, and dependencies must be explicitly provided to the command when it is initialized or run. This makes it easier to reason about the command's behavior and keep it insulated from changes elsewhere in the code.
|
20
21
|
- **Testability:** Because the logic is extracted from unnecessary context, testing its behavior is much cleaner and easier.
|
21
|
-
- **Composability:** Complex logic such as "find the object with this ID, update it with these attributes, and log the transaction to the reporting service" can be extracted into a series of simple Commands and composed together. The [
|
22
|
+
- **Composability:** Complex logic such as "find the object with this ID, update it with these attributes, and log the transaction to the reporting service" can be extracted into a series of simple Commands and composed together. The [step](#label-Command+Steps) feature allows for complex control flows.
|
22
23
|
- **Reusability:** Logic common to multiple data models or instances in your code, such as "persist an object to the database" or "find all records with a given user and created in a date range" can be refactored into parameterized commands.
|
23
24
|
|
24
25
|
### Alternatives
|
@@ -32,12 +33,10 @@ and [Waterfall](https://github.com/apneadiving/waterfall).
|
|
32
33
|
|
33
34
|
### Compatibility
|
34
35
|
|
35
|
-
Cuprum is tested against Ruby (MRI) 2.
|
36
|
+
Cuprum is tested against Ruby (MRI) 2.6 through 3.0.
|
36
37
|
|
37
38
|
### Documentation
|
38
39
|
|
39
|
-
Method and class documentation is available courtesy of [RubyDoc](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master).
|
40
|
-
|
41
40
|
Documentation is generated using [YARD](https://yardoc.org/), and can be generated locally using the `yard` gem.
|
42
41
|
|
43
42
|
### License
|
@@ -54,13 +53,9 @@ To report a bug or submit a feature request, please use the [Issue Tracker](http
|
|
54
53
|
|
55
54
|
To contribute code, please fork the repository, make the desired updates, and then provide a [Pull Request](https://github.com/sleepingkingstudios/cuprum/pulls). Pull requests must include appropriate tests for consideration, and all code must be properly formatted.
|
56
55
|
|
57
|
-
|
58
|
-
|
59
|
-
Hi, I'm Rob Smith, a Ruby Engineer and the developer of this library. I use these tools every day, but they're not just written for me. If you find this project helpful in your own work, or if you have any questions, suggestions or critiques, please feel free to get in touch! I can be reached [on GitHub](https://github.com/sleepingkingstudios/cuprum) or [via email](mailto:merlin@sleepingkingstudios.com). I look forward to hearing from you!
|
56
|
+
<a id="Commands"></a>
|
60
57
|
|
61
|
-
##
|
62
|
-
|
63
|
-
### Commands
|
58
|
+
## Commands
|
64
59
|
|
65
60
|
require 'cuprum'
|
66
61
|
|
@@ -68,9 +63,7 @@ Commands are the core feature of Cuprum. In a nutshell, each `Cuprum::Command` i
|
|
68
63
|
|
69
64
|
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).
|
70
65
|
|
71
|
-
|
72
|
-
|
73
|
-
#### Defining Commands
|
66
|
+
### Defining Commands
|
74
67
|
|
75
68
|
The recommended way to define commands is to create a subclass of `Cuprum::Command` and override the `#process` method.
|
76
69
|
|
@@ -141,7 +134,7 @@ inspect_command = Cuprum::Command.new(&:inspect) # Equivalent to above.
|
|
141
134
|
|
142
135
|
Commands defined using `Cuprum::Command.new` are quick to use, but more difficult to read and to reuse. Defining your own command class is recommended if a command definition takes up more than one line, or if the command will be used in more than one place.
|
143
136
|
|
144
|
-
|
137
|
+
### Result Values
|
145
138
|
|
146
139
|
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`).
|
147
140
|
|
@@ -165,7 +158,7 @@ result.class #=> Cuprum::Result
|
|
165
158
|
result.value #=> 'Greetings, programs!'
|
166
159
|
```
|
167
160
|
|
168
|
-
|
161
|
+
### Success, Failure, and Errors
|
169
162
|
|
170
163
|
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.
|
171
164
|
|
@@ -213,13 +206,13 @@ result.value #=> book
|
|
213
206
|
book.published? #=> false
|
214
207
|
```
|
215
208
|
|
216
|
-
|
209
|
+
### Command Currying
|
217
210
|
|
218
211
|
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
212
|
|
220
213
|
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
214
|
|
222
|
-
|
215
|
+
#### Currying Arguments
|
223
216
|
|
224
217
|
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
218
|
|
@@ -247,7 +240,7 @@ recruit_command.call
|
|
247
240
|
#=> returns a result with value 'Greetings, starfighter!'
|
248
241
|
```
|
249
242
|
|
250
|
-
|
243
|
+
#### Currying Keywords
|
251
244
|
|
252
245
|
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
246
|
|
@@ -526,42 +519,6 @@ result.success? #=> true
|
|
526
519
|
result.value #=> an instance of BookReservation
|
527
520
|
```
|
528
521
|
|
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.
|
532
|
-
|
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.
|
534
|
-
|
535
|
-
We can use this to rewrite our `ReserveBookByTitle` command to use methods:
|
536
|
-
|
537
|
-
```ruby
|
538
|
-
class ReserveBookByTitle < Cuprum::Result
|
539
|
-
private
|
540
|
-
|
541
|
-
def check_user_status(user)
|
542
|
-
CheckUserStatus.new(user)
|
543
|
-
end
|
544
|
-
|
545
|
-
def create_book_reservation(book:, user:)
|
546
|
-
CreateBookReservation.new(book: book, user: user)
|
547
|
-
end
|
548
|
-
|
549
|
-
def find_book_by_title(title)
|
550
|
-
FindBookByTitle.new.call(title)
|
551
|
-
end
|
552
|
-
|
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
|
561
|
-
```
|
562
|
-
|
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
522
|
#### Using Steps Outside Of Commands
|
566
523
|
|
567
524
|
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.
|
@@ -625,11 +582,44 @@ A few things to note about this example. First, we have a couple of examples of
|
|
625
582
|
|
626
583
|
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
584
|
|
628
|
-
###
|
585
|
+
### Handling Exceptions
|
629
586
|
|
630
|
-
require 'cuprum'
|
587
|
+
require 'cuprum/exception_handling'
|
588
|
+
|
589
|
+
Cuprum defines a utility module to rescue uncaught exceptions when calling a command.
|
590
|
+
|
591
|
+
```ruby
|
592
|
+
class UnsafeCommand < Cuprum::Command
|
593
|
+
private
|
631
594
|
|
632
|
-
|
595
|
+
def process
|
596
|
+
raise 'Something went wrong.'
|
597
|
+
end
|
598
|
+
end
|
599
|
+
|
600
|
+
class SafeCommand < UnsafeCommand
|
601
|
+
include Cuprum::ExceptionHandling
|
602
|
+
end
|
603
|
+
|
604
|
+
UnsafeCommand.new.call
|
605
|
+
#=> raises a StandardError
|
606
|
+
|
607
|
+
result = SafeCommand.new.call
|
608
|
+
#=> a Cuprum::Result
|
609
|
+
result.error
|
610
|
+
#=> a Cuprum::Errors::UncaughtException error.
|
611
|
+
result.error.message
|
612
|
+
#=> 'uncaught exception in SafeCommand -' \
|
613
|
+
' StandardError: Something went wrong.'
|
614
|
+
```
|
615
|
+
|
616
|
+
Exception handling is *not* included by default - add `include Cuprum::ExceptionHandling` to your command classes to use this feature.
|
617
|
+
|
618
|
+
<a id="Results"></a>
|
619
|
+
|
620
|
+
## Results
|
621
|
+
|
622
|
+
require 'cuprum'
|
633
623
|
|
634
624
|
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
625
|
|
@@ -696,42 +686,58 @@ result.success? #=> true
|
|
696
686
|
result.failure? #=> false
|
697
687
|
```
|
698
688
|
|
699
|
-
|
689
|
+
<a id="Errors"></a>
|
700
690
|
|
701
|
-
|
691
|
+
## Errors
|
702
692
|
|
703
|
-
|
693
|
+
require 'cuprum/error'
|
704
694
|
|
705
|
-
A `Cuprum::Error` encapsulates a specific failure state of a Command. Each Error has a `#message` property
|
695
|
+
A `Cuprum::Error` encapsulates a specific failure state of a Command. Each Error has a `#message` property which defaults to nil. Each Error also has a `#type` property which is determined by the Error class or subclass, although it can be overridden by passing a `:type` parameter to the constructor.
|
706
696
|
|
707
697
|
```ruby
|
708
698
|
error = Cuprum::Error.new
|
709
699
|
error.message => # nil
|
700
|
+
error.type => 'cuprum.error'
|
710
701
|
|
711
702
|
error = Cuprum::Error.new(message: 'Something went wrong.')
|
712
703
|
error.message => # 'Something went wrong.'
|
704
|
+
|
705
|
+
error = Cuprum::Error.new(type: 'example.custom_type')
|
706
|
+
error.type => 'example.custom_type'
|
713
707
|
```
|
714
708
|
|
715
709
|
Each application should define its own failure states as errors. For example, a typical web application might define the following errors:
|
716
710
|
|
717
711
|
```ruby
|
718
712
|
class NotFoundError < Cuprum::Error
|
713
|
+
TYPE = 'example.errors.not_found'
|
714
|
+
|
719
715
|
def initialize(resource:, resource_id:)
|
720
716
|
@resource = resource
|
721
717
|
@resource_id = resource_id
|
722
718
|
|
723
|
-
super(
|
719
|
+
super(
|
720
|
+
message: "#{resource} not found with id #{resource_id}",
|
721
|
+
resource: resource,
|
722
|
+
resource_id: resource_id
|
723
|
+
)
|
724
724
|
end
|
725
725
|
|
726
726
|
attr_reader :resource, :resource_id
|
727
727
|
end
|
728
728
|
|
729
729
|
class ValidationError < Cuprum::Error
|
730
|
+
TYPE = 'example.errors.validation'
|
731
|
+
|
730
732
|
def initialize(resource:, errors:)
|
731
733
|
@resource = resource
|
732
734
|
@errors = errors
|
733
735
|
|
734
|
-
super(
|
736
|
+
super(
|
737
|
+
errors: errors,
|
738
|
+
message: "#{resource} was invalid",
|
739
|
+
resource: resource
|
740
|
+
)
|
735
741
|
end
|
736
742
|
|
737
743
|
attr_reader :resource, :errors
|
@@ -740,11 +746,185 @@ end
|
|
740
746
|
|
741
747
|
It is optional but recommended to use a `Cuprum::Error` when returning a failed result from a command.
|
742
748
|
|
743
|
-
###
|
749
|
+
### Comparing Errors
|
744
750
|
|
745
|
-
|
751
|
+
There are circumstances when it is useful to compare Error objects, such as when writing tests to specify the failure states of a command. To accommodate this, you can pass additional properties to `Cuprum::Error.new` (or to `super` when defining a subclass). These "comparable properties", plus the type and message (if any), are used to compare the errors.
|
752
|
+
|
753
|
+
An instance of `Cuprum::Error` is equal to another (using the `#==` equality comparison) if and only if the two errors have the same `class` and the two errors have the same comparable properties.
|
754
|
+
|
755
|
+
```ruby
|
756
|
+
red = Cuprum::Error.new(message: 'wrong color', color: 'red')
|
757
|
+
blue = Cuprum::Error.new(message: 'wrong color', color: 'blue')
|
758
|
+
crimson = Cuprum::Error.new(message: 'wrong color', color: 'red')
|
759
|
+
|
760
|
+
red == blue
|
761
|
+
#=> false
|
762
|
+
|
763
|
+
red == crimson
|
764
|
+
#=> true
|
765
|
+
```
|
766
|
+
|
767
|
+
This can be particularly important when defining Error subclasses. By passing the constructor parameters to `super`, below, we will be able to compare different instances of the `NotFoundError`. The errors will only be equal if they have the same message, resource, and resource_id properties.
|
768
|
+
|
769
|
+
```ruby
|
770
|
+
class NotFoundError < Cuprum::Error
|
771
|
+
def initialize(resource:, resource_id:)
|
772
|
+
@resource = resource
|
773
|
+
@resource_id = resource_id
|
774
|
+
|
775
|
+
super(
|
776
|
+
message: "#{resource} not found with id #{resource_id}",
|
777
|
+
resource: resource,
|
778
|
+
resource_id: resource_id,
|
779
|
+
)
|
780
|
+
end
|
781
|
+
|
782
|
+
attr_reader :resource, :resource_id
|
783
|
+
end
|
784
|
+
```
|
785
|
+
|
786
|
+
Finally, by overriding the `#comparable_properties` method, you can customize how Error instances are compared.
|
787
|
+
|
788
|
+
```ruby
|
789
|
+
class WrongColorError < Cuprum::Error
|
790
|
+
def initialize(color:, shape:)
|
791
|
+
super(message: "the #{shape} is the wrong color")
|
792
|
+
|
793
|
+
@color = color
|
794
|
+
@shape = shape
|
795
|
+
end
|
796
|
+
|
797
|
+
attr_reader :color
|
798
|
+
|
799
|
+
protected
|
800
|
+
|
801
|
+
def comparable_properties
|
802
|
+
{ color: color }
|
803
|
+
end
|
804
|
+
end
|
805
|
+
```
|
806
|
+
|
807
|
+
### Serializing Errors
|
808
|
+
|
809
|
+
Some use cases require serializing error objects - for example, rendering an error response as JSON. To handle this, `Cuprum::Error` defines an `#as_json` method, which generates a representation of the error as a `Hash` with `String` keys. By default, this includes the `#type` and `#message` (if any) as well as an empty `:data` Hash.
|
810
|
+
|
811
|
+
Subclasses can override this behavior to include additional information in the `:data` Hash, which should always use `String` keys and have values composed of basic types and data structures. For example, if an error is passed a `Class`, consider serializing the name of the class to `:data`.
|
812
|
+
|
813
|
+
```ruby
|
814
|
+
error = Cuprum::Error.new
|
815
|
+
error.as_json #=> { data: {}, message: nil, type: 'cuprum.error' }
|
816
|
+
|
817
|
+
error = Cuprum::Error.new(message: 'Something went wrong.')
|
818
|
+
error.as_json #=> { data: {}, message: 'Something went wrong.', type: 'cuprum.error' }
|
819
|
+
|
820
|
+
error = Cuprum::Error.new(type: 'example.custom_error')
|
821
|
+
error.as_json #=> { data: {}, message: nil, type: 'example.custom_error' }
|
822
|
+
|
823
|
+
class ModuleError < Cuprum::Error
|
824
|
+
TYPE = 'example.module_error'
|
825
|
+
|
826
|
+
def initialize(actual:)
|
827
|
+
@actual = actual
|
828
|
+
message = "Expected a Module, but #{actual.name} is a Class"
|
829
|
+
|
830
|
+
super(actual: actual, message: message)
|
831
|
+
end
|
832
|
+
|
833
|
+
attr_reader :actual
|
834
|
+
|
835
|
+
private
|
836
|
+
|
837
|
+
def as_json_data
|
838
|
+
{ actual: actual.name }
|
839
|
+
end
|
840
|
+
end
|
841
|
+
|
842
|
+
error = ModuleError.new(actual: String)
|
843
|
+
error.as_json #=>
|
844
|
+
# {
|
845
|
+
# data: { actual: 'String' },
|
846
|
+
# message: 'Expected a Module, but String is a Class',
|
847
|
+
# type: 'example.module_error'
|
848
|
+
# }
|
849
|
+
```
|
850
|
+
|
851
|
+
**Important Note:** Be careful when serializing error data - this may expose sensitive information or internal details about your system that you don't want to display to users. Recommended practice is to have a whitelist of serializable errors; all other errors will display a generic error message instead.
|
852
|
+
|
853
|
+
<a id="Middleware"></a>
|
854
|
+
|
855
|
+
## Middleware
|
856
|
+
|
857
|
+
```ruby
|
858
|
+
require 'cuprum/middleware'
|
859
|
+
```
|
860
|
+
|
861
|
+
A middleware command wraps the execution of another command, allowing the developer to compose functionality without an explicit wrapper command. Because the middleware is responsible for calling the wrapped command, it has control over when that command is called, with what parameters, and how the command result is handled.
|
862
|
+
|
863
|
+
To use middleware, start by defining a middleware command. This can either be a class that includes Cuprum::Middleware, or a command instance that extends Cuprum::Middleware. Each middleware command's #process method takes as its first argument the wrapped command. By convention, any additional arguments and any keywords or a block are passed to the wrapped command, but some middleware will override ths behavior.
|
864
|
+
|
865
|
+
```ruby
|
866
|
+
class ExampleCommand < Cuprum::Command
|
867
|
+
private def process(**options)
|
868
|
+
return failure(options[:error]) if options[:error]
|
869
|
+
|
870
|
+
"Options: #{options.inspect}"
|
871
|
+
end
|
872
|
+
end
|
873
|
+
|
874
|
+
class LoggingMiddleware < Cuprum::Command
|
875
|
+
include Cuprum::Middleware
|
876
|
+
|
877
|
+
# The middleware injects a logging step before the wrapped command is
|
878
|
+
# called. Notice that this middleware is generic, and can be used with
|
879
|
+
# virtually any other command.
|
880
|
+
private def process(next_command, *args, **kwargs)
|
881
|
+
Logger.info("Calling command #{next_command.class}")
|
882
|
+
|
883
|
+
super
|
884
|
+
end
|
885
|
+
end
|
886
|
+
|
887
|
+
command = Command.new { |**opts| "Called with #{opts.inspect}" }
|
888
|
+
middleware = LoggingMiddleware.new
|
889
|
+
result = middleware.call(command, { id: 0 })
|
890
|
+
#=> logs "Calling command ExampleCommand"
|
891
|
+
result.value
|
892
|
+
#=> "Options: { id: 0 }"
|
893
|
+
```
|
746
894
|
|
747
|
-
|
895
|
+
When defining #process, make sure to either call super or call the wrapped command directly, unless the middleware is specifically intended not to call the wrapped command under those circumstances.
|
896
|
+
|
897
|
+
Middleware is powerful because it allows the developer to manipulate the parameters passed to a command, add handling to a result, or even intercept or override the command execution. These are some of the possible use cases for middleware:
|
898
|
+
|
899
|
+
- Injecting code before or after a command.
|
900
|
+
- Changing the parameters passed to a command.
|
901
|
+
- Adding behavior based on the command result.
|
902
|
+
- Overriding the command behavior based on the parameters.
|
903
|
+
|
904
|
+
```ruby
|
905
|
+
class AuthenticationMiddleware < Cuprum::Command
|
906
|
+
include Cuprum::Middleware
|
907
|
+
|
908
|
+
# The middleware finds the current user based on the given keywords. If
|
909
|
+
# a valid user is found, the user is then passed on to the command.
|
910
|
+
# If a user is not found, then the middleware will immediately halt (due
|
911
|
+
# to #step) and return the failing result from the authentication
|
912
|
+
# command.
|
913
|
+
private def process(next_command, *args, **kwargs)
|
914
|
+
current_user = step { AuthenticateUser.new.call(**kwargs) }
|
915
|
+
|
916
|
+
super(next_command, *args, current_user: current_user, **kwargs)
|
917
|
+
end
|
918
|
+
end
|
919
|
+
```
|
920
|
+
|
921
|
+
Middleware is loosely coupled, meaning that one middleware command can wrap any number of other commands. One example would be logging middleware, which could record when a command is called and with what parameters. For a more involved example, consider authorization in a web application. If individual actions are defined as commands, then a single authorization middleware class could wrap each individual action, reducing both the testing burden and the amount of code that must be maintained.
|
922
|
+
|
923
|
+
<a id="Operations"></a>
|
924
|
+
|
925
|
+
## Operations
|
926
|
+
|
927
|
+
require 'cuprum'
|
748
928
|
|
749
929
|
An Operation is like a Command, but with two key differences. First, an Operation retains a reference to the result object from the most recent time the operation was called, and delegates the methods defined by `Cuprum::Result` to the most recent result. This allows a called Operation to replace a `Cuprum::Result` in any code that expects or returns a result. Second, the `#call` method returns the operation instance, rather than the result itself.
|
750
930
|
|
@@ -775,15 +955,239 @@ Like a Command, an Operation can be defined directly by passing an implementatio
|
|
775
955
|
|
776
956
|
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.
|
777
957
|
|
778
|
-
|
779
|
-
|
780
|
-
[Module Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum%2FOperation%2FMixin)
|
958
|
+
### The Operation Mixin
|
781
959
|
|
782
960
|
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.
|
783
961
|
|
784
|
-
|
962
|
+
<a id="Matchers"></a>
|
963
|
+
|
964
|
+
## Matchers
|
965
|
+
|
966
|
+
require 'cuprum/matcher'
|
967
|
+
|
968
|
+
A Matcher provides a simple DSL for defining behavior based on a Cuprum result object.
|
969
|
+
|
970
|
+
```ruby
|
971
|
+
matcher = Cuprum::Matcher.new do
|
972
|
+
match(:failure) { 'Something went wrong' }
|
973
|
+
|
974
|
+
match(:success) { 'Ok' }
|
975
|
+
end
|
976
|
+
|
977
|
+
matcher.call(Cuprum::Result.new(status: :failure))
|
978
|
+
#=> 'Something went wrong'
|
979
|
+
|
980
|
+
matcher.call(Cuprum::Result.new(status: :success))
|
981
|
+
#=> 'Ok'
|
982
|
+
```
|
983
|
+
|
984
|
+
First, the matcher defines possible matches using the `.match` method. This can either be called on a subclass of `Cuprum::Matcher` or by passing a block to the constructor, as above. Each match clause must have the matching status, and a block that is executed when a result matches that clause. The clause can also filter by the result value or error (see Matching Values And Errors, below).
|
985
|
+
|
986
|
+
Once the matcher has found a matching clause, it then calls the block in the clause definition. If the block accepts an argument, the result is passed to the block; otherwise, the block is called with no arguments. This allows the match clause to use the error or value of the result.
|
987
|
+
|
988
|
+
```ruby
|
989
|
+
matcher = Cuprum::Matcher.new do
|
990
|
+
match(:failure) { |result| result.error.message }
|
991
|
+
end
|
992
|
+
|
993
|
+
error = Cuprum::Error.new(message: 'An error has occurred.')
|
994
|
+
matcher.call(Cuprum::Result.new(error: error))
|
995
|
+
#=> 'An error has occurred.'
|
996
|
+
```
|
997
|
+
|
998
|
+
If the result does not match any of the clauses, a `Cuprum::Matching::NoMatchError` is raised.
|
999
|
+
|
1000
|
+
```ruby
|
1001
|
+
matcher = Cuprum::Matcher.new do
|
1002
|
+
match(:success) { :ok }
|
1003
|
+
end
|
1004
|
+
|
1005
|
+
matcher.call(Cuprum::Result.new(status: :failure))
|
1006
|
+
#=> raises Cuprum::Matching::NoMatchError
|
1007
|
+
```
|
1008
|
+
|
1009
|
+
### Matching Values And Errors
|
1010
|
+
|
1011
|
+
In addition to a status, match clauses can specify the type of the value or error of a matching result. The error or value must be a Class or Module, and the clause will then match only results whose error or value is an instance of the specified Class or Module (or a subclass of the Class).
|
1012
|
+
|
1013
|
+
```ruby
|
1014
|
+
class MagicSmokeError < Cuprum::Error; end
|
1015
|
+
|
1016
|
+
matcher = Cuprum::Matcher.new do
|
1017
|
+
match(:failure) { 'Something went wrong.' }
|
1018
|
+
|
1019
|
+
match(:failure, error: Cuprum::Error) do |result|
|
1020
|
+
"ERROR: #{result.error.message}"
|
1021
|
+
end
|
1022
|
+
|
1023
|
+
match(:failure, error: MagicSmokeError) do
|
1024
|
+
"PANIC: #{result.error.message}"
|
1025
|
+
end
|
1026
|
+
end
|
1027
|
+
|
1028
|
+
matcher.call(Cuprum::Result.new(status: :failure))
|
1029
|
+
#=> 'Something went wrong.'
|
1030
|
+
|
1031
|
+
error = Cuprum::Error.new(message: 'An error has occurred.')
|
1032
|
+
matcher.call(Cuprum::Result.new(error: error)
|
1033
|
+
#=> 'ERROR: An error has occurred.'
|
1034
|
+
|
1035
|
+
error = MagicSmokeError.new(message: 'The magic smoke is escaping.')
|
1036
|
+
matcher.call(Cuprum::Result.new(error: error))
|
1037
|
+
#=> 'PANIC: The magic smoke is escaping.'
|
1038
|
+
```
|
1039
|
+
|
1040
|
+
The matcher will always apply the most specific match clause. In the example above, the result with a `MagicSmokeError` matches all three clauses, but only the final clause is executed.
|
1041
|
+
|
1042
|
+
You can also specify the value of a matching result:
|
1043
|
+
|
1044
|
+
```ruby
|
1045
|
+
matcher = Cuprum::Matcher.new do
|
1046
|
+
match(:success, value: String) { 'a String' }
|
1047
|
+
|
1048
|
+
match(:success, value: Symbol) { 'a Symbol' }
|
1049
|
+
end
|
1050
|
+
|
1051
|
+
matcher.call(Cuprum::Result.new(value: 'Greetings, programs!'))
|
1052
|
+
#=> 'a String'
|
1053
|
+
|
1054
|
+
matcher.call(Cuprum::Result.new(value: :greetings_starfighter))
|
1055
|
+
#=> 'a Symbol'
|
1056
|
+
```
|
1057
|
+
|
1058
|
+
### Using Matcher Classes
|
785
1059
|
|
786
|
-
|
1060
|
+
Matcher classes allow you to define custom behavior that can be called as part of the defined match clauses.
|
1061
|
+
|
1062
|
+
```ruby
|
1063
|
+
class LogMatcher < Cuprum::Matcher
|
1064
|
+
match(:failure) { |result| log(:error, result.error.message) }
|
1065
|
+
|
1066
|
+
match(:success) { log(:info, 'Ok') }
|
1067
|
+
|
1068
|
+
def log(level, message)
|
1069
|
+
puts "#{level.upcase}: #{message}"
|
1070
|
+
end
|
1071
|
+
end
|
1072
|
+
|
1073
|
+
matcher = LogMatcher.new
|
1074
|
+
matcher.call(Cuprum::Result.new(status: :success))
|
1075
|
+
#=> prints "INFO: Ok" to STDOUT
|
1076
|
+
```
|
1077
|
+
|
1078
|
+
Match clauses are also inherited by matcher subclasses. Inherited clauses are sorted the same as clauses defined on the matcher directly - the most specific clause is matched first, followed by less specific clauses and finally the generic clause (if any) for that result status.
|
1079
|
+
|
1080
|
+
```ruby
|
1081
|
+
class CustomLogMatcher < Cuprum::Matcher
|
1082
|
+
match(:failure, error: ReallyBadError) do |result|
|
1083
|
+
log(:fatal, result.error.message)
|
1084
|
+
end
|
1085
|
+
end
|
1086
|
+
|
1087
|
+
matcher = CustomLogMatcher.new
|
1088
|
+
result = Cuprum::Result.new(error: Cuprum::Error.new('Something went wrong.'))
|
1089
|
+
matcher.call(result)
|
1090
|
+
#=> prints "ERROR: Something went wrong." to STDOUT
|
1091
|
+
|
1092
|
+
result = Cuprum::Result.new(error: ReallyBadError.new('Computer on fire.'))
|
1093
|
+
matcher.call(result)
|
1094
|
+
#=> prints "FATAL: Computer on fire." to STDOUT
|
1095
|
+
```
|
1096
|
+
|
1097
|
+
### Match Contexts
|
1098
|
+
|
1099
|
+
Match contexts provide an alternative to defining custom matcher classes - instead of defining custom behavior in the matcher itself, the match clauses can be executed in the context of another object.
|
1100
|
+
|
1101
|
+
```ruby
|
1102
|
+
class Inflector
|
1103
|
+
def capitalize(message)
|
1104
|
+
message.split(' ').map(&:capitalize).join(' ')
|
1105
|
+
end
|
1106
|
+
end
|
1107
|
+
|
1108
|
+
matcher = Cuprum::Matcher.new(inflector) do
|
1109
|
+
match(:success) { |result| capitalize(result.value) }
|
1110
|
+
end
|
1111
|
+
matcher.call(Cuprum::Result.new(value: 'greetings starfighter'))
|
1112
|
+
#=> 'Greetings Starfighter'
|
1113
|
+
```
|
1114
|
+
|
1115
|
+
For example, a controller in a web framework might need to define behavior for handling different success and error cases for business logic that is defined as Commands. The controller itself defines methods such as `#render` and `#redirect` - by creating a matcher using the controller as the match context, the matcher can call upon those methods to generate a response.
|
1116
|
+
|
1117
|
+
You can also call an existing matcher with a new context. The `#with_context` method returns a copy of the matcher with the given object set as the match context.
|
1118
|
+
|
1119
|
+
```ruby
|
1120
|
+
matcher = Cuprum::Matcher.new do
|
1121
|
+
match(:success) { |result| capitalize(result.value) }
|
1122
|
+
end
|
1123
|
+
matcher
|
1124
|
+
.with_context(inflector)
|
1125
|
+
.call(Cuprum::Result.new(value: 'greetings starfighter'))
|
1126
|
+
#=> 'Greetings Starfighter'
|
1127
|
+
```
|
1128
|
+
|
1129
|
+
### Matcher Lists
|
1130
|
+
|
1131
|
+
Matcher lists handle matching a result against an ordered group of matchers.
|
1132
|
+
|
1133
|
+
When given a result, a matcher list will check for the most specific matching clause in each of the matchers. A clause matching both the value and error will match first, followed by a clause matching only the result value or error, and finally a clause matching only the result status will match.
|
1134
|
+
|
1135
|
+
If none of the matchers have a clause that matches the result, a `Cuprum::Matching::NoMatchError` will be raised.
|
1136
|
+
|
1137
|
+
```ruby
|
1138
|
+
generic_matcher = Cuprum::Matcher.new do
|
1139
|
+
match(:failure) { 'generic failure' }
|
1140
|
+
#
|
1141
|
+
match(:failure, error: CustomError) { 'custom failure' }
|
1142
|
+
end
|
1143
|
+
specific_matcher = Cuprum::Matcher.new do
|
1144
|
+
match(:failure, error: Cuprum::Error) { 'specific failure' }
|
1145
|
+
end
|
1146
|
+
matcher_list = Cuprum::MatcherList.new(
|
1147
|
+
[
|
1148
|
+
specific_matcher,
|
1149
|
+
generic_matcher
|
1150
|
+
]
|
1151
|
+
)
|
1152
|
+
|
1153
|
+
generic_matcher = Cuprum::Matcher.new do
|
1154
|
+
match(:failure) { 'generic failure' }
|
1155
|
+
|
1156
|
+
match(:failure, error: CustomError) { 'custom failure' }
|
1157
|
+
end
|
1158
|
+
specific_matcher = Cuprum::Matcher.new do
|
1159
|
+
match(:failure, error: Cuprum::Error) { 'specific failure' }
|
1160
|
+
end
|
1161
|
+
matcher_list = Cuprum::MatcherList.new(
|
1162
|
+
[
|
1163
|
+
specific_matcher,
|
1164
|
+
generic_matcher
|
1165
|
+
]
|
1166
|
+
)
|
1167
|
+
|
1168
|
+
# A failure without an error does not match the first matcher, so the
|
1169
|
+
# matcher list continues on to the next matcher in the list.
|
1170
|
+
result = Cuprum::Result.new(status: :failure)
|
1171
|
+
matcher_list.call(result)
|
1172
|
+
#=> 'generic failure'
|
1173
|
+
|
1174
|
+
# A failure with an error matches the first matcher.
|
1175
|
+
error = Cuprum::Error.new(message: 'Something went wrong.')
|
1176
|
+
result = Cuprum::Result.new(error: error)
|
1177
|
+
matcher_list.call(result)
|
1178
|
+
#=> 'specific failure'
|
1179
|
+
|
1180
|
+
# A failure with an error subclass still matches the first matcher, even
|
1181
|
+
# though the second matcher has a more exact match.
|
1182
|
+
error = CustomError.new(message: 'The magic smoke is escaping.')
|
1183
|
+
result = Cuprum::Result.new(error: error)
|
1184
|
+
matcher_list.call(result)
|
1185
|
+
#=> 'specific failure'
|
1186
|
+
```
|
1187
|
+
|
1188
|
+
One use case for matcher lists would be in defining hierarchies of classes or objects that have matching functionality. For example, a generic controller class might define default success and failure behavior, an included mixin might provide handling for a particular scope of errors, and a specific controller might override the default behavior for a given action. Using a matcher list allows each class or module to define its own behavior as independent matchers, which the matcher list then composes together.
|
1189
|
+
|
1190
|
+
## Command Factories
|
787
1191
|
|
788
1192
|
Commands are powerful and flexible objects, but they do have a few disadvantages compared to traditional service objects which allow the developer to group together related functionality and shared implementation details. To bridge this gap, Cuprum implements the CommandFactory class. Command factories provide a DSL to quickly group together related commands and create context-specific command classes or instances.
|
789
1193
|
|
@@ -836,7 +1240,7 @@ book.author #=> 'Ursula K. Le Guin'
|
|
836
1240
|
book.publisher #=> nil
|
837
1241
|
```
|
838
1242
|
|
839
|
-
|
1243
|
+
### The ::command Method And A Command Class
|
840
1244
|
|
841
1245
|
The first way to define a command for a factory is by calling the `::command` method and passing it the name of the command and a command class:
|
842
1246
|
|
@@ -848,7 +1252,7 @@ end
|
|
848
1252
|
|
849
1253
|
This makes the command class available on a factory instance as `::Build`, and generates the `#build` method which returns an instance of `BuildBookCommand`.
|
850
1254
|
|
851
|
-
|
1255
|
+
### The ::command Method And A Block
|
852
1256
|
|
853
1257
|
By calling the `::command` method with a block, you can define a command with additional control over how the generated command. The block must return an instance of a subclass of Cuprum::Command.
|
854
1258
|
|
@@ -957,7 +1361,7 @@ ary = result.value #=> an array with the selected books
|
|
957
1361
|
ary.count #=> 1
|
958
1362
|
```
|
959
1363
|
|
960
|
-
|
1364
|
+
### The ::command_class Method
|
961
1365
|
|
962
1366
|
The final way to define a command for a factory is calling the `::command_class` method with the command name and a block. The block must return a subclass (not an instance) of Cuprum::Command. This offers a balance between flexibility and power.
|
963
1367
|
|
@@ -1081,16 +1485,14 @@ books.count #=> 4
|
|
1081
1485
|
books.include?(book) #=> true
|
1082
1486
|
```
|
1083
1487
|
|
1084
|
-
|
1488
|
+
## Built In Commands
|
1085
1489
|
|
1086
1490
|
Cuprum includes a small number of predefined commands and their equivalent operations.
|
1087
1491
|
|
1088
|
-
|
1492
|
+
### IdentityCommand
|
1089
1493
|
|
1090
1494
|
require 'cuprum/built_in/identity_command'
|
1091
1495
|
|
1092
|
-
[Class Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum%2FBuiltIn%2FIdentityCommand)
|
1093
|
-
|
1094
1496
|
A pregenerated command that returns the value or result with which it was called.
|
1095
1497
|
|
1096
1498
|
```ruby
|
@@ -1100,12 +1502,10 @@ result.value #=> 'expected value'
|
|
1100
1502
|
result.success? #=> true
|
1101
1503
|
```
|
1102
1504
|
|
1103
|
-
|
1505
|
+
### IdentityOperation
|
1104
1506
|
|
1105
1507
|
require 'cuprum/built_in/identity_operation'
|
1106
1508
|
|
1107
|
-
[Class Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum%2FBuiltIn%2FIdentityOperation)
|
1108
|
-
|
1109
1509
|
A pregenerated operation that sets its result to the value or result with which it was called.
|
1110
1510
|
|
1111
1511
|
```ruby
|
@@ -1114,12 +1514,10 @@ operation.value #=> 'expected value'
|
|
1114
1514
|
operation.success? #=> true
|
1115
1515
|
```
|
1116
1516
|
|
1117
|
-
|
1517
|
+
### NullCommand
|
1118
1518
|
|
1119
1519
|
require 'cuprum/built_in/null_command'
|
1120
1520
|
|
1121
|
-
[Class Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum%2FBuiltIn%2FNullCommand)
|
1122
|
-
|
1123
1521
|
A pregenerated command that does nothing when called. Accepts any arguments.
|
1124
1522
|
|
1125
1523
|
```ruby
|
@@ -1129,12 +1527,10 @@ result.value #=> nil
|
|
1129
1527
|
result.success? #=> true
|
1130
1528
|
```
|
1131
1529
|
|
1132
|
-
|
1530
|
+
### NullOperation
|
1133
1531
|
|
1134
1532
|
require 'cuprum/built_in/null_operation'
|
1135
1533
|
|
1136
|
-
[Class Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum%2FBuiltIn%2FNullOperation)
|
1137
|
-
|
1138
1534
|
A pregenerated operation that does nothing when called. Accepts any arguments.
|
1139
1535
|
|
1140
1536
|
```ruby
|
@@ -1142,7 +1538,3 @@ operation = Cuprum::BuiltIn::NullOperation.new.call
|
|
1142
1538
|
operation.value #=> nil
|
1143
1539
|
operation.success? #=> true
|
1144
1540
|
```
|
1145
|
-
|
1146
|
-
## Reference
|
1147
|
-
|
1148
|
-
Method and class documentation is available courtesy of [RubyDoc](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master).
|