cuprum 0.10.0 → 1.0.0.rc.1

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.
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.10.0
4
+ version: 1.0.0.rc.1
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: 2020-12-22 00:00:00.000000000 Z
11
+ date: 2021-10-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sleeping_king_studios-tools
@@ -16,70 +16,70 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '0.8'
19
+ version: '1.0'
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.8'
26
+ version: '1.0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rspec
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '3.6'
33
+ version: '3.10'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '3.6'
40
+ version: '3.10'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rspec-sleeping_king_studios
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '2.3'
47
+ version: '2.5'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '2.3'
54
+ version: '2.5'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: rubocop
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: 0.49.1
61
+ version: 1.10.0
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: 0.49.1
68
+ version: 1.10.0
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: rubocop-rspec
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
73
  - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: '1.15'
75
+ version: '2.1'
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
- version: '1.15'
82
+ version: '2.1'
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: simplecov
85
85
  requirement: !ruby/object:Gem::Requirement
@@ -94,9 +94,11 @@ dependencies:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
96
  version: '0.15'
97
- description: An opinionated implementation of the Command pattern for Ruby applications.
98
- Cuprum wraps your business logic in a consistent, object-oriented interface and
99
- features status and error management, composability and control flow management.
97
+ description: |-
98
+ An opinionated implementation of the Command pattern for Ruby applications.
99
+ Cuprum wraps your business logic in a consistent, object-oriented interface
100
+ and features status and error management, composability and control flow
101
+ management.
100
102
  email:
101
103
  - merlin@sleepingkingstudios.com
102
104
  executables: []
@@ -104,6 +106,7 @@ extensions: []
104
106
  extra_rdoc_files: []
105
107
  files:
106
108
  - CHANGELOG.md
109
+ - CODE_OF_CONDUCT.md
107
110
  - DEVELOPMENT.md
108
111
  - LICENSE
109
112
  - README.md
@@ -113,7 +116,6 @@ files:
113
116
  - lib/cuprum/built_in/identity_operation.rb
114
117
  - lib/cuprum/built_in/null_command.rb
115
118
  - lib/cuprum/built_in/null_operation.rb
116
- - lib/cuprum/chaining.rb
117
119
  - lib/cuprum/command.rb
118
120
  - lib/cuprum/command_factory.rb
119
121
  - lib/cuprum/currying.rb
@@ -122,6 +124,13 @@ files:
122
124
  - lib/cuprum/errors.rb
123
125
  - lib/cuprum/errors/command_not_implemented.rb
124
126
  - lib/cuprum/errors/operation_not_called.rb
127
+ - lib/cuprum/errors/uncaught_exception.rb
128
+ - lib/cuprum/exception_handling.rb
129
+ - lib/cuprum/matcher.rb
130
+ - lib/cuprum/matcher_list.rb
131
+ - lib/cuprum/matching.rb
132
+ - lib/cuprum/matching/match_clause.rb
133
+ - lib/cuprum/middleware.rb
125
134
  - lib/cuprum/operation.rb
126
135
  - lib/cuprum/processing.rb
127
136
  - lib/cuprum/result.rb
@@ -129,6 +138,7 @@ files:
129
138
  - lib/cuprum/rspec.rb
130
139
  - lib/cuprum/rspec/be_a_result.rb
131
140
  - lib/cuprum/rspec/be_a_result_matcher.rb
141
+ - lib/cuprum/rspec/be_callable.rb
132
142
  - lib/cuprum/steps.rb
133
143
  - lib/cuprum/utils.rb
134
144
  - lib/cuprum/utils/instance_spy.rb
@@ -136,7 +146,9 @@ files:
136
146
  homepage: http://sleepingkingstudios.com
137
147
  licenses:
138
148
  - MIT
139
- metadata: {}
149
+ metadata:
150
+ bug_tracker_uri: https://github.com/sleepingkingstudios/cuprum/issues
151
+ source_code_uri: https://github.com/sleepingkingstudios/cuprum
140
152
  post_install_message:
141
153
  rdoc_options: []
142
154
  require_paths:
@@ -145,14 +157,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
145
157
  requirements:
146
158
  - - ">="
147
159
  - !ruby/object:Gem::Version
