12

My Grails application has a large number of enums that look like this:

public enum Rating {
    BEST("be"), GOOD("go"), AVERAGE("av"), BAD("ba"), WORST("wo")
    final String id

    private RateType(String id) {
        this.id = id
    }

    static public RateType getEnumFromId(String value) {
        values().find {it.id == value }
    }   
}

If I have a command object such as this:

class MyCommand {
    Rating rating
}

I would like to (for example) automatically convert a request parameter with value "wo" to Rating.WORST.

The procedure for defining custom converters is described here (in the context of converting Strings to Dates). Although this procedure works fine, I don't want to have to create a class implementing PropertyEditorSupport for each of my enums. Is there a better alternative?

Community
  • 1
  • 1
Dónal
  • 185,044
  • 174
  • 569
  • 824

2 Answers2

12

I found a solution I'm pretty happy with.

Step 1: Create an implementation of PropertyEditorSupport to convert text to/from the relevant Enum

public class EnumEditor extends PropertyEditorSupport {

    private Class<? extends Enum<?>> clazz

    public EnumEditor(Class<? extends Enum<?>> clazz) {
        this.clazz = clazz
    }

    public String getAsText() {
        return value?.id
    }

    public void setAsText(String text) {
        value = clazz.getEnumFromId(text)
    }
}

Step 2: Define a class that registers EnumEditor as a converter for the various enum classes. To change the list of enum classes that are bindable by id, just modify BINDABLE_ENUMS

public class CustomPropertyEditorRegistrar implements PropertyEditorRegistrar {

    private static final String REQUIRED_METHOD_NAME = 'getEnumFromId'

    // Add any enums that you want to bind to by ID into this list
    private static final BINDABLE_ENUMS = [Rating, SomeOtherEnum, SomeOtherEnum2]

    public void registerCustomEditors(PropertyEditorRegistry registry) {            

        BINDABLE_ENUMS.each {enumClass ->
            registerEnum(registry, enumClass)
        }
    }

    /**
     * Register an enum to be bound by ID from a request parameter
     * @param registry Registry of types eligible for data binding
     * @param enumClass Class of the enum
     */
    private registerEnum(PropertyEditorRegistry registry, Class<? extends Enum<?>> enumClass) {

        boolean hasRequiredMethod = enumClass.metaClass.methods.any {MetaMethod method ->
            method.isStatic() && method.name == REQUIRED_METHOD_NAME && method.parameterTypes.size() == 1
        }

        if (!hasRequiredMethod) {
            throw new MissingMethodException(REQUIRED_METHOD_NAME, enumClass, [String].toArray())
        }
        registry.registerCustomEditor(enumClass, new EnumEditor(enumClass))
    }
}

Step 3: Make Spring aware of the registry above by defining the following Spring bean in grails-app/conf/spring/resources.grooovy

customPropertyEditorRegistrar(CustomPropertyEditorRegistrar)
Dónal
  • 185,044
  • 174
  • 569
  • 824
  • Good job! I'm struggling with the same problem. Why the hell is this not a standard part of Grails? Grails' support for binding request parameters to domain/command objects is really awful. – mcv Dec 17 '09 at 18:40
  • 3
    Grails does support binding request parameters to enums, but the default binds by name. If you want to bind by some other enum property (e.g. by id, as above), you need to define the binding yourself – Dónal Sep 30 '11 at 08:12
11

So the default Databinding binds on the Enum name and not a separately defined property of the Enum. You can either create your own PropertyEditor as you have mentioned or do a work-around similar to this:

class MyCommand {
 String ratingId
    Rating getRating() {
     return Rating.getEnumFromId(this.ratingId)
    }
    static constraints = {
     ratingId(validator:{val, obj -> Rating.getEnumFromId(val) != null })
    }
}
Colin Harrington
  • 4,459
  • 2
  • 27
  • 32