cuprum 0.8.0 → 0.9.0.beta.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.
@@ -32,7 +32,7 @@ module Cuprum
32
32
  #
33
33
  # result.value #=> 15
34
34
  #
35
- # @example A Command with errors
35
+ # @example A Command with an error state
36
36
  # class DivideCommand < Cuprum::Command
37
37
  # def initialize divisor
38
38
  # @divisor = divisor
@@ -42,10 +42,8 @@ module Cuprum
42
42
  #
43
43
  # def process int
44
44
  # if @divisor.zero?
45
- # errors << 'errors.messages.divide_by_zero'
46
- #
47
- # return
48
- # end # if
45
+ # return Cuprum::Result.new(error: 'errors.messages.divide_by_zero')
46
+ # end
49
47
  #
50
48
  # int / @divisor
51
49
  # end # method process
@@ -54,14 +52,14 @@ module Cuprum
54
52
  # halve_command = DivideCommand.new(2)
55
53
  # result = halve_command.call(10)
56
54
  #
57
- # result.errors #=> []
58
- # result.value #=> 5
55
+ # result.error #=> nil
56
+ # result.value #=> 5
59
57
  #
60
- # command_with_errors = DivideCommand.new(0)
61
- # result = command_with_errors.call(10)
58
+ # divide_command = DivideCommand.new(0)
59
+ # result = divide_command.call(10)
62
60
  #
63
- # result.errors #=> ['errors.messages.divide_by_zero']
64
- # result.value #=> nil
61
+ # result.error #=> 'errors.messages.divide_by_zero'
62
+ # result.value #=> nil
65
63
  #
66
64
  # @example Command Chaining
67
65
  # class AddCommand < Cuprum::Command
@@ -86,9 +84,9 @@ module Cuprum
86
84
  # private
87
85
  #
88
86
  # def process int
89
- # errors << 'errors.messages.not_even' unless int.even?
87
+ # return int if int.even?
90
88
  #
91
- # int
89
+ # Cuprum::Errors.new(error: 'errors.messages.not_even')
92
90
  # end # method process
93
91
  # end # class
94
92
  #
@@ -122,7 +120,11 @@ module Cuprum
122
120
  # value of the block. This overrides the implementation in #process, if
123
121
  # any.
124
122
  def initialize &implementation
125
- define_singleton_method :process, &implementation if implementation
123
+ return unless implementation
124
+
125
+ define_singleton_method :process, &implementation
126
+
127
+ singleton_class.send(:private, :process)
126
128
  end # method initialize
127
129
  end # class
128
130
  end # module
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cuprum'
4
+
5
+ module Cuprum
6
+ # Wrapper class for encapsulating an error state for a failed Cuprum result.
7
+ # Additional details can be passed by setting the #message or by using a
8
+ # subclass of Cuprum::Error.
9
+ class Error
10
+ COMPARABLE_PROPERTIES = %i[message].freeze
11
+ private_constant :COMPARABLE_PROPERTIES
12
+
13
+ # @param message [String] Optional message describing the nature of the
14
+ # error.
15
+ def initialize(message: nil)
16
+ @message = message
17
+ end
18
+
19
+ # @return [String] Optional message describing the nature of the error.
20
+ attr_reader :message
21
+
22
+ # @param other [Cuprum::Error] The other object to compare.
23
+ #
24
+ # @return [Boolean] true if the other object has the same class and message;
25
+ # otherwise false.
26
+ def ==(other)
27
+ other.instance_of?(self.class) &&
28
+ comparable_properties.all? { |prop| send(prop) == other.send(prop) }
29
+ end
30
+
31
+ private
32
+
33
+ def comparable_properties
34
+ self.class.const_get(:COMPARABLE_PROPERTIES)
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cuprum/error'
4
+ require 'cuprum/errors'
5
+
6
+ module Cuprum::Errors
7
+ # Error class to be used when a Command is called without defining a #process
8
+ # method.
9
+ class CommandNotImplemented < Cuprum::Error
10
+ COMPARABLE_PROPERTIES = %i[command].freeze
11
+ private_constant :COMPARABLE_PROPERTIES
12
+
13
+ # Format for generating error message.
14
+ MESSAGE_FORMAT = 'no implementation defined for %s'
15
+
16
+ # @param command [Cuprum::Command] The command called without a definition.
17
+ def initialize(command:)
18
+ @command = command
19
+
20
+ class_name = command&.class&.name || 'command'
21
+ message = MESSAGE_FORMAT % class_name
22
+
23
+ super(message: message)
24
+ end
25
+
26
+ # @return [Cuprum::Command] The command called without a definition.
27
+ attr_reader :command
28
+
29
+ private
30
+
31
+ def comparable_properties
32
+ COMPARABLE_PROPERTIES
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cuprum/error'
4
+ require 'cuprum/errors'
5
+
6
+ module Cuprum::Errors
7
+ # Error class to be used when trying to access the result of an uncalled
8
+ # Operation.
9
+ class OperationNotCalled < Cuprum::Error
10
+ COMPARABLE_PROPERTIES = %i[operation].freeze
11
+ private_constant :COMPARABLE_PROPERTIES
12
+
13
+ # Format for generating error message.
14
+ MESSAGE_FORMAT = '%s was not called and does not have a result'
15
+
16
+ # @param operation [Cuprum::Operation] The uncalled operation.
17
+ def initialize(operation:)
18
+ @operation = operation
19
+
20
+ class_name = operation&.class&.name || 'operation'
21
+ message = MESSAGE_FORMAT % class_name
22
+
23
+ super(message: message)
24
+ end
25
+
26
+ # @return [Cuprum::Operation] The uncalled operation.
27
+ attr_reader :operation
28
+
29
+ private
30
+
31
+ def comparable_properties
32
+ COMPARABLE_PROPERTIES
33
+ end
34
+ end
35
+ end
@@ -1,4 +1,5 @@
1
1
  require 'cuprum/command'
