Añadiendo propiedades dinámicas en tus controladores Grails

Una de las principales ventajas de Groovy es el uso implícito de getters/setters para el acceso y escritura de variables; y la generación durante la compilación, de getters/setters para todos los atributos de nuestras clases.

Con un par de ejemplos se ve más claro: si tenemos una clase con dos atributos:

class Persona {
    String nombre
    int edad
}

El compilador de Groovy va a generar automáticamente los setters y los getters correspondientes, quedando la clase como si hubiera sido codificada así en Java:

public class Persona {
    private String nombre;
    private int edad;
    public String getNombre() { return nombre; }
    public setNombre(String nombre) { this.nombre = nombre; }
    public int getEdad() { return edad; }
    public setEdad(int edad) { this.edad = edad; }
}

Por otro lado, cuando accedemos a una variable desde Groovy, estamos usando implícitamente los getters y setters, de manera que el siguiente código Groovy:

def valor = miedad
persona.nombre = valor

Sería el equivalente a esto:

def valor = getMiedad()
getPersona().setNombre(valor)

Es decir, cualquier acceso a una variable que no exista en el contexto actual, o cualquier acceso a un atributo de un objeto, se transforma en un get/set (para saltarse esto, es necesario utilizar el operador “Java field” de la siguiente manera: persona.@nombre = valor)

Estos dos características nos permiten añadir, por ejemplo, nuevas variables en nuestros controladores cuyos valores se carguen de base de datos o de la sesión (o de los dos sitios). Por ejemplo, supongamos que tenemos un controlador que requiere en cada petición la lectura de base de datos del usuario y de una factura:

class EjemploController {
  def index = {
    def usuario = Usuario.get(session.miusuario) // Se supone que durante el login, se carga el valor miusuario en la sesion
    usuario.accesos = usuario.accesos + 1
    usuario.save()
    def factura = Factura.findByUsuarioAndFecha(usuario, new Date())
    sendEmailAviso(factura.importe, usuario.email)
    ...
  }
}

Podríamos crear dos métodos getUsuario() y getFactura() que se ocupen del acceso a base de datos y guardar los valores temporalmente en el request

class EjemploController {
  Usuario getUsuario() {
    if (!request.usuario) {
      request.usuario = Usuario.get(session.miusuario) // Se supone que durante el login, se carga el valor miusuario en la sesion
    }
      return request.usuario
  }
  Factura getFactura() {
    if (!request.factura) {
      request.factura = Factura.findByUsuarioAndFecha(usuario, new Date())
    }
    return request.factura
  }
  def index = {
    usuario.accesos = usuario.accesos + 1
    usuario.save()
    sendEmailAviso(factura.importe, usuario.email)
    ...
  }
}

Vemos ahora que el código se ha simplificado muchísimo. Por un lado no tenemos que crear las variables locales usuario y factura, pues siempre están disponibles en nuestras acciones, al ser métodos de la propia clase. Y por otro, no tenemos que estar copiando y pegando el código que se usa para acceder con Gorm a la base de datos y localizar nuestros objetos (incluso en el método getFactura(), en el finder, se utiliza el atributo usuario como un getter internamente)

Y si queremos guardar algo en la sesión, podemos hacerlo también, pero sin olvidarse de utilizar merge() en caso de que el objeto haya sido desincronizado del contexto de persistencia.

Usuario getUsuario() {
  if (!session.usuario) {
      session.usuario = Usuario.get(session.miusuario) // Se supone que durante el login, se carga el valor miusuario en la sesion
  }
  if (!session.usuario.isAttached()) {
      session.usuario = session.usuario.merge()
  }
  return session.usuario
}

Finalmente, todos los métodos que creemos se pueden alojar en una clase abstracta de la que hereden todos nuestros controladores, quedando el ejemplo final así:

abstract class AbstractEjemploController {
  Usuario getUsuario() {
    if (!session.usuario) {
      session.usuario = Usuario.get(session.miusuario)
    }
    if (!session.usuario.isAttached()) {
      session.usuario = session.usuario.merge()
    }
    return session.usuario
  }
  Factura getFactura() {
    if (!request.factura) {
      request.factura = Factura.findByUsuarioAndFecha(usuario, new Date())
    }
    return request.factura
  }
}

class EjemploController extends AbstractEjemploController {
  def index = {
    usuario.accesos = usuario.accesos + 1
    usuario.save()
    sendEmailAviso(factura.importe, usuario.email)
    ...
  }
}

Y nada más. Con este ejemplo hemos visto que, utilizando los scopes request y session para almacenar valores temporalmente, y enmascararlos como atributos dentro de métodos get, es una práctica muy interesante que da como resultado controladores más limpios, con menos código y más fáciles de mantener. Espero que os sirva de utilidad.

Más información: