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.
- 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
|