2
+ require 'cuprum/errors/operation_not_called'
2
3
 
3
4
  module Cuprum
4
5
  # Functional object with syntactic sugar for tracking the last result.
@@ -45,7 +46,6 @@ module Cuprum
45
46
  # @return [Cuprum::Result] The result from the most recent call of the
46
47
  # operation.
47
48
  attr_reader :result
48
- alias_method :to_result, :result
49
49
 
50
50
  # @overload call(*arguments, **keywords, &block)
51
51
  # Executes the logic encoded in the constructor block, or the #process
@@ -61,10 +61,6 @@ module Cuprum
61
61
  # @yield If a block argument is given, it will be passed to the
62
62
  # implementation.
63
63
  #
64
- # @raise [Cuprum::Errors::ProcessNotImplementedError] Unless a block was
65
- # passed to the constructor or the #process method was overriden by a
66
- # Command subclass.
67
- #
68
64
  # @see Cuprum::Command#call
69
65
  def call *args, &block
70
66
  reset! if called? # Clear reference to most recent result.
@@ -80,23 +76,18 @@ module Cuprum
80
76
  !result.nil?
81
77
  end # method called?
82
78
 
83
- # @return [Array] the errors from the most recent result, or nil if the
84
- # operation has not been called.
85
- def errors
86
- called? ? result.errors : nil
87
- end # method errors
79
+ # @return [Object] the error (if any) from the most recent result, or nil
80
+ # if the operation has not been called.
81
+ def error
82
+ called? ? result.error : nil
83
+ end # method error
88
84
 
89
- # @return [Boolean] true if the most recent result had errors, or false if
90
- # the most recent result had no errors or if the operation has not been
91
- # called.
85
+ # @return [Boolean] true if the most recent result had an error, or false
86
+ # if the most recent result had no error or if the operation has not
87
+ # been called.
92
88
  def failure?
93
89
  called? ? result.failure? : false
94
- end # method success?
95
-
96
- # @return [Boolean] true if the most recent was halted, otherwise false.
97
- def halted?
98
- called? ? result.halted? : false
99
- end # method halted?
90
+ end # method failure?
100
91
 
101
92
  # Clears the reference to the most recent call of the operation, if any.
102
93
  # This allows the result and any referenced data to be garbage collected.
@@ -110,13 +101,25 @@ module Cuprum
110
101
  @result = nil
111
102
  end # method reset
112
103
 
113
- # @return [Boolean] true if the most recent result had no errors, or false
114
- # if the most recent result had errors or if the operation has not been
115
- # called.
104
+ # @return [Boolean] true if the most recent result had no error, or false
105
+ # if the most recent result had an error or if the operation has not
106
+ # been called.
116
107
  def success?
117
108
  called? ? result.success? : false
118
109
  end # method success?
