Question

UPDATE

I have to apologize for confusing the readers. After I got totally lost in the code, I reverted all my changes from Mercurial repo, carefully applied the same logic as before -- and it worked. The answers below helped me understand the (new to me) concept better, and for that I gave them upvotes.

Bottom line: if a call to a missing method happens within a closure, and resolution set to DELEGATE_FIRST, methodMissing() will be called on the delegate. If it doesn't -- check you own code, there is a typo somewhere.

Thanks a lot!

Était-ce utile?

La solution

Edit: OK, now that you've clarified what your are doing (somewhat ;--))

Another approach (one that I use for DSLs) is to parse your closure group to map via a ClosureToMap utility like this:

// converts given closure to map method => value pairs (1-d, if you need nested, ask)
class ClosureToMap {
    Map map = [:]
    ClosureToMap(Closure c) {
        c.delegate = this
        c.resolveStrategy = Closure.DELEGATE_FIRST
        c.each{"$it"()}
    }
    def methodMissing(String name, args) {
        if(!args.size()) return
        map[name] = args[0]
    }
    def propertyMissing(String name) { name }
}

// Pass your closure to the utility and access the generated map
Map map = new ClosureToMap(your-closure-here)?.map

Now you can iterate through the map, perhaps adding methods to applicable MCL instance. For example, some of my domains have dynamic finders like:

def finders = {
    userStatusPaid = { Boolean active = true->
        eq {
            active    "$active"
            paid      true
        }
    }
}

I create a map using the ClosureToMap utility, and then iterate through, adding map keys (methods, like "userStatus") and values (in this case, closure "eq") to domain instance MCL, delegating the closure to our ORM, like so:

def injectFinders(Object instance) {
    if(instance.hasProperty('finders')) {
        Map m = ClosureToMap.new(instance.finders).map
        m?.each{ String method, Closure cl->
            cl.delegate = instance.orm
            cl.resolveStrategy = Closure.DELEGATE_FIRST
            instance.orm.metaClass."$method" = cl
        }
    }
}

In this way in controller scope I can do, say:

def actives = Orders.userStatusPaid()

and "eq" closure will delegate to the ORM and not domain Orders where an MME would occur.

Play around with it, hopefully I've given you some ideas for how to solve the problem. In Groovy, if you can't do it one way, try another ;--)

Good luck!

Original: Your missingMethod is defined on string metaclass; in order for it to be invoked, you need "someString".foo()

If you simply call foo() by itself within your closure it will fail, regardless of delegation strategy used; i.e. if you don't use the (String) delegate, good luck. Case in point, do "".foo() and it works.

I don't fully understand the issue either, why will you not have access to the closure's delegate? You are setting the closure's delegate and will invoke the closure, which means you will have access to the delegate within the closure itself (and can just delegate.foo())

Autres conseils

nope, you will not catch a missing method and redirect it to the delegate with metaclass magic.
the closure delegate is the chance to capture those calls and adapt them to the backing domain.
that means...
you should create your own delegate with the methods required by the dsl.
do not try to force a class to do delegate work if it's not designed for the task, or the code will get really messy in not time.
keep everything dsl related in a set of specially designed delegate classes and everything will suddenly become ridiculously simple and clear.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top