How to redirect to a 404 in Rails?

The newly Selected answer submitted by Steven Soroka is close, but not complete. The test itself hides the fact that this is not returning a true 404 - it's returning a status of 200 - "success". The original answer was closer, but attempted to render the layout as if no failure had occurred. This fixes everything:

render :text => 'Not Found', :status => '404'

Here's a typical test set of mine for something I expect to return 404, using RSpec and Shoulda matchers:

describe "user view" do
  before do
    get :show, :id => 'nonsense'
  end

  it { should_not assign_to :user }

  it { should respond_with :not_found }
  it { should respond_with_content_type :html }

  it { should_not render_template :show }
  it { should_not render_with_layout }

  it { should_not set_the_flash }
end

This healthy paranoia allowed me to spot the content-type mismatch when everything else looked peachy :) I check for all these elements: assigned variables, response code, response content type, template rendered, layout rendered, flash messages.

I'll skip the content type check on applications that are strictly html...sometimes. After all, "a skeptic checks ALL the drawers" :)

http://dilbert.com/strips/comic/1998-01-20/

FYI: I don't recommend testing for things that are happening in the controller, ie "should_raise". What you care about is the output. My tests above allowed me to try various solutions, and the tests remain the same whether the solution is raising an exception, special rendering, etc.


Don't render 404 yourself, there's no reason to; Rails has this functionality built in already. If you want to show a 404 page, create a render_404 method (or not_found as I called it) in ApplicationController like this:

def not_found
  raise ActionController::RoutingError.new('Not Found')
end

Rails also handles AbstractController::ActionNotFound, and ActiveRecord::RecordNotFound the same way.

This does two things better:

1) It uses Rails' built in rescue_from handler to render the 404 page, and 2) it interrupts the execution of your code, letting you do nice things like:

  user = User.find_by_email(params[:email]) or not_found
  user.do_something!

without having to write ugly conditional statements.

As a bonus, it's also super easy to handle in tests. For example, in an rspec integration test:

# RSpec 1

lambda {
  visit '/something/you/want/to/404'
}.should raise_error(ActionController::RoutingError)

# RSpec 2+

expect {
  get '/something/you/want/to/404'
}.to raise_error(ActionController::RoutingError)

And minitest:

assert_raises(ActionController::RoutingError) do 
  get '/something/you/want/to/404'
end

OR refer more info from Rails render 404 not found from a controller action


HTTP 404 Status

To return a 404 header, just use the :status option for the render method.

def action
  # here the code

  render :status => 404
end

If you want to render the standard 404 page you can extract the feature in a method.

def render_404
  respond_to do |format|
    format.html { render :file => "#{Rails.root}/public/404", :layout => false, :status => :not_found }
    format.xml  { head :not_found }
    format.any  { head :not_found }
  end
end

and call it in your action

def action
  # here the code

  render_404
end

If you want the action to render the error page and stop, simply use a return statement.

def action
  render_404 and return if params[:something].blank?

  # here the code that will never be executed
end

ActiveRecord and HTTP 404

Also remember that Rails rescues some ActiveRecord errors, such as the ActiveRecord::RecordNotFound displaying the 404 error page.

It means you don't need to rescue this action yourself

def show
  user = User.find(params[:id])
end

User.find raises an ActiveRecord::RecordNotFound when the user doesn't exist. This is a very powerful feature. Look at the following code

def show
  user = User.find_by_email(params[:email]) or raise("not found")
  # ...
end

You can simplify it by delegating to Rails the check. Simply use the bang version.

def show
  user = User.find_by_email!(params[:email])
  # ...
end