cuprum 0.6.0 → 0.7.0.rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,6 @@
1
- require 'cuprum/basic_command'
2
1
  require 'cuprum/chaining'
3
- require 'cuprum/not_implemented_error'
4
- require 'cuprum/result'
2
+ require 'cuprum/processing'
3
+ require 'cuprum/result_helpers'
5
4
 
6
5
  module Cuprum
7
6
  # Functional object that encapsulates a business logic operation with a
@@ -11,8 +10,8 @@ module Cuprum
11
10
  # by defining a subclass of Command and implementing the #process method.
12
11
  #
13
12
  # @example A Command with a block
14
- # double_function = Cuprum::Command.new { |int| 2 * int }
15
- # result = double_function.call(5)
13
+ # double_command = Cuprum::Command.new { |int| 2 * int }
14
+ # result = double_command.call(5)
16
15
  #
17
16
  # result.value #=> 10
18
17
  #
@@ -29,8 +28,8 @@ module Cuprum
29
28
  # end # method process
30
29
  # end # class
31
30
  #
32
- # triple_function = MultiplyCommand.new(3)
33
- # result = triple_function.call(5)
31
+ # triple_command = MultiplyCommand.new(3)
32
+ # result = command_command.call(5)
34
33
  #
35
34
  # result.value #=> 15
36
35
  #
@@ -53,14 +52,14 @@ module Cuprum
53
52
  # end # method process
54
53
  # end # class
55
54
  #
56
- # halve_function = DivideCommand.new(2)
57
- # result = halve_function.call(10)
55
+ # halve_command = DivideCommand.new(2)
56
+ # result = halve_command.call(10)
58
57
  #
59
58
  # result.errors #=> []
60
59
  # result.value #=> 5
61
60
  #
62
- # function_with_errors = DivideCommand.new(0)
63
- # result = function_with_errors.call(10)
61
+ # command_with_errors = DivideCommand.new(0)
62
+ # result = command_with_errors.call(10)
64
63
  #
65
64
  # result.errors #=> ['errors.messages.divide_by_zero']
66
65
  # result.value #=> nil
@@ -83,7 +82,7 @@ module Cuprum
83
82
  #
84
83
  # result.value #=> 5
85
84
  #
86
- # @example Conditional Chaining With #then And #else
85
+ # @example Conditional Chaining
87
86
  # class EvenCommand < Cuprum::Command
88
87
  # private
89
88
  #
@@ -97,17 +96,36 @@ module Cuprum
97
96
  # # The next step in a Collatz sequence is determined as follows:
98
97
  # # - If the number is even, divide it by 2.
99
98
  # # - If the number is odd, multiply it by 3 and add 1.
100
- # collatz_function =
99
+ # collatz_command =
101
100
  # EvenCommand.new.
102
- # then(DivideCommand.new(2)).
103
- # else(MultiplyCommand.new(3).chain(AddCommand.new(1)))
101
+ # chain(DivideCommand.new(2), :on => :success).
102
+ # chain(
103
+ # MultiplyCommand.new(3).chain(AddCommand.new(1),
104
+ # :on => :failure
105
+ # )
104
106
  #
105
- # result = collatz_function.new(5)
107
+ # result = collatz_command.new(5)
106
108
  # result.value #=> 16
107
109
  #
108
- # result = collatz_function.new(16)
110
+ # result = collatz_command.new(16)
109
111
  # result.value #=> 8
110
- class Command < Cuprum::BasicCommand
112
+ #
113
+ # @see Cuprum::Chaining
114
+ # @see Cuprum::Processing
115
+ # @see Cuprum::ResultHelpers
116
+ class Command
117
+ include Cuprum::Processing
111
118
  include Cuprum::Chaining
