views:

169

answers:

3

Wanna do the following:

BootStrap {
  def init = {servletContext ->
  ........
  MyDomainClass.metaClass.save = {-> 
     delegate.extraSave()
     //////// how to call original save() here?
  }

}
.........
}

P.S. MyDomainClass#extraSave is defined as public void extraSave(){.....}

+1  A: 

Not sure if the following works, but this might be a solution:

MyDomainClass.metaClass.origSave = MyDomainClass.metaClass.save
MyDomainClass.metaClass.save = {-> 
   delegate.extraSave()
   delegate.origSave()
}

Please give me feedbeck if the above worked...

Stefan
Tried that yesterday. Does not work saying that `origSave` is metaclass property or something and not the closure, something like that. Tried different ways to call it: `origSave()` and `origSave.call()`
archer
+2  A: 

First of all, Bootstrap.groovy may not be the best place to do this kind of metaprogramming. The problem with this approach is that the changes to the classes will be applied when the application starts, but you may lose these changes when the application is reloaded. Obviously this is only an issue during development, and not an issue at all if you don't mind restarting the server every time you make a change, but I'll bet this would quickly become a major annoyance. In order to have the changes applied when app is reloaded as well, you should move the metaprogramming into a plugin, where you can hook into the onChange application lifecycle event.

So the steps are:

  • Create a plugin
  • Do the metaprogramming in the doWithDynamicMethods and onChange closures of the plugin descriptor

Here's a complete example where I "override" the chain() method on all the controller classes. The code to do likewise for the save() method of domain classes should only require some obvious replacements, e.g. use application.domainClasses instead of application.controllerClasses

def doWithDynamicMethods = {ctx ->

    application.controllerClasses.each {controller ->
        replaceChain(controller)
    }
}

def onChange = {event ->
    if (application.isArtefactOfType(ControllerArtefactHandler.TYPE, event.source)) {
        def clz = application.getControllerClass(event.source?.name)
        replaceChain(clz)
    }
}

private replaceChain(controllerClass) {

    // Save a  reference to the grails chain() method
    def grailsChain = controllerClass.metaClass.pickMethod("chain", [Map] as Class[])

    controllerClass.metaClass.chain = {Map params ->

        println "My code to execute before chain goes here"

        // Invoke the grails chain() method
        grailsChain.invoke(delegate, [params] as Object[])

        println "My code to execute after chain goes here"
    }
}
Don
+2  A: 

why not leveraging the GORM events for this purpose? In the Domain class:

def extraSave() {
    // ...
}

def beforeInsert = {
    extraSave()
}

def beforeUpdate = {
    extraSave()
}

IMHO this a cleaner approach. Documentation can be found here

Siegfried Puchbauer
A shortcoming of this approach is that you need to add extraSave() to the event handlers of every domain class. A metaprogramming approach would only require you to define/invoke extraSave() in one place
Don
sure, I would still prefer a EntityInterceptor based approach when dealing with such things, though. Overriding GORM method can be a real PITA.
Siegfried Puchbauer
btw. for using an EntityInterceptor just create a subclass of org.hibernate.EmptyInterceptor and register it as a bean named "entityInterceptor" in your grails-app/conf/spring/resources.groovy (since Grails 1.2 - it's possible in older version as well, but the registration of the interceptor is more work)
Siegfried Puchbauer
I tried this approach even before trying to overload `save()`. The problem with it is that even handlers are called inside `save()` and `saveOrUpdate()`, so I need either to wrap my `extraSave()` with `.withNewSession` (which will lead to other problems, as I'm doing batching one level up), or think out something else.
archer