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.
March 2nd, 2007 at 7:18 pm
[...] of useful functionality. I currently use some great ones like acts_as_authenticated, authorization, acts_as_taggable, and friendly_identifier. By the time anyone actually finds this post, I’ll probably have [...]
April 23rd, 2007 at 6:55 am
The link to download the patch seems broken…
April 23rd, 2007 at 8:26 am
Thanks Peter. I recently migrated the blog and the patch didn’t make it. I brought the patch back and updated the link
June 12th, 2007 at 6:17 pm
Hi Micah,
You and I are using the same thing. find_tagged_with(list,user) is NOT a bug at all
I didnt have your problem however. I think it’s probably because you didn’t set your relationships properly in the models?
June 12th, 2007 at 8:04 pm
@Mike,
I’m not exactly sure what you’re trying to say. I left find_tagged_with(list, user) alone and added a new method that ignores the user for cases where you want to search irrespective of the user. That’s why I said it’s not strictly a bug, but a feature addition instead.
There’s probably a DRY’er way to do it, but I’m still learning.
June 13th, 2007 at 5:56 pm
Hi Micah,
Check out the following :-
http://www.hervalfreire.com/blog/2007/05/07/user-expected-got-user/
July 31st, 2007 at 12:19 am
[...] Midnight Oil » Blog Archive » acts_as_taggable per user from SlashDotSlash, plus a few fixes (tags: rails) [...]
October 8th, 2007 at 5:54 pm
[...] I used the standard legacy acts_as_taggable plugin with the per user tagging modifications and the even further mods. I looked at acts_as_taggable_on_steroids but found that the original one suited my needs better. I [...]
June 19th, 2008 at 7:17 am
[...] by Ariejan de Vroom, acts_as_taggable per user tagging by Ben Smith (and some hiccup fixing on that by Micah Wedemeyer/Ryan Felton), Creating a TagApplicator for acts_as_taggable by Bryan Ray and [...]