Grails: Querying across associations

November 24, 2009 9:45 am

Another nerdy post. Grails is a pretty good framework. I'm a python guy, so I prefer Django, but when forced to use Java-like things Grails is better than the alternative. However, it's still young. Django and Grails are both currently on their 1.1.1 releases, but Django is much more mature for its age.

In Django it's really easy to query across related objects (they're called "related objects" in Django and "associations" in Grails). Grails is still struggling with this. (Grails is also struggling with good, in-depth documentation, but that's not the purpose of this post.)

After much searching all I could find was some forum posts by the project lead of Grails, Graeme Rocher, from 2007 saying that nested associations aren't currently (as of 2007) supported.

Nested Associations: Suppose I have 3 classes: Person, Family, and Country. Suppose the classes are designed such that each person belongs to a family and each family is linked to an origin country. Now suppose you want to get a list of all persons whose family is from England. Persons don't have a direct link to that information, so you'd need to hop through the family to get to the country.

Based on the current setup you'd expect to be able to do something like:

Person.withCriteria {
family {
country {
eq("name", "England")
}
}
}

And you can. So for anyone searching for how to do this and finding that old post from 2007 saying you can't: it's wrong. You can.

But now let's pickup where I left off in my previous post with separating out query pieces for re-usability and adherence to the DRY principle.

We need to build a criteria object specifically and separate out the criteria to a separate closure:

def someView = {
def critBuilder = Person.createCriteria()
def critClosure = { filterByEngland.curry(critBuilder)() }
def results = critBuilder.list(max:params.max, offset:params.offset, critClosure)
def totalCount = results.totalCount
}

def filterByEngland = {critBuilder ->
critBuilder.family {
critBuilder.country {
eq("name", "England")
}
}
}

And now we can combine that with other pieces of modularized code. I have my queries broken up so that I can easy sort using different functions based on what kind of output the data is going to be used in. So you can have something like this:

def someView = {
def critBuilder = Person.createCriteria()
def critClosure = {
filterByEngland.curry(critBuilder)()
sortForCSV.curry(critBuilder)()
// sortForXML.curry(critBuilder)()
}
def results = critBuilder.list(max:params.max, offset:params.offset, critClosure)
def totalCount = results.totalCount
[results: results, totalCount: totalCount]
}

def filterByEngland = {critBuilder ->
critBuilder.family {
critBuilder.country {
eq("name", "England")
}
}
}

def sortForCSV = {critBuilder ->
critBuilder.order("lastName", "asc")
critBuilder.order("firstName", "asc")
critBuilder.order("age", "asc")
}

def sortForXML = {critBuilder ->
critBuilder.family {
critBuilder.country {
order("name", "asc")
}
order("id", "asc")
}
}


Since this nested association querying isn't documented anywhere (that I could find) and the only mention is that it _doesn't_ work, it was a pain in the butt figuring it out.

Other gripes with Grails. I can't define a relation to another class unless it is based on the primary_key of the classes. A less-than-usual case for sure, but there really isn't any good reason to disallow such a situation.

Leave a Reply

Your email address will not be published. Required fields are marked *