Ad-Hockery

ad-hockery: /ad·hok'@r·ee/, n.
Gratuitous assumptions... which lead to the appearance of semi-intelligent behavior but are in fact entirely arbitrary. Jargon File

Acyclic relationship validation in Grails

A common domain class use-case is for a self-referential relationship that must not be circular (a directed acyclic graph). For example, a Person class may have a parent property that is a many-to-one relationship with another Person instance. However a given Person cannot be his own parent or ancestor.

We’re using just such a relationship for trees of pages that inherit certain characteristics from their ancestors. In order to validate that our users aren’t setting up circular references I implemented the following constraint:

import org.codehaus.groovy.grails.validation.AbstractConstraint
import org.springframework.validation.Errors

class AcyclicConstraint extends AbstractConstraint {

 static final String DEFAULT_MESSAGE_CODE = "default.acyclic.violation.message"
 static final String NAME = "acyclic"

 private boolean validateConstraint

 protected void processValidate(Object target, Object propertyValue, Errors errors) {
  if (validateConstraint && propertyValue) {
   if (isCyclic(target, propertyValue)) {
    def args = [constraintPropertyName, constraintOwningClass, propertyValue] as Object[]
    rejectValue(target, errors, DEFAULT_MESSAGE_CODE, "${NAME}.violation", args)
   }
  }
 }

 void setParameter(Object constraintParameter) {
  if (!(constraintParameter instanceof Boolean)) {
   throw new IllegalArgumentException("Parameter for constraint [$NAME] of property [$constraintPropertyName] of class [$constraintOwningClass] must be a boolean value")
  }
  validateConstraint = constraintParameter
  super.setParameter(constraintParameter)
 }

 boolean supports(Class type) { true }

 String getName() { NAME }

 private boolean isCyclic(original, node) {
  boolean cyclic = false
  while (node != null) {
   if (node.id == original.id) {
    cyclic = true
    break
   }
   node = node."$propertyName"
  }
  return cyclic
 }
}

This is also available as a Gist.

Then I just needed to register my new constraint by adding ConstrainedProperty.registerNewConstraint(AcyclicConstraint.NAME, AcyclicConstraint) to grails-app/conf/spring/resources.groovy. Using the constraint is as simple as this:

class Person {

    String name
    Person parent

    static constraints = {
        parent acyclic: true
    }
}

The constraint can be mixed with others such as nullable: true. The really nice thing is that the constraint implementation doesn’t reference any of my domain classes directly, meaning it can be re-used in any other domain class.

When you’re using similar validation logic in multiple classes defining a constraint like this is a much better option than using a validator closure and like many things in Grails it turns out to be pretty easy to implement.

Web Statistics