cuprum 0.7.0 → 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.
@@ -4,36 +4,12 @@ module Cuprum
4
4
  autoload :Command, 'cuprum/command'
5
5
  autoload :Operation, 'cuprum/operation'
6
6
  autoload :Result, 'cuprum/result'
7
-
8
- DEFAULT_WARNING_PROC = ->(message) { Kernel.warn message }
9
- private_constant :DEFAULT_WARNING_PROC
7
+ autoload :Steps, 'cuprum/steps'
10
8
 
11
9
  class << self
12
- # @return [Proc] The proc called to display a warning message. By default,
13
- # delegates to Kernel#warn. Set this to configure the warning behavior
14
- # (e.g. to call a Logger).
15
- attr_writer :warning_proc
16
-
17
10
  # @return [String] The current version of the gem.
18
11
  def version
19
12
  VERSION
20
13
  end # method version
21
-
22
- # Displays a warning message. By default, delegates to Kernel#warn. The
23
- # warning behavior can be configured (e.g. to call a Logger) using the
24
- # #warning_proc= method.
25
- #
26
- # @param message [String] The warning message to display.
27
- #
28
- # @see #warning_proc=
29
- def warn message
30
- warning_proc.call(message)
31
- end # method warn
32
-
33
- private
34
-
35
- def warning_proc
36
- @warning_proc ||= DEFAULT_WARNING_PROC
37
- end # method warning_proc
38
14
  end # eigenclass
39
15
  end # module
@@ -12,15 +12,15 @@ module Cuprum::BuiltIn
12
12
  # #=> true
13
13
  #
14
14
  # @example With a result.
15
- # errors = ['errors.messages.unknown']
16
- # value = Cuprum::Result.new('result value', :errors => errors)
15
+ # error = 'errors.messages.unknown'
16
+ # value = Cuprum::Result.new(value: 'result value', error: error)
17
17
  # result = IdentityCommand.new.call(value)
18
18
  # result.value
19
19
  # #=> 'result value'
20
20
  # result.success?
21
21
  # #=> false
22
- # result.errors
23
- # #=> ['errors.messages.unknown']
22
+ # result.error
23
+ # #=> 'errors.messages.unknown'
24
24
  class IdentityCommand < Cuprum::Command
25
25
  private
26
26
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'cuprum'
2
4
 
3
5
  module Cuprum
@@ -11,6 +13,7 @@ module Cuprum
11
13
  # # the value of the previous command.
12
14
  #
13
15
  # class GenerateUrlCommand
16
+ # include Cuprum::Chaining
14
17
  # include Cuprum::Processing
15
18
  #
16
19
  # private
@@ -24,8 +27,8 @@ module Cuprum
24
27
  # chain(UrlSafeCommand.new).
25
28
  # chain(PrependDateCommand.new(post.created_at)).
26
29
  # call(post.title)
27
- # end # method process
28
- # end # class
30
+ # end
31
+ # end
29
32
  #
30
33
  # title = 'Greetings, programs!'
31
34
  # date = '1982-07-09'
@@ -47,54 +50,81 @@ module Cuprum
47
50
  # # the command is failing. This can be used to perform error handling.
48
51
  #
49
52
  # class CreateTaggingCommand
53
+ # include Cuprum::Chaining
50
54
  # include Cuprum::Processing
51
55
  #
52
56
  # private
53
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
+ #
54
86
  # # Tries to find the tag with the given name. If that fails, creates a
55
87
  # # new tag with the given name. If the tag is found, or if the new tag is
56
88
  # # successfully created, then creates a tagging using the tag. If the tag
57
89
  # # is not found and cannot be created, then the tagging is not created
58
90
  # # and the result of the CreateTaggingCommand is a failure with the
59
91
  # # appropriate error messages.
60
- # def process taggable, tag_name
61
- # FindTag.new.call(tag_name).
62
- # # The chained command is called with the value of the previous
63
- # # command, in this case the Tag or nil returned by FindTag.
64
- # chain(:on => :failure) do |tag|
65
- # # Chained commands share a result object, including errors. To
66
- # # rescue a command chain and return the execution to the "happy
67
- # # path", use on: :failure and clear the errors.
68
- # result.errors.clear
69
- #
70
- # Tag.create(tag_name)
71
- # end.
72
- # chain(:on => :success) do |tag|
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.
73
102
  # tag.create_tagging(taggable)
74
103
  # end
75
- # end # method process
76
- # end # method class
104
+ # .call(tag_name)
105
+ # end
106
+ # end
77
107
  #
78
108
  # post = Post.create(:title => 'Tagging Example')
79
109
  # example_tag = Tag.create(:name => 'Example Tag')
80
110
  #
81
111
  # result = CreateTaggingCommand.new.call(post, 'Example Tag')
82
112
  # result.success? #=> true
83
- # result.errors #=> []
113
+ # result.error #=> nil
84
114
  # result.value #=> an instance of Tagging
85
115
  # post.tags.map(&:name)
86
116
  # #=> ['Example Tag']
87
117
  #
88
118
  # result = CreateTaggingCommand.new.call(post, 'Another Tag')
