cuprum 0.9.1 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +70 -0
  3. data/DEVELOPMENT.md +42 -53
  4. data/README.md +728 -536
  5. data/lib/cuprum.rb +12 -6
  6. data/lib/cuprum/built_in.rb +3 -1
  7. data/lib/cuprum/built_in/identity_command.rb +6 -4
  8. data/lib/cuprum/built_in/identity_operation.rb +4 -2
  9. data/lib/cuprum/built_in/null_command.rb +5 -3
  10. data/lib/cuprum/built_in/null_operation.rb +4 -2
  11. data/lib/cuprum/command.rb +37 -59
  12. data/lib/cuprum/command_factory.rb +50 -24
  13. data/lib/cuprum/currying.rb +79 -0
  14. data/lib/cuprum/currying/curried_command.rb +116 -0
  15. data/lib/cuprum/error.rb +44 -10
  16. data/lib/cuprum/errors.rb +2 -0
  17. data/lib/cuprum/errors/command_not_implemented.rb +6 -3
  18. data/lib/cuprum/errors/operation_not_called.rb +6 -6
  19. data/lib/cuprum/errors/uncaught_exception.rb +55 -0
  20. data/lib/cuprum/exception_handling.rb +50 -0
  21. data/lib/cuprum/matcher.rb +90 -0
  22. data/lib/cuprum/matcher_list.rb +150 -0
  23. data/lib/cuprum/matching.rb +232 -0
  24. data/lib/cuprum/matching/match_clause.rb +65 -0
  25. data/lib/cuprum/middleware.rb +210 -0
  26. data/lib/cuprum/operation.rb +17 -15
  27. data/lib/cuprum/processing.rb +10 -14
  28. data/lib/cuprum/result.rb +2 -4
  29. data/lib/cuprum/result_helpers.rb +22 -0
  30. data/lib/cuprum/rspec/be_a_result.rb +10 -1
  31. data/lib/cuprum/rspec/be_a_result_matcher.rb +5 -7
  32. data/lib/cuprum/rspec/be_callable.rb +14 -0
  33. data/lib/cuprum/steps.rb +233 -0
  34. data/lib/cuprum/utils.rb +3 -1
  35. data/lib/cuprum/utils/instance_spy.rb +37 -30
  36. data/lib/cuprum/version.rb +13 -10
  37. metadata +34 -19
  38. data/lib/cuprum/chaining.rb +0 -420
data/lib/cuprum/utils.rb CHANGED
@@ -1,6 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'cuprum'
2
4
 
3
5
  module Cuprum
4
6
  # Namespace for utility modules.
5
7
  module Utils; end
6
- end # module
8
+ end
@@ -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 # spy_on
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 *_args, █ end
38
- end # class
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 # method clear_spies
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 command_class
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
- begin
78
- instance_spy = assign_spy(command_class)
79
+ instance_spy = assign_spy(command_class)
79
80
 
80
- yield instance_spy
81
- end # begin-ensure
81
+ yield instance_spy
82
82
  else
83
83
  assign_spy(command_class)
84
- end # if-else
85
- end # method spy_on
84
+ end
85
+ end
86
86
 
87
87
  private
88
88
 
89
- def assign_spy command_class
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 # method assign_spy
95
+ end
96
96
 
97
97
  def build_spy
98
98
  Cuprum::Utils::InstanceSpy::Spy.new
99
- end # method build_spy
99
+ end
100
100
 
101
- def call_spies_for command, *args, &block
101
+ def call_spies_for(command, *args, &block)
102
102
  spies_for(command).each { |spy| spy.call(*args, &block) }
103
- end # method call_spies_for
103
+ end
104
104
 
105
- def guard_spy_class! command_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 # method guard_spy_class!
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 # method instrument_call!
120
+ end
121
121
 
122
122
  def spies
123
123
  Thread.current[:cuprum_instance_spies] ||= {}
124
- end # method spies
124
+ end
125
125
 
126
- def spies_for command
126
+ def spies_for(command)
127
127
  spies.select { |mod, _| command.is_a?(mod) }.map { |_, spy| spy }
128
- end # method spies_for
129
- end # eigenclass
130
-
131
- # (see Cuprum::Command#call)
132
- def call *args, &block
133
- Cuprum::Utils::InstanceSpy.send(:call_spies_for, self, *args, &block)
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 # method call
137
- end # module
138
- end # module
143
+ end
144
+ end
145
+ end
@@ -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 = 9
13
+ MINOR = 11
12
14
  # Patch version.
13
- PATCH = 1
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 << ".#{prerelease}" if prerelease
33
+ str = "#{str}.#{prerelease}" if prerelease
32
34
 
33
35
  build = value_of(:BUILD)
34
- str << ".#{build}" if build
36
+ str = "#{str}.#{build}" if build
35
37
 
36
38
  str
37
- end # class method to_version
39
+ end
38
40
 
39
41
  private
40
42
 
41
- def value_of constant
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 # method value_of
50
- end # eigenclass
51
- end # module
51
+ end
52
+ end
53
+ end
52
54
 
55
+ # The current version of the gem.
53
56
  VERSION = Version.to_gem_version
54
- end # module
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.9.1
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: 2019-12-08 00:00:00.000000000 Z
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.7'
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.7'
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.6'
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.6'
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.3'
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.3'
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: 0.49.1
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: 0.49.1
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.15'
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.15'
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: An opinionated implementation of the Command pattern for Ruby applications.
98
- Cuprum wraps your business logic in a consistent, object-oriented interface and
99
- features status and error management, composability and control flow management.
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: '0'
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.0.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.
@@ -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