Thursday, August 19, 2010

Grails UrlMappings - mutually exclusive patterns

Let's say, I have URL patterns that are mutually exclusive and have to map to 2 different controllers. For instance, if I have an enum Vals with the following values: ROCK, PAPER, SCISSORS, and I want URLs like

/root/group/rock (or ROCK)
/root/group/paper (or PAPER) 
/root/group/scissors (or SCISSORS) to map to YouWinController

and

/root/group/(everything-else-but-ROCK|rock|PAPER|paper|SCISSORS|scissors) to map to YouLoseController


Both URL patterns are very similar, because each one consists of 3 strings separated by '/'. We can start by defining some constraints, but at the same time we have to define the exclusive pattern first. In this case, exclusive pattern is the one mapping to YouLoseController.

class UrlMappings {
    static mappings = {
        "/root/group/$loseParam"(controller: "youLose", action: "/loseAction")
        "/root/group/$winParam"(controller: "youWin", action: "/winAction") {
            constraints {
                  // get all string values from the enum
                  def list = stringList(Vals) 
                  // make sure lowercase values are also included
                  list.addAll(list*.toLowerCase()) 
                  $winParam(inList: list)
            }
        }
    }
}

Wednesday, August 18, 2010

Criteria queries don't work properly with Grails Enums

Looks like Grails (as of version 1.3.1) still didn't fix all the issues with Enums.

I'm using a criteria query like this:

c = MyDomainClass.createCriteria()
def list = c.list {
    and {
        classAttr {
            eq ("attr1", param1)
        }
        eq ("enumAttr", param2)
    }
} 

In this case, MyDomainClass.enumAttr represents an enum. It looks something like this:

enum CountryEnum {
    AUSTRIA("austria")
    BRAZIL("brazil")
    CANADA("canada")

    String name

    CountryEnum(String name) {
        this.name = name
    }
}

Grails, quite wisely, stores 'AUSTRIA', 'BRAZIL', and 'CANADA' in the DB.
However, the above criteria query generates the following error:
java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Enum

There a little section about Enums in Grails 1.1 release notes, which mentions that the value attribute should now be named 'id'. However switching 'name' to 'id' in my CountryEnum simply got rid of the exception, but the 'list' was still empty after the query completed. Extracting SQL from Hibernate debug output produced valid results, which means the problem lies somewhere in the Hibernate layer. Also DB values became 'austria', 'brazil', and 'canada', which are the values of 'id'.

Dynamic finders seem to handle Enums pretty well, but evidently the problem still exists when using Criteria or HQL.

Tuesday, August 17, 2010

Grails: using Groovy MarkupBuilder to render properly indented XML

When using Grails, the easiest way to render XML is to use 
 render obj as XML
command. However, that returns the entire XML on one line.

If you want XML to look pretty, you can use groovy.xml.MarkupBuilder

def showXml = {
    def writer = new StringWriter() 
    new MarkupBuilder(writer).root {
        child1 {
            child2('someValue')
        }       
    }

    render(text: writer.toString(), contentType: "text/xml")
}

This will produce a neat output where each tag is on a new line and properly indented.