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