¿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?

No rindas 404, 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 no not_found como lo llamé) en ApplicationController siguiente manera:

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

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

Esto hace dos cosas mejor:

1) Utiliza el controlador rescue_from incorporado de rescue_from para representar la página 404, y 2) interrumpe la ejecución de tu código, permitiéndote hacer cosas agradables como:

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

sin tener que escribir feos enunciados condicionales.

Como beneficio adicional, también es muy 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 

Estado HTTP 404

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

 def action # here the code render :status => 404 end 

Si desea representar la página 404 estándar, 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 represente la página de error y se detenga, simplemente use una statement de devolución.

 def action render_404 and return if params[:something].blank? # here the code that will never be executed end 

ActiveRecord y HTTP 404

Recuerde también que Rails rescata algunos errores de ActiveRecord, como ActiveRecord::RecordNotFound muestra la página de error 404.

Significa que no necesita rescatar esta acción usted mismo

 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 simplificar delegando en Rails la verificación. Simplemente usa la versión Bang.

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

La respuesta recientemente seleccionada presentada por Steven Soroka está cerca, pero no está completa. La prueba en sí misma oculta el hecho de que esto no está devolviendo un verdadero 404 – está devolviendo un estado de 200 – “éxito”. La respuesta original estaba más cerca, pero intentó renderizar el diseño como si no hubiera ocurrido ninguna falla. Esto arregla todo:

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

Aquí hay un conjunto típico de pruebas de mía para algo que espero devolver 404, usando los mezcladores RSpec y Shoulda:

 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 saludable paranoia me permitió detectar el desajuste del tipo de contenido cuando todo lo demás se veía shiny 🙂 Comprobé todos estos elementos: variables asignadas, código de respuesta, tipo de contenido de respuesta, plantilla procesada, diseño procesado, mensajes flash.

Voy a omitir la verificación del tipo de contenido en aplicaciones que son estrictamente html … a veces. Después de todo, “un escéptico revisa TODOS los cajones” 🙂

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

FYI: No recomiendo las pruebas de cosas que están 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 ya sea que la solución genere una excepción, una representación especial, etc.

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." 

La respuesta seleccionada no funciona en Rails 3.1+ ya que el manejador de errores se movió a un middleware (vea el problema de github ).

Esta es la solución que encontré con la que estoy muy 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 anular las funciones de Core Rails.

estos te ayudarán …

Controlador de aplicación

 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 

views / errors / 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 
 < %= render file: 'public/404', status: 404, formats: [:html] %> 

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

Para probar el manejo de errores, puede 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 

Si desea manejar diferentes 404 de diferentes maneras, considere capturarlos en sus controladores. Esto le permitirá hacer cosas como rastrear la cantidad de 404 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 que pueda probar el aumento de AR :: RecordNotFound de forma aislada. En las pruebas, puedo establecer este ENV var en falso, y mi rescue_from no se activará. De esta forma puedo probar el aumento separado de la lógica 404 condicional.

 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