Star rating in the Grails RichUI plugin
I’ve been working on a Grails application for rating the popularity of Grails plugins. Rather than just use a simple form with radio buttons or a drop-down list, I thought I’d use the star rating component of the RichUI plugin.
The initial set-up is easy enough. First, install the RichUI plugin.
grails install-plugin richui
then go into any GSP page where you want to use star rating and add
<resource:rating />
and after that it’s just a question of using the <g:render /> tag appropriately.
Mine looks like this:
<g:each in="${pluginList}" var="plugin">
...
<g:render template="rate"
model='[plugin: plugin, rating: "${plugin.rating}"]' />
along with some other stuff.
The documentation says to add a RatingController which computes the new rate. I considered just adding the rating method to my PluginController, but eventually decided to keep it separate. My RatingController looks like
class RatingController {
def rate = {
def rating = params.rating
def plugin = Plugin.get( params.id )
def average = (rating.toDouble() +
plugin.rating*plugin.totalVotes)/
(plugin.totalVotes + 1)
plugin.rating = average
plugin.totalVotes += 1
plugin.save()
session.voted[plugin.name] = true
render(template: "/plugin/rate",
model: [plugin: plugin, rating: average])
}
}
session.voted[...] business. I’ll come back to that in a moment.The template I’m using is called _rate.gsp in the plugin view folder. It consists of
<div id="plugin${plugin.id}">
<% def dynamic = !session.voted[plugin.name] %>
<richui:rating dynamic="${dynamic.toString()}" id="${plugin.id}" units="5"
rating="${rating}" updateId="plugin${plugin.id}" controller="rating" action="rate" />
<p class="static">
Rating ${java.text.NumberFormat.instance.format(plugin.rating)}
based on ${plugin.totalVotes} vote<g:if test="${plugin.totalVotes != 1}">s</g:if>
</p>
<g:if test="${!dynamic}">
<div style="color: green;" id="vote${plugin.id}">Thanks for voting!</div>
</g:if>
</div>
And here we see some good stuff. First, when you click on the star rating as it comes “out of the box”, it only updates the average value, which it optionally displays. I wanted to show the number of total votes, too. As it turns out, there’s an excellent blog post by Jan Sokol that deals with exactly this problem. See the blog post for details, but essentially it involves changing update ID to that for a div wrapper, which allows you to update a whole section instead.
The <richui:rating> tag has an attribute called dynamic which determines whether you can vote or not. If dynamic is false, you get a (naturally enough) static view. If dynamic is true, you can mouseover the stars, highlighting them as you go, and then click to vote.
In most applications I’ve seen that use the star rating, you have to register and login in order to be able to rate anything. I now believe that’s so they can render the rating tag as dynamic when you enter and then change it to static when you click. The state is then kept in a user table, which remembers whether you’ve already voted or not.
My problem, though, is that I wanted to let anyone vote without having to register (a decision I’m currently reviewing). So the question is, how do I keep a person from just voting over and over again?
I tried a couple of ideas, like a toggle or putting something in the page, but if someone browsed to a different page and came back they could vote again. In the end, I found that I can use the session, even though nobody has logged in!
(That may be obvious to you, but even after teaching server-side Java courses for years now, I guess it never really hit me that a session exists even if you’re not logged in. Whenever I visited a site with a shopping cart, I always had to log in. It turns out that was only to buy the products, not to have a session at all. I guess in retrospect it seems obvious, but I never really thought about it until now.)
Anyway, that left me with the question of how to use the session for 90-some different plugins. I decided to use a boolean array, where the index was the plugin name and the value was true if the person had voted and false otherwise. As you can see from the RatingController code above, whenever anyone votes, I simply go
session.voted[plugin.name] = true
and I’m all set, because the rating template has
<% def dynamic = !session.voted[plugin.name] %>
and
<richui:rating dynamic=”${dynamic.toString()}” … />
in it.
The only remaining question was how to initialize that array for the session. I decided that was a classic application of an interceptor, so in my controller for the plugins themselves, I have
def beforeInterceptor = {
if (!session || !session.voted) {
def voted = [:]
def names = Plugin.list().collect { it.name }
names.each { name ->
voted[name] = false
}
session.voted = voted
}
}
and there you have it. Of course, it’s better if you test, and while I still struggle with that, I was able to come up with something effective in this case. Here’s my PluginControllerTests class, which is, of course, an integration test. (There’s no doubt a way to make it a unit test instead, but hey, at least it’s tested.)
class PluginControllerTests extends GroovyTestCase {
def pc
void setUp() {
pc = new PluginController()
}
void testNoVotedArrayBeforeIntercepting() {
assertNull pc.session?.voted
}
void testVotedArrayExistsAfterIntercepting() {
pc.beforeInterceptor()
assertNotNull pc.session
assertNotNull pc.session.voted
}
void testVotedArrayHasAcegi() {
pc.beforeInterceptor()
assertFalse pc.session.voted['acegi']
}
void tearDown() {
pc = null
}
}
So it all works, at least so far. Soon I’ll be able to deploy it, but now I’m still fighting with CSS styles. Whoever thought doing layout with CSS was a good idea has some explaining to do.
Incidentally, anyone who has used WordPress for blogging knows the frustrations of trying to format code in it. I think there are plugins available to make it easier, but I’m not running WordPress myself — I’m letting them host my blog. The bottom line, therefore, is that sometimes it’s really hard to read code that is posted here. I think, when my app is finished, I’ll follow the recent trend and upload it to github. If I do that, I’ll be sure to mention it here.

About Kenneth Kousen
Ken Kousen is the President of Kousen IT, Inc., through which he does technical training, mentoring, and consulting in all areas of Java and XML. He is the author of the O'Reilly screencast "Up and Running Groovy", and the upcoming Manning book about Java/Groovy integration, entitled "Making Java Groovy".
He has been a tech reviewer for several books on software development. Over the past decade he's taught thousands of developers in business and industry. He is also an adjunct professor at the Rensselaer Polytechnic Institute site in Hartford, CT. His academic background includes two BS degrees from M.I.T., an MS and a Ph.D. from Princeton, and an MS in Computer Science from R.P.I.
More About Kenneth »Why Attend the NFJS Tour?
- » Cutting-Edge Technologies
- » Agile Practices
- » Peer Exchange
Current Topics:
- Languages on the JVM: Scala, Groovy, Clojure
- Enterprise Java
- Core Java, Java 7
- Agility
- Testing: Geb, Spock, Easyb
- REST
- NoSQL: MongoDB, Cassandra
- Hadoop
- Spring 3
- Automation Tools: Git, Hudson, Sonar
- HTML5, Ajax, jQuery, Usability
- Mobile Applications - iPhone and Android
- More...
NFJS, the Magazine
December Issue Now AvailableBDD and REST
by Brian SlettenMocks and Stubs in Groovy Tests
by Kenneth KousenAlgorithms for Better Text Search Results
by John GriffinKnowns and Unknowns of Scrum and Agile
by Brian Tarbox

