ABONAMENTE VIDEO REDACȚIA
RO
EN
NOU
Numărul 150
Numărul 149 Numărul 148 Numărul 147 Numărul 146 Numărul 145 Numărul 144 Numărul 143 Numărul 142 Numărul 141 Numărul 140 Numărul 139 Numărul 138 Numărul 137 Numărul 136 Numărul 135 Numărul 134 Numărul 133 Numărul 132 Numărul 131 Numărul 130 Numărul 129 Numărul 128 Numărul 127 Numărul 126 Numărul 125 Numărul 124 Numărul 123 Numărul 122 Numărul 121 Numărul 120 Numărul 119 Numărul 118 Numărul 117 Numărul 116 Numărul 115 Numărul 114 Numărul 113 Numărul 112 Numărul 111 Numărul 110 Numărul 109 Numărul 108 Numărul 107 Numărul 106 Numărul 105 Numărul 104 Numărul 103 Numărul 102 Numărul 101 Numărul 100 Numărul 99 Numărul 98 Numărul 97 Numărul 96 Numărul 95 Numărul 94 Numărul 93 Numărul 92 Numărul 91 Numărul 90 Numărul 89 Numărul 88 Numărul 87 Numărul 86 Numărul 85 Numărul 84 Numărul 83 Numărul 82 Numărul 81 Numărul 80 Numărul 79 Numărul 78 Numărul 77 Numărul 76 Numărul 75 Numărul 74 Numărul 73 Numărul 72 Numărul 71 Numărul 70 Numărul 69 Numărul 68 Numărul 67 Numărul 66 Numărul 65 Numărul 64 Numărul 63 Numărul 62 Numărul 61 Numărul 60 Numărul 59 Numărul 58 Numărul 57 Numărul 56 Numărul 55 Numărul 54 Numărul 53 Numărul 52 Numărul 51 Numărul 50 Numărul 49 Numărul 48 Numărul 47 Numărul 46 Numărul 45 Numărul 44 Numărul 43 Numărul 42 Numărul 41 Numărul 40 Numărul 39 Numărul 38 Numărul 37 Numărul 36 Numărul 35 Numărul 34 Numărul 33 Numărul 32 Numărul 31 Numărul 30 Numărul 29 Numărul 28 Numărul 27 Numărul 26 Numărul 25 Numărul 24 Numărul 23 Numărul 22 Numărul 21 Numărul 20 Numărul 19 Numărul 18 Numărul 17 Numărul 16 Numărul 15 Numărul 14 Numărul 13 Numărul 12 Numărul 11 Numărul 10 Numărul 9 Numărul 8 Numărul 7 Numărul 6 Numărul 5 Numărul 4 Numărul 3 Numărul 2 Numărul 1
×
▼ LISTĂ EDIȚII ▼
Numărul 9
Abonament PDF

Introducere în Grails (III)

Tavi Bolog
Development Lead at Nokia



DIVERSE

Sper că articolele precedente despre Grails v-au trezit interesul asupra framework-ului. Acesta este ultimul articol din seria "Introducere în Grails", prezentând următoarele topicuri:

  • Validatori custom
  • Internaționalizarea
  • Librării de tag-uri
  • Mapări de url-uri
  • Persistență

Validatori custom

În articolele precendente am discutat despre constrângeri care se aplică obiectelor domeniu. Grails oferă un set bogat de constrângeri: nullable, blank, size, etc. În cazul în care dorim validări adiționale asupra obiectelor noastre de domeniu, va trebui să ne scriem validatorii noștri.

Să încercăm să adăugam un validator custom care să nu permită folosirea cuvântului "test" ca parte a unui mesaj pe care vrem să îl postăm. În primul rând va trebui să specificăm folosirea unui validator custom:

static constraints = {
  message size:5..100, blank:false, validator:
  {val, obj -> obj.messageValidator(val)}	
}	

Apoi va trebui să definim metoda de validare în clasa Message:

private messageValidator(val) {
 if (val.toLowerCase().contains("test")) {
   return "test.is.not.allowed"
 }
 return true
}

Validatorul primește ca parametru valoarea de validat (în cazul nostru textul mesajului de postat). Dacă textul este ok relativ la regulile noastre, metoda de validare va returna "true". Orice altă expresie returnată va fi considerată ca validare eșuată la apelarea metodei "validate" pentru obiectul nostru Message și aplicația va afișa un mesaj de eroare. Vom vedea în secțiunea următoare cum să customizăm mesajele aplicației noastre, adăugând suport pentru internationalizare.

