Ich möchte eine 404-Seite in Rails "fälschen". In PHP würde ich einfach einen Header mit dem Fehlercode als solchem senden:
header("HTTP/1.0 404 Not Found");
Wie geht das mit Rails?
Ich möchte eine 404-Seite in Rails "fälschen". In PHP würde ich einfach einen Header mit dem Fehlercode als solchem senden:
header("HTTP/1.0 404 Not Found");
Wie geht das mit Rails?
Antworten:
Rendern Sie 404 nicht selbst, es gibt keinen Grund dafür; In Rails ist diese Funktionalität bereits integriert. Wenn Sie eine 404-Seite anzeigen möchten, erstellen Sie eine render_404
Methode (oder not_found
wie ich sie nannte) ApplicationController
wie folgt:
def not_found
raise ActionController::RoutingError.new('Not Found')
end
Schienen handhaben auch AbstractController::ActionNotFound
und auf ActiveRecord::RecordNotFound
die gleiche Weise.
Dies macht zwei Dinge besser:
1) Es verwendet den in Rails integrierten rescue_from
Handler, um die 404-Seite zu rendern, und 2) es unterbricht die Ausführung Ihres Codes und lässt Sie nette Dinge tun wie:
user = User.find_by_email(params[:email]) or not_found
user.do_something!
ohne hässliche bedingte Aussagen schreiben zu müssen.
Als Bonus ist es auch in Tests super einfach zu handhaben. Zum Beispiel in einem rspec-Integrationstest:
# 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)
Und am wenigsten:
assert_raises(ActionController::RoutingError) do
get '/something/you/want/to/404'
end
ODER verweisen Sie auf weitere Informationen von Rails Render 404, die von einer Controller-Aktion nicht gefunden wurden
ActionController::RecordNotFound
ist die bessere Option?
expect { visit '/something/you/want/to/404' }.to raise_error(ActionController::RoutingError)
/ via stackoverflow.com/a/1722839/993890
Um einen 404-Header zurückzugeben, verwenden Sie einfach die :status
Option für die Rendermethode.
def action
# here the code
render :status => 404
end
Wenn Sie die 404-Standardseite rendern möchten, können Sie das Feature in einer Methode extrahieren.
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
und nenne es in deiner Aktion
def action
# here the code
render_404
end
Wenn die Aktion die Fehlerseite rendern und stoppen soll, verwenden Sie einfach eine return-Anweisung.
def action
render_404 and return if params[:something].blank?
# here the code that will never be executed
end
Denken Sie auch daran, dass Rails einige ActiveRecord-Fehler behebt, z. B. das ActiveRecord::RecordNotFound
Anzeigen der 404-Fehlerseite.
Sie müssen diese Aktion also nicht selbst retten
def show
user = User.find(params[:id])
end
User.find
löst ein aus, ActiveRecord::RecordNotFound
wenn der Benutzer nicht existiert. Dies ist eine sehr mächtige Funktion. Schauen Sie sich den folgenden Code an
def show
user = User.find_by_email(params[:email]) or raise("not found")
# ...
end
Sie können es vereinfachen, indem Sie den Scheck an Rails delegieren. Verwenden Sie einfach die Bang-Version.
def show
user = User.find_by_email!(params[:email])
# ...
end
Die neu ausgewählte Antwort von Steven Soroka ist knapp, aber nicht vollständig. Der Test selbst verbirgt die Tatsache, dass dies keine echte 404 zurückgibt - es gibt einen Status von 200 zurück - "Erfolg". Die ursprüngliche Antwort war näher, versuchte jedoch, das Layout so zu rendern, als wäre kein Fehler aufgetreten. Dies behebt alles:
render :text => 'Not Found', :status => '404'
Hier ist ein typischer Testsatz von mir für etwas, von dem ich erwarte, dass er 404 mit RSpec- und Shoulda-Matchern zurückgibt:
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
Diese gesunde Paranoia ermöglichte es mir, die Nichtübereinstimmung des Inhaltstyps zu erkennen, wenn alles andere pfirsichfarben aussah :) Ich überprüfe alle diese Elemente: zugewiesene Variablen, Antwortcode, Antwortinhaltstyp, gerenderte Vorlage, gerendertes Layout, Flash-Nachrichten.
Ich überspringe die Überprüfung des Inhaltstyps für Anwendungen, die ausschließlich HTML sind ... manchmal. Immerhin "überprüft ein Skeptiker ALLE Schubladen" :)
http://dilbert.com/strips/comic/1998-01-20/
Zu Ihrer Information: Ich empfehle nicht, auf Dinge zu testen, die in der Steuerung geschehen, dh "should_raise". Was Sie interessiert, ist die Ausgabe. Bei den oben genannten Tests konnte ich verschiedene Lösungen ausprobieren, und die Tests bleiben gleich, unabhängig davon, ob die Lösung eine Ausnahme, ein spezielles Rendering usw. auslöst.
render :text => 'Not Found', :status => :not_found
.
config.consider_all_requests_local
Parameter in Ihrer environments/development.rb
Datei wahrscheinlich auf true gesetzt ist, eine 200 zurückgegeben wird . Wenn Sie einen Fehler, wie in der akzeptierten Lösung beschrieben, in Staging / Produktion
Sie können auch die Renderdatei verwenden:
render file: "#{Rails.root}/public/404.html", layout: false, status: 404
Wo können Sie wählen, ob Sie das Layout verwenden möchten oder nicht.
Eine andere Möglichkeit besteht darin, die Ausnahmen zu verwenden, um sie zu steuern:
raise ActiveRecord::RecordNotFound, "Record not found."
Die ausgewählte Antwort funktioniert in Rails 3.1+ nicht, da der Fehlerbehandler auf eine Middleware verschoben wurde (siehe Github-Problem) ).
Hier ist die Lösung, mit der ich ziemlich zufrieden bin.
In 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
und in application.rb
:
config.after_initialize do |app|
app.routes.append{ match '*a', :to => 'application#not_found' } unless config.consider_all_requests_local
end
Und in meinen Ressourcen (anzeigen, bearbeiten, aktualisieren, löschen):
@resource = Resource.find(params[:id]) or not_found
Dies könnte sicherlich verbessert werden, aber zumindest habe ich unterschiedliche Ansichten für not_found und internal_error, ohne die Kernfunktionen von Rails zu überschreiben.
|| not_found
Teil jedoch nicht. Rufen Sie einfach auf find!
(beachten Sie den Knall), und es wird ActiveRecord :: RecordNotFound ausgelöst, wenn die Ressource nicht abgerufen werden kann. Fügen Sie außerdem ActiveRecord :: RecordNotFound in der if-Bedingung zum Array hinzu.
StandardError
und nicht Exception
, nur für den Fall. Eigentlich werde ich die statische Standard-500-Seite verlassen und überhaupt keine benutzerdefinierten Seiten verwenden render_500
, was bedeutet, dass ich explizit eine rescue_from
Reihe von Fehlern im Zusammenhang mit 404
diese werden dir helfen ...
Anwendungscontroller
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
Fehler Controller
class ErrorsController < ApplicationController
def error_404
@not_found_path = params[:not_found]
end
end
Ansichten / Fehler / Fehler_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] %>
Fügen Sie dies einfach zu der Seite hinzu, die Sie auf der 404-Fehlerseite rendern möchten, und fertig.
Ich wollte einen "normalen" 404 für jeden angemeldeten Benutzer werfen, der kein Administrator ist, also schrieb ich am Ende so etwas in 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
routes.rb
get '*unmatched_route', to: 'main#not_found'
main_controller.rb
def not_found
render :file => "#{Rails.root}/public/404.html", :status => 404, :layout => false
end
Um die Fehlerbehandlung zu testen, können Sie Folgendes tun:
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
Wenn Sie mit verschiedenen 404 auf unterschiedliche Weise umgehen möchten, sollten Sie sie in Ihren Controllern abfangen. Auf diese Weise können Sie beispielsweise die Anzahl der von verschiedenen Benutzergruppen generierten 404s verfolgen, den Support mit Benutzern interagieren lassen, um herauszufinden, was schief gelaufen ist / welcher Teil der Benutzererfahrung möglicherweise optimiert werden muss, A / B-Tests durchführen usw.
Ich habe hier die Basislogik in ApplicationController platziert, aber sie kann auch in spezifischeren Controllern platziert werden, um nur für einen Controller eine spezielle Logik zu haben.
Der Grund, warum ich ein if mit ENV ['RESCUE_404'] verwende, ist, dass ich das Auslösen von AR :: RecordNotFound isoliert testen kann. In Tests kann ich diese ENV-Variable auf false setzen, und meine Rettung_von würde nicht ausgelöst. Auf diese Weise kann ich das Erhöhen getrennt von der bedingten 404-Logik testen.
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