¿Cómo redirigir a un 404 en Rails?


Me gustaría 'falsificar' una página 404 en Rails. En PHP, simplemente enviaría un encabezado con el código de error como tal:

header("HTTP/1.0 404 Not Found");

¿Cómo se hace eso con Rails?

Author: Andrei Eliade, 2010-03-05

9 answers

No renderices 404 tú mismo, no hay razón para hacerlo; Rails ya tiene esta funcionalidad incorporada. Si desea mostrar una página 404, cree un método render_404 (o not_found como lo llamé) en ApplicationController así:

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

Rails también maneja AbstractController::ActionNotFound, y ActiveRecord::RecordNotFound de la misma manera.

Esto hace dos cosas mejor:

1) Utiliza el controlador integrado de Rails rescue_from para renderizar la página 404, y 2) interrumpe la ejecución de su código, lo que le permite hacer cosas agradables como:

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

Sin tener que escribir declaraciones condicionales feas.

Como bono, también es súper fácil de manejar en las pruebas. Por ejemplo, en una prueba de integración rspec:

# 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)

Y minitest:

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

O consulte más información desde Rails render 404 no encontrado desde una acción del controlador

 1012
Author: Steven Soroka,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-09-27 10:46:37

Estado HTTP 404

Para devolver un encabezado 404, simplemente use la opción :status para el método render.

def action
  # here the code

  render :status => 404
end

Si desea representar la página estándar 404, puede extraer la característica en un método.

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

Y llámalo en tu acción

def action
  # here the code

  render_404
end

Si desea que la acción muestre la página de error y se detenga, simplemente use una instrucción return.

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

  # here the code that will never be executed
end

ActiveRecord y HTTP 404

También recuerde que Rails rescata algunos errores de ActiveRecord, como ActiveRecord::RecordNotFound mostrando la página de error 404.

Significa que no necesitas rescatar esta acción tú mismo{[14]]}

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

User.find genera un ActiveRecord::RecordNotFound cuando el usuario no existe. Esta es una característica muy poderosa. Mira el siguiente código

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

Puede simplificarlo delegando a Rails la comprobación. Simplemente use la versión bang.

def show
  user = User.find_by_email!(params[:email])
  # ...
end
 233
Author: Simone Carletti,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-08-31 09:48:48

La respuesta recientemente seleccionada enviada por Steven Soroka está cerca, pero no completa. La prueba en sí oculta el hecho de que esto no está devolviendo un verdadero 404 - está devolviendo un estado de 200 - "éxito". La respuesta original fue más cercana, pero intentó representar el diseño como si no hubiera ocurrido ningún error. Esto arregla todo:

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

Aquí hay un conjunto de pruebas típico mío para algo que espero devolver 404, usando RSpec y 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

Esta sana paranoia permitió para detectar el desajuste de tipo de contenido cuando todo lo demás se veía genial:) Compruebo todos estos elementos: variables asignadas, código de respuesta, tipo de contenido de respuesta, plantilla renderizada, diseño renderizado, mensajes flash.

Omitiré la verificación de tipo de contenido en aplicaciones que son estrictamente html...a veces. Después de todo, "un escéptico comprueba TODOS los cajones":)

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

Para tu información: No recomiendo probar cosas que son sucediendo en el controlador, es decir, "should_raise". Lo que te importa es la salida. Mis pruebas anteriores me permitieron probar varias soluciones, y las pruebas siguen siendo las mismas si la solución está planteando una excepción, renderizado especial,etc.

 56
Author: Jaime Bellmyer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-08-17 20:58:17

También puedes usar el archivo de renderizado:

render file: "#{Rails.root}/public/404.html", layout: false, status: 404

Donde puede elegir usar el diseño o no.

Otra opción es usar las excepciones para controlarlo:

raise ActiveRecord::RecordNotFound, "Record not found."
 16
Author: Paulo Fidalgo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-01-19 15:41:52

La respuesta seleccionada no funciona en Rails 3.1+ ya que el manejador de errores fue movido a un middleware (ver github issue).

Aquí está la solución que encontré con la que estoy bastante contento.

