Fletch
Fletch

Reputation: 5249

Can I append a closure to another in Groovy?

I have two very similar methods in Grails, something like "calculate statistics by os" and "calculate statistics by browser" - effectively both prepare some things, then run a similar query on the DB, then do things with the results. The only part where the methods differ is the query they run in the middle of my method -

def summary = c.list {
    eq('browser', Browser.get(1)) // OR eq('os', OS.get(1))
    between('date', dates.start, dates.end)
}

It occurred to me that the ideal way to refactor it would be to pass in the first line of the closure as a method parameter. Like

doStats (Closure query) {
    ...
    def summary = c.list {
        query
        between('date', dates.start, dates.end)
    }
}

I tried this but "query" gets ignored. I tried query() instead but then the query clause is executed where defined, so this doesn't work either. I suppose I could just pass the whole closure as a parameter but that seems wrong - the query might also get more complicated in future.

Anyone have any better ideas?

Upvotes: 7

Views: 5310

Answers (3)

Szymon Stepniak
Szymon Stepniak

Reputation: 42262

I found leftShift operator useful for composing closure from two separate ones. What you can do is:

Closure a = { /*...*/ }
Closure b = { /*...*/ }
Closure c = a << b

Take a look at this example:

def criteria = {
    projection Projections.distinct(Projections.property('id'))
    and {
        eq 'owner.id', userDetails.id

        if (filter.groupId) {
            eq 'group.id', filter.groupId
        }
    }
}

List<Long> ids = Contact.createCriteria().list(criteria << {
    maxResults filter.max
    firstResult filter.offset
})

Integer totalCount = Contact.createCriteria().count(criteria)

What you can see here is that I'm creating a criteria for listing ant counting GORM objects. Criterias for both cases are almost the same, but for listing purposes I also need to include limit and offset from command object.

Upvotes: 5

Tomas Lin
Tomas Lin

Reputation: 3532

You're using the criteria DSL which might be different than plain groovy closures.

To do what you're asking, you can use the method described here -

http://mrhaki.blogspot.com/2010/06/grails-goodness-refactoring-criteria.html

and put your query in to private method.

The more elegant solution for this is to use named queries in grails -

http://grails.org/doc/latest/ref/Domain%20Classes/namedQueries.html

Look at the

  recentPublicationsWithBookInTitle {
       // calls to other named queries…
       recentPublications()
       publicationsWithBookInTitle()
  }

example -

Upvotes: 3

tim_yates
tim_yates

Reputation: 171194

Not sure about with the Grails Criteria builder, but with other builders, you can do something like:

doStats (Closure query) {
    def summary = c.list {
        query( it )
        between('date', dates.start, dates.end)
    }
}

And call this via:

def f = { criteria ->
    criteria.eq( 'browser', Browser.get( 1 ) )
}
doStats( f )

If not, you're probably best looking at named queries like tomas says

Upvotes: 3

Related Questions