89
119
  # result.success? #=> true
90
- # result.errors #=> []
120
+ # result.error #=> nil
91
121
  # result.value #=> an instance of Tagging
92
122
  # post.tags.map(&:name)
93
123
  # #=> ['Example Tag', 'Another Tag']
94
124
  #
95
125
  # result = CreateTaggingCommand.new.call(post, 'An Invalid Tag Name')
96
126
  # result.success? #=> false
97
- # result.errors #=> [{ tag: { name: ['is invalid'] }}]
127
+ # result.error #=> [{ tag: { name: ['is invalid'] }}]
98
128
  # post.tags.map(&:name)
99
129
  # #=> ['Example Tag', 'Another Tag']
100
130
  #
@@ -111,6 +141,7 @@ module Cuprum
111
141
  # # ignored.
112
142
  #
113
143
  # class UpdatePostCommand
144
+ # include Cuprum::Chaining
114
145
  # include Cuprum::Processing
115
146
  #
116
147
  # private
@@ -120,12 +151,8 @@ module Cuprum
120
151
  # Find.new(Post).call(id).
121
152
  # yield_result(:on => :failure) do |result|
122
153
  # redirect_to posts_path
123
- #
124
- # # A halted result prevents further :on => :failure commands from
125
- # # being called.
126
- # result.halt!
127
154
  # end.
128
- # yield_result do |result|
155
+ # yield_result(on: :success) do |result|
129
156
  # # Assign our attributes and save the post.
130
157
  # UpdateAttributes.new.call(result.value, attributes)
131
158
  # end.
@@ -137,19 +164,43 @@ module Cuprum
137
164
  # end.
138
165
  # tap_result(:on => :always) do |result|
139
166
  # # Chaining :on => :always ensures that the command will be run,
140
- # # even if the previous result is failing or halted.
167
+ # # even if the previous result is failing.
141
168
  # if result.failure?
142
169
  # log_errors(
143
170
  # :command => UpdatePostCommand,
144
- # :errors => result.errors
171
+ # :error => result.error
145
172
  # )
146
173
  # end
147
174
  # end
148
175
  # end
149
176
  # end
150
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
+ #
151
197
  # @see Cuprum::Command
152
198
  module Chaining
199
+ # (see Cuprum::Processing#call)
200
+ def call(*args, **kwargs, &block)
201
+ yield_chain(super)
202
+ end
203
+
153
204
  # Creates a copy of the first command, and then chains the given command or
154
205
  # block to execute after the first command's implementation. When #call is
155
206
  # executed, each chained command will be called with the previous result
@@ -168,13 +219,11 @@ module Cuprum
168
219
  # @param on [Symbol] Sets a condition on when the chained block can run,
169
220
  # based on the previous result. Valid values are :success, :failure, and
170
221
  # :always. If the value is :success, the block will be called only if
171
- # the previous result succeeded and is not halted. If the value is
172
- # :failure, the block will be called only if the previous result failed
173
- # and is not halted. If the value is :always, the block will be called
174
- # regardless of the previous result status, even if the previous result
175
- # is halted. If no value is given, the command will run whether the
176
- # previous command was a success or a failure, but not if the command
177
- # chain has been halted.
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.
178
227
  #
179
228
  # @overload chain(on: nil) { |value| }
180
229
  # Creates an anonymous command from the given block. The command will be
@@ -183,70 +232,16 @@ module Cuprum
183
232
  # @param on [Symbol] Sets a condition on when the chained block can run,
184
233
  # based on the previous result. Valid values are :success, :failure, and
185
234
  # :always. If the value is :success, the block will be called only if
186
- # the previous result succeeded and is not halted. If the value is
187
- # :failure, the block will be called only if the previous result failed
188
- # and is not halted. If the value is :always, the block will be called
189
- # regardless of the previous result status, even if the previous result
190
- # is halted. If no value is given, the command will run whether the
191
- # previous command was a success or a failure, but not if the command
192
- # chain has been halted.
193
- #
194
- # @yieldparam value [Object] The value of the previous result.
195
- def chain command = nil, on: nil, &block
196
- command ||= Cuprum::Command.new(&block)
197
-
198
- clone.tap do |fn|
199
- fn.chained_procs <<
200
- {
201
- :proc => chain_command(command),
202
- :on => on
203
- } # end hash
204
- end # tap
205
- end # method chain
206
-
207
- # Shorthand for command.chain(:on => :failure). Creates a copy of the first
208
- # command, and then chains the given command or block to execute after the
209
- # first command's implementation, but only if the previous command is
210
- # failing.
211
- #
212
- # @return [Cuprum::Chaining] A copy of the command, with the chained
213
- # command.
214
- #
215
- # @see #chain
216
- #
217
- # @overload failure(command)
218
- # @param command [Cuprum::Command] The command to chain.
219
- #
220
- # @overload failure() { |value| }
221
- # Creates an anonymous command from the given block. The command will be
222
- # passed the value of the previous result.
223
- #
224
- # @yieldparam value [Object] The value of the previous result.
225
- def failure command = nil, &block
226
- chain(command, :on => :failure, &block)
227
- end # method failure
228
-
229
- # Shorthand for command.chain(:on => :success). Creates a copy of the first
230
- # command, and then chains the given command or block to execute after the
231
- # first command's implementation, but only if the previous command is
232
- # failing.
233
- #
234
- # @return [Cuprum::Chaining] A copy of the command, with the chained
235
- # command.
236
- #
237
- # @see #chain
238
- #
239
- # @overload success(command)
240
- # @param command [Cuprum::Command] The command to chain.
241
- #
242
- # @overload success() { |value| }
243
- # Creates an anonymous command from the given block. The command will be
244
- # passed the value of the previous result.
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.
245
240
  #
