ruby on rails - ¿Cómo redirigir a un 404 en Rails?
ruby-on-rails http (10)
Me gustaría "falsificar" una página 404 en Rails. En PHP, solo 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?
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 función mediante 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 llamalo en tu accion
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 declaración de retorno.
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
muestra la página de error 404.
Significa que no necesitas rescatar esta acción tú 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
Puedes simplificarlo delegando a Rails el cheque. Simplemente usa la versión bang.
def show
user = User.find_by_email!(params[:email])
# ...
end
La nueva respuesta seleccionada enviada por Steven Soroka está cerca, pero no está completa. La prueba en sí misma oculta el hecho de que esto no está devolviendo un 404 verdadero, 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 fallo. Esto arregla todo:
render :text => ''Not Found'', :status => ''404''
Este es un conjunto de pruebas típico para algo que espero que devuelva 404, usando los comparadores 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 la falta de coincidencia de tipo de contenido cuando todo lo demás parecía peachy :) Verifico todos estos elementos: variables asignadas, código de respuesta, tipo de contenido de respuesta, plantilla renderizada, diseño renderizado, mensajes flash.
Me saltearé la verificación de tipo de contenido en aplicaciones que son estrictamente html ... a veces. Después de todo, "un escéptico verifica TODOS los cajones" :)
http://dilbert.com/strips/comic/1998-01-20/
FYI: No recomiendo probar las 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 tanto si la solución está generando una excepción, una representación especial, etc.
La respuesta seleccionada no funciona en Rails 3.1+, ya que el controlador de errores se movió a un middleware (consulte el tema github ).
Aquí está la solución que encontré con la que estoy bastante feliz.
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 podría mejorarse, pero al menos tengo diferentes vistas para not_found e internal_error sin anular las funciones principales de Rails.
No rinda 404 usted mismo, no hay razón para hacerlo; Rails ya tiene incorporada esta funcionalidad. Si desea mostrar una página 404, cree un método render_404
(o not_found
como lo llamé) en ApplicationController
esta 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 manejador 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 bonitas como:
user = User.find_by_email(params[:email]) or not_found
user.do_something!
Sin tener que escribir feos 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
O refiera más información desde Rails render 404 no encontrado desde una acción del controlador
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
Quería lanzar un 404 ''normal'' para cualquier usuario registrado que no sea un administrador, así que terminé escribiendo algo así en Rails 5:
class AdminController < ApplicationController
before_action :blackhole_admin
private
def blackhole_admin
return if current_user.admin?
raise ActionController::RoutingError, ''Not Found''
rescue ActionController::RoutingError
render file: "#{Rails.root}/public/404", layout: false, status: :not_found
end
end
Si desea manejar diferentes 404s 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, hacer que el soporte interactúe con los usuarios para descubrir qué fue lo que falló o 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 que puedo probar el aumento de AR :: RecordNotFound de forma aislada. En las pruebas, puedo establecer esta var. ENV en falso, y mi archivo de rescate no se activará. De esta manera puedo probar la elevación separada 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
También puedes usar el archivo de render:
render file: "#{Rails.root}/public/404.html", layout: false, status: 404
Donde se puede optar por utilizar el diseño o no.
Otra opción es usar las Excepciones para controlarlo:
raise ActiveRecord::RecordNotFound, "Record not found."
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
<%= render file: ''public/404'', status: 404, formats: [:html] %>
simplemente agregue esto a la página que desea representar a la página de error 404 y listo.