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