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)