@khjrtbrg

Skip to main content

Dealing with Rails Errors

Heads up: This post doesn't jump right into the solution. If you just want the solution, click here for my solution or here for the rails documentation.

The Problem

Before Ada, I was an admin person at a small web development company. 90% of the time, that meant answering the phone, responding to emails, and similarly admin-y tasks. But every once in awhile, I was asked to QA our developers' work, or as I started calling it, "trying to break things".

I would meticulously go through a project, trying out any conceivable user action in order to find holes: from actual explosions to inconsistencies in design to framework error messages. Now that we've been working with Rails (for a week) and Sinatra (for a couple of weeks), I've realized that the big, ugly error messages I was so excited to find at work weren't necessarily the application exploding. Instead, being able to find an error message is more of an indication that whoever built it didn't cover every possible point of entry (which seems more and more daunting of a task in a large scale application).

Long story short: I don't like Error pages

It's my latest (and greatest) pet peeve and I want a quick way to avoid seeing it.

In my Sinatra blog, I had a quick little redirect at the very end of my router:

get "/*" do
  redirect to("/")
end

If your requested URL isn't found by the time you get to the bottom of this page, then it doesn't exist. It felt very clean to send users back to the homepage, but this is probably a good spot to stick a 404 page.

That works fine and well for a project with no database, but interacting with ActiveRecord and a database adds another level of complexity to the problem.

In this week's project (FarMarFinder 2.0 -- GitHub and Heroku), my partner and I used a method similar to this for each of our models to get around the problem:

def no_market_redirect
  find_market ? find_market : redirect_to(markets_path)
end

Our thinking was that if ActiveRecord can't find the requested record (find_market just looks up the record) we'll redirect the user to /markets.

Sad Realization: Rails already does this for you.

After a minute of Googling, I found out about Rail's rescue handling. Doh!

While we get fugly error messages when developing locally, Rails shows nice 500 and 404 pages when dealing with remote requests. I wish I knew that before going through the trouble of adding our redirect everywhere.

The Quick and Dirty Solution

Don't do anything and let Rails handle serving either the 404 or 500 page, as needed. If you want to style either the 404 or 500 page, you can find them in /pubic, as 404.html or 500.html

These files are static HTML, so you they don't inherit any CSS or use your basic layout.

You can checkout what your 404 page looks like locally by going to a page that doesn't exist. I'm a fan using of http://localhost:3000/banana

The Slightly Fancier Solution

Rails gives us a smancier way of handling errors: rescue_from.

To use this, just add the following code to the controller in question:

rescue_from ErrorToHandle, with: :method_you_want

ErrorToHandle will likely be ActiveRecord::RecordNotFound, but you can use it to handle any kind of error. :method_you_want should be whatever method you want call to handle the error.

Finally, a Before/After Example from FarMarFinder 2.0:

This is our markets_controller.rb before we knew about rescue_from:

class MarketsController < ApplicationController

  def index
    @markets = Market.all
  end

  def new
    @market = Market.new
  end

  def create
    @market = Market.new(market_params)
    if @market.save
      redirect_to edit_vendor_path, :notice => "Market Created!"
    else
      render "new"
    end
  end

  def edit_prep
    @markets = Market.all
    if @markets.size == 0
      redirect_to edit_vendor_path, :alert => "No Markets to Edit"
    end
  end

  def edit_post
    redirect_to edit_market_path(params[:market][:id])
  end

  def edit
    no_market_redirect
  end

  def update
    if find_market.update(market_params)
      redirect_to edit_vendor_path, :notice => "Market Updated!"
    else
      render "edit"
    end
  end

  def show
    if find_market
      find_market
    else
      redirect_to markets_path
    end
  end


  private

  def find_market
    @market = Market.find(params[:id]) if Market.find_by(id: params[:id])
  end

  def no_market_redirect
    find_market ? find_market : redirect_to(edit_markets_landing_path)
  end

  def market_params
    (params.require(:market).permit(:name, :location))
  end
end

And here it is after:

class MarketsController < ApplicationController
  rescue_from ActiveRecord::RecordNotFound, with: :market_not_found
  rescue_from NoMethodError, with: :edit_market_method_error

  def index
    @markets = Market.all
  end

  def new
    @market = Market.new
  end

  def create
    @market = Market.new(market_params)
    if @market.save
      redirect_to edit_vendor_path, :notice => "Market Created!"
    else
      render "new"
    end
  end

  def edit_prep
    @markets = Market.all
    if @markets.size == 0
      redirect_to edit_vendor_path, :alert => "No Markets to Edit"
    end
  end

  def edit_post
    redirect_to edit_market_path(params[:market][:id])
  end

  def edit
    find_market
  end

  def update
    if find_market.update(market_params)
      redirect_to edit_vendor_path, :notice => "Market Updated!"
    else
      render "edit"
    end
  end

  def show
    find_market
  end


  private

  def find_market
    @market = Market.find(params[:id])
  end

  def market_not_found
    redirect_to markets_path
  end

  def edit_market_method_error
    redirect_to edit_markets_landing_path
  end

  def market_params
    (params.require(:market).permit(:name, :location))
  end
end

It isn't a radical change, but it feels cleaner to have our show method be simply:

def show
  find_market
end

instead of:

def show
  if find_market
    find_market
  else
    redirect_to markets_path
  end
end