W Grails każda klasa dziedzinowa (ang. domain class) jest automatycznie rozszerzana (dzięki mechanizmowi metaprogramowania z Groovy) o metody, które pozwalają na wykonywanie zapytań zwracanych jako konkretne egzemplarze (instancje). Aby się o tym przekonać możemy skorzystać z podpowiadania w dowolnym IDE wspierającym Grails, np. NetBeans czy IntelliJ IDEA, bądź uruchomić konsolę Grails - poleceniem grails console - i skrypt (zakłada się dostępność klasy dziedzinowej Ksiazka):
import pl.jaceklaskowski.grails.*W wyniku otrzymamy aż 45 metod (nie wiem, jak wyświetlić dodatkowe metody, np. findBy, ale one mogą być obsługiwane przez metody methodMissing)
// utworzenie egzemplarza klasy dziedzinowej, np. Ksiazka
def args = [tytul: "O Grails", autor: "Jacek Laskowski"]
def ksiazka = new Ksiazka(args)
// wypisanie wszystkich dostępnych metod
def i = 0
ksiazka.metaClass.methods.each { metoda -> println "${++i}. $metoda" }
i
Typowym użyciem generowanych metod jest bodaj najpopularniejsza z nich, statyczna get() (jej również nie ma powyżej?!), która przyjmuje identyfikator klasy dziedzinowej i zwraca jej egzemplarz lub null.
def ksiazka = Ksiazka.get(params.id)Można również skorzystać z wersji rozszerzonej getAll(), która akceptuje serię identyfikatorów (lista lub varargs) i zwraca listę egzemplarzy.
def ksiazki = Ksiazka.getAll(1,2,3)Wczytanie egzemplarza klasy dziedzinowej w trybie do-odczytu, tj. zmiany nie zostaną utrwalone w bazie danych, to metoda read().
def ksiazka = Ksiazka.read(params.id)GORM udostępnia metodę list(), której parametry wejściowe określają liczbę zwracanych egzemplarzy (argument max), miejsce początkowe (offset) oraz sposób ich uporządkowania (sort oraz order z wartościami desc oraz asc).
def ksiazki = Ksiazka.list(max:10, sort: 'tytul', order: 'desc')Metoda count() zlicza wszystkie dostępne egzemplarze w bazie danych (w zasadzie powinienem napisać rekordów w bazie danych).
GORM udostępnia odmianę metody list - listOrderBy* dla każdego atrybutu klasy dziedzinowej, np. listOrderByTytul() czy listOrderByAutor().
Metoda save() zapisuje egzemplarz w bazie danych (zakładając, że kontrola poprawności danych zakończy się pomyślnie). W zależności od zmiany Hibernate (podstawa technologiczna GORM) wykona INSERT albo UPDATE. Dla niektórych baz danych może być konieczne jawne wskazanie INSERT - parametr insert z true/false, np.
ksiazka.save(insert:true)Skasowanie egzemplarza to delete().
W typowym zastosowaniu GORM związek 1-* oparty jest o java.util.Set. Jeśli chcielibyśmy zagwarantować porządek możemy wskazać Grails, aby związek oparty był na SortedSet (nie pozwala na duplikaty i utrzymuje porządek), przez jawne wskazanie typu dla atrybutu odpowiadającego związkowi, np.
class Ksiazka {SortedSet wymaga, aby umieszczane egzemplarze pochodziły od klasy implementującej Comparable (z metodą int compareTo(o)).
...
SortedSet autorzy
}
Możemy również wskazać porządek w zmiennej statycznej mapping z sort, np.
class Ksiazka {Jeśli jedynie wybrane zapytanie czy związek ma podlegać specjalnemu porządkowi wskazujemy interesujący nas związek przez jawne podanie go w mapping z sort, np.
...
static mapping = {
sort "nazwisko"
}
}
class Ksiazka {Można również podeprzeć się deklaracją związku opartej na java.util.List (pozwala na duplikaty i zachowuje porządek wstawiania). Możliwe jest również zdefiniowanie związku na mapie, z java.util.Map.
...
static mapping = {
autorzy sort "nazwisko"
}
}
GORM udostępnia metody addTo* oraz removeFrom* do, odpowiednio, dodawania i usuwania egzemplarzy ze wskazanego związku zwracając egzemplarz klasy dziedzinowej, na której zostały wywołane (kłania się koncepcja "fluent API"). Możemy, więc stworzyć dosyć interesujące konstrukcje programistyczne, gdzie zamiast podawać/powoływać egzemplarz i przypisywać/usuwać ze związku GORM zrobi to za nas, np. (Listing 10-8):
new Album(title:"Odelay",Zmiany w egzemplarzach mogą być propagowane "w dół" (kaskadowane) do egzemplarzy w związkach. Określenie propagowania zmian jest za pomocą właściwości belongsTo (ustala stronę wiodącą w związku na wskazaną klasę dziedzinową), np.
artist:beck,
year:1996)
.addToSongs(title:"Devil's Haircut", artist:beck, duration: 342343)
...
.save()
class Zona {Od razu wiadomo, kto rządzi w tym związku ;-) W takiej konfiguracji, skasowanie Zona kasuje egzemplarze Maz. Bez belongsTo, jedynie zapisy i aktualizacje są kaskadowane, ale nie kasowanie. Za pomocą metody cascade w bloku mapping określamy zachowanie kaskadowania, np.
// mąż
Maz maz
}
class Maz {
static belongsTo = [zona:Zona]
}
class Zona {Istnieje specjalny rodzaj kaskady delete-orphan dla przypadku, kiedy chcemy skasować obiekt zależny po usunięciu go ze związku bez jawnego skasowania.
Maz maz
static mapping = {
maz cascade:'save-update'
}
}
W GORM mamy do dyspozycji budowane dynamicznie zapytania typu find (ang. dynamic finders). Ich działanie polega na tworzeniu zapytań odpowiadających nazwie metody findBy z dołączonymi atrybutami klasy dziedzinowej z kwalifikatorami - Between, Equals, GreaterThan, GreaterThanOrEqual, InList, IsNull, IsNotNull, LessThan, LessThanOrEqual, Like oraz NotEqual, które z kolei łączymy w związki logiczne (z And lub Or) z parametrami wejściowymi będącymi koniecznymi do działania zapytania wartościami, np.:
Ksiazka.findByTytulLike('%Grails%')Podobnie działają dynamicznie tworzone metody findAllBy* oraz countBy*. grails console naszym sprzymierzeńcem, aby rozeznać się w możliwościach dynamicznych zapytań w GORM. W ten sposób, pozbywamy się warstwy DAO na dobre, którą staje się...GORM.
def dzisiaj = new Date()
Ksiazka.findByDataWydaniaBetween(dzisiaj-10, dzisiaj)
Ksiazka.findByTytulLikeAndDataWydaniaBetween('%Grails%', dzisiaj-10, dzisiaj)
Na dzisiaj wystarczy tego GORMowego szaleństwa. Dokończenie w kolejnym wpisie.
W Ruby Active Record wszystkie metody find_by_..., find_or_initialize_by_... są obsługiwane przez method_missing w GORM pewnie podobnie.
OdpowiedzUsuńCzy te klasy dziedzinowe muszą dziedziczyć po jakiejś specyficznej klasie tak jak w ActiveRecord muszą dziedziczyć po ActiveRecord::Base?
Nie. Są czyste, aż do granicy bólu ;-) Czyżbym obserwował narodziny Grailsera (=Grailsowca) z obozu Rubyists?! Mamy już chlebika z doświadczeniem PHPowym, teraz kolej na Rubystę. Akurat pasuje do określenia Grubysta, albo nawet RuGbysta :D
OdpowiedzUsuńGroovy, Grails i Scala interesują mnie i planuję trochę o nich poczytać w wolnej chwili
OdpowiedzUsuń