Plugins - Acts As Taggable On Steroids
Add to favoritesacts_as_taggable_on_steroids
If you find this plugin useful, please consider a donation to show your support!
http://www.paypal.com/cgi-bin/webscr?cmd=_send-money Email address: jonathan.viney@gmail.com
Instructions
This plugin is based on acts_as_taggable by DHH but includes extras such as tests, smarter tag assignment, and tag cloud calculations.
Installation
ruby script/plugin install http://svn.viney.net.nz/things/rails/plugins/acts_as_taggable_on_steroids
Usage
Prepare database
Generate and apply the migration:
ruby script/generate acts_as_taggable_migration rake db:migrate
Basic tagging
Let’s suppose users have many posts and we want those posts to have tags. The first step is to add acts_as_taggable to the Post class:
class Post < ActiveRecord::Base
acts_as_taggable
belongs_to :user
end
We can now use the tagging methods provided by acts_as_taggable, #tag_list and #tag_list=. Both these methods work like regular attribute accessors.
p = Post.find(:first) p.tag_list # [] p.tag_list = "Funny, Silly" p.save p.tag_list # ["Funny", "Silly"]
You can also add or remove arrays of tags.
p.tag_list.add("Great", "Awful")
p.tag_list.remove("Funny")
Finding tagged objects
To retrieve objects tagged with a certain tag, use find_tagged_with.
Post.find_tagged_with('Funny, Silly')
By default, find_tagged_with will find objects that have any of the given tags. To find only objects that are tagged with all the given tags, use match_all.
Post.find_tagged_with('Funny, Silly', :match_all => true)
See ActiveRecord::Acts::Taggable::InstanceMethods for more methods and options.
Tag cloud calculations
To construct tag clouds, the frequency of each tag needs to be calculated. Because we specified acts_as_taggable on the Post class, we can get a calculation of all the tag counts by using Post.tag_counts. But what if we wanted a tag count for an single user’s posts? To achieve this we call tag_counts on the association:
User.find(:first).posts.tag_counts
A helper is included to assist with generating tag clouds. Include it in your helper file:
module ApplicationHelper
include TagsHelper
end
Here is an example that generates a tag cloud.
Controller:
class PostController < ApplicationController
def tag_cloud
@tags = Post.tag_counts
end
end
View:
<% tag_cloud @tags, %w(css1 css2 css3 css4) do |tag, css_class| %>
<%= link_to tag.name, { :action => :tag, :id => tag.name }, :class => css_class %>
<% end %>
CSS:
.css1 { font-size: 1.0em; }
.css2 { font-size: 1.2em; }
.css3 { font-size: 1.4em; }
.css4 { font-size: 1.6em; }
Caching
It is useful to cache the list of tags to reduce the number of queries executed. To do this, add a column named cached_tag_list to the model which is being tagged. The column should be long enough to hold the full tag list and must have a default value of null, not an empty string.
class CachePostTagList < ActiveRecord::Migration
def self.up
add_column :posts, :cached_tag_list, :string
end
end
class Post < ActiveRecord::Base
acts_as_taggable
# The caching column defaults to cached_tag_list, but can be changed:
#
# set_cached_tag_list_column_name "my_caching_column_name"
end
The details of the caching are handled for you. Just continue to use the tag_list accessor as you normally would. Note that the cached tag list will not be updated if you directly create Tagging objects or manually append to the tags or taggings associations. To update the cached tag list you should call save_cached_tag_list manually.
Delimiter
If you want to change the delimiter used to parse and present tags, set TagList.delimiter. For example, to use spaces instead of commas, add the following to config/environment.rb:
TagList.delimiter = " "
Other
Problems, comments, and suggestions all welcome. jonathan.viney@gmail.com


