| 1 |
begin |
|---|
| 2 |
require 'coderay' |
|---|
| 3 |
rescue LoadError => ex |
|---|
| 4 |
end |
|---|
| 5 |
|
|---|
| 6 |
module Merb |
|---|
| 7 |
# ControllerExceptions are a way of simplifying controller code by placing |
|---|
| 8 |
# exceptional logic back into the MVC pattern. |
|---|
| 9 |
# |
|---|
| 10 |
# When a ControllerException is raised within your application merb will |
|---|
| 11 |
# attempt to re-route the request to your Exceptions controller to render |
|---|
| 12 |
# the error in a friendly mannor. |
|---|
| 13 |
# |
|---|
| 14 |
# For example you might have an action in your app that raises NotFound |
|---|
| 15 |
# if a some resource was not available |
|---|
| 16 |
# |
|---|
| 17 |
# def show |
|---|
| 18 |
# product = Product.find(params[:id]) |
|---|
| 19 |
# raise NotFound if product.nil? |
|---|
| 20 |
# [...] |
|---|
| 21 |
# end |
|---|
| 22 |
# |
|---|
| 23 |
# This would halt execution of your action and re-route it over to your |
|---|
| 24 |
# Exceptions controller which might look something like |
|---|
| 25 |
# |
|---|
| 26 |
# class Exceptions < Application |
|---|
| 27 |
# def not_found |
|---|
| 28 |
# render :layout => :none |
|---|
| 29 |
# end |
|---|
| 30 |
# end |
|---|
| 31 |
# |
|---|
| 32 |
# As usual the not_found action will look for a template in |
|---|
| 33 |
# app/views/exceptions/not_found.html.erb |
|---|
| 34 |
# |
|---|
| 35 |
# Note: All standard ControllerExceptions have an HTTP status code associated |
|---|
| 36 |
# with them which is sent to the browser when the action it is rendered. |
|---|
| 37 |
# |
|---|
| 38 |
# Note: If you do not specifiy how to handle raised ControllerExceptions |
|---|
| 39 |
# or an unhandlable exception occurs within your customised exception action |
|---|
| 40 |
# then they will be rendered using the built-in error template |
|---|
| 41 |
# in development mode this "built in" template will show stack-traces for |
|---|
| 42 |
# any of the ServerError family of exceptions (you can force the stack-trace |
|---|
| 43 |
# to display in production mode using the :exception_details config option in |
|---|
| 44 |
# merb.yml) |
|---|
| 45 |
# |
|---|
| 46 |
# |
|---|
| 47 |
# Internal Exceptions |
|---|
| 48 |
# |
|---|
| 49 |
# Any other rogue errors (not ControllerExceptions) that occur during the |
|---|
| 50 |
# execution of you app will be converted into the ControllerException |
|---|
| 51 |
# InternalServerError, and like all ControllerExceptions can be caught |
|---|
| 52 |
# on your Exceptions controller. |
|---|
| 53 |
# |
|---|
| 54 |
# InternalServerErrors return status 500, a common use for cusomizing this |
|---|
| 55 |
# action might be to send emails to the development team, warning that their |
|---|
| 56 |
# application have exploded. Mock example: |
|---|
| 57 |
# |
|---|
| 58 |
# def internal_server_error |
|---|
| 59 |
# MySpecialMailer.deliver( |
|---|
| 60 |
# "team@cowboys.com", |
|---|
| 61 |
# "Exception occured at #{Time.now}", |
|---|
| 62 |
# params[:exception]) |
|---|
| 63 |
# render :inline => 'Something is wrong, but the team are on it!' |
|---|
| 64 |
# end |
|---|
| 65 |
# |
|---|
| 66 |
# Note: The special param[:exception] is available in all Exception actions |
|---|
| 67 |
# and contains the ControllerException that was raised (this is handy if |
|---|
| 68 |
# you want to display the associated message or display more detailed info) |
|---|
| 69 |
# |
|---|
| 70 |
# |
|---|
| 71 |
# Extending ControllerExceptions |
|---|
| 72 |
# |
|---|
| 73 |
# To extend the use of the ControllerExceptions one may extend any of the |
|---|
| 74 |
# HTTPError classes. |
|---|
| 75 |
# |
|---|
| 76 |
# As an example we can create an exception called AdminAccessRequired. |
|---|
| 77 |
# |
|---|
| 78 |
# class AdminAccessRequired < Merb::ControllerExceptions::Unauthorized; end |
|---|
| 79 |
# |
|---|
| 80 |
# Add the required action to our Exceptions controller |
|---|
| 81 |
# |
|---|
| 82 |
# class Exceptions < Application |
|---|
| 83 |
# def admin_access_required |
|---|
| 84 |
# render |
|---|
| 85 |
# end |
|---|
| 86 |
# end |
|---|
| 87 |
# |
|---|
| 88 |
# In app/views/exceptions/admin_access_required.rhtml |
|---|
| 89 |
# |
|---|
| 90 |
# <h1>You're not an administrator!</h1> |
|---|
| 91 |
# <p>You tried to access <%= @tried_to_access %> but that URL is |
|---|
| 92 |
# restricted to administrators.</p> |
|---|
| 93 |
# |
|---|
| 94 |
module ControllerExceptions |
|---|
| 95 |
|
|---|
| 96 |
# Mapping of status code names to their numeric value. |
|---|
| 97 |
STATUS_CODES = {} |
|---|
| 98 |
|
|---|
| 99 |
class Base < StandardError |
|---|
| 100 |
def name |
|---|
| 101 |
self.class.to_s.snake_case.split('::').last |
|---|
| 102 |
end |
|---|
| 103 |
|
|---|
| 104 |
# |
|---|
| 105 |
# Registers any subclasses with status codes for easy lookup by |
|---|
| 106 |
# set_status in Merb::Controller. |
|---|
| 107 |
# |
|---|
| 108 |
# Inheritance ensures this method gets inherited by any subclasses, so |
|---|
| 109 |
# it goes all the way down the chain of inheritance. |
|---|
| 110 |
# |
|---|
| 111 |
def self.inherited(subclass) |
|---|
| 112 |
if subclass.const_defined?(:STATUS) |
|---|
| 113 |
STATUS_CODES[subclass.name.snake_case.to_sym] = subclass.const_get(:STATUS) |
|---|
| 114 |
end |
|---|
| 115 |
end |
|---|
| 116 |
end |
|---|
| 117 |
|
|---|
| 118 |
class Informational < Merb::ControllerExceptions::Base; end |
|---|
| 119 |
class Continue < Merb::ControllerExceptions::Informational; STATUS = 100; end |
|---|
| 120 |
class SwitchingProtocols < Merb::ControllerExceptions::Informational; STATUS = 101; end |
|---|
| 121 |
class Successful < Merb::ControllerExceptions::Base; end |
|---|
| 122 |
class OK < Merb::ControllerExceptions::Successful; STATUS = 200; end |
|---|
| 123 |
class Created < Merb::ControllerExceptions::Successful; STATUS = 201; end |
|---|
| 124 |
class Accepted < Merb::ControllerExceptions::Successful; STATUS = 202; end |
|---|
| 125 |
class NonAuthoritativeInformation < Merb::ControllerExceptions::Successful; STATUS = 203; end |
|---|
| 126 |
class NoContent < Merb::ControllerExceptions::Successful; STATUS = 204; end |
|---|
| 127 |
class ResetContent < Merb::ControllerExceptions::Successful; STATUS = 205; end |
|---|
| 128 |
class PartialContent < Merb::ControllerExceptions::Successful; STATUS = 206; end |
|---|
| 129 |
class Redirection < Merb::ControllerExceptions::Base; end |
|---|
| 130 |
class MultipleChoices < Merb::ControllerExceptions::Redirection; STATUS = 300; end |
|---|
| 131 |
class MovedPermanently < Merb::ControllerExceptions::Redirection; STATUS = 301; end |
|---|
| 132 |
class MovedTemporarily < Merb::ControllerExceptions::Redirection; STATUS = 302; end |
|---|
| 133 |
class SeeOther < Merb::ControllerExceptions::Redirection; STATUS = 303; end |
|---|
| 134 |
class NotModified < Merb::ControllerExceptions::Redirection; STATUS = 304; end |
|---|
| 135 |
class UseProxy < Merb::ControllerExceptions::Redirection; STATUS = 305; end |
|---|
| 136 |
class TemporaryRedirect < Merb::ControllerExceptions::Redirection; STATUS = 307; end |
|---|
| 137 |
class ClientError < Merb::ControllerExceptions::Base; end |
|---|
| 138 |
class BadRequest < Merb::ControllerExceptions::ClientError; STATUS = 400; end |
|---|
| 139 |
class MultiPartParseError < Merb::ControllerExceptions::BadRequest; end |
|---|
| 140 |
class Unauthorized < Merb::ControllerExceptions::ClientError; STATUS = 401; end |
|---|
| 141 |
class PaymentRequired < Merb::ControllerExceptions::ClientError; STATUS = 402; end |
|---|
| 142 |
class Forbidden < Merb::ControllerExceptions::ClientError; STATUS = 403; end |
|---|
| 143 |
class NotFound < Merb::ControllerExceptions::ClientError; STATUS = 404; end |
|---|
| 144 |
class ActionNotFound < Merb::ControllerExceptions::NotFound; end |
|---|
| 145 |
class TemplateNotFound < Merb::ControllerExceptions::NotFound; end |
|---|
| 146 |
class LayoutNotFound < Merb::ControllerExceptions::NotFound; end |
|---|
| 147 |
class MethodNotAllowed < Merb::ControllerExceptions::ClientError; STATUS = 405; end |
|---|
| 148 |
class NotAcceptable < Merb::ControllerExceptions::ClientError; STATUS = 406; end |
|---|
| 149 |
class ProxyAuthenticationRequired < Merb::ControllerExceptions::ClientError; STATUS = 407; end |
|---|
| 150 |
class RequestTimeout < Merb::ControllerExceptions::ClientError; STATUS = 408; end |
|---|
| 151 |
class Conflict < Merb::ControllerExceptions::ClientError; STATUS = 409; end |
|---|
| 152 |
class Gone < Merb::ControllerExceptions::ClientError; STATUS = 410; end |
|---|
| 153 |
class LengthRequired < Merb::ControllerExceptions::ClientError; STATUS = 411; end |
|---|
| 154 |
class PreconditionFailed < Merb::ControllerExceptions::ClientError; STATUS = 412; end |
|---|
| 155 |
class RequestEntityTooLarge < Merb::ControllerExceptions::ClientError; STATUS = 413; end |
|---|
| 156 |
class RequestURITooLarge < Merb::ControllerExceptions::ClientError; STATUS = 414; end |
|---|
| 157 |
class UnsupportedMediaType < Merb::ControllerExceptions::ClientError; STATUS = 415; end |
|---|
| 158 |
class RequestRangeNotSatisfiable < Merb::ControllerExceptions::ClientError; STATUS = 416; end |
|---|
| 159 |
class ExpectationFailed < Merb::ControllerExceptions::ClientError; STATUS = 417; end |
|---|
| 160 |
class ServerError < Merb::ControllerExceptions::Base; end |
|---|
| 161 |
class NotImplemented < Merb::ControllerExceptions::ServerError; STATUS = 501; end |
|---|
| 162 |
class BadGateway < Merb::ControllerExceptions::ServerError; STATUS = 502; end |
|---|
| 163 |
class ServiceUnavailable < Merb::ControllerExceptions::ServerError; STATUS = 503; end |
|---|
| 164 |
class GatewayTimeout < Merb::ControllerExceptions::ServerError; STATUS = 504; end |
|---|
| 165 |
class HTTPVersionNotSupported < Merb::ControllerExceptions::ServerError; STATUS = 505; end |
|---|
| 166 |
class InternalServerError < Merb::ControllerExceptions::ServerError; |
|---|
| 167 |
STATUS = 500 |
|---|
| 168 |
DEFAULT_TEMPLATE = ::Merb::Dispatcher::DEFAULT_ERROR_TEMPLATE |
|---|
| 169 |
|
|---|
| 170 |
def initialize(exception = nil) |
|---|
| 171 |
@exception = exception |
|---|
| 172 |
@coderay = CodeRay rescue nil |
|---|
| 173 |
end |
|---|
| 174 |
|
|---|
| 175 |
def backtrace |
|---|
| 176 |
@exception ? @exception.backtrace : backtrace |
|---|
| 177 |
end |
|---|
| 178 |
|
|---|
| 179 |
def message |
|---|
| 180 |
@exception ? @exception.message : message |
|---|
| 181 |
end |
|---|
| 182 |
end |
|---|
| 183 |
end |
|---|
| 184 |
|
|---|
| 185 |
# PLEASE STOP REMOVING THIS ONE GUYS! It's used to show exceptions in the log file |
|---|
| 186 |
# this is the second time I've had to add it back. |
|---|
| 187 |
def self.exception(e) |
|---|
| 188 |
"#{ e.message } - (#{ e.class })\n" << |
|---|
| 189 |
"#{(e.backtrace or []).join("\n")}" |
|---|
| 190 |
end |
|---|
| 191 |
|
|---|
| 192 |
end |
|---|