119
+ include Cuprum::ResultHelpers
120
+
121
+ # Returns a new instance of Cuprum::Command.
122
+ #
123
+ # @yield [*arguments, **keywords, &block] If a block is given, the
124
+ # #call method will wrap the block and set the result #value to the return
125
+ # value of the block. This overrides the implementation in #process, if
126
+ # any.
127
+ def initialize &implementation
128
+ define_singleton_method :process, &implementation if implementation
129
+ end # method initialize
112
130
  end # class
113
131
  end # module
@@ -1,9 +1,9 @@
1
1
  require 'cuprum/command'
2
2
 
3
3
  module Cuprum
4
- # Functional object that with syntactic sugar for tracking the last result.
4
+ # Functional object with syntactic sugar for tracking the last result.
5
5
  #
6
- # An Operation is like a Function, but with two key differences. First, an
6
+ # An Operation is like a Command, but with two key differences. First, an
7
7
  # Operation retains a reference to the result object from the most recent time
8
8
  # the operation was called and delegates the methods defined by Cuprum::Result
9
9
  # to the most recent result. This allows a called Operation to replace a
@@ -28,14 +28,14 @@ module Cuprum
28
28
  # end # if-else
29
29
  # end # create
30
30
  #
31
- # Like a Function, an Operation can be defined directly by passing an
31
+ # Like a Command, an Operation can be defined directly by passing an
32
32
  # implementation block to the constructor or by creating a subclass that
33
33
  # overwrites the #process method.
34
34
  #
35
35
  # @see Cuprum::Command
36
36
  class Operation < Cuprum::Command
37
37
  # Module-based implementation of the Operation methods. Use this to convert
38
- # an already-defined function into an operation.
38
+ # an already-defined command into an operation.
39
39
  #
40
40
  # @example
41
41
  # class CustomOperation < CustomCommand
@@ -45,6 +45,7 @@ module Cuprum
45
45
  # @return [Cuprum::Result] The result from the most recent call of the
46
46
  # operation.
47
47
  attr_reader :result
48
+ alias_method :to_result, :result
48
49
 
49
50
  # @overload call(*arguments, **keywords, &block)
50
51
  # Executes the logic encoded in the constructor block, or the #process
@@ -82,7 +83,7 @@ module Cuprum
82
83
  # @return [Array] the errors from the most recent result, or nil if the
83
84
  # operation has not been called.
84
85
  def errors
85
- super || (called? ? result.errors : nil)
86
+ called? ? result.errors : nil
86
87
  end # method errors
87
88
 
88
89
  # @return [Boolean] true if the most recent result had errors, or false if
