Page 145 of 173

Rails3 + własne dynamiczne strony błędów (404 i 500)

Uwaga! - kod ten działa TYLKO dla Railsów3.
Opis "starego" routingu błędów dostępny tutaj.

Migrując Susanoo na Rails 3.0.0 okazało się że poprzez zmiany w middleware i obsługę błędów w inny sposób - mój system zarządzania renderowaniem błędów nie działa.Szczerze mówiąc niezbyt mnie to martwiło, ponieważ nie był on "aż tak" fajny. Korzystając z okazji (czyt. nie mając wyjścia) zacząłem googlać za sposobami rozwiązania tego problemu. Metody takie jak

rescue_action_in_public
rescue_action_locally

przestały działać.Pierwszą myślą jaka mi się nasunęła, było obsługiwanie błędów przerzucając "złe trafienia" po routsach do kontrolera błędów

match ':action', :to => "errors#index"
match '*anything', :to => "errors#index"

niestety rozwiązanie takie miało jedną wielką wadę - nie obsługiwało internal server error'ów. Błędy 404 mogłem za jego pomocą obsługiwać, jednak w wypadku wystąpienia 500 - nic z tego. Dodatkowo takie rozwiązanie nie działało w wypadku zgłoszenia wyjątkuActiveRecord::RecordNotFound.Nie było więc wyjścia ;) trzeba było napisać własny plugin. Poniżej opiszę jak działa, a tymczasem dla niecierpliwych link do githuba:
http://github.com/mensfeld/custom_errors_handler.

W README jest dokładnie opisane jak go wykorzystać, jednak tutaj wspomnę dla pewności:

  • wrzucamy dop vendor/plugin
  • tworzymy 404.erb, 500.erb w views danego kontrolera/modułu lub w views kontrolera/modułu ale w subkatalogu "layouts"
  • reset serwera
  • "sztuczne" bądź też prawdziwe wygenerowanie 404/500
  • ?
  • profit ;)

A teraz omówienie pluginu.

Cała zabawa w utworzenie tego typu pluginu, polegała na “przejęciu” błędów z ActionDispatch::ShowExceptions. Można to było zrobić, nadpisując metodę render_exception.

Jak widać poniżej, sam kod nie jest wielki. Przechwytuje on błędy i przekazuje je do kontrolera CustomErrorsHandler gdzie nastąpi rozpoznanie błędu i wyrenderowanie odpowiedniego szablonu.

module ActionDispatch
  class ShowExceptions
    private
      def render_exception_with_template(env, exception)
        body = CustomErrorsHandlerController.action(rescue_responses[exception.class.name]).call(env)
        log_error(exception)
        body
      rescue
        render_exception_without_template(env, exception)
      end

      alias_method_chain :render_exception, :template
  end
end

Skoro mamy już przekazywanie błędów, przejdźmy do kontrolera który się tym zajmuje. Jest to CustomErrorsHandlerController.

Obsługuje on 3 rodzaje błędów:

  1. :internal_server_error (500)
  2. :not_found (404)
  3. :unprocessable_entity (403)

Korzysta także z 3 metod pomocniczych:

  1. error_layout
  2. template?
  3. translate_error

Zanim opiszę kod odpowiedzialny za działanie, opiszę każdą z tych metod.

Pierwszą będzie translate_error:

  def translate_error(e)
    case e
    when :internal_server_error then '500'
    when :not_found then '404'
    when :unprocessable_entity then '500'
    else '500'
    end
  end

Metoda ta zwraca "liczbowy" odpowiednik danego błędu. Potrzebujemy tego, ponieważ w naszych szablonach nie chcemy używać szablonuinternal_server_error.erb ale raczej 500.erb.

Druga czyli template? zwraca nam informację czy dany plik szablonu istnieje. Ścieżkę podajemy zaczynając od widoków, czyli nie app/views/jakis_kontroler/, ale /jakis_kontroler/. Kod tej metody jest dość prosty:

  def template?(template)
    FileTest.exist?(File.join(Rails.root, 'app', 'views', "#{template}.erb"))
  end

