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