Rails3, development env. i strony z błędami 404, 500 etc

Witam,

Od jakiegoś czasu uczę się railsów. Celem zaliczenia pewnego przedmiotu na studiach (jak i zdobycia sławy, pieniędzy oraz posady programisty rails :wink: ) postanowiłem stworzyć cms’a opartego na owej technologii. Wszystko jest już prawie na ukończeniu, wiec powoli zacząłem się zabierać za optymalizację kodu, dodawanie wodotrysków w jquery i za podstawowe zabezpieczenia. I tu właśnie dochodzimy do sedna sprawy…

Jeżeli umieszczę gdzieś moją aplikację, wtedy serwer będzie ładował środowisko production i jeśli wystąpi jakiś błąd, wyświetlona zostanie któraś ze stron (404, 500) z folderu public. Co jeśli chciałbym wyświetlić bardziej dynamiczne strony (np. wyświetlajace error message i najpopularniejsze produkty), albo poprostu przekierowywał użytkownika na stronę główną?
Googlując przez przez jakiś czas znalazłem 3 następujące rozwiązania…

  1. Pierwszym sposobem jest skorzystanie z metody rescue_from w application_controller’rze i stworzenie controller’a specjalnie do wyświetlania błędów…

[code]unless Rails.application.config.consider_all_requests_local
rescue_from Exception, :with => :render_error
rescue_from ActiveRecord::RecordNotFound, :with => :render_not_found
rescue_from AbstractController::ActionNotFound, :with => :render_not_found
rescue_from ActionController::RoutingError, :with => :render_not_found
rescue_from ActionController::UnknownController, :with => :render_not_found
rescue_from ActionController::UnknownAction, :with => :render_not_found
end
.
.
.
private
def render_error exception
Rails.logger.error(exception)
redirect_to root_path
#albo wyświetli stronę z controllera errors

render :controller=>‘errors’, :action=>‘error_500’, :status=>500

end

def render_not_found exception
Rails.logger.error(exception)
redirect_to root_path
#albo wyświetli stronę z controllera errors

render :controller=>‘errors’, :action=>‘error_404’, :status=>404

end[/code]
… ale to rozwiązanie nie dawje żadnych oczekiwanych rezultatów.
(Gdzieś czytałem, że to bug i ma to być poprawione w Rails 3.1)

  1. Drugim sposobem było dodanie
match "*path" , :to => "errors#error_404"

do routes.rb. Ta linijka kodu działała jedynie, jeśli użytkownik wywołał scieżkę typu http://www.aaa.pl/asdasdasdas. Jeśli wywołany zostałby adres http://www.aaa.pl/websites/asdasdasdas (controller istnieje, ale akcja nie), wtedy załadowana zostałaby statyczna strona z folderu public. Próbowałem jeszcze akrobacji typu "*path/*act" , :to => "products#show", :id=>1
albo match ":controller(/*act)" , :to => "products#show"
, ale one też nie zadziałały…

  1. Trzecim rozwiązaniem było stworzenie pliku z odpowiednim kodem i wrzucenie go do initializers’ów:

# initializers/error_pages.rb module ActionDispatch class ShowExceptions protected def rescue_action_in_public(exception) status = status_code(exception).to_s template = ActionView::Base.new(["#{Rails.root}/app/views"]) if ["404"].include?(status) file = "/errors/404.html.erb" else file = "/errors/500.html.erb" end body = template.render(:file => file) render(status, body) end end end
To rozwiązanie było najbardziej obiecujące, bo działało na wszystkie błędne adresy jakie testowałem, ale niestety nie wyświetlało layaut’u (dodanie deklaracji layout ‘user’ w controllerze nic nie dało). Co prawda mógłbym w tym momencie wrzucić kod z layaoutu do każdego z plików z błędem, ale co byłoby wtedy z konwencją DRY? :wink:

Wiem, że gdzieś pewnie popełniłem błąd. Jestem również pewien, że podczas pracy nad aplikacjami ktoś z Was zetknął się z tym problemem, liczę więc na Waszą pomoc i doświadczenie…

