cuprum 0.6.0 → 0.7.0.rc.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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