@@ -0,0 +1,180 @@
1
+ require 'cuprum/not_implemented_error'
2
+ require 'cuprum/utils/result_not_empty_warning'
3
+
4
+ module Cuprum
5
+ # Functional implementation for creating a command object. Cuprum::Processing
6
+ # defines a #call method, which performs the implementation defined by
7
+ # #process and returns an instance of Cuprum::Result.
8
+ #
9
+ # @example Defining a command with Cuprum::Processing.
10
+ # class AdderCommand
11
+ # include Cuprum::Processing
12
+ #
13
+ # def initialize addend
14
+ # @addend = addend
15
+ # end # constructor
16
+ #
17
+ # private
18
+ #
19
+ # def process int
20
+ # int + addend
21
+ # end # method process
22
+ # end # class AdderCommand
23
+ #
24
+ # adder = AdderCommand.new(2)
25
+ # result = adder.call(3)
26
+ # #=> an instance of Cuprum::Result
27
+ # result.value #=> 5
28
+ # result.success? #=> true
29
+ #
30
+ # @example Defining a command with error handling.
31
+ # class SquareRootCommand
32
+ # include Cuprum::Processing
33
+ #
34
+ # private
35
+ #
36
+ # def process value
37
+ # if value.negative?
38
+ # result.errors << 'value cannot be negative'
39
+ #
40
+ # return nil
41
+ # end # if
42
+ #
43
+ # Math.sqrt(value)
44
+ # end # method process
45
+ # end # class
46
+ #
47
+ # result = SquareRootCommand.new.call(2)
48
+ # result.value #=> 1.414
49
+ # result.success? #=> true
50
+ # result.failure? #=> false
51
+ # result.errors #=> []
52
+ #
53
+ # result = SquareRootCommand.new.call(-1)
54
+ # result.value #=> nil
55
+ # result.success? #=> false
56
+ # result.failure? #=> true
57
+ # result.errors #=> ['value cannot be negative']
58
+ #
59
+ # @see Cuprum::Command
60
+ module Processing
61
+ VALUE_METHODS = %i[to_result value success?].freeze
62
+ private_constant :VALUE_METHODS
63
+
64
+ # Returns a nonnegative integer for commands that take a fixed number of
65
+ # arguments. For commands that take a variable number of arguments, returns
66
+ # -n-1, where n is the number of required arguments.
67
+ #
68
+ # @return [Integer] The number of arguments.
69
+ def arity
70
+ method(:process).arity
71
+ end # method arity
72
+
73
+ # @overload call(*arguments, **keywords, &block)
74
+ # Executes the command implementation and returns a Cuprum::Result or
75
+ # compatible object.
76
+ #
77
+ # Each time #call is invoked, the object performs the following steps:
78
+ #
79
+ # 1. Creates a result object, typically an instance of Cuprum::Result.
80
+ # The result is assigned to the command as the private #result reader.
81
+ # 2. The #process method is called, passing the arguments, keywords, and
82
+ # block that were passed to #call. The #process method can set errors,
83
+ # set the status, or halt the result via the #result reader method.
84
+ # 3. If #process returns a result, that result is returned by #call.
85
+ # Otherwise, the return value of #process is assigned to the #value
86
+ # property of the result, and the result is returned by #call.
87
+ #
88
+ # @param arguments [Array] Arguments to be passed to the implementation.
89
+ #
90
+ # @param keywords [Hash] Keywords to be passed to the implementation.
91
+ #
92
+ # @return [Cuprum::Result] The result object for the command.
93
+ #
94
+ # @yield If a block argument is given, it will be passed to the
95
+ # implementation.
96
+ #
97
+ # @raise [Cuprum::NotImplementedError] Unless the #process method was
98
+ # overriden.
99
+ def call *args, &block
100
+ result = build_result(nil, :errors => build_errors)
101
+
102
+ process_with_result(result, *args, &block)
103
+ end # method call
104
+
105
+ private
106
+
107
+ # @return [Cuprum::Result] The current result. Only available while #process
108
+ # is being called.
109
+ attr_reader :result
110
+
111
+ # @!visibility public
112
+ #
113
+ # Generates an empty errors object. When the command is called, the result
114
+ # will have its #errors property initialized to the value returned by
115
+ # #build_errors. By default, this is an array. If you want to use a custom
116
+ # errors object type, override this method in a subclass.
117
+ #
118
+ # @return [Array] An empty errors object.
119
+ def build_errors
120
+ []
121
+ end # method build_errors
122
+
123
+ def build_result value, errors:
124
+ Cuprum::Result.new(value, :errors => errors)
125
+ end # method build_result
126
+
127
+ def merge_results result, other
128
+ if value_is_result?(other)
129
+ return result if result == other
130
+
131
+ if result.respond_to?(:empty?) && !result.empty?
132
+ Cuprum.warn(Cuprum::Utils::ResultNotEmptyWarning.new(result).message)
133
+ end # if
134
+
135
+ other.to_result
136
+ else
137
+ result.value = other
138
+
139
+ result
140
+ end # if-else
141
+ end # method merge_results
142
+
143
+ # @!visibility public
144
+ # @overload process(*arguments, **keywords, &block)
145
+ # The implementation of the command, to be executed when the #call method
146
+ # is called. Can add errors to or set the status of the result, and the
147
+ # value of the result will be set to the value returned by #process. Do
148
+ # not call this method directly.
149
+ #
150
+ # @param arguments [Array] The arguments, if any, passed from #call.
151
+ #
152
+ # @param keywords [Hash] The keywords, if any, passed from #call.
153
+ #
154
+ # @yield The block, if any, passed from #call.
155
+ #
156
+ # @return [Object] the value of the result object to be returned by #call.
157
+ #
158
+ # @raise [Cuprum::NotImplementedError] Unless a block was passed to the
159
+ # constructor or the #process method was overriden by a Command
160
+ # subclass.
161
+ #
162
+ # @note This is a private method.
163
+ def process *_args
164
+ raise Cuprum::NotImplementedError, nil, caller(1..-1)
165
+ end # method process
166
+
167
+ def process_with_result result, *args, &block
168
+ @result = result
169
+ value = process(*args, &block)
170
+
171
+ merge_results(result, value)
172
+ ensure
173
+ @result = nil
174
+ end # method process_with_result
175
+
176
+ def value_is_result? value
177
+ VALUE_METHODS.all? { |method_name| value.respond_to?(method_name) }
178
+ end # method value
179
+ end # module
180
+ end # module
@@ -1,11 +1,10 @@
1
1
  require 'cuprum'
