iberck
11/22/2016 - 3:49 PM

Grails constraints [validation]

Grails constraints [validation]

Constraints

Los constraints sirven para validar que los valores asignados a las propiedades sean válidos, además a través de GORM ayudan a la generación del esquema de la bd.

Los constraints se aplican en dos lugares:

  • En el databinding (valida los datos de la vista) a través del método validate().
  • Cuando se guarda el domain class (valida los datos hacia la bd) a través del método save().

Constraints Defaults

Por defecto, todas las propiedades de un domain/command object son nullable: false (obligatorias) y la única manera de cambiarlo es sobreescribiendo la propiedad. Por lo tanto si se agrega un validator personalizado, se mantendrá el constraint nullable:false.

Nullable vs Blank

Cuando se realiza databinding en los input sin valor estos vienen como cadenas vacías, sin embargo el databinder convierte dichas cadenas vacías a null para que aplique el constraint por default de todas las propiedades nullable:false. No importa si la cadena tiene espacios " ", dicha cadena será convertida a null. Por lo tanto, no tiene mucho sentido manejar el constraint blank si los databinding solo provienen del request.

El constraint blank sirve para validar que una propiedad no esté en blanco después de aplicar un trim, por lo que rechaza los valores "" o " ". Utilice blank para garantizar que un valor no entre a la bd en blanco ya que del request nunca vendrá una cadena vacía.

Constraints exclusivos para cadenas

Existen algunos constraints que únicamente aplican para propiedades de tipo String, por ejemplo: creditCard, email, matches, blank, etc.

max vs maxSize

max: Asegura que el valor no exceda a max

maxSize: Asegura que el size de una colección no sobrepase a maxSize.

min: Asegura que el valor no sea menor a min

minSize: Asegura que el size de una colección no sea menor a minSize.

size

Utiliza un groovy range para restringir el tamaño de una colección, un número o el length de un String

String telCelular
...

telCelular size: 8..15 // mínimo 8, máximo 15 (si acepta Strings de tamaño 8 y 15)

Nota: Currently this constraint cannot be used in addition to blank or nullable. Use a custom validator for these combinations.

Constraints property order

Los constraints de una propiedad se ejecutan en el orden que fueron definidos, en el siguiente ejemplo se ejecuta primero email:true y después el validator. Sin embargo todos los constraints son ejecutados y se van acumulando, es decir, aunque el nombre no sea un email válido se ejecutará el validator.

package com.testing

@Validateable
class Command1 {

    String yourEmail;

    static constraints = {
        yourEmail email:true, validator: { value ->
            if (value < 4) {
                return "erroneo"
            }
        }
    }
}

Esta regla no aplica para el constraint por default nullable:false, cuando no está presente es el primer constraint en evaluarse por lo tanto si la propiedad es null no se ejecutará ninguno de los subsecuentes constraints. Esto está bien para no estar evaluando valores null en las validaciones personalizadas.

Nota: Si se escribe el constraint nullable:false en una propiedad, no importa si está antes o después de un validator, siempre será el primero en evaluarse.

Las propiedades se escriben en el scaffolding de acuerdo a cómo aparecen en los constraints, se puede cambiar el orden de una propiedad declarándola con constraints vacíos:

@Validateable
class Command1 {
    String nombre
    String apellido

    static constraints = {
        nombre()
        apellido()
    }
}

Validadores personalizados

Grails permite definir validadores personalizados en cualquier campo, para ello es necesario escribir un validator en los constraints el cual puede recibir 1, 2 o 3 parámetros, de acuerdo al número de parámetros es la manera como se agregan los mensajes de error.

Validator con 1 parámetro

Utilicelo cuando solo necesite el valor del campo para realizar la validación:

package com.testing

@Validateable
class Command1 {

    BigDecimal costoContrato = 0

    static constraints = {
        costoContrato validator: { value ->
            if (value < 4) {
                return "erroneo"
            }
        }
    }
}

Validator con 2 parámetros

El primer parámetro es el valor introducido por el usuario sobre el campo (value), el segundo la instancia de la clase (command1). La instancia de la clase siempre tendrá todos los valores introducidos por el usuario en todos los campos.

Cuando el validator recibe 2 parámetros, el mensaje de error se agrega retornando el código de error:

package com.testing

@Validateable
class Command1 {

    BigDecimal costoContrato = 0
    int maxCosto = 90

    static constraints = {
        costoContrato validator: { value, command1 ->
            if (value < command1.maxCosto) {
                return "erroneo"
            }
        }
    }
}

