I followed the instructions on SlashDotSlash to modify the acts_as_taggable plugin by DHH to allow for per user tagging.
Unfortunately, there were a few hiccups that had to be dealt with. I’ll try to lay them out here.
Note: If you just want to jump right in, look for the patch at the bottom of the post.
1. User expected, got User
I don’t know what this error is about. It is some sort of type-mismatch that I don’t understand. However, I do know how to fix it (I think).
Edit the Tag.rb file and replace the on method with the following:
def on(taggable, user)
taggings.create :taggable => taggable, :user_id => user.id
end
As you can see, I simply replaced the :user with :user_id. This explicitly sets the ID field rather than relying on Rails to infer it. This solved the problem for me, although it did not shine any light on what was happening. Comments?
2. Duplicate tagging on polymorphic models
I was having a problem where tags were multiplying on certain polymorphic models. For example, I would add the tag “tag1″ to an object. Then, I would edit the object and add “tag2″ When I saved, the final list would be “tag1 tag2 tag1″ Editing and re-saving would result in “tag1 tag2 tag1 tag1 tag2 tag1″ Obviously, the tags were multiplying.
It didn’t take too long to figure out that they were not being destroyed as they should before the new tags were added. The tag_with method is supposed to destroy all previous tags and then add the new ones. To fix this, replace the Tagging.destroy_all call in tag_with to the following:
Tagging.destroy_all(” taggable_id = #{self.id}” +
” and taggable_type = ‘#{acts_as_taggable_options[:taggable_type]}’” +
” and user_id = #{user.id}”)
The problem was that DHH’s original acts_as_taggable plugin handled polymorphic models by setting their taggable_type to the base class (immediate child of ActiveRecord::Base). This is because Single Table Inheritance guarantees that all these polymorphic objects will be in the same table and have different IDs with respect to that table. Somewhere during the rewrite on SlashDotSlash, this must have been missed. So, the taggable_type was being set to the base class, but the destroy call was looking for objects of the actual instance class, and therefore missing all the taggings.
3. find_tagged_with
Not strictly a bug, but a feature addition. The find_tagged_with method on SlashDotSlash was altered to require a user. This is good in cases where you want to retrieve tags on a user basis, but sometimes you just want all objects with a tag, regardless of the user. So, I made a simple addition and inserted find_tagged_with_by_user and left the original find_tagged_with alone.
Here are the methods:
# Finds objects tagged with any of a list of tags, tagged by a specific user
def find_tagged_with_by_user(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
# Finds objects tagged with any of a list of tags.
def find_tagged_with(list)
find_by_sql([
"SELECT #{table_name}.* FROM #{table_name}, tags, taggings " +
"WHERE #{table_name}.#{primary_key} = taggings.taggable_id " +
"AND taggings.taggable_type = ? " +
"AND taggings.tag_id = tags.id AND tags.name IN (?)",
acts_as_taggable_options[:taggable_type], list
])
end
SVN Patch
Here is a patch that goes from DHH’s original acts_as_taggable to my updated version. I wish I could make one that goes from the SlashDotSlash version, but I neglected to tag that version separately from other stuff, so I can’t really get back to a pristine version.
Note: This patch does everything except update your database tables. Details for that can be found in the SlashDotSlash article.
NO WARRANTY ON THIS PATCH! I am not responsible for anything that happens as a result of you applying this patch to your codebase!
My thanks go out to DHH for creating acts_as_taggable as well as RoR (duh!). Also, thanks to Ben at SlashDotSlash for posting his additions that added the per-user tagging that I desperately wanted.
Recent Comments