2
2
 
3
3
  module Cuprum
4
- # Data object that encapsulates the result of calling a Cuprum function or
5
- # operation.
4
+ # Data object that encapsulates the result of calling a Cuprum command.
6
5
  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
6
+ # @param value [Object] The value returned by calling the command.
7
+ # @param errors [Array] The errors (if any) generated when the command was
9
8
  # called.
10
9
  def initialize value = nil, errors: []
11
10
  @value = value
@@ -14,10 +13,10 @@ module Cuprum
14
13
  @halted = false
15
14
  end # constructor
16
15
 
17
- # @return [Object] the value returned by calling the function.
16
+ # @return [Object] the value returned by calling the command.
18
17
  attr_accessor :value
19
18
 
20
- # @return [Array] the errors (if any) generated when the function was
19
+ # @return [Array] the errors (if any) generated when the command was
21
20
  # called.
22
21
  attr_accessor :errors
23
22
 
@@ -66,7 +65,7 @@ module Cuprum
66
65
  value.nil? && errors.empty? && @status.nil? && !halted?
67
66
  end # method empty?
68
67
 
69
- # Marks the result as a failure, whether or not the function generated any
68
+ # Marks the result as a failure, whether or not the command generated any
70
69
  # errors.
71
70
  #
72
71
  # @return [Cuprum::Result] The result.
@@ -76,13 +75,13 @@ module Cuprum
76
75
  self
77
76
  end # method failure!
78
77
 
79
- # @return [Boolean] false if the function did not generate any errors,
78
+ # @return [Boolean] false if the command did not generate any errors,
80
79
  # otherwise true.
81
80
  def failure?
82
81
  @status == :failure || (@status.nil? && !errors.empty?)
83
82
  end # method failure?
84
83
 
85
- # Marks the result as halted. Any subsequent chained functions will not be
84
+ # Marks the result as halted. Any subsequent chained commands will not be
86
85
  # run.
87
86
  #
88
87
  # @return [Cuprum::Result] The result.
@@ -92,13 +91,13 @@ module Cuprum
92
91
  self
93
92
  end # method halt!
94
93
 
95
- # @return [Boolean] true if the function has been halted, and will not run
96
- # any subsequent chained functions.
94
+ # @return [Boolean] true if the command has been halted, and will not run
95
+ # any subsequent chained commands.
97
96
  def halted?
98
97
  @halted
99
98
  end # method halted?
100
99
 
101
- # Marks the result as a success, whether or not the function generated any
100
+ # Marks the result as a success, whether or not the command generated any
102
101
  # errors.
103
102
  #
104
103
  # @return [Cuprum::Result] The result.
@@ -108,12 +107,17 @@ module Cuprum
108
107
  self
109
108
  end # method success!
110
109
 
111
- # @return [Boolean] true if the function did not generate any errors,
110
+ # @return [Boolean] true if the command did not generate any errors,
112
111
  # otherwise false.