En el ejemplo anterior, la llave "erroneo" será buscada en messages.properties como:

com.testing.Command1.erroneo ó erroneo

Validator con 2 parámetros y argumentos

package com.testing

@Validateable
class Command1 {

    BigDecimal costoContrato = 0

    static constraints = {
        costoContrato validator: { value, command1 ->
            if (value < 4) {
                return ["erroneo", 97]
            }
        }
    }
}

erroneo=El valor {2} es erroneo

Los posibles placeholders que se pueden utilizar en el key de messages.properties son:

{0}: nombre del campo

{1}: Nombre completo de la clase

{2}: Valor introducido en la propiedad

{3}: primer parámetro del array

{4}: segundo parámetro del array

{N}: parámetro N del array.

Validator con 3 parámetros

El validator que recibe 3 parámetros, recibe los mismos parámetros que el validador de 2 parámetros y un tercero que es el objeto errors. Cuando se utiliza esta versión por convensión se está indicando a Grails que los errores serán introducidos directamente en el objeto errors por lo que ya no se deberá retornar el código de error. En esta versión la manera de terminar la ejecución del validador es con un return.

pagoPrimerManto nullable: true, validator: { value, _this, errors ->
            // Cuando el número de mantenimientos del depósito inicial es 0, quiere decir que no se está incluyendo ningún
            def pagoPrimerMantoRequired = _this.numMantosDepIni==0
            if (pagoPrimerMantoRequired && value == null) {
                errors.rejectValue("pagoPrimerManto", "pagoPrimerManto.notnull", null, "")
                return
            }

            if (value[Calendar.YEAR] != _this.anioAReservar) {
                errors.rejectValue("pagoPrimerManto", "diferent.message", null, "")
            }

        }

Validator general en los constraints

Se puede escribir un validator a nivel de propiedad que valide los valores de distintas propiedades del objeto, sin embargo no es lo más entendible ya que un validator a nivel de propiedad se entiende que sirve para validar de manera personalizada la propiedad donde se define.

Una manera de lograrlo es agregando al command una propiedad extra que se encargará de validar todas las propiedades del objeto:

@Validateable
class Command1 {
    String nombre
    String apellido
    boolean customValidator

    static constraints = {
        customValidator nullable: true, validator: { value, command1, errors ->
            if (command1.nombre.equals(command1.apellido)) {
                errors.reject("nombreapellido.equals.error")
                return
            }
        }
    }
}
<g:form action="formSubmit">
    <g:hasErrors bean="${command1}">
        <div class="alert alert-danger">
            <g:renderErrors bean="${command1}" as="list"/>
        </div>
    </g:hasErrors>


    Nombre: <g:textField name="nombre" value="${fieldValue(bean: command1, field: "nombre")}"/><br/>
    Apellido: <g:textField name="apellido" value="${fieldValue(bean: command1, field: "apellido")}"/><br/>

    <g:submitButton name="enviar"/>
</g:form>

FAQ

¿Cuando se valida un constraint de una propiedad y se utiliza _this, ya vienen rellenas todas las propiedades?

Si, todas las propiedades vienen rellenas, en esta etapa no se hace binding para pensar si vienen o no llenas, esta es una etapa de validación y por lo tanto se encuentran todos los valores puestos en las propiedades.

¿Cómo obtengo el valor antiguo de una propiedad?

class Persona {
    String nombre
    String apellido

    static constraints = {
        nombre validator: {value,_this->
            def oldValue = _this.getPersistentValue('nombre')
        }
        apellido validator: {value,_this->
            def oldValue = _this.getPersistentValue('apellido')
        }
    }
}

¿Cuando se escribe un constraint, se sobreescriben los constraints por default?

El único constraint por default es nullable:false y la única manera de sobreescribirlo es sobreescribiendo la propiedad nullable. Por lo tanto si se agrega un validator personalizado, se mantendrá el constraint nullable:false.

¿Cómo evitar que el argumento enviado se formatee como un integer y venga separado con comas ?

return ["mantosAcelerables.more1abono", String.valueOf(manto.year)]

Referencia

http://docs.grails.org/2.5.x/ref/Constraints/validator.html

<!-- ------------------------------------ -->
<!-- Mostrar errores -->
<!-- ------------------------------------ -->
<g:hasErrors bean="${bean}">
    <div class="alert alert-danger">
        <g:renderErrors bean="${bean}" as="list"/>
    </div>
</g:hasErrors>