How do you share common methods in different grails controllers?
Asked Answered
C

5

26

Currently when I need to share a method like processParams(params) between different controllers, I use either inheritance or services. Both solution has some inconvenients :

  • With inheritance, you cannot use multiple inheritance which means that you need to have all of your controller utility methods in one place. And also, there is a bug in grails that does not detect any code changes in Base Controller classes in development mode (you need to restart the app)
  • With services, you don't have access to all injected properties like params, session, flush...

So my question is : is there any other way to use some common methods accessible for multiple controllers ?

Charlatanry answered 16/11, 2010 at 15:0 Comment(0)
L
22

One option I like is to write the common methods as a category, then mix it into the controllers as necessary. It gives a lot more flexibility than inheritance, has access to stuff like params, and the code is simple and understandable.

Here's a tiny example:

@Category(Object)
class MyControllerCategory {
    def printParams() {
        println params
    }
}

@Mixin(MyControllerCategory)
class SomethingController {

    def create = {
        printParams()
        ...
    }

    def save = {
        printParams()
    }
}
Lutes answered 16/11, 2010 at 17:26 Comment(5)
Actually, you don't need the @Category annotation to use the @Mixin annotation. - You'd only need it when using the alternative method SomethingController.mixin([MyControllerCategory]) (which requires MyControllerCategory to provide a static method printParams()). The Groovy docs provide samples similar to yours, but this isn't necessary. - This misunderstanding might be due to the fact that the usage of categories has been undergoing an evolutionary process - once being clumsy by syntax, and now being superseded by the @Mixin annotation.Sestina
Finally, the major difference between @Mixin and @Delegate is that delegate methods are statically compiled into the target class whereas mixin methods are invoked at runtime.Sestina
Is there any way to make MyControllerCategory subject to dependency injection?Veradis
If you use this pattern, and run into issues testing using Spock, wherein the mixed in class is reset between Spock tests, check out a solution here: linkNutshell
traits are replacing @Mixin and @Mixin is deprecated with Groovy 2.3. More about traits: #23122390 & mscharhag.com/groovy/groovy-introduces-traitsKoestler
P
3

This doesn't help the restarting in development mode issue you have, but it's the way I've solved this problem. It's ugly and probably not good practice, but I factor common code into classes as closures. Then I can do something like:

new ControllerClosures().action(this)

and from with in the controllerClosures class

def action={
    it.response.something
    return [allYourData]
}
Pecuniary answered 16/11, 2010 at 15:9 Comment(0)
T
3

Common functionality is a call for a new class, not necessarily common ancestor. The question formulation is missing responsibility statement for it. Needless to say, it's a single responsibility that we create a new class for. I take further decisions basing on class responsibility.

I prefer a hybrid of robbbert's and Jared's answers: I construct extra classes, passing them necessary controller internals as parameters. Sometimes the classes develop from method objects. Like:

def action = {
  def doer = SomeResponsibilityDoer(this.request, this.response)
  render doer.action()
}

Not so brief, but lets you get code under tests and keep coupling low.

As SomeResponsibilityDoer is only going to have couple of fields - request an response - it's not a big deal constructing it with every request.

It's also not a big deal having SomeResponsibilityDoer not reloaded on controller change in dev, because:

  1. Initially, you can declare it in some of Controller files - it will be reloaded. After you complete it, hopefully it won't change often, so move it to src/groovy.
  2. Even more important, it's faster and better for design to develop under unit tests than under application running and reloading a Contoller.
Taryn answered 23/11, 2010 at 14:17 Comment(0)
S
2

You can use the Delegation design pattern:

class Swimmer {
    def swim() { "swimming" }
}

class Runner {
    def run() { "running" }
}

class Biker {
    def bike() { "biking" }
}

class Triathlete { 
    @Delegate Swimmer swimmer
    @Delegate Runner runner
    @Delegate Biker biker
}

def triathlete = new Triathlete(
    swimmer: new Swimmer(),
    runner: new Runner(),
    biker: new Biker()
)

triathlete.swim()
triathlete.run()
triathlete.bike()

In case of a controller, assign the helper class directly at the instance field (or in the nullary constructor):

class HelperClass {
    def renderFoo() { render 'foo' }
}

class FooController {
    private @Delegate HelperClass helperClass = new HelperClass()

    def index = { this.renderFoo() }
}

The delegate's type information gets compiled into the containing class.

Sestina answered 16/11, 2010 at 15:22 Comment(2)
The delegate class doesn't have access to the internals of the owner class, such as the render method or params.Lutes
That's unfortunately true, and thus @Delegate will not be the methodology of choice in this case.Sestina
P
1

You can write all the common method in commonService and use that service to envoke commmon method

Pledge answered 14/5, 2013 at 10:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.