GABRIELEROSELLI

Rails - Tagging a Model.

Tagging is a very simple concept. Wikipedia describes a tag as “a non-hierarchical keyword or term assigned to a piece of information”.

There are few gems out there about tagging such as acts-as-taggable-on, is_taggable and so on. I do value the convenience and power of gems. And if I can I’d rather build my own logic. Mostly because it is the easiest way to deepen my knowledge on the subject.

If you are dealing only with one model that needs tagging, developing such a system in Rails is quite simple. For this article I will use tagging a blog post as example.

I usually establish the model associations right away. In this case it’s a many to many association between tags and posts like so:

In the post model:

1 class Post < ActiveRecord::Base
2  has_many :tags, :through => :posts_tags
3  has_many :posts_tags 
4 end

In the tag model:

1 class Tag < ActiveRecord::Base
2  has_many :posts_tags
3 end 

In the join post tag model:

1 class PostsTag < ActiveRecord::Base
2  belongs_to :post belongs_to :tag
3 end

Assuming that your post table is already in place, the next step is to generate the migrations to create the tags and the join posts_tags tables.

$ rails g migration CreateTags
$ rails g migration CreatePostsTags
 1 class CreateTags < ActiveRecord::Migration
 2  def self.up 
 3   create_table :tags do |t| 
 4    t.string :name 
 5    t.timestamps
 6   end
 7  end
 8 
 9  def self.down
10   drop_table :tags 
11  end 
12 end 
 1 class CreatePostsTags < ActiveRecord::Migration
 2  def self.up
 3   create_table :posts_tags do |t| 
 4   t.references :post
 5   t.references :tag
 6   t.timestamps
 7  end 
 8 end
 9 
10  def self.down
11   drop_table :posts_tags
12  end
13 end

Let’s not forget to migrate.

 $ rake db:migrate

After the migration, controller’s actions are next so that we can find, create and associate tags to posts when posts are created or edited.

 1 class PostsController < ApplicationController
 2  def new
 3   @post = Post.new
 4   @tags = Tag.all
 5 
 6   respond_to do |format|
 7    format.html # new.html.erb
 8    format.xml { render :xml => @post }
 9   end
10  end
11 
12  def edit
13   @post = Post.find(params[:id])
14   @tags = Tag.all
15  end
16 
17  def create
18   @post = Post.new(params[:post])
19   unless params[:tags].nil?
20    params[:tags] = params[:tags].collect{|tag| Tag.find_or_create_by_name(tag)} 
21   end
22 
23   respond_to do |format|
24    if @post.save
25     @post.create_new_tags(params[:new_tags][0]) unless params[:new_tags].nil?
26     @post.tags << params[:tags] unless params[:tags]nil?
27 
28     format.html { redirect_to(@post, :notice => 'Post was successfully created.') }
29     format.xml  { render :xml => @post, :status => :created, :location => @post }
30    else
31     format.html { render :action => "new" } 
32     format.xml { render :xml => @post.errors, :status => :unprocessable_entity }
33    end
34   end
35  end
36 
37  def update
38   @post = Post.find(params[:id])
39    unless params[:tags].nil?
40     params[:tags] = params[:tags].collect{|tag| Tag.find_or_create_by_name(tag)}
41    end
42   respond_to do |format|
43    if @post.update_attributes(params[:post])
44     @post.create_new_tags(params[:new_tags][0]) unless params[:new_tags].nil?
45     @post.tags << params[:tags] unless params[:tags].nil?
46     format.html { redirect_to(vendors_path, :notice => "Vendor was successfully updated.") }
47     format.xml { head :ok }
48    else format.html { render :action => "edit" }
49     format.xml { render :xml => @vendor.errors, :status => :unprocessable_entity }
50    end
51   end
52  end
53 end

On line 25 and 45 a “create_new_tags(params[:new_tags])” method is called for @post. This method allows the user the option to create new tags by providing a list of comma separated values. The method in the post model looks like this:

1 class Post < ActiveRecord::Base
2  def create_new_tags(tags) 
3   new_tags = tags.split(%r{,\s*}) 
4   new_tags.each do |tag|
5    self.tags.create(:name => tag)
6   end
7  end
8 end

Finally the post form view brings the above logic to the surface. The text field starting on line 1 is add new tags as a list of comma separated values. The check boxes assign existing tags to the post.

 1 <div>
 2  <%= label_tag :tags %>
 3  <%= text_field_tag('new_tags[]')%> 
 4 </div>
 5 <div>
 6  <% @tags.each do |tag| %>
 7 </div>
 8  <%= check_box_tag('tags[]', tag.name) %>
 9  <%= label_tag(tag.name)%>
10 <% end %>

And that’s it. You can now tag the post model.