Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Making Grails controllers more DRY?

I am looking for ways on how to cleanup my Grails controller code. In various controllers i more or less have the same logic..

  • get the object
  • check if it exists
  • etc..

Is there a suggested way on making controller actions reuse common code?

--- solution ---

All answers to the question have contributed to the solution we have implemented.

We created a class that is used in our controllers using the Mixin approach. One of the methods that the mixin exposes is the withObject method. This method takes the domainname from the controller and uses this a base for the method. This behaviour can be overridden of course!

def withObject(object=this.getClass().getName()-"Controller", id="id", Closure c) {
    assert object
    def obj =  grailsApplication.classLoader.loadClass(object).get(params[id])
    if(obj) {
        c.call obj
    } else {
        flash.message = "The object was not found"
        redirect action: "list"
    }
}

So all answers have contributed to the solution! Thanks a lot!

like image 505
Marco Avatar asked Nov 25 '11 08:11

Marco


1 Answers

I always pull out this blog post when this question comes up:

http://mrpaulwoods.wordpress.com/2011/01/23/a-pattern-to-simplify-grails-controllers/

Basically you have a private helper for various domains in your controllers.

private def withPerson(id="id", Closure c) {
    def person = Person.get(params[id])
    if(person) {
        c.call person
    } else {
        flash.message = "The person was not found."
        redirect action:"list"
    }
}

The way you code the getter is very flexible and a typical use for me (that is not covered in the blog) is for editing etc.

I normally code this way (i like the pattern for its clear division and readability):

 def editIssue() {
    withIssue { Issue issue ->
        def issueTypes = IssueTypeEnum.values().collect {it.text }
        [issueTypes:issueTypes,activePage:"issue", issue: issue]
    }
}

 def doEditIssue(IssueCommand cmd) {
    if(cmd.validate()) {
        withIssue { Issue issue ->
            issue.updateIssue(cmd)
            redirect(action: "show", id: issue.id)
        }
    }
    else {
        def issueTypes = IssueTypeEnum.values().collect {it.text }
        render(view: "edit", model:[issueTypes:issueTypes,issue:cmd,activePage:"issue"])
    }
}

With my getter helper being:

private def withIssue( Closure c) {
    def issue = Issue.get(params.id)
    if(issue) {
        c.call issue
    }
    else {
        response.sendError(404)
    }
}

I do think that the mixin method (very similar to the 'extend a common abstract controller' way) is nice too, but this way gives two advantages:

  1. You can type the helper, like you see I do in the closure giving you access to the methods etc in STS/IDEA (not tested Netbeans)
  2. The repetition is not very high, and the ability to change the getter (to use for example BarDomain.findByFoo(params.id) etc)

In the view I bind to edit() I just put an id="${issue.id}" in the <g:form> and it works seamlessly.

like image 79
Oliver Tynes Avatar answered Nov 15 '22 21:11

Oliver Tynes