GRAILS: Groovy pentru aplicatii Web
Grails ca tehnologie Web
Dezvoltarea aplicatiilor Web a rãmas un proces complex si dificil, în ciuda eforturilor de simplificare a acestui proces prin crearea de produse de tip “framework”. In plus, au apãrut noi cerinte impuse acestor aplicatii si noi tehnologii (Web Services, Ajax, Portleti s.a), care duc la mãrirea efortului necesar pentru stãpânirea si folosirea noilor tehnologii.
Una dintre problemele aplicatiilor Web care a devenit supãrãtoare prin amplificarea sa este cea a fisierelor XML cu rol de descriptor sau de configurare. Aceste fisiere sunt necesare pentru integrarea de componente Web dezvoltate separat în aplicatii Web complete, pentru adaptarea aplicatiilor la un anumit server si pentru modificarea unor parametri ai aplicatiei fãrã modificarea codului sursã al acestor componente.
Primele fisiere de configurare XML (numite “web.xml”) au apãrut odatã cu componentele de tip servlet având mai multe functii: asocierea clasei Java cu rol de servlet cu un nume folosit de clienti în cererile HTTP, asocierea unor pagini afisate în caz de eroare cu codurile de eroare, numele paginii afisate initial (care se adaugã unui URL partial incomplet), asocierea unui nume cu contextul aplicatiei, asocierea de nume unor resurse (baze de date si alte componente) s.a.
Spring a fost conceput ca un mecanism software de asamblare a unor componente Web dezvoltate separat, de diverse firme, în aplicatii Web complete, functionale. Legãturile dintre componente sunt specificate tot în fisiere de configurare XML.
O simplificare importantã a acestei probleme s-a produs prin utilizarea de conventii care sã înlocuiascã configurarea explicitã (“Convention over configuration”), idee adusã în atentia publicã de un framework numit “Ruby on Rails (RoR)”, bazat pe limbajul “Ruby” (diferit de Java).
Acest framework s-a remarcat si prin reducerea codului sursa Ruby în raport cu codul Java necesar pentru aceeasi functionalitate oferitã de aplicatie.
In paralel cu extinderea si perfectionarea tehnologiei RoR a apãrut în comunitatea Java ideea aducerii acestei tehnologii pe platforma Java în douã forme:
- Prin limbajul JRuby care pãstreazã sintaxa Ruby dar genereazã cod intermediar Java si foloseste JRE, adicã masina virtualã Java si bibliotecile de clase Java;
- prin limbajul Groovy cu o sintaxã similarã cu Java, dar cu inovatii preluate din Ruby, cum ar fi obiectele functionale (“closures”) s.a.
Grails este un framework complet (“full-stack”) pentru aplicatii Web, bazat pe limbajul Groovy si care înlocuieste configurarea prin respectarea unor conventii, la fel ca si Ruby on Rails. Grails nu aduce noi tehnologii Web, dar simplificã considerabil utilizarea unor tehnologii existente si se bazeazã pe câteva produse de succes cum sunt Spring si Hibernate (pentru ORM).
Intr-o aplicatie Grails se pot utiliza cod Java, pagini JSP si metode JDBC, dar este mult mai simplu sã se foloseasca Groovy în loc de Java (pentru clasele controller care corespund unor servleti si pentru clasele domeniu care descriu datele folosite în aplicatie), sã se utilizeze facilitatile GORM în locul claselor Hibernate, sã se foloseascã noile tag-uri din paginile GSP în locul celor din paginile JSP, etc. Prin extensiile de tip “plugin” se simplificã si se aduce la stilul Groovy-Grails utilizarea unor produse software diverse pentru autorizare si autentificare, pentru brokere de mesaje (ActiveMQ, de ex.), pentru creare de servicii Web, pentru tehnologia Ajax, pentru partea de interfatã (layouts, templates, componente UI s.a.), pentru portaluri s.a.
Un alt obiectiv al tehnologiei Grails este reducerea timpului necesar pentru dezvoltarea si testarea de noi aplicatii Web (care necesitã un numãr mare de rulãri, atât pentru eliminarea erorilor cât si pentru încercarea de variante si solutii alternative); în acest scop se foloseste un server rapid inclus în aplicatie, o bazã de date cu performante foarte bune (HSQLDB), generarea automatã de cod Groovy si GSP prin “scaffolding” si generarea de teste functionale.
Tehnologia Grails este atrãgãtoare prin timpul record în care putem crea un schelet al
aplicatiei, care apoi poate fi extins si perfectionat atât pe partea de prezentare (“View”), cât si pe partea de persistentã (“Model”). In plus, aplicatia este automat structuratã (modularizatã) într-o formã care permite dezvoltarea ulterioarã a unor module relativ independent de alte module.
Pentru o interfatã mai elaboratã, pentru o functionalitate mai complexã sau pentru aplicatii mai speciale trebuie însã stãpânite limbajul Groovy, tehnologia JSP (GSP în varianta Groovy) si alte cunostinte care pot proveni dintr-o experientã anterioarã (cu baze de date, cu tranzactii, cu securitate Web, etc.) sau trebuie dobândite din documentatiile Grails si din documentatia altor produse sau tehnologii.
Structura unei aplicatii Grails
O aplicatie Web dezvoltatã în Grails foloseste o anumitã structurã de directoare si nume de fisiere, care respectã acele conventii ce înlocuiesc configurarea si corespund schemei MVC.
Urmeazã o prezentare sumarã a acestor conventii, cu mentiunea cã multe dintre ele pot fi modificate prin atribuirea explicitã de nume, diferite de cele atribuite implicit.
O clasã domeniu Grails corespunde unei entitãti persistente din JPA (Java Persistence API). Toate clasele domeniu sunt grupate într-un director (“domain”), iar numele de fisiere trebuie sã fie aceleasi cu numele claselor groovy (un fisier contine o singurã clasã).
Fiecare clasã domeniu va avea asociatã o clasã controler, cu un nume derivat din numele clasei domeniu; de exemplu clasa domeniu “Book” (din fisierul “Book.groovy”) este asociatã implicit cu clasa controler “BookController” din fisierul “BookController.groovy”. Toate clasele controler sunt grupate în directorul “controllers”. O clasã controler contine mai multe actiuni, fiecare având un nume si un rol în cadrul aplicatiei. Actiunile corespund claselor servlet din aplicatiile Web programate în Java, iar un controler corespunde unui servlet dispecer.
Fiecare controler (si clasã domeniu) are asociat un numãr de pagini gsp ce vor fi trimise la client ca rãspuns la o cerere; aceste pagini se aflã în fisiere cu acelasi nume ca al actiunilor din controler si sunt grupate într-un (sub)director cu acelasi nume ca si clasa domeniu.
Procesul de generare automatã numit “scaffolding” genereazã pentru fiecare clasã domeniu un fisier controler cu câte opt actiuni numite “create”, “save”, “show”, “update”, ”delete”, “edit”, “list” si “index” (dirijat cãtre actiunea “list”) si câte patru pagini gsp numite “create”,”show”,”edit” si “list”. Paginile “create”, “show” si “edit” afiseazã proprietãtile unei instante a clasei domeniu, iar pagina “list” afiseazã toate instantele sub forma unui tabel.
Pagina “index.gsp” creatã automat de Grails contine o listã de legãturi cu numele claselor controler din aplicatie. Selectarea de cãtre client a numelui “BookController”, de exemplu, va declansa actiunea “list” si va folosi pagina “list.gsp” pentru afisarea continutului tabelului “book” din baza de date creatã automat pentru aplicatie (cu un nume implicit sau dat explicit într-un fisier de configurare “DataSource.groovy” (din directorul “conf”).
Directorul, subdirectoarele si câteva fisiere ale aplicatiei sunt create automat prin comanda
grails create-app [nume_aplicatie]
Cel mai important subdirector este directorul “grails-app”, care va contine fisierele scrise de cel care dezvoltã aplicatia si/sau fisiere generate prin “scaffolding”. Structura directorului “grails-app” reflectã componentele schemei MVC (Model-View-Controller) astfel:
- “grails-app/domain” contine clasele care descriu datele din domeniul aplicatiei (inclusiv relatiile dintre ele, restrictii, validãri s.a.), adicã o parte importantã a componentei “Model”
- “grails-app/views” contine pagini gsp, jsp sau html ce formeazã componenta “View” din MVC (partea de prezentare sau de interfatã cu clientii aplicatiei)
- “grails-app/controllers” contine clasele “controler” care preiau cererile clientilor, le prelucreazã si compun rãspunsuri la aceste cereri, folosind în general datele din clasele domeniu. Clasele controler comunicã atât cu clasele domeniu (folosind metode de regãsire, modificare, memorare din aceste clase), cât si cu paginile din componenta “View”.
- “grails-app/services” contine clase “service” cu prelucrãri de date si care sunt folosite de obieci de clasele controler; ele corespund unor clase “helper” din aplicatii Java si fac parte din ceea ce se numeste logica aplicatiei (“business logic”).
Un alt subdirector din “grails-app” care poate fi de interes chiar si în aplicatii simple este cel de configurare (“grails-app/conf”), cu fisiere generate automat la crearea aplicatiei, dar care pot fi modificate pentru modificarea parametrilor bazei de date (“DataSource.groovy”) sau pentru a introduce unele operatii de initializare în aplicatie (“BootStrap.groovy”).
Directorul “grails-app/views” contine pagina index a aplicatiei (“index.gsp”), subdirectoare cu paginile “gsp” generate pentru fiecare clasa domeniu (cate 4 pentru cele 4 operatii CRUD) si un subdirector “layout” cu fisierul “main.gsp” care descrie aspectul paginii index (si foloseste în acest scop fisierul “main.css” din subdirectorul “web-app/css”.
Subdirectorul “web-app” este pe acelasi nivel cu “grails-app” si grupeazã “resurse” statice folosite de aplicatie: foi de stil “css”, imagini, biblioteci de tag-uri (“tld”), biblioteci de functii Javascript (“js”) s.a.
In rezumat, orice aplicatie Grails va avea urmãtoarea structurã a directorului aplicatiei (simplificatã aici prin omiterea unor subdirectoare mai rar folosite):
bookstore
grails-app
conf
controllers
domain
services
views
layout
index.gsp
lib
test
web-app
application.properties
Fisierul “application.properties” contine versiunea de Grails sub care s-a creat aplicatia, numele aplicatiei, lista de plugin-uri instalate în aplicatie, s.a.
Fisierele din componenta unei aplicatii Grails care prelucreazã date (cereri client, date extrase din baza de date, s.a.) pot fi scrise în Groovy si/sau în Java si sunt cãutate în urmãtoarele subdirectoare (la executia aplicatiei):
- grails-app/controllers : clase controler care prelucreazã cereri client si compun rãspunsuri.
- grails-app/services : clase auxiliare folosite de clasele controler, servicii Web, s.a.
- grails-app/conf : clase filtru folosite de clasele controler
- src/groovy, src/java : clase auxiliare folosite de clasele controler sau de clase service
Comenzi uzuale Grails
Dezvoltatorul unei aplicatii Grails are la dispozitie trei moduri de lucru:
- Modul dezvoltare (“development environment”)
- Modul exploatare (“production environment”)
- Modul testare (“test environment”)
Alegerea modului de lucru se face în comanda care lanseazã în executie aplicatia:
grails run-app [mod]
implicit fiind modul “dezvoltare”.
Fiecare comandã grails lanseazã un script Groovy.
Toate comenzile trebuie date în directorul aplicatiei (dar nu si în subdirectoare).
Prima comandã folositã este comanda:
grails create-app [nume_aplicatie]
care are ca efect crearea în directorul curent a unui subdirector cu numele “nume_aplicatie” si care contine o serie de subdirectoare si fisiere create automat prin aceastã comandã. Este important de retinut cã urmãtoarele comenzi “grails” trebuie date numai în directorul creat de comanda “create-app” (dar nu si în unul din subdirectoarele sale).
Crearea unei noi clase (domeniu, controler, service, etc.) se poate face cu orice editor de texte sau se poate genera un schelet al clasei folosind o comandã de forma:
grails create-controller
Fisierul generat se va numi “HomeController.groovy” si va contine urmãtoarea clasã:
class HomeController {
def index= { }
}
“index” reprezintã numele unei proprietãti a clasei controller, care poate fi initializat cu un functor (“closure”), adicã cu una sau câteva instructiuni între acolade. In acelasi timp “index” reprezintã numele implicit al actiunii din controlerul executat.
Scheletul clasei controler va fi apoi completat cu actiunile necesare aplicatiei.
Pentru lansarea serverului incorporat în Grails si aplicatiei se foloseste comanda:
grails run-app
La fiecare lansare se recompileazã automat fisierele “groovy” astfel cã orice modificare efectuatã în aceste fisiere se reflectã în comportarea aplicatiei.
O aplicatie finalizatã si testatã poate fi împachetatã într-o arhivã de tip “war” pentru a fi instalatã (“deployed”) pe un server de “productie” (unde are loc exploatarea aplicatiei). Arhiva poate fi folositã pe orice server container de servleti sau server de aplicatii compatibil JEE, dar este destul de voluminoasã pentru cã include multe biblioteci de clase (aduse din kit-ul Grails) chiar dacã nu toate sunt folosite de aplicatie, si extensii “plugin” (Hibernate s.a.).
Pentru crearea unei arhive “war” cu fisierele aplicatiei se foloseste comanda:
grails war
Versiunile Grails se modificã încã destul de repede, iar Grails verificã concordanta dintre versiunea Grails a unei aplicatii (din fisierul “application.properties”) si versiunea de framework si nu executã o aplicatie mai veche. Pentru actualizarea versiunii unei aplicatii existã comanda:
grails upgrade
Comenzile disponibile în linie de comandã sunt în jur de 40 si pot fi aflate prin comanda:
grails help
utilizatã tot în directorul unei aplicatii Grails, ca si celelalte comenzi.
Scaffolding în Grails
O aplicatie Grails are douã pãrti mari, situate pe douã niveluri (“layers”): nivelul Web si nivelul GORM; la acestea se mai adaugã uneori nivelul “service” (servicii de prelucrare a datelor). Nivelul GORM se referã la baze de date folosite de aplicatie si operatii cu acestea.
Cele mai importante componente de pe nivelul Web sunt cele numite “controllers” si “views”; un controler se executã pe server si are ca rezultat o paginã GSP, transformatã (tot pe server) într-o paginã HTML trimisã cãtre client pentru a fi afisatã de un program browser. Pagina GSP este un “view” adicã ea compune o imagine vãzutã de client, folosind fragmente de cod HTML, marcaje specifice GSP si alte marcaje (“tags”) definite în biblioteci de marcaje.
Un controler are rolul unui servlet dispecer, în sensul cã el primeste cererile HTTP de la clienti si, în functie de continutul cererii, selecteazã una din actiunile continute de acel controler. Un controler este, pentru utilizatori, un punct de intrare în aplicatie, în sensul cã o aplicatie Grails standard prezintã în pagina initialã (“Home”) o listã de controlere din aplicatie, din care se poate alege un controler pentru solicitarea anumitor servicii oferite de aplicatie. Pagina “home” este un fel de meniu cu functiile disponibile în cadrul aplicatiei respective.
Selectarea unui controler si a unei actiuni se face prin componentele URL continute într-o cerere HTTP dupã schema urmãtoare:
http://server/appname/controller/action/id
Exemplu:
http://localhost:8080/bookstore/book/show/3
Aici cererea se adreseazã actiunii “show” din controlerul “BookController” din aplicatia “bookstore” si se referã la articolul cu Id=3 dintr-un tabel care are (probabil) numele “book”.
In lipsa componentei “action” se considerã selectatã implicit actiunea cu numele “index” (sau cu orice alt nume, dacã este singura actiune din controler). In lipsa componentei “controller” este cãutat un fisier “index.jsp” sau “index.gsp”, care contine pagina “home” din aplicatie.
Pentru actiunile ce contin operatii cu tabelul de utilizatori (adãugare, modificare, eliminare) si pentru paginile gsp asociate putem recurge la generarea automatã de fisiere de cãtre Grails prin “Scaffolding”. Generarea poate fi cerutã atunci când existã cel putin o clasã “domeniu”, deci o clasã care corespunde unui tabel al bazei de date si care se aflã în subdirectorul “domain”.
Grails poate genera fisiere controler si pagini GSP pentru cele 4 operatii CRUD (Create, Read, Update, Delete) necesare pentru folosirea acestei baze de date printr-o interfatã graficã prietenoasã. Sunt disponibile douã metode de generare cod prin “scaffolding”:
- Generare staticã, înainte de compilare, folosind comanda urmãtoare:
grails generate-all
- Generare dinamicã, în cursul compilãrii (ca efect al comenzii “grails run-app”), dacã fiecare clasã controler include o linie de forma
def scaffold=true sau def scaffold=domain-class
Exemplu:
class UserController {
def scaffold=User // def scaffold=true
}
Generarea dinamicã are avantajul cã o modificare în clasa domeniu se va reflecta si în continutul fisierelor gsp generate la fiecare nouã lansare a aplicatiei; de exemplu adãugarea unui nou câmp va avea ca efect crearea unei noi rubrici in formularul folosit la crearea sau la editarea unui utilizator.
Generarea staticã are avantajul cã se poate vedea si se poate modifica codul generat, atât pentru clasele controler cât si pentru paginile GSP.
Actiunile “show” si “edit” sunt identice, iar paginile GSP asociate lor sunt aproape la fel: ambele afiseazã valorile proprietãtilor unui singur articol (instantã a unei clase domeniu) si au un buton de stergere (“delete”), diferenta este cã “show.gsp” are un buton de editare “edit”, iar “edit.gsp” are un buton de salvare în baza de date “save”.
Urmeazã secvente dintr-un controler generat static:
class UserController {
def index = { redirect(action:list) }
def list = {
params.max = Math.min( params.max ? params.max.toInteger() : 10, 100)
[ userInstanceList: User.list( params ), userInstanceTotal: User.count() ]
}
def show = {
def userInstance = User.get( params.id )
if(!userInstance) {
flash.message = "User not found with id ${params.id}"
redirect(action:list)
}
else { return [ userInstance : userInstance ] }
}
def create = {
def userInstance = new User()
userInstance.properties = params
return ['userInstance':userInstance]
}
def save = {
def userInstance = new User(params)
if(!userInstance.hasErrors() && userInstance.save()) {
flash.message = "User ${userInstance.id} created"
redirect(action:show,id:userInstance.id)
}
else
render(view:'create', model:[userInstance:userInstance])
}
…
}
GORM: Grails ORM
GORM preia din Hibernate câmpul de identificare generat automat, câmpul de versiune pentru sincronizarea accesului concurent la date, proprietãtile de tip colectie pentru asocieri, dar înlocuieste fisierele de configurare si adnotãrile Hibernate prin proprietãti statice cu nume predefinit în clasele domeniu (hasMany, belongsTo, constraints) si înlocuieste limbajul HQL prin metode Groovy de cãutare generate dinamic (Dynamic Finders).
Incepând cu versiunea 1.1.1 Grails adaugã automat fiecãrei aplicatii un plugin pentru Hibernate.
Pentru fiecare tabel al bazei de date trebuie definitã o clasã domeniu, în subdirectorul “../grails-app/domain”. La câmpurile (proprietãtile) definite explicit Grails adaugã automat douã câmpuri:
- Un câmp de identificare (“Id”), numeric, cu valori începând de la 1, care cresc pe mãsurã ce se adaugã noi articole la tabel. Acest câmp poate fi folosit în metodele “get” si “getAll” de regãsire a unuia sau a mai multe articole dintr-un tabel dupã valoarea câmpului “id”. Utilizatorii aplicatiei folosesc câmpul “Id” pentru editarea (modificarea) unui articol existent, prin click de mouse pe numãrul articolului de modificat.
- Un câmp pentru versiunea de modificare a fiecãrui articol prin optiunea “update”; câmpul “version” este folosit pentru a semnaliza modificarea simultanã de cãtre mai multi utilizatori a unui aceluiasi articol dintr-un tabel.
Grails adaugã automat claselor domeniu metode utile în operatii cu baze de date, unele cu nume fixe iar altele cu nume care depind de numele câmpurilor clasei (coloanelor din tabel):
list(), save(), count(), delete()
get (int), findAll() , findAllById(int), findAllByTitle(String)
Asocieri 1:1 în Grails
Asocierea între douã entitãti (tabele, clase domeniu) se face prin utilizarea într-o clasã a unei variabile (proprietãti) de tipul celeilalte clase. Exemplu de asociere unidirectionalã, între un autor si o carte (un autor are o singura carte):
class Book {
String title
}
class Author {
String name
Book book
}
O asocieree Grails "one-to-one" pot fi unidirectionalã, bidirectionalã sau bidirectionalã de apartenentã ("belongsTo").
Exemplu de asociere 1:1 bidirectionalã în care un autor are o singurã carte si o carte are un singur autor, implementatã printr-o cheie strãinã (BOOK_ID) adãugatã tabelului AUTHOR.
class Book {
String title
Author author
static constraints= { author (nullable:true) }
}
class Author {
String name
Book book
static constraints= { book (nullable:true) }
}
Constrângerile sunt restrictii impuse valorilor permise pentru fiecare proprietate; de exemplu “nullable” cere ca valoarea unui câmp sã nu poatã lipsi (“false”) sau sã poatã lipsi (“true”). In exemplul anterior, ar trebui completat autorul unei noi carti sau cartea unui nou autor, prin selectare dintr-un “combobox” (“select” in HTML); la prima carte sau la primul autor nu avem ce selecta pentru cã nu existã încã autori (cãrti). De aceea a fost adãugatã restrictia “nullable:true”, pentru a permite o carte fãrã autor si un autor fãrã carte.
Controlere generate prin scaffolding nu permit eliminarea unui autor sau eliminarea unei cãrti, în absenta clauzei “belongsTo”.
Utilizarea proprietãtii statice "belongsTo" în clasa "Book" înseamnã cã fiecare carte apartine unui autor. Ca urmare, se poate adãuga unui autor o carte dar nu se poate adãuga unei cãrti un autor. Altã consecintã este cã stergerea unui autor are ca efect (în cascadã) stergerea cãrtii acelui autor, iar stergerea unei cãrti nu este posibilã.
In exemplul anterior proprietatea “belongsTo” se poate adãuga ambelor clase domeniu sau numai uneia dintre ele. Exemplu de carte care se sterge autoamt la eliminarea autorului sãu:
class Book {
String title
Author author
static belongsTo = Author
static constraints= { author (nullable:true) }
}
Asocieri 1:n în Grails
O asociere 1:n foloseste proprietatea staticã "hasMany" în clasa care "detine" asocierea (de exemplu, în clasa "Author"). Grails genereazã automat o proprietate de tip “Set” (multime) cu numele cheii din asocierea “hasMany”. Exemplu de asociere 1:n unidirectionalã:
class Author {
String name
static hasMany=[books:Book]
}
class Book {
String title
}
Variabila “books” de tip “Set” este adãugatã automat clasei “Author” si reprezintã multimea cãrtilor unui anumit autor. Exemplu de afisare a cartilor unui autor cu nume cunoscut:
def a = Author.findByName(“A1”);
a.books.each { println it.title }
Variabila "books" poate fi declaratã explicit ca listã sau ca multime ordonatã. Exemplu:
class Author {
String name
TreeSet books
static hasMany=[books:Book]
}
O asociere 1:n unidirectionalã este implementatã printr-un tabel suplimentar (de tip "join") având drept coloane câmpul "Id" din cele douã tabele legate. (tabelul AUTHOR_BOOK va contine coloanele AUTHOR_ID si BOOK_ID).
Asocierea 1:n bidirectionalã se realizeazã prin adãugarea proprietãtii "belongsTo" în clasa detinutã ("Book" în acest exemplu) si nu prin adãugarea unui câmp “Author” în clasa “Book”. Exemplu :
// bidirectional one-to-many, with belongsTo
class Author {
String name
static hasMany=[books:Book]
}
class Book{
String title
static belongsTo= [author:Author]
}
O asociere 1:n bidirectionalã este implementatã printr-o coloanã suplimentarã cu o cheie "strãinã" (coloana AUTHOR_ID în tabelul BOOK)
O legãtura dintre un autor si o carte se face numai prin editarea unui autor, nu si prin editarea unei cãrti. Altfel spus, se poate adãuga o carte unui autor dar nu se poate adauga un autor unei cãrti. Tabelul de cãrti este de fapt un tabel de legãturi autori-cãrti.
Paginile gsp generate prin "scaffolding" nu afiseazã cãrtile unui autor în lista de autori, dar prin editarea unui autor pot fi vãzute cãrtile sale.
In varianta "belongsTo" stergerea unui autor produce si stergerea cãrtilor care-i apartin (stergere în cascadã). Se poate sterge o carte .
Aceastã proprietate modificã si paginile gsp folosite în operatiile de creare a unei noi carti, de afisare si de editare a unei carti (..views/book dar nu si paginile gsp din subdirectorul ..views/author). La crearea unei noi cãrti putem selecta autorul (introdus anterior) dintr-un "combobox" din pagina "create.gsp".
Asocieri m:n în Grails
Pentru o asociere "many-to-many" între cãrti si autori (o carte poate avea mai multi autori si un autor poate avea mai multe cãrti) se va adãuga clasei Book linia urmãtoare:
static hasMany = [authors:Author]
Asocierea între cãrti si autori se realizeazã printr-un tabel de "join" creat automat. Intr-o asociere “many-to-many” nu poate lipsi proprietatea “belongsTo”, pentru ca sã se poatã sterge automat asocierile dintre cãrti si autori, din tabela de join (care nu este direct accesibilã din controlerele generate prin “scaffolding”).
Clasele domeniu vor arãta astfel:
class Book {
static belongsTo = Author // nu poate lipsi !
static hasMany = [authors:Author]
String title
}
class Author {
static hasMany = [books:Book]
String name
}
Tabelul generat (AUTHOR_BOOK) are drept cheie primarã combinatia (AUTHOR_ID, BOOK_ID) si coloanele AUTHOR_ID si BOOK_ID.
Clauza "belongsTo" trebuie adaugatã clasei care apartine altei clase (aici clasa Book). Nu este posibilã adãugarea proprietatii "belongsTo" si clasei care detine asocierea (aici clasa "Author"). Diferenta dintre clasa care detine asocierea (“Author”) si clasa care apartine altei clase (“Book”) se vede la operatiile de modificare (“update”) si de stergere.
Adãugarea unei legãturi autor-carte se poate face numai prin editarea unui autor, nu si prin editarea unei cãrti (dar la editarea unei carti se vãd autorii ei). Modificarea cartilor unui autor se poate face numai prin editarea articolului acelui autor (clasa care detine relatia).
La crearea unui nou autor nu se pot introduce si nume de cãrti, dar se poate edita acel autor imediat dupã creare. Deci crearea unui autor trebuie urmatã de editarea sa pentru stabilirea de asocieri.
Eliminarea unui autor are ca efect eliminarea legãturilor de la acel autor la cãrtile sale (în tabelul de join), dar nu si eliminarea acelor cãrti din tabelul de cãrti.
O carte nu poate fi eliminatã din tabelul de cãrti dacã ea apartine cel putin unui autor.
Pagina show.gsp generatã automat nu permite afisarea cartilor unui autor sau afisarea autorilor unei carti în lista de autori sau de cãrti; dar aceste informatii apar la editarea unui autor sau unei cãrti.
Modificarea claselor controler generate automat
Grails genereazã automat numai clase controller si pagini GSP pentru cele 4 operatii CRUD (create, read=list, update=edit,delete) cu fiecare tabel în parte din baza de date.
Adãugarea de cod claselor controler generate poate fi necesarã fie pentru adãugarea de noi functii aplicatiei, fie pentru îmbunãtãtirea performantelor aplicatiei.
Ca exemplu de interogare solicitatã de utilizatori in aplicatia cu carti si autori este aflarea tuturor autorilor unei cãrti.
Se pot adãuga metode suplimentare claselor domeniu (fatã de metodele get/set generate automat); de exemplu se poate adãuga o metodã care produce o colectie (o listã) cu toate cãrtile unui autor, o metodã care adaugã o nouã carte unui anumit autor sau o metodã care eliminã o carte din cele care apartin unui autor. Exemplu:
class Author {
static hasMany = [books:Book]
String name
List getBooks () {return books.collect {it.book} }
}
Pentru alte facilitãti decât cele obtinute prin "scaffolding" si pentru a oferi utilizatorilor posibilitatea unor interogãri care necesitã cãutarea în mai multe tabele legate prin asocieri 1:n sau m:n trebuie adãugate si/sau modificate clase controler si pagini GSP pentru aplicatia respectivã.
Inainte de a face astfel de modificãri se poate folosi consola Groovy pentru a testa secventele Groovy care realizeazã noile facilitati si variantele posibile de implementare.
De observat cã anumite metode Groovy au unele particularitãti de utilizare în Grails:
- metoda deleteAll() nu este permisã pentru eliminarea tuturor liniilor unui tabel si tebuie scris un ciclu cu metoda "delete" în acest scop.
- efectul unor metode care necesitã scriere pe disc (save, delete, s.a.) nu este imediat, pentru optimizarea performantelor; dacã se doreste un efect imediat atunci se foloseste în aceste metode argumentul flush:true. Exemplu:
def b=Book.findByTitle("B2")
b.delete (flush:true)
- iterarea pe o relatie (cum este "books" din "Author") poate sã genereze mai multe citiri de pe disc ("lazy fetching", implicit) sau sã citeascã toatã relatia în memorie ("eager fetching").
O variantã la generarea automatã a tabelului de asociere dintre cãrti si autori este crearea explicitã a unei astfel de tabel, prin definirea unei clase domeniu cu legãturi spre tabelele asociate. Fie urmãtoarele clase domeniu :
class Book {
static hasMany = [ab: Link] // o carte poate avea mai multi autori
String title
}
class Author {
static hasMany = [ab: Link] // un autor poate avea mai multe carti
String name
}
class Link { // legaturi autori-carti
Author author
Book book
}
De observat cã tabelul Link va contine 3 coloane: ID, AUTHOR_ID, BOOK_ID si nu contine toate coloanele din tabelele AUTHOR si BOOK (dacã e nevoie ca un tabel Link sã continã toate coloanele altui tabel Author atunci se adaugã proprietatea static embedded = ['author'] în clasa Link ).
Coloanele AUTHOR_ID si BOOK_ID sunt considerate chei strãine, pentru cã ele corespund unor chei primare din alte tabele (colona ID). Cheia primara este Id-ul clasei Link.
Secventa standard de adãugare a unei cãrti la un autor este de forma urmãtoare:
def book = new Book(title:"Grails Book")
def author = Author.get(1)
author.addToBooks(book)
author.save()
O solutie preferabilã poate fi modificarea procedurii de adãugare a unei noi cãrti la un autor folosind asocierea inversã, de la carte la autor:
def book = new Book(title:"New Grails Book")
def author = Author.get(1)
book.author = author
book.save()
Limbajul specific domeniului GORM (GORM DSL)
La definirea unei entitãti se pot specifica diferite asocieri printr-un bloc static “mapping”. In exemplul urmãtor se atribuie unui tabel un nume diferit de cel al entitãtii asociate si se atribuie unei coloane un nume diferit de numele câmpului asociat:
class Person {
String addr1
static mapping = {
table 'people'
addr1 column: ‘Main_Address’
}
}
Astfel de modificãri fatã de conventiile implicite pot fi necesare atunci când se foloseste o bazã de date mostenitã de la aplicatii anterioare (“legacy database”).
Alte facilitãti permise de blocul “mapping” sunt:
- eliminare numãr de versiune adãugat automat fiecãrui tabel (version false)
- alt algoritm de generare identificatori articole (Id)
- specificarea unui index asociat unei coloane (index:’Name_Idx’)
- criteriul de sortare (sort “name”)
- definirea de chei compuse:
class Person {
String firstName
String lastName
static mapping = {
id composite: ['firstName', 'lastName']
}
}
Constrângerile impuse câmpurilor tabelelor (“constraints”) apar în clasele domeniu si se folosesc atât pentru specificarea dimensiunii acestor campuri, cât si pentru validarea datelor introduse de utilizatori. Pe baza claselor domeniu se genereazã schema bazei de date.
Implicit coloanele tabelelor sunt de tip “varchar” si au o dimensiune de 256 octeti, care poate fi modificatã prin cuvintele “size” (dimensiune exactã) sau “maxSize” (dimensiunea maximã) sau “inList” (dimensiune stabilitã automat în functie de cel mai lung sir din acea coloanã).
Pentru câmpuri numerice se pot folosi cuvintele “min”, “max”, “range” si “scale”. Exemplu:
class Book {
String author // nume autor
Integer year // anul aparitiei
static constraints= {
author (maxSize:50, blank:false)
year (min:1800, max:2010, nullable:false)
}
}
Validarea valorilor introduse se face fie automat, fie explicit prin apelarea metodei
boolean validate()
pentru o instantã a clasei domeniu. Metoda “hasErrors” poate fi apelatã pentru o instantã pentru a verifica dacã au fost erori la validare sau la salvare în tabel.
Exemplu de validare explicitã :
def b = new Book (year: 209)
b.validate()
if (b.hasErrors())
b.errors.each { println it }
Forme de interogare a bazelor de date în Grails
Un controler generat automat selecteazã un articol prin metoda “get” cu parametru întreg (valoare “Id”), ca urmare a unui click pe campul “Id” efectuat de utilizator. Formularul afisat în “list.gsp” foloseste marcajul pentru asocierea unei actiuni cu selectarea câmpului “Id” afisat în prima coloanã din tabelul prezentat utilizatorului:
| Exemple de metode corecte pentru o clasã “Book” cu câmpurile “title”,”author”,”year” :
R.Fischer: Grails Persistence with GORM and GSQL. Apress.2009
J.Bashar : Groovy and Grails Receipes. Apress. 2009
C. Judd, J.Nusairat, J. Shingler : Beginning Groovy and Grails. Apress. 2009
G.Smith, P.Ledbrook: Grails in Action. Manning. 2009
D. Klein: Grails – A Quick-Start Guide. Pragmatic Programmers. 2009