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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 457807ef9d8b39d6980467ad264972081f01b9ec
4
- data.tar.gz: 92fedf44dfd331ef25e1429c8f35c2af7ede6180
3
+ metadata.gz: 5a329ab1299b862bc041a64a19da75c47101f902
4
+ data.tar.gz: 570f6b167f4b76570166a6293f81682b10197b59
5
5
  SHA512:
6
- metadata.gz: ef543f82ad31ce828f01d4bfea7805da392ea524f4477af24ca77cb80b77ad623d9439af35ef2f8f5ef6d25b119e03dec104b73db966e287f2ebfc8141ab70de
7
- data.tar.gz: 73c642f2383e11273184ec3145ab61808cd80646bdf1687ca9abb94a439d3f9bfbc12fceb4806845c49ac7b91eda570997a2e2d669a2c566da5dbca88a9e4df4
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
- ## Errors
3
+ ## Function
4
4
 
5
- - Dedicated errors object.
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
- ## Function
12
+ ## Operation
8
13
 
9
- - Chaining with #then, #else, etc.
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
- ## Features
26
+ ## Functions
21
27
 
22
- ### Functions
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
- #### Defining With a Block
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
- #### Defining With a Subclass
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
- #### Success, Failure, and Errors
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
- ### Operations
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
- In addition, an operation defines the following methods:
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
- ### Results
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 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. It defines the following methods:
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)
@@ -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, *keywords, &block)
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] unless a block was passed to the
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
- Cuprum::Result.new.tap do |result|
104
- @errors = result.errors
146
+ call_chained_functions do
147
+ Cuprum::Result.new.tap do |result|
148
+ @errors = result.errors
105
149
 
106
- result.value = process(*args, &block)
150
+ result.value = process(*args, &block)
107
151
 
108
- @errors = nil
109
- end # tap
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
- def errors
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.
@@ -8,7 +8,7 @@ module Cuprum
8
8
  # Major version.
9
9
  MAJOR = 0
10
10
  # Minor version.
11
- MINOR = 2
11
+ MINOR = 3
12
12
  # Patch version.
13
13
  PATCH = 0
14
14
  # Prerelease version.
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.2.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-18 00:00:00.000000000 Z
11
+ date: 2017-08-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec