cuprum 0.8.0 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
data/lib/cuprum.rb
CHANGED
@@ -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
|
-
#
|
16
|
-
# value = Cuprum::Result.new('result value', :
|
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.
|
23
|
-
# #=>
|
22
|
+
# result.error
|
23
|
+
# #=> 'errors.messages.unknown'
|
24
24
|
class IdentityCommand < Cuprum::Command
|
25
25
|
private
|
26
26
|
|
data/lib/cuprum/chaining.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'cuprum'
|
2
4
|
|
3
5
|
module Cuprum
|
@@ -25,8 +27,8 @@ module Cuprum
|
|
25
27
|
# chain(UrlSafeCommand.new).
|
26
28
|
# chain(PrependDateCommand.new(post.created_at)).
|
27
29
|
# call(post.title)
|
28
|
-
# end
|
29
|
-
# end
|
30
|
+
# end
|
31
|
+
# end
|
30
32
|
#
|
31
33
|
# title = 'Greetings, programs!'
|
32
34
|
# date = '1982-07-09'
|
@@ -53,50 +55,76 @@ module Cuprum
|
|
53
55
|
#
|
54
56
|
# private
|
55
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
|
+
#
|
56
86
|
# # Tries to find the tag with the given name. If that fails, creates a
|
57
87
|
# # new tag with the given name. If the tag is found, or if the new tag is
|
58
88
|
# # successfully created, then creates a tagging using the tag. If the tag
|
59
89
|
# # is not found and cannot be created, then the tagging is not created
|
60
90
|
# # and the result of the CreateTaggingCommand is a failure with the
|
61
91
|
# # appropriate error messages.
|
62
|
-
# def process
|
63
|
-
#
|
64
|
-
#
|
65
|
-
#
|
66
|
-
#
|
67
|
-
#
|
68
|
-
#
|
69
|
-
#
|
70
|
-
#
|
71
|
-
#
|
72
|
-
# Tag.create(tag_name)
|
73
|
-
# end.
|
74
|
-
# 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.
|
75
102
|
# tag.create_tagging(taggable)
|
76
103
|
# end
|
77
|
-
#
|
78
|
-
#
|
104
|
+
# .call(tag_name)
|
105
|
+
# end
|
106
|
+
# end
|
79
107
|
#
|
80
108
|
# post = Post.create(:title => 'Tagging Example')
|
81
109
|
# example_tag = Tag.create(:name => 'Example Tag')
|
82
110
|
#
|
83
111
|
# result = CreateTaggingCommand.new.call(post, 'Example Tag')
|
84
112
|
# result.success? #=> true
|
85
|
-
# result.
|
113
|
+
# result.error #=> nil
|
86
114
|
# result.value #=> an instance of Tagging
|
87
115
|
# post.tags.map(&:name)
|
88
116
|
# #=> ['Example Tag']
|
89
117
|
#
|
90
118
|
# result = CreateTaggingCommand.new.call(post, 'Another Tag')
|
91
119
|
# result.success? #=> true
|
92
|
-
# result.
|
120
|
+
# result.error #=> nil
|
93
121
|
# result.value #=> an instance of Tagging
|
94
122
|
# post.tags.map(&:name)
|
95
123
|
# #=> ['Example Tag', 'Another Tag']
|
96
124
|
#
|
97
125
|
# result = CreateTaggingCommand.new.call(post, 'An Invalid Tag Name')
|
98
126
|
# result.success? #=> false
|
99
|
-
# result.
|
127
|
+
# result.error #=> [{ tag: { name: ['is invalid'] }}]
|
100
128
|
# post.tags.map(&:name)
|
101
129
|
# #=> ['Example Tag', 'Another Tag']
|
102
130
|
#
|
@@ -123,12 +151,8 @@ module Cuprum
|
|
123
151
|
# Find.new(Post).call(id).
|
124
152
|
# yield_result(:on => :failure) do |result|
|
125
153
|
# redirect_to posts_path
|
126
|
-
#
|
127
|
-
# # A halted result prevents further :on => :failure commands from
|
128
|
-
# # being called.
|
129
|
-
# result.halt!
|
130
154
|
# end.
|
131
|
-
# yield_result do |result|
|
155
|
+
# yield_result(on: :success) do |result|
|
132
156
|
# # Assign our attributes and save the post.
|
133
157
|
# UpdateAttributes.new.call(result.value, attributes)
|
134
158
|
# end.
|
@@ -140,11 +164,11 @@ module Cuprum
|
|
140
164
|
# end.
|
141
165
|
# tap_result(:on => :always) do |result|
|
142
166
|
# # Chaining :on => :always ensures that the command will be run,
|
143
|
-
# # even if the previous result is failing
|
167
|
+
# # even if the previous result is failing.
|
144
168
|
# if result.failure?
|
145
169
|
# log_errors(
|
146
170
|
# :command => UpdatePostCommand,
|
147
|
-
# :
|
171
|
+
# :error => result.error
|
148
172
|
# )
|
149
173
|
# end
|
150
174
|
# end
|
@@ -172,6 +196,11 @@ module Cuprum
|
|
172
196
|
#
|
173
197
|
# @see Cuprum::Command
|
174
198
|
module Chaining
|
199
|
+
# (see Cuprum::Processing#call)
|
200
|
+
def call(*args, **kwargs, &block)
|
201
|
+
yield_chain(super)
|
202
|
+
end
|
203
|
+
|
175
204
|
# Creates a copy of the first command, and then chains the given command or
|
176
205
|
# block to execute after the first command's implementation. When #call is
|
177
206
|
# executed, each chained command will be called with the previous result
|
@@ -190,13 +219,11 @@ module Cuprum
|
|
190
219
|
# @param on [Symbol] Sets a condition on when the chained block can run,
|
191
220
|
# based on the previous result. Valid values are :success, :failure, and
|
192
221
|
# :always. If the value is :success, the block will be called only if
|
193
|
-
# the previous result succeeded
|
194
|
-
#
|
195
|
-
#
|
196
|
-
#
|
197
|
-
#
|
198
|
-
# previous command was a success or a failure, but not if the command
|
199
|
-
# 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.
|
200
227
|
#
|
201
228
|
# @overload chain(on: nil) { |value| }
|
202
229
|
# Creates an anonymous command from the given block. The command will be
|
@@ -205,62 +232,16 @@ module Cuprum
|
|
205
232
|
# @param on [Symbol] Sets a condition on when the chained block can run,
|
206
233
|
# based on the previous result. Valid values are :success, :failure, and
|
207
234
|
# :always. If the value is :success, the block will be called only if
|
208
|
-
# the previous result succeeded
|
209
|
-
#
|
210
|
-
#
|
211
|
-
#
|
212
|
-
#
|
213
|
-
# previous command was a success or a failure, but not if the command
|
214
|
-
# chain has been halted.
|
215
|
-
#
|
216
|
-
# @yieldparam value [Object] The value of the previous result.
|
217
|
-
def chain command = nil, on: nil, &block
|
218
|
-
clone.chain!(command, :on => on, &block)
|
219
|
-
end # method chain
|
220
|
-
|
221
|
-
# Shorthand for command.chain(:on => :failure). Creates a copy of the first
|
222
|
-
# command, and then chains the given command or block to execute after the
|
223
|
-
# first command's implementation, but only if the previous command is
|
224
|
-
# failing.
|
225
|
-
#
|
226
|
-
# @return [Cuprum::Chaining] A copy of the command, with the chained
|
227
|
-
# command.
|
228
|
-
#
|
229
|
-
# @see #chain
|
230
|
-
#
|
231
|
-
# @overload failure(command)
|
232
|
-
# @param command [Cuprum::Command] The command to chain.
|
233
|
-
#
|
234
|
-
# @overload failure() { |value| }
|
235
|
-
# Creates an anonymous command from the given block. The command will be
|
236
|
-
# 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.
|
237
240
|
#
|
238
241
|
# @yieldparam value [Object] The value of the previous result.
|
239
|
-
def
|
240
|
-
clone.chain!(command, :on
|
241
|
-
end
|
242
|
-
|
243
|
-
# Shorthand for command.chain(:on => :success). Creates a copy of the first
|
244
|
-
# command, and then chains the given command or block to execute after the
|
245
|
-
# first command's implementation, but only if the previous command is
|
246
|
-
# failing.
|
247
|
-
#
|
248
|
-
# @return [Cuprum::Chaining] A copy of the command, with the chained
|
249
|
-
# command.
|
250
|
-
#
|
251
|
-
# @see #chain
|
252
|
-
#
|
253
|
-
# @overload success(command)
|
254
|
-
# @param command [Cuprum::Command] The command to chain.
|
255
|
-
#
|
256
|
-
# @overload success() { |value| }
|
257
|
-
# Creates an anonymous command from the given block. The command will be
|
258
|
-
# passed the value of the previous result.
|
259
|
-
#
|
260
|
-
# @yieldparam value [Object] The value of the previous result.
|
261
|
-
def success command = nil, &block
|
262
|
-
clone.chain!(command, :on => :success, &block)
|
263
|
-
end # method success
|
242
|
+
def chain(command = nil, on: nil, &block)
|
243
|
+
clone.chain!(command, on: on, &block)
|
244
|
+
end
|
264
245
|
|
265
246
|
# As #yield_result, but always returns the previous result when the block is
|
266
247
|
# called. The return value of the block is discarded.
|
@@ -272,9 +253,9 @@ module Cuprum
|
|
272
253
|
# @return (see #yield_result)
|
273
254
|
#
|
274
255
|
# @see #yield_result
|
275
|
-
def tap_result
|
276
|
-
clone.tap_result!(:
|
277
|
-
end
|
256
|
+
def tap_result(on: nil, &block)
|
257
|
+
clone.tap_result!(on: on, &block)
|
258
|
+
end
|
278
259
|
|
279
260
|
# Creates a copy of the command, and then chains the block to execute after
|
280
261
|
# the command implementation. When #call is executed, each chained block
|
@@ -284,24 +265,25 @@ module Cuprum
|
|
284
265
|
# @param on [Symbol] Sets a condition on when the chained block can run,
|
285
266
|
# based on the previous result. Valid values are :success, :failure, and
|
286
267
|
# :always. If the value is :success, the block will be called only if the
|
287
|
-
# previous result succeeded
|
288
|
-
#
|
289
|
-
#
|
290
|
-
# 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
|
291
271
|
# value is given, the command will run whether the previous command was a
|
292
|
-
# success or a failure
|
272
|
+
# success or a failure.
|
293
273
|
#
|
294
274
|
# @yieldparam result [Cuprum::Result] The #result of the previous command.
|
295
275
|
#
|
296
276
|
# @return [Cuprum::Chaining] A copy of the command, with the chained block.
|
297
277
|
#
|
298
278
|
# @see #tap_result
|
299
|
-
def yield_result
|
300
|
-
clone.yield_result!(:
|
301
|
-
end
|
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
|
+
|
305
287
|
# @!visibility public
|
306
288
|
#
|
307
289
|
# As #chain, but modifies the current command instead of creating a clone.
|
@@ -318,13 +300,11 @@ module Cuprum
|
|
318
300
|
# @param on [Symbol] Sets a condition on when the chained block can run,
|
319
301
|
# based on the previous result. Valid values are :success, :failure, and
|
320
302
|
# :always. If the value is :success, the block will be called only if
|
321
|
-
# the previous result succeeded
|
322
|
-
#
|
323
|
-
#
|
324
|
-
#
|
325
|
-
#
|
326
|
-
# previous command was a success or a failure, but not if the command
|
327
|
-
# chain has been halted.
|
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.
|
328
308
|
#
|
329
309
|
# @overload chain!(on: nil) { |value| }
|
330
310
|
# Creates an anonymous command from the given block. The command will be
|
@@ -333,34 +313,36 @@ module Cuprum
|
|
333
313
|
# @param on [Symbol] Sets a condition on when the chained block can run,
|
334
314
|
# based on the previous result. Valid values are :success, :failure, and
|
335
315
|
# :always. If the value is :success, the block will be called only if
|
336
|
-
# the previous result succeeded
|
337
|
-
#
|
338
|
-
#
|
339
|
-
#
|
340
|
-
#
|
341
|
-
# previous command was a success or a failure, but not if the command
|
342
|
-
# chain has been halted.
|
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.
|
343
321
|
#
|
344
322
|
# @yieldparam value [Object] The value of the previous result.
|
345
|
-
def chain!
|
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
|
+
|
346
329
|
command ||= Cuprum::Command.new(&block)
|
347
330
|
|
348
331
|
chained_procs <<
|
349
332
|
{
|
350
|
-
:
|
351
|
-
:
|
333
|
+
proc: chain_command(command),
|
334
|
+
on: on
|
352
335
|
} # end hash
|
353
336
|
|
354
337
|
self
|
355
|
-
end
|
338
|
+
end
|
339
|
+
# rubocop:enable Metrics/MethodLength
|
356
340
|
|
357
341
|
def chained_procs
|
358
342
|
@chained_procs ||= []
|
359
|
-
end
|
343
|
+
end
|
360
344
|
|
361
|
-
|
362
|
-
yield_chain(super)
|
363
|
-
end # method call
|
345
|
+
# rubocop:disable Metrics/MethodLength
|
364
346
|
|
365
347
|
# @!visibility public
|
366
348
|
#
|
@@ -375,17 +357,23 @@ module Cuprum
|
|
375
357
|
# @return (see #tap_result)
|
376
358
|
#
|
377
359
|
# @see #tap_result
|
378
|
-
def 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
|
+
|
379
366
|
tapped = ->(result) { result.tap { block.call(result) } }
|
380
367
|
|
381
368
|
chained_procs <<
|
382
369
|
{
|
383
|
-
:
|
384
|
-
:
|
370
|
+
proc: tapped,
|
371
|
+
on: on
|
385
372
|
} # end hash
|
386
373
|
|
387
374
|
self
|
388
|
-
end
|
375
|
+
end
|
376
|
+
# rubocop:enable Metrics/MethodLength
|
389
377
|
|
390
378
|
# @!visibility public
|
391
379
|
#
|
@@ -400,51 +388,54 @@ module Cuprum
|
|
400
388
|
# @return (see #yield_result)
|
401
389
|
#
|
402
390
|
# @see #yield_result
|
403
|
-
def 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
|
+
|
404
397
|
chained_procs <<
|
405
398
|
{
|
406
|
-
:
|
407
|
-
:
|
399
|
+
proc: block,
|
400
|
+
on: on
|
408
401
|
} # end hash
|
409
402
|
|
410
403
|
self
|
411
|
-
end
|
404
|
+
end
|
412
405
|
|
413
406
|
private
|
414
407
|
|
415
|
-
def chain_command
|
408
|
+
def chain_command(command)
|
416
409
|
if command.arity.zero?
|
417
|
-
->(
|
410
|
+
->(_result) { command.call }
|
418
411
|
else
|
419
|
-
->(result) { command.
|
420
|
-
end
|
421
|
-
end
|
412
|
+
->(result) { command.call(result.value) }
|
413
|
+
end
|
414
|
+
end
|
422
415
|
|
423
|
-
def skip_chained_proc?
|
416
|
+
def skip_chained_proc?(last_result, on:)
|
424
417
|
return false if on == :always
|
425
418
|
|
426
|
-
return true if last_result.respond_to?(:halted?) && last_result.halted?
|
427
|
-
|
428
419
|
case on
|
429
420
|
when :success
|
430
421
|
!last_result.success?
|
431
422
|
when :failure
|
432
423
|
!last_result.failure?
|
433
|
-
end
|
434
|
-
end
|
424
|
+
end
|
425
|
+
end
|
435
426
|
|
436
|
-
def yield_chain
|
427
|
+
def yield_chain(first_result)
|
437
428
|
chained_procs.reduce(first_result) do |result, hsh|
|
438
|
-
next result if skip_chained_proc?(result, :
|
429
|
+
next result if skip_chained_proc?(result, on: hsh[:on])
|
439
430
|
|
440
431
|
value = hsh.fetch(:proc).call(result)
|
441
432
|
|
442
433
|
if value_is_result?(value)
|
443
|
-
value.
|
434
|
+
value.to_cuprum_result
|
444
435
|
else
|
445
|
-
build_result(value)
|
446
|
-
end
|
447
|
-
end
|
448
|
-
end
|
449
|
-
end
|
450
|
-
end
|
436
|
+
build_result(value: value)
|
437
|
+
end
|
438
|
+
end
|
439
|
+
end
|
440
|
+
end
|
441
|
+
end
|