Ostatnią metodą jest error_layout. Odpowiada ona za znalezienie szablonu błędu, przeszukując strukturę katalogów, zaczynając od ścieżki "najgłębszej", stopniowo idąc wyżej w hierarchii katalogów.

  def error_layout(path, e)
    e = <strong>translate_error</strong>(e)
    path= path.split('/')
    path.size.downto(0) do |i|
      VALID_ERRORS_SUBDIRS.each { |lay_path|
        template_path = File.join((path[0,i]).join('/'), lay_path, e)
        return template_path if template?(template_path)
      }
      template_path = File.join(path[0,i], e)
      return template_path if template?(template_path)
    end
    e
  end

Metoda ta przyjmuje dwa argumenty - pierwszym z nich jest path. Path jest ścieżką skąd wywołany był błąd. Czyli przykładowo: jeśli błąd wystąpił w kontrolerze MyTest w module Samples (Samples::MyTest), w akcji index, to będzie następujący:

/samples/my_test/index

Parametr e jest to nazwa błędu, którą następnie tłumaczymy naszą metodą translate_error.

Mając ścieżkę rozbijamy ją tak, aby mieć jej kolejne "stopnie".

    path= path.split('/')

Następnie "przelatujemy" całą strukturę sprawdzając takie katalogi (w kolejności):

/samples/my_test/index/layouts
/samples/my_test/index
/samples/my_test/layouts
/samples/my_test/
/samples/layouts/
/samples/
layouts

Skąd się wzięło "layouts"? Otóż w stałej VALID_ERRORS_SUBDIRS mamy zdefiniowane podkatalogi katalogów które sprawdzamy, tak by sprawdzić także je. Dzięki temu, layouty błędów możemy umieszczać w podkatalogach layouts danych kontrolerów/modułów, nie zaś w katalogach głównych.

Zdefiniowane jest to jako stała, ponieważ może ktoś z Was zapragnie mieć inne podkatalogi na błędy (np podkatalog errors). Wtedy wystarczy sobie takowe dopisać.

Następnie łączymy ścieżkę oraz kod błędu i sprawdzamy metodą template? czy taki szablon istnieje. Odpowiada za to, ten fragment kodu:

      VALID_ERRORS_SUBDIRS.each { |lay_path|
        template_path = File.join((path[0,i]).join('/'), lay_path, e)
        return template_path if template?(template_path)
      }
      template_path = File.join(path[0,i], e)
      return template_path if template?(template_path)

Na samym końcu, jeśli żaden z naszych "dynamicznych" 404/500 nie został znaleziony, zwracamy standardowy 404/500. Skutkuje to wyrenderowaniem layoutu znajdującego się w /public.

To by było na tyle. Jeśli chodzi o testy do tego pluginu, to nie bardzo wiem jak przetestować ActionDispatch::ShowExceptions. Jeśli ktoś z Was to potrafi, prosiłbym o kontakt.

Rails 3 + Paperclip + Errors, Errors, Errors ;)

Testując dzisiaj paperclipa, okazało się że mimo tego że na gicie jest branch dla Rails3, to niestety nie działa on zbyt dobrze.

Wprawdzie działa on przyzwoicie jeśli przesyłamy poprawne pliki (typ, rozmiar), jednak gdy zaczynamy "się bawić", Paperclip sypie m.in. takimi błędami:

can't convert nil into Integer
An error was received while processing: #<Paperclip::NotIdentifiedByImageMagickError
 #<Paperclip::NotIdentifiedByImageMagickError: /tmp/stream,9641,0.zip is not recognized by the 'identify' command.>

Rozwiązaniem tego problemu jest zainstalowanie Paperclipa jako pluginu, nie jako gema. Wiem że nie jest to ani zbyt eleganckie ani zgodne z drogą obraną przez Rails3, jednak póki co ważniejsze jest że działa ;)

Ważne!
Nowe wersje z branchu Rails3, jak i z branchu dla Rails2, nie działają! Jeśli chcesz odpalić Paperclipa w Rails3 - pobierz wersję 2.2.9.1. Następnie podmień w pliku paperclip.rb RAILS_ROOT na Rails.root.

Ważniejsze!
Nowe wersje z mastera działają na Rails3 bez zarzutu (ale niestety nie na każdym systemie - nie mam pojęcia od czego to zależy).

Copyright © 2025 Closer to Code

Theme by Anders NorenUp ↑