Rails : Exception Catching

Posted by John
on Thursday, 06 March 2008

Global Error Catching

To handle errors globally in your application, add..

(within application.rb) def rescueactionin_public(exception) render :text => "

There was a global error processing your request.

" end def local_request? false end

The final def def local_request? tells rails that it should act the same way in development mode.

Local Error Catching

To catch errors locally within your form submits do...

def update return unless request.post? begin User.update(self.current_user.id, params[:user]) render :action => 'success' rescue ActiveRecord::RecordInvalid flash[:notice] = "Sorry, your profile was not saved" render :action => 'profile'
rescue flash[:notice] = "Sorry, something went wrong" render :action => 'profile' end end

Simple process, here we're basically saying if the user has submitted form data (done a POST), then process the code; if not redirect back to the sending page.

If yes then update the User object relevant to the specific user record (found by the id), updating the record with the parameters from the form. Save the record and render the 'success' page.

If we get a problem with saving the record (activerecord), display a message and goto the 'profile' page.

If we get some other error, display the second message and goto the 'profile' page.

Notify Me of Errors via Email

On top of this you can enhance it with flash messages and send off an email with the error code, here's one such way.

(in application.rb) protected

def log_error(exception) super(exception)

begin
    Alert.deliver_errormail(
      exception, 
      clean_backtrace(exception), 
      @session.instance_variable_get("@data"), 
      @params, 
      @request.env)
rescue => e
    logger.error(e)
end

end

Adding this makes it send error emails in development mode..

def local_request? false end

Now create an Mailer object with...

script/generate mailer Alert

And put this code in your generated alert.rb file

class Alert < ActionMailer::Base def errormail(exception, trace, session, params, env, sent_on = Time.now) content_type "text/html" @recipients = 'john@gmail.com' @from = 'admin@mysite.com' @subject = "[Error] exception in #{env['REQUEST_URI']}" @senton = senton @body["exception"] = exception @body["trace"] = trace @body["session"] = session @body["params"] = params @body["env"] = env end end

And create a appropriate alert/errormail.rhtml file in your view for the formatted error email...

Error report from <%= Time.now %>

<% if @session['user'] -%> <% end -%>
Message<%= @exception.message %>
Location<%= @env['REQUEST_URI'] %>
Action<%= @params.delete('action') %>
Controller<%= @params.delete('controller') %>
Query<%= @env['QUERY_STRING'] %>
Method<%= @env['REQUEST_METHOD'] %>
SSL<%= @env['SERVER_PORT'].to_i == 443 ? "true" : "false" %>
Agent<%= @env['HTTP_USER_AGENT'] %>
User id<%= @session['user'].id %>
User name<%= @session['user'].name %>
User email<%= @session['user'].email %>
Registered<%= @session['user'].created_at %>

Backtrace

<%= @trace.to_a.join("

\n

") -%>

Params


<% for key, val in @params -%>

<%= key %>

<%= val.to_yaml.to_a.join("

\n

") %>

<% end if @params -%>

Session


<% for key, val in @session -%>

<%= key %>

<%= val.to_yaml.to_a.join("

\n

") %>

<% end if @session -%>

Environment


<% for key, val in @env -%> <% end if @env -%>
<%= key %> <%= val %>

Now whenever an error happens you'll get an email about it, good for the production environment.

Setup the Emailer

Remember to setup the emailer otherwise no emails will be sent.

By adding this to your environments/production.rb

config.actionmailer.raisedelivery_errors = true ActionMailer::Base.delivery_method = :sendmail ActionMailer::Base.sendmail_settings = { :location => '/usr/sbin/sendmail', :arguments => '-i -t' }

Or more detailed,

Setup a DNS MX Record so you don't get blacklisted!

A further note, remember to add an MX record for the domain your using otherwise your site will be blacklisted by anti-spam sites.

An MX record basically tells other sites where they should send emails to, it's like saying where the postman should deliver incoming letters.

Now if your only sending emails you shouldn't need this but as a previous commenter noted, most anti-spam sites blacklist you if your sending and don't have one; regardless of whether you want the site to receive emails or not. Hence you've got to include one.

A simple example is...

type: MX name: mysite.com data: ASPMX.L.GOOGLE.COM. auxilary info: 1

Now ok, this is actually telling anti-spammers your using Google Applications to handle your email (which isn't strictly true) but at least it get's them off your back until you setup your own full-blown POSTFIX mail server.

Which is something I'm working on writing an article for.

Expect that in a future posting.

Freezing Rails

Posted by John
on Tuesday, 18 December 2007

Sometimes with remote server's you may have to supply the files relating to the current version of rails your writing for in order to run your app on someone else's server, like mosso.com.

Freezing Rails

So in Terminal, while in your Rails apps directory run:

rake rails:freeze:gems

This will unpack the rails version your using into the /vendor/rails directory of your app.

From there you can upload it to your server and it should just work.

Thawing Out

To reverse the process...

rake rails:unfreeze

This removes the frozen version from your vendor/rails directory.

Picking Up Changes

Also note new rails versions come with config file changes, to pick these up run:

rake rails:update:configs

Freezing to the Edge

You can also freeze (or grab a local development copy) of rails via...

rake rails:freeze:edge

Or Freeze to a specific release with...

rake rails:freeze:edge TAG=rel_2-0-2

This freezes to Rails 2.0.2

Also for...

You can also do this for individual gems, by first grabbing the gemsonrails gem...

sudo gem install gemsonrails

Then moving to your Rails app directory and running...

gemsonrails

This installs some extra RAKE tasks into your app, so now you can freeze a local copy of say the RedCloth gem into your app via...

rake gems:freeze GEM=RedCloth