Exámenes Grails #1

Grails

Trabajando con una herramienta podemos encontrarnos con ciertos casos de código curiosos. Cuanta más "magia" contenga la librería, lenguaje o framework es más probable encontrarnos con alguno, por magia me refiero a algo que desconocemos como funciona internamente exactamente y que tampoco está documentado en el manual de referencia. Después de investigar unos minutos, horas o algún día puede que demos con la explicación lógica del comportamiento que a priori no entendemos.

Grails tiene una buena cantidad de magia y unos cuantos años programando con él me he encontrado con unas pocas de estas situaciones que comentaré a continuación. Primeramente expondré el código de los casos y al final del artículo la explicación, cual es comportamiento o que se puede producir. Están realizados usando Grails 2.4.5 aunque el comportamiento será el mismo en muchas otras versiones.

Caso #1

class FooTagLib {
    static namespace = 'foo'
}

class FooController {
    def list(){
        [foo: null]
    }
}

En este caso el namespace de un taglib colisiona con el nombre de una variable del modelo que se envía a un gsp. La pregunta es, ¿en el gsp que es ${foo}?.

Caso #2

class FooTagLib {
    static namespace = 'foo'
}

class FooController {
    def list(){
        [foo: new Date()]
    }
}

Esta es una variación del ejemplo anterior, mismo taglib mismo controlador pero ahora en la variable devolvemos un dato en vez de un null. Y teniendo en cuenta que un taglib también lo podemos usar de esta forma:

${foo.hello()}

La pregunta es la misma, ¿en el gsp que es ${foo}?

Caso #3

class Book {
    int x
    int y = 0
    void setY(int y) {
        if (!x) throw new Exception()
        this.y = y
    }
}

class BookController {
    def save() {
        def book = new Book()
        book.properties = [y:1,x:1]
    }
}

Este es un ejemplo de la magia que hace el setProperties de una entidad de dominio pasándole un mapa para que establezca las propiedades. A tener en cuenta la regla de la verdad de Groovy y el orden que se establecen las propiedades. La pregunta es, ¿cual es valor de book.x y de book.y después de llamar a la acción BookController.save? ¿0, 1 o se produce una excepción?

Explicación caso #1

El valor de foo en el gsp será algo como esto:

org.codehaus.groovy.grails.web.taglib.NamespacedTagDispatcher@10eb24a

Es decir foo es la referencia al taglib y no el valor null devuelto en el controlador. ¿Que implica esto? Que no debes usar como nombre de una variable del modelo para la vista el namespace asignado a un taglib.

Explicación caso #2

En esta variación del caso #1, foo toma el valor esperado, un objeto de tipo Date. Y este es el motivo por que no debes usar como nombre de una variable del modelo para la vista, dependiendo si la variable tiene valor null o un objeto, en la vista foo toma la referencia del taglib o el objeto. Debes conocer los namespaces de tus taglibs y de los taglibs de los plugins que incluyas en el proyecto. Como estos últimos no dependen de uno mismo hay que tener especial cuidado.

Los usos de un taglib tal que ${foo.hello()} provocan una excepción ya que este caso el objeto Date no tiene un método de nombre hello pero <foo:hello/> si funcionará en cualquier caso.

Explicación caso #3

La respuesta es que el valor de book.x es 1 y el de book.y es 0. ¿Que está pasando? Las propiedades se van estableciendo según el orden definido por el mapa, primero se intenta establecer el valor de y y como x en ese momento se evalúa a falso por ser 0 se lanza la excepción, luego se establece x a valor 1. Esto unido a que el setPropeties de las entidades debe hacer internamente un try/catch silenciando las excepciones que se produzcan hará que nos preguntemos que está sucediendo hasta que añadamos algunas trazas.

Estos casos explicados así pueden parecer divertidos pero en el momento de estar investigándolos durante unas cuantas horas sin encontrar la explicación no lo son tanto, más si están presentes en producción e impide a unas docenas de usuarios de la aplicación emplearla o dificultándoselo en gran medida y cuando al mismo tiempo QA pregunta cuando va a estar disponible la corrección para solucionar el problema que esté originando.

En otro artículo comentaré otros casos similares a estos pero de Groovy.