148
- version: '0'
160
+ version: 2.5.0
149
161
  required_rubygems_version: !ruby/object:Gem::Requirement
150
162
  requirements:
151
- - - ">="
163
+ - - ">"
152
164
  - !ruby/object:Gem::Version
153
- version: '0'
165
+ version: 1.3.1
154
166
  requirements: []
155
- rubygems_version: 3.1.2
167
+ rubygems_version: 3.1.4
156
168
  signing_key:
157
169
  specification_version: 4
158
170
  summary: An opinionated implementation of the Command pattern.
@@ -1,441 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'cuprum'
4
-
5
- module Cuprum
6
- # Mixin to implement command chaining functionality for a command class.
7
- # Chaining commands allows you to define complex logic by composing it from
8
- # simpler commands, including branching logic and error handling.
9
- #
10
- # @example Chaining Commands
11
- # # By chaining commands together with the #chain instance method, we set up
12
- # # a series of commands to run in sequence. Each chained command is passed
13
- # # the value of the previous command.
14
- #
15
- # class GenerateUrlCommand
16
- # include Cuprum::Chaining
17
- # include Cuprum::Processing
18
- #
19
- # private
20
- #
21
- # # Acts as a pipeline, taking a value (the title of the given post) and
22
- # # calling the underscore, URL safe, and prepend date commands. By
23
- # # passing parameters to PrependDateCommand, we can customize the command
24
- # # in the pipeline to the current context (in this case, the Post).
25
- # def process post
26
- # UnderscoreCommand.new.
27
- # chain(UrlSafeCommand.new).
28
- # chain(PrependDateCommand.new(post.created_at)).
29
- # call(post.title)
30
- # end
31
- # end
32
- #
33
- # title = 'Greetings, programs!'
34
- # date = '1982-07-09'
35
- # post = Post.new(:title => title, :created_at => date)
36
- # url = GenerateUrlCommand.new.call(post).value
37
- # #=> '1982_07_09_greetings_programs'
38
- #
39
- # title = 'Plasma-based Einhanders in Popular Media'
40
- # date = '1977-05-25'
41
- # post = Post.new(:title => title, :created_at => date)
42
- # url = GenerateUrlCommand.new.call(post).value
43
- # #=> '1977_05_25_plasma_based_einhanders_in_popular_media'
44
- #
45
- # @example Conditional Chaining
46
- # # Commands can be conditionally chained based on the success or failure of
47
- # # the previous command using the on: keyword. If the command is chained
48
- # # using on: :success, it will only be called if the result is passing.
49
- # # If the command is chained using on: :failure, it will only be called if
50
- # # the command is failing. This can be used to perform error handling.
51
- #
52
- # class CreateTaggingCommand
53
- # include Cuprum::Chaining
54
- # include Cuprum::Processing
55
- #
56
- # private
57
- #
58
- # def create_tag
59
- # Command.new do |tag_name|
60
- # tag = Tag.new(name: tag_name)
61
- #
62
- # return tag if tag.save
63
- #
64
- # Cuprum::Result.new(error: tag.errors)
65
- # end
66
- # end
67
- #
68
- # def create_tagging(taggable)
69
- # Command.new do |tag|
70
- # tagging = tag.build_tagging(taggable)
71
- #
72
- # return tagging if tagging.save
73
- #
74
- # Cuprum::Result.new(error: tagging.errors)
75
- # end
76
- # end
77
- #
78
- # def find_tag
79
- # Command.new do |tag_name|
80
- # tag = Tag.where(name: tag_name).first
81
- #
82
- # tag || Cuprum::Result.new(error: 'tag not found')
83
- # end
84
- # end
85
- #
86
- # # Tries to find the tag with the given name. If that fails, creates a
87
- # # new tag with the given name. If the tag is found, or if the new tag is
88
- # # successfully created, then creates a tagging using the tag. If the tag
89
- # # is not found and cannot be created, then the tagging is not created
90
- # # and the result of the CreateTaggingCommand is a failure with the
91
- # # appropriate error messages.
92
- # def process(taggable, tag_name)
93
- # find_tag
94
- # .chain(on: :failure) do
95
- # # If the finding the tag fails, this step is called, returning a
96
- # # result with a newly created tag.
97
- # create_tag.call(tag_name)
98
- # end
99
- # .chain(:on => :success) do |tag|
100
- # # Finally, the tag has been either found or created, so we can
101
- # # create the tagging relation.
102
- # tag.create_tagging(taggable)
103
- # end
104
- # .call(tag_name)
105
- # end
106
- # end
107
- #
108
- # post = Post.create(:title => 'Tagging Example')
109
- # example_tag = Tag.create(:name => 'Example Tag')
110
- #
111
- # result = CreateTaggingCommand.new.call(post, 'Example Tag')
112
- # result.success? #=> true
113
- # result.error #=> nil
114
- # result.value #=> an instance of Tagging
115
- # post.tags.map(&:name)
116
- # #=> ['Example Tag']
117
- #
118
- # result = CreateTaggingCommand.new.call(post, 'Another Tag')
119
- # result.success? #=> true
120
- # result.error #=> nil
121
- # result.value #=> an instance of Tagging
122
- # post.tags.map(&:name)
123
- # #=> ['Example Tag', 'Another Tag']
124
- #
125
- # result = CreateTaggingCommand.new.call(post, 'An Invalid Tag Name')
126
- # result.success? #=> false
127
- # result.error #=> [{ tag: { name: ['is invalid'] }}]
128
- # post.tags.map(&:name)
129
- # #=> ['Example Tag', 'Another Tag']
130
- #
131
- # @example Yield Result and Tap Result
132
- # # The #yield_result method allows for advanced control over a step in the
133
- # # command chain. The block will be yielded the result at that point in the
134
- # # chain, and will wrap the returned value in a result to the next chained
135
- # # command (or return it directly if the returned value is a result).
136
- # #
137
- # # The #tap_result method inserts arbitrary code into the command chain
138
- # # without interrupting it. The block will be yielded the result at that
139
- # # point in the chain and will pass that same result to the next chained
140
- # # command after executing the block. The return value of the block is
141
- # # ignored.
142
- #
143
- # class UpdatePostCommand
144
- # include Cuprum::Chaining
145
- # include Cuprum::Processing
146
- #
147
- # private
148
- #
149
- # def process id, attributes
150
- # # First, find the referenced post.
151
- # Find.new(Post).call(id).
152
- # yield_result(:on => :failure) do |result|
153
- # redirect_to posts_path
154
- # end.
155
- # yield_result(on: :success) do |result|
156
- # # Assign our attributes and save the post.
157
- # UpdateAttributes.new.call(result.value, attributes)
158
- # end.
159
- # tap_result(:on => :success) do |result|
160
- # # Create our tags, but still return the result of our update.
161
- # attributes[:tags].each do |tag_name|
162
- # CreateTaggingCommand.new.call(result.value, tag_name)
163
- # end
164
- # end.
165
- # tap_result(:on => :always) do |result|
166
- # # Chaining :on => :always ensures that the command will be run,
167
- # # even if the previous result is failing.
168
- # if result.failure?
169
- # log_errors(
170
- # :command => UpdatePostCommand,
171
- # :error => result.error
172
- # )
173
- # end
174
- # end
175
- # end
176
- # end
177
- #
178
- # @example Protected Chaining Methods
179
- # # Using the protected chaining methods #chain!, #tap_result!, and
180
- # # #yield_result!, you can create a command class that composes other
181
- # # commands.
182
- #
183
- # # We subclass the build command, which will be executed first.
184
- # class CreateCommentCommand < BuildCommentCommand
185
- # include Cuprum::Chaining
186
- # include Cuprum::Processing
187
- #
188
- # def initialize
189
- # # After the build step is run, we validate the comment.
190
- # chain!(ValidateCommentCommand.new)
191
- #
192
- # # If the validation passes, we then save the comment.
193
- # chain!(SaveCommentCommand.new, on: :success)
194
- # end
195
- # end
196
- #
197
- # @see Cuprum::Command
198
- module Chaining
199
- # (see Cuprum::Processing#call)
200
- def call(*args, **kwargs, &block)
201
- yield_chain(super)
202
- end
203
-
204
- # Creates a copy of the first command, and then chains the given command or
205
- # block to execute after the first command's implementation. When #call is
206
- # executed, each chained command will be called with the previous result
207
- # value, and its result property will be set to the previous result. The
208
- # return value will be wrapped in a result and returned or yielded to the
209
- # next block.
210
- #
211
- # @return [Cuprum::Chaining] A copy of the command, with the chained
212
- # command.
213
- #
214
- # @see #yield_result
215
- #
216
- # @overload chain(command, on: nil)
217
- # @param command [Cuprum::Command] The command to chain.
218
- #
219
- # @param on [Symbol] Sets a condition on when the chained block can run,
220
- # based on the previous result. Valid values are :success, :failure, and
221
- # :always. If the value is :success, the block will be called only if
222
- # the previous result succeeded. If the value is :failure, the block
223
- # will be called only if the previous result failed. If the value is
224
- # :always, the block will be called regardless of the previous result
225
- # status. If no value is given, the command will run whether the
226
- # previous command was a success or a failure.
227
- #
228
- # @overload chain(on: nil) { |value| }
229
- # Creates an anonymous command from the given block. The command will be
230
- # passed the value of the previous result.
231
- #
232
- # @param on [Symbol] Sets a condition on when the chained block can run,
233
- # based on the previous result. Valid values are :success, :failure, and
234
- # :always. If the value is :success, the block will be called only if
235
- # the previous result succeeded. If the value is :failure, the block
236
- # will be called only if the previous result failed. If the value is
237
- # :always, the block will be called regardless of the previous result
238
- # status. If no value is given, the command will run whether the
239
- # previous command was a success or a failure.
240
- #
241
- # @yieldparam value [Object] The value of the previous result.
242
- def chain(command = nil, on: nil, &block)
243
- clone.chain!(command, on: on, &block)
244
- end
245
-
246
- # As #yield_result, but always returns the previous result when the block is
247
- # called. The return value of the block is discarded.
248
- #
249
- # @param (see #yield_result)
250
- #
251
- # @yieldparam result [Cuprum::Result] The #result of the previous command.
252
- #
253
- # @return (see #yield_result)
254
- #
255
- # @see #yield_result
256
- def tap_result(on: nil, &block)
257
- clone.tap_result!(on: on, &block)
258
- end
259
-
260
- # Creates a copy of the command, and then chains the block to execute after
261
- # the command implementation. When #call is executed, each chained block
262
- # will be yielded the previous result, and the return value wrapped in a
263
- # result and returned or yielded to the next block.
264
- #
265
- # @param on [Symbol] Sets a condition on when the chained block can run,
266
- # based on the previous result. Valid values are :success, :failure, and
267
- # :always. If the value is :success, the block will be called only if the
268
- # previous result succeeded. If the value is :failure, the block will be
269
- # called only if the previous result failed. If the value is :always, the
270
- # block will be called regardless of the previous result status. If no
271
- # value is given, the command will run whether the previous command was a
272
- # success or a failure.
273
- #
274
- # @yieldparam result [Cuprum::Result] The #result of the previous command.
275
- #
276
- # @return [Cuprum::Chaining] A copy of the command, with the chained block.
277
- #
278
- # @see #tap_result
279
- def yield_result(on: nil, &block)
280
- clone.yield_result!(on: on, &block)
281
- end
282
-
283
- protected
284
-
285
- # rubocop:disable Metrics/MethodLength
286
-
287
- # @!visibility public
288
- #
289
- # As #chain, but modifies the current command instead of creating a clone.
290
- # This is a protected method, and is meant to be called by the command to be
291
- # chained, such as during #initialize.
292
- #
293
- # @return [Cuprum::Chaining] The current command.
294
- #
295
- # @see #chain
296
- #
297
- # @overload chain!(command, on: nil)
298
- # @param command [Cuprum::Command] The command to chain.
299
- #
300
- # @param on [Symbol] Sets a condition on when the chained block can run,
301
- # based on the previous result. Valid values are :success, :failure, and
302
- # :always. If the value is :success, the block will be called only if
303
- # the previous result succeeded. If the value is :failure, the block
304
- # will be called only if the previous result failed. If the value is
305
- # :always, the block will be called regardless of the previous result
306
- # status. If no value is given, the command will run whether the
307
- # previous command was a success or a failure.
308
- #
309
- # @overload chain!(on: nil) { |value| }
310
- # Creates an anonymous command from the given block. The command will be
311
- # passed the value of the previous result.
312
- #
313
- # @param on [Symbol] Sets a condition on when the chained block can run,
314
- # based on the previous result. Valid values are :success, :failure, and
315
- # :always. If the value is :success, the block will be called only if
316
- # the previous result succeeded. If the value is :failure, the block
317
- # will be called only if the previous result failed. If the value is
318
- # :always, the block will be called regardless of the previous result
319
- # status. If no value is given, the command will run whether the
320
- # previous command was a success or a failure.
321
- #
322
- # @yieldparam value [Object] The value of the previous result.
323
- def chain!(command = nil, on: nil, &block)
324
- SleepingKingStudios::Tools::CoreTools.deprecate(
325
- "#{self.class}#chain",
326
- message: 'Use the #step method to compose commands.'
327
- )
328
-
329
- command ||= Cuprum::Command.new(&block)
330
-
331
- chained_procs <<
332
- {
333
- proc: chain_command(command),
334
- on: on
335
- } # end hash
336
-
337
- self
338
- end
339
- # rubocop:enable Metrics/MethodLength
340
-
341
- def chained_procs
342
- @chained_procs ||= []
343
- end
344
-
345
- # rubocop:disable Metrics/MethodLength
346
-
347
- # @!visibility public
348
- #
349
- # As #tap_result, but modifies the current command instead of creating a
350
- # clone. This is a protected method, and is meant to be called by the
351
- # command to be chained, such as during #initialize.
352
- #
353
- # @param (see #tap_result)
354
- #
355
- # @yieldparam result [Cuprum::Result] The #result of the previous command.
356
- #
357
- # @return (see #tap_result)
358
- #
359
- # @see #tap_result
360
- def tap_result!(on: nil, &block)
361
- SleepingKingStudios::Tools::CoreTools.deprecate(
362
- "#{self.class}#tap_result",
363
- message: 'Use the #step method to compose commands.'
364
- )
365
-
366
- tapped = ->(result) { result.tap { block.call(result) } }
367
-
368
- chained_procs <<
369
- {
370
- proc: tapped,
371
- on: on
372
- } # end hash
373
-
374
- self
375
- end
376
- # rubocop:enable Metrics/MethodLength
377
-
378
- # @!visibility public
379
- #
380
- # As #yield_result, but modifies the current command instead of creating a
381
- # clone. This is a protected method, and is meant to be called by the
382
- # command to be chained, such as during #initialize.
383
- #
384
- # @param (see #yield_result)
385
- #
386
- # @yieldparam result [Cuprum::Result] The #result of the previous command.
387
- #
388
- # @return (see #yield_result)
389
- #
390
- # @see #yield_result
391
- def yield_result!(on: nil, &block)
392
- SleepingKingStudios::Tools::CoreTools.deprecate(
393
- "#{self.class}#yield_result",
394
- message: 'Use the #step method to compose commands.'
395
- )
396
-
397
- chained_procs <<
398
- {
399
- proc: block,
400
- on: on
401
- } # end hash
402
-
403
- self
404
- end
405
-
406
- private
407
-
408
- def chain_command(command)
409
- if command.arity.zero?
410
- ->(_result) { command.call }
411
- else
412
- ->(result) { command.call(result.value) }
413
- end
414
- end
415
-
416
- def skip_chained_proc?(last_result, on:)
417
- return false if on == :always
418
-
419
- case on
420
- when :success
421
- !last_result.success?
422
- when :failure
423
- !last_result.failure?
424
- end
425
- end
426
-
427
- def yield_chain(first_result)
428
- chained_procs.reduce(first_result) do |result, hsh|
429
- next result if skip_chained_proc?(result, on: hsh[:on])
430
-
431
- value = hsh.fetch(:proc).call(result)
432
-
433
- if value_is_result?(value)
434
- value.to_cuprum_result
435
- else
436
- build_result(value: value)
437
- end
438
- end
439
- end
440
- end
441
- end