En ApplicationController:

  unless Rails.application.config.consider_all_requests_local
    rescue_from Exception, with: :handle_exception
  end

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

  def handle_exception(exception=nil)
    if exception
      logger = Logger.new(STDOUT)
      logger.debug "Exception Message: #{exception.message} \n"
      logger.debug "Exception Class: #{exception.class} \n"
      logger.debug "Exception Backtrace: \n"
      logger.debug exception.backtrace.join("\n")
      if [ActionController::RoutingError, ActionController::UnknownController, ActionController::UnknownAction].include?(exception.class)
        return render_404
      else
        return render_500
      end
    end
  end

  def render_404
    respond_to do |format|
      format.html { render template: 'errors/not_found', layout: 'layouts/application', status: 404 }
      format.all { render nothing: true, status: 404 }
    end
  end

  def render_500
    respond_to do |format|
      format.html { render template: 'errors/internal_server_error', layout: 'layouts/application', status: 500 }
      format.all { render nothing: true, status: 500}
    end
  end

Y en application.rb:

config.after_initialize do |app|
  app.routes.append{ match '*a', :to => 'application#not_found' } unless config.consider_all_requests_local
end

Y en mis recursos (mostrar, editar, actualizar, eliminar):

@resource = Resource.find(params[:id]) or not_found

Esto ciertamente podría mejorarse, pero al menos, tengo diferentes vistas para not_found e internal_error sin sobreescribir las funciones core Rails.

 11
Author: Augustin Riedinger,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-08-31 09:49:36

Estos te ayudarán...

Controlador de aplicaciones

class ApplicationController < ActionController::Base
  protect_from_forgery
  unless Rails.application.config.consider_all_requests_local             
    rescue_from ActionController::RoutingError, ActionController::UnknownController, ::AbstractController::ActionNotFound, ActiveRecord::RecordNotFound, with: lambda { |exception| render_error 404, exception }
  end

  private
    def render_error(status, exception)
      Rails.logger.error status.to_s + " " + exception.message.to_s
      Rails.logger.error exception.backtrace.join("\n") 
      respond_to do |format|
        format.html { render template: "errors/error_#{status}",status: status }
        format.all { render nothing: true, status: status }
      end
    end
end

Controlador de errores

class ErrorsController < ApplicationController
  def error_404
    @not_found_path = params[:not_found]
  end
end

Vistas/errores/error_404.HTML.haml

.site
  .services-page 
    .error-template
      %h1
        Oops!
      %h2
        404 Not Found
      .error-details
        Sorry, an error has occured, Requested page not found!
        You tried to access '#{@not_found_path}', which is not a valid page.
      .error-actions
        %a.button_simple_orange.btn.btn-primary.btn-lg{href: root_path}
          %span.glyphicon.glyphicon-home
          Take Me Home
 7
Author: Caner Çakmak,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-08-10 11:51:13
<%= render file: 'public/404', status: 404, formats: [:html] %>

Simplemente agregue esto a la página que desea renderizar en la página de error 404 y listo.

 1
Author: Ahmed Reza,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-11-01 06:42:03

Para probar el manejo de errores, puedes hacer algo como esto:

feature ErrorHandling do
  before do
    Rails.application.config.consider_all_requests_local = false
    Rails.application.config.action_dispatch.show_exceptions = true
  end

  scenario 'renders not_found template' do
    visit '/blah'
    expect(page).to have_content "The page you were looking for doesn't exist."
  end
end
 0
Author: maprihoda,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-04-14 20:40:50

Si desea manejar diferentes 404s de diferentes maneras, considere capturarlos en sus controladores. Esto le permitirá hacer cosas como rastrear el número de 404s generados por diferentes grupos de usuarios, tener soporte para interactuar con los usuarios para averiguar qué salió mal / qué parte de la experiencia del usuario podría necesitar ajustes, hacer pruebas A/B, etc.

Aquí he colocado la lógica base en ApplicationController, pero también se puede colocar en controladores más específicos, para tener una lógica especial solo para un controlador.

La razón por la que estoy usando un if con ENV['RESCUE_404'], es para poder probar la elevación de AR::RecordNotFound de forma aislada. En tests, puedo establecer este ENV var en false, y mi rescue_from no se dispararía. De esta manera puedo probar la subida separada de la lógica condicional 404.

class ApplicationController < ActionController::Base

  rescue_from ActiveRecord::RecordNotFound, with: :conditional_404_redirect if ENV['RESCUE_404']

private

  def conditional_404_redirect
    track_404(@current_user)
    if @current_user.present?
      redirect_to_user_home          
    else
      redirect_to_front
    end
  end

end
 0
Author: Houen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-07-25 07:15:19