cuprum 0.8.0 → 0.10.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 +5 -5
- data/CHANGELOG.md +85 -9
- data/DEVELOPMENT.md +58 -42
- data/README.md +414 -754
- data/lib/cuprum.rb +1 -25
- data/lib/cuprum/built_in/identity_command.rb +4 -4
- data/lib/cuprum/chaining.rb +139 -148
- data/lib/cuprum/command.rb +24 -15
- 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/error.rb +37 -0
- data/lib/cuprum/errors/command_not_implemented.rb +35 -0
- data/lib/cuprum/errors/operation_not_called.rb +35 -0
- data/lib/cuprum/operation.rb +37 -28
- data/lib/cuprum/processing.rb +45 -82
- data/lib/cuprum/result.rb +52 -127
- data/lib/cuprum/result_helpers.rb +14 -105
- data/lib/cuprum/rspec.rb +8 -0
- data/lib/cuprum/rspec/be_a_result.rb +19 -0
- data/lib/cuprum/rspec/be_a_result_matcher.rb +286 -0
- data/lib/cuprum/steps.rb +275 -0
- data/lib/cuprum/utils/instance_spy.rb +9 -2
- data/lib/cuprum/version.rb +1 -1
- metadata +14 -8
- data/lib/cuprum/errors/process_not_implemented_error.rb +0 -14
- data/lib/cuprum/utils/result_not_empty_warning.rb +0 -72
@@ -1,113 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'cuprum'
|
2
4
|
|
3
5
|
module Cuprum
|
4
|
-
# Helper methods
|
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
|
6
|
+
# Helper methods for generating Cuprum result objects.
|
50
7
|
module ResultHelpers
|
51
8
|
private
|
52
9
|
|
53
|
-
|
54
|
-
|
55
|
-
|
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!
|
10
|
+
def build_result(error: nil, status: nil, value: nil)
|
11
|
+
Cuprum::Result.new(error: error, status: status, value: value)
|
12
|
+
end
|
84
13
|
|
85
|
-
|
86
|
-
|
87
|
-
|
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!
|
14
|
+
def failure(error)
|
15
|
+
build_result(error: error)
|
16
|
+
end
|
97
17
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
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
|
18
|
+
def success(value)
|
19
|
+
build_result(value: value)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/cuprum/rspec.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cuprum/rspec/be_a_result_matcher'
|
4
|
+
|
5
|
+
module RSpec
|
6
|
+
module Matchers # rubocop:disable Style/Documentation
|
7
|
+
def be_a_failing_result
|
8
|
+
be_a_result.with_status(:failure)
|
9
|
+
end
|
10
|
+
|
11
|
+
def be_a_passing_result
|
12
|
+
be_a_result.with_status(:success).and_error(nil)
|
13
|
+
end
|
14
|
+
|
15
|
+
def be_a_result
|
16
|
+
Cuprum::RSpec::BeAResultMatcher.new
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,286 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cuprum/errors/operation_not_called'
|
4
|
+
require 'cuprum/rspec'
|
5
|
+
|
6
|
+
module Cuprum::RSpec
|
7
|
+
# Custom matcher that asserts the actual object is a Cuprum result object with
|
8
|
+
# the specified properties.
|
9
|
+
class BeAResultMatcher # rubocop:disable Metrics/ClassLength
|
10
|
+
DEFAULT_VALUE = Object.new.freeze
|
11
|
+
private_constant :DEFAULT_VALUE
|
12
|
+
|
13
|
+
RSPEC_MATCHER_METHODS = %i[description failure_message matches?].freeze
|
14
|
+
private_constant :RSPEC_MATCHER_METHODS
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
@expected_error = DEFAULT_VALUE
|
18
|
+
@expected_value = DEFAULT_VALUE
|
19
|
+
end
|
20
|
+
|
21
|
+
# @return [String] a short description of the matcher and expected
|
22
|
+
# properties.
|
23
|
+
def description
|
24
|
+
message = 'be a Cuprum result'
|
25
|
+
|
26
|
+
return message unless expected_properties?
|
27
|
+
|
28
|
+
"#{message} #{properties_description}"
|
29
|
+
end
|
30
|
+
|
31
|
+
# Checks that the given actual object is not a Cuprum result.
|
32
|
+
#
|
33
|
+
# @param actual [Object] The actual object to match.
|
34
|
+
#
|
35
|
+
# @return [Boolean] false if the actual object is a result; otherwise true.
|
36
|
+
def does_not_match?(actual)
|
37
|
+
@actual = actual
|
38
|
+
|
39
|
+
raise ArgumentError, negated_matcher_warning if expected_properties?
|
40
|
+
|
41
|
+
!actual_is_result?
|
42
|
+
end
|
43
|
+
|
44
|
+
# @return [String] a summary message describing a failed expectation.
|
45
|
+
def failure_message
|
46
|
+
message = "expected #{actual.inspect} to #{description}"
|
47
|
+
|
48
|
+
if !actual_is_result?
|
49
|
+
message + ', but the object is not a result'
|
50
|
+
elsif actual_is_uncalled_operation?
|
51
|
+
message + ', but the object is an uncalled operation'
|
52
|
+
elsif !properties_match?
|
53
|
+
message + properties_failure_message
|
54
|
+
else
|
55
|
+
# :nocov:
|
56
|
+
message
|
57
|
+
# :nocov:
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# @return [String] a summary message describing a failed negated
|
62
|
+
# expectation.
|
63
|
+
def failure_message_when_negated
|
64
|
+
"expected #{actual.inspect} not to #{description}"
|
65
|
+
end
|
66
|
+
|
67
|
+
# Checks that the given actual object is a Cuprum result or compatible
|
68
|
+
# object and has the specified properties.
|
69
|
+
#
|
70
|
+
# @param actual [Object] The actual object to match.
|
71
|
+
#
|
72
|
+
# @return [Boolean] true if the actual object is a result with the expected
|
73
|
+
# properties; otherwise false.
|
74
|
+
def matches?(actual)
|
75
|
+
@actual = actual
|
76
|
+
|
77
|
+
actual_is_result? && !actual_is_uncalled_operation? && properties_match?
|
78
|
+
end
|
79
|
+
|
80
|
+
# Sets an error expectation on the matcher. Calls to #matches? will fail
|
81
|
+
# unless the actual object has the specified error.
|
82
|
+
#
|
83
|
+
# @param error [Cuprum::Error, Object] The expected error.
|
84
|
+
#
|
85
|
+
# @return [BeAResultMatcher] the updated matcher.
|
86
|
+
def with_error(error)
|
87
|
+
@expected_error = error
|
88
|
+
|
89
|
+
self
|
90
|
+
end
|
91
|
+
alias_method :and_error, :with_error
|
92
|
+
|
93
|
+
# Sets a status expectation on the matcher. Calls to #matches? will fail
|
94
|
+
# unless the actual object has the specified status.
|
95
|
+
#
|
96
|
+
# @param status [Symbol] The expected status.
|
97
|
+
#
|
98
|
+
# @return [BeAResultMatcher] the updated matcher.
|
99
|
+
def with_status(status)
|
100
|
+
@expected_status = status
|
101
|
+
|
102
|
+
self
|
103
|
+
end
|
104
|
+
alias_method :and_status, :with_status
|
105
|
+
|
106
|
+
# Sets a value expectation on the matcher. Calls to #matches? will fail
|
107
|
+
# unless the actual object has the specified value.
|
108
|
+
#
|
109
|
+
# @param value [Object] The expected value.
|
110
|
+
#
|
111
|
+
# @return [BeAResultMatcher] the updated matcher.
|
112
|
+
def with_value(value)
|
113
|
+
@expected_value = value
|
114
|
+
|
115
|
+
self
|
116
|
+
end
|
117
|
+
alias_method :and_value, :with_value
|
118
|
+
|
119
|
+
private
|
120
|
+
|
121
|
+
attr_reader \
|
122
|
+
:actual,
|
123
|
+
:expected_error,
|
124
|
+
:expected_status,
|
125
|
+
:expected_value
|
126
|
+
|
127
|
+
def actual_is_result?
|
128
|
+
actual.respond_to?(:to_cuprum_result)
|
129
|
+
end
|
130
|
+
|
131
|
+
def actual_is_uncalled_operation?
|
132
|
+
result.error.is_a?(Cuprum::Errors::OperationNotCalled)
|
133
|
+
end
|
134
|
+
|
135
|
+
def compare_items(expected, actual)
|
136
|
+
return expected.matches?(actual) if expected.respond_to?(:matches?)
|
137
|
+
|
138
|
+
expected == actual
|
139
|
+
end
|
140
|
+
|
141
|
+
def error_failure_message
|
142
|
+
return '' if error_matches?
|
143
|
+
|
144
|
+
"\n expected error: #{inspect_expected(expected_error)}" \
|
145
|
+
"\n actual error: #{result.error.inspect}"
|
146
|
+
end
|
147
|
+
|
148
|
+
def error_matches?
|
149
|
+
return @error_matches unless @error_matches.nil?
|
150
|
+
|
151
|
+
return @error_matches = true unless expected_error?
|
152
|
+
|
153
|
+
@error_matches = compare_items(expected_error, result.error)
|
154
|
+
end
|
155
|
+
|
156
|
+
def expected_properties?
|
157
|
+
(expected_error? && !expected_error.nil?) ||
|
158
|
+
expected_status? ||
|
159
|
+
expected_value?
|
160
|
+
end
|
161
|
+
|
162
|
+
def expected_error?
|
163
|
+
expected_error != DEFAULT_VALUE
|
164
|
+
end
|
165
|
+
|
166
|
+
def expected_status?
|
167
|
+
!!expected_status
|
168
|
+
end
|
169
|
+
|
170
|
+
def expected_value?
|
171
|
+
expected_value != DEFAULT_VALUE
|
172
|
+
end
|
173
|
+
|
174
|
+
def inspect_expected(expected)
|
175
|
+
return expected.description if rspec_matcher?(expected)
|
176
|
+
|
177
|
+
expected.inspect
|
178
|
+
end
|
179
|
+
|
180
|
+
def negated_matcher_warning
|
181
|
+
"Using `expect().not_to be_a_result#{properties_warning}` risks false" \
|
182
|
+
' positives, since any other result will match.'
|
183
|
+
end
|
184
|
+
|
185
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
186
|
+
# rubocop:disable Metrics/AbcSize
|
187
|
+
def properties_description
|
188
|
+
msg = ''
|
189
|
+
ary = []
|
190
|
+
ary << 'value' if expected_value?
|
191
|
+
ary << 'error' if expected_error? && !expected_error.nil?
|
192
|
+
|
193
|
+
unless ary.empty?
|
194
|
+
msg = "with the expected #{tools.array_tools.humanize_list(ary)}"
|
195
|
+
end
|
196
|
+
|
197
|
+
return msg unless expected_status?
|
198
|
+
|
199
|
+
return "with status: #{expected_status.inspect}" if msg.empty?
|
200
|
+
|
201
|
+
msg + " and status: #{expected_status.inspect}"
|
202
|
+
end
|
203
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
204
|
+
# rubocop:enable Metrics/AbcSize
|
205
|
+
|
206
|
+
def properties_failure_message
|
207
|
+
properties_short_message +
|
208
|
+
status_failure_message +
|
209
|
+
value_failure_message +
|
210
|
+
error_failure_message
|
211
|
+
end
|
212
|
+
|
213
|
+
def properties_match?
|
214
|
+
error_matches? && status_matches? && value_matches?
|
215
|
+
end
|
216
|
+
|
217
|
+
def properties_short_message
|
218
|
+
ary = []
|
219
|
+
ary << 'status' unless status_matches?
|
220
|
+
ary << 'value' unless value_matches?
|
221
|
+
ary << 'error' unless error_matches?
|
222
|
+
|
223
|
+
", but the #{tools.array_tools.humanize_list(ary)}" \
|
224
|
+
" #{tools.integer_tools.pluralize(ary.size, 'does', 'do')} not match:"
|
225
|
+
end
|
226
|
+
|
227
|
+
def properties_warning
|
228
|
+
ary = []
|
229
|
+
ary << 'value' if expected_value?
|
230
|
+
ary << 'status' if expected_status?
|
231
|
+
ary << 'error' if expected_error?
|
232
|
+
|
233
|
+
return '' if ary.empty?
|
234
|
+
|
235
|
+
message = ".with_#{ary.first}()"
|
236
|
+
|
237
|
+
return message if ary.size == 1
|
238
|
+
|
239
|
+
message + ary[1..-1].map { |str| ".and_#{str}()" }.join
|
240
|
+
end
|
241
|
+
|
242
|
+
def result
|
243
|
+
@result ||= actual.to_cuprum_result
|
244
|
+
end
|
245
|
+
|
246
|
+
def rspec_matcher?(value)
|
247
|
+
RSPEC_MATCHER_METHODS.all? do |method_name|
|
248
|
+
value.respond_to?(method_name)
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
def status_failure_message
|
253
|
+
return '' if status_matches?
|
254
|
+
|
255
|
+
"\n expected status: #{expected_status.inspect}" \
|
256
|
+
"\n actual status: #{result.status.inspect}"
|
257
|
+
end
|
258
|
+
|
259
|
+
def status_matches?
|
260
|
+
return @status_matches unless @status_matches.nil?
|
261
|
+
|
262
|
+
return @status_matches = true unless expected_status?
|
263
|
+
|
264
|
+
@status_matches = result.status == expected_status
|
265
|
+
end
|
266
|
+
|
267
|
+
def tools
|
268
|
+
SleepingKingStudios::Tools::Toolbelt.instance
|
269
|
+
end
|
270
|
+
|
271
|
+
def value_failure_message
|
272
|
+
return '' if value_matches?
|
273
|
+
|
274
|
+
"\n expected value: #{inspect_expected(expected_value)}" \
|
275
|
+
"\n actual value: #{result.value.inspect}"
|
276
|
+
end
|
277
|
+
|
278
|
+
def value_matches?
|
279
|
+
return @value_matches unless @value_matches.nil?
|
280
|
+
|
281
|
+
return @value_matches = true unless expected_value?
|
282
|
+
|
283
|
+
@value_matches = compare_items(expected_value, result.value)
|
284
|
+
end
|
285
|
+
end
|
286
|
+
end
|
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
|