Grails Cat

Creació d'un projecte Grails

Em basaré en aquest l'article que hi ha la web d'Oracle: http://www.oracle.com/technology/pub/articles/dev2arch/2006/10/introduction-groovy-grails.html

Instal·lació de Grails

pendent

Creació del projecte

Comencem per la creació de l'estructura de directoris del projecte. Per tal que Grails sàpiga on es troben les coses caldrà que el nostre projecte segueixi les convencions de Grails, justament gràcies a aquests frameworks basats en "convenció abans que configuració" ens estalviarem moltes hores de feina avorrida.

Per fer-ho ens trobem amb una sèrie de guions a la rails que ens permetran generar de formà automàtica l'estructura de carpetes que estàndar de Grails. Caldrà que ens situem al directori que contindrà el projecte Grails per exemple '/home/tramuntanal/prog/projectes' i que des d'allí fem:

grails create-app gestorApats

on 'gestorApats és el nom del projecte. Sí, farem una aplicació per a la gestió d'apats. Després d'executar el 'create-app' Grails generarà tota l'estructura de directoris, amb els guions i les llibreries necessàries per a iniciar un projecte Grails. El que fa aquesta comanda ho podem veure en el fitxer $GRAILS_HOME/scripts/CreateApp.groovy en el dsl Gant.

Estructua de directoris estàndard de Grails

Vegem l'estructura en qüestió:

grails-fig1.gif

Persistència (base de dades)

Per defecte, amb Grails, hi trobem HSQL una de les bases de dades que permet efectuar la 'persistència' en memòria. És útil per a desenvolupar i per a fer tests, però en la seva configuració 'en memòria' es perdren totes les dades quan es tanca l'aplicació.

Si volem utilitzar una altra base de dades només hem de copiar el corresponent controlador (driver) a la carpeta 'gestorApats/lib' i editar el fitxer 'gestorApats/grails-app/conf/ApplicationDataSource.groovy per a configurar-hi els paràmetres de connexió.

Classes del domini

En Grails les classes de domini, que es troben en el directori '/grails-app/domain', corresponen a entitats que es mapegen a la base de dades. Aquestes entitats es poden relacionar mitjançant associacions i, per defecte, Grails les dota dels mètodes per a fer-ne CRUD (Create-Read-Update-Delete).

Comencem doncs creant una Recepta amb els corresponents Ingredinents. Per fer-ho disposem d'una altra comanda que automàticament ens generarà les classes que l'hi indiquem. Aquesta vegada però, haurem de fer la crida des de l'arrel del nostre projecte($PROJECTES/gestorApats). Pel nostre exemple passarem els noms de cada entitat com a paràmetre:

grails create-domain-class Recepta
grails create-domain-class Ingredient
grails create-domain-class Aliment

amb aquestes crides Grails ens generarà dins de 'gestorApats/grails-app/domain' els fitxers Recepta.groovy, Ingredient.groovy i Aliment.groovy, i dins de 'gestorApats/test/integration' els corresponents testos, encara que buits per tal que els implementem segons calgui.

Pel que fa a les classes de domini veiem que aquestes no hereden de cap classe ni implementen cap interfície del framework Grails. Tot i això, quan s'instancien les classes de domini Grails els hi afegeix tres mètodes i dues propietats: id i version (totes dues del tipus Long). Són les propietats que Hibernate utilitza per identificar l'entitat i controlar-ne l'estat. Les operacions que s'afegeixen a les classes de domini són les operacions 'toString', 'equals' i 'hashCode' que utilitzen l''id' de la classe per tal de definir-ne la igualtat. Aquest tipus de mètodes, injectats en temps d'execució, s'anomenen Mètodes Dinàmics. Els Mètodes Dinàmics es basen en el Meta Object Protocol de Groovy.

class Recepta
{
        String nom
        static hasMany= [ ingredients: Ingredient ]
        Set ingredients= new HashSet()
}

En el disseny del nostre Gestor d'àpats una Recepta pot contenir varis Ingredients en diferents quantitats, alhora cada Ingredient correspon a un Aliment. La relació entre una Recepta i els seus Ingredients queda definida en la declaració 'hasMany' gràcies a la qual Grails afegirà, en el moment de crear cada instància de Recepta una propietat 'ingredients' del tipus 'java.util.Set'.

