Home > grails > Data Binding and One-To-Many Associations

Data Binding and One-To-Many Associations


Flattr this
Data Binding in Grails is referred to as the process of binding incoming request data, typically only available as key/value string pairs, to some domain object.

Let’s take a look at the BlogController’s save closure out of an example project I’ve been showing at the last Austrian EJUG meeting [0]:

    def save = {
        def blogInstance = new Blog(params)
        if (blogInstance.save(flush: true)) {
            flash.message = "${message(code: 'default.created.message', args: [message(code: 'blog.label', default: 'Blog'), blogInstance.id])}"
            redirect(action: "show", id: blogInstance.id)
        }
        else {
            render(view: "create", model: [blogInstance: blogInstance])
        }
    }

The second line in the save closure creates an instance of the Blog class. As you might already know, creating objects can be done in multiple ways when working with Groovy. In this case, we use Groovy’s feature to invoke the “default constructor” and bind the given map containing property values immediately to the fresh instance.

def blogInstance = new Blog(params)

However, when updating an already persisted blog instance we need to use a mechanism provided by Grails: the properties property.

You can use the properties property to assign a map of key/value pairs to a domain class instance, thus updating the object. By the way: Grails uses Spring’s data binding capabilities to perform data binding [1]. This means that you can use all the possibilities provided by Spring, e.g. defining custom PropertyEditor classes.

So far so good. But what happens when it comes to binding many-to-one or one-to-one associations? The answer is pretty simple: it just works [2]. GrailsDataBinder even supports lazy object instantiation of domain classes. Whenever the other side of the association does not exist, it is created by Grails and populated with the given request parameter values:

/blog/save?title=Bla&author.name=John%20Doe

But what happens when working with unidirectional one-to-many associations?

In my EJUG example we had such a one-to-many association between a Blog and multiple BlogPost instances. The design decision was to keep all domain class relationships as lean as possible, therefore we decided that a relationship between a blog post and its blog is not necessary, as the Blog is a domain aggregate [3].

A bit of digging into Grails’ GrailsDataBinder revealed method bindCollectionAssociation:

private void bindCollectionAssociation(MutablePropertyValues mpvs, PropertyValue pv) {
  Object v = pv.getValue();

  Collection collection = (Collection) bean.getPropertyValue(pv.getName());
  collection.clear();
  final Class associatedType = getReferencedTypeForCollection(pv.getName(), getTarget());
  final boolean isArray = v != null && v.getClass().isArray();
  final PropertyEditor propertyEditor = findCustomEditor(collection.getClass(), pv.getName());

  // ...
  if(isArray) {

    Object[] identifiers = (Object[])v;
    for (Object id : identifiers) {
      if (id != null) {
        associateObjectForId(pv, id,associatedType);
      }
    }

    mpvs.removePropertyValue(pv);
  }
// ...

As you can see in the code snippet above, whenever the given property value v is an array, it assumes that it is an array of identifiers. Than it iterates over the identifiers and tries to associate an object for the current identifier with the associated type retrieved via getReferencedTypeForCollection.

In associateObjectForId it uses MOP programming to invoke the associations addTo* method:

// ...
MetaClassRegistry reg = GroovySystem.getMetaClassRegistry();
MetaClass mc = reg.getMetaClass(target.getClass());
final String addMethodName = "addTo" + GrailsNameUtils.getClassNameRepresentation(name);
mc.invokeMethod(target, addMethodName,obj);
// ...

Given a one-to-many association this means that we can simply use Grails default data binding functionality to bind this type of associations. In the view we could use a multi select box to map all selected blog post identifiers to a single request parameter:

<g:select name="blogPosts" from="${org.ast.domain.BlogPost.list()}" multiple="yes" optionKey="id" size="5" value="${blogInstance?.blogPosts}" />

As a result, the properties property can be used to automatically bind one-to-many associations.

blog.properties = params

The only requirement is the array type which is important to notice when constructing the identifiers list manually:

params['blogPosts'] = someList as String[]
blog.properties = params

Conclusion

Data Binding in Grails is based on Spring’s validation and data binding mechanisms. When binding unidirectional one-to-many associations the GrailsDataBinder automatically associates objects due to the given identifiers.

[0] Grails Talk at the Austrian EJUG meeting in June ’10
[1] Spring Data Binding and Validation Documentation
[2] Data Binding – Grails Documentation
[3] Domain Driven Design Patterns: The Aggregate

Categories: grails
  1. July 2, 2010 at 12:32 pm

    You need to watch out for some gotchas. Behaviour when binding Sets vs Lists varies depending on Grails versions. Sets in 1.3.1 do not bind with array subscript notation – but are documented as meant to be.

    Also careful when binding to a collection where the members are cascade orphan – they may be deleted when the collection is cleared and re-assigned. Also if they nave non-nullable backrefs to the owner you may experience pain.

    • July 2, 2010 at 12:57 pm

      marc, thanks for mentioning these gotchas!

      GrailsDataBinder clears the collection as one of the very first actions (as shown in the code sample), programmers using association data binding need to be aware of that fact.

    • Sub
      August 2, 2010 at 12:24 am

      “Sets in 1.3.1 do not bind with array subscript notation – but are documented as meant to be.”

      Hi Marc, would you elaborate? If Set’s don’t make use of array subscript notation for binding – how does it work?

  1. No trackbacks yet.

Leave a comment