119
110
 
111
+ # Returns the most result if the operation was previously called.
112
+ # Otherwise, returns a failing result.
113
+ #
114
+ # @return [Cuprum::Result] the most recent result or failing result.
115
+ def to_cuprum_result
116
+ return result if result
117
+
118
+ error = Cuprum::Errors::OperationNotCalled.new(operation: self)
119
+
120
+ Cuprum::Result.new(error: error)
121
+ end
122
+
120
123
  # @return [Object] the value of the most recent result, or nil if the
121
124
  # operation has not been called.
122
125
  def value
@@ -131,15 +134,12 @@ module Cuprum
131
134
  # @!method called?
132
135
  # (see Cuprum::Operation::Mixin#called?)
133
136
 
134
- # @!method errors
135
- # (see Cuprum::Operation::Mixin#errors)
137
+ # @!method error
138
+ # (see Cuprum::Operation::Mixin#error)
136
139
 
137
140
  # @!method failure?
138
141
  # (see Cuprum::Operation::Mixin#failure?)
139
142
 
140
- # @!method halted?
141
- # (see Cuprum::Operation::Mixin#halted?)
142
-
143
143
  # @!method reset!
144
144
  # (see Cuprum::Operation::Mixin#reset!)
145
145
 
@@ -149,6 +149,9 @@ module Cuprum
149
149
  # @!method success?
150
150
  # (see Cuprum::Operation::Mixin#success?)
151
151
 
152
+ # @!method to_cuprum_result
153
+ # (see Cuprum::Operation::Mixin#to_cuprum_result?)
154
+
152
155
  # @!method value
153
156
  # (see Cuprum::Operation::Mixin#value)
154
157
  end # class
@@ -1,5 +1,6 @@
1
- require 'cuprum/errors/process_not_implemented_error'
2
- require 'cuprum/utils/result_not_empty_warning'
1
+ # frozen_string_literal: true
2
+
3
+ require 'cuprum/errors/command_not_implemented'
3
4
 
4
5
  module Cuprum
5
6
  # Functional implementation for creating a command object. Cuprum::Processing
@@ -12,14 +13,14 @@ module Cuprum
12
13
  #
13
14
  # def initialize addend
14
15
  # @addend = addend
15
- # end # constructor
16
+ # end
16
17
  #
17
18
  # private
18
19
  #
19
20
  # def process int
20
21
  # int + addend
21
- # end # method process
22
- # end # class AdderCommand
22
+ # end
23
+ # end
23
24
  #
24
25
  # adder = AdderCommand.new(2)
25
26
  # result = adder.call(3)
@@ -35,32 +36,27 @@ module Cuprum
35
36
  #
36
37
  # def process value
37
38
  # if value.negative?
38
- # result.errors << 'value cannot be negative'
39
- #
40
- # return nil
41
- # end # if
39
+ # return Cuprum::Result.new(error: 'value cannot be negative')
40
+ # end
42
41
  #
43
42
  # Math.sqrt(value)
44
- # end # method process
45
- # end # class
43
+ # end
44
+ # end
46
45
  #
47
46
  # result = SquareRootCommand.new.call(2)
48
47
  # result.value #=> 1.414
49
48
  # result.success? #=> true
50
49
  # result.failure? #=> false
51
- # result.errors #=> []
50
+ # result.error #=> nil
52
51
  #
53
52
  # result = SquareRootCommand.new.call(-1)
54
53
  # result.value #=> nil
55
54
  # result.success? #=> false
56
55
  # result.failure? #=> true
57
- # result.errors #=> ['value cannot be negative']
56
+ # result.error #=> 'value cannot be negative'
58
57
  #
59
58
  # @see Cuprum::Command
60
59
  module Processing
61
- VALUE_METHODS = %i[to_result value success?].freeze
62
- private_constant :VALUE_METHODS
63
-
64
60
  # Returns a nonnegative integer for commands that take a fixed number of
65
61
  # arguments. For commands that take a variable number of arguments, returns
66
62
  # -n-1, where n is the number of required arguments.
@@ -68,7 +64,7 @@ module Cuprum
68
64
  # @return [Integer] The number of arguments.
69
65
  def arity
70
66
  method(:process).arity
71
- end # method arity
67
+ end
72
68
 
73
69
  # @overload call(*arguments, **keywords, &block)
74
70
  # Executes the command implementation and returns a Cuprum::Result or
