cuprum 0.9.1 → 0.11.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|