tags:

views:

131

answers:

3

I need to add common actions to a number of my controllers without using inheritance. All our controllers extend an Abstract controller and the functionality I want to include does not make sense in the abstract controller.

My initial idea was to use a Mixin, but it appears that actions, since they are closures, are not 'mixed-in' to the controllers that include the mixin, only methods are.

Can anyone suggest a way I can get "mixin' like functionality to add actions to controllers?

thanks!

+4  A: 

The way to do it is to iterate over your controllers and add methods to it using metaprogramming.

For one example, check out perf4j plugin (file Perf4jGrailsPlugin.groovy).

In it, you'll see something like:

application.controllerClasses.each() {
            addPerf4jMethods(it.clazz, log)
        } 

def addPerf4jMethods(artefactClass, log) {
        log.info "Adding Per4j methods: ${artefactClass}..."


        artefactClass.metaClass.withStopwatch << { Closure callable ->
            withStopwatch("", null, callable)
        }

        artefactClass.metaClass.withStopwatch << { String tag, Closure callable ->
            withStopwatch(tag, null, callable)
        } 
}

In the code above, you are iterating through all controllers and adding methods withStopWatch, so they are now available everywhere. If you only have to do it in some controllers, you can obviously put in additional checks.

You can put this code into BootStrap.groovy, so it runs every time app starts up.

Jean Barmash
Controller actions are not methods, but rather properties of type Closure! Thus, your example isn't quite right in this context. However, you can of course also add properties via ExpandoMetaClass, so nothing wrong about using metaprogramming in general to solve this issue. And thanks for referencing the Perf4j plugin (which happens to be written by me ;-) ).
Daniel Rinser
Nice - it's a great plugin. I was trying to augment it to automatically add withStopwatch methods in scaffolded controllers, but got tripped up by order of loading. Hope to try again.
Jean Barmash
It's better to do this kind of metaprogramming in a plugin descriptor file than in Bootstrap.groovy beccause metaprogramming in Bootstrap is not applied when the app reloads
Don
+4  A: 

All our controllers extend an Abstract controller and the functionality I want to include does not make sense in the abstract controller.

Then why not have a second abstract controller that extends the base controller and is in turn extended by only those controllers that need this functionality?

This sounds like the easiest and cleanest solution to me - certainly better than using metaprogramming. "Prefer composition to inheritance" does not mean that inheritance is a fundamentally dirty thing that should be avoided at all costs.

Michael Borgwardt
If this is an option, then this is definitely better than what I suggested in my answer.
Jean Barmash
I agree. There is nothing fundamentally wrong with inheritance, but the functionality I want to add is more 'framework' in nature and it just doesn't smell right to put it in abstract controllers. I may have to, though.
BigCanOfTuna
+1  A: 

A third option that I often use in this type of situation is a static import. This is actually a part of java (not groovy) that many people aren't aware of.

So if you have a class like this with the static methods that you want to share:

class Foo {
    static baz = { -> 
     return "baz!"
    }
}

And use "import static" in the classes that you want to have that method available:

import static Foo.baz

class Bar {
    static void main(String[] args) {
     assert "baz!" == baz.call()
    }
}

No superclass or mixin magic needed, let the compiler do the work for you.

Ted Naleid