cuprum 0.7.0 → 0.10.0.rc.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
- Cuprum::Utils::InstanceSpy.send(:call_spies_for, self, *args, &block)
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
@@ -8,13 +8,13 @@ module Cuprum
8
8
  # Major version.
9
9
  MAJOR = 0
10
10
  # Minor version.
11
- MINOR = 7
11
+ MINOR = 10
12
12
  # Patch version.
13
13
  PATCH = 0
14
14
  # Prerelease version.
15
- PRERELEASE = nil
15
+ PRERELEASE = :rc
16
16
  # Build metadata.
17
- BUILD = nil
17
+ BUILD = 0
18
18
 
19
19
  class << self
20
20
  # Generates the gem version string from the Version constants.
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cuprum
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.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: 2018-06-30 00:00:00.000000000 Z
11
+ date: 2020-08-01 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: sleeping_king_studios-tools
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.8'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.8'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: rspec
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -101,14 +115,23 @@ files:
101
115
  - lib/cuprum/built_in/null_operation.rb
102
116
  - lib/cuprum/chaining.rb
103
117
  - lib/cuprum/command.rb
104
- - lib/cuprum/not_implemented_error.rb
118
+ - lib/cuprum/command_factory.rb
119
+ - lib/cuprum/currying.rb
120
+ - lib/cuprum/currying/curried_command.rb
121
+ - lib/cuprum/error.rb
122
+ - lib/cuprum/errors.rb
123
+ - lib/cuprum/errors/command_not_implemented.rb
124
+ - lib/cuprum/errors/operation_not_called.rb
105
125
  - lib/cuprum/operation.rb
106
126
  - lib/cuprum/processing.rb
107
127
  - lib/cuprum/result.rb
108
128
  - lib/cuprum/result_helpers.rb
129
+ - lib/cuprum/rspec.rb
130
+ - lib/cuprum/rspec/be_a_result.rb
131
+ - lib/cuprum/rspec/be_a_result_matcher.rb
132
+ - lib/cuprum/steps.rb
109
133
  - lib/cuprum/utils.rb
110
134
  - lib/cuprum/utils/instance_spy.rb
111
- - lib/cuprum/utils/result_not_empty_warning.rb
112
135
  - lib/cuprum/version.rb
113
136
  homepage: http://sleepingkingstudios.com
114
137
  licenses:
@@ -125,12 +148,11 @@ required_ruby_version: !ruby/object:Gem::Requirement
125
148
  version: '0'
126
149
  required_rubygems_version: !ruby/object:Gem::Requirement
127
150
  requirements:
128
- - - ">="
151
+ - - ">"
129
152
  - !ruby/object:Gem::Version
130
- version: '0'
153
+ version: 1.3.1
131
154
  requirements: []
132
- rubyforge_project:
133
- rubygems_version: 2.6.13
155
+ rubygems_version: 3.1.2
134
156
  signing_key:
135
157
  specification_version: 4
136
158
  summary: An opinionated implementation of the Command pattern.
@@ -1,14 +0,0 @@
1
- require 'cuprum'
2
-
3
- module Cuprum
4
- # Error class for calling a Command that was not given a definition block
5
- # or have a #process method defined.
6
- class NotImplementedError < StandardError
7
- # Error message for a NotImplementedError.
8
- DEFAULT_MESSAGE = 'no implementation defined for command'.freeze
9
-
10
- def initialize message = nil
11
- super(message || DEFAULT_MESSAGE)
12
- end # constructor
13
- end # class
14
- end # module
@@ -1,72 +0,0 @@
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
- return ''.freeze if warnings.empty?
20
-
21
- MESSAGE + humanize_list(warnings).freeze
22
- end # method message
23
-
24
- # @return [Boolean] True if a warning is generated, otherwise false.
25
- def warning?
26
- !warnings.empty?
27
- end # method warning?
28
-
29
- private
30
-
31
- attr_reader :result
32
-
33
- def errors_not_empty_warning
34
- return nil if result.errors.empty?
35
-
36
- "there were already errors #{@result.errors.inspect}".freeze
37
- end # method errors_not_empty_warning
38
-
39
- def halted_warning
40
- return nil unless result.halted?
41
-
42
- 'the command was halted'.freeze
43
- end # method halted_warning
44
-
45
- def humanize_list list, empty_value: ''
46
- return empty_value if list.size.zero?
47
-
48
- return list.first.to_s if list.size == 1
49
-
50
- return "#{list.first} and #{list.last}" if list.size == 2
51
-
52
- "#{list[0...-1].join ', '}, and #{list.last}"
53
- end # method humanize_list
54
-
55
- def status_set_warning
56
- status = result.send(:status)
57
-
58
- return nil if status.nil?
59
-
60
- "the status was set to #{status.inspect}".freeze
61
- end # method status_set_warning
62
-
63
- def warnings
64
- @warnings ||=
65
- [
66
- errors_not_empty_warning,
67
- status_set_warning,
68
- halted_warning
69
- ].compact
70
- end # method warnings
71
- end # class
72
- end # module