I’m sure many people are using the brilliant Rails plugin Acts As Taggable, and probably just as many using Tom Fake’s tag cloud method to generate nice tag clouds on their sites.

Prerequisites

I made a bit of a boo-boo with the original version of this article. The code I have provided is built upon a modification to the acts_as_taggable plugin I made a few weeks ago. In order to create clouds you have to add a new method as documented at the top of the TechKnow Zenze – Acts as Taggable Tag Cloud turorial. It is this method I modify in this article. Thanks to James King for pointing this out to me.

To Clarify, you need:

  1. The acts_as_taggable plugin.
  2. To modify it as shown here.

Onto the Good Stuff

Recently I started to add a second model to my app, which also required tagging, and discovered that there was no built in way to get all the tags for the post model (To display as a blog tag cloud) or the image model (To display as a gallery tag cloud). There is a few hacks on the Rails wiki, but they are just that, fairly ugly hacks. I hope to do better here. I will present two similar methods here, one of which follows rails convention, the other which is slightly more user friendly.

Following Rails Convention

Anyone who has used either find or count with more complex queries will be familiar with using the :conditions argument in the method calls. The following modification to the method self.tags, in the file lib/tag.rb which is in the acts_as_taggable folder, allows :conditions to be used to retrieve only a prarticular taggable_type via it’s model name.

By adding the following line between lines 4 and 5:


query << " AND #{options[:conditions]}" if options[:conditions] != nil

Our method will now look like this:

  def self.tags(options = {})
    query = "select tags.id, name, count(*) as count"
    query << " from taggings, tags"
    query << " where tags.id = tag_id"
    query << " AND #{options[:conditions]}" if options[:conditions] != nil
    query << " group by tag_id"
    query << " order by #{options[:order]}" if options[:order] != nil
    query << " limit #{options[:limit]}" if options[:limit] != nil
    tags = Tag.find_by_sql(query)
  end

We can then call this as so:

@tags = Tag.tags(:order => "name", :conditions => "taggings.taggable_type = 'post'")

Despite the long winded method call, our hack does follow convention, and would allow other conditions to be passed if necessary. This hack is my personal choice between the two presented here, purely because it keeps convention.

A Bit More User Friendly

The above method is great, but if you want a short and quick way to set the taggable_type without knowing how the tables are related, this method is slightly more user friendly. We are modifying the same file and method as above.

By adding the following line between lines 4 and 5:


query << " AND taggings.taggable_type = #{options[:taggable_type]}" if options[:taggable_type] != nil

Our method will now look like this:

  def self.tags(options = {})
    query = "select tags.id, name, count(*) as count"
    query << " from taggings, tags"
    query << " where tags.id = tag_id"
    query << " AND taggings.taggable_type = #{options[:taggable_type]}" if options[:taggable_type] != nil
    query << " group by tag_id"
    query << " order by #{options[:order]}" if options[:order] != nil
    query << " limit #{options[:limit]}" if options[:limit] != nil
    tags = Tag.find_by_sql(query)
  end
We can call this as so:
@tags = Tag.tags(:order => "name", :taggable_type => "post")

Thats it! Our second method would probably benefit from a more friendly argument such as :model rather than the not entirely easy to remember :taggable_type, but I leave this up to you.

Update – The Easiest Method Possible

Thanks to Labratz comment, I have now eliminated a small error in my code. He also suggested an auxiliary method to add to acts_as_taggable (Which makes use of my second method above) to allow tags to be found for a specific model using:

def tags(options = {})
   options.merge!(:taggable_type => self.to_s)
   Tag.tags(options)
end

Now we can call this with:

tags_for_post_model = Post.tags

Tada! Now I feel happy about the whole thing, much less of a hack. Check out Labratz blog post for the full lowdown.

Did you like my Ruby on Rails related article? Then why not recommend me on Working with Rails?