When you're writing code, Don't Repeat Yourself. Now say that 5 times. *rimshot*
One of the things that I find myself repeating a lot of in many business apps is queries. It's common to have a rule or filter that applies to many different cases. I came across such a situation recently and wanted to figure out a way to share that filter across many different queries. This is what I came up with for keeping those Criteria DRY.
To start with, I'll use an example of an Article. This could be a blog post or a newspaper article. One of the rules of the system is that Articles need to be published before they are visible by end users. Because of this seemingly simple rule, every time we query for Articles, we will need to check the published flag. If you get a lot of queries, that ends up being a lot of repetition.
Here's our example domain class:
Now we need to add a query that will retrieve our domain instance by its slug (a slug is a publishing term for a short name given to an article, in the web world it has become a term often used for a search engine optimization technique that uses the title instead of an artificial ID). To perform that query we might write something like this on the Article class:
We want to query based on the slug, but we also want to only allow a published Article to be shown. This would allow us to unpublish an article if necessary. Without the approved filter, if the link had gotten out, people could still view the article.
Next we decide we want to list all of the Articles in a particular category so we write something like this, again filtering by the approved flag.
Two simple examples like this might not be that big of a deal. But you can easily see how this would grow if you added more custom queries or if you had some more complicated filtering logic. Another common case would be if you had the same filter across many different domain objects. (What if the Article had attachments and comments all of which needed their own approval?) What you need is a way to share that logic among multiple withCriteria calls.
The trick to this is understanding how withCriteria and createCriteria work in GORM. They are both implemented using a custom class called HibernateCriteriaBuilder. That class invokes the closures that you pass to it on itself. Sounds confusing. Basically the elements in the closure of your criteria queries get executed as if the were called on an instance of HibernateCriteriaBuilder.
would be the equivalent of calling something like:
That little bit of knowledge allow you to reach into your meta programming bag of tricks and add new calls to the HibernateCriteriaBuilder. Every Class in groovy has a metaClass that is used to extend types of that Class. In this case we'll add a Closure that will combine our criteria with other criteria like so:
This ands together our eq call with all of the other parts of the passed in closure. Now we can put the whole thing together into a domain class with a reusable filter.
And there you have it. Do you have any other techniques that can be used to DRY criteria?