Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 614
Fletch Avatar asked Jan 17 '11 14:01

Fletch


People also ask

What is -> in Groovy?

It is used to separate where you declare bindings for your closure from the actual code, eg: def myClosure = { x, y -> x + y } the part before -> declares that the closure has two arguments named x and y while the second part is the code of the closure.

What is the role of closure and listeners in Groovy?

It references the variables which are declared in its surrounding scope. We know that anonymous inner classes are not supported by Groovy. Inline listeners can be determined with the help of closures. In groovy, listener closures are used as listener adapters.

How do I return a Groovy closure?

We can even return closures from methods or other closures. We can use the returned closure to execute the logic from the closure with the explicit call() method or the implicit syntax with just the closure object followed by opening and closing parentheses (()).


3 Answers

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.

like image 163
Szymon Stepniak Avatar answered Nov 09 '22 19:11

Szymon Stepniak


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 -

like image 28
Tomas Lin Avatar answered Nov 09 '22 18:11

Tomas Lin


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

like image 35
tim_yates Avatar answered Nov 09 '22 18:11

tim_yates