Class: Slayer::ResultMatcher

Inherits:
Object
  • Object
show all
Defined in:
lib/slayer/result_matcher.rb

Overview

ResultMatcher is the object passed to the block of a Command.call. The ResultMatcher allows the block-author to specify which piece of logic they would like to invoke based on the state of the Result object.

In the event that multiple blocks match the Result, only the most specific matching block will be invoked. Status matches take precedence over default matches. If there are two blocks with a matching status, the pass/fail block takes precedence over the all block.

Matching based on success or failure

The ResultMatcher matches calls to #pass to a Result that returns true for Slayer::Result#success?, calls to #fail to a Result that returns true for Slayer::Result#failure?, and calls to #all to a Result in either state.

A matching call to #pass or #fail takes precedence over matching calls to #all

Matching based on status

Additionally, the ResultMatcher can also match by the Slayer::Result#status. If a status or statuses is passed to #pass, #fail, or #all, these will only be invoked if the status of the Result matches the passed in status.

If the default block is the same as the block for one of the statuses the status :default can be used to indicate which block should be used as the default. Successful status matches take precedence over default matchers.

Both pass and fail must be handled

If the block form of a Command.call is invoked, both the block must handle the default status for both a Slayer::Result#success? and a Slayer::Result#failure?. If both are not handled, the matching block will not be invoked and a CommandResultNotHandledError will be raised.

Examples:

Matcher invokes the matching pass block, with precedence given to #pass and #fail

# Call produces a successful Result
SuccessCommand.call do |m|
  m.pass { puts "Pass!" }
  m.fail { puts "Fail!" }
  m.all  { puts "All!"  } # will never be invoked, due to both a pass and fail response existing
end
# => prints "Pass!"

Matcher invokes the matching status of the result object, or the default

# Call produces a successful Result with status :ok
SuccessCommand.call do |m|
  m.pass(:ok) { puts "Pass, OK!" }
  m.pass      { puts "Pass, default!" }
  m.fail      { puts "Fail!" }
end
# => prints "Pass, OK!"

# Call produces a successful Result with status :created
SuccessCommand.call do |m|
  m.pass(:ok) { puts "Pass, OK!" }
  m.pass      { puts "Pass, default!" }
  m.fail      { puts "Fail!" }
end
# => prints "Pass, default!"

Matcher invokes the explicitly indicated default block

# Call produces a successful Result with status :created
SuccessCommand.call do |m|
  m.pass(:ok, :default) { puts "Pass, OK!" }
  m.pass(:great)        { puts "Pass, default!" }
  m.fail                { puts "Fail!" }
end
# => prints "Pass, OK!"

Matcher must handle both pass and fail defaults.

# Call produces a successful Result with status :ok
SuccessCommand.call do |m|
  m.pass(:ok) { puts "Pass, OK!"}
  m.fail      { puts "Fail!" }
end
# => raises CommandResultNotHandledError (because no default pass was provided)

# Call produces a successful Result with status :ok
SuccessCommand.call do |m|
  m.pass(:ok, :default) { puts "Pass, OK!"}
  m.fail                { puts "Fail!" }
end
# => prints "Pass, OK!"

# Call produces a successful Result with status :ok
SuccessCommand.call do |m|
  m.pass(:ok) { puts "Pass, OK!"}
  m.all       { puts "All!" }
end
# => prints "Pass, OK!"

# Call produces a successful Result with status :ok
SuccessCommand.call do |m|
  m.pass(:ok, :default) { puts "Pass, OK!"}
end
# => raises CommandResultNotHandledError (because no default fail was provided)

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(result, command) ⇒ ResultMatcher

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns a new instance of ResultMatcher


102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/slayer/result_matcher.rb', line 102

def initialize(result, command)
  @result = result
  @command = command

  @status = result.status || :default

  @handled_default_pass = false
  @handled_default_fail = false

  # These are set to false if they are never set. If they are set to `nil` that
  # means the block intentionally passed `nil` as the block to be executed.
  @matching_block       = false
  @matching_all         = false
  @default_block        = false
  @default_all          = false
  @ensure_block         = false
end

Instance Attribute Details

#commandObject (readonly)

Returns the value of attribute command


99
100
101
# File 'lib/slayer/result_matcher.rb', line 99

def command
  @command
end

#resultObject (readonly)

Returns the value of attribute result


99
100
101
# File 'lib/slayer/result_matcher.rb', line 99

def result
  @result
end

Instance Method Details

#all(*statuses, &block) ⇒ Object

Provide a block that should be invoked for any Slayer::Result. This has a lower precedence that either #pass or #fail.


167
168
169
170
171
172
173
174
175
176
177
# File 'lib/slayer/result_matcher.rb', line 167

def all(*statuses, &block)
  statuses << :default if statuses.empty?
  @handled_default_pass ||= statuses.include?(:default)
  @handled_default_fail ||= statuses.include?(:default)

  block_is_match   = statuses.include?(@status)
  block_is_default = statuses.include?(:default)

  @matching_all = block if block_is_match
  @default_all  = block if block_is_default
end

#ensure(&block) ⇒ Object

Provide a block that should be always be invoked after other blocks have executed. This block will be invoked even if the other block raises an error.


181
182
183
# File 'lib/slayer/result_matcher.rb', line 181

def ensure(&block)
  @ensure_block = block
end

#execute_ensure_blockObject


209
210
211
212
213
214
215
# File 'lib/slayer/result_matcher.rb', line 209

def execute_ensure_block
  # rubocop:disable Style/IfUnlessModifier
  if @ensure_block != false # nil should pass this test
    @ensure_block.call(@result, @command)
  end
  # rubocop:enable Style/IfUnlessModifier
end

#execute_matching_blockObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Executes the provided block that best matched the Slayer::Result. If no block matched nothing is executed


196
197
198
199
200
201
202
203
204
205
206
207
# File 'lib/slayer/result_matcher.rb', line 196

def execute_matching_block
  if @matching_block != false # nil should pass this test
    @matching_block&.call(@result, @command) # explicit nil will not get called with
                                             # safe navigation (&.)
  elsif @matching_all != false
    @matching_all&.call(@result, @command)
  elsif @default_block != false
    @default_block&.call(@result, @command)
  elsif @default_all
    @default_all&.call(@result, @command)
  end
end

#fail(*statuses, &block) ⇒ Object

Provide a block that should be invoked if the Slayer::Result is a failure.


147
148
149
150
151
152
153
154
155
156
# File 'lib/slayer/result_matcher.rb', line 147

def fail(*statuses, &block)
  statuses << :default if statuses.empty?
  @handled_default_fail ||= statuses.include?(:default)

  block_is_match   = @result.failure? && statuses.include?(@status)
  block_is_default = @result.failure? && statuses.include?(:default)

  @matching_block = block if block_is_match
  @default_block  = block if block_is_default
end

#handled_defaults?Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns Whether both the pass and the fail defaults have been handled.


188
189
190
# File 'lib/slayer/result_matcher.rb', line 188

def handled_defaults?
  return @handled_default_pass && @handled_default_fail
end

#pass(*statuses, &block) ⇒ Object

Provide a block that should be invoked if the Slayer::Result is a success.


128
129
130
131
132
133
134
135
136
137
# File 'lib/slayer/result_matcher.rb', line 128

def pass(*statuses, &block)
  statuses << :default if statuses.empty?
  @handled_default_pass ||= statuses.include?(:default)

  block_is_match   = @result.success? && statuses.include?(@status)
  block_is_default = @result.success? && statuses.include?(:default)

  @matching_block = block if block_is_match
  @default_block  = block if block_is_default
end