Die Fehlerausgabe in Ruby on Rails kann manchmal zur Verzweiflung führen. Zwar gibt es von Haus aus die beiden Fehlerdateien „404.html“ und „500.html“ im public Ordner – was aber tuen, wenn man diese Seiten nicht statisch ausliefern will und stattdessen eine dynamische Fehlerseite generieren möchte.

1. Schritt: Fehlermeldung dynamisch generieren

Seit Rails 2.0 gibt es die praktische Methode rescue_action_in_public(). Diese wird automatisch aufgerufen, wenn beim Rendern der Seite etwas schiefgeht. Dabei ist zu beachten, dass rescue_action_in_public nicht aufgerufen wird, wenn der Request local war. Also nicht wundern, wenn auf dem heimischen Testserver weiterhin die Error Logs angezeigt werden (ist ja auch besser so fürs Debugging).

Eine praktische Anwendung für rescue_action_in_public wäre die folgende (eingefügt in die controller/application.rb damit es global gilt):

[sourcecode lang=“ror“]
def rescue_action_in_public(exception)
set_meta("robots","noindex")
case exception
when ::ActiveRecord::RecordNotFound
when ::ActiveRecord::RecordInvalid
when ::ActionController::RoutingError
when ::ActionController::UnknownController
when ::ActionController::UnknownAction
when ::ActionController::MethodNotAllowed
render :template => "/general/error_404.html.erb", :status => 404
else
render :template => "/general/error_500.html.erb", :status => 500
end
end
[/sourcecode]

Der Code ist ja ziemlich selbstsprechend, wenn ein Fehler auftritt, bei dem etwas fehlt (Datenbankeintrag, Action, Methode, etc.) wird ein 404 Fehler ausgegeben (dabei wird hier die Datei „/general/error_404.html.erb“ gerendert). Ansonsten die entsprechende 500er Fehlerdatei. Alternativ könnte man natürlich auch einen Redirect auf eine beliebige Seite einbauen.

Noch zu bemerken sei der Befehl set_meta(„robots“,“noindex“) – hier rufe ich die (selbstgeschriebene) Methode zum Setzen des Meta Datensatzes „Robots“ auf und gebe dort an, dass die fehlerhafte Seite nicht indiziert werden soll. Damit die Seite nicht trotz eines 404 Status-Codes in einem Suchmaschinenindex landet.

2. Schritt: Statische Dateien löschen

Der Ordnung zu liebe sollte man nun noch die Dateien 404.html und 500.html im public Folder löschen oder zumindest umbenennen, so dass der Webserver diese nicht mehr direkt ausliefert, wenn die URL „http://www.meine-url.de/404.html“ geöffnet wird, sei es durch einen Redirect oder sonst wie.

Danach noch die routes.rb im „/config“ Ordner um die folgenden Zeilen ergänzen (ganz am Ende einfügen!):

[sourcecode lang=“ror“]
map.connect ‚/404.:format‘, :controller => "/general", :action => "error_404"
map.connect ‚/404‘, :controller => "/general", :action => "error_404"
map.connect ‚/500.:format‘, :controller => "/general", :action => "error_500"
map.connect ‚/500‘, :controller => "/general", :action => "error_500"
map.connect ‚*path‘, :controller => ‚/general‘, :action => ‚error_404‘ unless ::ActionController::Base.consider_all_requests_local
[/sourcecode]

Hier werden dann die Direktaufrufe der 404.html und /404 auf das richtige Script umgeleitet. Die letzte Zeile wiederum greift, wenn die aufgerufene URL zu gar keiner anderen Route passt, dann wird ebenfalls die 404 Fehlermeldung gerendert. Letzteres könnte man auch weglassen, denn wenn diese Route nicht da wäre, würde eh ein ActionController::UnknownAction Fehler eintreten und dann greift ja wieder die rescue_action_in_public().

Design Patterns

Natürlich kann man in jeden einzelnen Controller eine eigene rescue_action_in_public Methode einbauen, um so die Fehlerseite möglichst auf den Context anzupassen. So würde es zum Beispiel Sinn machen, in einen Mp3 Controller eine Fehlerseite einzubauen, die den Benutzer informiert, dass das gewünschte Mp3 File nicht vorhanden ist, er sich doch aber die folgenden anderen Songs anhören könnte. Auf diese Weise hält man sich an moderne Designrichtlinien, die genau dies empfehlen: Den Besucher nicht auf einer langweiligen 404-Fehlerseite zurücklassen, sondern stattdessen ihn an die Hand zu nehmen und zu gültigen Inhalten zu führen.

  • xbaun

    Hi,
    bin gerade über deinen Beitrag gestolpert und er hat mir auch gleich weitergeholfen 🙂
    Wollt dich nur auf einen Fehler aufmerksam machen, das Ruby bei der case-when Anweisung bei jeder when-Bedingung abbricht die zutrifft und nicht tiefer geht, auch wenn der ausführende Teil leer ist. Also hier schon bei ActiveRecord::RecordNotFound, wenn diese Exception zutrifft und somit wird der render Aufruf erst garnicht ausgeführt.

    mfg