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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +18 -0
- data/DEVELOPMENT.md +45 -42
- data/README.md +469 -62
- data/lib/cuprum.rb +1 -1
- data/lib/cuprum/built_in.rb +1 -1
- data/lib/cuprum/built_in/identity_command.rb +1 -1
- data/lib/cuprum/chaining.rb +300 -92
- data/lib/cuprum/command.rb +36 -18
- data/lib/cuprum/operation.rb +6 -5
- data/lib/cuprum/processing.rb +180 -0
- data/lib/cuprum/result.rb +17 -13
- data/lib/cuprum/result_helpers.rb +113 -0
- data/lib/cuprum/utils/instance_spy.rb +26 -26
- data/lib/cuprum/utils/result_not_empty_warning.rb +65 -0
- data/lib/cuprum/version.rb +3 -3
- metadata +7 -5
- data/lib/cuprum/basic_command.rb +0 -212
data/lib/cuprum/command.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
|
-
require 'cuprum/basic_command'
|
2
1
|
require 'cuprum/chaining'
|
3
|
-
require 'cuprum/
|
4
|
-
require 'cuprum/
|
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
|
-
#
|
15
|
-
# result
|
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
|
-
#
|
33
|
-
# result
|
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
|
-
#
|
57
|
-
# result
|
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
|
-
#
|
63
|
-
# result
|
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
|
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
|
-
#
|
99
|
+
# collatz_command =
|
101
100
|
# EvenCommand.new.
|
102
|
-
#
|
103
|
-
#
|
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 =
|
107
|
+
# result = collatz_command.new(5)
|
106
108
|
# result.value #=> 16
|
107
109
|
#
|
108
|
-
# result =
|
110
|
+
# result = collatz_command.new(16)
|
109
111
|
# result.value #=> 8
|
110
|
-
|
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
|
data/lib/cuprum/operation.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
require 'cuprum/command'
|
2
2
|
|
3
3
|
module Cuprum
|
4
|
-
# Functional object
|
4
|
+
# Functional object with syntactic sugar for tracking the last result.
|
5
5
|
#
|
6
|
-
# An Operation is like a
|
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
|
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
|
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
|
-
|
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
|
data/lib/cuprum/result.rb
CHANGED
@@ -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
|
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
|
8
|
-
# @param errors [Array] The errors (if any) generated when the
|
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
|
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
|
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
|
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
|
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
|
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
|
96
|
-
# any subsequent chained
|
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
|
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
|
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
|