Amb el 'hasMany' aconseguim que al desar/actualitzar una Recepta Grails faci els desats i les actualitzacions dels seus Ingredients. Però perquè tamé es facin els esborrats en cascada caldrà que indiquem que la classe Ingredient pertany a una Recepta amb la declaració 'belongsTo'. Vegem-ho:

class Ingredient
{
    static belongsTo= [recepta: Recepta]
    Aliment aliment
    Double quantitat
    String unitats
}

També en el codi d'Ingredient veiem la relació d'un a un que té amb Aliment, només declarant una propietat del tipus desitjat GORM ja és capaç d'establir aquesta relació.

Tot i que la llibreria GORM (Grails Object Relational Mapping) de Grails utilitza Hibernate 3 internament no cal que ens preocupem de configurar cap fitxer de persistència amb el mapeig de les propietats dels objectes.

Controladors

També podem generar els controladors i les vistes automàticament amb Grails, per fer-ho utilitzem la comanda 'generate-all'.

Aquesta comanda crea un andami (scaffolding) que conté les operacions i vistes per a fer el CRUD d'una classe del domini.

grails generate-all Recepta
grails generate-all Ingredient
grails generate-all Aliment

Aquests controladors que s'han generat són els que gestionaran les peticions http que arribin a l'aplicació web i que vagin destinades a les entitats Recepta i Ingredient. Si obrim algun d'aquests fitxers amb un editor veurem que totes les operacions pel CRUD ja hi són implementades. Fixem-nos en que les operacions en sí no són mètodes si no que són funcions (concretament 'closures') assignades a atributs, és a dir, les propietats del controlador són punters a mètodes.

Aquests tancaments (closures) no són més que blocs de codi que es poden assignar a variables. Així des d'un punt del codi podem executar blocs creats en un altre punt només tenint una referència al bloc en qüestió. Doncs no cal cap referència a l'objecte que conté el bloc.

Cada closure del controlador s'utilitzarà per atendre les peticions per a la URI corresponent al seu nom. Per exemple una petició a '/recepta/index' serà atesa per ReceptaController.index, '/recepta/show' serà atesa per ReceptaController.show, i així amb les altres operacions del controlador.

Vistes

Una vegada el controlador hagi processat una petició serà la vista associada a l'operació que s'ha executat la que s'utilitzarà per generar la resposta.

Les vistes es troben ordenades a 'grails-app/views/' dins la carpeta amb el mateix nom que el controlador al que corresponen. Les vistes són els fitxers .gsp (un per cada mètode). Aquests fitxers .gsp són l'equivalent en Grails a les pàgines .jsp, és a dir que són plantilles HTML que contenen codi groovy incrustat, aquest codi groovy s'executarà mentre es processa la pàgina per a generar la resposta.

Fins aquí ja tenim una primera versió funcional de l'aplicació de gestió d'apats.

Vegem el resultat

Fent

grails run-app

ja en tenim prou per accedir a GetorApats en el navegador. En realitat el que fa Grails és aixecar un servidor Resin i publicar a través seu la nostra aplicació a la URL indicada a l'última línia de les traces de log.
welcome.tiff

En aquesta vista inicial Grails ens llista el conjunt de controladors que hi ha a l'aplicació. Tot i que no tots haurien de ser accessibles des de l'inici i caldrà modificar aquesta pàgina per transformar-la en un menú, ara ja podem accedir directament a les operacions disponibles a l'aplicació.

Com podeu veure en aquest moment ja disposem d'un CRUD complert per a les tres entitats del domini que hem creat. Podem veure'n les dades, llistar-les, modificar-les i esborrar-les. Tot això escrivent vuit línies de codi a les classes del domini. No està malament, oi!!?

Entorn de desenvolupament

Amb l'ordre 'grails run-app' arranquem la nostra aplicació en un entorn de desenvolupament. El servidor resin no és apte per a producció però durant el desenvolupament ens perment mantenir-lo arrancat i anar fent modificacions en el codi de manera són automàticament re-carregades en el servidor. Cada pocs segons es revisa si algun fitxer ha sigut modificat, si és així, es recarrega.

Això permet accelerar molt el cicle de desenvolupament, estalviant-nos els passos de compilació i desplegament, sovint molt costosos.

Adaptem l'aplicació