@@ -76,14 +72,12 @@ module Cuprum
76
72
  #
77
73
  # Each time #call is invoked, the object performs the following steps:
78
74
  #
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.
75
+ # 1. The #process method is called, passing the arguments, keywords, and
76
+ # block that were passed to #call.
77
+ # 2. If the value returned by #process is a Cuprum::Result or compatible
78
+ # object, that result is directly returned by #call.
79
+ # 3. Otherwise, the value returned by #process will be wrapped in a
80
+ # successful result, which will be returned by #call.
87
81
  #
88
82
  # @param arguments [Array] Arguments to be passed to the implementation.
89
83
  #
@@ -93,43 +87,31 @@ module Cuprum
93
87
  #
94
88
  # @yield If a block argument is given, it will be passed to the
95
89
  # implementation.
96
- #
97
- # @raise [Cuprum::Errors::ProcessNotImplementedError] Unless the #process
98
- # method was overriden.
99
- def call *args, &block
100
- process_with_result(build_result, *args, &block)
101
- end # method call
102
-
103
- private
90
+ def call(*args, &block)
91
+ value = process(*args, &block)
104
92
 
105
- # @return [Cuprum::Result] The current result. Only available while #process
106
- # is being called.
107
- attr_reader :result
93
+ return value.to_cuprum_result if value_is_result?(value)
108
94
 
109
- def build_result value = nil, **options
110
- Cuprum::Result.new(value, options)
111
- end # method build_result
95
+ build_result(value: value)
96
+ end
112
97
 
113
- def merge_results result, other
114
- if value_is_result?(other)
115
- return result if result == other
116
-
117
- warn_unless_empty!(result)
98
+ private
118
99
 
119
- other.to_result
120
- else
121
- result.value = other
100
+ def build_result(error: nil, status: nil, value: nil)
101
+ Cuprum::Result.new(error: error, status: status, value: value)
102
+ end
122
103
 
123
- result
124
- end # if-else
125
- end # method merge_results
104
+ def failure(error)
105
+ build_result(error: error)
106
+ end
126
107
 
127
108
  # @!visibility public
128
109
  # @overload process(*arguments, **keywords, &block)
129
110
  # The implementation of the command, to be executed when the #call method
130
- # is called. Can add errors to or set the status of the result, and the
131
- # value of the result will be set to the value returned by #process. Do
132
- # not call this method directly.
111
+ # is called. If #process returns a result, that result will be returned by
112
+ # #call; otherwise, the value returned by #process will be wrapped in a
113
+ # successful Cuprum::Result object. This method should not be called
114
+ # directly.
133
115
  #
134
116
  # @param arguments [Array] The arguments, if any, passed from #call.
135
117
  #
@@ -139,34 +121,19 @@ module Cuprum
139
121
  #
140
122
  # @return [Object] the value of the result object to be returned by #call.
141
123
  #
142
- # @raise [Cuprum::Errors::ProcessNotImplementedError] Unless a block was
143
- # passed to the constructor or the #process method was overriden by a
144
- # Command subclass.
145
- #
146
124
  # @note This is a private method.
147
- def process *_args
148
- raise Cuprum::Errors::ProcessNotImplementedError, nil, caller(1..-1)
149
- end # method process
150
-
151
- def process_with_result result, *args, &block
152
- @result = result
153
- value = process(*args, &block)
154
-
155
- merge_results(result, value)
156
- ensure
157
- @result = nil
158
- end # method process_with_result
159
-
160
- def value_is_result? value
161
- VALUE_METHODS.all? { |method_name| value.respond_to?(method_name) }
162
- end # method value
125
+ def process(*_args)
126
+ error = Cuprum::Errors::CommandNotImplemented.new(command: self)
163
127
 
164
- def warn_unless_empty! result
165
- return unless result.respond_to?(:empty?) && !result.empty?
128
+ build_result(error: error)
129
+ end
166
130
 
167
- not_empty = Cuprum::Utils::ResultNotEmptyWarning.new(result)
131
+ def success(value)
132
+ build_result(value: value)
133
+ end
168
134
 
169
- Cuprum.warn(not_empty.message) if not_empty.warning?
170
- end # method warn_unless_empty!
171
- end # module
172
- end # module
135
+ def value_is_result?(value)
136
+ value.respond_to?(:to_cuprum_result)
137
+ end
138
+ end
139
+ end