module Egregious

Constants

VERSION

Public Class Methods

_load_exception_codes() click to toggle source

internal method that loads the exception codes

# File lib/egregious.rb, line 42
def self._load_exception_codes

  # by default if there is not mapping they map to 500/internal server error
  exception_codes = {
      SecurityError=>status_code(:forbidden)
  }
  # all status codes have a exception class defined
  Rack::Utils::HTTP_STATUS_CODES.each do |key, value|
    exception_codes.merge!(eval("Egregious::#{Egregious.clean_class_name(value)}")=>value.downcase.gsub(/\s|-/, '_').to_sym)
  end

  if defined?(ActionController)
    exception_codes.merge!({
                             AbstractController::ActionNotFound=>status_code(:bad_request),
                             ActionController::InvalidAuthenticityToken=>status_code(:bad_request),
                             ActionController::MethodNotAllowed=>status_code(:not_allowed),
                             ActionController::MissingFile=>status_code(:not_found),
                             ActionController::RoutingError=>status_code(:bad_request),
                             ActionController::UnknownController=>status_code(:bad_request),
                             ActionController::UnknownHttpMethod=>status_code(:not_allowed)
                             #ActionController::MissingTemplate=>status_code(:not_found)
                           })
  end

  if defined?(ActiveModel)
    exception_codes.merge!({
                               ActiveModel::MissingAttributeError=>status_code(:bad_request)})
  end

  if defined?(ActiveRecord)
    exception_codes.merge!({
                             ActiveRecord::AttributeAssignmentError=>status_code(:bad_request),
                             ActiveRecord::MultiparameterAssignmentErrors=>status_code(:bad_request),
                             ActiveRecord::ReadOnlyAssociation=>status_code(:forbidden),
                             ActiveRecord::ReadOnlyRecord=>status_code(:forbidden),
                             ActiveRecord::RecordInvalid=>status_code(:bad_request),
                             ActiveRecord::RecordNotFound=>status_code(:not_found),
                             ActiveRecord::UnknownAttributeError=>status_code(:bad_request)
                           })
    begin
      exception_codes.merge!({
                               ActiveRecord::HasAndBelongsToManyAssociationForeignKeyNeeded=>status_code(:bad_request)
                             })
    rescue => e
      # Unknown if using Rails 4
    end
  end
  
  if defined?(Mongoid)
    require 'egregious/extensions/mongoid'
    
    exception_codes.merge!({
                             Mongoid::Errors::InvalidFind=>status_code(:bad_request),
                             Mongoid::Errors::DocumentNotFound=>status_code(:not_found),
                             Mongoid::Errors::Validations=>status_code(:unprocessable_entity)
                           })
    
    if defined?(Mongoid::VERSION) && Mongoid::VERSION > '3'
      exception_codes.merge!({
                               Mongoid::Errors::ReadonlyAttribute=>status_code(:forbidden),
                               Mongoid::Errors::UnknownAttribute=>status_code(:bad_request)
                             })
    end
  end

  if defined?(Warden)
    exception_codes.merge!({
                               Warden::NotAuthenticated=>status_code(:unauthorized),
                           })
  end

  if defined?(CanCan)
    # technically this should be forbidden, but for some reason cancan returns AccessDenied when you are not logged in
    exception_codes.merge!({CanCan::AccessDenied=>status_code(:unauthorized)})
    exception_codes.merge!({CanCan::AuthorizationNotPerformed => status_code(:unauthorized)})
  end

  @@exception_codes = exception_codes
end
clean_class_name(str) click to toggle source

Must sub out (Experimental) to avoid a class name: VariantAlsoNegotiates(Experimental)

