Below is a page from a blogging application that shows a list of articles. We’d like to add tagging so that each article can have a number of tags assigned to it. In this episode we’ll show two different techniques to add tagging support to a Rails app.

Adding Tagging With Acts-as-Taggable-on

There are a lot of tagging libraries available but by far the most popular is acts-as-taggable-on. This gem is a little dated but it’s one of the few to have been maintained over the years. To use it in a Rails application we just need to add it to the gemfile then run bundle to install it.

/Gemfile

gem 'acts-as-taggable-on'

Next we need to run a generator that the gem provides to create a migration and then migrate the database to add the fields that acts-as-taggable-on uses.

terminal

$ rails g acts_as_taggable_on:migration
$ rake db:migrate

This migration creates two tables: tags and taggings. We don’t have to create models for these as they’re included with the gem so we can get started with adding tagging to our application. We want an extra field on the page for editing an article where we can add tags for that article. To do this we’ll modify the Article model by adding a call to acts_as_taggable, which is a method provided by the gem, and also a tag_list field to the attr_accessible list.

We can try this out now. If we edit an article and add a couple of tags to the new field (separated by commas) we’ll see those tags listed when we go back to edit the article again, so it looks as if this is working so far.

If you want to add auto-completion behaviour when you edit the tags, take a look at episode 102. We won’t be doing this here; the next thing we’ll do is add each article’s tags under its content. The simplest way to do this is to show the tag_list for each article.

It would be better if the tags were links so that if we click one we’ll see just the articles that have that tag. The tag_list attribute actually returns an array of strings, even though when we set the tag list it’s set through a single string with the tags separated by commas. We can turn this array of tags into a set of links like this:

We don’t have any routes set up for tags so we’ll do that now. We could set up a full tags resource but to keep things simple we’ll set up a single route. This will take a :tag argument and map to the articles index page.

When we reload the page this doesn’t quite work, however, as the HTML for the tags is escaped. To fix this we can pass this code through the raw method so that it isn’t escaped. Whenever we use this method it’s a sign that we’re doing something a little complicated for the view layer and that we should move it off into a helper method. We won’t do that here, but it’s worth bearing this in mind.

When we reload the page now we have links for each tag, each of which points to the correct path. Clicking on one doesn’t filter the list of articles yet, though.

We can do this easily enough in the ArticlesController’s index action by checking for a :tag parameter and filtering the list by that tag if it’s found. The gem provides a tagged_with that we can used to find the articles with a given tag.

When we reload the page now the articles are filtered by the selected tag.

Adding a Tag Cloud

Our tag list works now but what if we want to browse all the tags? A common way to do this is to use a tag cloud that shows all the tags, each with a size proportional to its popularity. We’ll add this near the top of the index template and as the gem supplies a tag_cloud method doing this is quite easy.

This method takes two arguments. The first one is a set of the tags that we want to display. An easy way to get this is to call Article.tag_counts which will give us a list of the tags and how often each one is used, which is needed to know how big each tag should be shown. The second argument we pass in should be a array of the CSS classes we want to use for the various sizes. The method also takes a block which is passed a tag object and the CSS class that matches that tag. Inside this block we render out the tag, passing link_to the tag object’s name, the tag_path (which we also pass the tag name as we want to use the name in the URL), and the CSS class. We don’t have styles defined for our tag cloud so we’ll add those now.

When we reload the articles page now we’ll see a tag cloud with the two tags that we’ve already added. If we add some more tags to the articles and reload the page we’ll see a better looking tag cloud with tags that vary in size depending on the number of articles with that tag. Clicking a tag will filter the articles to those with that tag.

There’s a lot more we can do here such as highlighting the current tag or sorting the tags by their name but we won’t do those in our application.

Implementing Tagging From Scratch

We’ve done a lot with the acts-as-taggable-on gem but how much work would it be to implement something like this from scratch? Before we can do this we’ll need to roll back some of the functionality that we’ve added. First we’ll undo the database change we made and remove the migration.

terminal

$ rake db:rollback
$ rails d acts_as_taggable_on:migration

Next we’ll remove the gem from the gem file and run bundle again. We can then start rebuilding the tagging functionality from scratch. First we’ll generate a Tag model with a name column.

terminal

$ rails g model tag name

Next we’ll create a Tagging model that belongs to both Tag and Article.

terminal

$ rails g model tagging tag:belongs_to article:belongs_to

We could set up a polymorphic association here which is what the gem does internally but since we have a simple setup we’ll link directly to the Article model here. We can then run rake db:migrate to ad the new tables. Next we need to set up the associations in the models, starting with Tag.

First we set up the associations for taggings and for tags through taggings then we implement the four methods that we call on the Article model elsewhere in our application. The tagged_with method returns the articles with tags matching a given name while tag_counts returns the tags and a count for each one. (This method may well be better off in the Tag model.) We also have methods for getting and setting the tag_list, although there’s a slight change here as the getter returns a string instead of an array. Because of this we’ll need to change the index template as it currently treats the tag_list as an array. Instead we’ll call article.tags and map the name attribute so that it has similar behaviour to the gem.

This method is fairly simple and takes the same arguments as the gem’s method. In it we find the tag with the highest count so that we can determine the relative popularity of the other tags and determine the class to use with each one. We now have roughly the same functionality but written from scratch. If we add a couple of tags to an article then go back to the list we’ll see the tags next to that article and also added to the cloud.

The majority of the code for our solution is in the Article model. We’ve not had to write too much code to implement this and now we don’t have an extra dependency in our application. There are a lot of features in the gem that we haven’t added to our scratch solution such as different contexts that we can supply when we create tags on a model. With the gem we can also mark owners so that we can make, say, a User a tagger and the gem will keep track of this when we track models.

If you just want a simple tagging solution then implementing it from scratch is a good idea, but for a full featured solution them gem makes more sense.