Pzdr.

IMO rozwiazanie 1, z tym ze ten bug o ktorym gdzies czytales dotyczy

rescue_from ActionController::RoutingError

i najprosciej sobie z tym poradzic dodajac do routes to co masz w 2 sposobie.

Poczytaj, sa tez inne rozwiazania
https://rails.lighthouseapp.com/projects/8994/tickets/4444-can-no-longer-rescue_from-actioncontrollerroutingerror

Dobrym pomysłem jest generowanie (za pomocą raketaska, odpalanego regularnie jeśli chcesz tam umieścić jakiś zmieniający się czasem kontent) statycznych stron błędów (na przykład raz na dobę w cronie). Jeśli dobrze to rozegrasz, to będziesz miał za jednym zamachem i strony błędu z layoutem jak reszta aplikacji, i odpowiednie ustawienie treści. Jeśli potrzebujesz kodu to powiedz, wrzucę na gista, w jakimś projekcie dawno temu to robiłem i zdawało egzamin.

macwoj - dzięki za link. W pierwszym poście Luigi Montanez najprawdopodobniej wyjaśnił dlaczego pierwsza metoda nie działa.
Jeśli chodzi o …

rescue_from ActionController::RoutingError

u mnie w konsoli, po wbiciu jakiegoś niepoprawnego adresu dostaję inny error…

AbstractController::ActionNotFound

… którego metoda nr 2 nie chce wychwycić. Ponadto drugi sposób, jak dalej jest to opisane w linku, może blokować moduły rozszerzające aplikację o swoje własne ścieżki.

Tomash - zaciekawiło mnie Twoje rozwiązanie, choć muszę przyznać, że jeszcze nie zetknąłem się z raketaskiem ;p Pytanie jednak jest następujące: co jeśli chciałbym wyświetlić dane np. o aktualnie zalogowanym użytkowniku w jakimś bocznym czy górnym panelu? Z opisu wynika, że twoje rozwiązanie było by dobre dla stron odświeżanych od czasu do czasu. Co jeśli dane musiały by być jednak dostarczane na bieżąco?

Wracając do metody nr czy…
Wpadłem na następujący pomysł:

module ActionDispatch class ShowExceptions def rescue_action_in_public(exception) status = status_code(exception).to_s template = (["404"].include?(status))? 404 : 500 ::ActionController::Base::redirect_to :controller=>"errors", :action=>"error_#{template}" end end end
Rzecz w tym, że w poprzedniej wersji użyta została metoda render, ale z ActionDispatch::ShowExceptions - dlatego nie można było przekazać informacji o layout’cie. Chodzi teraz o to, żeby w module powyżej zaimportować jakoś odpowiednią metodę z ActionController::Base.
Pytanie tylko jak? a może jakoś przeciążyć render, albo redirect_to ?

Jest metoda action_missing która działa podobnie dla akcji w kontrolerach jak method_missing dla wszystkich metod w ruby, możesz spróbować użyć.

slawosz - Na temat metody action_missing nic nie znalazłem. Jeśli chodzi o method_missing, to wykopałem na jakimś forum informację, że wystarczy tę metodę nadpisać w application_controller’rze:

def method_missing(m, *args, &block) Rails.logger.error(m) redirect_to :controller=>"errors", :action=>"error_404" end
po czym doszedłem do tego, że w połączeniu z metodą nr 1…

unless Rails.application.config.consider_all_requests_local rescue_from Exception, :with => :method_missing rescue_from ActiveRecord::RecordNotFound, :with => :method_missing rescue_from AbstractController::ActionNotFound, :with => :method_missing rescue_from ActionController::RoutingError, :with => :method_missing rescue_from ActionController::UnknownController, :with => :method_missing rescue_from ActionController::UnknownAction, :with => :method_missing end
… uzyskamy działające rozwiązanie HA! :slight_smile:

Jeśli macie jakieś dodatkowe uwagi, sugestie, czy rodzaj errora lub przypadku jakiego nie uwzględniłem, byłbym więcej niż rad za ich przytoczenie :wink:

pzdr.