General rescue throughout controller when id not found - RoR
Asked Answered
O

4

17

I have stumbled upon a situation where my application looks for an id that does not exist in the database. An exception is thrown. Of course, this is a pretty standard situation for any web developer.

Thanks to this answer I know that using rescue deals with the situation pretty neatly, like so:

def show
  @customer = Customer.find(params[:id])
  rescue ActiveRecord::RecordNotFound #customer with that id cannot be found
    redirect_to action: :index        #redirect to index page takes place instead of crashing
end

In case the customer cannot be found, the user gets redirected to the index page. This works absolutely fine.

Now, this is all nice, but I need to do the same rescue attempts in actions like show, edit, destroy, etc, i.e. every controller method that needs a specific id.

Having said that, here's my question: Isn't there any way to generally tell my controller that if it can't find the id in any of its methods, it shall redirect to the index page (or, generally, perform a specific task)?

Onetime answered 17/11, 2012 at 20:31 Comment(0)
K
37

You must use rescue_from for this task. See example in the Action Controller Overview Guide

class ApplicationController < ActionController::Base
  rescue_from ActiveRecord::RecordNotFound, :with => :record_not_found

  private

  def record_not_found
    redirect_to action: :index
  end
end
Koontz answered 17/11, 2012 at 20:38 Comment(4)
Awesome, that does the trick, thank you! Out of curiosity: why should that method be private?Onetime
Just for cleanliness: the method does not need to be visible to other controllers, that's why it is declared private. It would work equally if the method is not declared private.Koontz
You could also add a flash message flash[:notice] = "No record found"Fairground
It is very nice and really help meHerr
W
8

Rails has a built-in rescue_from class method:

class CustomersController < ApplicationController
  rescue_from ActiveRecord::RecordNotFound, with: :index
  ...
end
Wagstaff answered 17/11, 2012 at 20:38 Comment(2)
This looks very elegant. I tried it, but it leaves me with an empty page in the browser, and in the url it still says customers/:id. Am I missing something? It appears to me it tries to render index instead of redirecting to it.Onetime
You're right.. try something like what Baldrick suggests below (i.e, an intermediate method with a redirect).Wagstaff
B
3

If you're talking about doing this within a single controller (as opposed to doing this globally in every controller) then here are a couple options:

You can use a before_filter to setup your resource:

class CustomerController < ApplicationController
  before_filter :get_customer, :only => [ :show, :update, :delete ]

  def show
  end

  private

  def get_customer
    @customer = ActiveRecord.find(params[:id])
    rescue ActiveRecord::RecordNotFound
      redirect_to :action => :index
  end
end

Or you might use a method instead. I've been moving in this direction rather than using instance variables inside views, and it would also help you solve your problem:

class CustomerController < ApplicationController
  def show
    # Uses customer instead of @customer
  end

  private

  def customer
    @customer ||= Customer.find(params[:id])
    rescue ActiveRecord::RecordNotFound
      redirect_to :action => :index
  end
  helper_method :customer
end
Berlauda answered 17/11, 2012 at 20:45 Comment(2)
The word "globally" caught my attention. Is there a way to do this throughout all controllers? That would be extremely helpful.Onetime
I think to do what I described in a global way might require a little metaprogramming. But to simply rescue the ActiveRecord::RecordNotFound globally you could use rescue_from in your ApplicationController.Berlauda
W
1

In certain cases, I would recommend that you use Model.find_by_id(id) as opposed to Model.find(id). Instead of throwing an exception, .find_by_id returns nil. if the record could not be found.

Just make sure to check for nils to avoid NoMethodError!

P.S. For what it's worth, Model.find_by_id(id) is functionally equivalent to Model.where(id: id), which would allow you to build out some additional relations if you want.

Workaday answered 17/11, 2012 at 20:44 Comment(1)
I know that you mean that where and find_by are similar in that neither throws an exception if the record can't be found, but it's worth noting that where returns an active-record relation whereas find_by returns a single record (if not nil). Also, I think I'd rather throw an exception than catch it and prevent null-pointer exceptions everywhere. Not sure what the value in that is.Onetime

© 2022 - 2024 — McMap. All rights reserved.