Quan creem un nou Ingredient veiem que els desplegables per seleccionar l'aliment i la recepta mostren l'identificador de base de dades de l'objecte. Per modificar-ho obrirem el la vista que hi ha a 'grails-app/views/ingredient/create.gsp'.

Fixem-nos en els taglibs que s'utilitzen per a generar automàticament els desplegables. Concretament em refereixo al 'g:select' que d'Aliment. Cal que li afegim un atribut 'optionValue' per tal que la llibreria sàpiga quina propietat d'Aliment volem mostrar, de manera que el taglib quedaria:

<g:select optionKey="id" optionValue="name" from="${Aliment.list()}" name="aliment.id" value="${ingredientInstance?.aliment?.id}" ></g:select>

D'altra banda no volem canviar la Recepta a la que estem afegint l'Ingredient així que eliminarem de la taula la fila on hi ha el corresponent desplegable de Recepta. Però sí que volem recordar a l'usuari a quina recepta està afegint l'Ingredient, per fer-ho afegirem el següent text sobre la taula:

...<g:form action="save" method="post" >
Esteu afegint aquest ingredient a la recepta:
<br />
<b>${ingredientInstance.recepta.nom}</b>
<input type="hidden" id="recepta.id" name="recepta.id" value="${fieldValue(bean:ingredientInstance,field:'recepta.id')}" />
<table>...

Amb aquests canvis ens quedarà una vista com aquesta:
createIngredient.tiff

Continuem traient els enllaços a 'Crear Ingredient' que hi ha a les altres vistes d'Ingredient: edit, list i show. Ens referim concretament a la línia:

<span class="menuButton"><g:link class="create" action="create">New Ingredient</g:link></span>

Cal que eliminem aquest enllaç perquè ara només volem que es pugui crear un nou Ingredient a partir d'una Recepta.

Utilitzem serveis per a implementar la lògica de negoci

Cert i fals, la lògica de negoci la podem trobar també en els objectes de domini, però els serveis són l'únic punt des del que els controladors haurien d'accedir a la lògica de negoci. Entenent per 'lògica de negoci' el conjunt d'ordres explícitament destinades a resoldre el problema que l'aplicació vol resoldre. És a dir, no és lògica de negoci ni les vistes, ni els controladors, ni les llibreries de persistència, etc.

Els serveis són automàticament disponibles en el contenidor de DI

Després d'aquest rotllo, centrem-nos en els serveis. Per crear un servei disposem de l'ordre 'grails create-service', que ens crearà un servei en el directori 'grails-app/services'. A l'hora d'arrencar Grails detectarà tots els serveis que hi hagi en el directori convingut i els inicialitzarà i farà disponibles dins l'applicationContext (el contenidor de dependències de Spring) de l'aplicació.

Els serveis tenen transaccionalitat per defecte

A més, els serveis per defecte tenen transaccionalitat del tipus PROPAGATION_REQUIRED, encara que podem desabilitar-ne la transaccionalitat declarant una propietat 'transactional' a fals.

static transactional = false

Aquesta propietat, si no hi és, implícitament s'assumeix que val 'true', encara que moltes vegades ens podem trobar codi on es declara aquest valor explícitament, més que res per recordar al programador que hi ha la transaccionalitat.

Els serveis, com la resta d'artefactes de Grails, poden aprofitar la injecció de dependències que Grails utilitza per defecte.

Què tenim i què mengem avui?

Creem un servei que ens retorni les receptes que contenen un o uns ingredients determinats (segurament els que ens queden a la nevera). Anomenarem a aquest servei ReceptaService. Per crear-lo només haurem d'invocar:

grails create-service recepta

Com podem comprovar el ReceptaService s'ha creat amb la declaració de transaccional i amb un mètode plantilla que caldrà que renombrem a 'cercaReceptesQueContinguin' i que l'implementem.

class ReceptaService
{
 
    boolean transactional = true
 
    /**
     * Retorna un mapa on l'aliment és la clau i un llistat de receptes el valor.
     */
    def cercaReceptesQueContinguin(nomsAliments)
    {
        def alimentsIreceptes= [:]
        nomsAliments.each {
            def aliment= Aliment.findByName(it)
            if (aliment) {
                def ingredients= Ingredient.findAllByAliment(aliment)
 
            def receptes= []
            ingredients.each {
                def rcpts= Recepta.findAll("from Recepta as r, Ingredient as i where i.id=? and i.recepta=r", [it.id])
                rcpts.each { receptes <<  it[0] }
            }
 
            alimentsIreceptes[ it ]= receptes
            }
        }
        return alimentsIreceptes
    }
}

