Grails constraints [validation]
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:
validate()
.save()
.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
.
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.
Existen algunos constraints que únicamente aplican para propiedades de tipo String
, por ejemplo: creditCard
, email
, matches
, blank
, etc.
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
.
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.
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()
}
}
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.
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"
}
}
}
}
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
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.
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, "")
}
}
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>
_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.
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')
}
}
}
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
.
return ["mantosAcelerables.more1abono", String.valueOf(manto.year)]
<!-- ------------------------------------ -->
<!-- Mostrar errores -->
<!-- ------------------------------------ -->
<g:hasErrors bean="${bean}">
<div class="alert alert-danger">
<g:renderErrors bean="${bean}" as="list"/>
</div>
</g:hasErrors>