Written on
Data Binding and One-To-Many Associations
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])
}
}
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)
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

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);
}
// ...
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);
// ...
<g:select name="blogPosts" from="${org.ast.domain.BlogPost.list()}" multiple="yes" optionKey="id" size="5" value="${blogInstance?.blogPosts}" />
properties
property can be used to automatically bind one-to-many associations.
blog.properties = params
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 theGrailsDataBinder
automatically associates objects due to the given identifiers.