iberck
10/12/2016 - 3:45 AM

Grails validación y manejo de errores

Grails validación y manejo de errores

Método validate

El método validate() valida que los valores de las propiedades del objeto coincidan con los constraints definidos en dicho objeto, si no coinciden agrega los respectivos errores en el objeto errors.

Fases de validación

Primero, al hacer el binding de los valores del request hacia el domain object se valida que los tipos de datos sean correctos (por ejemplo no se puede asignar "ad" a un int).

Después, el método validate() o save() (internamente invoca a validate()) validan los valores de las propiedades contra los constraints definidos en el domain/command object.

Cuando se envía un domain/command object como argumento en una acción de un controlador, por automático realiza las validaciones de tipo y de constraints, es decir invoca al método validate().

Mostrando errores de validación

${persona.nombre}: Cuando hay errores de validación es imposible mostrar los valores incorrectos capturados por el usuario.

${fieldValue(bean: persona, field: 'nombre')}: Cuando hay errores de validación, muestra el valor incorrecto capturado por el usuario, si no los hay obtiene el valor del field.

Grails forms (mostrar errores)

El siguiente pedazo de código muestra una manera genérica de utilizar un formulario con controles y manejo de errores.

El databinding lo hace en base al name del control, no tiene nada que ver el atributo value.

El atributo value sirve para asignar el valor del control, el value del control se obtiene a través del taglib fieldValue

El taglib fieldValue sirve para poder mostrar el valor erroneamente capturado (cuando se captura y tiene algún error), si no se obtuviera el valor a través del tablig fieldValue se obtendría el último valor guardado en el bean.

index.gsp:

<div class="container">

    <div class="row">
        <div class="col-xs-12">
            <g:hasErrors bean="${cmd}">
                <div class="alert alert-danger">
                    <g:renderErrors bean="${cmd}" as="list"/>
                </div>
            </g:hasErrors>
        </div>
    </div>

    <form class="form-horizontal">
        <div class="form-group">
            <label for="nombre" class="col-sm-3 control-label">Nombre:</label>

            <div class="col-sm-3">
                <g:textField name="nombre" value="${fieldValue(bean: cmd, field: 'nombre')}"/>
            </div>
        </div>

        <g:actionSubmit action="send" value="Enviar"/>
    </form>

</div>

TestController.groovy:

class TestController {

    def index() {
        render view: "index", model:[cmd: new MyCommand()] // index.gsp
    }

    def send(MyCommand cmd) {
        log.info("nombre==>${cmd.nombre}")
        render view: "index", model: [cmd:cmd]
    }

    private static class MyCommand {
        String nombre

        static constraints = {
            nombre minSize: 5
        }
    }
}

Objeto errors

Todo domain class o command object tiene un objeto errors. El objeto errors almacena los mensajes de error y los valores erroneamente capturados.

La propiedad errors de cada domain class es una instancia de la interfaz Errors de Spring. La interfaz proporciona métodos para navegar por los errores de navegación y también para obtener los valores originales.

errors.reject: Sirve para registrar un error global

errors.reject(String errorCode)
errors.reject(String errorCode, Object[] errorArgs, String defaultMessage)
errors.reject(String errorCode, String defaultMessage)

errors.rejectValue: Sirve para registrar un error a nivel de field

Con el siguiente código se puede utilizar hasErrors como operador ternario: ${hasErrors(bean: membresiaInstance, field: "ptosxanio", "1") ? "danger" : "primary"}

Personalizando mensajes de error

Renombrar un field name:

<full packagePath>.<domain name>.<propertyName>.<label>=<message>

Un field no cumple la validación nullable:

<full packagePath>.<domain name>.<propertyName>.nullable=<message>

Servicios que lanzan excepciones

Es un error querer lanzar una grails.validation.ValidationException ya que esta excepción es utilizada dentro por el propio Grails para agregar errores a un domain class/command object. En vez de eso nosotros tenemos el mecanismo de alto nivel static constraints del domain class/command object.

Lo mejor es que un command object realice las validaciones y se ayude de servicios.

Si un servicio necesita lanzar excepciones, cree sus propias excepciones RuntimeException para descartar los valores introducidos temporalmente en el domain class, ya que al no ser errores constraints, éstos no serán descartados y persistidos automáticamente hacia la base de datos a causa de OSIV en Grails. Recuerde lanzar excepciones para situaciones excepcionales.

Mostrar mensajes de error

A menudo los errores se muestran con un render ya que se envía de vuelta a la vista el command object/doman class con los errores de validación. Cuando sea así, utilice request.error = message para introducir el mensaje de error porque si lo introduce como flash.error = message dicho error será mostrado 2 veces.

Cuando utilice redirect introduzca el mensaje de error con flash.error para que el mensaje viva durante el doble request.

Se pierde el objeto errors de un domain class desde el controlador hacia la vista

Esto sucede porque cuando termina un método @Transactional, invoca el método save() sobre el domain class el cual a su vez limpia el objeto errors.

Hay que tener mucho cuidado con agregar errores al objeto errors de un domain class en un método de servicio ya que se pierden por arte de magia. La manera de evitar que se pierdan es lanzando una RuntimeException y cachándola en el controlador, de esta forma se hace rollback de la transacción y se evita sea invocado el método save().

Otra manera de evitarlo es invocando el método discard() sobre la instancia para que la saque de la sesión y no se ejecute sobre ella automáticamente el método save():

@Transactional
class AlumnoService {

    @Transactional
    void updateAlumno(Alumno alumno) {
        if (alumno.edad > 80) {
            alumno.errors.reject("alumno.error.edad")
            alumno.discard()
            return
        }
        alumno.save(failOnError: true)
    }

}

http://stackoverflow.com/questions/32767450/grails-validation-errors-disappear-from-service-to-controller

// ---------------------------------------
// Errors reject
// ---------------------------------------
errors.reject("key_codigo_error")

// ---------------------------------------
// Errors reject con argumentos
// ---------------------------------------
errors.reject("key_codigo_error", ["arg1", "arg2"] as Object[], "defaultMessage(optional)")