data-bindinggrailsgroovycommand-objects

Grails command object data binding


Grails has very good support for binding request parameters to a domain object and it's associations. This largely relies on detecting request parameters that end with .id and automatically loading those from the database.

However, it's not clear how to populate the associations of a command object. Take the following example:

class ProductCommand {

    String name
    Collection<AttributeTypeCommand> attributeTypes 
    ProductTypeCommand productType
}

This object has a single-ended association with ProductTypeCommand and a many-ended association with AttributeTypeCommand. The list of all attribute types and product types are available from an implementation of this interface

interface ProductAdminService {
    Collection<AttributeTypeCommand> listAttributeTypes();
    Collection<ProductTypeCommand> getProductTypes();
}

I use this interface to populate the product and attribute type selection lists in a GSP. I also dependency-inject this interface into the command object, and use it to "simulate" attributeTypes and productType properties on the command object

class ProductCommand {

    ProductAdminService productAdminService

    String name   

    List<Integer> attributeTypeIds = []
    Integer productTypeId

    void setProductType(ProductTypeCommand productType) {
        this.productTypeId = productType.id
    }

    ProductTypeCommand getProductType() {
        productAdminService.productTypes.find {it.id == productTypeId}        
    }

    Collection<AttributeTypeCommand> getAttributeTypes() {

        attributeTypeIds.collect {id ->
            productAdminService.getAttributeType(id)
        }
    }

    void setAttributeTypes(Collection<AttributeTypeCommand> attributeTypes) {
        this.attributeTypeIds = attributeTypes.collect {it.id}
    }
}

What actually happens is that the attributeTypeIds and productTypeId properties are bound to the relevant request parameters and the getters/setters "simulate" productType and attributeTypes properties. Is there a simpler way to populate the associations of a command object?


Solution

  • Do you actually need to have sub-commands for attributeTypes and productType properties? Any reason you're not using PropertyEditorSupport binding? E.g.:

    public class ProductTypeEditor extends PropertyEditorSupport
    {
        ProductAdminService productAdminService // inject somewhow
        void setAsText(String s)
        {
            if (s) value = productAdminService.productTypes.find { it.id == s.toLong() }
        }
    
        public String getAsText()
        {
            value?.id        
        }
    }
    

    (and something similar for attributeType object), and register these in a editor registrar:

    import java.beans.PropertyEditorSupport
    public class CustomEditorRegistrar implements PropertyEditorRegistrar {
        public void registerCustomEditors(PropertyEditorRegistry reg) {
            reg.registerCustomEditor(ProductType, new ProductTypeEditor())
            reg.registerCustomEditor(AttributeType, new AttributeTypeEditor())
        }
    }
    

    And register in your resources.groovy:

    beans =
    {
        customEditorRegistrar(CustomEditorRegistrar)
    }
    

    then in your Cmd you just have:

    class ProductCommand {
        String name
        List<AttributeType> attributeTypes = []
        ProductType productType
    }
    

    If you do need actual sub-command associations then I've done something similar to what @Andre Steingress has suggested, in combination with PropertyEditorSupport binding:

    // parent cmd
    import org.apache.commons.collections.ListUtils
    import org.apache.commons.collections.FactoryUtils
    public class DefineItemConstraintsCmd implements Serializable
    {
        List allItemConstraints = ListUtils.lazyList([], FactoryUtils.instantiateFactory(ItemConstraintsCmd))
        //...
    }    
    // sub cmd
    @Validateable
    class ItemConstraintsCmd implements Serializable
    {
        Item item // this has an ItemEditor for binding
        //...
    }
    

    Hopefully I've not misunderstood what you're trying to achieve :)