In Files

ActiveRecord::Acts::Taggable::ClassMethods

This mixin provides an easy way for adding tagging capabilities (also known as folksnomy) to your active record objects. It allows you to add tags to your objects as well as search for tagged objects.

It assumes you are using a fully-normalized tagging database schema. For that, you need a table (by default, named tags) to hold all tags in your application and this table must have a primary key (normally a id int autonumber column) and a name varchar column. You must also define a model class related to this table (by default, named Tag).

All tag names will be stored in this tags table. Taggable objects should reside in their own tables, like any other object. Tagging objects is performed by the acts_as_taggable mixin using a has_and_belong_to_many relationship that is automatically created on the taggable class, and as so, a join table must exist between the tags table and the taggable object table.

The name of the join table follows the standards for rails

Unless the join table is explicitly specified as an option, it is guessed using the lexical order of the class names.

The join table must be composed of the foreign keys from the tags table and the taggable object table, so for instance, if we have a tags table named tags (related to a Tag model) and a taggable photos table (related to a Photo model), there should be a join table tags_photos with int FK columns photo_id and tag_id. If you dont use a explicit full model related to the join table (through the :join_class_name option), you must not add a primary key to the join table.

The acts_as_taggable adds the instance methods tag, tag_names, +tag_names= +, +tag_names<< +, +tagged_with? + for adding tags to the object and also the class method find_tagged_with method for search tagged objects.

Examples:

class Photo < ActiveRecord::Base
  # this creates a 'tags' collection, through a has_and_belongs_to_many 
  # relationship that utilizes the join table 'photos_tags'.
  acts_as_taggable :normalizer => Proc.new {|name| name.downcase}
end

photo = Photo.new

# splits and adds to the tags collection
photo.tag "wine beer alcohol" 

# don't need to split since it's an array, but replaces the tags collection
# trailing and leading spaces are properly removed
photo.tag [ 'wine ', ' vodka'], :clear => true  

photo.tag_names # => [ 'wine', 'vodka' ]
# You can remove tags one at a time or in a group
photo.tag_remove 'wine'
photo.tag_remove 'wine beer alcohol'

# appends new tags with a different separator
# the 'wine' tag wont be duplicated
photo.tag_names << 'wine, beer, alcohol', :separator => ','

# The difference between +tag_names+ and +tags+ is that +tag_names+ 
# holds an array of String objects, mapped from +tags+, while +tags+ 
# holds the actual +has_and_belongs_to_many+ collection, and so, is
# composed of +Tag+ objects.
photo.tag_names.size # => 4
photo.tags.size # => 4
# Now you can clear all tags in one call
photo.clear_tags! 

# Find photos with 'wine' OR 'whisky'
Photo.find_tagged_with :any => [ 'wine', 'whisky' ]

