this page has too many (some contradicting) information, yeah? can someone clean it up?
require ’/Users/josh/Source/rails/activerecord/lib/active_record’
that’s going to be a difficult task for the interpreter, eh?
i thought maybe it’s edge rails (i’m only 1.2.3)?
another e.g.: create_table :taggings do |t| t.integer :tag_id, :taggable_id t.string :taggable_type end
is this the future or a huge mistake?This plugin worked great for me.
I recommend ignoring the warnings below and going straight to the howto – its very easy.
Before you read any of the info below:
I spent two days fooling around with the acts_as_taggable plugin, applying all of the modifications below and it still didn’t work very well. After searching around I found acts_as_taggable_on_steroids which is a complete rewrite, or version 2 of this plugin and works great. I highly recommend it if you want to save yourself from a major headache! Look in the README and Tests for instructions and usage examples.
Is someone actively maintaining this plugin? Why aren’t the cool hacks shown below already part of the plugin? There is also a great tag_cloud method described here
(another post on this plugin can be found at rails.co.za )
(Some french stuff on acts_as_taggable_on_steroids plugin can be read at this address : Nuage de tag avec RubyOnRails)
The Instructions
First, this plugin is different from the acts_as_taggable gem, foremost in that it uses features that are not in Rails 1.0 (like has_many :through and :as). The plugin lets you use tags across models, unlike the gem, and is preferable in most cases. So either upgrade to Rails 1.1, or if you can handle being on the bleeding edge, install the “edge version of rails”: EdgeRails. To switch to EdgeRails, in your application’s main directory do:
rake rails:freeze:edge
To undo this later, just type:
rake rails:unfreeze
Next, install the acts_as_taggable plugin:
script/plugin install acts_as_taggable
Now create a migration to add two tables to store the tagging information:
script/generate migration add_tag_support
Insert the following:
class AddTagSupport < ActiveRecord::Migration
def self.up
#Table for your Tags
create_table :tags do |t|
t.column :name, :string
end
create_table :taggings do |t|
t.column :tag_id, :integer
#id of tagged object
t.column :taggable_id, :integer
#type of object tagged
t.column :taggable_type, :string
end
# Index your tags/taggings
add_index :tags, :name
add_index :taggings, [:tag_id, :taggable_id, :taggable_type]
end
def self.down
drop_table :taggings
drop_table :tags
end
end
rake db:migrate
First create a model that you want to tag. In this example books. So we generate a database table books and generate the model:
script/generate model Book
Next we add the acts_as_taggable line to the modelfile (book.rb):
class Book < ActiveRecord::Base
acts_as_taggable
end
Finally we should have created the tags and taggings tables so we can start tagging:
mybook = Book.new
mybook.tag_with('red library book')
mybook.save
mybook.tag_list #gives back 'red library book'
Book.find_tagged_with('library') #gives your Book
This generates three tags with the names ‘red’, ‘library’ and ‘book’ in the tag table. Next it creates rows in the taggings table with the taggable_type set to ‘Book’ and the tag_id set to the corresponding tag in the tag table. The taggable_id is set to the id of the book in your books table.
Nice huh?
ALSO: The View (forms)
<%= text_field_tag 'tag_list', @item.tags.collect{|t| t.name}.join(" ") %>
if you are using the alias below, you can use this in the view instead and then you don’t have to do the controller-fu below.
<%= text_field 'item', 'tag_list' %>
AND The Controller (if using the default generated scaffolding).
For example in say books_controller.rb add the following to your ‘update’ and ‘create’ methods:
@book.tag_with(params[:tag_list])
- David Andrew Thompson
Question:
If tag_with was named tag_list= it would just work in most controllers/views with normal @model.update(params[:model]) calls, instead of having to do it manually like above.
Is there a reason why its not called tag_list=, or that the method isn’t aliased for this purpose?
If anyone wants to do this, just add:
alias tag_list= tag_with
under the tag_with method in acts_as_taggable.rb
-Nate K.
AND The Fixtures. tags.yml looks like this:
big:
id: 1
name: Big
small:
id: 2
name: Small
taggings.yml looks like this:
big_elephant:
id: 1
tag_id: 1
taggable_id: 50 # Elephant is an Animal record with id 50
taggable_type: Animal
small_beer:
id: 1
tag_id: 1
taggable_id: 20 # Beer is a Beverage record with id 20
taggable_type: Beverage
Note:
This post has some useful information about how the code in /vendor/plugins/acts_as_taggable/lib works.
Note:
I was having issues with the tag.collect putting information in single quotes and on an update that information begin submitted to the database as ”’test” “series’” instead of “test series” so I corrected line 52:
tags.collect { |tag| tag.name.include?(" ") ? "'#{tag.name}'" : tag.name }.join(" ")
so that the tags will be in double quotes and insert into the database correctly:
tags.collect { |tag| tag.name.include?(" ") ? '"' +"#{tag.name}" + '"' : tag.name }.join(" ")
Note:
I needed the tags_count method, so I put “my own” in module SingletonMethods (in acts_as_taggable.rb), like this:
def tags_count(options) sql = "SELECT tags.id AS id, tags.name AS name, COUNT(*) AS count FROM tags, taggings, #{table_name} " sql << "WHERE taggings.taggable_id = #{table_name}.#{primary_key} AND taggings.tag_id = tags.id " sql << "AND #{sanitize_sql(options[:conditions])} " if options[:conditions] sql << "GROUP BY tags.name " sql << "HAVING count #{options[:count]} " if options[:count] sql << "ORDER BY #{options[:order]} " if options[:order] sql << "LIMIT #{options[:limit]} " if options[:limit] find_by_sql(sql) end
@tags = Post.tags_count :conditions => "posts.posttype = 'public'", :order => 'count DESC'
Note:
The tag_with is destructive, it deletes all previous tags which is not always what you want on an edit. I added the following method: EO
def add_tags(list)
Tag.transaction do
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)
else
Tag.find_or_create_by_name(name).on(self)
end
end
end
end
add_tags was a deprecated association method (?!). Thus, no edits to it were working… Change the name of the above to add_tag, add_tag_list, or something similar, and all will be well.
another addition to the above:
In order to prevent multiple additions to taggable on the same tag change this:
Tag.find_or_create_by_name(name).on(self)
t = Tag.find_or_create_by_name(name)
t.on(self) unless self.tags.include? t
Note:
ClassMethod for filtering tags by the Class of the item being tagged. This lets you filter tag to those available for a certain class. Used to assign roles to a users in a system.
It is an ugly hack as it should be an instance method that uses self.class to find out the jind od the class it is after.
def class_tagged(class_type)
@taggings = Tagging.find_all_by_taggable_type(class_type)
@tags = Hash.new
@taggings.each do |tagging|
@tags[tagging.tag.name]= tagging.tag
end
return @tags.values
end
... response to above?
The above can be accomplished with a new ClassMethod. The following allows you to retrieve all unique tags for a given Class (as an array). I am guessing you would like to have this capability if you had multiple models that were acts_as_taggable, and wanted to list the tags for only a certain model. At the time of writing this my application only has one model, so I cannot test with multiple models. —markG
--- acts_as_taggable.rb ---
--- (in module ClassMethods) ---
def tag_list
@taggings = Tagging.find_all_by_taggable_type(ActiveRecord::Base.send(:class_name_of_active_record_descendant, self).to_s)
return @taggings.collect { |tagging| tagging.tag.name }.uniq
end
--- usage ---
Mymodel.tag_list
A Better Solution to Get Tags by Model?
Rather than implementing a whole new method, a line added to the existing self.tag method in tag.rb allows you to retrieve tags by model name.
By adding the following line between lines 4 and 5:
query << " AND taggings.taggable_type = #{options[:taggable_type]}'" if options[:taggable_type] != nil
Our method will now look like this:
def self.tags(options = {})
query = "select tags.id, name, count(*) as count"
query << " from taggings, tags"
query << " where tags.id = tag_id"
query << " AND taggings.taggable_type = #{options[:taggable_type]}'" if options[:taggable_type] != nil
query << " group by tag_id"
query << " order by #{options[:order]}" if options[:order] != nil
query << " limit #{options[:limit]}" if options[:limit] != nil
tags = Tag.find_by_sql(query)
end
We can call this as so:
@tags = Tag.tags(:order => "name", :taggable_type => "post")
I’ve fully documented this in my article, Modifiy Acts_As_Taggable to Return Tags By Model, including both this version and a version that follows Rails conventions more closely.
Note
User-specific acts_as_taggable functionality
Note
I wanted to allow multiple word tags (like “New York Yankees”). This little modification in tags.rb lets you send in an array of anything (preferably strings) to use as your tags, instead of breaking up a string into single-word tags.
def self.parse(list)
unless list.kind_of? Array
tag_names = []
# first, pull out the quoted tags
list.gsub!(/"(.*?)"s*/ ) { tag_names << $1; "" }
# then, replace all commas with a space
list.gsub!(/,/, " ")
# then, get whatever's left
tag_names.concat list.split(/s/)
# strip whitespace from the names
tag_names = tag_names.map { |t| t.strip }
# delete any blank tag names
tag_names = tag_names.delete_if { |t| t.empty? }
return tag_names
else
tag_names = list.collect {|tag| tag.nil? ? nil : tag.to_s}
return tag_names.compact
end
end
The default method find_tagged_with will search for any of the tags, and doesn’t return unique results.
This method will do a strict tag search and only return unique results. (works in mysql… does it work in other db engines?)
def find_tagged_with!(list)
find_by_sql([
"SELECT #{table_name}.* FROM #{table_name} " +
"WHERE #{table_name}.#{primary_key} in (" +
" SELECT taggable_id FROM taggings, tags " +
" WHERE tags.id=taggings.tag_id " +
" AND taggings.taggable_type = ? " +
" AND tags.name IN (?) " +
" GROUP BY taggable_id " +
" HAVING count(tags.id) >= ? " +
" )",
acts_as_taggable_options[:taggable_type], list, list.to_a.length
])
end
The above method find_tagged_with! will behave strangely when duplicate tags are introduced (acts_as_taggable doesn’t check/remove for dupes by default and they’re easy to get in a large list if users are entering tags). Changing the last line of Tag.parse like this solves the issue:
- return tag_names
+ return tag_names.uniq
def find_tagged_with(list)
find_by_sql([
"SELECT #{table_name}.*, COUNT(tags.id) AS count 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 (?)" +
"GROUP BY posts.id ORDER BY count DESC",
acts_as_taggable_options[:taggable_type], list
])
end
The following opens the pagination class back up adds the abilty to filter based on a single tag. Pass a :tag => “tag” and the pagination with be base on though s items that are tagged with it. Belongs in acts_as_taggable.rb. EO
module ActionController
module Pagination
MY_OPTIONS = {
:tag => 'All'
}
DEFAULT_OPTIONS.merge!(MY_OPTIONS) {|key, old, new| old}
alias old_find_collection_for_pagination find_collection_for_pagination
def find_collection_for_pagination(model, options, paginator)
models = old_find_collection_for_pagination(model, options, paginator)
if options[:tag]=="All"
models
else
models.delete_if {|m| !m.is_tagged_with?(options[:tag])}
end
end
end
end
Note
I tried the acts as taggable pagination fix as described above and it didn’t work for me. However, I did solve my problem with the following code when paginating through a list of tags:
def show_all_tags
per_page = 12
if params[:id] then
@tag_title = params[:id]
@tags = params[:id].split.map {|o| CGI::unescape(o)}
@members = Member.find_tagged_with(@tags)
@members_pages, @members = paginate(Member, {:conditions => ['tags.name = ?', @tag_title], :include => [:tags], :per_page => per_page})
else
redirect_to :action => 'list'
end
end
Dave, it probably didn’t work because you also need to add an is_tagged_with? method under the InstanceMethods of acts_as_taggable.rb like this:
module InstanceMethods
...
def is_tagged_with?(tag)
tags.include?(tag)
end
end
Yet Another Pagination Solution for acts_as_taggable
This uses the more powerful AR find method. Replace the find_tagged_with method on the act_as_taggable.rb with:
def find_tagged_with(list, options = {})
find(:all,
:from => "#{table_name}, tags, taggings",
:conditions => ["#{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],
:offset => options[:offset],
:limit => options[:limit],
:order => options[:order])
end
Enjoy,
Sergio Bayona
And another method to paginate
This uses will_paginate and the Paginator gem, described here
Question:
Does anyone have a method for tag clouds with the plugin?
-Doug M.
Answer:
Here ’s a tag cloud that will work with the plugin.
Tom Fakes has an article on creating tag clouds for the acts_as_taggable gem, NOT the plugin.
I have an article with another way to do tag clouds
blog.wolfman.com
Do not try to apply tags to a Model during save. You will end up with a “stack too deep” error. You’re best to apply them :after_save
If you are seeing entries in the tagging table which are being added but the taggable_id is not filled in, it is because you are trying to add the tags too early. Follow the above advice and call tag_with only after the object has been saved.
Question:
I have overidden both acts_as_taggable.rb and tag.rb with slightly different versions in my application. When I make a call to acts_as_taggable from my web app all is peachy. However when I make the same call using script/runner it uses my acts_as_taggable but the original tag.rb. Any ideas? Is there a hard-coded reference to the original tag.rb
Adding count cache field
If you want to keep track and easily display the number of objects the tag is refering to, all you need to do is add the automagic field taggings_count to your tag table and enable cache in the belongs_to field. After that you can get the number of references by querying tag.taggings_count. For full writeup, go here: Counter for acts as taggable
Question:
Post
has_many :comments
Comment
belongs_to :Post, :counter_cache=>true
acts_as_taggable #alos enable counter cache
I find comments_count cannot work properly after added acts_as_taggable plugin
who can tell me why?
Question:
How do you delete a tag?
Question:
How do you validate that a tag was entered when adding from a form?
A. Add the following to tag.rb: validates_presence_of :name
Question:
I tried the plugin. I’m sure a add the line acts_as_taggable in the model I would like to tag. But I got a method missing error when I’m testing tag_with(). Any idea what I missed? THX
A. I fixed it with a server reload.
Question:
How do you use this pluging with STI and record the :taggable_type as the subclass, not the base class? Example….
class Foo < ActiveRecord::Base
end
class SubFoo < Foo
acts_as_taggable
end
In script/console,
c = SubFoo.create(.....)
c.tag_with(“mysubfoo stuff tag”)
Then in the database I execute the query
“select distinct taggable_type from taggings;
mysql output → Foo
Thanks in advance!
Question:
Is there a way to have multiple groups of tags on the same model. Say I want a set of tags that relates to how the model would be displayed in the layout and another that would be used for related links and be actually shown to the user. If I just group them all together I’d end up having a lot of very cryptic tags showing up in my list to the user. I’ve looked and cannot find anything about this.
-Christopher G.