Rozdział 3. "Understanding Domain Classes" dotyczy warstwy modelu, który w Grails jest oparty na klasach dziedzinowych (odpowiadają one encjom w JPA). Każda z klas dziedzinowych składa się z właściwości (atrybutów), które domyślnie są mapowane na tabele w bazie danych, aby możliwy był trwały zapis stanu ich egzemplarzy (instancji). Poza kolumnami, które odpowiadają właściwościom klasy dziedzinowej, Grails dodaje kolumny id (unikatowy identyfikator) oraz version (służy do obsługi optymistycznego blokowania).
Warunki jakie muszą spełniać atrybuty klasy dziedzinowej wyrażane są przez statyczną właściwość constraints, której wartością jest domknięcie. Domknięcie może zawierać ograniczenia dla podzbioru atrybutów. Wykonanie save() klasy dziedzinowej to automatyczne wykonanie domknięcia constraints, którego niespełnienie skutkuje błędem kontroli i wstrzymaniem zapisu do bazy danych.
Z klasą dziedzinową związany jest automatycznie generowany atrybut errors - egzemplarz klasy org.springframework.validation.Errors. Jedną z metod interfejsu Errors jest getAllErrors(), więc dostęp do wszystkich błędów kontroli poprawności danej klasy dziedzinowej sprowadza się do:
<egzemplarz-klasy-dziedzinowej>.errors.allErrors.each {Grails dostarcza metodę validate() do każdej klasy dziedzinowej, która zwraca wartość logiczną true, przy poprawnie zakończonej kontroli poprawności. Mamy do dyspozycji również metodę clearErrors(), która czyści zgłoszone do tej pory błędy (błędy są kumulowane).
// wykonaj operację na pojedynczym błędzie reprezentowanym przez it, np.
println it.defaultMessage
}
Poza prostymi warunkami (ograniczeniami) określonymi w domknięciu constraints (w książce wymieniono 19 reguł), możemy zdefiniować własne przez domknięcie validator, która zwraca wartość logiczną true przy poprawnie zakończonej kontroli. Domknięcie validator akceptuje dwa argumenty - pierwszy jest wartością atrybutu do kontroli, a drugi egzemplarz klasy dziedzinowej. Możemy również zwrócić wartość typu String, która będzie identyfikatorem komunikatu błędu (domyślnie <klasa-dziedzinowa>.<atrybut>.validator.error).
Wyłączenie pola z mechanizmu utrwalania (zapisu do bazy) - oznaczenie pola jako ulotne (ang. transient) - to umieszczenie jego nazwy w liście w statycznym atrybucie transients, np.:
class Wyrazenie {To jest pierwsza z konstrukcji, które nie podobają mi się, bo przede wszystkim wymagają wsparcia IDE, aby nie popełnić błędu - literówki - w nazwie pola ulotnego. Aż się prosi o mechanizm adnotacji, albo stworzenie nowego podobnego do constraints, gdzie podaje się nazwy atrybutów jako metody. Skoro tam można, tu również. Muszę zapytać o to na grupie użytkowników Grails. ...po chwili...Już!
Integer poleUlotne
static transients = ['poleUlotne']
}
Nie wszystkie atrybuty klasy dziedzinowej muszą odpowiadać polu (instancji) klasy. Wystarczy metoda odczytu (ang. getter) albo zapisu (ang. setter), aby Grails potraktował to jako atrybut klasy. Zasada ulotności działa również i w tym przypadku. Wyłączenie z zapisu to transients z nazwą atrybutu.
Grails, za pomocą GORM, mapuje klasy dziedzinowe bez konieczności plików XML - "objaw" zasady konwencji-ponad-konfigurację. Możliwe jest zdefiniowanie własnego mapowania za pomocą dedykowanego DSL zwanego Custom Database Mapping DSL lub ORM DSL. Warto z niego skorzystać przy wdrażaniu aplikacji z istniejącym schematem bazodanowym, który nie przystaje do domyślnego mapowania w Grails. Użycie ORM DSL jest możliwe przez deklarację publicznego atrybutu statycznego mapping jako domknięcia z column, np. (Listing 3-13):
class Person {Dodatkowo w przykładzie wyłączono wersjonowanie - version false, które służy do optymistycznego blokowania.
String firstName
static mapping = {
id column: 'person_id'
firstName column: 'person_first_name'
version false
}
}
Domyślna nazwa tabeli odpowiada nazwie klasy dziedzinowej - zmiana przez table 'nazwa-tabeli' w mapping.
Relacje w Grails - zadeklarowanie atrybutu w klasie dziedzinowej typu innej klasy dziedzinowej to relacja jeden-do-jednego, w której obie strony są równoważne (nie ma właściciela relacji). Określenie strony wiodącej - właściciela relacji - to static belongsTo = [car:Car], gdzie klasa zawierająca tą deklarację będzie automatycznie posiadała atrybut car typu Car. Nazwa atrybutu może być dowolna, ale naturalnie nazwać ją zgodnie z jej typem (bo ta nazwa będzie nazwą bytu w dziedzinie/modelu, więc będzie intuicyjna). Jeśli nie chcemy modelować zależności zwrotnej (zależnej), np. w klasie Car z powrotem do klasy wiodącej, wystarczy w klasie wiodącej zadeklarować belongsTo z typem Class zamiast mapą, np. static belongsTo = Car. Oczywiście w obu przypadkach operacje są przekazywane kaskadowo w dół grafu (powiązań) i np. skasowanie klasy wiodącej będzie kasowało klasę zależną.
Relacja jeden-do-wielu modelowana jest atrybutem hasMany, która jest mapą, która z kolei składa się z klucza będącego automatycznie dodawanym do klasy dziedzinowej atrybutem-kolekcją oraz wartością mapy będącej typem obiektów w kolekcji. Domyślną kolekcją jest java.util.Set. Jeśli potrzebujemy bardziej wyrafinowanej kolekcji musimy zadeklarować jej typ explicite, np. (Listing 3-19):
class Album {Klasa dziedzinowa w Grails może rozszerzać inną za pomocą typowej konstrukcji Groovy/Java - słowo kluczowe extends. W/g autorów dobry model charakteryzuje się co najwyżej 2-poziomową hierarchią dziedziczenia. Domyślnym mapowaniem dla hierarchii dziedziczenia jest tablica-per-hierarchia. Włączenie mapowania tablica-per-klasa to deklaracja tablePerHierarchy false w static mapping w klasie macierzystej (najbardziej ogólnej). tablica-per-hierarchia nie pozwala na włączanie warunków typu "niepuste", ale jest szybka w działaniu, podczas gdy tablica-per-klasa to powolne zapytanie złączeniowe (JOIN) na kilku tabelach. Strategia mapowania jest deklarowana na poziomie hierarchii/klasy i może być różna dla różnych klas dziedzinowych.
String title
static hasMany = [songs:Song]
SortedSet songs
}
Włączenie zanurzenia klasy jakby była integralną częścią składową innej, tak aby pojedyncza tabela zawierała kolumny obu to deklaracja static embedded = ['nazwa-atrybutu'], gdzie typ nazwa-atrybutu to klasa zanurzana.
Grails wspiera dwa typy testowania - jednostkowe i integracyjne. Pierwsze znajdują się w klasach w katalogu test/unit, a drugie w test/integration. Rozbudowywanie klas dziedzinowych o metody dynamiczne, np. validate(), save() jest wyłącznie w testach integracyjnych. Stąd testy jednostkowe są bardzo szybkie (bez całego bagażu "integracyjnego"). Każdorazowe wykonanie polecenia grails create-* tworzy poza właściwym bytem, również klasę z testem integracyjnym.
Brak komentarzy:
Prześlij komentarz