Tuesday, 10 February 2009

Binding to collection fields on command objects

One of the cool new features of Grails 1.1 is the ability to bind form elements to collection properties of domain objects. Unfortunately it doesn't quite work out of the box for command objects.

Suppose I have a command class like this:

    class MyCommand {
Map things = [:]
}

and my form posts params like this:

    things[key1] = value1
things[key2] = value2
things[key3] = value3

I'd hope that the binder could cope with this and populate my command object's things property with the appropriate keys and values. It doesn't work as it seems that Grails attempts to figure out what the type of the object contained in the collection is. For domain objects this is possible since the hasMany closure can be used. Command objects don't have this available and so you end up with a nasty stack trace boiling down to a NullPointerException.

However there is a way to 'fool' the data binder. All it's trying to do is figure out the value type for the Map so it can use an appropriate PropertyEditor to decode the HTTP request parameter value. In this example the value is just a String. Initializing the Map like this provides a workaround:

    import org.apache.commons.collections.MapUtils
import org.apache.commons.collections.FactoryUtils

class MyCommand {
Map things = MapUtils.lazyMap([:], FactoryUtils.constantFactory(''))
}

That's using a LazyMap from Commons Collections which (in case you can't guess) is a Map implementation that populates a default value when get is called for an unmapped key. The Map in the command object will use the empty string as its default value which is enough for the Grails data binder to figure out the value type and bind our parameters.

I haven't tried the same trick with richer data types yet (e.g. a collection of domain objects in a command) but I don't think there's any reason why a similar workaround returning a different value from the Factory shouldn't work.

No comments: