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.
@@ -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