# Finds photos with 'wine' AND 'whisky' using a different separator.
# This is also known as tag combos.
Photo.find_tagged_with(:all => 'wine+whisky', :separator => '+'

# Gets the top 10 tags for all photos
Photo.tags_count :limit => 10 # => { 'beer' => 68, 'wine' => 37, 'vodka' => '22', ... }

# Gets the tags count that are greater than 30
Photo.tags_count :count => '> 30' # => { 'beer' => 68, 'wine' => 37 }

# Replace allows you to find_tagged_with, remove the old tags and add the new ones
Photo.replace_tag("beer whisky","wine vodka")
# Display the photos returned from the tags_count call using 9 different CSS classes
<% Photo.cloud(@photo_tags, %w(cloud1 cloud2 cloud3 cloud4 cloud5 cloud6 cloud7 cloud8 cloud9)) do |tag, cloud_class| %>
  <%= link_to(h("<#{tag}>"), tag_photos_url(:name => tag), { :class => cloud_class } ) -%>
<% end %>

# Display the photos returned from the tags_count call using 5 different font sizes
<% Photo.cloud(@photo_tags, %w(x-small small medium large x-large)) do |tag, font_size| %>
  <%= link_to(h("<#{tag}>"), tag_photos_url(:name => tag), { style: => "font-size: #{font_size}" } ) -%>
<% end %>

You can also use full join models if you want to take advantage of ActiveRecords callbacks, timestamping, inheritance and other features on the join records as well. For that, you use the :join_class_name option. In this case, the join table must have a primary key.

class Person
  # This defines a class +TagPerson+ automagically.
  acts_as_taggable :join_class_name => 'TagPerson'
end

# We can open the +TagPerson+ class and add features to it.
class TagPerson
  acts_as_list :scope => :person
  belongs_to :created_by, :class_name => 'User', :foreign_key => 'created_by_id'
  before_save :do_some_validation
  after_save :do_some_stats
end

# We can do some interesting things with it now
person = Person.new
person.tag "wine beer alcohol", :attributes => { :created_by_id => 1 }
Person.find_tagged_with(:any => 'wine', :condition => "tags_people.created_by_id = 1 AND tags_people.position = 1")

Public Instance Methods

acts_as_taggable(options = {}) click to toggle source

This method defines a has_and_belongs_to_many relationship between the target class and the tag model class. It also adds several instance methods for tagging objects of the target class, as well as a class method for searching objects that contains specific tags.

The options are:

The :collection parameter receives a symbol defining the name of the tag collection method and it defaults to :tags.

The :tag_class_name parameter receives the tag model class name and it defaults to +'Tag'+.

The :tag_class_column_name parameter receives the tag model class name attribute and it defaults to +'name'+.

The +:normalizer + paramater takes a Procs. This is used to normalize all tags Simple example :normalizer => Proc.new {|name| name.capitalize}

The :join_class_name parameter receives the model class name that joins the tag model and the taggable model. This automagically defines the join model class that can be opened and extended.

The remaining options are passed on to the has_and_belongs_to_many declaration. The :join_table parameter is defined by default using the standard has_and_belongs_to_many behavior.

# File lib/taggable.rb, line 165
def acts_as_taggable(options = {})

  options = { :collection => :tags, :tag_class_name => 'Tag', :tag_class_column_name => 'name', :normalizer=> Proc.new {|name| name}}.merge(options)
  collection_name = options[:collection]
  tag_model = options[:tag_class_name].constantize
  tag_model_name = options[:tag_class_column_name]
  normalizer = options[:normalizer]
  if tag_model.table_name < self.table_name
    default_join_table = "#{tag_model.table_name}_#{self.table_name}"
  else
    default_join_table = "#{self.table_name}_#{tag_model.table_name}"
  end
  options[:join_table] ||= default_join_table
  options[:foreign_key] ||= self.name.to_s.foreign_key
  options[:association_foreign_key] ||= tag_model.to_s.foreign_key
 
  # not using a simple has_and_belongs_to_many but a full model
  # for joining the tags table and the taggable object table
  if join_class_name = options[:join_class_name]
    Object.class_eval "class #{join_class_name} < ActiveRecord::Base; set_table_name '#{options[:join_table]}' end" unless Object.const_defined?(join_class_name)
    
    join_model = join_class_name.constantize 
    tagged = self
    join_model.class_eval do
      belongs_to :tag, :class_name => tag_model.to_s
      belongs_to :tagged, :class_name => tagged.name.to_s
      define_method(:normalizer, normalizer) 
      define_method(tag_model_name.to_sym) { self[tag_model_name] ||= normalizer(tag.send(tag_model_name.to_sym)) }
    end
    
    
    options[:class_name] ||= join_model.to_s
    tag_pk, tag_fk = tag_model.primary_key, options[:association_foreign_key]
    t, tn, jt = tag_model.table_name, tag_model_name, join_model.table_name
    options[:finder_sql] ||= "SELECT #{jt}.*, #{t}.#{tn} AS #{tn} FROM #{jt}, #{t} WHERE #{jt}.#{tag_fk} = #{t}.#{tag_pk} AND #{jt}.#{options[:foreign_key]} = \#{quoted_id}"
  else
    join_model = nil
  end
  
  # set some class-wide attributes needed in class and instance methods                    
  write_inheritable_attribute(:tag_foreign_key, options[:association_foreign_key])                
  write_inheritable_attribute(:taggable_foreign_key, options[:foreign_key])                
  write_inheritable_attribute(:normalizer, normalizer)                
  write_inheritable_attribute(:tag_collection_name, collection_name)
  write_inheritable_attribute(:tag_model, tag_model)
  write_inheritable_attribute(:tag_model_name, tag_model_name)
  write_inheritable_attribute(:tags_join_model, join_model)
  write_inheritable_attribute(:tags_join_table, options[:join_table])                                      
  write_inheritable_attribute(:tag_options, options)
  
  [ :collection, :tag_class_name, :tag_class_column_name, :join_class_name,:normalizer].each { |key| options.delete(key) } # remove these, we don't need it anymore
  [ :join_table, :association_foreign_key ].each { |key| options.delete(key) } if join_model # dont need this for has_many

  # now, finally add the proper relationships          
  class_eval do
    include ActiveRecord::Acts::Taggable::InstanceMethods
    extend ActiveRecord::Acts::Taggable::SingletonMethods            
    
    class_inheritable_reader :tag_collection_name, :tag_model, :tag_model_name, :tags_join_model, 
                             :tags_options, :tags_join_table,
                             :tag_foreign_key, :taggable_foreign_key,:normalizer
    if join_model
      has_many collection_name, options
    else
      has_and_belongs_to_many collection_name, options
    end
  end                     
  
end

[Validate]

Generated with the Darkfish Rdoc Generator 2.