246
241
  # @yieldparam value [Object] The value of the previous result.
247
- def success command = nil, &block
248
- chain(command, :on => :success, &block)
249
- end # method success
242
+ def chain(command = nil, on: nil, &block)
243
+ clone.chain!(command, on: on, &block)
244
+ end
250
245
 
251
246
  # As #yield_result, but always returns the previous result when the block is
252
247
  # called. The return value of the block is discarded.
@@ -258,17 +253,9 @@ module Cuprum
258
253
  # @return (see #yield_result)
259
254
  #
260
255
  # @see #yield_result
261
- def tap_result on: nil, &block
262
- tapped = ->(result) { result.tap { block.call(result) } }
263
-
264
- clone.tap do |fn|
265
- fn.chained_procs <<
266
- {
267
- :proc => tapped,
268
- :on => on
269
- } # end hash
270
- end # tap
271
- end # method tap_result
256
+ def tap_result(on: nil, &block)
257
+ clone.tap_result!(on: on, &block)
258
+ end
272
259
 
273
260
  # Creates a copy of the command, and then chains the block to execute after
274
261
  # the command implementation. When #call is executed, each chained block
@@ -278,73 +265,177 @@ module Cuprum
278
265
  # @param on [Symbol] Sets a condition on when the chained block can run,
279
266
  # based on the previous result. Valid values are :success, :failure, and
280
267
  # :always. If the value is :success, the block will be called only if the
281
- # previous result succeeded and is not halted. If the value is :failure,
282
- # the block will be called only if the previous result failed and is not
283
- # halted. If the value is :always, the block will be called regardless of
284
- # the previous result status, even if the previous result is halted. If no
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
285
271
  # value is given, the command will run whether the previous command was a
286
- # success or a failure, but not if the command chain has been halted.
272
+ # success or a failure.
287
273
  #
288
274
  # @yieldparam result [Cuprum::Result] The #result of the previous command.
289
275
  #
290
276
  # @return [Cuprum::Chaining] A copy of the command, with the chained block.
291
277
  #
292
278
  # @see #tap_result
293
- def yield_result on: nil, &block
294
- clone.tap do |fn|
295
- fn.chained_procs <<
296
- {
297
- :proc => block,
298
- :on => on
299
- } # end hash
300
- end # tap
301
- end # method yield_result
279
+ def yield_result(on: nil, &block)
280
+ clone.yield_result!(on: on, &block)
281
+ end
302
282
 
303
283
  protected
304
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
+
305
341
  def chained_procs
306
342
  @chained_procs ||= []
307
- end # method chained_procs
343
+ end
308
344
 
309
- def process_with_result *args, &block
310
- yield_chain(super)
311
- end # method call
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
312
405
 
313
406
  private
314
407
 
315
- def chain_command command
408
+ def chain_command(command)
316
409
  if command.arity.zero?
317
- ->(result) { command.process_with_result(result) }
410
+ ->(_result) { command.call }
318
411
  else
319
- ->(result) { command.process_with_result(result, result.value) }
320
- end # if-else
321
- end # method chain_command
412
+ ->(result) { command.call(result.value) }
413
+ end
414
+ end
322
415
 
323
- def skip_chained_proc? last_result, on:
416
+ def skip_chained_proc?(last_result, on:)
324
417
  return false if on == :always
325
418
 
326
- return true if last_result.respond_to?(:halted?) && last_result.halted?
327
-
328
419
  case on
329
420
  when :success
330
421
  !last_result.success?
331
422
  when :failure
332
423
  !last_result.failure?
333
- end # case
334
- end # method skip_chained_proc?
424
+ end
425
+ end
335
426
 
336
- def yield_chain first_result
427
+ def yield_chain(first_result)
337
428
  chained_procs.reduce(first_result) do |result, hsh|
338
- next result if skip_chained_proc?(result, :on => hsh[:on])
429
+ next result if skip_chained_proc?(result, on: hsh[:on])
339
430
 
340
431
  value = hsh.fetch(:proc).call(result)
341
432
 
342
433
  if value_is_result?(value)
343
- value.to_result
434
+ value.to_cuprum_result
344
435
  else
345
- build_result(value, :errors => build_errors)
346
- end # if-else
347
- end # reduce
348
- end # method yield_chain
349
- end # module
350
- end # modue
436
+ build_result(value: value)
437
+ end
438
+ end
439
+ end
440
+ end
441
+ end