2004-11-24 01:04:44 +00:00
module ActionController #:nodoc:
2006-11-13 02:03:50 +00:00
# Actions that fail to perform as expected throw exceptions. These exceptions can either be rescued for the public view
2004-11-24 01:04:44 +00:00
# (with a nice user-friendly explanation) or for the developers view (with tons of debugging information). The developers view
2007-01-15 07:17:58 +00:00
# is already implemented by the Action Controller, but the public view should be tailored to your specific application.
#
# The default behavior for public exceptions is to render a static html file with the name of the error code thrown. If no such
# file exists, an empty response is sent with the correct status code.
2004-11-24 01:04:44 +00:00
#
2007-01-15 07:17:58 +00:00
# You can override what constitutes a local request by overriding the <tt>local_request?</tt> method in your own controller.
# Custom rescue behavior is achieved by overriding the <tt>rescue_action_in_public</tt> and <tt>rescue_action_locally</tt> methods.
2004-11-24 01:04:44 +00:00
module Rescue
2006-11-13 02:03:50 +00:00
LOCALHOST = '127.0.0.1' . freeze
DEFAULT_RESCUE_RESPONSE = :internal_server_error
DEFAULT_RESCUE_RESPONSES = {
2007-09-24 23:12:25 +00:00
'ActionController::RoutingError' = > :not_found ,
'ActionController::UnknownAction' = > :not_found ,
'ActiveRecord::RecordNotFound' = > :not_found ,
'ActiveRecord::StaleObjectError' = > :conflict ,
'ActiveRecord::RecordInvalid' = > :unprocessable_entity ,
'ActiveRecord::RecordNotSaved' = > :unprocessable_entity ,
'ActionController::MethodNotAllowed' = > :method_not_allowed ,
'ActionController::NotImplemented' = > :not_implemented ,
'ActionController::InvalidAuthenticityToken' = > :unprocessable_entity
2006-11-13 02:03:50 +00:00
}
DEFAULT_RESCUE_TEMPLATE = 'diagnostics'
DEFAULT_RESCUE_TEMPLATES = {
'ActionController::MissingTemplate' = > 'missing_template' ,
'ActionController::RoutingError' = > 'routing_error' ,
'ActionController::UnknownAction' = > 'unknown_action' ,
'ActionView::TemplateError' = > 'template_error'
}
2006-04-29 18:10:14 +00:00
def self . included ( base ) #:nodoc:
2006-11-13 02:03:50 +00:00
base . cattr_accessor :rescue_responses
base . rescue_responses = Hash . new ( DEFAULT_RESCUE_RESPONSE )
base . rescue_responses . update DEFAULT_RESCUE_RESPONSES
base . cattr_accessor :rescue_templates
base . rescue_templates = Hash . new ( DEFAULT_RESCUE_TEMPLATE )
base . rescue_templates . update DEFAULT_RESCUE_TEMPLATES
2007-09-23 21:56:22 +00:00
base . class_inheritable_hash :rescue_handlers
base . rescue_handlers = { }
2004-12-06 18:25:01 +00:00
base . extend ( ClassMethods )
2004-11-24 01:04:44 +00:00
base . class_eval do
2006-04-29 20:20:22 +00:00
alias_method_chain :perform_action , :rescue
2004-11-24 01:04:44 +00:00
end
end
2004-12-16 17:45:37 +00:00
module ClassMethods #:nodoc:
2004-12-06 18:25:01 +00:00
def process_with_exception ( request , response , exception )
new . process ( request , response , :rescue_action , exception )
end
2007-09-23 21:56:22 +00:00
# Rescue exceptions raised in controller actions by passing at least one exception class and a :with option that contains the name of the method to be called to respond to the exception.
# Handler methods that take one argument will be called with the exception, so that the exception can be inspected when dealing with it.
#
# class ApplicationController < ActionController::Base
# rescue_from User::NotAuthorized, :with => :deny_access # self defined exception
# rescue_from ActiveRecord::RecordInvalid, :with => :show_errors
#
# protected
# def deny_access
# ...
# end
#
# def show_errors(exception)
# exception.record.new_record? ? ...
# end
# end
def rescue_from ( * klasses )
options = klasses . extract_options!
unless options . has_key? ( :with ) # allow nil
raise ArgumentError , " Need a handler. Supply an options hash that has a :with key as the last argument. "
end
klasses . each do | klass |
rescue_handlers [ klass . name ] = options [ :with ]
end
end
2004-12-06 18:25:01 +00:00
end
2004-11-24 01:04:44 +00:00
protected
# Exception handler called when the performance of an action raises an exception.
def rescue_action ( exception )
2005-07-27 13:55:39 +00:00
log_error ( exception ) if logger
2005-07-27 14:17:47 +00:00
erase_results if performed?
2004-11-24 01:04:44 +00:00
2007-05-26 20:07:34 +00:00
# Let the exception alter the response if it wants.
# For example, MethodNotAllowed sets the Allow header.
if exception . respond_to? ( :handle_response! )
exception . handle_response! ( response )
end
2004-11-24 01:04:44 +00:00
if consider_all_requests_local || local_request?
rescue_action_locally ( exception )
else
rescue_action_in_public ( exception )
end
end
# Overwrite to implement custom logging of errors. By default logs as fatal.
def log_error ( exception ) #:doc:
2006-12-19 23:16:10 +00:00
ActiveSupport :: Deprecation . silence do
if ActionView :: TemplateError === exception
logger . fatal ( exception . to_s )
else
logger . fatal (
" \n \n #{ exception . class } ( #{ exception . message } ): \n " +
clean_backtrace ( exception ) . join ( " \n " ) +
" \n \n "
)
end
2004-11-24 01:04:44 +00:00
end
end
2007-01-15 07:17:58 +00:00
# Overwrite to implement public exception handling (for requests answering false to <tt>local_request?</tt>). By
# default will call render_optional_error_file. Override this method to provide more user friendly error messages.s
2004-11-24 01:04:44 +00:00
def rescue_action_in_public ( exception ) #:doc:
2006-11-13 02:03:50 +00:00
render_optional_error_file response_code_for_rescue ( exception )
end
2007-01-15 07:17:58 +00:00
# Attempts to render a static error page based on the <tt>status_code</tt> thrown,
# or just return headers if no such file exists. For example, if a 500 error is
# being handled Rails will first attempt to render the file at <tt>public/500.html</tt>.
2007-09-23 21:56:22 +00:00
# If the file doesn't exist, the body of the response will be left empty.
2007-01-15 07:17:58 +00:00
def render_optional_error_file ( status_code )
2006-11-13 02:03:50 +00:00
status = interpret_status ( status_code )
path = " #{ RAILS_ROOT } /public/ #{ status [ 0 , 3 ] } .html "
if File . exists? ( path )
render :file = > path , :status = > status
else
head status
2005-02-15 01:45:35 +00:00
end
2004-11-24 01:04:44 +00:00
end
2006-11-13 02:03:50 +00:00
# True if the request came from localhost, 127.0.0.1. Override this
# method if you wish to redefine the meaning of a local request to
# include remote IP addresses or other criteria.
2004-11-24 01:04:44 +00:00
def local_request? #:doc:
2006-11-13 02:03:50 +00:00
request . remote_addr == LOCALHOST and request . remote_ip == LOCALHOST
2004-11-24 01:04:44 +00:00
end
2006-11-13 02:03:50 +00:00
# Render detailed diagnostics for unhandled exceptions rescued from
# a controller action.
2004-11-24 01:04:44 +00:00
def rescue_action_locally ( exception )
2005-07-28 17:41:19 +00:00
add_variables_to_assigns
2005-07-25 09:39:57 +00:00
@template . instance_variable_set ( " @exception " , exception )
2006-11-13 02:03:50 +00:00
@template . instance_variable_set ( " @rescues_path " , File . dirname ( rescues_path ( " stub " ) ) )
2007-10-02 05:32:14 +00:00
@template . send! ( :assign_variables_from_controller )
2005-07-27 14:17:47 +00:00
2005-07-25 09:39:57 +00:00
@template . instance_variable_set ( " @contents " , @template . render_file ( template_path_for_local_rescue ( exception ) , false ) )
2006-11-13 02:03:50 +00:00
2006-09-17 16:20:32 +00:00
response . content_type = Mime :: HTML
2007-09-03 00:18:30 +00:00
render_for_file ( rescues_path ( " layout " ) , response_code_for_rescue ( exception ) )
2004-11-24 01:04:44 +00:00
end
2006-11-13 02:03:50 +00:00
2007-09-23 21:56:22 +00:00
# Tries to rescue the exception by looking up and calling a registered handler.
def rescue_action_with_handler ( exception )
if handler = handler_for_rescue ( exception )
if handler . arity != 0
handler . call ( exception )
else
handler . call
end
true # don't rely on the return value of the handler
end
end
2004-11-24 01:04:44 +00:00
private
def perform_action_with_rescue #:nodoc:
2006-11-13 02:03:50 +00:00
perform_action_without_rescue
rescue Exception = > exception # errors from action performed
2007-09-24 19:56:11 +00:00
return if rescue_action_with_handler ( exception )
2006-11-13 02:03:50 +00:00
rescue_action ( exception )
2004-11-24 01:04:44 +00:00
end
def rescues_path ( template_name )
2007-02-20 22:09:12 +00:00
" #{ File . dirname ( __FILE__ ) } /templates/rescues/ #{ template_name } .erb "
2004-11-24 01:04:44 +00:00
end
def template_path_for_local_rescue ( exception )
2006-11-13 02:03:50 +00:00
rescues_path ( rescue_templates [ exception . class . name ] )
2004-11-24 01:04:44 +00:00
end
2006-11-13 02:03:50 +00:00
2005-02-15 01:45:35 +00:00
def response_code_for_rescue ( exception )
2006-11-13 02:03:50 +00:00
rescue_responses [ exception . class . name ]
2005-02-15 01:45:35 +00:00
end
2006-11-13 02:03:50 +00:00
2007-09-23 21:56:22 +00:00
def handler_for_rescue ( exception )
if handler = rescue_handlers [ exception . class . name ]
method ( handler )
end
end
2004-11-24 01:04:44 +00:00
def clean_backtrace ( exception )
2006-11-13 02:03:50 +00:00
if backtrace = exception . backtrace
if defined? ( RAILS_ROOT )
backtrace . map { | line | line . sub RAILS_ROOT , '' }
else
backtrace
end
end
2004-11-24 01:04:44 +00:00
end
end
2007-10-02 05:32:14 +00:00
end