Agile Web Development

Build it. Launch it. Love it.

Acts as taggable redux

ActsAsTaggableRedux
===================

Allows for user owned tags to be added to multiple classes, and makes tags easier to work with.


Prerequisites
=============

Install Edge Rails before you get started so you get RESTful routing.

ActsAsTaggableRedux depends on database tables to store tagging information.  Create the migration for these tables with this command:
  
  rake acts_as_taggable:db:create
  
Then run the migration to create the tables with this command:
  
  rake db:migrate
  
Also you will need to add this to your user model:
  acts_as_tagger
  
OPTIONAL: The helper functions assume the pressence of a tags controller, that is what the tag clouds and tags will link to.
  
OPTIONAL: To pretty up tag clouds and lists you can generate an example stylesheet with this command:

  rake acts_as_taggable:stylesheet:create

and then include this in your layouts that have tag clouds:

  <%= stylesheet_link_tag 'acts_as_taggable_stylesheet' %>


Example
=======

The following is an example of how you might integrate tags with an Item model.

config/routes.rb
  may.resource :items, :tags
  
  
app/views/items/new.erb
  New Item
  
  <% form_for(:item, @item) do |f| -%>
    
    <%= error_message_for :item %>
    
    Tags: <%= f.text_field :tag_list -%>
    
    <%= submit_tag "Save" -%>
    
  <% end -%>
  
if you want users to own taggings change the tags line to this
  Tags: <%= f.text_field :tag_list, :value => @item.tag_list(user) -%>
and add this line beneath it
  <%= f.hidden_field :user_id, :value => user.id -%>
  
app/views/items/show.erb
  Item tagged with: 
  <% item.tags.each do |tag| -%>
    <%= link_to_tag(tag) %>
  <% end -%>
  
app/views/items/edit.erb
  New Item
  
  <% form_for(:item, @item, :html => { :method => :post }) do |f| -%>
  
    <%= error_messages_for :item %>
    
    Tags: <%= f.text_field :tag_list -%>
    
    <%= submit_tag "Save" -%>
  
  <% end -%>


app/controllers/items_controller.rb
  class ItemController < ApplicationController
    def new
      @item = Item.new
    end
    
    def create
      @item = Item.new(params[:item])
      
      respond_to do |format|
        if @item.save
          flash[:notice] = 'Item was successfully created.'
          format.html { redirect_to item_url(@item) }
          format.xml  { head :created, :location => item_url(@item) }
        else
          format.html { render :action => "new" }
          format.xml  { render :xml => @item.errors.to_xml }
        end
      end
    end

    def show
      @item = Item.find(params[:id], :include => :tags)
    end
    
    def edit
      @item = Item.find(params[:id])
    end
    
    def update
      @item = Item.find(params[:id])
      
      respond_to do |format|
        if @item.update_attributes(params[:item])
          flash[:notice] = 'Item was successfully updated.'
          format.html { redirect_to item_url(@item) }
          format.xml  { head :updated, :location => item_url(@item) }
        end
          format.html { render :action => "edit"}
          format.xml  { render :xml => @item.errors.to_xml}
      end
    end
  end



Tag clouds
==========

Tag clouds are created by a helper function, and depend on the counter cache to get fast accurate counts.  To ensure this keeps working properly, don't add new tags to a taggable in any way other than using the tag.tag(taggable) style.  This will ensure that the caches don't lose track.  Also, see the prerequisites for installing the stylesheet so that the tag cloud actually looks like a tag cloud.  Otherwise, just pop into a view that you want the tag cloud to appear and type this:

  <%= tag_cloud %>
  
  
  
Copyright (c) 2007 monki(Wesley Beary), released under the MIT license

Vitals

Home http://github.com/geemus/acts_as_taggable_redux
Repository http://github.com/geemus/acts_as_taggable_redux
License Rails' (MIT)
Tags Tag_red ddddd folksonomy
Rating (14 votes)
Owner monki
Created 16 May 2007