Internaționalizarea

Majoritatea aplicațiilor web sunt destinate utilizatorilor care vorbesc limbi diferite. De asemenea, mesajele default pe care Grails le pune la dispoziție pentru a trata erori, nu sunt întotdeauna user-friendly (vezi mai sus). Suportul de internaționalizare al Grails se bazează pe Spring MVC. Setările de limbă sunt implementate folosind obiectul Locale, asociat cu sesiunea unui user.

Grails stochează pachetele de mesaje în folderul grails-app/i18n ca fișiere de proprietăți Java. Convenția este că pachetele de mesaje încep cu "messages" și se termină cu numele locale-ului. Câteva exemple:

  • messages.properties
  • messages_en.properties
  • messages_pt_BR.properties
  • messages_ro.properties

Pentru aplicația noastră vom folosi doar două fișiere:

  • messages.properties,
  • messages_ro.properties.

Să luam exemplul de mai sus, unde mesajul de eroare pus la dispoziție de Grails nu a fost foarte user friendly și să adăugam textul nostru de eroare în fișierele de proprietăți.

Deschide https://github.com/tavibolog/GrailsSocialNetwork/tree/master/grails-app/i18n/messages.properties și adaugă:

message.name=My message
test.is.not.allowed=Test is not allowed!

Acum încearcă să postezi un mesaj care conține cuvântul "test". Vom observa două lucruri:

test.is.not.allowed=Test nu este admis!
message.name=Mesajul meu

După cum ați observat am mai adăugat o pereche cheie-valoare pentru un nou text și am dori să o folosim în fișierele gsp. Aceasta este foarte simplu în Grails. Deschide https://github.com/tavibolog/GrailsSocialNetwork/blob/master/grails-app/views/message/create.gsp și caută stringul "Message" și înlocuiește-l cu: . Acest tag va cere Grails să caute în fișierele de internaționalizare după o cheie numită "message.name" și să o folosească în funcție de setarea de limbă curentă.

Aplicația noastră nu are un selector de limbă, dar Grails ne ajută să testăm schimbarea setării de limbă, expunând un parametru de request, numit "lang" care poate fi folosit pentru selectarea limbii în aplicația noastră. Dacă dorim să schimbăm limba aplicației în română, va trebui să facem un request folosind "lang=ro", ca și mai jos:

http://localhost:8080/GrailsSocialNetwork/message/create?lang=ro

Pagina de creare a unui mesaj va arăta semi-internaționalizat (iar mesajul de eroare venit de la validatorul custom va fi afișat de asemenea în română):

Câteodată avem nevoie de suport de internaționalizare la nivelul controllerului.

Să încercăm să adăugăm suport de internaționalizare pentru mesajul de eroare afișat în cazul în care combinația user-parolă nu este validă.

