Witaj na blogu prowadzonym przez Sebana. Spisuję tutaj swoje uwagi na różny temat. Przeważają tematy związane z Internetem, popieranymi przeze mnie rozwiązaniami dotyczącymi wykorzystania komputerów, oraz kilka innych. Przeczytasz tu również recenzje książek IT.

Whiny nils w Rails

09 kwietnia 2008 | Klucze: Ogólne, programowanie, rails, ruby, Techblog
3 komentarze. trackback

Czy zdarzyło wam się szukać błędu bardzo, bardzo długo, a na końcu krzyknąć ,,Eureka!''? Mi się w tym tygodniu zdarzyło. Dwa dni szukałem błędu w aplikacji, którą piszę w pracy. Sytuacja była o tyle dziwna, że testy wcale nie powodowały powstania tego błędu ...

Błąd pojawiał się w takiej linii:

 
  item.unit_id = Unit.find_by_name(some_name).id rescue Unit.create!(:name => some_name)
 
Abstrahując już od innych rzeczy w tym kodzie, szczególną uwagę należy zwrócić na rescue. W środowiskach development i test ten kod działał poprawnie. Po uruchomieniu aplikacji na środowisku ,,wstępnie produkcyjnym'' przestawał działać, pięknie rzucając wyjątkiem z poziomu MySQL o błędnym indeksie. Dopisałem sporo testów, ale to ciągle działa. Przejrzałem logi z maszyny produkcyjnej. Napisałem test w identycznymi parametrami jak request wyzwalający błąd, a to mi działa. Porównałem dla pewności jeszcze wersje gemów, nawet MySQL (tak wiem chore, ale tonący brzytwy się chwyta). Dziś rano okazało się, że wszystkiemu winne są whiny nils no i ja po części również. Poniżej przykład działania tej opcji konfigurującej środowisko.

 
# config.whiny_nils = true
>> Unit.find_by_name("bad name")
=> nil
>> Unit.find_by_name("bad name").id
RuntimeError: Called id for nil, which would mistakenly be 4
>> Unit.find_by_name("bad name").id rescue "tworzę nowy"
=> "tworzę nowy"
 
 
# config.whiny_nils = false
>> Unit.find_by_name("bad name")
=> nil
>> Unit.find_by_name("bad name").id
(irb):2: warning: Object#id will be deprecated; use Object#object_id
=> 4
>> Unit.find_by_name("bad name").id rescue "tworzę nowy?"
(irb):3: warning: Object#id will be deprecated; use Object#object_id
 
Czujecie różnicę? W drugim przypadku aplikacja nie miała nawet okazji stworzyć brakującego rekordu w tabeli Units bo nigdy nie mogło tam dojść do wyjątku, tylko ostrzeżenie (które nota bene też nie pojawiało się w logu :(). Jeśli jeszcze nie to spróbujcie kilka razy w konsoli irb nil.id lub nil.object_id. Wynik łatwy do przewidzenia ;-).

Co w tym wszystkim mnie zastanawia najbardziej? Czy domyślna konfiguracja środowisk development i test narzekająca whiny_nils na nil.id jest prawidłowa? Niby zachowanie prawidłowe bo Rails krzyczy do programisty o złym użyciu metody id, ale z drugiej strony ciekaw jestem czy wielu przechwytuje ten wyjątek i w jakiś sposób go obsługuje, nawet nieświadomie. I czemu w środowisku production ta opcja jest wyłączona? A dla Was, które ustawienie wydaje się ciekawe? Wolicie wyjątek czy ostrzeżenie?


KOMENTARZE

11 kwietnia 2008 | Radarek |

I tak i nie. Zauważ, że metodę „id” posiada każdy obiekt w Rubym (Object#id), dlatego ciężko żeby przy whiny_nils=false railsy wchodziły Ci w drogę gdy wywołujesz metodę „id” na nilu (to też przecież obiekt). W Rubym 1.9 nie będzie już tej metody (zostaje tylko „object_id”), więc prawdopodobnie wyłapałbyś ten błąd.

Ostrzeżenie (w 2 przypadku) nie mogło się pojawić w logach bo pochodzi od interpretera Rubiego a nie od samych Railsów. Pojawiło się prawdopodobnie na konsoli serwera lub w jego logach.

11 kwietnia 2008 | Radarek |

Jeszcze gwoli ścisłości:

$ irb
>> nil.id
(irb):1: warning: Object#id will be deprecated; use Object#object_id
=> 4

12 kwietnia 2008 | Seban |

Radarku wiem o Object#id i Object#object_id. Największe moje zdziwienie wywołała różnica pomiędzy konfiguracjami środowisk development, test, a production.