iberck
11/29/2016 - 8:13 PM

Grails custom databinding

Grails custom databinding

@BindUsing (propiedad)

Se utiliza para hacer un binding personalizado de la propiedad marcada con la anotación. El closure @BindUsing solo es lanzado para una propiedad, por lo tanto debe haber un parámetro en el request con el mismo nombre de la propiedad.

class Product {
  
    // Product _this, DataBindingSource source
    @BindUsing({ _this, source ->
 
        // El parámetro source contiene los valores.
        final String productId = source['productId']
 
        // ID format is like {code}-{identifier},
        // eg. TOYS-067e6162.
        final productIdParts = productId.split('-')
 
        // EL CLOSURE DEBE RETORNAR EL OBJETO QUE SE ASIGNARÁ A LA PROPIEDAD.
        new ProductId(
            code: productIdParts[0],
            identifier: productIdParts[1])
 
    })
    ProductId productId
 
}
 
// Class for product identifier.
class ProductId {
    String code
    String identifier
}

@BindUsing (clase)

Sirve para hacer un binding propiedad por propiedad hasta hacer el binding de todas las propiedades de la clase, para ello es necesario implementar la interfaz BindingHelper.

@BindUsing se utiliza comunmente a nivel de propiedad, rara vez se utiliza a nivel de clase ya que utiliza un tipo genérico para el tipo de retorno lo cual significa que todas las propiedades de la clase deberían ser del mismo tipo. Además tiene un bug cuando en el request hay múltiples campos con el mismo nombre.

Nota: se hizo una prueba y @BindUsing a nivel de clase es ignorado en grails 2.5.2: http://stackoverflow.com/questions/22258653/grails-bindusing-on-a-class-being-ignored-and-bindusing-vs-valueconverter

@BindingFormat

@BindingFormat permite hacer un binding personalizado a partir de un formato que viene de la anotación. Se define un FormattedValueConverter el cual proporciona el valor del request y el formato que proviene de la anotación para poder crear un objeto de cualquier tipo el cual es el asignado a la propiedad marcada con esta anotación. El formato soporta i18n.

Un buen caso de uso es construir un Date a partir de una fecha en cadena que viene del request y el formato indicado en la anotación de la propiedad.

Ejemplo que transforma el valor del request al formato indicado en la anotación:

package com.myapp.converters

import org.grails.databinding.converters.FormattedValueConverter

class FormattedStringValueConverter implements FormattedValueConverter {

    def convert(value, String format) {
        if('UPPERCASE' == format) {
            value = value.toUpperCase()
        } else if('LOWERCASE' == format) {
            value = value.toLowerCase()
        }
        value
    }

    Class getTargetType() {
        // specifies the type to which this converter may be applied
        String
    }
}
// grails-app/conf/spring/resources.groovy
beans = {
    formattedStringConverter com.myapp.converters.FormattedStringValueConverter
}
import org.grails.databinding.BindingFormat

class Person {
    @BindingFormat('UPPERCASE')
    String someUpperCaseString

    @BindingFormat('LOWERCASE')
    String someLowerCaseString
}

Custom Data Converters/ BigDecimal converter

Sirven para indicar a grails cómo debe hacer el databinding hacia cierto tipo de dato. Por ejemplo para que grails pueda hacer automáticamente databinding de objetos BigDecimal a lo largo de toda la aplicación, es necesario definir un custom data converter para BigDecimal.

Por ejemplo podría escribir un ValueConverter de la clase Address para que grails sepa cómo hacer el databinding de la cadena del request hacia la propiedad de tipo Address.

Grails tiene definidos ValueConverters para los tipos de dato básicos (Long, Integer, ...), esto le permite saber cómo transformar el valor en cadena del request a cada uno de los tipos de dato básicos.

Con la siguiente clase se indica a Grails cómo debe hacer el databinding cada que encuentre una propiedad de tipo BigDecimal:

class BigDecimalValueConverter implements ValueConverter {

    @Override
    boolean canConvert(Object value) {
        // La expresión regular acepta solo números decimales en el formato "23.22"
        value instanceof String && value ==~ /([\+\-])*[0-9]*(\.[0-9]+)*/
    }

    @Override
    Object convert(Object value) {
//        log.trace("Convirtiendo el valor: $value a BigDecimal")
        // Independientemente del locale el separador decimal que espera el constructor es siempre "."
        return new BigDecimal(value)
    }

    @Override
    Class<?> getTargetType() {
        return BigDecimal
    }
}
beans = {
    defaultGrailsBigDecimalConverter(BigDecimalValueConverter)
}
class Account {
    BigDecimal value
}

Structured Data Binding Editors

A diferencia de los ValueConverter que se crea un objeto a partir de 1 valor del request, con estos editores se pueden crear objetos a partir de varios parámetros del request.

Un ejemplo común puede ser crear un objeto Date a partir de los parámetros del request birthday_month, birthday_date y birthday_year.