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 ?

fabien7474
  • 16,300
  • 22
  • 96
  • 124

5 Answers5

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()
    }
}
ataylor
  • 64,891
  • 24
  • 161
  • 189
  • 8
    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. – robbbert Nov 16 '10 at 17:48
  • 2
    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. – robbbert Nov 16 '10 at 17:49
  • Is there any way to make `MyControllerCategory` subject to dependency injection? – Dónal May 09 '11 at 11:37
  • 3
    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: [link](http://grails.1312388.n4.nabble.com/Call-a-method-on-the-target-class-when-using-own-TestMixin-td4637497.html#a4637776) – P. Deters Feb 01 '13 at 14:11
  • 4
    `traits` are replacing `@Mixin` and `@Mixin` is deprecated with Groovy 2.3. More about traits: http://stackoverflow.com/questions/23121890/difference-between-delegate-mixin-and-traits-in-groovy#answer-23124968 & http://www.mscharhag.com/groovy/groovy-introduces-traits – Daniel Dec 23 '15 at 15:26
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]
}
cdeszaq
  • 30,869
  • 25
  • 117
  • 173
Jared
  • 39,513
  • 29
  • 110
  • 145
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.
Victor Sergienko
  • 13,115
  • 3
  • 57
  • 91
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.

robbbert
  • 2,183
  • 15
  • 15
  • 6
    The delegate class doesn't have access to the internals of the owner class, such as the render method or params. – ataylor Nov 16 '10 at 17:37
  • 2
    That's unfortunately true, and thus `@Delegate` will not be the methodology of choice in this case. – robbbert Nov 16 '10 at 18:00
1

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

Anand Kushwaha
  • 457
  • 4
  • 15