Friday, November 13, 2009

Grails, validation, constraints, oh my!

I must admit that I am continually impressed by the ease with which grails makes previously tedious tasks simple and easy. In my current project I found it necessary to enforce a constraint that a date be in the future. My first reaction was along the lines of "no problem, I'll just validate the data in the business tier", and then luckily discovered a much more elegant solution in grails: constraints.

The grails reference defines a constraint as "Allows the definition of declarative validation constraints". Grails comes built in with numerous constraints that fit most day to day needs: url, email, credit card, and many more! To define a set of constraints you only need to define a constraints block.

For example, imagine you need a "User" domain object, requiring a "name", and "email" properties. Our business rules dictate that the name property must not be blank, be between 3 and 30 characters, and the email address provided must be a valid email address. This class (and the required validation) would look like the following:

class User {
    static constraints = {
        name(size:3..30, blank: false)
        email(email: true)
    }

    String name
    String email
}

If these constraints are not enough you are provided with the ability to utilize a closure and write anything you need. Here is an example of a custom validator:

class User {
    String login
    static constraints = {
        login(validator: {
            if (it.contains('admin')) return ['invalid.login']
        })
    }
}

If you wish to define a constraint that can be used across classes you can implement the Constraint interface. I chose to extend AbstractConstraint which takes care of some of the underlying boilerplate code that would need to be written. Here is a fictional example which defines an isFred constrains which is applied to String values, and validates that the provided string does not contain "fred".

class IsFredConstraint extends AbstractConstraint {

    private boolean fred;

    public boolean supports(Class type) {
        return type != null && String.class.isAssignableFrom(type);
    }

    public void setParameter(Object constraintParameter) {
        if (!(constraintParameter instanceof Boolean))
            throw new IllegalArgumentException("Parameter for constraint [isFred] of property [" + constraintPropertyName + "] of class [" + constraintOwningClass + "] must be a boolean value");

        this.fred = ((Boolean) constraintParameter).booleanValue();
        super.setParameter(constraintParameter);
    }

    public String getName() {
        return "isFred";
    }

    protected void processValidate(Object target, Object propertyValue, Errors errors) {
        if (fred) {
            String value = propertyValue.toString();
            def args = (Object[]) [constraintPropertyName, constraintOwningClass, propertyValue]

            if (value.contains("fred")) {
                super.rejectValue(target, errors, "invalid.fred", "not.isFred", args);
            }
        }
    }
}

Any file ending in "Constraint" will auto-magically be added as a constraint so name the above file "FredConstraint.groovy" and place it in the /grails-app/utils directory. It can then be used like so:
static constraints = {
      login(isFred:true)
    }

This is my first foray into grail validation, so if there is a better approach please let me know in the comments!

2 comments:

  1. You might also want to check out this plugin, which came out a few weeks ago.

    http://grails.org/plugin/constraints

    It makes defining constraints even easier.

    ReplyDelete
  2. We had some problems with constraints and subclasses. It is a know bug: http://jira.codehaus.org/browse/GRAILS-1623

    ReplyDelete