Wednesday, October 22, 2008

Grails Conditional Tags as Method Calls Trap

As you probably know grails tags can also be used within controllers as method calls. In this post I will describe the trap that caught me when I tried to use conditional tag within if statement.

Default usage of grails tag as method call can be described with the following example. g.render tag within gsp page will look like this:

<g:render template="someTemplate" model:[name:"peter"]/>

While within controller you can use this tag like this:

g.render(template:"someTemplate", name:"peter")
I must admit that this is excellent feature of the grails and I use it often. But you can get confused if you try to use conditional tags within if statements. Lets try one example.

Imagine that we have following conditional tag userHasGoldMembership that displays its content only when logged in user has gold membership. Usage of this tag within gsp page may look like this:

...
<g:userHasGoldMembership>
<g:link controller="abc" action="xyz">Perform this action</g:link>
</g:userHasGoldMembership>
...

What example does is that link 'Perform this action' will be displayed only if logged in user has gold membership.

Now let us try to use this tag as method call within controller as condition within if.

def price = 100
if (g.userHasGoldMembership()) {
price = price - discount
}
chargeCreditCard()

Well to my surprise what happens here is that g.userHasGoldMembership() always resolves to false. And after some thinking it becomes clear why this happens. Let us analyze how in the most cases conditional tags work. For example, for mentioned conditional tag implementation may look like this (and I believe this is the case for the majority of conditional tags):

class MembershipTagLib {
def userHasGoldMembership = {attrs, body ->
if (session.user.isGold()) {
out << body()
}
}
}

What we see in this code is that in the case that user has gold membership returned value from the tag will be body of the tag. In all other cases nothing is returned. But what is body within construct if(g.userHasGoldMembership())?

Well there is no body. This means that in both cases, user has and user has not gold membership, result of the tag used as method call will be null.

Does it mean that we cannot use conditional tags? Of course not. It is enough to provide body. Version of the if statement that works is:

def price = 100
if (g.userHasGoldMembership() {true}) {
price = price - discount
}
chargeCreditCard()

Did you notice the {true} part? This is actually body of the tag. It means that when user has gold membership, tag will return result of body execution and that is value true.

And of course example from the real conditional tag that is probably widely used is from acegi. Acegi plugin provides tag <g:ifAllGranted role="LIST_OF_ROLES">content to display</g:ifAllGranted>. And if you want to reuse this plugin within your controller then you have to use it in the form of

if (g.ifAllGranter(role="ROLE_ADMIN"){true}) {
do something
}
otherwise you will never get into the if part of the condition.

3 comments:

Graeme Rocher said...

Why can't you just do:

g.userHasGoldMembership() {
price = price - discount
}

Since the body will only be evaluated if the condition in the tag evaluates to true

jan said...

Thanks for an advice.

It just didn't come to my mind.

Shiraz said...

A very useful post + comments.