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.