# File lib/egregious.rb, line 17
def self.clean_class_name(str)
  str.gsub(/\s|-|'/,'').sub('(Experimental)','')
end
exception_codes() click to toggle source

this method exposes the @@exception_codes class variable allowing someone to re-configure the mapping. For example in a rails initializer: Egregious.exception_codes = {NameError => “503”} or If you want the default mapping and then want to modify it you should call the following: Egregious.exception_codes.merge!({MyCustomException=>“500”})

# File lib/egregious.rb, line 154
def self.exception_codes
  @@exception_codes
end
exception_codes=(exception_codes) click to toggle source

this method exposes the @@exception_codes class variable allowing someone to re-configure the mapping. For example in a rails initializer: Egregious.exception_codes = {NameError => “503”} or If you want the default mapping and then want to modify it you should call the following: Egregious.exception_codes.merge!({MyCustomException=>“500”})

# File lib/egregious.rb, line 163
def self.exception_codes=(exception_codes)
  @@exception_codes=exception_codes
end
included(base) click to toggle source
# File lib/egregious.rb, line 245
def self.included(base)
  base.class_eval do
    rescue_from 'Exception' , :with => :egregious_exception_handler
  end
end
root() click to toggle source

exposes the root of the app

# File lib/egregious.rb, line 126
def self.root
  @@root
end
root=(root) click to toggle source

set the root directory and stack traces will be cleaned up

# File lib/egregious.rb, line 131
def self.root=(root)
  @@root=root
end
status_code(status) click to toggle source
# File lib/egregious.rb, line 27
def self.status_code(status)
  if status.is_a?(Symbol)
    key = status.to_s.split("_").map {|e| e.capitalize}.join(" ")
    Rack::Utils::HTTP_STATUS_CODES.invert[key] || 500
  else
    status.to_i
  end
end
status_code_for_exception(exception) click to toggle source
# File lib/egregious.rb, line 181
def self.status_code_for_exception(exception)
  status_code(self.exception_codes[exception.class]  ||
             (exception.respond_to?(:http_status) ? (exception.http_status||:internal_server_error) : :internal_server_error))
end

Public Instance Methods

build_html_file_path(status) click to toggle source
# File lib/egregious.rb, line 241
def build_html_file_path(status)
  File.join(Rails.root, 'public', "#{status_code(status)}.html")
end
clean_backtrace(exception) click to toggle source

a little helper to help us clean up the backtrace if root is defined it removes that, for rails it takes care of that

# File lib/egregious.rb, line 137
def clean_backtrace(exception)
  if backtrace = exception.backtrace
    if Egregious.root
      backtrace.map { |line|
        line.sub Egregious.root.to_s, ''
      }
    else
      backtrace
    end
  end
end
egregious_exception_handler(exception) click to toggle source

this is the method that handles all the exceptions we have mapped

# File lib/egregious.rb, line 187
def egregious_exception_handler(exception)
  egregious_flash(exception)
  egregious_log(exception)
  egregious_respond_to(exception)
end
egregious_flash(exception) click to toggle source

override this if you want your flash to behave differently

# File lib/egregious.rb, line 194
def egregious_flash(exception)
  flash.now[:alert] = exception.message
end
egregious_log(exception) click to toggle source

override this if you want your logging to behave differently

# File lib/egregious.rb, line 199
def egregious_log(exception)
  logger.fatal(
      "\n\n" + exception.class.to_s + ' (' + exception.message.to_s + '):\n    ' +
          clean_backtrace(exception).join("\n    ") +
          "\n\n")
  notify_airbrake(exception)
end
egregious_respond_to(exception) click to toggle source

override this if you want to change your respond_to behavior

# File lib/egregious.rb, line 222
def egregious_respond_to(exception)
  respond_to do |format|
    status = status_code_for_exception(exception)
    format.xml { render :xml=> exception.to_xml, :status => status }
    format.json { render :json=> exception.to_json, :status => status }
    # render the html page for the status we are returning it exists...if not then render the 500.html page.
    format.html {
      # render the rails exception page if we are local/debugging
      if(Rails.application.config.consider_all_requests_local || request.local?)
        raise exception
      else
        render :file => File.exists?(build_html_file_path(status)) ?
                                    build_html_file_path(status) : build_html_file_path('500'),
                         :status => status
      end
    }
  end
end
exception_codes() click to toggle source

this method will auto load the exception codes if they are not set by an external configuration call to self.exception_code already it is called by the status_code_for_exception method

# File lib/egregious.rb, line 170
def exception_codes
  return Egregious.exception_codes
end
notify_airbrake(exception) click to toggle source

override this if you want to control what gets sent to airbrake

# File lib/egregious.rb, line 208
def notify_airbrake(exception)
  # tested with airbrake 4.3.5 and 5.0.5
  if defined?(Airbrake)
    if(Airbrake.respond_to?(:notify_or_ignore))
      env['airbrake.error_id'] = Airbrake.notify_or_ignore(exception, airbrake_request_data) # V4
    else
      # V5
      notice = Airbrake::Rack::NoticeBuilder.new(env).build_notice(exception)
      env['airbrake.error_id'] = Airbrake.notify(notice)
    end
  end
end
status_code(status) click to toggle source
# File lib/egregious.rb, line 36
def status_code(status)
  Egregious.status_code(status)
end
status_code_for_exception(exception) click to toggle source

this method will lookup the exception code for a given exception class if the exception is not in our map then see if the class responds to :http_status if not it will return 500

# File lib/egregious.rb, line 177
def status_code_for_exception(exception)
    Egregious.status_code_for_exception(exception)
end