113
112
  def success?
114
113
  @status == :success || (@status.nil? && errors.empty?)
115
114
  end # method success?
116
115
 
116
+ # @return [Cuprum::Result] The result.
117
+ def to_result
118
+ self
119
+ end # method to_result
120
+
117
121
  # @api private
118
122
  def update other_result
119
123
  return self if other_result.nil?
@@ -0,0 +1,113 @@
1
+ require 'cuprum'
2
+
3
+ module Cuprum
4
+ # Helper methods that delegate result methods to the currently processed
5
+ # result.
6
+ #
7
+ # @example
8
+ # class LogCommand
9
+ # include Cuprum::Processing
10
+ # include Cuprum::ResultHelpers
11
+ #
12
+ # private
13
+ #
14
+ # def process log
15
+ # case log[:level]
16
+ # when 'fatal'
17
+ # halt!
18
+ #
19
+ # 'error'
20
+ # when 'error' && log[:message]
21
+ # errors << message
22
+ #
23
+ # 'error'
24
+ # when 'error'
25
+ # failure!
26
+ #
27
+ # 'error'
28
+ # else
29
+ # 'ok'
30
+ # end # case
31
+ # end # method process
32
+ # end # class
33
+ #
34
+ # result = LogCommand.new.call(:level => 'info')
35
+ # result.success? #=> true
36
+ #
37
+ # string = 'something went wrong'
38
+ # result = LogCommand.new.call(:level => 'error', :message => string)
39
+ # result.success? #=> false
40
+ # result.errors #=> ['something went wrong']
41
+ #
42
+ # result = LogCommand.new.call(:level => 'error')
43
+ # result.success? #=> false
44
+ # result.errors #=> []
45
+ #
46
+ # result = LogCommand.new.call(:level => 'fatal')
47
+ # result.halted? #=> true
48
+ #
49
+ # @see Cuprum::Command
50
+ module ResultHelpers
51
+ private
52
+
53
+ # @!visibility public
54
+ #
55
+ # Provides a reference to the current result's errors object. Messages or
56
+ # error objects added to this will be included in the #errors method of the
57
+ # returned result object.
58
+ #
59
+ # @return [Array, Object] The errors object.
60
+ #
61
+ # @see Cuprum::Result#errors.
62
+ #
63
+ # @note This is a private method, and only available when executing the
64
+ # command implementation as defined in the constructor block or the
65
+ # #process method.
66
+ def errors
67
+ result&.errors
68
+ end # method errors
69
+
70
+ # @!visibility public
71
+ #
72
+ # Marks the current result as failed. Calling #failure? on the returned
73
+ # result object will evaluate to true, whether or not the result has any
74
+ # errors.
75
+ #
76
+ # @see Cuprum::Result#failure!.
77
+ #
78
+ # @note This is a private method, and only available when executing the
79
+ # command implementation as defined in the constructor block or the
80
+ # #process method.
81
+ def failure!
82
+ result&.failure!
83
+ end # method failure!
84
+
85
+ # @!visibility public
86
+ #
87
+ # Marks the current result as halted.
88
+ #
89
+ # @see Cuprum::Result#halt!.
90
+ #
91
+ # @note This is a private method, and only available when executing the
92
+ # command implementation as defined in the constructor block or the
93
+ # #process method.
94
+ def halt!
95
+ result&.halt!
96
+ end # method halt!
97
+
98
+ # @!visibility public
99
+ #
100
+ # Marks the current result as passing. Calling #success? on the returned
101
+ # result object will evaluate to true, whether or not the result has any
102
+ # errors.
103
+ #
104
+ # @see Cuprum::Result#success!.
105
+ #
106
+ # @note This is a private method, and only available when executing the
107
+ # command implementation as defined in the constructor block or the
108
+ # #process method.
109
+ def success!
110
+ result&.success!
111
+ end # method success!
112
+ end # module
113
+ end # module