Tag g:select w Grails

Ostatnio miałem z Grailsami cokolwiek niefajną sytuację, a poszło o tag g:select. Zasadniczo nie wiem, co zawiniło – moje luźne podejście, brak dostatecznego przetestowania, czy mało przejrzysta dokumentacja. Pewnie wszystko po trochu.

Zacznijmy jednak od początku. Jak można się łatwo domyślić w GSP użycie tagu g:select ma docelowo doprowadzić do wyrenderowania znacznika <select>. Póki co wszystko jasne. Znaczącym ułatwieniem jest również fakt, iż znacznik ten może posiadać atrybut multiple, który pozwala użytkownikowi na wybranie nie jednego, ale kilkunastu elementów z listy. W moim przypadku były to kategorie – choć to rzecz bez znaczenia, grunt, iż etykietami były łańcuchy znaków, zaś przekazywanymi wartościami identyfikatory liczbowe (konkretnie wartości klucza głównego w bazie danych). Przyjmijmy taką oto sytuację:

<g:select name="kategorie" from="${kategorieList}" value="${wybranePoprzednioID}"
 optionKey="id" multiple="true" />

Na liście kategorii znajduje się kilkanaście obiektów, zaś identyfikatory wybrane z listy (po np. wadliwej walidacji i ponownym wyrenderowaniu formularza) znajdują się w zmiennej/kolekcji o nazwie ${wybranePoprzednioID}. Po stronie kontrolera działał taki oto pseudokod:

if( kategorie ) {
    kategorie.each {
       zapiszIDDoBazy( it )
    }
}

Kiedy wybierano na liście tylko jeden element wszystko było OK. Co więcej – kiedy na liście wybierano kilka elementów również wszystko było OK. Problem zaczął się w momencie, gdy aplikacja wylądowała na serwerze preprodukcyjnym. W czym problem? Otóż po wybraniu jednego elementu z listy okazywało się, że po ponownym wyświetleniu formularza (po udanym zapisie do bazy danych) zaznaczona kategoria nie pokrywała się z tą, którą wybrano przed zapisaniem. Ot zagwozdka. Przy wybieraniu dwóch lub więcej elementów – działało poprawnie. Szybki debug i oto co się okazało.

Kategorie w bazie danych znalazły się ‘ręcznie’. To znaczy, że zostały wprowadzone grubym klientem bezpośrednio do bazy danych i oczywiście sekwencja przydzieliła ładne identyfikatory startując od numeru 1 (z krokiem też o 1). Zatem mieliśmy bodajże 7 kategorii, spośród których można było wybierać, każda posiadająca identyfikator mniejszy niż 10. Wraz z przejściem na preprodukcję do pracy wzięli się nie tylko testerzy (ci przetestowali aplikację i wszystko było OK, nawet po dodaniu kategorii), ale również docelowi użytkownicy. A ci z kolei pododawali o wiele więcej kategorii do wyboru i tym samym identyfikatory rekordów zaczęły składać się z 2 cyfr. Co z tego wynika?

Otóż Groovy w konstrukcji each przechodzi po kolejnych elementach obiektu, na rzecz którego został wywołany. W przypadku kolekcji oznacza to przejście po wszystkich elementach, zaś w przypadku łańcuchów tekstowych – po każdym znaku w tymże łańcuchu! Przy wybraniu kilkunastu elementów wartość parametru była kolekcją – zatem iteracja szła dobrze. Przy zaznaczeniu kategorii ‘początkowych’, czyli takich, które miały identyfikatory mniejsze niż 10 również wszystko było OK. Problemem było wybranie 1 elementu o identyfikatorze np. 14Grails traktował wówczas parametr jako łańcuch tekstowy i wykonanie takiego kodu:

if( kategorie ) {
 kategorie.each {
println it
 }
}

Dla identyfikatora 14 dawało na wyjściu:


1
4

Czyli jako dwie oddzielne wartości. Widzicie już problem? W bazie danych istniały kategorie o takich identyfikatorach (1 oraz 4) zatem przy zapisie nie było mowy o wyrzuceniu wyjątku czy błędach – wszystko się zgadzało, kategorie istniały to Grails grzecznie je zapisał. Problem był taki, że to dane wejściowe nie były do końca tymi, na które liczyliśmy.  Czyli zamiast jednej kategorii o identyfikatorze 14 mieliśmy dwie kategorie o identyfikatorach oraz 4.  Typowy błąd logiczny – godzinka na debugowaniu w plecy.  Stąd ten wpis i mam nadzieję, że dzięki niemu zdarzy się uniknąć komuś takiej sytuacji 😉

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s