web-dev-qa-db-ger.com

Wie kann man in Rails zu einem 404 umleiten?

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?

468
Yuval Karmi

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

1033
Steven Soroka

HTTP 404 Status

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

ActiveRecord und HTTP 404

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
239
Simone Carletti

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.

58
Jaime Bellmyer

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."
18
Paulo Fidalgo

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.

12

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
7
Caner Çakmak

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
1
emptywalls
<%= 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.

1
Ahmed Reza

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
0
maprihoda

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
0
Houen
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
0
Arkadiusz Mazur