class Substation::Chain

Implements a chain of responsibility for an action

An instance of this class will typically contain (in that order) a few processors that process the incoming {Request} object, one processor that calls an action ({Processor::Pivot}), and some processors that process the outgoing {Response} object.

@example chain processors (used in instance method examples)

module App

  class Processor

    def initialize(handler = nil)
      @handler = handler
    end

    protected

    attr_reader :handler

    class Incoming < self
      include Substation::Processor::Incoming
    end

    class Outgoing < self
      include Substation::Processor::Outgoing

      private

      def respond_with(response, output)
        response.class.new(response.request, output)
      end
    end
  end

  class Validator < Processor::Incoming
    def call(request)
      result = handler.call(request.input)
      if result.success?
        request.success(request.input)
      else
        request.error(result.output)
      end
    end
  end

  class Pivot < Handler
    include Substation::Chain::Pivot

    def call(request)
      handler.call(request)
    end
  end

  class Presenter < Processor::Outgoing
    def call(response)
      respond_with(response, handler.new(response.output))
    end
  end
end

Constants

EMPTY

Empty chain

Public Class Methods

exception_response(state, data, exception) click to toggle source

Return an exception response

@param [Request, Response] state

the initial state passed into the chain

@param [Object] data

the processed data available when the exception was raised

@param [Class<StandardError>] exception

the exception instance that was raised

@return [Response::Exception]

@api private

# File lib/substation/chain.rb, line 90
def self.exception_response(state, data, exception)
  output = Response::Exception::Output.new(data, exception)
  Response::Exception.new(state.to_request(data), output)
end

Public Instance Methods

call(request) click to toggle source

Call the chain

Invokes all processors and returns either the first {Response::Failure} that it encounters, or if all goes well, the {Response::Success} returned from the last processor.

@example

module App
  SOME_ACTION = Substation::Chain.new [
    Validator.new(MY_VALIDATOR),
    Pivot.new(Actions::SOME_ACTION),
    Presenter.new(Presenters::SomePresenter)
  ]

  env     = Object.new # your env would obviously differ
  input   = { 'name' => 'John' }
  request = Substation::Request.new(env, input)

  response = SOME_ACTION.call(request)

  if response.success?
    response.output # => the output wrapped in a presenter
  else
    response.output # => if validation, pivot or presenter failed
  end
end

@param [Request] request

the request to handle

@return [Response::Success]

the response returned from the last processor

@return [Response::Failure]

the response returned from the failing processor's failure chain

@return [Response::Exception]

the response returned from invoking the {#exception_chain}

@api public

# File lib/substation/chain.rb, line 137
def call(request)
  reduce(request) { |result, processor|
    begin
      response = processor.call(result)
      return response unless processor.success?(response)
      processor.result(response)
    rescue => exception
      return on_exception(request, result.data, exception)
    end
  }
end

Private Instance Methods

on_exception(state, data, exception) click to toggle source

Call the failure chain in case of an uncaught exception

@param [Request] request

the initial request passed into the chain

@param [Object] data

the processed data available when the exception was raised

@param [Class<StandardError>] exception

the exception instance that was raised

@return [Response::Exception]

@api private

# File lib/substation/chain.rb, line 165
def on_exception(state, data, exception)
  response = self.class.exception_response(state, data, exception)
  exception_chain.call(response)
end