Ich möchte eine 404-Seite in Rails "fälschen". In PHP würde ich einfach einen Header mit dem Fehlercode als solchen senden:
header("HTTP/1.0 404 Not Found");
Wie geht das mit Rails?
Machen Sie 404 nicht selbst, es gibt keinen Grund dafür. Rails hat diese Funktionalität bereits integriert. Wenn Sie eine 404-Seite anzeigen möchten, erstellen Sie ein render_404
Methode (oder not_found
wie ich es nannte) in ApplicationController
wie folgt:
def not_found
raise ActionController::RoutingError.new('Not Found')
end
Schienen handhaben auch AbstractController::ActionNotFound
, und ActiveRecord::RecordNotFound
in der gleichen Weise.
Das macht zwei Dinge besser:
1) Es verwendet Rails 'eingebautes rescue_from
Handler zum Rendern der 404-Seite, und 2) es unterbricht die Ausführung Ihres Codes, so dass Sie nette Dinge tun können 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 super einfach in Tests 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 minitest:
assert_raises(ActionController::RoutingError) do
get '/something/you/want/to/404'
end
ODER beziehen Sie sich auf weitere Informationen von Rails-Rendering 404 wurde von einer Controller-Aktion nicht gefunden
Um einen 404-Header zurückzugeben, verwenden Sie einfach das :status
Option für die Rendermethode.
def action
# here the code
render :status => 404
end
Wenn Sie die Standard-404-Seite 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 wie das ActiveRecord::RecordNotFound
Anzeige der 404-Fehlerseite.
Dies bedeutet, dass Sie diese Aktion nicht selbst retten müssen
def show
user = User.find(params[:id])
end
User.find
wirft ein ActiveRecord::RecordNotFound
wenn der Benutzer nicht existiert. Dies ist eine sehr leistungsstarke 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 echten 404 zurückgibt - es wird ein Status von 200 zurückgegeben - "Erfolg". Die ursprüngliche Antwort war näher, aber es wurde versucht, das Layout so zu rendern, als ob kein Fehler aufgetreten wäre. Dies behebt alles:
render :text => 'Not Found', :status => '404'
Hier ist ein typischer Testsatz von mir für etwas, von dem ich erwarte, dass es 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 inhaltliche Nichtübereinstimmung zu erkennen, wenn alles andere pfirsichfarben aussah :) Ich überprüfe alle diese Elemente: zugewiesene Variablen, Antwortcode, Art des Antwortinhalts, Vorlage, Layout, Flash-Nachrichten.
Ich überspringe die Prüfung des Inhaltstyps für Anwendungen, die ausschließlich in HTML vorliegen ... manchmal. Immerhin "prüft ein Skeptiker ALLE Schubladen" :)
http://dilbert.com/strips/comic/1998-01-20/
Zu Ihrer Information: Ich empfehle nicht, nach Dingen zu suchen, die im Controller passieren, z. B. "should_raise". Was Sie interessiert, ist die Ausgabe. Bei den obigen 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.
Sie können auch die Renderdatei verwenden:
render file: "#{Rails.root}/public/404.html", layout: false, status: 404
Wo Sie wählen können, ob Sie das Layout verwenden möchten oder nicht.
Eine andere Option ist die Verwendung der Ausnahmen, um dies zu steuern:
raise ActiveRecord::RecordNotFound, "Record not found."
Die ausgewählte Antwort funktioniert nicht in Rails 3.1+, 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 verschiedene Ansichten für not_found und internal_error, ohne die Kernfunktionen Rails) zu überschreiben.
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
Fehlercontroller
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
Ich wollte für jeden angemeldeten Benutzer, der kein Administrator ist, eine 'normale' 404 ausgeben, also schrieb ich 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
<%= render file: 'public/404', status: 404, formats: [:html] %>
fügen Sie dies einfach der Seite hinzu, die Sie auf der 404-Fehlerseite rendern möchten, und Sie sind fertig.
Um die Fehlerbehandlung zu testen, können Sie folgendermaßen vorgehen:
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 unterschiedliche 404-Werte auf unterschiedliche Weise verarbeiten möchten, sollten Sie in Betracht ziehen, sie in Ihren Controllern abzufangen. Auf diese Weise können Sie beispielsweise die Anzahl der von verschiedenen Benutzergruppen erzeugten 404-Werte nachverfolgen, den Support mit den Benutzern interagieren lassen, um herauszufinden, was schief gelaufen ist/welcher Teil der Benutzererfahrung möglicherweise Optimierungen erfordert, A/B-Tests durchführen usw.
Ich habe hier die Basislogik in ApplicationController platziert, aber es kann auch in spezifischeren Controllern platziert werden, um eine spezielle Logik nur für einen Controller 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 mein rescue_from 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
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