cuprum 0.2.0 → 0.3.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 +12 -0
- data/DEVELOPMENT.md +26 -4
- data/README.md +198 -12
- data/lib/cuprum/function.rb +186 -10
- data/lib/cuprum/result.rb +9 -5
- data/lib/cuprum/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5a329ab1299b862bc041a64a19da75c47101f902
|
4
|
+
data.tar.gz: 570f6b167f4b76570166a6293f81682b10197b59
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3bc9f429efab47bf5fcee2f843481afeae3e0709bafefd0810b7b4cb91038826ae0af00218b26f08d033f76fa7d90445ddc6f410a9a725741c3f43d0b8fcec02
|
7
|
+
data.tar.gz: 5aab1f5a727267c4b6cd95c037a987b36fdbc7c5c94fc2d03add97c996fcb1eabcbc36247bf2dddb89ae09ac252e4543ebd34d4f03cfcd984ab0d29726e30daf
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,17 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## 0.3.0
|
4
|
+
|
5
|
+
The "Nothing To Lose But Your Chains" Update.
|
6
|
+
|
7
|
+
## Functions
|
8
|
+
|
9
|
+
Now support chaining via the `#chain`, `#then`, and `#else` methods.
|
10
|
+
|
11
|
+
## Results
|
12
|
+
|
13
|
+
Can pass a value and/or an errors object to the constructor.
|
14
|
+
|
3
15
|
## 0.2.0
|
4
16
|
|
5
17
|
The "Fully Armed and Operational" Update.
|
data/DEVELOPMENT.md
CHANGED
@@ -1,13 +1,35 @@
|
|
1
1
|
# Development
|
2
2
|
|
3
|
-
##
|
3
|
+
## Function
|
4
4
|
|
5
|
-
-
|
5
|
+
- Handle when block or #process returns a Result instance.
|
6
|
+
- #build_errors method
|
7
|
+
- Predefined functions/operations:
|
8
|
+
- NullFunction
|
9
|
+
- IdentityFunction
|
10
|
+
- MapFunction
|
6
11
|
|
7
|
-
##
|
12
|
+
## Operation
|
8
13
|
|
9
|
-
-
|
14
|
+
- #reset! should return self
|
10
15
|
|
11
16
|
## Result
|
12
17
|
|
18
|
+
- Abort chaining with #halt!, #halted? methods.
|
13
19
|
- Force success or failure status with #success!, #failure! methods.
|
20
|
+
|
21
|
+
## Documentation
|
22
|
+
|
23
|
+
Chaining Case Study: |
|
24
|
+
|
25
|
+
CMS application - creating a new post.
|
26
|
+
Directory has many Posts
|
27
|
+
Post has a Content
|
28
|
+
Content has many ContentVersions
|
29
|
+
Post has many Tags
|
30
|
+
|
31
|
+
Find Directory
|
32
|
+
Create Post
|
33
|
+
Create Content
|
34
|
+
Create ContentVersion
|
35
|
+
Tags.each { FindOrCreate Tag }
|
data/README.md
CHANGED
@@ -7,6 +7,12 @@ citizen of your application.
|
|
7
7
|
|
8
8
|
Cuprum is tested against Ruby 2.4.
|
9
9
|
|
10
|
+
## Documentation
|
11
|
+
|
12
|
+
Method and class documentation is available courtesy of [RubyDoc](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master).
|
13
|
+
|
14
|
+
Documentation is generated using [YARD](https://yardoc.org/), and can be generated locally using the `yard` gem.
|
15
|
+
|
10
16
|
## Contribute
|
11
17
|
|
12
18
|
### GitHub
|
@@ -17,32 +23,100 @@ The canonical repository for this gem is located at https://github.com/sleepingk
|
|
17
23
|
|
18
24
|
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 (see above, and feel encouraged to submit bug reports or merge requests there) or via email at merlin@sleepingkingstudios.com. I look forward to hearing from you!
|
19
25
|
|
20
|
-
##
|
26
|
+
## Functions
|
21
27
|
|
22
|
-
|
28
|
+
[Class Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum%2FFunction)
|
23
29
|
|
24
30
|
Functions are the core feature of Cuprum. In a nutshell, each Cuprum::Function is a functional object that encapsulates a business logic operation. A Function 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.
|
25
31
|
|
26
32
|
Each Function implements a `#call` method that wraps your defined business logic and returns an instance of Cuprum::Result. The result wraps the returned data (with the `#value` method), any `#errors` generated when running the Function, and the overall status with the `#success?` and `#failure` methods. For more details about Cuprum::Result, see below.
|
27
33
|
|
28
|
-
|
34
|
+
### Methods
|
35
|
+
|
36
|
+
A Cuprum::Function defines the following methods:
|
37
|
+
|
38
|
+
#### #initialize
|
39
|
+
|
40
|
+
initialize { |*arguments, **keywords, &block| ... } #=> Cuprum::Function
|
41
|
+
|
42
|
+
Returns a new instance of Cuprum::Function. 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.
|
43
|
+
|
44
|
+
[Method Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum/Function#initialize-instance_method)
|
45
|
+
|
46
|
+
#### #call
|
47
|
+
|
48
|
+
call(*arguments, **keywords) { ... } #=> Cuprum::Result
|
49
|
+
|
50
|
+
Executes the logic encoded in the constructor block, or the #process method if no block was passed to the constructor.
|
51
|
+
|
52
|
+
[Method Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum/Function#call-instance_method)
|
53
|
+
|
54
|
+
#### #chain
|
55
|
+
|
56
|
+
Registers a function or block to run after the current function, or after the last chained function if the current function already has one or more chained function(s). This creates and modifies a copy of the current function. See Chaining Functions, below.
|
57
|
+
|
58
|
+
chain(function, on: nil) #=> Cuprum::Function
|
59
|
+
|
60
|
+
The function will be passed the `#value` of the previous function result as its parameter, and the result of the chained function will be returned (or passed to the next chained function, if any).
|
61
|
+
|
62
|
+
chain(on: nil) { |result| ... } #=> Cuprum::Function
|
63
|
+
|
64
|
+
The block will be passed the #result of the previous function as its parameter. If your use case depends on the status of the previous function or on any errors generated, use the block form of #chain.
|
65
|
+
|
66
|
+
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 function, if any). If the block returns any other value (including nil), the #result of the previous function will be returned or passed to the next function.
|
67
|
+
|
68
|
+
[Method Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum/Function#chain-instance_method)
|
69
|
+
|
70
|
+
#### `#then`
|
71
|
+
|
72
|
+
Shorthand for `function.chain(:on => :success)`. Registers a function or block to run after the current function. The chained function will only run if the previous function was successfully run.
|
73
|
+
|
74
|
+
then(function) #=> Cuprum::Function
|
75
|
+
|
76
|
+
The function will be passed the `#value` of the previous function result as its parameter, and the result of the chained function will be returned (or passed to the next chained function, if any).
|
77
|
+
|
78
|
+
then() { |result| ... } #=> Cuprum::Function
|
79
|
+
|
80
|
+
The block will be passed the #result of the previous function as its parameter. If your use case depends on the status of the previous function or on any errors generated, use the block form of #chain.
|
81
|
+
|
82
|
+
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 function, if any). If the block returns any other value (including nil), the #result of the previous function will be returned or passed to the next function.
|
83
|
+
|
84
|
+
[Method Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum/Function#then-instance_method)
|
85
|
+
|
86
|
+
#### `#else`
|
87
|
+
|
88
|
+
Shorthand for `function.chain(:on => :failure)`. Registers a function or block to run after the current function. The chained function will only run if the previous function was unsuccessfully run.
|
89
|
+
|
90
|
+
else(function) #=> Cuprum::Function
|
91
|
+
|
92
|
+
The function will be passed the `#value` of the previous function result as its parameter, and the result of the chained function will be returned (or passed to the next chained function, if any).
|
93
|
+
|
94
|
+
else() { |result| ... } #=> Cuprum::Function
|
95
|
+
|
96
|
+
The block will be passed the #result of the previous function as its parameter. If your use case depends on the status of the previous function or on any errors generated, use the block form of #chain.
|
97
|
+
|
98
|
+
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 function, if any). If the block returns any other value (including nil), the #result of the previous function will be returned or passed to the next function.
|
99
|
+
|
100
|
+
[Method Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum/Function#else-instance_method)
|
101
|
+
|
102
|
+
### Defining With a Block
|
29
103
|
|
30
104
|
Functions can be used right out of the box by passing a block to the Cuprum::Function constructor, as follows:
|
31
105
|
|
32
106
|
# A Function with a block
|
33
|
-
double_function = Function.new { |int| 2 * int }
|
107
|
+
double_function = Cuprum::Function.new { |int| 2 * int }
|
34
108
|
result = double_function.call(5)
|
35
109
|
|
36
110
|
result.value #=> 10
|
37
111
|
|
38
112
|
The constructor block will be called each time `Function#call` is executed, and will be passed all of the arguments given to `#call`. You can even define a block parameter, which will be passed along to the constructor block when `#call` is called with a block argument.
|
39
113
|
|
40
|
-
|
114
|
+
### Defining With a Subclass
|
41
115
|
|
42
116
|
Larger applications will want to create Function subclasses that encapsulate their business logic in a reusable, composable fashion. The implementation for each subclass is handled by the `#process` private method. If a subclass or its ancestors does not implement `#process`, a `Cuprum::Function::NotImplementedError` will be raised.
|
43
117
|
|
44
118
|
# A Function subclass
|
45
|
-
class MultiplyFunction
|
119
|
+
class MultiplyFunction < Cuprum::Function
|
46
120
|
def initialize multiplier
|
47
121
|
@multiplier = multiplier
|
48
122
|
end # constructor
|
@@ -61,12 +135,12 @@ Larger applications will want to create Function subclasses that encapsulate the
|
|
61
135
|
|
62
136
|
As with the block syntax, a Function whose implementation is defined via the `#process` method will call `#process` each time that `#call` is executed, and will pass all arguments from `#call` on to `#process`. The value returned by `#process` will be assigned to the result `#value`.
|
63
137
|
|
64
|
-
|
138
|
+
### Success, Failure, and Errors
|
65
139
|
|
66
140
|
Whether defined with a block or in the `#process` method, the Function implementation can access an `#errors` object while in the `#call` method. Any errors added to the errors object will be exposed by the `#errors` method on the result object.
|
67
141
|
|
68
142
|
# A Function with errors
|
69
|
-
class DivideFunction
|
143
|
+
class DivideFunction < Cuprum::Function
|
70
144
|
def initialize divisor
|
71
145
|
@divisor = divisor
|
72
146
|
end # constructor
|
@@ -104,7 +178,77 @@ In addition, the result object defines `#success?` and `#failure?` predicates. I
|
|
104
178
|
result.failure? #=> true
|
105
179
|
result.value #=> nil
|
106
180
|
|
107
|
-
###
|
181
|
+
### Chaining Functions
|
182
|
+
|
183
|
+
Because Cuprum::Function instances are proper objects, they can be composed like any other object. Cuprum::Function also defines methods for chaining functions together. When a chain of functions is called, each function in the chain is called in sequence and passed the value of the previous function. The result of the last function in the chain is returned from the chained call.
|
184
|
+
|
185
|
+
class AddFunction < Cuprum::Function
|
186
|
+
def initialize addend
|
187
|
+
@addend = addend
|
188
|
+
end # constructor
|
189
|
+
|
190
|
+
private
|
191
|
+
|
192
|
+
def process int
|
193
|
+
int + @addend
|
194
|
+
end # method process
|
195
|
+
end # class
|
196
|
+
|
197
|
+
double_and_add_one = MultiplyFunction.new(2).chain(AddFunction.new(1))
|
198
|
+
result = double_and_add_one(5)
|
199
|
+
|
200
|
+
result.value #=> 5
|
201
|
+
|
202
|
+
For finer control over the returned result, `#chain` can instead be called with a block that yields the most recent result. If the block returns a Cuprum::Result, that result is returned or passed to the next function.
|
203
|
+
|
204
|
+
MultiplyFunction.new(3).
|
205
|
+
chain { |result| Cuprum::Result.new(result + 1) }.
|
206
|
+
call(3)
|
207
|
+
#=> Returns a Cuprum::Result with a value of 10.
|
208
|
+
|
209
|
+
Otherwise, the block is still called but the previous result is returned or passed to the next function in the chain.
|
210
|
+
|
211
|
+
AddFunction.new(2).
|
212
|
+
chain { |result| puts "There are #{result.value} lights!" }.
|
213
|
+
call(2)
|
214
|
+
#=> Writes "There are 4 lights!" to STDOUT.
|
215
|
+
#=> Returns a Cuprum::Result with a value of 4.
|
216
|
+
|
217
|
+
#### Conditional Chaining
|
218
|
+
|
219
|
+
The `#chain` method can be passed an optional `:on` keyword, with values of `:success` and `:failure` accepted. If `#chain` is called with `:on => :success`, then the chained function or block will **only** be called if the previous result `#success?` returns true. Conversely, if `#chain` is called with `:on => :failure`, then the chained function will only be called if the previous result `#failure?` returns true.
|
220
|
+
|
221
|
+
In either case, execution will then pass to the next function in the chain, which may itself be called or not if it was conditionally chained. Calling a conditional function chain will return the result of the last called function.
|
222
|
+
|
223
|
+
The methods `#then` and `#else` serve as shortcuts for `#chain` with `:on => :success` and `:on => :failure`, respectively.
|
224
|
+
|
225
|
+
class EvenFunction < Cuprum::Function
|
226
|
+
private
|
227
|
+
|
228
|
+
def process int
|
229
|
+
errors << 'errors.messages.not_even' unless int.even?
|
230
|
+
|
231
|
+
int
|
232
|
+
end # method process
|
233
|
+
end # class
|
234
|
+
|
235
|
+
# The next step in a Collatz sequence is determined as follows:
|
236
|
+
# - If the number is even, divide it by 2.
|
237
|
+
# - If the number is odd, multiply it by 3 and add 1.
|
238
|
+
collatz_function =
|
239
|
+
EvenFunction.new.
|
240
|
+
then(DivideFunction.new(2)).
|
241
|
+
else(MultiplyFunction.new(3).chain(AddFunction.new(1)))
|
242
|
+
|
243
|
+
result = collatz_function.new(5)
|
244
|
+
result.value #=> 16
|
245
|
+
|
246
|
+
result = collatz_function.new(16)
|
247
|
+
result.value #=> 8
|
248
|
+
|
249
|
+
## Operations
|
250
|
+
|
251
|
+
[Class Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum%2FOperation)
|
108
252
|
|
109
253
|
An Operation is like a Function, but with an additional trick of tracking its own most recent execution result. This allows us to simplify some conditional logic, especially boilerplate code used to interact with frameworks.
|
110
254
|
|
@@ -130,36 +274,78 @@ Like a Function, an Operation can be defined directly by passing an implementati
|
|
130
274
|
|
131
275
|
An operation inherits the `#call` method from Cuprum::Function (see above), and delegates the `#value`, `#errors`, `#success?`, and `#failure` methods to the most recent result (see below). If the operation has not been called, the operation will return default values.
|
132
276
|
|
133
|
-
|
277
|
+
### Methods
|
278
|
+
|
279
|
+
A Cuprum::Operation inherits the methods from Cuprum::Function (see above), and defines the following additional methods:
|
134
280
|
|
135
281
|
#### `#result`
|
136
282
|
|
283
|
+
result() #=> Cuprum::Result
|
284
|
+
|
137
285
|
The most recent result, from the previous time `#call` was executed for the operation.
|
138
286
|
|
287
|
+
[Method Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum/Operation#result-instance_method)
|
288
|
+
|
139
289
|
#### `#called?`
|
140
290
|
|
291
|
+
called?() #=> true, false
|
292
|
+
|
141
293
|
True if the operation has been called and there is a result available by calling `#result` or one of the delegated methods, otherwise false.
|
142
294
|
|
295
|
+
[Method Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum/Operation#called%3F-instance_method)
|
296
|
+
|
143
297
|
#### `#reset!`
|
144
298
|
|
299
|
+
reset!()
|
300
|
+
|
145
301
|
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.
|
146
302
|
|
147
|
-
|
303
|
+
[Method Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum/Operation#reset!-instance_method)
|
304
|
+
|
305
|
+
## Results
|
306
|
+
|
307
|
+
[Class Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum%2FResult)
|
308
|
+
|
309
|
+
A Cuprum::Result is a data object that encapsulates the result of calling a Cuprum function - the returned value, the success or failure status, and any errors generated by the function.
|
148
310
|
|
149
|
-
A
|
311
|
+
value = 'A result value'.freeze
|
312
|
+
result = Cuprum::Result.new(value)
|
313
|
+
|
314
|
+
result.value
|
315
|
+
#=> 'A result value'
|
316
|
+
|
317
|
+
### Methods
|
318
|
+
|
319
|
+
A Cuprum::Result defines the following methods:
|
150
320
|
|
151
321
|
#### `#value`
|
152
322
|
|
323
|
+
value() #=> Object
|
324
|
+
|
153
325
|
The value returned by the function. For example, for an increment function that added 1 to a given integer, the `#value` of the result object would be the incremented integer.
|
154
326
|
|
327
|
+
[Method Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum/Result#value-instance_method)
|
328
|
+
|
155
329
|
#### `#errors`
|
156
330
|
|
331
|
+
errors() #=> Array
|
332
|
+
|
157
333
|
The errors generated by the function, or an empty array if no errors were generated.
|
158
334
|
|
335
|
+
[Method Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum/Result#errors-instance_method)
|
336
|
+
|
159
337
|
#### `#success?`
|
160
338
|
|
339
|
+
success?() #=> true, false
|
340
|
+
|
161
341
|
True if the function did not generate any errors, otherwise false.
|
162
342
|
|
343
|
+
[Method Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum/Result#success%3F-instance_method)
|
344
|
+
|
163
345
|
#### `#failure?`
|
164
346
|
|
347
|
+
failure?() #=> true, false
|
348
|
+
|
165
349
|
True if the function generated one or more errors, otherwise false.
|
350
|
+
|
351
|
+
[Method Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum/Result#failure%3F-instance_method)
|
data/lib/cuprum/function.rb
CHANGED
@@ -8,13 +8,13 @@ module Cuprum
|
|
8
8
|
# by defining a subclass of Function and implementing the #process method.
|
9
9
|
#
|
10
10
|
# @example A Function with a block
|
11
|
-
# double_function = Function.new { |int| 2 * int }
|
11
|
+
# double_function = Cuprum::Function.new { |int| 2 * int }
|
12
12
|
# result = double_function.call(5)
|
13
13
|
#
|
14
14
|
# result.value #=> 10
|
15
15
|
#
|
16
16
|
# @example A Function subclass
|
17
|
-
# class MultiplyFunction
|
17
|
+
# class MultiplyFunction < Cuprum::Function
|
18
18
|
# def initialize multiplier
|
19
19
|
# @multiplier = multiplier
|
20
20
|
# end # constructor
|
@@ -32,7 +32,7 @@ module Cuprum
|
|
32
32
|
# result.value #=> 15
|
33
33
|
#
|
34
34
|
# @example A Function with errors
|
35
|
-
# class DivideFunction
|
35
|
+
# class DivideFunction < Cuprum::Function
|
36
36
|
# def initialize divisor
|
37
37
|
# @divisor = divisor
|
38
38
|
# end # constructor
|
@@ -61,6 +61,49 @@ module Cuprum
|
|
61
61
|
#
|
62
62
|
# result.errors #=> ['errors.messages.divide_by_zero']
|
63
63
|
# result.value #=> nil
|
64
|
+
#
|
65
|
+
# @example Function Chaining
|
66
|
+
# class AddFunction < Cuprum::Function
|
67
|
+
# def initialize addend
|
68
|
+
# @addend = addend
|
69
|
+
# end # constructor
|
70
|
+
#
|
71
|
+
# private
|
72
|
+
#
|
73
|
+
# def process int
|
74
|
+
# int + @addend
|
75
|
+
# end # method process
|
76
|
+
# end # class
|
77
|
+
#
|
78
|
+
# double_and_add_one = MultiplyFunction.new(2).chain(AddFunction.new(1))
|
79
|
+
# result = double_and_add_one(5)
|
80
|
+
#
|
81
|
+
# result.value #=> 5
|
82
|
+
#
|
83
|
+
# @example Conditional Chaining With #then And #else
|
84
|
+
# class EvenFunction < Cuprum::Function
|
85
|
+
# private
|
86
|
+
#
|
87
|
+
# def process int
|
88
|
+
# errors << 'errors.messages.not_even' unless int.even?
|
89
|
+
#
|
90
|
+
# int
|
91
|
+
# end # method process
|
92
|
+
# end # class
|
93
|
+
#
|
94
|
+
# # The next step in a Collatz sequence is determined as follows:
|
95
|
+
# # - If the number is even, divide it by 2.
|
96
|
+
# # - If the number is odd, multiply it by 3 and add 1.
|
97
|
+
# collatz_function =
|
98
|
+
# EvenFunction.new.
|
99
|
+
# then(DivideFunction.new(2)).
|
100
|
+
# else(MultiplyFunction.new(3).chain(AddFunction.new(1)))
|
101
|
+
#
|
102
|
+
# result = collatz_function.new(5)
|
103
|
+
# result.value #=> 16
|
104
|
+
#
|
105
|
+
# result = collatz_function.new(16)
|
106
|
+
# result.value #=> 8
|
64
107
|
class Function
|
65
108
|
# Error class for calling a Function that was not given a definition block
|
66
109
|
# or have a #process method defined.
|
@@ -83,7 +126,7 @@ module Cuprum
|
|
83
126
|
define_singleton_method :process, &implementation if implementation
|
84
127
|
end # method initialize
|
85
128
|
|
86
|
-
# @overload call(*arguments,
|
129
|
+
# @overload call(*arguments, **keywords, &block)
|
87
130
|
# Executes the logic encoded in the constructor block, or the #process
|
88
131
|
# method if no block was passed to the constructor.
|
89
132
|
#
|
@@ -96,25 +139,158 @@ module Cuprum
|
|
96
139
|
# @yield If a block argument is given, it will be passed to the
|
97
140
|
# implementation.
|
98
141
|
#
|
99
|
-
# @raise [NotImplementedError]
|
142
|
+
# @raise [NotImplementedError] Unless a block was passed to the
|
100
143
|
# constructor or the #process method was overriden by a Function
|
101
144
|
# subclass.
|
102
145
|
def call *args, &block
|
103
|
-
|
104
|
-
|
146
|
+
call_chained_functions do
|
147
|
+
Cuprum::Result.new.tap do |result|
|
148
|
+
@errors = result.errors
|
105
149
|
|
106
|
-
|
150
|
+
result.value = process(*args, &block)
|
107
151
|
|
108
|
-
|
109
|
-
|
152
|
+
@errors = nil
|
153
|
+
end # tap
|
154
|
+
end # call_chained_functions
|
110
155
|
end # method call
|
111
156
|
|
157
|
+
# Registers a function or block to run after the current function, or after
|
158
|
+
# the last chained function if the current function already has one or more
|
159
|
+
# chained function(s). This creates and modifies a copy of the current
|
160
|
+
# function.
|
161
|
+
#
|
162
|
+
# @param on [Symbol] Sets a condition on when the chained function can run,
|
163
|
+
# based on the status of the previous function. Valid values are :success
|
164
|
+
# and :failure, and will constrain the function to run only if the
|
165
|
+
# previous function succeeded or failed, respectively. If no value is
|
166
|
+
# given, the function will run whether the previous function was a success
|
167
|
+
# or a failure.
|
168
|
+
#
|
169
|
+
# @overload chain(function, on: nil)
|
170
|
+
# The function will be passed the #value of the previous function result
|
171
|
+
# as its parameter, and the result of the chained function will be
|
172
|
+
# returned (or passed to the next chained function, if any).
|
173
|
+
#
|
174
|
+
# @param function [Cuprum::Function] The function to call after the
|
175
|
+
# current or last chained function.
|
176
|
+
#
|
177
|
+
# @overload chain(on: :nil, &block)
|
178
|
+
# The block will be passed the #result of the previous function as its
|
179
|
+
# parameter. If your use case depends on the status of the previous
|
180
|
+
# function or on any errors generated, use the block form of #chain.
|
181
|
+
#
|
182
|
+
# If the block returns a Cuprum::Result (or an object responding to #value
|
183
|
+
# and #success?), the block result will be returned (or passed to the next
|
184
|
+
# chained function, if any). If the block returns any other value
|
185
|
+
# (including nil), the #result of the previous function will be returned
|
186
|
+
# or passed to the next function.
|
187
|
+
#
|
188
|
+
# @yieldparam result [Cuprum::Result] The #result of the previous
|
189
|
+
# function.
|
190
|
+
#
|
191
|
+
# @return [Cuprum::Function] The chained function.
|
192
|
+
def chain function = nil, on: nil, &block
|
193
|
+
proc = convert_function_or_proc_to_proc(block || function)
|
194
|
+
|
195
|
+
chain_function(proc, :on => on)
|
196
|
+
end # method chain
|
197
|
+
|
198
|
+
# Shorthand for function.chain(:on => :failure). Registers a function or
|
199
|
+
# block to run after the current function. The chained function will only
|
200
|
+
# run if the previous function was unsuccessfully run.
|
201
|
+
#
|
202
|
+
# @overload else(function)
|
203
|
+
#
|
204
|
+
# @param function [Cuprum::Function] The function to call after the
|
205
|
+
# current or last chained function.
|
206
|
+
#
|
207
|
+
# @overload else(&block)
|
208
|
+
#
|
209
|
+
# @yieldparam result [Cuprum::Result] The #result of the previous
|
210
|
+
# function.
|
211
|
+
#
|
212
|
+
# @return [Cuprum::Function] The chained function.
|
213
|
+
#
|
214
|
+
# @see #chain
|
215
|
+
def else function = nil, &block
|
216
|
+
proc = convert_function_or_proc_to_proc(block || function)
|
217
|
+
|
218
|
+
chain_function(proc, :on => :failure)
|
219
|
+
end # method else
|
220
|
+
|
221
|
+
# Shorthand for function.chain(:on => :success). Registers a function or
|
222
|
+
# block to run after the current function. The chained function will only
|
223
|
+
# run if the previous function was successfully run.
|
224
|
+
#
|
225
|
+
# @overload then(function)
|
226
|
+
#
|
227
|
+
# @param function [Cuprum::Function] The function to call after the
|
228
|
+
# current or last chained function.
|
229
|
+
#
|
230
|
+
# @overload then(&block)
|
231
|
+
#
|
232
|
+
# @yieldparam result [Cuprum::Result] The #result of the previous
|
233
|
+
# function.
|
234
|
+
#
|
235
|
+
# @return [Cuprum::Function] The chained function.
|
236
|
+
#
|
237
|
+
# @see #chain
|
238
|
+
def then function = nil, &block
|
239
|
+
proc = convert_function_or_proc_to_proc(block || function)
|
240
|
+
|
241
|
+
chain_function(proc, :on => :success)
|
242
|
+
end # method then
|
243
|
+
|
244
|
+
protected
|
245
|
+
|
246
|
+
def call_chained_functions
|
247
|
+
chained_functions.reduce(yield) do |result, hsh|
|
248
|
+
next result if skip_chained_function?(result, :on => hsh[:on])
|
249
|
+
|
250
|
+
value = hsh.fetch(:proc).call(result)
|
251
|
+
|
252
|
+
value_is_result?(value) ? value : result
|
253
|
+
end # reduce
|
254
|
+
end # method call_chained_functions
|
255
|
+
|
256
|
+
def chain_function proc, on: nil
|
257
|
+
hsh = { :proc => proc }
|
258
|
+
hsh[:on] = on if on
|
259
|
+
|
260
|
+
clone.tap do |fn|
|
261
|
+
fn.chained_functions << hsh
|
262
|
+
end # tap
|
263
|
+
end # method chain_function
|
264
|
+
|
265
|
+
def chained_functions
|
266
|
+
@chained_functions ||= []
|
267
|
+
end # method chained_functions
|
268
|
+
|
112
269
|
private
|
113
270
|
|
114
271
|
attr_reader :errors
|
115
272
|
|
273
|
+
def convert_function_or_proc_to_proc function_or_proc
|
274
|
+
return function_or_proc if function_or_proc.is_a?(Proc)
|
275
|
+
|
276
|
+
->(result) { function_or_proc.call(result) }
|
277
|
+
end # method convert_function_or_proc_to_proc
|
278
|
+
|
116
279
|
def process *_args
|
117
280
|
raise NotImplementedError, nil, caller(1..-1)
|
118
281
|
end # method process
|
282
|
+
|
283
|
+
def skip_chained_function? last_result, on:
|
284
|
+
case on
|
285
|
+
when :success
|
286
|
+
!last_result.success?
|
287
|
+
when :failure
|
288
|
+
!last_result.failure?
|
289
|
+
end # case
|
290
|
+
end # method skip_chained_function?
|
291
|
+
|
292
|
+
def value_is_result? value
|
293
|
+
value.respond_to?(:value) && value.respond_to?(:success?)
|
294
|
+
end # method value
|
119
295
|
end # class
|
120
296
|
end # module
|
data/lib/cuprum/result.rb
CHANGED
@@ -4,16 +4,20 @@ module Cuprum
|
|
4
4
|
# Data object that encapsulates the result of calling a Cuprum function or
|
5
5
|
# operation.
|
6
6
|
class Result
|
7
|
+
# @param value [Object] The value returned by calling the function.
|
8
|
+
# @param errors [Array] The errors (if any) generated when the function was
|
9
|
+
# called.
|
10
|
+
def initialize value = nil, errors: []
|
11
|
+
@value = value
|
12
|
+
@errors = errors
|
13
|
+
end # constructor
|
14
|
+
|
7
15
|
# @return [Object] the value returned by calling the function.
|
8
16
|
attr_accessor :value
|
9
17
|
|
10
|
-
attr_writer :errors
|
11
|
-
|
12
18
|
# @return [Array] the errors (if any) generated when the function was
|
13
19
|
# called.
|
14
|
-
|
15
|
-
@errors ||= []
|
16
|
-
end # method errors
|
20
|
+
attr_accessor :errors
|
17
21
|
|
18
22
|
# @return [Boolean] false if the function did not generate any errors,
|
19
23
|
# otherwise true.
|
data/lib/cuprum/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cuprum
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rob "Merlin" Smith
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-08-
|
11
|
+
date: 2017-08-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rspec
|