cuprum 0.3.0 → 0.4.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: 5a329ab1299b862bc041a64a19da75c47101f902
4
- data.tar.gz: 570f6b167f4b76570166a6293f81682b10197b59
3
+ metadata.gz: '0797c743296d8ae77c0cebf8db95ed48bb7701ac'
4
+ data.tar.gz: 78eef973b33c1ae167ac74f7e04169142ef69e45
5
5
  SHA512:
6
- metadata.gz: 3bc9f429efab47bf5fcee2f843481afeae3e0709bafefd0810b7b4cb91038826ae0af00218b26f08d033f76fa7d90445ddc6f410a9a725741c3f43d0b8fcec02
7
- data.tar.gz: 5aab1f5a727267c4b6cd95c037a987b36fdbc7c5c94fc2d03add97c996fcb1eabcbc36247bf2dddb89ae09ac252e4543ebd34d4f03cfcd984ab0d29726e30daf
6
+ metadata.gz: 356d580bea16226d831453bcb98cfe32bc6d38b264b53c6935cb91adccfc9f627ed416269374c42dc513187d34add5aa5a3298cfc8b4ef897dd785a58436bfdb
7
+ data.tar.gz: 0e181b037184bdd6e0ea774cc1861cea9997ba6d01bee5cba4b92e99ce5348d9a1b66f536b634e91269cc9dfc4a4b0c68172d7be76f32f33ff663d65eef008a9
data/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.0
4
+
5
+ The "Halt And Catch Fire" Update.
6
+
7
+ ## Functions
8
+
9
+ Can now call `#success!` or `#failure!` in a function block or `#process` method to override the default, error-based status for the result. This allows for a passing result that still has errors, or a failing result that does not have explicit errors.
10
+
11
+ Can now call `#halt!` in a function block or `#process` method. If a function has been halted, then any subsequent chained functions will not be run unless they were chained with the `:on => :always` option.
12
+
13
+ Can now generate results with custom error objects by overriding the `#build_errors` method.
14
+
15
+ Fixed an inconsistency issue when a function block or `#process` method returned an instance of `Cuprum::Result`.
16
+
17
+ ## Operations
18
+
19
+ Calling `#call` on an operation now returns the operation instance.
20
+
21
+ ## Results
22
+
23
+ Can now call `#success!` or `#failure!` to override the default, error-based status.
24
+
25
+ Can now call `#halt!` and check the `#halted?` status. A halted result will prevent subsequent chained functions from being run.
26
+
3
27
  ## 0.3.0
4
28
 
5
29
  The "Nothing To Lose But Your Chains" Update.
data/DEVELOPMENT.md CHANGED
@@ -2,22 +2,17 @@
2
2
 
3
3
  ## Function
4
4
 
5
- - Handle when block or #process returns a Result instance.
6
5
  - #build_errors method
7
6
  - Predefined functions/operations:
8
7
  - NullFunction
9
8
  - IdentityFunction
10
9
  - MapFunction
10
+ - RetryFunction
11
11
 
12
12
  ## Operation
13
13
 
14
- - #reset! should return self
15
-
16
14
  ## Result
17
15
 
18
- - Abort chaining with #halt!, #halted? methods.
19
- - Force success or failure status with #success!, #failure! methods.
20
-
21
16
  ## Documentation
22
17
 
23
18
  Chaining Case Study: |
