Multiples clases de dominio y controladores

enero 15, 2010

Aveces, necesitamos crear multiples clases de dominio junto con sus controladores para hacer un sencillo scaffolding. una forma de ganar tiempo al hacer este proceso es:

1) grails install-templates, para modificar la forma en la que se generan los artefactos. No cometer el error de traducir las vistas desde aqui, pues la idea es hacerlo con i18n. Lo que si va mos a modificar son los templates de los controladores para incluir cambiarlo con def scaffold = true en el lugar apropiado, en el archivo src/templates/artifacts/Controller.groovy. Tambien ahí hay otros archivos a modificar si se quisiera, por ejemplo las clases de dominio, etc. Este paso hay que hacerlo por cada nuevo proyecto. Debe haber un script que permita hacer la modificación de manera global, pero no voy a perder tiempo buscandolo porque no me parece tan importante el hecho que sea global.

2) Para crear rapidamente multiples clases de dominio y controladores, vamos a editar los scripts encargados de crearlos de tal forma que reciban más de un argumento. El cambio que hagamos si que va a quedar permanente para todos los nuevos proyectos que hagamos. Vamos a la carpeta GRAILS_HOME/scripts. Primero editamos CreateDomainClass.groovy para que la parte final quede así:

def names = argsMap["params"]
names.each{
createArtifact(name: it, suffix: "", type: "DomainClass", path: "grails-app/domain")
createUnitTest(name: it, suffix: "")
}

Ahora editamos CreateController.groovy:

names.each{
createArtifact(name: it, suffix: type, type: type, path: "grails-app/controllers")
def viewsDir = "${basedir}/grails-app/views/${propertyName}"
ant.mkdir(dir:viewsDir)
event("CreatedFile", [viewsDir])
createUnitTest(name: it, suffix: type, superClass: "ControllerUnitTestCase")
}

Con esto, y valiendonos de los brace expansions de bash ahora podemos hacer cosas como:
grails create-domain-class mycompany.{clase1,clase2,clase3}
grails create-controller mycompany.{clase1,clase2,clase3}

NOTA: mycompany sería el nombre del paquete que quiero usar. Cuando no uso paquetes, grails 1.2 me pone problema al tratar de hacer crud desde la web.

e reciban

Enums en Grails 1.1+

diciembre 28, 2009

Supongamos que estoy modelando diseñando una aplicación. Encuentro que uno de los objetos del dominio, está limitado a un conjunto de valores que puede tomar. En terminos generales existen dos alternativas:

  • Si el conjunto de valores cambia en el tiempo, como por ejemplo el tipo de usuario (Administrador, Invitado, etc) asignado a cada usuario para gestionar sus permisos dentro de la aplicación, que en cualquier momento nos puede dar por agregar un nuevo tipo con otros provilegios: Una buena estrategia puede ser utilizar “lookup tables”, lo que en Grails corresponde a una clases de dominio que simplemente tienen su id y una propiedad adicional, por decir algo value, que es la que se encarga de almacenar cada uno de los posibles valores. De hecho hay quienes desnormalizan las “lookup tables” y meten todos los valores en una sola tabla (“domain/lookup table”), tal como lo comentan aqui.
  • Si el conjunto de valores es estatico, por ejemplo el genero (Masculino o Femenino) de una persona: Las “lookup tables” tambien podrían servir, pero quizá una enumeración sea más apropiada dado que precisamente para estos casos es que fueron pensadas: Conjunto (de tamaño razonable) de datos preestablecidos conocidos que no interesan ser cambiados a futuro. Una de las principales ventajas que nos brindan es que permiten una validación de Tipos.

En este post nos interesa abordar el segundo caso, cuando queremos usar enumeraciones. Asumo que se sabe implementar una enumeración en Groovy.

En GRAILS definimos una enumeración por ejemplo en src/groovy (de esta forma no será persistente):

enum Estado{
EVALUANDO_CICLO('EVALU', 'Evaluando ciclo'),
INVESTIGANDO_CAUSA_BENEFICIO('INVES', 'Investigando causa beneficio'),
IMPLANTANDO_SOLUCION_MEJORA('IMPLA', 'Implantando solucion mejora'),
VERIFICANDO_SOLUCION_MEJORA('VERIF', 'Verificando solucion mejora'),
CERRADA('CERRA', 'Cerrada');
final String id
final String value
Estado(String id, String value) { this.id = id;
this.value = value }
}

Notese que al ser groovy, los getter y setters han sido creados para id y para value. Luego en nuestra clase de dominio simplemente agregamos la enumeración como si se tratase de cualquier otra clase:

class Persona{
Estado estado = Estado.
EVALUANDO_CICLO
}

Con esto ya hemos logrado que cada vez que se haga persistente un objeto de la clase Persona, va a invocar al méotdo getId de su miembro tipo Estado y lo retornado es lo que se almacenará en la base de datos.

Por ejemplo si tuvieramos:

new Persona(estado:Estado.VERIFICANDO_SOLUCION_MEJORA).save()

obtendriamos en nuestra base de datos almacenado “VERIF“, y no todo el name() del enum (VERIFICANDO_SOLUCION_MEJORA), con el consiguente ahorro de recursos; esta es la gran ventaja de usar el método getId(). De hecho pudimos usar incluso algo más liviano como por ejemplo un entero, pero digamos que usando un pequeño String no es tan grave en recursos y da cierta legibilidad a los datos almacenados en la base de datos.  Como propiedad id podemos utilizar cualquier tipo de dato soportado por hibernate como Integer, String, Float, etc. Para mayor información sobre el getId() aqui (notese que es una funcionalidad de 1.1+).

Notese que GORM es lo suficientemente inteligente como para no tener que especificarle los posibles datos mediante un constraint tipo inList. Sinembargo, necesitamos que en la vista se vea la propiedad value y no la llamada a name(). Para esto debemos modificar la vista, en las partes donde se muestre el valor de la enum.

create.gsp y edit.gsp: <g:select name=”estado” from=”${InvariantManager$Estado?.values()}” value=”${solicitudNC_SInstance?.estado}” optionValue=’value’/>

show.gsp: <td valign=”top”>${solicitudNC_SInstance?.estado?.value?.encodeAsHTML()}</td>

list.gsp: <td>${fieldValue(bean: solicitudNC_SInstance, field: “estado.value“)}</td>

Como vemos, hemos logrado almacenar poca información en la base de datos (getId() = VERIF), utilizar enumeraciones legibles en código (name() = VERIFICANDO_SOLUCION_MEJORA) y presentando  en la vista un mensaje amigable al usuario (getValue() = Verificando solucion mejora), que más adelante podremos internacionalizar fácilemente tal como se explican en la documentación:

// create select with internationalized labels (this is useful for small static lists and the inList constraint)
// expected properties in messages.properties:
// book.category.M=Mystery
// book.category.T=Thriller
// book.category.F=Fantasy
<g:select name="book.category" from="${['M', 'T', 'F']}" valueMessagePrefix="book.category" />

Info adicional:

  • Internacionalización: aqui, aqui, aqui.
  • Un acercamiento similar, pero desconocen el uso del getId: aqui.

Seguir

Recibe cada nueva publicación en tu buzón de correo electrónico.