Per a la vista, haurem de crear un fitxer manualment. Com que serà una vista que no correspon a cap entitat Grails no pot saber què i com volem mostrar, per tant haurem d'implementar vista i controlador manualment. Crearem la vista a 'grails-app/views/queMenjo/index.gsp'. Per presentar el formulari un fitxer index.gsp com aquest ens servirà:

<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
        <meta name="layout" content="main" />
        <title>Qu&egrave; menjo?</title>
    </head>
    <body>
        <div class="nav">
            <span class="menuButton"><a class="home" href="${createLinkTo(dir:'')}">Home</a></span>
        </div>
        <div class="body">
            <h1>Quins aliments tinc?</h1>
            <g:if test="${flash.message}">
            <div class="message">${flash.message}</div>
            </g:if>
            <g:form url="[action:'list']" method="get" >
                <div class="dialog">
                    <input type="text" id="aliments" name="aliments" value="" size="100" />
                </div>
                <div class="buttons">
                    <span class="button"><g:actionSubmit class="save" value="Cerca Receptes" action="list"/></span>
                </div>
            </g:form>
        </div>
    </body>
</html>

d'altra banda el resultat el mostrarem a list.gsp:

<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
        <meta name="layout" content="main" />
        <title>Qu&egrave; menjo?</title>
    </head>
    <body>
        <div class="nav">
            <span class="menuButton"><a class="home" href="${createLinkTo(dir:'')}">Home</a></span>
        </div>
        <div class="body">
            <h1>Quins aliments tinc?</h1>
            <g:if test="${flash.message}">
            <div class="message">${flash.message}</div>
            </g:if>
            <g:form url="[action:'list']" method="get" >
                <div class="dialog">
                    <input type="text" id="aliments" name="aliments" value="${nomsAliments}" size="100" />
                </div>
                <div class="buttons">
                    <span class="button"><g:actionSubmit class="save" value="Cerca Receptes" action="list"/></span>
                </div>
            </g:form>
<br/>
        <g:if test="!${receptesPerIngredients}.empty()">
        <h2>Receptes per ingredient</h2>
            <g:each in="${receptesPerIngredients}" status="i" var="entry">
                <h3>${entry.key}</h3>
                <ul>
                    <g:each in="${entry.value}" var="recepta"><li><g:link controller="recepta" action="show" id="${recepta.id}">${recepta.nom}</g:link></li></g:each>
                </ul>
            </g:each>
        </g:if>
        </div>
    </body>
</html>

Ara només queda enllaçar model i vista mitjançant un controlador. Creem el 'QueMenjoController' així:

class QueMenjoController {
 
    def receptaService
 
    def index = {
    }
 
    def list = {
        def aliments= []
        params.aliments?.split(",").each { aliments << it.trim() }
        def receptes= receptaService.cercaReceptesQueContinguin(aliments)
        ["receptesPerIngredients": receptes, "nomsAliments": params.aliments]
     }
 
}

Si ara fem un 'grails run-app' ja disposarem de la nova funcionalitat. Certament podíem haver implementat la cerca de receptes per ingredient en el 'QueMenjoController' però l'avantatge d'haver utilitzat un servei per implementar aquesta cerca és que ara tenim el codi que 'sap' fer-la en un sol lloc i el podem reutilitzar des de qualsevol altre controlador o servei.

queMenjo.tiff

Els següents passos podrien ser:
* Implementar el test per al mètodes que haguem imlementat nosaltres en els controladors. És a dir, crear el test 'GestorApats/test/integration/QueMenjoControllerTest'.
* Implementar el test per a ReceptaService que hi ha a 'GestorApats/test/integration/ReceptaServiceTest'
* Eliminar l'enllaç al controlador d'Ingredient de la pàgina d'inici
* Completar l'aplicació de forma incremental al vostre gust!

Però aquestes tasques us les deixo per a vosaltres. Espero que gaudiu tant com jo amb Groovy i Grails!

Vagi bé!!

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License