Implementarea curentă (https://github.com/tavibolog/GrailsSocialNetwork/blob/master/grails-app/controllers/com/todaysoftmag/gsn/UserController.groovy ) este mai jos:

render(view: "login", model: [message: "Wrong username or password!"]) 

Localizarea este făcută folosindu-se tagul "message"; tot ce trebuie să facem este să apelăm tagul cu parametri necesari. Iată codul schimbat:

render(view: "login", model: [message: message(code:"error.wrong", args:[message(code: "error.usernameorpassword")])])

Deși pare puțin complicat, s-a vrut prezentarea suport de parametri în conținutul internaționalizat folosind Grails. În cazul nostru, vrem să afișăm un text definit de cheia "error.wrong", care are la rândul ei parametri, definiți în lista de argumente "args" - aceștia putând fi internaționalizați. În ambele cazuri, trebuie doar să apelăm tagul "message" cu parametri necesari: "code" - specifică textul de afișat și "args" - specifică eventuali parametri de înlocuit în textul internaționalizat.

Următorul pas este să definim noile string-uri în messages.properties:

error.wrong=Wrong {0}
error.usernameorpassword=username or passwordd

și messages_ro.properties:

error.wrong={0} sunt gresite
error.usernameorpassword=username sau parola

Acum, dacă încercăm să ne logăm cu o combinație invalidă de user și parolă, specificând o setare de limbă, aplicația va afișa un mesaj de eroare localizat.

Librării de tag-uri

Librăriile de tag-uri sunt o modalitate elegantă de a construi componente re-utilizabile în fișierele gsp. Librăriile de tag-uri sunt stocate în /grails-app/taglib. Librăriile de tag-uri sunt definite ca și clase Groovy și pot avea mai multe metode. Ele pot să aibă și namespace-uri pentru a nu intra în coliziune cu tag-urile standard Grails sau tag-uri definite de alți developeri.

Să încercăm să construim o librărie de tag-uri care va fi folosită să afișeze un mesaj de întâmpinare pentru userii aplicației noastre. Momentan acesta este implementată în list.gsp (https://github.com/tavibolog/GrailsSocialNetwork/blob/master/grails-app/views/message/list.gsp ):


Hi ${session.user}

Abordarea acesta are mai multe probleme:

  • Funcționează doar în acest view;
  • Nu afișează un mesaj dacă userul nu este logat în aplicație;
  • Este mai complicat de menținut pentru că va trebui să modificăm fișierul gsp de câte ori vrem să schimbăm implementarea și eventual va trebui replicat și în alte fișiere.

Soluția este să folosim o librărie de tag-uri. Pentru a crea o librărie de taguri, rulăm următoarea comandă:

create-tag-lib com.todaysoftmag.gsn.Display

Aceasta va crea o librărie de taguri numită DisplayTagLib și fișierul de test asociat: DisplayTagLibTests.

package com.todaysoftmag.gsn
 
class DisplayTagLib {
static namespace = "display"
	
def greetings = {attrs ->
  if (session && session.user) {
    out << "
Hello, " + session.user + "!
" } else { out << g.link(controller: "user", action: "login"){"not signed in"} } } }

Să observăm acum implementarea și să o explicăm:

  • Numele pachetului este la fel ca și pentru orice clasă Java/Groovy;
  • "namespace" - este definit pentru a proteja librăria noastră de coliziuni cu alte librării standard Grails sau definite de alți developer sau pentru a plasa metodele librăriei în contextul potrivit. Pentru a folosi metoda definită în librărie, trebuie doar să o apelăm: ;
  • "greetings" este numele metodei pe care librăria noastră îl expune, definit ca și closure;
  • "attrs" - este un hash care conține parametri ce sunt pasați tagului de către codul client. În cazul în care apelăm tagul ca și: , în metoda "greetings" o sa avem access la attrs["greetingName"];
  • Metodelei librăriei de taguri au acces la sesiunea userului;
  • "out" - este streamul în care se scrie rezultatul metodei;
  • Logica tag libului este: verificăm dacă există o sesiune și un obiect "user" pe sesiune. Dacă da, atunci afișăm un salut pentru utilizator. Dacă nu, afișăm un link în care cerem utilizatorului să se logheze. Ca exercițiu, puteți adăuga suport de internaționalizare pentru acest link.

Acum că am definit librăria de tag-uri, să o și folosim în locul potrivit: main.gsp (https://github.com/tavibolog/GrailsSocialNetwork/blob/master/grails-app/views/layouts/main.gsp ).

Aceasta este scheletul care include toate paginile aplicației, deci pare locul potrivit să afișăm mesajul de întâmpinare:



Mapări de Urluri

Grails oferă un suport ușor de folosit pentru a manipula url-urile aplicației. "Locul de joacă" este UrlMappings.groovy, stocat în grails-app/conf (https://github.com/tavibolog/GrailsSocialNetwork/blob/master/grails-app/conf/UrlMappings.groovy ).

Până acum, aplicația noastră folosea ca și pagină de start pagina default pusă la dispoziție de Grails: http://localhost:8080/GrailsSocialNetwork . Acum, am dori să schimbăm asta și să afișam pagina de login. Mai mult, am dori să afișăm un nume mai simplu pentru url-ul de login, care este definit default ca și "/user/login". Am prefera ceva mai simplu, cum ar fi "/login".

Pentru a realiza acest lucru avem nevoie de câteva schimbări în fișierul de mapări de url-uri:

"/login" (controller: "user", action: "login")
 
"/"(view:"/user/login")
Prima modificare este pentru a avea un url de login mai simplu ("/login" în loc de "/user/login"). A doua modificare este pentru a cere Grails să afișeze view-ul de login de fiecare data când cineva accesează: http://localhost:8080/GrailsSocialNetwork - e.g. rădăcina aplicației.

Datorită acestor modificări, aplicația noastră arată mai bine când este accesată pentru prima dată:

Persistența

Până acum, aplicația noastră a folosit un mecanism de persistență în memorie (la fiecare restart datele se pierdeau). În această secțiune aș dori să descriu cum să adăugăm aplicației un data source persistent, folosim GORM. Configurarea data source-urilor se face în DataSource.groovy (https://github.com/tavibolog/GrailsSocialNetwork/blob/master/grails-app/conf/DataSource.groovy ).

Primul pas este să definim câteva configurări ale data source-ului, cum ar fi pooling, driverul folosit pentru conectare, username și parolă:

dataSource { pooled = true driverClassName = "com.mysql.jdbc.Driver" username = "tavi" password = "tavi" }

Toate aceste configurări pot fi suprascrise pentru fiecare mediu de dezvoltare. Să luam ca și exemplu, mediul de "development", cel pe care îl folosim când implementăm aplicația:

development {
    dataSource {
      dbCreate = "create-drop" 
       // one of "create"""create-drop",  
       //"update", ""validate", """
      url = "jdbc:mysql://localhost:3306/gsn"
        }
    }

Aici definim url-ul data source-ului, în cazul nostru un MySQL server, rulând o bază de date numită "gsn". De asemenea, când aplicația pornește, va șterge tabelele existente și va crea un nou set după cum este specificat de către clasele de domeniu: User și Message. Aceasta este foarte la îndemână de folosit pentru dezvoltare, dar de evitat într-un sistem de producție.

O ultimă configurare ar fi să permitem folosirea dependenței MySQL în proiectul nostru. În BuildConfig.groovy (https://github.com/tavibolog/GrailsSocialNetwork/blob/master/grails-app/conf/BuildConfig.groovy ), adăugăm conectorul MySQL ca și dependență dinamică:

dependencies {     
    runtime "mysql:mysql-connector-java:5.1.16"
}

Asumând că aveți o bază de date MySQL disponibilă la acest url: jdbc:mysql://localhost:3306/gsn și o combinație user/parolă să o accesați, să restartăm aplicația.

Hmm, Grails nu pornește din cauza unei erori:

| Error 2013-02-24 21:37:48,821 [pool-7-thread-1] ERROR hbm2ddl.SchemaExport  - Unsuccessful:
create table user (id varchar(255) not null auto_increment, version bigint not null, first_name varchar(10) not null, last_name varchar(10) not null, password varchar(10) not null, user_name varchar(10) not null unique, primary key (id))
| Error 2013-02-24 21:37:48,823 [pool-7-thread-1] ERROR hbm2ddl.SchemaExport  - Incorrect column specifier for column "id"

Din fericire, aceasta este ușor de fixat. Eroarea a apărut pentru că atributul "id" al clasei User este de tip String, dar GORM încearcă să îl seteze ca și AUTO_INCREMENT, însă doar tipurile numerice pot fi auto-incrementate în MySQL. Deci, să schimbăm tipul atributului nostru de la "String" la "long" și să lăsăm GORM și MySQL să se ocupe de generarea id-urilor noastre:

class User {
	long id // prior it  was String id = UUID.randomUUID().toString()
	String firstName
	String lastName
	String userName
	String password
…
}

Acum, aplicația va porni fără probleme. Dacă aruncăm o privire la baza de date "gsn", observăm că aceasta conține două tabele: "user" și "message"; GORM (via Hibernate) a creat tabele folosind atributele claselor domeniu și relațiile dintre ele. Datorită rutinei de ințializare din BootStrap.groovy, tabelele aplicației noastre au deja conținut pre-încărcat și sunt gata de folosit. De asemenea conținutul salvat acum poate supraviețui restartării unei aplicații.

Concluzie

În acest moment, am reușit să construim o aplicație folosind funcționalitătile de bază ale Grails și Groovy. Bine-înțeles, aplicația nu este pregătită de rulat în producție, dar este folositoare la învățarea unei nou framework de development.

Grails ajută developerii să devină productivi după câteva săptămâni de development la nivelul la care pot să construiască o aplicație web cu conținut dinamic. Bine-înțeles, universal Grails și Groovy este foarte vast și necesită mai mult timp pentru a-l stăpâni.

Sper că aceste trei articole v-au trezit interesul pentru a încerca Grails și Groovy în activitățile de web development. Acest framework a meritat investiția pentru echipa mea.

Bibliografie

http://grails.org/documentation

http://www.springsource.org/downloads/sts-ggts

NUMĂRUL 149 - Development with AI

Sponsori

  • Accenture
  • BT Code Crafters
  • Accesa
  • Bosch
  • Betfair
  • MHP
  • BoatyardX
  • .msg systems
  • P3 group
  • Ing Hubs
  • Cognizant Softvision
  • Colors in projects