When I edit an entry, perhaps deleting a tag (sorry I forget the conditions), I sometimes get this. The taggings table has no 'id' column.
ActiveRecord::StatementInvalid (Mysql::Error: #42S22Unknown column 'taggings.id' in 'field list': SELECT
taggings.idAS t0r0,taggings.tag_idAS t0r1,taggings.taggable_idAS t0r2,taggings.taggable_typeAS t0r3,tags.idAS t1r0,tags.nameAS t1r1,tags.descriptionAS t1r2,tags.created_atAS t1r3,tags.modified_atAS t1r4 FROMtaggingsLEFT OUTER JOINtagsONtags.id =taggings.tagid WHERE (taggings.taggableid = 93 AND taggings.taggabletype = 'Encounter' AND (tag_id IN (34))) ): /usr/lib64/ruby/gems/1.8/gems/activerecord-2.0.2/lib/activerecord/connectionadapters/abstract_adapter.rb:150:inlog' /usr/lib64/ruby/gems/1.8/gems/activerecord-2.0.2/lib/active_record/connection_adapters/mysql_adapter.rb:281:inexecute' /usr/lib64/ruby/gems/1.8/gems/activerecord-2.0.2/lib/activerecord/connectionadapters/mysql_adapter.rb:481:inselect' /usr/lib64/ruby/gems/1.8/gems/activerecord-2.0.2/lib/active_record/connection_adapters/abstract/database_statements.rb:7:inselectallwithoutquerycache' /usr/lib64/ruby/gems/1.8/gems/activerecord-2.0.2/lib/activerecord/connectionadapters/abstract/query_cache.rb:53:inselect_all' /usr/lib64/ruby/gems/1.8/gems/activerecord-2.0.2/lib/active_record/connection_adapters/abstract/query_cache.rb:74:incache_sql' /usr/lib64/ruby/gems/1.8/gems/activerecord-2.0.2/lib/activerecord/connectionadapters/abstract/query_cache.rb:53:inselect_all' /usr/lib64/ruby/gems/1.8/gems/activerecord-2.0.2/lib/active_record/associations.rb:1242:inselectallrows' /usr/lib64/ruby/gems/1.8/gems/activerecord-2.0.2/lib/active_record/associations.rb:1124:infind_with_associations' /usr/lib64/ruby/gems/1.8/gems/activerecord-2.0.2/lib/active_record/associations.rb:1122:incatch' /usr/lib64/ruby/gems/1.8/gems/activerecord-2.0.2/lib/active_record/associations.rb:1122:infind_with_associations' /usr/lib64/ruby/gems/1.8/gems/activerecord-2.0.2/lib/active_record/base.rb:1232:infind_every' /usr/lib64/ruby/gems/1.8/gems/activerecord-2.0.2/lib/active_record/base.rb:503:infind' /usr/lib64/ruby/gems/1.8/gems/activerecord-2.0.2/lib/active_record/associations/has_many_association.rb:66:infind' /vendor/plugins/actsas_taggableonsteroids/lib/actsas_taggable.rb:177:insave_tags' /usr/lib64/ruby/gems/1.8/gems/activerecord-2.0.2/lib/active_record/connection_adapters/abstract/database_statements.rb:66:intransaction' /usr/lib64/ruby/gems/1.8/gems/activerecord-2.0.2/lib/active_record/transactions.rb:80:intransaction' /vendor/plugins/acts_as_taggable_on_steroids/lib/acts_as_taggable.rb:175:insave_tags'This is great. original acts_as_taggable did not work for me, but your pluggin does, thank a lot.
@Sebjørn you might get that Uninitialized Constant error if you're using rails2: application config (like TagList.delimiter = ...) needs to go in a file in a config/initializers
NB: would be helpful to update the howto above with this info.
Noticed a small bug in acts_as_taggable.rb in which, if the primary key for your model is not id, the findtaggedwith(string, :match_all => true) will fail out. Quick and easy fix is to change
{tablename}.id to #{tablename}.#{primary_key}
Cheers
For the life of me, I cant get this plugin to behave. It will save to my model's cachedtaglist field 100% of the time, but will not also save the tag association via the tags and taggings tables. The only thing that I can tell is special about my usage is that I am using Single Table Inheritance. Anyone else had this same issue? I'm desperate to know more ... of all the tagging plugins this is the only one supporting STI that I know of.
There is an optimization issue with tag_cloud. You are doing an Inner Join on Posts, which will probably do a full table scan, which is not needed and a problem when that table is rather large.
Here is what you are generating for sql:
SELECT tags.id, tags.name, COUNT() AS count FROM
tagsINNER JOIN taggings ON tags.id = taggings.tag_id INNER JOIN posts ON posts.id = taggings.taggable_id WHERE (taggings.taggable_type = 'Post') GROUP BY tags.id, tags.name HAVING COUNT() > 0 ORDER BY tags.nameShould be:
SELECT tags.id, tags.name, COUNT() AS count FROM
tagsINNER JOIN taggings ON tags.id = taggings.tag_id WHERE (taggings.taggable_type = 'Post') GROUP BY tags.id, tags.name HAVING COUNT() > 0 order by tags.nameHope this is cool to post here as it asks ?'s about the plugin AND rails polymorphism... Also kinda long
I've been using the actsas_taggableon_steroids plugin successfully for awhile with models descended directly from ActiveRecord, but just ran into something strange in a new use case where I have models that are subclasses of other models.
I'm looking for an explanation and a workaround or correction to the way I'm doing things and it occurs to me that someone may have an answer if they have good knowledge about rails' polymorphic operation OR about the way acts_as_taggable works...
Here's my problem -
1) Models -
class Group < ActiveRecord::Base
class Circle < Group
class SupportCircle < Circle
2) I have Circle declared as acts_as_taggable
3) when I tag a Circle, the 'taggings' table record for it has 'Group' for the 'taggable_type' column
Q1: why isn't it 'Circle' for the 'taggable_type'? Q2: where in the code is this assignment of the value done? (I can't seem to find it and assume it's some "under the covers" magic that Rails is doing...?)
4) actsas_taggable.rb's 'findtagged_with' method does the query with a join that has this fragment:
AND #{tablename}taggings.taggable_type = '#{name}'
So, when I do a search for Circle objs tagged with a tag, it fails to find those tagged objs because when I call Circle.findtaggedwith(tag), the 'name' is coming in as 'Circle' while the entry in the table is 'Group' as described above.
Q3: why is the search value inconsistent with the 'tagging' insert's assignment of value for 'taggable_type'?
5) I want to start tagging SupportCircle's and not sure of a few things...
Q4: with an acts_as_ declaration, do I need to explicitly declare that in a subclass or will SupportCircle be taggable because Circle is declared so? Q5: if I want to distinguish between Circles and SupportCircles in my searches, I do separate calls for each with Circle.findtaggedwith(tag) and SupportCircle.findtaggedwith(tag) respectively, and from the look of that query, I'll only get Circles from the first and SupportCircles from the second, right? (yes, I think... just checking 'cause of the unexpected behaviors I'm seeing)
Sorry for the length of this and all the questions, but as I said above, perhaps some of you will know specifics of acts_as_taggable and others may now the general Rails answers and that will help me figure out how to make what I want to do work.
Thanks in advance.
[Juan] > Can I use 'findtaggedwith' method trough associations?
I've just hit the same problem as you with through associations. This is NOT a problem with the plugin but is an issue with rails - see:
http://dev.rubyonrails.org/ticket/6821
this has been fixed in edge rails - version 9022 so you should find that all works fine if you switch to edge rails.
This helped me out with creating and updating the tags in the view.
I haven't figured out how to make them link_to yet though.
http://www.rubyplus.org/episodes/26-SEO-for-Rails-app-using-Acts-as-Taggable-on-Steroids-and-Meta-Tags-Plugins.html
OK, I know this is probably a fairly simple question but bear with me. I've added tags to 2 separate models (topics and posts) successfully from within the console. I'm just struggling to do it within the view context. Controller:
@topic.save end
View (show): <% formfor(:topic, :url => objecturl, :html => { :method => :put }) do |f| %> <p> <label for="topic_title">Add Tag</label> </p> <%= f.text_field :tags %>
<%= submit_tag "add tag" %> <% end %>
It's giving me the error "can't convert String into Array". So I'm guessing it's not actually using the tag_list.add method.
Any suggestions :) Cheers, Dan
Hi.
Great plug-in, but I have a problem with setting the delimiter. I am a novice in the ruby/rails-world.
I get the error "rake aborted! uninitialized constant TagList" when trying to do "rake db:migrate" when I add the line TagList.delimiter = " " to environment.rb. If I comment out the line, everything works fine.
Do you have any idea? I am pretty sure it is something stupid that I just haven't understood ;)
I'm new to Rails so I apologize if this is pretty basic.
First, I'd like the thank you for an awesome plugin. It works flawlessly and has been very simple for me with one exception...
How do I return the actual number of times a tag is used rather than the name of the tag? For instance:
Url.tag_counts will return the tag names to be used in a tag cloud. I'd like to actually know that tag1 was used X amount of times and tag2 was used Y amount of times, etc.
Any suggestions would be greatly appreciated.
tags made of a phrase of two words, such as "rain hats", when used in a linkto where id is the tag, result in a broken link on rails 2.0.1 as the link has a space in it. Is this a problem with linkto url encoding? How are we to compensate for this?
Another problem I found: In PostgreSQL 'LIKE' clause is case-sensitive :( 'ILIKE' is the case-insensitive version. It is not a big problem for me, as I can just do this little modification in the code but I just thought you might want to know that.
That is a great plugin Jonathan. To make an even greater use of it I need to make several models taggable. But now, is there a simple way to get combined tag counts for all of my models? Thanks
Right now when I try to add a validation to a tag (without trying to specify any extra associations than the ones already made by the plugin) and it fails to validate I get the following error:
Cannot associate new records through 'Advice#taggings' on '#'. Both records must have an id in order to create the has_many :through record associating them.
Advice is the model I'm using it on.
How would you go about validating individual tags? I find that I can't use validates_associated on my models because I don't have any associations declared. The way the tags and taggings models are built though it doesn't seem like there's an easy way to declare associations. Is there a way to declare them easily? Is there another way to validate each individual tag?
Please ignore my post on 18 Feb 2008 for about savecachedtag_list. I found that it is fixed in the latest version.
Disregard my previous comment.. that behavior was being caused by the same entities being assigned the same tag multiple times in the taggings table... not sure how that happened.
It might be prudent to change the count being done by matchall from COUNT(*) to something like COUNT(DISTINCT #{tablename}.id) to avoid problems like that...
I get the impression that :match_all => true is pretty broken right now. For example...
I have 200 or so records with the tag "tag1".
Model.findtaggedwith('tag1') will return all 200, but Model.findtaggedwith('tag1', :matchall => true) returns a single row. Model.findtaggedwith('tag1,tag2', :matchall => true) should return no rows, but instead returns them all.
I have two models Region and Product:
Region has_many :products Product belongs_to :region
When I do this:
@region.products.findtaggedwith("casa")
I get:
ActiveRecord::StatementInvalid: Mysql::Error: Unknown column 'productstags.name' in 'where clause': SELECT products.* FROM products INNER JOIN productsregions ON products.id = productsregions.productid WHERE ((productsregions.regionid = 1)) AND ((products_tags.name LIKE 'casa'))
Can I use 'findtaggedwith' method trough associations?
For the savecachedtaglist function in actsastaggable.rb. Just found that if remove the last tag for some model. The cachedtaglist will not be updated to empty string because the condition "and !taglist.blank?" will disallow the cachedtaglist to update
I'd like to get 5 results with the most tags in common with the current model. How should I go about doing this?
Migration not working!
I installed acts_as_taggable and found this after looking for help on getting that working. Running migrate does not create any tables. I get no errors though.
I'm using Rails 2.0
Fix Tag.destroy_unused for Rails 2.0
Its easy enough to add yourself, but I think the find method should accept and merge :select and :joins options. Cant see any reason not to, and I very quickly found a reason to do.
This is a very useful plugin. I use it to classify very large amount of items.
I often to find items that match some tags and donot match some other tags. does plugin support:
foobar.findtaggedwith(:include => ["a","c"], :exclude => ["b"] (to find something mathcs "a,c" but "b") ???
as far as i remeber, some previous version support foobar.findtaggedwith("a,c,!b") to find items was tagged with "a,c" but wasnot tagged with "b".
Thank you for replies
The plugin is good, thanks. But, I need a way to count and show the number of taggables assigned to each tag. Also, I want to know if you is working in updates for acts_as_taggable?
I think that the functionality is broken (at least under RoR 2.0.x). MyEntity.taglist always returns arrays of Strings and not arrays of Tags, so the count-method is not available on the TagList's items and tagcloud fails.
using the tag cloud as mentioned in the view example will miss-up your tags.
so instead of using: <pre>%w(css1 css2 css3 css4)</pre>
use a hash: <pre>["css1", "css2", "css3", "css4"]</pre>
Stoney, I found that that test will pass if you add this line to savetags in actsas_taggable.rb at line 175.
<pre> oldtags.each{|tag| tag.destroy} if Tag.destroyunused </pre>
See this post:
<a href="http://blog.s21g.com/genki?tag=bug">http://blog.s21g.com/genki?tag=bug</a>
working from example below, I added this to actsas_taggable to find related tags. Not fully tested so use with caution. I modeled it closely after tagcounts <code> # Calculate the tag counts for all related tags. def relatedtagcounts(tags, options = {}) tags = TagList.from(tags) if tags.is_a?(String)
Tag.find(:all, findoptionsforrelatedtag_counts(tags, options)) end
</code.
With Rails 2.0.1, this plugin fails testtagdestroyedwhenunused in actsas_taggabletest.rb. I don't see why this would be. Any advice?
Thought of a feature request in the course of using this plugin. It would be nice if taggings had an additional association attribute, like user_id, so that the ownership of tags can be determined. That way, tag deletion could be limited to the tag author.
<p>How in the world do you add on to existing tags using a form? My solution works, but I feel like it's not ideal.</p> <p>So my acts_as_taggable model is a Place. In the form, I have something like this:</p> <code> <%= textfield("place", "taglist") %> </code>
<p>Then in the create method I have:</p> <code>
we just need a temp place object to grab the tag_list the user entered
<br> @temp_place = Place.new(params[:place]) <br>
find the existing place
<br> @place = Place.find(some id I use to find it) <br>
add on to its tag_list with the new tag list the user entered
<br> @place.taglist.add(@tempplace.tag_list) <br>
save the place, thus updating the tag_list
</code> <p>The Place model isn't my primary model in the form, hopefully that explains why I did some things like I did. Can anyone suggest a different way of adding on to the tag_list? Thanks!</p>
Mario, make sure you have the line 'include TagsHelper' in your applicationhelper.rb file *and* make sure that you don't have a helper file named tagshelper.rb in your helpers directory, which would interfere with the TagsHelper from the plugin.
Jaime, something like this should get you somewhere near:
Model.tag_counts :limit => 100, :order => 'count desc'
Hi, I have an application with more than 100.000 tags (a web spider), and I would like to show a tag cloud, but the method explained above will generate a cloud for all the tags (Model.tag_counts), and that wouldn't work for me as I have so many tags.
So, how can I limit the number of tags on the tag cloud? I would like to show, say, the 100 most popular tags. Or maybe paginate the tag cloud, ordered by popularity or any other atribute?
Thanks
Any suggestions for getting a tagcount based off of, for instance, posts made by multiple users? I find that if I do something like category.posts.tagcounts, it works great. But if I do something like Post.find(:all, :conditions => whatever) or get a collection of posts via a sql query, I get an exception, "undefined method tag_counts for Array". Thanks!
[22 October 2007]
Great plugin! I just noticed that if you use eager loading with :match_all that it doesn't return the tag intersection like it would if you leave off eager loading...
Resource.findtaggedwith(tags, :match_all => true) # works
Resource.findtaggedwith(tags, :include => [:tags], :match_all => true) # doesn't work
[15 October 2007]
Make findtaggedwith correctly apply :conditions
Add Tag.destroy_unused option.
Thanks Michal, I've applied a fix.
Hi, i found a bug when using :conditions and finding by multiple tags. The plugin creates SQL like "condition AND firsttagcontition OR secondtagcondition" which should be "condition AND (firsttagcontition OR secondtagcondition)".
I also extended my copy of this plugin a bit because my language is not safe-chars which i coul use in uri so i quickly set up an attribute called stringid a modified it so i can select tags by their stringids (which are safe chars usable in url). Ordinary languages are not safe chars so you may consider this a feature request.
Good work guys, keep it up!
First of all, thx for the nice plugin! However, I just tried to get it to work with latest rails (rev >= 7873) and it seems as if the call to 'findor_createwithlikebyname' @line 158 in actsastaggable.rb doesn't work anymore? (I'm getting undefined method error). I changed it to 'findorcreateby_name' and it works now (obviously without the 'like' sql stuff, if that's what it was meant for :)
Any ideas?
Christian,
That's the behaviour I needed for a project a hile back. I don't mind adding an option to remove unused tags though. Send through a patch if you like.
Thanks for this great plug-in. However I have problems deleting tags. Using object.tag_list.remove(params[:tag]), only removes the row from the tagging table but not from the tag table. Any ideas???
Yuval,
You can use optionsforfindtaggedwith to generate the find options, and then use those with pagination.
Another request :) extend findtaggedwith with pagination, so you can make a single call that returns a paginator and result set for the current page, using the submitted options.
The tag cloud helper wasn't working for me, producing one tag with a maximum index while everything else was stuck at min. I used the existing tag_counts property but used the tag cloud helper found here http://www.juixe.com/techknow/index.php/2006/07/15/acts-as-taggable-tag-cloud/ and it seems to work as expected. I may have missed something.
There is a little mistake on the tag_cloud example. On this line:
<%= linkto tag.name, { :action => :tag, :id => name }, :class => cssclass %>
It should be:
<%= linkto tag.name, { :action => :tag, :id => tag.name }, :class => cssclass %>
That is :id => tag.name (not just "name")
P.S. New parse option works great. Cheers.
Thanks Jonathan! I'll try out the latest rev. My concern with the remove method is that it depends on a tag name, which could be any combination of querystring-destroying values. I'd rather use the ID, hence my going directly to an object's taggings.
Thanks beltrachi, I've corrected the readme.
Yuval,
Your method for constructing a list of tags is fine. You could use tag_list instead of tags to take advantage of caching if you are using it.
To remove a tagging, you could pass in the tag name rather than the id, and then use tag_list.remove
object = Object.find(params[:id]) object.tag_list.remove(params[:tag]) object.save
The API for TagList#add has been changed. The changelog has the details. I also justed added a shortcut for parsing a tag list that you want to add.
object.tag_list.add(params[:whatever], :parse => true)
Cheers, -Jonathan.
[2 October 2007]
Add :parse option to TagList#new and TagList#add.
tag_list = TagList.new("One, Two", :parse => true) # ["One", "Two"]
tag_list # ["One", "Two"] tag_list.add("Three, Four", :parse => true) # ["One", "Two", "Three", "Four"]
Remove TagList#names.
I continue to have problems with this plugin, particularly after the last update, and sparse documentation has me scratching my head. Does anyone by chance have a fully operational and documented implementation online anywhere for code references?
Specifically, I need to do the following:
Get a formatted list of an object's tags. I am using object.tags.map {|tag| "#{taglink(tag)}"}.join(" | ") where taglink is a helper that returns a formatted search link for the tag. This works fine, but I'm not sure if it's correct.
Delete a specific tag for an object. Using a similar map as above, I am generating delete links that pass in the ID of the object and the id of the tag, then in the controller doing: Object.find(params[:objectid]).taggings.findbytagid(params[:tag_id]).destroy. As above, this works fine, but seems too verbose.
Where I am having problems is in adding new tags for an object that has pre-existing tags. Before the latest revisions, I was doing Object.find(params[:id]).tag_list.add(TagList.parse(params[:whatever])) but this no longer seems to work, and instead adds everything I enter as a single tag, even though they're seperated by commas.
Any help appreciated.
There is a little error on the tag_cloud view:
<% tagcloud @tags, %w(css1 css2 css3 css4) do |tag, cssclass| %> <%= linkto tag.name, { :action => :tag, :id => '''tag.'''name }, :class => cssclass %> <% end %>
Anyway, congratultations for this plugin!
[29 September 2007]
[27 September 2007]
Okay, I figured out my problem with match_all. Under Postgres, if you use a GROUP BY clause, all returned columns must either be in the GROUP BY or be in aggregate functions. From the docs:
When GROUP BY is present, it is not valid for the SELECT list expressions to refer to ungrouped columns except within aggregate functions, since there would be more than one possible value to return for an ungrouped column.
The solution is a small fix to acts_as_taggable.rb. Around line 62. Rearrange the block that generates the GROUP BY clause such that it adds all of the table's columns.
if options.delete(:match_all) group = "#{taggingsalias}.taggableid" column_names.each do |colname| group += ", #{table_name}.#{colname}" end group += " HAVING COUNT(#{taggingsalias}.taggableid) = #{tags.size}" end
http://bealetech.com/articles/2007/09/26/actsas_taggableonsteroids-matchall-and-postgres
:matchall doesn't seem to work for me. The query it generates doesn't work against Postgres 8.2. Basically, I have a model called NewsRelease, which has "actsastaggable". Using NewsRelease.findtagged_with("test test2") generates the following query:
SELECT DISTINCT newsreleases.* FROM newsreleases LEFT OUTER JOIN taggings newsreleasestaggings ON newsreleasestaggings.taggableid = newsreleases.id AND newsreleasestaggings.taggabletype = 'NewsRelease' LEFT OUTER JOIN tags newsreleasestags ON newsreleasestags.id = newsreleasestaggings.tagid WHERE (newsreleasestags.name LIKE 'test' OR newsreleasestags.name LIKE 'test2') GROUP BY newsreleasestaggings.taggableid HAVING COUNT(newsreleasestaggings.taggableid) = 1
Unfortunately, Postgres doesn't like that syntax. Specifically, it says:
ERROR: column "news_releases.id" must appear in the GROUP BY clause or be used in an aggregate function
Any thoughts?
RE: global_tag_counts
Just to put you in the user context : observe tags that are related to this article. Don't you think that it should be great to know which of these are most used ? (ok, there's a lot of them are used for evil...)
@jonathan : could you add a tags_helper.rb generator, principally for cloud helper that are wide used.
Thanks for all ;)
Hi, it's about Tag cloud calculations.
I needed to only show tags from ONE post, but with GLOBAL tags calculation (to show how much tags of a given post are popular)
Example : "tag1"(post 1,2,3)=3 "tag2"(post 1,2)=2 "tag3"(post 2)=2. I would like global counts for tags of (post 1), so "tag1"(3) and "tag2"(2).
Obviously, Post.tag_counts(:conditions => "taggings.taggable_id=#{@post_1.id}") return "tag1"(1) and "tag2"(1) (ok, each tag is uniq for a given post)
So, there's maybe (not) a good SQL query for that, but I decided to use Post.tag_counts result, and then 'filter' via ruby condition ( self.tag_list.include? tag[:name]) on collect.
I put it as an InstanceMethods in Taggable module. Then you can ask : @post.global_tag_counts
Here's the code : http://pastie.caboo.se/100963
And here's a Tagshelper : http://pastie.caboo.se/100968
I think it becomes slow with many tags, but I didn't observe any counter-perf up to 1000 tags. Ruby's not so slow ;)
Regards,
[17 September 2007]
[12 September 2007]
Make the TagList class inherit from Array.
BACKWARDS INCOMPATIBILITY:
TagList#initialize, TagList#add, and TagList#remove no longer accept array arguments.
Old: TagList.new(["One", "Two"]) New: TagList.new("One", "Two")
Deprecate obsolete TagList#names.
Yuval, i think that you should use object.tag_list.names to get array of object's tags. I've been also searching today for this method and found that one. It works, but in my opinion TagList class inherit Array class and implement Enumerable. Then instead of "names" we could simply run "each" method.
Please adjust you documentation. It should be
TagList.delimiter = " "
instead of
Tag.delimiter = " "
Thanks Jonathan, that makes more sense now: use tag_list when saving tags, but directly referce object.tags when displaying/parsing tags. Cheers.
Yuval:
If you directly modify the taggings or tags associations you need to take care of the caching yourself by calling savecachedtaglist. The taglist accessor is designed to be used by a text input on a form.
John N:
The second example you give has two problems. First, the accessor is called taglist not tagnames. Secondly, you need to save the post after setting the tag_list.
I have found some behavior I believe to be inconsistent. Consider the following two test cases:
def testdoubletag_passes p = Post.create(:text => "Test", :tag_list => "one, one, two") p.save
end
def testdoubletag_fails p = Post.create(:text => "Test") p.save p.tag_names = "one, one, two"
end
Seems like the should both pass.
Further to my last comment, I've ended up just using object.tags rather than object.tag_list, as it seems to return a proper collection. Is this bad form with regards to this plugin? Does it bypass the caching? Cheers.
Hey guys, this may come across as a newb question. That's because I'm a newb. Anyway, I've saved a class by setting its taglist and can successfully search on tags and retrieve an object's tags. However, what I want to do is parse those tags and apply a specific format/url to each of them. It appears that object.taglist is a string. Do I need to do my own splitting and parsing or is there a way of getting back the tag list as an array? Cheers.
Bryce, please send through a failing test case. jonathan.viney@gmail.com
Great update Jonathan. I noticed one thing that doesn't quite work right anymore. If you pass a :conditions option to findtaggedwith the WHERE clause logic doesn't work correctly because the tag matching conditions are not encapsulated (so the ORs override anything you put in the conditions). I fixed this quickly by changing
conditions << tags.map { |t| sanitizesql(["#{tagsalias}.name LIKE ?", t]) }.join(" OR ")
to
conditions << "( " + tags.map { |t| sanitizesql(["#{tagsalias}.name LIKE ?", t]) }.join(" OR ") + " )"
in acts_as_taggable.rb.
[26 August 2006]
[25 August 2007]
[23 August 2007]
Make search comparisons case-insensitive across different databases. [Moisés Machado]
Improve compatiblity with STI. [Moisés Machado]
I'm getting Todd's error also: "when I try to do the "prepare database" step, I get some error like "Mysql::Error: Table *.tags' doesn't exist: SHOW FIELDS FROM tags"
Anyone know why? Seems like a bootstrapping bug in the plugin.
Having trouble with x.tag_list.remove(params[:untagme]) x.save and end up with an error
Was successful in adding several a thousand tags with
So now sure where problem might be.
Mysql::Error: Unknown column 'id' in 'where clause': DELETE FROM taggings WHERE
id= NULLHi Guys can anyone suggest a way to add two independant sets of tags for a model? thanks in advance
Hi, great Plugin!
I think it would be usefull to eliminate commas before adding a single tag via tag_list.add():
tag_list.add(params[:tag][:name].gsub(/\,/, ' '))
I have a little problem. In my tag cloud, when I have a tag with a period(es: "web2.0"), and I send this param to Contoller, when I print this param in the controller, became "web2". Can you help me? Thanks
A really nice feature would be to add caching functionality for objects with related tags. So instead of having to call findtaggedwith and run a huge query (especially if your DB is large and your objects have lots of common tags -- and also right now it's impossible to get random results without find every object first), it would already have the related object taggable_id's cached somewhere. I'd work on this myself but my ruby/sql-fu isn't strong enough yet.
Can this be used on the User model? I want users to be able to tag themselves - as well as posts.
I have blogged about the method I use to paginate actsas_taggable with willpaginate.
http://blog.wolfman.com/articles/2007/07/30/paginating-actsas_taggable-with-willpaginate
Here's one of the things I suggested, though I haven't tested it entirely.
Post.relatedtagcounts(tags) returns the tag counts for all posts tagged with tags, excluding the tags not in tags.
def relatedtagcounts(tags) tags = if tags.is_a?(String) TagList.from(tags).names else tags.dup end
I'm not sure if this functionality exists or doesn't, but it would be nice:
Something like: TaggableType.find(whatever).tag_counts returns the union of all the tags found in that set.
Or... something like: Tag.findrelatedtagcounts(taglist) Outputs the tag counts for all tags not in the list that are applied to all taggables tagged by the tags in the list
Plugin changes:
Respect custom table names for the Tag and Tagging classes.
Fix the :exclude option for findtaggedwith
can someone please give me a hint to get it to work with activescaffold?
I asked this once, but I think it got lost in the shuffle, does anyone have any experience with using this in a per-user way. I want to integrate this with the restful_authentication plugin and have each tag scoped to a user. Thanks for any help!
anyone knows how to get it work with the plugin "will_paginate"?
Thanks for a great plugin. As I was customizing your code, I ran across the :exclude option of the findoptionsfortaggedwith() function. My tests show that it doesn't work.
I believe the problem is the logic of the following statement: conditions = sanitizesql(["#{tablename}_tags.name #{"NOT" if options.delete(:exclude)} IN (?)", tags])
Think about it, simply adding a NOT won't do the job if a taggable item has more than one tag.
I hack the file lib/acts_as_taggable.rb,replace the code as : { :select => "DISTINCT #{table_name}.*", :joins => "LEFT OUTER JOIN taggings #{tablename}taggings ON #{tablename}taggings.taggableid = #{tablename}.#{primarykey} AND #{tablename}taggings.taggabletype = '#{name}' " + "LEFT OUTER JOIN tags #{tablename}tags ON #{tablename}tags.id = #{tablename}taggings.tag_id", :conditions => conditions, :group => group }.update(options)
to:
hack by RainChen @ 2007-7-19 16:40
I saw there were some places like these needed bo be changed.Hope you could deal with these issue and update to the svn.
you forgot the tablenameprefix config. I use it in my app.
Can the tags be scoped to a user so that a user has their own tags and clouds? Can anyone give me an example of how you would do this?
Plugin changes:
Dimitry, been working on this today myself. To get the counts by tag (ignoring any associations):
tagcounts = TaggedModel.tagcounts() tag_counts.each {|x| p x.name, x.count}
This gets you the count tags for tags associated with this model.
There are other options you can give tag_counts - check the source code.
Cheers, --Kip
Is there any way to get count information for a Tag? I do tag = Tag.find_by_name("tagname"), but tag.count always returns 0.
I'd like to do this most efficient way too (with caching if possible).
Any ideas? Been at it all day Thanks!
I may be misusing this awesome tool, but when I tag an object with something and then another users tags it, my original tag is replaced by anything the second user adds. Any suggestions?
Here is my code:
When trying to user the plugin with one table I always get a "false" as response when I try to save the model, e.g. p= Product.find(:first) p.tag_list="Golf"
p.save ==> false p.reload.tag_list => #<TagList:0x320094c @names=[]>
The tables Tags and Taggings are empty after this experiment.
Please help!
By the way, if you drop and recreate a blank (no tables) development database, you can recreate this error. I do this regularly, using a remigrate script that I created to ensure I have a fully working migration set.
I have the same error as aurels and Todd Lee from 20 June 2007. The problem happens when you run rake migrate on a project using AATOS when you have a blank database. I have traced it to line 8 of tag.rb, which contains "delegate :delimiter, :delimeter=, :to => TagList"
If you comment out that line, the migration will load. It has something to do with the model loading and looking for relationships before the tables are created.
Thanks!
Hello.
I get the same problem as Todd Lee posted on 20 Jun 2007, could the cause be found? For me, I'm on Ubuntu Linux with Rails Edge.
Thanks from Belgium!
Plugin changes:
Joe, I can't reproduce your problem. Can you email me a failing test case?
Quick bug report, and hopefully a good fix - if a user has a tag list that looks like: "foo, bar" and changes the case of a tag, i.e. to "Foo, bar", the case-changed tag is dropped, so the resulting tag list would be "bar".
From a code perspective, what's happening is this:
In save_tags:
There are a few options to fix it. One is simply to reverse the delete and the add - first delete, then add.
Another, and I'm not sure how the performance would compare - i'm not sure whether DB string matching is faster than Ruby string matching, would be to delete all tags then add all tags
Is it possible to create two kinds of tags? For example, I would like to be able to tag an object with Description tags in one form field and then Misc. tags in another form field. All for the same class/object.
I've changed the SQL. Oshoma, can you try it again with Postgres and email me with the results.
Other plugin changes:
Add validation to Tag model.
findoptionsfortaggedwith should always return a hash.
findtaggedwith passing in no tags should return an empty array.
Improve compatibility with PostgreSQL.
Thanks, -Jonathan.
testtagcounts_* unit tests all fail on my machine. I believe this is PostgreSQL-specific; Postgres complains, "Exception: PGError: ERROR: column "count" does not exist". I am running PostgreSQL 8.2, rails 1.2.3, and the 2007-06-21 version of your plugin. I can send you the query SQL if need be.
Potential fix: in actsas_taggable.rb, replace "count" with "count(*)" in the two lines containing the :atleast and :at_most options. This eliminates the Postgres complaint and the tests subsequently pass.
I am no SQL expert so this is worth further verification.
Thanks for working on this plugin, it's good stuff.
I've fixed the extra .rb on the file name.
I'm not sure why your migration isn't working. Please email me with more information.
Hi, when I try to do the "prepare database" step, I get some error like "Mysql::Error: Table *.tags' doesn't exist: SHOW FIELDS FROM tags"
This looks like it happens when I start from scratch.(I never used acts_as_taggable plugin)
When I comment out all rows in "init.rb", it works. Am I doing something wrong?
And one minor thing.. when "script/generate actsas_taggablemigration" is run, the generated migration file name has .rb twice(003actsastaggablemigration.rb.rb)
I'm using windows xp/rails 1.2.3.
More changes:
These changes were reasonably substantial so I'd appreciate it if a few people could test them out so we can get bugs ironed out quickly. Please email me directly with any findings.
Just updated the new Rev. 246 and some tags are gone but some are not.
Anyone else having these problems?
Recent changes:
Enjoy! -Jonathan.
It would be great if you could include the schema for the tables used by this plugin. It is getting more difficult to find information on the DHH plugin and its docs now that it appears to be deprecated and/or no longer maintained. Thanks.
As I can get the list an тегов for обекта in rhtml file? @message.tag_list returns ощибку, but in script/console this works well
This plugin is great, I wrote some french help on how to setup the plugin and build a tag cloud.
<a href="http://www.stoneageblog.com/articles/2007/06/03/nuage-de-tag-avec-ruby-on-rails-acts_as_taggable/"> Nuage de tag avec Ruby On Rails</a>
Adding on the idea of herval, I made a simple change which was sufficient for us so that we dont touch db during the read usages of tags:
In acts_as_taggable.rb
Added the following to Module ClassMethods
beforesave :savecaches
Added the following method to module InstanceMethods def save_caches
if defined?(@tag_list)
self.tagscache = @taglist end
end
Added a column tags_cache in the table for the model where u need tags.
Then whenever u need to do only a read, use tags_cache on the model.
Hey,
Make sure your tagging_type column is sufficiently long enough to contain your taggable object names!
I had a class object named ChannelBlogEntry which was stored as ChannelBlog.
This might save you 30/40 minutes.
adding caching to the plugin: http://hervalicio.us/blog/2007/05/25/caching-things-with-actsas_taggableon_steroids/
great and handy piece of work, by the way :)
hi! I also wanted to use this plugin, but it produces the same error as hari (2 may 2007) describes!
Please provide some help to me/us!
Please, help! When I use this plugin, and write
acts_as_taggable
after thew name of the instance the error "Illegal fuction call" occures. It can'y see the methods from the plugion. What should I do?
Is it possible to count tags related to another tag with this plugin as it is? That is, find objects tagged with something, and then count those objects other tags and construct a tag cloud from that.
I thought something like Foo.findtaggedwith('tag').tag_counts would do it, but it doesn't work.
Plugin changes:
I've created a scalable version of this plugin. It keeps track of count in a variable instead of repeating db column entries. Also it brings back the add_tag feature from DHH. check it out at http://rubyforge.org/projects/scalabletagging/
The code change needed is as follows (hopefully someone can check this in.. hint: hint: ::) <code> vendor/plugins/actsas_taggableonsteroids/lib/actsas_taggable.rb 74c74
< group_by = 'tags.id, tags.name having count(*) > 0'
> group_by = ' tags.name having count(*) > 0' </code>
the following:<code> def testphotosetshouldhavetag_counts @photoset.tag_list = "crazy, lady, crazy" @photoset.save assert Photoset.tag_counts y Photoset.tag_counts end </code> Gives me:
<code>
Why would i get this when doing Photoset.tag_counts. Shouldn't hot have one entry and a count of "2" instead of "1"?
i just added these two methods: module InstanceMethods def untag(tag_name) taglist = self.taglist.split(', ') taglist.delete(tagname.include?(',') ? "\"#{tagname}\"" : tagname) @taglist = taglist.join(', ') end def tag(tag_name) @taglist = "#{self.taglist}, #{tagname.include?(',') ? "\"#{tagname}\"" : tag_name}" end end
they make life much easier - using the tag_name for tagging/untagging.
Nice plugin. I have just downloaded and got it up and running. I have just changed the tag delimeter to be a space (Tag.delimeter=" ").
This saves the tags correctly. However when you read them back again with tag_list they are comma separated again.
eg article.tag_list = "Ruby Rails" => "Ruby Rails" article.save => true article.tag_list => "Rails, Ruby" article.taglist = article.taglist => "Rails, Ruby" article.save => true article.tag_list => "Ruby, \"Rails,\""
the read_tags method seems to still have comma functionality hardcoded into it. I think it should be looking at the new definition in Tag
def read_tags tags.collect do |tag| tag.name.include?(Tag.delimiter) ? "\"#{tag.name}\"" : tag.name end.join(Tag.delimiter) end
Plugin changes:
I've just finished implementing it on my first Rails project and it works like a charm.
My only gripe though is that the tags are csv. It would be good to see this plugin with space delimited tags.
Thanks for making this plugin available!
First off, great plug-in. Glad to see a new choice for tagging. I'm starting to use it now on one of my sites and it's working great.
However, I agree with Matt. Marshalling and unmarshalling into and out of csv is cumbersome. I would prefer to deal with the tags as arrays and then front-end filter/present them as best works for my sites.
I also wish it was easily configurable for the Tag.parse method whether to split on spaces or commas, whether to allow quoted strings, etc. I have modified the code for my purposes, but now I'll have to manually merge plug-in updates.
Still, great plug-in and really glad you made it available!
For those looking for the schema to use to store the tags, you can use the schema defined in vendor/plugins/actsas_taggableon_steroids/test/schema.rb after installing the plugin.
Actually you will only need the first two tables. Add it to a migration and you are done.
Hi there.. Great plugin, very useful. I'm using it in a project now. I'm finding one part of it to be a little hard to use though. Whenever I want to get or set the tags, like to display to the front end or to set the tag list from an input box, I have to do a lot of prep on the data to get it to work in the CSV style for io. Wouldn't it be a whole lot easier if the interface to get and set tags were an array instead of a pseudo CSV string? I think I'm going to change the code to do that for us, but I wanted to suggest it to be part of the main distribution..
Any way it goes, thanks for writing and releasing this!
there is a minor bug, I tried to fix, but my little Ruby knowledge won't let... the bug is that when you put repeated tags when creating a new taggable model object, they all get added to the database, so a user could type the same tag 20 times and that tag will be top list in the tag cloud.
how to fix it? anyone got a sugestion?
tag = tag.uniq didn't work
The SQL in findtaggedwith does not work with eager finds because of table name conflict in the constructed query. For example - Model.findtaggedwith(tag, :include => :tags) fails with a SQL error.
This is easily fixed by specifying aliases for the tables in SQL ("LEFT OUTER JOIN taggings tt ...").
It would be cool if it could remove duplicate tags on an object e.g. if a user entered "fish, chips, peas, peas" only 1 peas tag would be recorded for that object.
This plug in is really very nice but the only thing that I dint like is that you cannot have any separator other than ',' and I wanted to separate the tags with space.
I am new to ruby on rails but one thing I really like about it is convention over configuration, but it seems to be missing here.
I hope you will modify this plug in soon..
Actually, I think I've figured it out now that I took the time to look at what the method was returning in the source :). Thanks for the write up.
Sorry, stupid question here, but I just came across AaToS and I'm having trouble getting from where this posts leaves off to a tag cloud. Do I still need to define a "tag cloud" method as in the basic AaT?
It appears that my URL below got a little tweaked due to the formatting that this blog uses.. Didn't think to try code blocks on it. Anyhow, if you look at the URL you can see where it is missing the "_" characters in the acts as taggable on steriods URL.. Or, I guess you could just to go to the site and search for it.
Since folks have commented on the lack of database schema, however no one has taken a second to post it for others after eventually finding it, I thought this would be a good chance for me to get some karma credit..
http://www.dotrb.com/articles/2007/01/28/actsas_taggableon_steroids-database-schema
Jamie, it appears to be back up. In case you need to contact him in the future though the email is: jonathan .dot. viney @at@ gmail .dot. com
The svn server for this plugin has been down for a while now, and I can't find any way to contact the plugins owner because his site has gone like the repository.
This plugin is cool. The only thing missing is the database structure (you have to google to find it).
But, I even got pagination working, with tag searching
page = (params[:page] ||= 1).to_i itemsperpage = 10 offset = (page - 1) * itemsperpage
@document_count=Tag.find(:first, :conditions=>["name = ?",@params[:tag]]).taggings.count
@documentpages = Paginator.new(self,@documentcount, itemsperpage, page) @documents=Document.findtaggedwith(@params[:tag], :limit=>itemsperpage, :offset=>offset,:order=>'created_at DESC')
I recommend that if you call this "acts as taggable on steroids" you simply improve upon the functionality of acts_as_taggable (hence, putting it on steroids), rather than creating an entirely different plugin.
You are confusing this with DHH's plugin. The proper way to add tags with this plugin is:
@foo.tag_list = "mytag, two words, red"
Hi, I tried the plugin. I'm sure a add the line actsas_taggable in the model I would like to tag. But I got a method missing error when I'm testing tagwith(). Any idea what I missed.
THX