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
@@ -2,19 +2,19 @@ require 'cuprum/utils'
|
|
2
2
|
|
3
3
|
module Cuprum::Utils
|
4
4
|
# Utility module for instrumenting calls to the #call method of any instance
|
5
|
-
# of a
|
6
|
-
# functionality of code that calls a
|
7
|
-
# the
|
8
|
-
# call a
|
5
|
+
# of a command class. This can be used to unobtrusively test the
|
6
|
+
# functionality of code that calls a command without providing a reference to
|
7
|
+
# the command instance, such as chained commands or methods that create and
|
8
|
+
# call a command instance.
|
9
9
|
#
|
10
|
-
# @example Observing calls to instances of a
|
10
|
+
# @example Observing calls to instances of a command.
|
11
11
|
# spy = Cuprum::Utils::InstanceSpy.spy_on(CustomCommand)
|
12
12
|
#
|
13
13
|
# expect(spy).to receive(:call).with(1, 2, 3, :four => '4')
|
14
14
|
#
|
15
15
|
# CustomCommand.new.call(1, 2, 3, :four => '4')
|
16
16
|
#
|
17
|
-
# @example Observing calls to a chained
|
17
|
+
# @example Observing calls to a chained command.
|
18
18
|
# spy = Cuprum::Utils::InstanceSpy.spy_on(ChainedCommand)
|
19
19
|
#
|
20
20
|
# expect(spy).to receive(:call)
|
@@ -31,14 +31,14 @@ module Cuprum::Utils
|
|
31
31
|
# end # spy_on
|
32
32
|
module InstanceSpy
|
33
33
|
# Minimal class that implements a #call method to mirror method calls to
|
34
|
-
# instances of an instrumented
|
34
|
+
# instances of an instrumented command class.
|
35
35
|
class Spy
|
36
36
|
# Empty method that accepts any arguments and an optional block.
|
37
37
|
def call *_args, █ end
|
38
38
|
end # class
|
39
39
|
|
40
40
|
class << self
|
41
|
-
# Retires all spies. Subsequent calls to the #call method on
|
41
|
+
# Retires all spies. Subsequent calls to the #call method on command
|
42
42
|
# instances will not be mirrored to existing spy objects.
|
43
43
|
def clear_spies
|
44
44
|
Thread.current[:cuprum_instance_spies] = nil
|
@@ -50,7 +50,7 @@ module Cuprum::Utils
|
|
50
50
|
# that the #call method is called for an object of the given type, the
|
51
51
|
# spy's #call method will be invoked with the same arguments and block.
|
52
52
|
#
|
53
|
-
# @param
|
53
|
+
# @param command_class [Class, Module] The type of command to spy on.
|
54
54
|
# Must be either a Module, or a Class that extends Cuprum::Command.
|
55
55
|
#
|
56
56
|
# @raise [ArgumentError] If the argument is neither a Module nor a Class
|
@@ -59,54 +59,54 @@ module Cuprum::Utils
|
|
59
59
|
# @note Calling this method for the first time will prepend the
|
60
60
|
# Cuprum::Utils::InstanceSpy module to Cuprum::Command.
|
61
61
|
#
|
62
|
-
# @overload spy_on(
|
62
|
+
# @overload spy_on(command_class)
|
63
63
|
# @return [Cuprum::Utils::InstanceSpy::Spy] The instance spy.
|
64
64
|
#
|
65
|
-
# @overload spy_on(
|
65
|
+
# @overload spy_on(command_class, &block)
|
66
66
|
# Yields the instance spy to the block, and returns nil.
|
67
67
|
#
|
68
68
|
# @yield [Cuprum::Utils::InstanceSpy::Spy] The instance spy.
|
69
69
|
#
|
70
70
|
# @return [nil] nil.
|
71
|
-
def spy_on
|
72
|
-
guard_spy_class!(
|
71
|
+
def spy_on command_class
|
72
|
+
guard_spy_class!(command_class)
|
73
73
|
|
74
74
|
instrument_call!
|
75
75
|
|
76
76
|
if block_given?
|
77
77
|
begin
|
78
|
-
instance_spy = assign_spy(
|
78
|
+
instance_spy = assign_spy(command_class)
|
79
79
|
|
80
80
|
yield instance_spy
|
81
81
|
end # begin-ensure
|
82
82
|
else
|
83
|
-
assign_spy(
|
83
|
+
assign_spy(command_class)
|
84
84
|
end # if-else
|
85
85
|
end # method spy_on
|
86
86
|
|
87
87
|
private
|
88
88
|
|
89
|
-
def assign_spy
|
90
|
-
existing_spy = spies[
|
89
|
+
def assign_spy command_class
|
90
|
+
existing_spy = spies[command_class]
|
91
91
|
|
92
92
|
return existing_spy if existing_spy
|
93
93
|
|
94
|
-
spies[
|
94
|
+
spies[command_class] = build_spy
|
95
95
|
end # method assign_spy
|
96
96
|
|
97
97
|
def build_spy
|
98
98
|
Cuprum::Utils::InstanceSpy::Spy.new
|
99
99
|
end # method build_spy
|
100
100
|
|
101
|
-
def call_spies_for
|
102
|
-
spies_for(
|
101
|
+
def call_spies_for command, *args, &block
|
102
|
+
spies_for(command).each { |spy| spy.call(*args, &block) }
|
103
103
|
end # method call_spies_for
|
104
104
|
|
105
|
-
def guard_spy_class!
|
106
|
-
return if
|
105
|
+
def guard_spy_class! command_class
|
106
|
+
return if command_class.is_a?(Module) && !command_class.is_a?(Class)
|
107
107
|
|
108
|
-
return if
|
109
|
-
|
108
|
+
return if command_class.is_a?(Class) &&
|
109
|
+
command_class <= Cuprum::Command
|
110
110
|
|
111
111
|
raise ArgumentError,
|
112
112
|
'must be a class inheriting from Cuprum::Command',
|
@@ -123,8 +123,8 @@ module Cuprum::Utils
|
|
123
123
|
Thread.current[:cuprum_instance_spies] ||= {}
|
124
124
|
end # method spies
|
125
125
|
|
126
|
-
def spies_for
|
127
|
-
spies.select { |mod, _|
|
126
|
+
def spies_for command
|
127
|
+
spies.select { |mod, _| command.is_a?(mod) }.map { |_, spy| spy }
|
128
128
|
end # method spies_for
|
129
129
|
end # eigenclass
|
130
130
|
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'cuprum/utils'
|
2
|
+
|
3
|
+
module Cuprum::Utils
|
4
|
+
# Helper class for building a warning message when a command returns a result,
|
5
|
+
# but the command's current result already has errors, a set status, or is
|
6
|
+
# halted.
|
7
|
+
class ResultNotEmptyWarning
|
8
|
+
MESSAGE = '#process returned a result, but '.freeze
|
9
|
+
private_constant :MESSAGE
|
10
|
+
|
11
|
+
# @param result [Cuprum::Result] The result for which to generate the
|
12
|
+
# warning message.
|
13
|
+
def initialize result
|
14
|
+
@result = result
|
15
|
+
end # constructor
|
16
|
+
|
17
|
+
# @return [String] The warning message for the given result.
|
18
|
+
def message
|
19
|
+
# byebug
|
20
|
+
MESSAGE + humanize_list(warnings).freeze
|
21
|
+
end # method message
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
attr_reader :result
|
26
|
+
|
27
|
+
def errors_not_empty_warning
|
28
|
+
return nil if result.errors.empty?
|
29
|
+
|
30
|
+
"there were already errors #{@result.errors.inspect}".freeze
|
31
|
+
end # method errors_not_empty_warning
|
32
|
+
|
33
|
+
def halted_warning
|
34
|
+
return nil unless result.halted?
|
35
|
+
|
36
|
+
'the command was halted'.freeze
|
37
|
+
end # method halted_warning
|
38
|
+
|
39
|
+
def humanize_list list, empty_value: ''
|
40
|
+
return empty_value if list.size.zero?
|
41
|
+
|
42
|
+
return list.first.to_s if list.size == 1
|
43
|
+
|
44
|
+
return "#{list.first} and #{list.last}" if list.size == 2
|
45
|
+
|
46
|
+
"#{list[0...-1].join ', '}, and #{list.last}"
|
47
|
+
end # method humanize_list
|
48
|
+
|
49
|
+
def status_set_warning
|
50
|
+
status = result.send(:status)
|
51
|
+
|
52
|
+
return nil if status.nil?
|
53
|
+
|
54
|
+
"the status was set to #{status.inspect}".freeze
|
55
|
+
end # method status_set_warning
|
56
|
+
|
57
|
+
def warnings
|
58
|
+
[
|
59
|
+
errors_not_empty_warning,
|
60
|
+
status_set_warning,
|
61
|
+
halted_warning
|
62
|
+
].compact
|
63
|
+
end # method warnings
|
64
|
+
end # class
|
65
|
+
end # module
|
data/lib/cuprum/version.rb
CHANGED
@@ -8,13 +8,13 @@ module Cuprum
|
|
8
8
|
# Major version.
|
9
9
|
MAJOR = 0
|
10
10
|
# Minor version.
|
11
|
-
MINOR =
|
11
|
+
MINOR = 7
|
12
12
|
# Patch version.
|
13
13
|
PATCH = 0
|
14
14
|
# Prerelease version.
|
15
|
-
PRERELEASE =
|
15
|
+
PRERELEASE = :rc
|
16
16
|
# Build metadata.
|
17
|
-
BUILD =
|
17
|
+
BUILD = 0
|
18
18
|
|
19
19
|
class << self
|
20
20
|
# Generates the gem version string from the Version constants.
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cuprum
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.0.rc.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rob "Merlin" Smith
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2018-03-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rspec
|
@@ -94,7 +94,6 @@ files:
|
|
94
94
|
- LICENSE
|
95
95
|
- README.md
|
96
96
|
- lib/cuprum.rb
|
97
|
-
- lib/cuprum/basic_command.rb
|
98
97
|
- lib/cuprum/built_in.rb
|
99
98
|
- lib/cuprum/built_in/identity_command.rb
|
100
99
|
- lib/cuprum/built_in/identity_operation.rb
|
@@ -104,9 +103,12 @@ files:
|
|
104
103
|
- lib/cuprum/command.rb
|
105
104
|
- lib/cuprum/not_implemented_error.rb
|
106
105
|
- lib/cuprum/operation.rb
|
106
|
+
- lib/cuprum/processing.rb
|
107
107
|
- lib/cuprum/result.rb
|
108
|
+
- lib/cuprum/result_helpers.rb
|
108
109
|
- lib/cuprum/utils.rb
|
109
110
|
- lib/cuprum/utils/instance_spy.rb
|
111
|
+
- lib/cuprum/utils/result_not_empty_warning.rb
|
110
112
|
- lib/cuprum/version.rb
|
111
113
|
homepage: http://sleepingkingstudios.com
|
112
114
|
licenses:
|
@@ -123,9 +125,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
123
125
|
version: '0'
|
124
126
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
125
127
|
requirements:
|
126
|
-
- - "
|
128
|
+
- - ">"
|
127
129
|
- !ruby/object:Gem::Version
|
128
|
-
version:
|
130
|
+
version: 1.3.1
|
129
131
|
requirements: []
|
130
132
|
rubyforge_project:
|
131
133
|
rubygems_version: 2.6.13
|
data/lib/cuprum/basic_command.rb
DELETED
@@ -1,212 +0,0 @@
|
|
1
|
-
require 'cuprum/not_implemented_error'
|
2
|
-
|
3
|
-
module Cuprum
|
4
|
-
# Functional object that encapsulates a business logic operation with a
|
5
|
-
# standardized interface and returns a result object.
|
6
|
-
class BasicCommand
|
7
|
-
# Returns a new instance of Cuprum::BasicCommand.
|
8
|
-
#
|
9
|
-
# @yield [*arguments, **keywords, &block] If a block is given, the
|
10
|
-
# #call method will wrap the block and set the result #value to the return
|
11
|
-
# value of the block. This overrides the implementation in #process, if
|
12
|
-
# any.
|
13
|
-
def initialize &implementation
|
14
|
-
define_singleton_method :process, &implementation if implementation
|
15
|
-
end # method initialize
|
16
|
-
|
17
|
-
# @overload call(*arguments, **keywords, &block)
|
18
|
-
# Executes the logic encoded in the constructor block, or the #process
|
19
|
-
# method if no block was passed to the constructor, and returns a
|
20
|
-
# Cuprum::Result object with the return value of the block or #process,
|
21
|
-
# the success or failure status, and any errors generated.
|
22
|
-
#
|
23
|
-
# @param arguments [Array] Arguments to be passed to the implementation.
|
24
|
-
#
|
25
|
-
# @param keywords [Hash] Keywords to be passed to the implementation.
|
26
|
-
#
|
27
|
-
# @return [Cuprum::Result] The result object for the command.
|
28
|
-
#
|
29
|
-
# @yield If a block argument is given, it will be passed to the
|
30
|
-
# implementation.
|
31
|
-
#
|
32
|
-
# @raise [Cuprum::NotImplementedError] Unless a block was passed to the
|
33
|
-
# constructor or the #process method was overriden by a Command
|
34
|
-
# subclass.
|
35
|
-
def call *args, &block
|
36
|
-
wrap_result { |result| merge_results(result, process(*args, &block)) }
|
37
|
-
end # method call
|
38
|
-
|
39
|
-
private
|
40
|
-
|
41
|
-
# @!visibility public
|
42
|
-
#
|
43
|
-
# Generates an empty errors object. When the function is called, the result
|
44
|
-
# will have its #errors property initialized to the value returned by
|
45
|
-
# #build_errors. By default, this is an array. If you want to use a custom
|
46
|
-
# errors object type, override this method in a subclass.
|
47
|
-
#
|
48
|
-
# @return [Array] An empty errors object.
|
49
|
-
def build_errors
|
50
|
-
[]
|
51
|
-
end # method build_errors
|
52
|
-
|
53
|
-
def convert_value_to_result value
|
54
|
-
return nil unless value_is_result?(value)
|
55
|
-
|
56
|
-
if value.respond_to?(:result) && value_is_result?(value.result)
|
57
|
-
return value.result
|
58
|
-
end # if
|
59
|
-
|
60
|
-
value
|
61
|
-
end # method convert_value_to_result
|
62
|
-
|
63
|
-
# @!visibility public
|
64
|
-
#
|
65
|
-
# Provides a reference to the current result's errors object. Messages or
|
66
|
-
# error objects added to this will be included in the #errors method of the
|
67
|
-
# returned result object.
|
68
|
-
#
|
69
|
-
# @return [Array, Object] The errors object.
|
70
|
-
#
|
71
|
-
# @see Cuprum::Result#errors.
|
72
|
-
#
|
73
|
-
# @note This is a private method, and only available when executing the
|
74
|
-
# function implementation as defined in the constructor block or the
|
75
|
-
# #process method.
|
76
|
-
def errors
|
77
|
-
@result&.errors
|
78
|
-
end # method errors
|
79
|
-
|
80
|
-
# @!visibility public
|
81
|
-
#
|
82
|
-
# Marks the current result as failed. Calling #failure? on the returned
|
83
|
-
# result object will evaluate to true, whether or not the result has any
|
84
|
-
# errors.
|
85
|
-
#
|
86
|
-
# @see Cuprum::Result#failure!.
|
87
|
-
#
|
88
|
-
# @note This is a private method, and only available when executing the
|
89
|
-
# function implementation as defined in the constructor block or the
|
90
|
-
# #process method.
|
91
|
-
def failure!
|
92
|
-
@result&.failure!
|
93
|
-
end # method failure!
|
94
|
-
|
95
|
-
# @!visibility public
|
96
|
-
#
|
97
|
-
# Marks the current result as halted.
|
98
|
-
#
|
99
|
-
# @see Cuprum::Result#halt!.
|
100
|
-
#
|
101
|
-
# @note This is a private method, and only available when executing the
|
102
|
-
# function implementation as defined in the constructor block or the
|
103
|
-
# #process method.
|
104
|
-
def halt!
|
105
|
-
@result&.halt!
|
106
|
-
end # method halt!
|
107
|
-
|
108
|
-
# :nocov:
|
109
|
-
def humanize_list list, empty_value: ''
|
110
|
-
return empty_value if list.size.zero?
|
111
|
-
|
112
|
-
return list.first.to_s if list.size == 1
|
113
|
-
|
114
|
-
return "#{list.first} and #{list.last}" if list.size == 2
|
115
|
-
|
116
|
-
"#{list[0...-1].join ', '}, and #{list.last}"
|
117
|
-
end # method humanize_list
|
118
|
-
# :nocov:
|
119
|
-
|
120
|
-
def merge_results result, other
|
121
|
-
if value_is_result?(other)
|
122
|
-
Cuprum.warn(result_not_empty_warning) unless result.empty?
|
123
|
-
|
124
|
-
convert_value_to_result(other)
|
125
|
-
else
|
126
|
-
result.value = other
|
127
|
-
|
128
|
-
result
|
129
|
-
end # if-else
|
130
|
-
end # method merge_results
|
131
|
-
|
132
|
-
# @!visibility public
|
133
|
-
# @overload process(*arguments, **keywords, &block)
|
134
|
-
# The implementation of the function, to be executed when the #call method
|
135
|
-
# is called. Can add errors to or set the status of the result, and the
|
136
|
-
# value of the result will be set to the value returned by #process. Do
|
137
|
-
# not call this method directly.
|
138
|
-
#
|
139
|
-
# @param arguments [Array] The arguments, if any, passed from #call.
|
140
|
-
#
|
141
|
-
# @param keywords [Hash] The keywords, if any, passed from #call.
|
142
|
-
#
|
143
|
-
# @yield The block, if any, passed from #call.
|
144
|
-
#
|
145
|
-
# @return [Object] the value of the result object to be returned by #call.
|
146
|
-
#
|
147
|
-
# @raise [Cuprum::NotImplementedError] Unless a block was passed to the
|
148
|
-
# constructor or the #process method was overriden by a Command
|
149
|
-
# subclass.
|
150
|
-
#
|
151
|
-
# @note This is a private method.
|
152
|
-
def process *_args
|
153
|
-
raise Cuprum::NotImplementedError, nil, caller(1..-1)
|
154
|
-
end # method process
|
155
|
-
|
156
|
-
def result_not_empty_warning # rubocop:disable Metrics/MethodLength
|
157
|
-
warnings = []
|
158
|
-
|
159
|
-
unless @result.errors.empty?
|
160
|
-
warnings << "there were already errors #{@result.errors.inspect}"
|
161
|
-
end # unless
|
162
|
-
|
163
|
-
status = @result.send(:status)
|
164
|
-
unless status.nil?
|
165
|
-
warnings << "the status was set to #{status.inspect}"
|
166
|
-
end # unless
|
167
|
-
|
168
|
-
if @result.halted?
|
169
|
-
warnings << 'the function was halted'
|
170
|
-
end # if
|
171
|
-
|
172
|
-
message = '#process returned a result, but '
|
173
|
-
message <<
|
174
|
-
humanize_list(warnings, :empty_value => 'the result was not empty')
|
175
|
-
|
176
|
-
message
|
177
|
-
end # method result_not_empty_warning
|
178
|
-
|
179
|
-
# @!visibility public
|
180
|
-
#
|
181
|
-
# Marks the current result as passing. Calling #success? on the returned
|
182
|
-
# result object will evaluate to true, whether or not the result has any
|
183
|
-
# errors.
|
184
|
-
#
|
185
|
-
# @see Cuprum::Result#success!.
|
186
|
-
#
|
187
|
-
# @note This is a private method, and only available when executing the
|
188
|
-
# function implementation as defined in the constructor block or the
|
189
|
-
# #process method.
|
190
|
-
def success!
|
191
|
-
@result&.success!
|
192
|
-
end # method success!
|
193
|
-
|
194
|
-
def value_is_result? value
|
195
|
-
value.respond_to?(:value) && value.respond_to?(:success?)
|
196
|
-
end # method value
|
197
|
-
|
198
|
-
def wrap_result
|
199
|
-
result = Cuprum::Result.new(:errors => build_errors)
|
200
|
-
|
201
|
-
begin
|
202
|
-
@result = result
|
203
|
-
|
204
|
-
result = yield result
|
205
|
-
ensure
|
206
|
-
@result = nil
|
207
|
-
end # begin-ensure
|
208
|
-
|
209
|
-
result
|
210
|
-
end # method wrap_result
|
211
|
-
end # class
|
212
|
-
end # module
|