Comments

  • Avatar
    Sat
    16 May 2007

    I found this plugin to be much better than acts_as_taggable and acts_as_taggable_on_steroids. It also has the DB optmization of yet another plugin out there included.

    Monki, thanks for putting this out there. I just had one glitch, the tag_cloud method is not working in my view. It gives me a method not found error.

    Can you point out what is wrong? BTW I am a RoR noob.

    Thanks Sat

  • Avatar
    16 May 2007

    Could you provide the whole error message? Feel free to post it or email it to me and I will see what I can do.

  • Sat
    17 May 2007

    I am trying to display the tag cloud in a view. This view is for a model that is taggable.

    Here is the error message as requested:

    undefined method `tag_url' for #<#<Class:0x483cc9c>:0x483cc74>

    Extracted source (around line #40):

    37: <%= submit_tag 'Save' %> 38: </fieldset> 39: <% end -%> 40: <%= tag_cloud %>

    Thanks Sat

  • Avatar
    17 May 2007

    That means RESTful routes aren't getting setup properly. You will need EDGE rails, and a proper routes.rb

    For edge, the easy way: rake rails:freeze:edge Or you can use svn/externals (google for 'edge rails svn')

    Then make sure your routes.rb includes: map.resource :tags

  • thurin
    22 May 2007

    I have it working with Rails 1.2.3 using:

    map.resources :tags

    in my routes.rb. The only issue i see is that when clicking a tag either from the cloud of from the tag list on an item i get the following error:

    "uninitialized constant TagsController"

    I assume I need to write a TagsController to handle looking for items that match the tag?

  • Avatar
    26 May 2007

    I suppose you wouldn't have to have a tags controller, but the helpers are written with that assumption. (Thanks for pointing it out, I'll add it to the README).

  • Sat
    16 June 2007

    Thanks Thurin, that worked for me too.

    Is there a way to filter the tag cloud for the current user? I am using acts_as_authenticated, so I can identify the current user any time.

    Thanks in advance for all the help

  • begin
    10 July 2007

    how i can integrate tags with an Item model and a user model? I have it working with Rails 1.2.3 using: i add this to my user model: acts_as_tagger but give me an error when i create item: NoMethodError in ItemsController#create undefined method `tag_list=' for #<Item:0x47fe848>

    then i add "acts_as_taggable" to my item model, ArgumentError in ItemsController#create Wrong number of arguments (1 for 0)

    new.rhtml: <b>Tags:</b> <%= f.text_field :tag_list, :value => @item.tag_list(current_user) -%> <%= f.hidden_field :user_id, :value => current_user.id -%>

    I am using acts_as_authenticated, help me please.

  • Avatar
    11 July 2007

    I would say the issue is probably in your ItemsController, I'd need to see the code for that. Feel free to send me an email and I can probably help sort you out.

  • Chris
    18 July 2007

    It looks like it parses the tags by spaces versus commas, is there a way to switch that behavior so that it looks at the commas and groups words together?

  • Avatar
    21 July 2007

    I haven't provided an easy option for switching the token delimiter at this point I'm afraid. It should be pretty easy to switch. The portion in question is in acts_as_taggable_redux/lib/tags.rb in the parse function.
    To prevent commas being replaced by spaces, comment out: list.gsub!(/,/, " ")

    Then change the split around whitespace: tag_names.concat(list.split(/\s/))

    To a split around commas: tag_names.concat(list.split(/,/))

    And you should be set. I may have to add more explicit options for this kind of thing in the future.

  • Avatar
    aroedl
    30 July 2007

    @Sat:

    Make sure that your controller is called "Tags" and not "Tag" and that you have

    map.resources :tags
    

    (not map.resource) in routes.rb.

  • Avatar
    Sat
    15 August 2007

    I see that there is a new version that can handle user specific tags. How do I upgrade? Run script/plugin install --force? Is there any need to run db:migrate again when upgrading?

    TIA for your help

  • Avatar
    23 August 2007

    I think script/plugin install --force would certainly get you the newest version. You shouldn't need to run db:migrate again if you ran it before. Theoretically the user specific stuff was all there before in the database, just some of the functions weren't working quite right for it. After script/plugin you should be good to go, feel free to email me if it gives you problems and I will get the fixed up as soon as I can. Thanks for using the plugin, hope it is helpful.

  • Avatar
    Sat
    1 September 2007

    How does one display a tag_cloud for the current_user?

    Thanks Sat

  • Avatar
    Hari
    7 September 2007

    I would like to search Items by tags,

    So I created a TagsController,

    so that on a request like /tags/23 from the tag cloud, I can create a show method in TagsController that can do the find_tagged_with and return the Items.

    How do I make this happen? As such, I cannot call find_tagged_with from the TagsController.

    TIA

  • Avatar
    ben
    17 January 2008

    Thanks for the plugin! A few additions is it helps anyone:

    1) Need to add "acts_as_taggable" to model being tagged (items in your example)

    2) Modified find_tagged_with to work with will_paginate plugin

        def find_tagged_with(tags, options = {}, page = 1, per_page = 10)
          options.assert_valid_keys([:match, :user])
    
          tags = Tag.parse(tags)
          return [] if tags.empty?
    
          group = &quot;#{table_name}_taggings.taggable_id HAVING COUNT(#{table_name}_taggings.taggable_id) = #{tags.size}&quot; if options[:match] == :all
          conditions = sanitize_sql([&quot;#{table_name}_tags.name IN (?)&quot;, tags])
          conditions += sanitize_sql([&quot; AND #{table_name}_taggings.user_id = ?&quot;, options[:user]]) if options[:user]
    
          paginate(:all, 
            { 
              :select =&gt;  &quot;DISTINCT #{table_name}.*&quot;,
              :joins  =&gt;  &quot;LEFT OUTER JOIN taggings #{table_name}_taggings ON #{table_name}_taggings.taggable_id = #{table_name}.#{primary_key} AND #{table_name}_taggings.taggable_type = '#{name}' &quot; +
                          &quot;LEFT OUTER JOIN tags #{table_name}_tags ON #{table_name}_tags.id = #{table_name}_taggings.tag_id&quot;,
              :conditions =&gt; conditions,
              :group  =&gt;  group,
              :page =&gt; page,
              :per_page =&gt; per_page
            })
        end
    

    3) Modified helper for better seo - added this to ActsAsTaggableHelper

    def tag_url(tag)

    &quot;/tags/#{tag.name}&quot;
    

    end

    4) Added delicious styled tagging form helper to do this: a) add this to ActsAsTaggableHelper def delicious_tags(obj, f)

    res = %(&lt;br/&gt;&lt;b&gt;Tags:&lt;/b&gt; #{f.text_field :tag_list})
    res &lt;&lt; %(&lt;div style=&quot;border:solid 1px; margin:10px; padding:10px;&quot;&gt;)
    res &lt;&lt; spaced_tags_delicious(obj)
    res &lt;&lt; &quot;&lt;/div&gt;&quot;
    return res
    

    end

    def spaced_tags_delicious(obj)

    res = []
    Tag.find(:all, :order =&gt; &quot;name&quot;).each do |tag|
      link = %(javascript:swap_tag('#{tag.name}','#{obj.class.name.tableize.singularize}_tag_list'))
      res &lt;&lt; link_to(tag.name, link)
    end
    return res.join(&quot; &quot;)
    

    end

    b) include tags.js

    //modified selections from http://del.icio.us/ui/static/post.js?v=3o3

    tag_delimitor = " "

    function swap_tag(tag,elem) {

    swap_tag(tag,elem,true)
    

    } function swap_tag(tag,elem,change_case){

    if (change_case == true) {
        tag = trim(tag).toLowerCase();
    }
    else {
        tag = trim(tag)
    }
    tags = document.getElementById(elem)
    var tagArray = trim(tags.value).split(tag_delimitor)
    var present = false;
    if (trim(tagArray[0]) == '') tagArray.splice(0,1);
    for (t=0; t&lt;tagArray.length; t++) {
        if (trim(tagArray[t]).toLowerCase() == tag || (change_case == false &amp;&amp; trim(tagArray[t]) == tag) ) 
        { 
          tagArray.splice(t,1); 
          present=true;
          t-=1;  
        }
    }
    if (!present) { tagArray.push(tag); }
    var content = tagArray.join(tag_delimitor)
    //tags.value = (content.length &gt; 1) ? content + ', ' : content
    tags.value = content;
    //focusTo(tags)
    

    }

    function trim(str) { return str.replace(/^\s|\s$/g,""); }

    c) Call from your form like

                &lt;%=delicious_tags (@blog_entry, f)%&gt;
    

    4) Also added a simple "spaced tag" helper to ActsAsTaggableHelper to put tags in blog footers (or whatever)

    def spaced_tags(obj)

    res = []
    obj.tags.each do |tag|
      res &lt;&lt; link_to_tag(tag)
    end
    return res.join(&quot; &quot;)
    

    end

  • Avatar
    ben
    17 January 2008

    Or - if the moderator prefers - here are changes I made in a blog post http://mudabone.com/aietc/?p=762

  • Avatar
    Hari
    25 January 2008

    How can I generate a tag cloud per user? Is it possible?

    Thanks

  • Matt V
    27 July 2008

    to get the migration generator to work with rails 2.1, I had to change tasks/acts_as_taggable_tasks.rake and add

    require 'environment'

    before require 'rails_generator'

    hat tip to: http://d.hatena.ne.jp/kusakari/20080721/1216634680

  • jsyrjala
    18 September 2008

    It seems that the svn repository is not updated. There seems to be an updated repository in github: http://github.com/monki/acts_as_taggable_redux/

  • Avatar
    renuka
    13 February 2009

    Hi,

    I want to rename tags so can anyone say me how can i do that? which function should i use?

    Thanks in advance :)

  • 26 February 2009

    FYI, This plugin is references and used in the sample application contained in the book "Ruby on Rails Bible"

  • Avatar
    James Mak
    10 August 2009

    Hi,

    I'm following the tutorial in Tim Fisher's book Ruby on Rails Bible to use this plugin. Building the acts_as_taggable DB migration file as instructed in chapter 8 resulted in the "uninitialized class variable @ @ configuration in Rails" error.

    I goolged and attempted the most common suggestion: adding require 'environment' inside the plugin's rakefile, to no avail.

    Could anyone help? Thanks a lot!

    Below is the rake output with --trace:


    rake acts_as_taggable:db:create --trace
    (in /home/james/ror_proj/book_shelf)
    rake aborted!
    uninitialized class variable @@configuration in Rails /usr/local/lib/ruby/gems/1.8/gems/rails-2.3.3/lib/initializer.rb:20:in configuration' /usr/local/lib/ruby/gems/1.8/gems/rails-2.3.3/lib/rails_generator/lookup.rb:109:inuse_component_sources!' /usr/local/lib/ruby/gems/1.8/gems/rails-2.3.3/lib/rails_generator/lookup.rb:55:in included' /usr/local/lib/ruby/gems/1.8/gems/rails-2.3.3/lib/rails_generator.rb:38:ininclude' /usr/local/lib/ruby/gems/1.8/gems/rails-2.3.3/lib/rails_generator.rb:38:in send' /usr/local/lib/ruby/gems/1.8/gems/rails-2.3.3/lib/rails_generator.rb:38 /usr/local/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:31:ingem_original_require' /usr/local/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:31:in require' /usr/local/lib/ruby/gems/1.8/gems/activesupport-2.3.3/lib/active_support/dependencies.rb:156:inrequire' /usr/local/lib/ruby/gems/1.8/gems/activesupport-2.3.3/lib/active_support/dependencies.rb:521:in new_constants_in' /usr/local/lib/ruby/gems/1.8/gems/activesupport-2.3.3/lib/active_support/dependencies.rb:156:inrequire' /home/james/ror_proj/book_shelf/vendor/plugins/acts_as_taggable_redux/tasks/acts_as_taggable_tasks.rake:2 /usr/local/lib/ruby/gems/1.8/gems/rake-0.8.7/lib/rake.rb:1882:in in_namespace' /usr/local/lib/ruby/gems/1.8/gems/rake-0.8.7/lib/rake.rb:910:innamespace' /home/james/ror_proj/book_shelf/vendor/plugins/acts_as_taggable_redux/tasks/acts_as_taggable_tasks.rake:1 /usr/local/lib/ruby/gems/1.8/gems/activesupport-2.3.3/lib/active_support/dependencies.rb:145:in load_without_new_constant_marking' /usr/local/lib/ruby/gems/1.8/gems/activesupport-2.3.3/lib/active_support/dependencies.rb:145:inload' /usr/local/lib/ruby/gems/1.8/gems/activesupport-2.3.3/lib/active_support/dependencies.rb:521:in new_constants_in' /usr/local/lib/ruby/gems/1.8/gems/activesupport-2.3.3/lib/active_support/dependencies.rb:145:inload' /usr/local/lib/ruby/gems/1.8/gems/rails-2.3.3/lib/tasks/rails.rb:7 /usr/local/lib/ruby/gems/1.8/gems/rails-2.3.3/lib/tasks/rails.rb:7:in each' /usr/local/lib/ruby/gems/1.8/gems/rails-2.3.3/lib/tasks/rails.rb:7 /usr/local/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:31:ingem_original_require' /usr/local/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:31:in require' /home/james/ror_proj/book_shelf/Rakefile:10 /usr/local/lib/ruby/gems/1.8/gems/rake-0.8.7/lib/rake.rb:2383:inload' /usr/local/lib/ruby/gems/1.8/gems/rake-0.8.7/lib/rake.rb:2383:in raw_load_rakefile' /usr/local/lib/ruby/gems/1.8/gems/rake-0.8.7/lib/rake.rb:2017:inload_rakefile' /usr/local/lib/ruby/gems/1.8/gems/rake-0.8.7/lib/rake.rb:2068:in standard_exception_handling' /usr/local/lib/ruby/gems/1.8/gems/rake-0.8.7/lib/rake.rb:2016:inload_rakefile' /usr/local/lib/ruby/gems/1.8/gems/rake-0.8.7/lib/rake.rb:2000:in run' /usr/local/lib/ruby/gems/1.8/gems/rake-0.8.7/lib/rake.rb:2068:instandard_exception_handling' /usr/local/lib/ruby/gems/1.8/gems/rake-0.8.7/lib/rake.rb:1998:in run' /usr/local/lib/ruby/gems/1.8/gems/rake-0.8.7/bin/rake:31 /usr/local/bin/rake:19:inload'

    /usr/local/bin/rake:19

Add a comment