taak.
trafił mnie dziś bardzo “fajny" błąd.
w sofcie który pisałem, piszę i się zajmuję mam taki kawałek kodu:
for my $object ( @objects ) { next unless $self->validate_object( $object ); $self->save_object_to_database( $object ); }
kod ten pobiera z podanej listy obiekty (tak naprawdę to nie obiekty tylko struktury (hasze haszy). potem waliduje zawartość i jeśli walidacja się udała – wpisuje do bazy.
trywiał.
jednym z elementów walidacji jest podmiana pewnych wartości na wartości słownikowe.
powiedzmy, że mamy w “obiekcie" element “region" i ma on wartość “WARSZAWA". w odpowiednim słowniku sprawdzam czy wartość WARSZAWA jest dopuszczalna. jak nie – walidacja się nie udała. jak tak, zamiast stringu “WARSZAWA" wstawiam numeryczny identyfikator z bazy. np. 15.
kod który to robi:
sub validate_object { my $self = shift; my $object = shift; while (my ($param, $dictionary) = each %{ $self->validation_rules }) { next unless defined $object->{ $param }; my $object_value = $object->{ $param }; if ( $self->dictionaries->{ $dictionary }->{ $object_value } ) { $object->{ $param } = $self->dictionaries->{ $dictionary }->{ $object_value }; next; } $self->log("error at validation ..."); return; } return 1; }
może nie jest to najbardziej czytelne, ale po kolei:
hash $self->validation_rules ma pary klucz/wartość, gdzie klucz jest nazwą klucza (elementu) z obiektu, a wartość jest nazwą słownika którym mamy dany element walidować.
przykładowo hash ten może zawierać:
"region" => 'REGION_LIST'
reguł jest standardowo około 10.
słowniki są zwracane z metody $self->dictionaries(). zwracana struktura to hashref, mający jako klucz nazwę słownika, a jako wartość – hashref z parami – tekstowa wartość => numeryczny identyfikator.
przykładowo:
{ 'REGION_LIST' => { 'WARSZAWA' => 1, 'KRAKÓW' => 2, ...}, 'CATEGORIES_LIST' => { 'MOTO' => 15, 'AGD' => 21, ... }, ... }
proste.
czy widzicie błąd w kodzie funkcji validate_object() ?
nie?
podpowiem. objaw który do mnie trafił, to to, że metoda save_object_to_database() zwracała bład sql'a, który mówił, że wartość ‘WARSZAWA' nie jest prawidłowa dla pola numerycznego.
nadal nie wiecie?
otóż metoda each(). zapamiętuje ona w haszu ostatnio zwrócony element. tak aby przy następnym wywołaniu zwrócić kolejny.
co się więc stanie gdy któryś z obiektów sie nie zwaliduje?
załóżmy, że mamy 10 reguł. od 1 do 10. przy obiekcie “a" zwalidowały się reguły 1, 2, 3, 4, a przy regule 5 pojawił się błąd. został zalogowany ($self->log), metoda validate_object się skończyła pustym returnem. więc w głównej metodzie został pobrany kolejny obiekt – “b".
przy walidowaniu obiektu “b", wywołujemy each(), który zwraca którą regułę? 6! potem 7, 8, 9, 10 i na tym skończy. czyli reguły 1-5 w ogóle nie są sprawdzone. i nawet jeśli obiekt jest poprawny – wartości odpowiednich pól nie zostają zamienione na id'y. i stąd błąd przy insercie.
czemu o tym piszę?
dwa powody.
po pierwsze: może się to komuś przyda.
po drugie: może to spowoduje, że o tym nie zapomnę. i następnym razem zamiast bawić się each() użyję po prostu keys.