data/README.md CHANGED
@@ -99,6 +99,59 @@ If the block returns a Cuprum::Result (or an object responding to #value and #su
99
99
 
100
100
  [Method Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum/Function#else-instance_method)
101
101
 
102
+ #### `#build_errors`
103
+
104
+ *Private method*. Generates an empty errors object. When the function is called, the result will have its `#errors` property initialized to the value returned by `#build_errors`. By default, this is an array. If you want to use a custom errors object type, override this method in a subclass.
105
+
106
+ build_errors() #=> Array
107
+
108
+ [Method Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum/Function#build_errors-instance_method)
109
+
110
+ ### Implementation Hooks
111
+
112
+ These methods are only available while the Function is being called, and allow the implementation to update the errors of and override the results of the result object.
113
+
114
+ #### `#errors`
115
+
116
+ Only available while the Function is being called. Provides access to the errors object of the generated Cuprum::Result, which is by default an instance of Array.
117
+
118
+ errors() #=> Array
119
+
120
+ Inside of the Function block or the `#process` method, you can add errors to the result.
121
+
122
+ function =
123
+ Cuprum::Function.new do
124
+ errors << "I'm sorry, something went wrong."
125
+
126
+ nil
127
+ end # function
128
+
129
+ result = function.call
130
+ result.failure?
131
+ #=> true
132
+ result.errors
133
+ #=> ["I'm sorry, something went wrong."]
134
+
135
+ [Method Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum/Function#errors-instance_method)
136
+
137
+ #### `#success!`
138
+
139
+ Only available while the Function is being called. If called, marks the result object as passing, even if the result has errors.
140
+
141
+ success!() #=> NilClass
142
+
143
+ #### `#failure!`
144
+
145
+ Only available while the Function is being called. If called, marks the result object as failing, even if the result does not have errors.
146
+
147
+ failure!() #=> NilClass
148
+
149
+ #### `#halt!`
150
+
151
+ Only available while the Function is being called. If called, halts the function chain (see Chaining Functions, below). Subsequent chained functions will not be called unless they were chained with the `:on => :always` option.
152
+
153
+ halt!() #=> NilClass
154
+
102
155
  ### Defining With a Block
103
156
 
104
157
  Functions can be used right out of the box by passing a block to the Cuprum::Function constructor, as follows:
@@ -246,11 +299,35 @@ The methods `#then` and `#else` serve as shortcuts for `#chain` with `:on => :su
246
299
  result = collatz_function.new(16)
247
300
  result.value #=> 8
248
301
 
302
+ #### Halting A Function Chain
303
+
304
+ If the `#halt` method is called as part of a Function block or `#process` method, the function chain is halted. Any subsequent chained functions will not be called unless they were chained with the `:on => :always` option. This allows you to terminate a Function chain early without having to raise and rescue an exception.
305
+
306
+ panic_function =
307
+ Cuprum::Function.new do |value|
308
+ halt!
309
+
310
+ value
311
+ end # function
312
+
313
+ result =
314
+ double_function.
315
+ then(panic_function).
316
+ then(AddFunction.new(1)). #=> This is never executed.
317
+ chain(:on => :always) { |count| puts "There are #{count} lights!" }.
318
+ call(2)
319
+ #=> Writes "There are 4 lights!" to STDOUT.
320
+
321
+ result.value #= 4
322
+ result.halted? #=> true
323
+
249
324
  ## Operations
250
325
 
251
326
  [Class Documentation](http://www.rubydoc.info/github/sleepingkingstudios/cuprum/master/Cuprum%2FOperation)
252
327
 
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.
328
+ An Operation is like a Function, 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.
329
+
330
+ These two features allow developers to simplify logic around calling and using the results of operations, and reduce the need for boilerplate code (particularly when using an operation as part of an existing framework, such as inside of an asynchronous worker or a Rails controller action).
254
331
 
255
332
  class CreateBookOperation < Cuprum::Operation
256
333
  def process
@@ -128,7 +128,9 @@ module Cuprum
128
128
 
129
129
  # @overload call(*arguments, **keywords, &block)
130
130
  # Executes the logic encoded in the constructor block, or the #process
131
- # method if no block was passed to the constructor.
131
+ # method if no block was passed to the constructor, and returns a
132
+ # Cuprum::Result object with the return value of the block or #process,
133
+ # the success or failure status, and any errors generated.
132
134
  #
133
135
  # @param arguments [Array] Arguments to be passed to the implementation.
134
136
  #
@@ -144,12 +146,12 @@ module Cuprum
144
146
  # subclass.
145
147
  def call *args, &block
146
148
  call_chained_functions do
147
- Cuprum::Result.new.tap do |result|
148
- @errors = result.errors
149
+ Cuprum::Result.new(:errors => build_errors).tap do |result|
150
+ @result = result
149
151
 
150
- result.value = process(*args, &block)
152
+ merge_results(result, process(*args, &block))
151
153
 
152
- @errors = nil
154
+ @result = nil
153
155
  end # tap
154
156
  end # call_chained_functions
155
157
  end # method call
@@ -160,11 +162,14 @@ module Cuprum
160
162
  # function.
161
163
  #
162
164
  # @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.
165
+ # based on the status of the previous function. Valid values are :success,
166
+ # :failure, and :always. A value of :success will constrain the function
167
+ # to run only if the previous function succeeded. A value of :failure will
168
+ # constrain the function to run only if the previous function failed. A
169
+ # value of :always will ensure the function is always run, even if the
170
+ # function chain has been halted. If no value is given, the function will
171
+ # run whether the previous function was a success or a failure, but not if
172
+ # the function chain has been halted.
168
173
  #
169
174
  # @overload chain(function, on: nil)
170
175
  # The function will be passed the #value of the previous function result
@@ -243,16 +248,6 @@ module Cuprum
243
248
 
244
249
  protected
245
250
 
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
251
  def chain_function proc, on: nil
257
252
  hsh = { :proc => proc }
258
253
  hsh[:on] = on if on
@@ -268,7 +263,27 @@ module Cuprum
268
263
 
269
264
  private
270
265
 
271
- attr_reader :errors
266
+ # @!visibility public
267
+ #
268
+ # Generates an empty errors object. When the function is called, the result
269
+ # will have its #errors property initialized to the value returned by
270
+ # #build_errors.By default, this is an array. If you want to use a custom
271
+ # errors object type, override this method in a subclass.
272
+ #
273
+ # @return [Array] an empty errors object.
274
+ def build_errors
275
+ []
276
+ end # method build_errors
277
+
278
+ def call_chained_functions
279
+ chained_functions.reduce(yield) do |result, hsh|
280
+ next result if skip_chained_function?(result, :on => hsh[:on])
281
+
282
+ value = hsh.fetch(:proc).call(result)
283
+
284
+ convert_value_to_result(value) || result
285
+ end # reduce
286
+ end # method call_chained_functions
272
287
 
273
288
  def convert_function_or_proc_to_proc function_or_proc
274
289
  return function_or_proc if function_or_proc.is_a?(Proc)
@@ -276,11 +291,97 @@ module Cuprum
276
291
  ->(result) { function_or_proc.call(result) }
277
292
  end # method convert_function_or_proc_to_proc
278
293
 
294
+ def convert_value_to_result value
295
+ return nil unless value_is_result?(value)
296
+
297
+ if value.respond_to?(:result) && value_is_result?(value.result)
298
+ return value.result
299
+ end # if
300
+
301
+ value
302
+ end # method convert_value_to_result
303
+
304
+ # @!visibility public
305
+ #
306
+ # Provides a reference to the current result's errors object. Messages or
307
+ # error objects added to this will be included in the #errors method of the
308
+ # returned result object.
309
+ #
310
+ # @return [Array, Object] the errors object.
311
+ #
312
+ # @see Cuprum::Result#errors.
313
+ #
314
+ # @note This is a private method, and only available when executing the
315
+ # function implementation as defined in the constructor block or the
316
+ # #process method.
317
+ def errors
318
+ @result&.errors
319
+ end # method errors
320
+
321
+ # @!visibility public
322
+ #
323
+ # Marks the current result as failed. Calling #failure? on the returned
324
+ # result object will evaluate to true, whether or not the result has any
325
+ # errors.
326
+ #
327
+ # @see Cuprum::Result#failure!.
328
+ #
329
+ # @note This is a private method, and only available when executing the
330
+ # function implementation as defined in the constructor block or the
331
+ # #process method.
332
+ def failure!
333
+ @result&.failure!
334
+ end # method failure!
335
+
336
+ def halt!
337
+ @result&.halt!
338
+ end # method halt!
339
+
340
+ def merge_errors result, other
341
+ return unless other.respond_to?(:errors)
342
+
343
+ result.errors += other.errors
344
+ end # method merge_errors
345
+
346
+ def merge_results result, other
347
+ if value_is_result?(other)
348
+ result.value = other.value
349
+
350
+ merge_errors(result, other)
351
+ else
352
+ result.value = other
353
+ end # if-else
354
+
355
+ result
356
+ end # method merge_results
357
+
358
+ # @!visibility public
359
+ # @overload process(*arguments, **keywords, &block)
360
+ # The implementation of the function, to be executed when the #call method
361
+ # is called. Can add errors to or set the status of the result, and the
362
+ # value of the result will be set to the value returned by #process. Do
363
+ # not call this method directly.
364
+ #
365
+ # @param arguments [Array] The arguments, if any, passed from #call.
366
+ #
367
+ # @param keywords [Hash] The keywords, if any, passed from #call.
368
+ #
369
+ # @yield The block, if any, passed from #call.
370
+ #
371
+ # @return [Object] the value of the result object to be returned by #call.
372
+ #
373
+ # @raise NotImplementedError
374
+ #
375
+ # @note This is a private method.
279
376
  def process *_args
280
377
  raise NotImplementedError, nil, caller(1..-1)
281
378
  end # method process
282
379
 
283
380
  def skip_chained_function? last_result, on:
381
+ return false if on == :always
382
+
383
+ return true if last_result.respond_to?(:halted?) && last_result.halted?
384
+
284
385
  case on
285
386
  when :success
286
387
  !last_result.success?
@@ -289,6 +390,21 @@ module Cuprum
289
390
  end # case
290
391
  end # method skip_chained_function?
291
392
 
393
+ # @!visibility public
394
+ #
395
+ # Marks the current result as passing. Calling #success? on the returned
396
+ # result object will evaluate to true, whether or not the result has any
397
+ # errors.
398
+ #
399
+ # @see Cuprum::Result#success!.
400
+ #
401
+ # @note This is a private method, and only available when executing the
402
+ # function implementation as defined in the constructor block or the
403
+ # #process method.
404
+ def success!
405
+ @result&.success!
406
+ end # method success!
407
+
292
408
  def value_is_result? value
293
409
  value.respond_to?(:value) && value.respond_to?(:success?)
294
410
  end # method value
@@ -3,10 +3,17 @@ require 'cuprum/function'
3
3
  module Cuprum
4
4
  # Functional object that with syntactic sugar for tracking the last result.
5
5
  #
6
- # An Operation is like a Function, but with an additional trick of tracking
7
- # its own most recent execution result. This allows us to simplify some
8
- # conditional logic, especially boilerplate code used to interact with
9
- # frameworks.
6
+ # An Operation is like a Function, but with two key differences. First, an
7
+ # Operation retains a reference to the result object from the most recent time
8
+ # the operation was called and delegates the methods defined by Cuprum::Result
9
+ # to the most recent result. This allows a called Operation to replace a
10
+ # Cuprum::Result in any code that expects or returns a result. Second, the
11
+ # #call method returns the operation instance, rather than the result itself.
12
+ #
13
+ # These two features allow developers to simplify logic around calling and
14
+ # using the results of operations, and reduce the need for boilerplate code
15
+ # (particularly when using an operation as part of an existing framework,
16
+ # such as inside of an asynchronous worker or a Rails controller action).
10
17
  #
11
18
  # @example
12
19
  # def create
@@ -31,11 +38,31 @@ module Cuprum
31
38
  # operation.
32
39
  attr_reader :result
33
40
 
34
- # (see Cuprum::Function#call)
41
+ # @overload call(*arguments, **keywords, &block)
42
+ # Executes the logic encoded in the constructor block, or the #process
43
+ # method if no block was passed to the constructor, and returns the
44
+ # operation object.
45
+ #
46
+ # @param arguments [Array] Arguments to be passed to the implementation.
47
+ #
48
+ # @param keywords [Hash] Keywords to be passed to the implementation.
49
+ #
50
+ # @return [Cuprum::Operation] the called operation.
51
+ #
52
+ # @yield If a block argument is given, it will be passed to the
53
+ # implementation.
54
+ #
55
+ # @raise [NotImplementedError] Unless a block was passed to the
56
+ # constructor or the #process method was overriden by a Function
57
+ # subclass.
58
+ #
59
+ # @see Cuprum::Function#call
35
60
  def call *args, &block
36
61
  reset! if called? # Clear reference to most recent result.
37
62
 
38
63
  @result = super
64
+
65
+ self
39
66
  end # method call
40
67
 
41
68
  # @return [Boolean] true if the operation has been called and has a
@@ -57,6 +84,11 @@ module Cuprum
57
84
  called? ? result.failure? : false
58
85
  end # method success?
59
86
 
87
+ # @return [Boolean] true if the most recent was halted, otherwise false.
88
+ def halted?
89
+ called? ? result.halted? : false
90
+ end # method halted?
91
+
60
92
  # Clears the reference to the most recent call of the operation, if any.
61
93
  # This allows the result and any referenced data to be garbage collected.
62
94
  # Use this method to clear any instance variables or state internal to the
data/lib/cuprum/result.rb CHANGED
@@ -10,6 +10,8 @@ module Cuprum
10
10
  def initialize value = nil, errors: []
11
11
  @value = value
12
12
  @errors = errors
13
+ @status = nil
14
+ @halted = false
13
15
  end # constructor
14
16
 
15
17
  # @return [Object] the value returned by calling the function.
@@ -19,16 +21,52 @@ module Cuprum
19
21
  # called.
20
22
  attr_accessor :errors
21
23
 
24
+ # Marks the result as a failure, whether or not the function generated any
25
+ # errors.
26
+ #
27
+ # @return [Cuprum::Result] The result.
28
+ def failure!
29
+ @status = :failure
30
+
31
+ self
32
+ end # method failure!
33
+
22
34
  # @return [Boolean] false if the function did not generate any errors,
23
35
  # otherwise true.
24
36
  def failure?
25
- !errors.empty?
37
+ @status == :failure || (@status.nil? && !errors.empty?)
26
38
  end # method failure?
27
39
 
40
+ # Marks the result as halted. Any subsequent chained functions will not be
41
+ # run.
42
+ #
43
+ # @return [Cuprum::Result] The result.
44
+ def halt!
45
+ @halted = true
46
+
47
+ self
48
+ end # method halt!
49
+
50
+ # @return [Boolean] true if the function has been halted, and will not run
51
+ # any subsequent chained functions.
52
+ def halted?
53
+ @halted
54
+ end # method halted?
55
+
56
+ # Marks the result as a success, whether or not the function generated any
57
+ # errors.
58
+ #
59
+ # @return [Cuprum::Result] The result.
60
+ def success!
61
+ @status = :success
62
+
63
+ self
64
+ end # method success!
65
+
28
66
  # @return [Boolean] true if the function did not generate any errors,
29
67
  # otherwise false.
30
68
  def success?
31
- errors.empty?
69
+ @status == :success || (@status.nil? && errors.empty?)
32
70
  end # method success?
33
71
  end # class
34
72
  end # module
@@ -8,7 +8,7 @@ module Cuprum
8
8
  # Major version.
9
9
  MAJOR = 0
10
10
  # Minor version.
11
- MINOR = 3
11
+ MINOR = 4
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.3.0
4
+ version: 0.4.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-29 00:00:00.000000000 Z
11
+ date: 2017-09-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
@@ -117,7 +117,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
117
117
  version: '0'
118
118
  requirements: []
119
119
  rubyforge_project:
120
- rubygems_version: 2.6.11
120
+ rubygems_version: 2.6.13
121
121
  signing_key:
122
122
  specification_version: 4
123
123
  summary: A lightweight, functional-lite toolkit.