acts_as_taggable per user tagging
David Heinemeier Hansson created the acts_as_taggable Ruby on Rails plugin to allow tagging of any Active Record object. This is great for tagging objects application-wide, but I required user specific tags for my Rails app. Hopefully the following outlines the exact changes required to add the same functionality to your own Rails app – post a comment if you have any problems.
Note: There is also an acts_as_taggable gem which is different than the plugin. The acts_as_taggable plugin is only available for Rails 1.1.
Install acts_as_taggable plugin
script/plugin install acts_as_taggable
Create database tables
The following are mysql specific create table definitions:
-- tags DROP TABLE IF EXISTS tags; CREATE TABLE IF NOT EXISTS tags ( id int(11) unsigned NOT NULL auto_increment, name varchar(255) NOT NULL, created_at datetime NOT NULL, updated_at datetime NOT NULL, PRIMARY KEY (id) ) TYPE=InnoDB; -- taggings DROP TABLE IF EXISTS taggings; CREATE TABLE taggings ( id int(11) unsigned NOT NULL auto_increment, tag_id int(11) unsigned NOT NULL, taggable_id int(11) unsigned NOT NULL, taggable_type varchar(255), user_id int(11) unsigned NOT NULL, PRIMARY KEY (id) ) ENGINE=InnoDB;
The above taggings table differs from the normal acts_as_taggable plugin requirement by adding a user_id column to associate a tagging with an individual user. I am assuming that your Rails application already has a users table (I am using the login_engine to provide user authentication).
Update Rails objects
Add acts_as_taggable to the class you want to be able to tag
class ItemToTag < ActiveRecord::Base acts_as_taggable end
class User < ActiveRecord::Base has_many :taggings has_many :tags, :through => :taggings, :select => "DISTINCT tags.*" end
Modify acts_as_taggable Plugin
To add per-user taggings we need to modify the acts_as_taggable plugin itself. First we add belongs_to :user to associate a tagging with a user.
class Tagging < ActiveRecord::Base
belongs_to :tag
belongs_to :taggable, :polymorphic => true
belongs_to :user
def self.tagged_class(taggable)
ActiveRecord::Base.send(:class_name_of_active_record_descendant, taggable.class).to_s
end
def self.find_taggable(tagged_class, tagged_id)
tagged_class.constantize.find(tagged_id)
end
end
Next replace the existing on method with the following in the Tag class (tag.rb file).
def on(taggable, user) tagging = taggings.create :taggable => taggable, :user => user end
Update acts_as_taggable.rb class by replacing each of the following methods with those given below:
def find_tagged_with(list, user)
find_by_sql([
"SELECT #{table_name}.* FROM #{table_name}, tags, taggings " +
"WHERE #{table_name}.#{primary_key} = taggings.taggable_id " +
"AND taggings.user_id = ? " +
"AND taggings.taggable_type = ? " +
"AND taggings.tag_id = tags.id AND tags.name IN (?)",
user.id, acts_as_taggable_options[:taggable_type], list
])
end
def tag_with(list, user)
Tag.transaction do
Tagging.destroy_all(" taggable_id = #{self.id} and taggable_type = '#{self.class}' and user_id = #{user.id}")
Tag.parse(list).each do |name|
if acts_as_taggable_options[:from]
send(acts_as_taggable_options[:from]).tags.find_or_create_by_name(name).on(self, user)
else
Tag.find_or_create_by_name(name).on(self, user)
end
end
end
end
def tag_list
tags.collect { |tag| tag.name.include?(" ") ? "'#{tag.name}'" : tag.name }.join(" ")
end
Usage
item_to_tag.tag_with('ruby rails', current_user)
item_to_tag.save
item_to_tag.tag_list # => 'ruby rails'
current_user.tags # => 'ruby rails'
ItemToTag.find_tagged_with('ruby', current_user)
Note: The tag_with is destructive, it deletes all previous tags.
Update
Previously the tags collection for the User object would return ALL taggings (with duplicates if multiple objects had been tagged with the same tag). Thanks to Ian Li who kindly suggested the use of :select => "DISTINCT tags.*" for the tags has_many relationship in the User class, duplicates are now filtered at the database.
Update II
I’ve replaced the taggings.destroy_all with the following in the tag_with method. This is in response to feedback from Bryan and Anna regarding the destructive nature of the previous tag_with method. Thanks for the comments.
Tagging.destroy_all(" taggable_id = #{self.id} and taggable_type = '#{self.class}' and user_id = #{user.id}")
Since writing this post back in April 2006 I’ve started a new project with slightly different tagging requirements, I’ll create a new article when I’m done and update the content here.
References
Pretty much all of this information came from the Ruby on Rails wiki and Rails Weenie so props to them.
About this entry
You’re currently reading “acts_as_taggable per user tagging,” an entry on Slash Dot Dash
- Published:
- 04.29.06 / 2pm
- Category:
- Ruby on Rails

Comments are closed
Comments are currently closed on this entry.