Keeping Grails namedQueries DRY

The Named queries support in Grails is cool feature where you define a partial criteria query and can call it directly, or compose it with additional criteria to create complex queries. For example you might want to query for instances created before a specified date:

class Foo {
   String name
   Date dateCreated
   Integer bar

   static namedQueries = {
      createdBefore { date ->
         le 'dateCreated', date
      }
   }
}

Using this we can use just the named query:

def moreThanTwoDaysOld = Foo.createdBefore(new Date() - 2).list()

or further refine the query:

def moreThanTwoDaysOld = Foo.createdBefore(new Date() - 2) {
   lt 'bar', 20
}

But what if you have common named queries across various domain classes? It turns out it’s not hard to keep things DRY, but it’s a bit tricky.


To see how the solution works, it’s important to understand how the definion of the named queries is implemented. It’s a static Closure in a domain class, which contains methods whose names will end up being the names of the queries. This will be more clear if we add in the optional parentheses that were omitted above:

static namedQueries = {
   createdBefore({ date ->
      le 'dateCreated', date
   })
}

So it should hopefully be clear that this is an invocation of the createdBefore method, and its only argument is a Closure containing the various criteria statements. The Grails builder that parses the namedQueries blocks has method-missing support since there’s obviously no createdBefore method, and the method name and argument are used to build a named query. If we’re going to change where the named queries are defined, we need to continue to call these missing methods.

The approach I suggest is to define static methods in a helper class in src/groovy which return the Closures that define the queries. The method names don’t matter, but for consistency it’s best to use the same method name in the utility class as the named query’s name in the domain class:

package com.burtbeckwith.blog

class NamedQueries {

   static createdBefore() {{ date ->
      le 'dateCreated', date
   }}
}

The syntax is a bit weird since the method returns a Closure, so you end up with double braces.

Now update the domain class so the argument of the missing createdBefore method isn’t an inline Closure, but the one that the NamedQueries.createdBefore() method returns:

import com.burtbeckwith.blog.NamedQueries

class Foo {
   String name
   Date dateCreated
   Integer bar

   static namedQueries = {
      createdBefore NamedQueries.createdBefore()
   }
}

Now this named query can be reused in any domain class. The code that uses the queries doesn’t change at all, and if you later decide to modify the logic, you only need to change it in one place instead of every location that you copy/pasted it.


This post is a continuation of my earler Stuff I Learned Consulting post since I used this on that consulting engagement, but I thought that this topic was big enough that it deserved its own post.

3 Responses to “Keeping Grails namedQueries DRY”

  1. Peter Ledbrook says:

    As an addendum, I would recommend documenting something like the double braces, otherwise it looks like a typo without a good understanding of what the code is doing.

  2. jneira says:

    I’ve used “return” keyword to return closures but i didnt know the double brace option.

  3. Jeff Brown says:

    The double brace thing does look a little odd at first glance and some might think it is some kind of trick related specifically to closures, but it really isn’t. I hope this helps and doesn’t confuse things more, but all of the following are doing the same thing…

    class NamedQueries {
     
       static createdBefore() {{ date ->
          le 'dateCreated', date
       }}
    }
    
    
    class NamedQueries {
     
       static createdBefore() {
           { date ->
               le 'dateCreated', date
           }
       }
    }
    
    
    class NamedQueries {
     
       static createdBefore() {return { date ->
          le 'dateCreated', date
       }}
    }
    
    
    class NamedQueries {
     
       static createdBefore() {
           return { date ->
               le 'dateCreated', date
           }
       }
    }
    
    
    class NamedQueries {
     
       static createdBefore() {
           def theClosure = { date ->
               le 'dateCreated', date
           }
           theClosure
       }
    }
    
    
    class NamedQueries {
     
       static createdBefore() {
           def theClosure = { date ->
               le 'dateCreated', date
           }
           return theClosure
       }
    }
    
Creative Commons License
This work is licensed under a Creative Commons Attribution 3.0 License.