require 'rack/utils'
require 'action_controller'
# The HTTPStatus module is the core of the http_status_exceptions gem and
# contains all functionality.
#
# The module contains HTTPStatus::Base class, which is used as a
# superclass for every HTTPStatus exception. Subclasses, like
# HTTPStatus::Forbidden or HTTPStatus::NotFound will be
# generated on demand by the HTTPStatus.const_missing method.
#
# Moreover, it contains methods to handle these exceptions and integrate this
# functionality into ActionController::Base. When this module is in
# included in the ActionController::Base class, it will call
# rescue_from on it to handle all HTTPStatus::Base
# exceptions with the HTTPStatus#http_status_exceptions method.
#
# The exception handler will try to render a response with the correct
# HTTPStatus. When no suitable template is found to render the exception with,
# it will simply respond with an empty HTTP status code.
module HTTPStatus
# The current gem release version. Do not set this value by hand, it will
# be done automatically by them gem release script.
VERSION = "0.3.0"
# The Base HTTP status exception class is used as superclass for every
# exception class that is constructed. It implements some shared
# functionality for finding the status code and determining the template
# path to render.
#
# Subclasses of this class will be generated on demand when a non-exisiting
# constant of the HTTPStatus module is requested. This is
# implemented in the HTTPStatus.const_missing method.
class Base < StandardError
# The path from which the error documents are loaded.
cattr_accessor :template_path
@@template_path = 'shared/http_status'
# The layout in which the error documents are rendered
cattr_accessor :template_layout
@@template_layout = nil # Use the standard layout template setting by default.
attr_reader :details
# Initializes the exception instance.
# message:: The exception message.
# details:: An object with details about the exception.
def initialize(message = nil, details = nil)
@details = details
super(message)
end
# Returns the HTTP status symbol corresponding to this class. This is one
# of the symbols that can be found in the map that can be found in
# ActionController::StatusCodes.
#
# This method should be overridden by subclasses, as it returns
# :internal_server_error by default. This is done automatically
# when a new exception class is being generated by
# HTTPStatus.const_missing.
def self.status
:internal_server_error
end
# Returns the HTTP status symbol (as defined by Rails) corresponding to
# this instance. By default, it calls the class method of the same name.
def status
self.class.status
end
# The numeric status code corresponding to this exception class. Uses the
# status symbol to code map in ActionController::StatusCodes.
def self.status_code
Rack::Utils::SYMBOL_TO_STATUS_CODE[self.status]
end
# The numeric status code corresponding to this exception. By default, it
# calls the class method of the same name.
def status_code
self.class.status_code
end
# The name of the template that should be used as error page for this
# exception class.
def self.template
"#{template_path}/#{status}"
end
# The name of the template that should be used as error page for this
# exception. By default, it calls the class method of the same name.
def template
self.class.template
end
end
# This function will install a rescue_from handler for HTTPStatus::Base
# exceptions in the class in which this module is included.
#
# base:: The class in which the module is included. Should be
# ActionController::Base during the initialization of the gem.
def self.included(base)
base.send(:rescue_from, HTTPStatus::Base, :with => :http_status_exception)
end
# Generates a HTTPStatus::Base subclass on demand based on the
# constant name. The constant name should correspond to one of the status
# symbols defined in ActionController::StatusCodes. The function
# will raise an exception if the constant name cannot be mapped onto one of
# the status symbols.
#
# This method will create a new subclass of HTTPStatus::Base and
# overrides the status class method of the class to return the correct
# status symbol.
#
# const:: The name of the missing constant, for which an exception
# class should be generated.
def self.const_missing(const)
status_symbol = const.to_s.underscore.to_sym
raise "Unrecognized HTTP Status name!" unless Rack::Utils::SYMBOL_TO_STATUS_CODE.has_key?(status_symbol)
klass = Class.new(HTTPStatus::Base)
klass.cattr_accessor(:status)
klass.status = status_symbol
const_set(const, klass)
return const_get(const)
end
# The default handler for raised HTTP status exceptions. It will render a
# template if available, or respond with an empty response with the HTTP
# status corresponding to the exception.
#
# You can override this method in your ApplicationController to
# handle the exceptions yourself.
#
# exception:: The HTTP status exception to handle.
def http_status_exception(exception)
@exception = exception
render_options = {:template => exception.template, :status => exception.status}
render_options[:layout] = exception.template_layout if exception.template_layout
render(render_options)
rescue ActionView::MissingTemplate
head(exception.status)
end
end
# Include the HTTPStatus module into ActionController::Base to enable
# the http_status_exception exception handler.
ActionController::Base.send(:include, HTTPStatus)