cuprum 0.9.1 → 0.10.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 +20 -0
- data/DEVELOPMENT.md +53 -47
- data/README.md +283 -483
- data/lib/cuprum.rb +1 -0
- data/lib/cuprum/chaining.rb +22 -1
- data/lib/cuprum/command.rb +8 -1
- data/lib/cuprum/command_factory.rb +43 -19
- data/lib/cuprum/currying.rb +78 -0
- data/lib/cuprum/currying/curried_command.rb +109 -0
- data/lib/cuprum/operation.rb +1 -1
- data/lib/cuprum/processing.rb +10 -14
- data/lib/cuprum/result.rb +1 -1
- data/lib/cuprum/result_helpers.rb +22 -0
- data/lib/cuprum/rspec/be_a_result_matcher.rb +3 -3
- data/lib/cuprum/steps.rb +275 -0
- data/lib/cuprum/utils/instance_spy.rb +9 -2
- data/lib/cuprum/version.rb +4 -4
- metadata +11 -7
data/lib/cuprum/result.rb
CHANGED
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cuprum'
|
4
|
+
|
5
|
+
module Cuprum
|
6
|
+
# Helper methods for generating Cuprum result objects.
|
7
|
+
module ResultHelpers
|
8
|
+
private
|
9
|
+
|
10
|
+
def build_result(error: nil, status: nil, value: nil)
|
11
|
+
Cuprum::Result.new(error: error, status: status, value: value)
|
12
|
+
end
|
13
|
+
|
14
|
+
def failure(error)
|
15
|
+
build_result(error: error)
|
16
|
+
end
|
17
|
+
|
18
|
+
def success(value)
|
19
|
+
build_result(value: value)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -191,7 +191,7 @@ module Cuprum::RSpec
|
|
191
191
|
ary << 'error' if expected_error? && !expected_error.nil?
|
192
192
|
|
193
193
|
unless ary.empty?
|
194
|
-
msg = "with the expected #{tools.
|
194
|
+
msg = "with the expected #{tools.array_tools.humanize_list(ary)}"
|
195
195
|
end
|
196
196
|
|
197
197
|
return msg unless expected_status?
|
@@ -220,8 +220,8 @@ module Cuprum::RSpec
|
|
220
220
|
ary << 'value' unless value_matches?
|
221
221
|
ary << 'error' unless error_matches?
|
222
222
|
|
223
|
-
", but the #{tools.
|
224
|
-
" #{tools.
|
223
|
+
", but the #{tools.array_tools.humanize_list(ary)}" \
|
224
|
+
" #{tools.integer_tools.pluralize(ary.size, 'does', 'do')} not match:"
|
225
225
|
end
|
226
226
|
|
227
227
|
def properties_warning
|
data/lib/cuprum/steps.rb
ADDED
@@ -0,0 +1,275 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cuprum/result_helpers'
|
4
|
+
|
5
|
+
module Cuprum
|
6
|
+
# The Steps supports step by step processes that halt on a failed step.
|
7
|
+
#
|
8
|
+
# After including Cuprum::Steps, use the #steps instance method to wrap a
|
9
|
+
# series of instructions. Each instruction is then defined using the #step
|
10
|
+
# method. Steps can be defined either as a block or as a method invocation.
|
11
|
+
#
|
12
|
+
# When the steps block is evaluated, each step is called in sequence. If the
|
13
|
+
# step resolves to a passing result, the result value is returned and
|
14
|
+
# execution continues to the next step. If all of the steps pass, then the
|
15
|
+
# result of the final step is returned from the #steps block.
|
16
|
+
#
|
17
|
+
# Conversely, if any step resolves to a failing result, that failing result is
|
18
|
+
# immediately returned from the #steps block. No further steps will be called.
|
19
|
+
#
|
20
|
+
# For example, consider updating a database record using a primary key and an
|
21
|
+
# attributes hash. Broken down into its basics, this requires the following
|
22
|
+
# instructions:
|
23
|
+
#
|
24
|
+
# - Using the primary key, find the existing record in the database.
|
25
|
+
# - Update the record object with the given attributes.
|
26
|
+
# - Save the updated record back to the database.
|
27
|
+
#
|
28
|
+
# Note that each of these steps can fail for different reasons. For example,
|
29
|
+
# if a record with the given primary key does not exist in the database, then
|
30
|
+
# the first instruction will fail, and the follow up steps should not be
|
31
|
+
# executed. Further, whatever context is executing these steps probably wants
|
32
|
+
# to know which step failed, and why.
|
33
|
+
#
|
34
|
+
# @example Defining Methods As Steps
|
35
|
+
# def assign_attributes(record, attributes); end
|
36
|
+
#
|
37
|
+
# def find_record(primary_key); end
|
38
|
+
#
|
39
|
+
# def save_record(record); end
|
40
|
+
#
|
41
|
+
# def update_record(primary_key, attributes)
|
42
|
+
# steps do
|
43
|
+
# record = step :find_record, primary_key
|
44
|
+
# record = step :assign_attributes, record, attributes
|
45
|
+
# step :save_record, record
|
46
|
+
# end
|
47
|
+
# end
|
48
|
+
#
|
49
|
+
# @example Defining Blocks As Steps
|
50
|
+
# class AssignAttributes < Cuprum::Command; end
|
51
|
+
#
|
52
|
+
# class FindRecord < Cuprum::Command; end
|
53
|
+
#
|
54
|
+
# class SaveRecord < Cuprum::Command; end
|
55
|
+
#
|
56
|
+
# def update_record(primary_key, attributes)
|
57
|
+
# steps do
|
58
|
+
# record = step { FindRecord.new.call(primary_key) }
|
59
|
+
# record = step { AssignAttributes.new.call(record, attributes) }
|
60
|
+
# step { SaveRecord.new.call(record) }
|
61
|
+
# end
|
62
|
+
# end
|
63
|
+
module Steps
|
64
|
+
include Cuprum::ResultHelpers
|
65
|
+
|
66
|
+
class << self
|
67
|
+
# @!visibility private
|
68
|
+
def execute_method(receiver, method_name, *args, **kwargs, &block)
|
69
|
+
if block_given? && kwargs.empty?
|
70
|
+
receiver.send(method_name, *args, &block)
|
71
|
+
elsif block_given?
|
72
|
+
receiver.send(method_name, *args, **kwargs, &block)
|
73
|
+
elsif kwargs.empty?
|
74
|
+
receiver.send(method_name, *args)
|
75
|
+
else
|
76
|
+
receiver.send(method_name, *args, **kwargs)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# @!visibility private
|
81
|
+
def extract_result_value(result)
|
82
|
+
return result unless result.respond_to?(:to_cuprum_result)
|
83
|
+
|
84
|
+
result = result.to_cuprum_result
|
85
|
+
|
86
|
+
return result.value if result.success?
|
87
|
+
|
88
|
+
throw :cuprum_failed_step, result
|
89
|
+
end
|
90
|
+
|
91
|
+
# rubocop:disable Metrics/MethodLength
|
92
|
+
# @!visibility private
|
93
|
+
def validate_method_name(method_name)
|
94
|
+
if method_name.nil?
|
95
|
+
raise ArgumentError,
|
96
|
+
'expected a block or a method name',
|
97
|
+
caller(1..-1)
|
98
|
+
end
|
99
|
+
|
100
|
+
unless method_name.is_a?(String) || method_name.is_a?(Symbol)
|
101
|
+
raise ArgumentError,
|
102
|
+
'expected method name to be a String or Symbol',
|
103
|
+
caller(1..-1)
|
104
|
+
end
|
105
|
+
|
106
|
+
return unless method_name.empty?
|
107
|
+
|
108
|
+
raise ArgumentError, "method name can't be blank", caller(1..-1)
|
109
|
+
end
|
110
|
+
# rubocop:enable Metrics/MethodLength
|
111
|
+
end
|
112
|
+
|
113
|
+
# @overload step()
|
114
|
+
# Executes the block and returns the value, or halts on a failure.
|
115
|
+
#
|
116
|
+
# @yield Called with no parameters.
|
117
|
+
#
|
118
|
+
# @return [Object] the #value of the result, or the returned object.
|
119
|
+
#
|
120
|
+
# The #step method is used to evaluate a sequence of processes, and to
|
121
|
+
# fail fast and halt processing if any of the steps returns a failing
|
122
|
+
# result. Each invocation of #step should be wrapped in a #steps block,
|
123
|
+
# or used inside the #process method of a Command.
|
124
|
+
#
|
125
|
+
# If the object returned by the block is a Cuprum result or compatible
|
126
|
+
# object (such as a called operation), the value is converted to a Cuprum
|
127
|
+
# result via the #to_cuprum_result method. Otherwise, the object is
|
128
|
+
# returned directly from #step.
|
129
|
+
#
|
130
|
+
# If the returned object is a passing result, the #value of the result is
|
131
|
+
# returned by #step.
|
132
|
+
#
|
133
|
+
# If the returned object is a failing result, then #step will throw
|
134
|
+
# :cuprum_failed_result and the failing result. This is caught by the
|
135
|
+
# #steps block, and halts execution of any subsequent steps.
|
136
|
+
#
|
137
|
+
# @example Calling a Step
|
138
|
+
# # The #do_something method returns the string 'some value'.
|
139
|
+
# step { do_something() } #=> 'some value'
|
140
|
+
#
|
141
|
+
# value = step { do_something() }
|
142
|
+
# value #=> 'some value'
|
143
|
+
#
|
144
|
+
# @example Calling a Step with a Passing Result
|
145
|
+
# # The #do_something_else method returns a Cuprum result with a value
|
146
|
+
# # of 'another value'.
|
147
|
+
# step { do_something_else() } #=> 'another value'
|
148
|
+
#
|
149
|
+
# # The result is passing, so the value is extracted and returned.
|
150
|
+
# value = step { do_something_else() }
|
151
|
+
# value #=> 'another value'
|
152
|
+
#
|
153
|
+
# @example Calling a Step with a Failing Result
|
154
|
+
# # The #do_something_wrong method returns a failing Cuprum result.
|
155
|
+
# step { do_something_wrong() } # Throws the :cuprum_failed_step symbol.
|
156
|
+
#
|
157
|
+
# @overload step(method_name, *arguments, **keywords)
|
158
|
+
# Calls the method and returns the value, or halts on a failure.
|
159
|
+
#
|
160
|
+
# @param method_name [String, Symbol] The name of the method to call. Must
|
161
|
+
# be the name of a method on the current object.
|
162
|
+
# @param arguments [Array] Positional arguments to pass to the method.
|
163
|
+
# @param keywords [Hash] Keyword arguments to pass to the method.
|
164
|
+
#
|
165
|
+
# @yield A block to pass to the method.
|
166
|
+
#
|
167
|
+
# @return [Object] the #value of the result, or the returned object.
|
168
|
+
#
|
169
|
+
# The #step method is used to evaluate a sequence of processes, and to
|
170
|
+
# fail fast and halt processing if any of the steps returns a failing
|
171
|
+
# result. Each invocation of #step should be wrapped in a #steps block,
|
172
|
+
# or used inside the #process method of a Command.
|
173
|
+
#
|
174
|
+
# If the object returned by the block is a Cuprum result or compatible
|
175
|
+
# object (such as a called operation), the value is converted to a Cuprum
|
176
|
+
# result via the #to_cuprum_result method. Otherwise, the object is
|
177
|
+
# returned directly from #step.
|
178
|
+
#
|
179
|
+
# If the returned object is a passing result, the #value of the result is
|
180
|
+
# returned by #step.
|
181
|
+
#
|
182
|
+
# If the returned object is a failing result, then #step will throw
|
183
|
+
# :cuprum_failed_result and the failing result. This is caught by the
|
184
|
+
# #steps block, and halts execution of any subsequent steps.
|
185
|
+
#
|
186
|
+
# @example Calling a Step
|
187
|
+
# # The #zero method returns the integer 0.
|
188
|
+
# step :zero #=> 0
|
189
|
+
#
|
190
|
+
# value = step :zero
|
191
|
+
# value #=> 0
|
192
|
+
#
|
193
|
+
# @example Calling a Step with a Passing Result
|
194
|
+
# # The #add method adds the numbers and returns a Cuprum result with a
|
195
|
+
# # value equal to the sum.
|
196
|
+
# step :add, 2, 2
|
197
|
+
# #=> 4
|
198
|
+
#
|
199
|
+
# # The result is passing, so the value is extracted and returned.
|
200
|
+
# value = step :add, 2, 2
|
201
|
+
# value #=> 4
|
202
|
+
#
|
203
|
+
# @example Calling a Step with a Failing Result
|
204
|
+
# # The #divide method returns a failing Cuprum result when the second
|
205
|
+
# # argument is zero.
|
206
|
+
# step :divide, 1, 0
|
207
|
+
# # Throws the :cuprum_failed_step symbol, which should be caught by the
|
208
|
+
# # enclosing #steps block.
|
209
|
+
def step(method_name = nil, *args, **kwargs, &block)
|
210
|
+
result =
|
211
|
+
if !block_given? || method_name || !args.empty? || !kwargs.empty?
|
212
|
+
Cuprum::Steps.validate_method_name(method_name)
|
213
|
+
|
214
|
+
Cuprum::Steps
|
215
|
+
.execute_method(self, method_name, *args, **kwargs, &block)
|
216
|
+
else
|
217
|
+
block.call
|
218
|
+
end
|
219
|
+
|
220
|
+
Cuprum::Steps.extract_result_value(result)
|
221
|
+
end
|
222
|
+
|
223
|
+
# Returns the first failing #step result, or the final result if none fail.
|
224
|
+
#
|
225
|
+
# The #steps method is used to wrap a series of #step calls. Each step is
|
226
|
+
# executed in sequence. If any of the steps returns a failing result, that
|
227
|
+
# result is immediately returned from #steps. Otherwise, #steps wraps the
|
228
|
+
# value returned by a block in a Cuprum result.
|
229
|
+
#
|
230
|
+
# @yield Called with no parameters.
|
231
|
+
#
|
232
|
+
# @yieldreturn A Cuprum result, or an object to be wrapped in a result.
|
233
|
+
#
|
234
|
+
# @return [Cuprum::Result] the result or object returned by the block,
|
235
|
+
# wrapped in a Cuprum result.
|
236
|
+
#
|
237
|
+
# @example With A Passing Step
|
238
|
+
# result = steps do
|
239
|
+
# step { success('some value') }
|
240
|
+
# end
|
241
|
+
# result.class #=> Cuprum::Result
|
242
|
+
# result.success? #=> true
|
243
|
+
# result.value #=> 'some value'
|
244
|
+
#
|
245
|
+
# @example With A Failing Step
|
246
|
+
# result = steps do
|
247
|
+
# step { failure('something went wrong') }
|
248
|
+
# end
|
249
|
+
# result.class #=> Cuprum::Result
|
250
|
+
# result.success? #=> false
|
251
|
+
# result.error #=> 'something went wrong'
|
252
|
+
#
|
253
|
+
# @example With Multiple Steps
|
254
|
+
# result = steps do
|
255
|
+
# # This step is passing, so execution continues on to the next step.
|
256
|
+
# step { success('first step') }
|
257
|
+
#
|
258
|
+
# # This step is failing, so execution halts and returns this result.
|
259
|
+
# step { failure('second step') }
|
260
|
+
#
|
261
|
+
# # This step will never be called.
|
262
|
+
# step { success('third step') }
|
263
|
+
# end
|
264
|
+
# result.class #=> Cuprum::Result
|
265
|
+
# result.success? #=> false
|
266
|
+
# result.error #=> 'second step'
|
267
|
+
def steps
|
268
|
+
result = catch(:cuprum_failed_step) { yield }
|
269
|
+
|
270
|
+
return result if result.respond_to?(:to_cuprum_result)
|
271
|
+
|
272
|
+
success(result)
|
273
|
+
end
|
274
|
+
end
|
275
|
+
end
|
@@ -129,8 +129,15 @@ module Cuprum::Utils
|
|
129
129
|
end # eigenclass
|
130
130
|
|
131
131
|
# (see Cuprum::Command#call)
|
132
|
-
def call *args, &block
|
133
|
-
|
132
|
+
def call *args, **kwargs, &block
|
133
|
+
if kwargs.empty?
|
134
|
+
Cuprum::Utils::InstanceSpy.send(:call_spies_for, self, *args, &block)
|
135
|
+
else
|
136
|
+
# :nocov:
|
137
|
+
Cuprum::Utils::InstanceSpy
|
138
|
+
.send(:call_spies_for, self, *args, **kwargs, &block)
|
139
|
+
# :nocov:
|
140
|
+
end
|
134
141
|
|
135
142
|
super
|
136
143
|
end # method call
|
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 = 10
|
12
12
|
# Patch version.
|
13
|
-
PATCH =
|
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.10.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: 2020-08-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: sleeping_king_studios-tools
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '0.
|
19
|
+
version: '0.8'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '0.
|
26
|
+
version: '0.8'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: rspec
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -116,6 +116,8 @@ files:
|
|
116
116
|
- lib/cuprum/chaining.rb
|
117
117
|
- lib/cuprum/command.rb
|
118
118
|
- lib/cuprum/command_factory.rb
|
119
|
+
- lib/cuprum/currying.rb
|
120
|
+
- lib/cuprum/currying/curried_command.rb
|
119
121
|
- lib/cuprum/error.rb
|
120
122
|
- lib/cuprum/errors.rb
|
121
123
|
- lib/cuprum/errors/command_not_implemented.rb
|
@@ -123,9 +125,11 @@ files:
|
|
123
125
|
- lib/cuprum/operation.rb
|
124
126
|
- lib/cuprum/processing.rb
|
125
127
|
- lib/cuprum/result.rb
|
128
|
+
- lib/cuprum/result_helpers.rb
|
126
129
|
- lib/cuprum/rspec.rb
|
127
130
|
- lib/cuprum/rspec/be_a_result.rb
|
128
131
|
- lib/cuprum/rspec/be_a_result_matcher.rb
|
132
|
+
- lib/cuprum/steps.rb
|
129
133
|
- lib/cuprum/utils.rb
|
130
134
|
- lib/cuprum/utils/instance_spy.rb
|
131
135
|
- lib/cuprum/version.rb
|
@@ -144,11 +148,11 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
144
148
|
version: '0'
|
145
149
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
146
150
|
requirements:
|
147
|
-
- - "
|
151
|
+
- - ">"
|
148
152
|
- !ruby/object:Gem::Version
|
149
|
-
version:
|
153
|
+
version: 1.3.1
|
150
154
|
requirements: []
|
151
|
-
rubygems_version: 3.
|
155
|
+
rubygems_version: 3.1.2
|
152
156
|
signing_key:
|
153
157
|
specification_version: 4
|
154
158
|
summary: An opinionated implementation of the Command pattern.
|