cuprum 0.9.1 → 0.11.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +70 -0
- data/DEVELOPMENT.md +42 -53
- data/README.md +728 -536
- data/lib/cuprum.rb +12 -6
- data/lib/cuprum/built_in.rb +3 -1
- data/lib/cuprum/built_in/identity_command.rb +6 -4
- data/lib/cuprum/built_in/identity_operation.rb +4 -2
- data/lib/cuprum/built_in/null_command.rb +5 -3
- data/lib/cuprum/built_in/null_operation.rb +4 -2
- data/lib/cuprum/command.rb +37 -59
- data/lib/cuprum/command_factory.rb +50 -24
- data/lib/cuprum/currying.rb +79 -0
- data/lib/cuprum/currying/curried_command.rb +116 -0
- data/lib/cuprum/error.rb +44 -10
- data/lib/cuprum/errors.rb +2 -0
- data/lib/cuprum/errors/command_not_implemented.rb +6 -3
- data/lib/cuprum/errors/operation_not_called.rb +6 -6
- data/lib/cuprum/errors/uncaught_exception.rb +55 -0
- data/lib/cuprum/exception_handling.rb +50 -0
- data/lib/cuprum/matcher.rb +90 -0
- data/lib/cuprum/matcher_list.rb +150 -0
- data/lib/cuprum/matching.rb +232 -0
- data/lib/cuprum/matching/match_clause.rb +65 -0
- data/lib/cuprum/middleware.rb +210 -0
- data/lib/cuprum/operation.rb +17 -15
- data/lib/cuprum/processing.rb +10 -14
- data/lib/cuprum/result.rb +2 -4
- data/lib/cuprum/result_helpers.rb +22 -0
- data/lib/cuprum/rspec/be_a_result.rb +10 -1
- data/lib/cuprum/rspec/be_a_result_matcher.rb +5 -7
- data/lib/cuprum/rspec/be_callable.rb +14 -0
- data/lib/cuprum/steps.rb +233 -0
- data/lib/cuprum/utils.rb +3 -1
- data/lib/cuprum/utils/instance_spy.rb +37 -30
- data/lib/cuprum/version.rb +13 -10
- metadata +34 -19
- data/lib/cuprum/chaining.rb +0 -420
data/lib/cuprum/utils.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'cuprum/utils'
|
2
4
|
|
3
5
|
module Cuprum::Utils
|
@@ -28,14 +30,14 @@ module Cuprum::Utils
|
|
28
30
|
# expect(spy).to receive(:call)
|
29
31
|
#
|
30
32
|
# CustomCommand.new.call
|
31
|
-
# end
|
33
|
+
# end
|
32
34
|
module InstanceSpy
|
33
35
|
# Minimal class that implements a #call method to mirror method calls to
|
34
36
|
# instances of an instrumented command class.
|
35
37
|
class Spy
|
36
38
|
# Empty method that accepts any arguments and an optional block.
|
37
|
-
def call
|
38
|
-
end
|
39
|
+
def call(*_args, &block); end
|
40
|
+
end
|
39
41
|
|
40
42
|
class << self
|
41
43
|
# Retires all spies. Subsequent calls to the #call method on command
|
@@ -44,7 +46,7 @@ module Cuprum::Utils
|
|
44
46
|
Thread.current[:cuprum_instance_spies] = nil
|
45
47
|
|
46
48
|
nil
|
47
|
-
end
|
49
|
+
end
|
48
50
|
|
49
51
|
# Finds or creates a spy object for the given module or class. Each time
|
50
52
|
# that the #call method is called for an object of the given type, the
|
@@ -68,41 +70,39 @@ module Cuprum::Utils
|
|
68
70
|
# @yield [Cuprum::Utils::InstanceSpy::Spy] The instance spy.
|
69
71
|
#
|
70
72
|
# @return [nil] nil.
|
71
|
-
def spy_on
|
73
|
+
def spy_on(command_class)
|
72
74
|
guard_spy_class!(command_class)
|
73
75
|
|
74
76
|
instrument_call!
|
75
77
|
|
76
78
|
if block_given?
|
77
|
-
|
78
|
-
instance_spy = assign_spy(command_class)
|
79
|
+
instance_spy = assign_spy(command_class)
|
79
80
|
|
80
|
-
|
81
|
-
end # begin-ensure
|
81
|
+
yield instance_spy
|
82
82
|
else
|
83
83
|
assign_spy(command_class)
|
84
|
-
end
|
85
|
-
end
|
84
|
+
end
|
85
|
+
end
|
86
86
|
|
87
87
|
private
|
88
88
|
|
89
|
-
def assign_spy
|
89
|
+
def assign_spy(command_class)
|
90
90
|
existing_spy = spies[command_class]
|
91
91
|
|
92
92
|
return existing_spy if existing_spy
|
93
93
|
|
94
94
|
spies[command_class] = build_spy
|
95
|
-
end
|
95
|
+
end
|
96
96
|
|
97
97
|
def build_spy
|
98
98
|
Cuprum::Utils::InstanceSpy::Spy.new
|
99
|
-
end
|
99
|
+
end
|
100
100
|
|
101
|
-
def call_spies_for
|
101
|
+
def call_spies_for(command, *args, &block)
|
102
102
|
spies_for(command).each { |spy| spy.call(*args, &block) }
|
103
|
-
end
|
103
|
+
end
|
104
104
|
|
105
|
-
def guard_spy_class!
|
105
|
+
def guard_spy_class!(command_class)
|
106
106
|
return if command_class.is_a?(Module) && !command_class.is_a?(Class)
|
107
107
|
|
108
108
|
return if command_class.is_a?(Class) &&
|
@@ -111,28 +111,35 @@ module Cuprum::Utils
|
|
111
111
|
raise ArgumentError,
|
112
112
|
'must be a class inheriting from Cuprum::Command',
|
113
113
|
caller(1..-1)
|
114
|
-
end
|
114
|
+
end
|
115
115
|
|
116
116
|
def instrument_call!
|
117
117
|
return if Cuprum::Command < Cuprum::Utils::InstanceSpy
|
118
118
|
|
119
119
|
Cuprum::Command.prepend(Cuprum::Utils::InstanceSpy)
|
120
|
-
end
|
120
|
+
end
|
121
121
|
|
122
122
|
def spies
|
123
123
|
Thread.current[:cuprum_instance_spies] ||= {}
|
124
|
-
end
|
124
|
+
end
|
125
125
|
|
126
|
-
def spies_for
|
126
|
+
def spies_for(command)
|
127
127
|
spies.select { |mod, _| command.is_a?(mod) }.map { |_, spy| spy }
|
128
|
-
end
|
129
|
-
end
|
130
|
-
|
131
|
-
# (see Cuprum::
|
132
|
-
def call
|
133
|
-
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# (see Cuprum::Processing#call)
|
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
|
-
end
|
137
|
-
end
|
138
|
-
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
data/lib/cuprum/version.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Cuprum
|
2
4
|
# @api private
|
3
5
|
#
|
@@ -8,9 +10,9 @@ module Cuprum
|
|
8
10
|
# Major version.
|
9
11
|
MAJOR = 0
|
10
12
|
# Minor version.
|
11
|
-
MINOR =
|
13
|
+
MINOR = 11
|
12
14
|
# Patch version.
|
13
|
-
PATCH =
|
15
|
+
PATCH = 0
|
14
16
|
# Prerelease version.
|
15
17
|
PRERELEASE = nil
|
16
18
|
# Build metadata.
|
@@ -28,17 +30,17 @@ module Cuprum
|
|
28
30
|
str = "#{MAJOR}.#{MINOR}.#{PATCH}"
|
29
31
|
|
30
32
|
prerelease = value_of(:PRERELEASE)
|
31
|
-
str
|
33
|
+
str = "#{str}.#{prerelease}" if prerelease
|
32
34
|
|
33
35
|
build = value_of(:BUILD)
|
34
|
-
str
|
36
|
+
str = "#{str}.#{build}" if build
|
35
37
|
|
36
38
|
str
|
37
|
-
end
|
39
|
+
end
|
38
40
|
|
39
41
|
private
|
40
42
|
|
41
|
-
def value_of
|
43
|
+
def value_of(constant)
|
42
44
|
return nil unless const_defined?(constant)
|
43
45
|
|
44
46
|
value = const_get(constant)
|
@@ -46,9 +48,10 @@ module Cuprum
|
|
46
48
|
return nil if value.respond_to?(:empty?) && value.empty?
|
47
49
|
|
48
50
|
value
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
52
54
|
|
55
|
+
# The current version of the gem.
|
53
56
|
VERSION = Version.to_gem_version
|
54
|
-
end
|
57
|
+
end
|
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.
|
4
|
+
version: 0.11.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:
|
11
|
+
date: 2021-07-26 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
|
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
|
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.
|
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.
|
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.
|
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.
|
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:
|
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:
|
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
|
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
|
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:
|
98
|
-
|
99
|
-
|
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: []
|
@@ -113,26 +115,39 @@ files:
|
|
113
115
|
- lib/cuprum/built_in/identity_operation.rb
|
114
116
|
- lib/cuprum/built_in/null_command.rb
|
115
117
|
- lib/cuprum/built_in/null_operation.rb
|
116
|
-
- lib/cuprum/chaining.rb
|
117
118
|
- lib/cuprum/command.rb
|
118
119
|
- lib/cuprum/command_factory.rb
|
120
|
+
- lib/cuprum/currying.rb
|
121
|
+
- lib/cuprum/currying/curried_command.rb
|
119
122
|
- lib/cuprum/error.rb
|
120
123
|
- lib/cuprum/errors.rb
|
121
124
|
- lib/cuprum/errors/command_not_implemented.rb
|
122
125
|
- lib/cuprum/errors/operation_not_called.rb
|
126
|
+
- lib/cuprum/errors/uncaught_exception.rb
|
127
|
+
- lib/cuprum/exception_handling.rb
|
128
|
+
- lib/cuprum/matcher.rb
|
129
|
+
- lib/cuprum/matcher_list.rb
|
130
|
+
- lib/cuprum/matching.rb
|
131
|
+
- lib/cuprum/matching/match_clause.rb
|
132
|
+
- lib/cuprum/middleware.rb
|
123
133
|
- lib/cuprum/operation.rb
|
124
134
|
- lib/cuprum/processing.rb
|
125
135
|
- lib/cuprum/result.rb
|
136
|
+
- lib/cuprum/result_helpers.rb
|
126
137
|
- lib/cuprum/rspec.rb
|
127
138
|
- lib/cuprum/rspec/be_a_result.rb
|
128
139
|
- lib/cuprum/rspec/be_a_result_matcher.rb
|
140
|
+
- lib/cuprum/rspec/be_callable.rb
|
141
|
+
- lib/cuprum/steps.rb
|
129
142
|
- lib/cuprum/utils.rb
|
130
143
|
- lib/cuprum/utils/instance_spy.rb
|
131
144
|
- lib/cuprum/version.rb
|
132
145
|
homepage: http://sleepingkingstudios.com
|
133
146
|
licenses:
|
134
147
|
- MIT
|
135
|
-
metadata:
|
148
|
+
metadata:
|
149
|
+
bug_tracker_uri: https://github.com/sleepingkingstudios/cuprum/issues
|
150
|
+
source_code_uri: https://github.com/sleepingkingstudios/cuprum
|
136
151
|
post_install_message:
|
137
152
|
rdoc_options: []
|
138
153
|
require_paths:
|
@@ -141,14 +156,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
141
156
|
requirements:
|
142
157
|
- - ">="
|
143
158
|
- !ruby/object:Gem::Version
|
144
|
-
version:
|
159
|
+
version: 2.5.0
|
145
160
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
146
161
|
requirements:
|
147
162
|
- - ">="
|
148
163
|
- !ruby/object:Gem::Version
|
149
164
|
version: '0'
|
150
165
|
requirements: []
|
151
|
-
rubygems_version: 3.
|
166
|
+
rubygems_version: 3.1.4
|
152
167
|
signing_key:
|
153
168
|
specification_version: 4
|
154
169
|
summary: An opinionated implementation of the Command pattern.
|
data/lib/cuprum/chaining.rb
DELETED
@@ -1,420 +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, &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
|
-
# @!visibility public
|
286
|
-
#
|
287
|
-
# As #chain, but modifies the current command instead of creating a clone.
|
288
|
-
# This is a protected method, and is meant to be called by the command to be
|
289
|
-
# chained, such as during #initialize.
|
290
|
-
#
|
291
|
-
# @return [Cuprum::Chaining] The current command.
|
292
|
-
#
|
293
|
-
# @see #chain
|
294
|
-
#
|
295
|
-
# @overload chain!(command, on: nil)
|
296
|
-
# @param command [Cuprum::Command] The command to chain.
|
297
|
-
#
|
298
|
-
# @param on [Symbol] Sets a condition on when the chained block can run,
|
299
|
-
# based on the previous result. Valid values are :success, :failure, and
|
300
|
-
# :always. If the value is :success, the block will be called only if
|
301
|
-
# the previous result succeeded. If the value is :failure, the block
|
302
|
-
# will be called only if the previous result failed. If the value is
|
303
|
-
# :always, the block will be called regardless of the previous result
|
304
|
-
# status. If no value is given, the command will run whether the
|
305
|
-
# previous command was a success or a failure.
|
306
|
-
#
|
307
|
-
# @overload chain!(on: nil) { |value| }
|
308
|
-
# Creates an anonymous command from the given block. The command will be
|
309
|
-
# passed the value of the previous result.
|
310
|
-
#
|
311
|
-
# @param on [Symbol] Sets a condition on when the chained block can run,
|
312
|
-
# based on the previous result. Valid values are :success, :failure, and
|
313
|
-
# :always. If the value is :success, the block will be called only if
|
314
|
-
# the previous result succeeded. If the value is :failure, the block
|
315
|
-
# will be called only if the previous result failed. If the value is
|
316
|
-
# :always, the block will be called regardless of the previous result
|
317
|
-
# status. If no value is given, the command will run whether the
|
318
|
-
# previous command was a success or a failure.
|
319
|
-
#
|
320
|
-
# @yieldparam value [Object] The value of the previous result.
|
321
|
-
def chain!(command = nil, on: nil, &block)
|
322
|
-
command ||= Cuprum::Command.new(&block)
|
323
|
-
|
324
|
-
chained_procs <<
|
325
|
-
{
|
326
|
-
proc: chain_command(command),
|
327
|
-
on: on
|
328
|
-
} # end hash
|
329
|
-
|
330
|
-
self
|
331
|
-
end
|
332
|
-
|
333
|
-
def chained_procs
|
334
|
-
@chained_procs ||= []
|
335
|
-
end
|
336
|
-
|
337
|
-
# @!visibility public
|
338
|
-
#
|
339
|
-
# As #tap_result, but modifies the current command instead of creating a
|
340
|
-
# clone. This is a protected method, and is meant to be called by the
|
341
|
-
# command to be chained, such as during #initialize.
|
342
|
-
#
|
343
|
-
# @param (see #tap_result)
|
344
|
-
#
|
345
|
-
# @yieldparam result [Cuprum::Result] The #result of the previous command.
|
346
|
-
#
|
347
|
-
# @return (see #tap_result)
|
348
|
-
#
|
349
|
-
# @see #tap_result
|
350
|
-
def tap_result!(on: nil, &block)
|
351
|
-
tapped = ->(result) { result.tap { block.call(result) } }
|
352
|
-
|
353
|
-
chained_procs <<
|
354
|
-
{
|
355
|
-
proc: tapped,
|
356
|
-
on: on
|
357
|
-
} # end hash
|
358
|
-
|
359
|
-
self
|
360
|
-
end
|
361
|
-
|
362
|
-
# @!visibility public
|
363
|
-
#
|
364
|
-
# As #yield_result, but modifies the current command instead of creating a
|
365
|
-
# clone. This is a protected method, and is meant to be called by the
|
366
|
-
# command to be chained, such as during #initialize.
|
367
|
-
#
|
368
|
-
# @param (see #yield_result)
|
369
|
-
#
|
370
|
-
# @yieldparam result [Cuprum::Result] The #result of the previous command.
|
371
|
-
#
|
372
|
-
# @return (see #yield_result)
|
373
|
-
#
|
374
|
-
# @see #yield_result
|
375
|
-
def yield_result!(on: nil, &block)
|
376
|
-
chained_procs <<
|
377
|
-
{
|
378
|
-
proc: block,
|
379
|
-
on: on
|
380
|
-
} # end hash
|
381
|
-
|
382
|
-
self
|
383
|
-
end
|
384
|
-
|
385
|
-
private
|
386
|
-
|
387
|
-
def chain_command(command)
|
388
|
-
if command.arity.zero?
|
389
|
-
->(_result) { command.call }
|
390
|
-
else
|
391
|
-
->(result) { command.call(result.value) }
|
392
|
-
end
|
393
|
-
end
|
394
|
-
|
395
|
-
def skip_chained_proc?(last_result, on:)
|
396
|
-
return false if on == :always
|
397
|
-
|
398
|
-
case on
|
399
|
-
when :success
|
400
|
-
!last_result.success?
|
401
|
-
when :failure
|
402
|
-
!last_result.failure?
|
403
|
-
end
|
404
|
-
end
|
405
|
-
|
406
|
-
def yield_chain(first_result)
|
407
|
-
chained_procs.reduce(first_result) do |result, hsh|
|
408
|
-
next result if skip_chained_proc?(result, on: hsh[:on])
|
409
|
-
|
410
|
-
value = hsh.fetch(:proc).call(result)
|
411
|
-
|
412
|
-
if value_is_result?(value)
|
413
|
-
value.to_cuprum_result
|
414
|
-
else
|
415
|
-
build_result(value: value)
|
416
|
-
end
|
417
|
-
end
|
418
|
-
end
|
419
|
-
end
|
420
|
-
end
|