class Coach::Handler

Constants

STATUS_CODE_FOR_EXCEPTIONS

Attributes

config[R]
name[R]

Public Class Methods

new(middleware, config = {}) click to toggle source
# File lib/coach/handler.rb, line 12
def initialize(middleware, config = {})
  @config = config
  if middleware.is_a?(String)
    @name = middleware
  else
    @middleware = middleware
    @name = middleware.name
    # This triggers validation of the middleware chain, to raise any errors early on.
    root_item
  end
end

Public Instance Methods

build_request_chain(sequence, context) click to toggle source

Given a middleware sequence, filter out items not applicable to the current request, and set up a chain of instantiated middleware objects, ready to serve a request.

# File lib/coach/handler.rb, line 73
def build_request_chain(sequence, context)
  sequence.reverse.reduce(nil) do |successor, item|
    item.build_middleware(context, successor)
  end
end
build_sequence(item, context) click to toggle source

Traverse the middlware tree to build a linear middleware sequence, containing only middlewares that apply to this request.

# File lib/coach/handler.rb, line 62
def build_sequence(item, context)
  sequence = item.middleware.middleware_dependencies.map do |child_item|
    build_sequence(child_item.set_parent(item), context)
  end

  dedup_sequence([*sequence, item].flatten)
end
call(env) click to toggle source

The Rack interface to handler - builds a middleware chain based on the current request, and invokes it. rubocop:disable Metrics/MethodLength

# File lib/coach/handler.rb, line 29
def call(env)
  context = { request: ActionDispatch::Request.new(env) }
  sequence = build_sequence(root_item, context)
  chain = build_request_chain(sequence, context)

  event = build_event(context)

  publish("start_handler.coach", event.dup)
  instrument("finish_handler.coach", event) do
    response = chain.instrument.call
  rescue StandardError => e
    raise
  ensure
    # We want to populate the response and metadata fields after the middleware
    # chain has completed so that the end of the instrumentation can see them. The
    # simplest way to do this is pass the event by reference to ActiveSupport, then
    # modify the hash to contain this detail before the instrumentation completes.
    #
    # This way, the last finish_handler.coach event will have all the details.
    status = response.try(:first) || STATUS_CODE_FOR_EXCEPTIONS
    event.merge!(
      response: {
        status: status,
        exception: e,
      }.compact,
      metadata: context.fetch(:_metadata, {}),
    )
  end
end
inspect() click to toggle source
# File lib/coach/handler.rb, line 79
def inspect
  "#<Coach::Handler[#{name}]>"
end

Private Instance Methods

build_event(context) click to toggle source

Event to send with notifications

# File lib/coach/handler.rb, line 106
def build_event(context)
  { middleware: name, request: context[:request] }
end
dedup_sequence(sequence) click to toggle source

Remove middleware that have been included multiple times with the same config, leaving only the first instance

# File lib/coach/handler.rb, line 101
def dedup_sequence(sequence)
  sequence.uniq { |item| [item.class, item.middleware, item.config] }
end
middleware() click to toggle source
# File lib/coach/handler.rb, line 95
def middleware
  @middleware ||= ActiveSupport::Dependencies.constantize(name)
end
root_item() click to toggle source
# File lib/coach/handler.rb, line 87
def root_item
  @root_item ||= MiddlewareItem.new(middleware, config).tap(&:validate!)
rescue Coach::Errors::MiddlewareDependencyNotMet => e
  # Remove noise of validation stack trace, reset to the handler callsite
  e.backtrace.clear.concat(Thread.current.backtrace)
  raise e
end