Douglas F Shearer

Modify Acts_As_Taggable to Return Tags By Model


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?

 

Comments


Gravatar

James King

October 23 2006 12:11

Douglas,

I think that the self.tags method that your technique modifies isn't included in the tag.rb model of the original plugin. Perhaps you need to mention that it is necessary to follow the tutorial at the link below before implementing your technique.

http://www.juixe....

Gravatar

Douglas F Shearer

October 23 2006 21:34

Thanks James, I've now updated the article. I should have though to check this worked on a new installation of acts_as_taggable, i forgot I had already added a new method to it.

Gravatar

Labrat

November 12 2006 16:08

Thanks for the write up. I was wondering if this was possible by default as well. You saved me some time.

BTW, you're missing an apostrophe before: #{options[:taggable_type]}'"

I also added the following to acts_as_taggable under module SingletonMethods :

# my hack
def tags(options = {})
Tag.tags(:taggable_type => self.to_s)
end

Now I can do:

@tags = Post.tags

Thanks!

Gravatar

Douglas F Shearer

November 13 2006 21:25

Hi Labrat!

Nice little method you have there, that really simplifies the method call!

I've added it to my post, with the obligatory link to your own blog post.

After having a look at my own production source, the apostrophe shouldn't be there at all, but putting it's matching pair in is a valid fix, so thanks for spotting it.

Gravatar

Labrat

November 14 2006 02:34

Cheers for the honourable mention. It's much better to have all the info in one place.

I've updated my post accordingly. I'll be keeping an eye on this blog.

Gravatar

d

November 30 2006 12:39

It seems that for sqlite3, this section must be quoted or else an error will get thrown, so any sqlite users may gain from adding single quotes around '#{options[:taggable_type]}'

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

Gravatar

JohnB

January 14 2007 08:29

It would seem a bit nicer if
Tag.tags(:order => "name", :taggable_type => "post")
could become
Tag.tags_relate_to_posts(:order => "name")

It seems like it should be fairly easy (with method_missing) to pull out the string "posts" and use it singular form as the taggable_type value. Thus if we had no need for a specific "Post" model we could still read and write the 'Post' tag.

Gravatar

Douglas F Shearer

January 14 2007 13:59

I like that JohnB! I'm currently in the process of creating v2 of my blogging software, and that seems like an excellent piece of functionality to add.

Add Your Comments


Commenting is closed for this entry.

 

You Are Here


Douglas F Shearer

This is the homepage of Douglas F Shearer, a software developer and mountainbike racer. More…

Hire Me!


I'm available for hire. Ruby, Java and PHP work, both remotely (Worldwide) and locally (Scotland). Find out more or email me.

Flickr Latest


Stay Informed


What is RSS?

Categories


  1. Bike (93)
  2. Coding (85)
  3. Other (46)

Top Tags