Grails: entendendo as validações (constraints)

No meu último post falei sobre alguns detalhes poucos conhecidos das buscas por critérios. Agora chegou a hora de expor algumas coisas bem legais sobre as validações, aka constraints, oferecidas pelo framework.

O básico

As constraints nos permitem definir declarativamente as regras de validação que devemos aplicar às nossas classes de domínio. São muito similares ao projeto “Hibernate Validator”. A diferença é que ao invés de usarmos anotações, iremos usar uma DSL interna definida pelo framework. Assim como no caso da busca por critérios, a DSL usada é baseada no recurso builder presente na linguagem Groovy.

Para declararmos as regras de validação, devemos incluir um bloco estático na nossa classe de domínio chamado constraints, tal como no exemplo abaixo:


class DominioBonito {
String nome
Integer codigo
String email

static constraints = {
nome (nullable:false, blank:false, unique:true)
codigo(min:1)
email(email:true)
}
}

A sintaxe é muito simples: em cada linha você deverá incluir o nome do campo a ser validado, seguido de um par de parênteses contendo as regras a ser aplicadas. Cada regra recebe um ou mais parâmetros, que variam de acordo com a sua implementação. Não se assuste com isto, pois estes atributos se tornarão familiares a você com o tempo.

Todas as constraints default do Grails podem ser acessadas neste link: http://grails.org/doc/latest/ref/Constraints/Usage.html (salve-o em seus bookmarks para referência, pois é uma mão na roda)

Agora que o básico já foi dito, podemos ver detalhes menos conhecidos deste recurso.

Constraints e scaffolding

Eis um detalhe das constraints que não acho bacana: a sua presença influencia o scaffolding. Caso você queira definir a ordem em que os atributos de suas classes de domínio são expostos por este recurso, tudo o que você deve fazer é alterar a ordem na qual os atributos são definidos no bloco constraints da sua classe de domínio.

Há inclusive algumas constraints cujo objetivo é justamente definir regras para o scaffolding. São elas:

display (true ou false): define se o campo vai aparecer ou não nas páginas geradas pelo scaffolding
editable (true ou false): define se o campo será editável no scaffolding ou não
format (string): no caso de datas e números, define qual o formato no qual estes serão expostos, digitados pelo usuário (útil d+!)
password (true ou false): se você definir como true, será exposto um input do tipo password na sua interface gráfica
widget (string): define qual widget deverá ser renderizado. Você deve apenas fornecer qual o nome da tag usada, como por exemplo textarea

Dica: evite este recurso. A classe de domínio deve ser a parte de negócio da sua aplicação, não a responsável pela visualização.

Constraints e GORM

As constraints também influenciam o funcionamento do GORM. No caso da criação das tabelas do banco de dados estar sendo executada pelo framework, as constraints são usadas para definir o tamanho dos campos, assim como o fato de aceitarem ou não valores nulos. Como exemplos, posso citar as seguintes constraints:

maxSize: no caso de campos do tipo varchar ou char, define qual o seu tamanho
nullable: define se o campo pode aceitar valores nulos ou não
unique: cria um índice de unicidade para a coluna relacionada

No caso de maxSize, caso não seja incluida, será usado como valor default de tamanho para campos textuais o padrão do SGBD que, normalmente, é 255 caracteres.

Constraints customizadas

Ok, Grails te oferece uma série de constraints úteis, mas e se você quiser criar uma personalizada? Por exemplo: no cadastro de usuários, ter uma classe de domínio na qual seja verificada a igualdade das senhas digitadas. Como você faz isto? Neste caso, você usa a constraint validator. Veja o exemplo abaixo:


class Usuario {

String nome
String login
String senha1
String senha2

static constraints = {
senha2(validator:  {valor, objeto ->
objeto.properties['senha2'] == valor
})
}

}

A constraint valiator neste caso recebe como valor uma closure que deverá possuir dois parâmetros, obrigatóriamente nesta ordem: valor, objeto. O primeiro representa o valor a ser validado, e o segundo o objeto a ser verificado que, no caso, é a sua instância da classse de domínio a ser validada. O objeto precisa ser passado como parâmetro porque as constraints são um bloco estático. Caso contrário, não haveria como usar este recurso em um ambiente concorrente como aplicações web.

Se o valor da constraint é true, é sinal de que o teste passou. No entanto, esta constraint também pode retornar uma string. No caso, esta string representa uma mensagem de erro, que é definida nos arquivos de mensagens do Grails, presentes no diretório grails-app/i18n. Eu poderia reescrever o exemplo acima da seguinte maneira portanto:


class Usuario {

String nome
String login
String senha1
String senha2

static constraints = {
 senha2(validator:  {valor, objeto ->
if (objeto.properties['senha2'] == valor)
return true
else
return "erro.senhas.diferentes"
})
}

}

E esta mensagem seria exposta na minha camada de visualização tirando proveito do recurso de internacionalização do Grails.

Você também pode usar esta constraint passando como parâmetro uma closure que possua um único parâmetro, tal como neste exemplo:


