Monday, July 7, 2008

Experience with Act as Taggable Plugin

While working on grails tutorials site I have also used Act as Taggable plugin. In this post I will try to describe my experience while using this plugin. Although plugin is in alpha phase it gives you solid ground to implement tag cloud. At least you need not to start from the scratch.

First step is of course to install plugin. Download latest version from google code svn repository of Act as Taggable Plugin. Then in the console type:

grails install-plugin grails-acts-as-taggable-0.1.zip 
That line will install plugin. To mark some domain class as taggable add implements Taggable to that domain class. Other basics you can find in act as taggable tutorial on the grails home page.

Let us have a deeper look at what this plugin offers.

If you inspect the code you will see that there are two domain classes Tag and Tagging. This means that this plugin will add two tables into your database. Class Tag represents tag and has only one property: name. Class Tagging is used as connection between Tag and any Taggable class. Interesting property here is String taggableType. This property represents magic how to connect any tag with any taggable domain class. This property equals to the class name of the taggable domain class. This way you are able to connect same or different set of tags to different taggable classes.

Beside those two domain classes there are two more groovy classes: Taggable (already mentioned in previous section) interface and TaggableMixin. Taggable interface is just interface with no methods but it is heavily used within TaggableMixin to add additional methods to all classes that implements this interface. It is really good to have a look at the TaggableMixin class.

TaggableMixin defines four closures: addTag, getTags, setTags, removeTags, and removeTag. Method getTags will return list of all tags for the calling Taggable instance. But as it uses join(" ") to separate tag names, if you have tags with the spaces those tags will be separated into multiple tags. This was not behavior I needed so I added additional closure getTagsAsList. This closure does the same thing as getTags but will not split your tag if it has space. The code of the closure is:

Taggable.metaClass.getTagsAsList = { ->
def taggings = Tagging.findAllWhere( taggableId: delegate.id, taggableType: delegate.class.toString())

def result = taggings.inject([]) {list, tagging ->
list.add(tagging.tag.name)
return list
}

return result
}

Next thing you will probably need is list of tags in this form: [tagName:count, tagName:count,...]. Taggable plugin currently does not offer method that will do it for you so I have created one for me. I didn't put it into source of taggable plugin because I want to minimize merging problems when new version of plugin is released. Code looks like this:

def tagGrouping = Tagging.executeQuery("select distinct t.tag.name, count(t.tag.id) from Tagging t group by t.tag.id")

def values = tagGrouping.inject([:]) {val, obj ->
val.put(obj[0], obj[1])
return val
}

Note that I didn't add control where taggalbeType = 'class of taggable instance'. If you need it just add that line. I think that this method should be part of the TaggableMixin too.

Further on what you will probably need is to get all instances (or subset of them) tagged with provided tag and total number of those instances. So for example you want to get all tutorials tagged with GORM tag. As such query is currently not supported within plugin I have created my methods. Lines of code you can use are:

def tls = Tagging.executeQuery
("select distinct t from TutorialLink t, Tagging tgg, Tag tg
where tg.name = ? and tgg.tag = tg and tgg.taggableType = ? and tgg.taggableId=t.id
order by t.dateCreated desc"
, [params.selectedTag, TutorialLink.class.toString()], [max:max,offset:offset])

def total = Tagging.executeQuery
("select count(t) from TutorialLink t, Tagging tgg, Tag tg
where tg.name = ? and tgg.tag = tg and tgg.taggableType = ? and tgg.taggableId=t.id
order by t.dateCreated desc"
, [params.selectedTag, TutorialLink.class.toString()])[0]

I must admit that these lines are more specialized for grails tutorials but they can be generalized with the minimum effort.

As you can see I took Act as Pluggable plugin and made it more user friendly by adding few simple methods. Hopefully these methods will be soon available in the official plugin. Usage of plugin is simple and gives you excellent base to increase your productivity.

No comments: