Module: Egregious

Defined in:
lib/egregious.rb,
lib/egregious/version.rb,
lib/egregious/extensions/exception.rb

Defined Under Namespace

Classes: Error

Constant Summary collapse

VERSION =
"0.2.13"
@@exception_codes =
self._load_exception_codes
@@root =
defined?(Rails) ? Rails.root : nil

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

._load_exception_codesObject

internal method that loads the exception codes



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# 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) ⇒ Object

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



17
18
19
# File 'lib/egregious.rb', line 17

def self.clean_class_name(str)
  str.gsub(/\s|-|'/,'').sub('(Experimental)','')
end

.exception_codesObject

this method exposes the @@exception_codes class variable allowing someone to re-configure the mapping. For example in a rails initializer: Egregious.exception_codes = => “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”)



154
155
156
# File 'lib/egregious.rb', line 154

def self.exception_codes
  @@exception_codes
end

.exception_codes=(exception_codes) ⇒ Object

this method exposes the @@exception_codes class variable allowing someone to re-configure the mapping. For example in a rails initializer: Egregious.exception_codes = => “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”)



163
164
165
# File 'lib/egregious.rb', line 163

def self.exception_codes=(exception_codes)
  @@exception_codes=exception_codes
end

.included(base) ⇒ Object



245
246
247
248
249
# File 'lib/egregious.rb', line 245

def self.included(base)
  base.class_eval do
    rescue_from 'Exception' , :with => :egregious_exception_handler
  end
end

.rootObject

exposes the root of the app



126
127
128
# File 'lib/egregious.rb', line 126

def self.root
  @@root
end

.root=(root) ⇒ Object

set the root directory and stack traces will be cleaned up



131
132
133
# File 'lib/egregious.rb', line 131

def self.root=(root)
  @@root=root
end

.status_code(status) ⇒ Object



27
28
29
30
31
32
33
34
# 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) ⇒ Object



181
182
183
184
# 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

Instance Method Details

#build_html_file_path(status) ⇒ Object



241
242
243
# 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) ⇒ Object

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



137
138
139
140
141
142
143
144
145
146
147
# 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) ⇒ Object

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



187
188
189
190
191
# 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) ⇒ Object

override this if you want your flash to behave differently



194
195
196
# File 'lib/egregious.rb', line 194

def egregious_flash(exception)
  flash.now[:alert] = exception.message
end

#egregious_log(exception) ⇒ Object

override this if you want your logging to behave differently



199
200
201
202
203
204
205
# 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) ⇒ Object

override this if you want to change your respond_to behavior



222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
# 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_codesObject

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



170
171
172
# File 'lib/egregious.rb', line 170

def exception_codes
  return Egregious.exception_codes
end

#notify_airbrake(exception) ⇒ Object

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



208
209
210
211
212
213
214
215
216
217
218
219
# 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) ⇒ Object



36
37
38
# File 'lib/egregious.rb', line 36

def status_code(status)
  Egregious.status_code(status)
end

#status_code_for_exception(exception) ⇒ Object

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



177
178
179
# File 'lib/egregious.rb', line 177

def status_code_for_exception(exception)
    Egregious.status_code_for_exception(exception)
end