class Testolina {
Integer valor
static constraints = {
valor(validator: {it % 2 == 0}
}
}

Neste caso, apenas o valor é verificado, que deverá ser um número par.

Constraints globais e compartilhadas

Um recurso muito útil e pouco comentado do Grails é a possibilidade de se criar regras de validação globais e compartilhadas. Imagine a seguinte situação: no seu projeto, por default, todos os atributos devem ser não nulos por default. Não é chato ter de repetir esta regra em todas as suas classes de domínio?

Você inclui uma regra de validação global no arquivo grails-app/conf/Config.groovy. Basta adicionar um código similar ao abaixo:


grails.gorm.default.constraints = {
'*'(nullable:false)
}

O * diz que esta regra se aplica a todos os atributos. Hei! Se eu inclui *, eu também posso incluir o nome de um atributo padrão em minhas classes, como, por exemplo, “nome”? Claro! Veja o exemplo abaixo:


grails.gorm.default.constraints = {
'nome'(nullable:false, maxSize:128)
}

Não é legal? Bom: esta é uma constraint global, mas o que seria uma constraint compartilhada?

Uma constraint compartilhada é uma regra de validação que pode ser aplicada em mais de um local nas suas classes de domínio. Vamos supor que você tenha vários atributos os quais devam, obrigatóriamente, possuir no máximo 20 caracteres. Você incluiria no mesmo arquivo um código similar ao abaixo:


grails.gorm.default.constraints = {
compartilhada(maxSize:20)
}

E para usá-la em suas classes de domínio, a declararia tal como abaixo:


class Testolina {
String rua
static constraints = {
rua (shared: "compartilhada")
}
}

Fácil e direto ao ponto. Eu amo isto!

Como validar constraints

Ok, você sabe como declarar suas regras de validação. Agora precisa saber como usá-las. No caso do Grails, toda classe de domínio possui injetado um método chamado validate. Caso alguma regra de validação falhe, a variável interna errors da classe de domínio será alterada incluindo os atributos que estão com problema. O código abaixo ilustra bem a situação:


dominio.validate()
if (dominio.hasErrors()) {
//danou-se! Há erros aqui!
}

O que pouca gente sabe é que você pode validar apenas alguns campos ao invés de todos se quiser. Como fazer isto? Simples: passe para o método validate() uma lista contendo os nomes dos campos a serem validados, tal como no código abaixo:


dominio.validate(['nome', 'email'])
if (dominio.hasErrors()) {
//danou-se! ou o atributo nome ou o atributo email estão ferrados! Droga!
}

E este atributo errors hein?

Como mencionei, toda classe de domínio possui injetado um atributo chamado errors. Este na realidade é uma instância da interface Errors do Spring. E sabe o que é mais legal nesta interface? Ela tem um método chamado reject implementado de varias formas.

Se for necessário incluir uma validação que vá além das constraints, vamos supor, alguma regra de negócio maluca, você poderia incluir uma validação ALÉM das constraints. Vamos supor que por alguma razão sinistra da vida, você precise adicionar uma validação que não seja feita por constraints (por exemplo, algo a ser validado ANTES do método save ser executado). Você poderia escrever um código como o abaixo:


class DominioMaluco {
def  beforeQualquerCoisa= {
if (condicaoMaluca) {
errors.reject("codigo.do.erro")
}
}
}

No caso, o método reject está adicionando ao atributo errors um novo código de erro. Aquele mesmo tipo de código de erro que incluimos nas mensagens de internacionalização padrão do Grails.

Sugiro que você dê uma lida nesta interface. Ela vai te possibilitar ir BEM além da validação padrão oferecida pelo framework.

Concluindo

Neste post vimos algo além do feijão com arroz que normalmente encontramos sobre o assunto. Como ficou claro, as constraints possuem uma profundidade muito maior que a esperada.

Minha sugestão para quem estiver começando no assunto é a seguinte: tenha nos seus bookmarks o link que expus acima contendo as constraints default do Grails. Estando habituado com estas, estude seu código fonte (é surpreendentemente simples).

Tendo feito isto, parta para as constraints customizadas e, em seguida, caso alguma coisa muito sinistra aconteça, ai sim use e abuse da API de validação do Spring, que é a base em cima da qual as constraints são construídas.

11 thoughts on “Grails: entendendo as validações (constraints)

  1. Cara parabens pelo post… muito interessante, principalmente as constraints globais e compartilhadas que eu desconhecia. Abraços.

    Responda

    admin Reply:

    Opa, valeu!

    Responda

  2. Se eu tivesse conhecido essas constraints globais e compatilhadas antes tinha me poupado muito trabalho.. hehe

    Mais uma vez, muito bom o post.

    Responda

    admin Reply:

    Oi Gregory, fico feliz que tenha lhe sido útil. Valeu!

    Responda

  3. Nossa.. como a maioria dos seus posts.. muito útil!!
    Estou na fase final de um projeto (o primeiro que estou fazendo em grails sozinha)..
    Se soubesse disso antes.. realmente algumas coisas se tornariam muito mais rápidas!

    Obrigada pelas dicas :)

    Responda

    admin Reply:

    Oi Emanuella, fico muito feliz que tenha gostado.
    Valeu!

    Responda

  4. Pq o Grails não usa o Hibernate Validation?

    Responda

    Kico (Henrique Lobo Weissmann) Reply:

    Oi Lucas,
    não sei se a resposta que vou te dar é a melhor pois é baseada em um chute.

    Como o Grails é na realidade um projeto baseado em Spring MVC, talvez fizesse mais sentido usar o Spring Validation no lugar, que foi o que fiezeram.

    Responda

Leave a Reply

Your email address will not be published. Required fields are marked *