class Slayer::ResultMatcher

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 {Result#success?}, calls to {#fail} to a {Result} that returns true for {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 {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 {Result#success?} and a {Result#failure?}. If both are not handled, the matching block will not be invoked and a {CommandResultNotHandledError} will be raised.

@example 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!"

@example 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!"

@example 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!"

@example 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)

Attributes

command[R]
result[R]

Public Class Methods

new(result, command) click to toggle source

@api private

# 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

Public Instance Methods

all(*statuses, &block) click to toggle source

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

@param statuses [Array<status>] Statuses that should be compared to the {Result}. If

any of provided statuses match the {Result} this block will be considered a match.
The symbol +:default+ can also be used to indicate that this should match any {Result}
not matched by other matchers.

If no value is provided for statuses it defaults to +:default+.
# 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) click to toggle source

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.

# File lib/slayer/result_matcher.rb, line 181
def ensure(&block)
  @ensure_block = block
end
execute_ensure_block() click to toggle source

Executes the ensure block if one exists. @api private

# File lib/slayer/result_matcher.rb, line 210
def execute_ensure_block
  run_block(@ensure_block) if @ensure_block != false # nil should pass this test
end
execute_matching_block() click to toggle source

Executes the provided block that best matched the {Result}. If no block matched nothing is executed

@api private

# File lib/slayer/result_matcher.rb, line 196
def execute_matching_block
  if @matching_block != false # nil should pass this test
    run_block(@matching_block)
  elsif @matching_all != false
    run_block(@matching_all)
  elsif @default_block != false
    run_block(@default_block)
  elsif @default_all
    run_block(@default_all)
  end
end
fail(*statuses, &block) click to toggle source

Provide a block that should be invoked if the {Result} is a failure.

@param statuses [Array<status>] Statuses that should be compared to the {Result}. If

any of provided statuses match the {Result} this block will be considered a match.
The symbol +:default+ can also be used to indicate that this should match any {Result}
not matched by other matchers.

If no value is provided for statuses it defaults to +:default+.
# 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?() click to toggle source

@return Whether both the pass and the fail defaults have been handled.

@api private

# File lib/slayer/result_matcher.rb, line 188
def handled_defaults?
  return @handled_default_pass && @handled_default_fail
end
pass(*statuses, &block) click to toggle source

Provide a block that should be invoked if the {Result} is a success.

@param statuses [Array<status>] Statuses that should be compared to the {Result}. If

any of provided statuses match the {Result} this block will be considered a match.
The symbol +:default+ can also be used to indicate that this should match any {Result}
not matched by other matchers.

If no value is provided for statuses it defaults to +:default+.
# 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

Private Instance Methods

run_block(block) click to toggle source
# File lib/slayer/result_matcher.rb, line 216
def run_block(block)
  block.call(@result.value, @result, @command) if block # explicit nil should fail this test
end