<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DZone Snippets: acts_as_taggable code</title>
    <link>http://snippets.dzone.com/posts</link>
    <pubDate>Sun, 27 Jul 2008 00:57:15 GMT</pubDate>
    <description>DZone Snippets: acts_as_taggable code</description>
    <item>
      <title>Extending acts_as_taggable to take scope into account</title>
      <link>http://snippets.dzone.com/posts/show/3110</link>
      <description>The acts_as_taggable plugin is great and so useful. But it discards scope, and it doesn't work like any other model attribute in your controller views [using normal @model.update(params[:model]) calls].&lt;br /&gt;&lt;br /&gt;I have changed it to:&lt;br /&gt;* add an alias tag_list= for tag_with (found in the "rails wiki":http://wiki.rubyonrails.com/rails/pages/ActsAsTaggablePluginHowto),&lt;br /&gt;* updated find_tagged_with and count_tagged_with to use scope (after reading an article from Jamis on "ActiveRecord::Base.find":http://weblog.jamisbuck.org/2006/11/20/under-the-hood-activerecord-base-find-part-2)&lt;br /&gt;&lt;br /&gt;Scope is used when doing this for instance:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;  Message.with_scope :find =&gt; {:conditions =&gt; ["project_id = ?", @project]} do&lt;br /&gt;    @messages = Message.find_tagged_with(tag.name)&lt;br /&gt;  end&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Then only the messages tagged with tag.name that belong to project @project.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;*acts_as_taggable.rb*&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;module ActiveRecord&lt;br /&gt;  module Acts #:nodoc:&lt;br /&gt;    module Taggable #:nodoc:&lt;br /&gt;      def self.included(base)&lt;br /&gt;        base.extend(ClassMethods)  &lt;br /&gt;      end&lt;br /&gt;      &lt;br /&gt;      module ClassMethods&lt;br /&gt;        def acts_as_taggable(options = {})&lt;br /&gt;          write_inheritable_attribute(:acts_as_taggable_options, {&lt;br /&gt;            :taggable_type =&gt; ActiveRecord::Base.send(:class_name_of_active_record_descendant, self).to_s,&lt;br /&gt;            :from =&gt; options[:from]&lt;br /&gt;          })&lt;br /&gt;          &lt;br /&gt;          class_inheritable_reader :acts_as_taggable_options&lt;br /&gt;&lt;br /&gt;          has_many :taggings, :as =&gt; :taggable, :dependent =&gt; true&lt;br /&gt;          has_many :tags, :through =&gt; :taggings&lt;br /&gt;&lt;br /&gt;          include ActiveRecord::Acts::Taggable::InstanceMethods&lt;br /&gt;          extend ActiveRecord::Acts::Taggable::SingletonMethods          &lt;br /&gt;        end&lt;br /&gt;      end&lt;br /&gt;      &lt;br /&gt;      module SingletonMethods&lt;br /&gt;        def find_tagged_with(list)&lt;br /&gt;          tagged_with list&lt;br /&gt;        end&lt;br /&gt;&lt;br /&gt;        def count_tagged_with(list)&lt;br /&gt;          tagged_with list, :count&lt;br /&gt;        end&lt;br /&gt;&lt;br /&gt;        # DRY sql query and handle scope&lt;br /&gt;        protected&lt;br /&gt;        def tagged_with(list, type = :find)&lt;br /&gt;          if type == :count&lt;br /&gt;            sql = "SELECT COUNT(DISTINCT #{table_name}.#{primary_key}) AS cnt "&lt;br /&gt;          else&lt;br /&gt;            sql = "SELECT DISTINCT #{table_name}.* "&lt;br /&gt;          end&lt;br /&gt;          sql &lt;&lt; "FROM #{table_name}, tags, taggings "&lt;br /&gt;&lt;br /&gt;          conditions = [&lt;br /&gt;            "#{table_name}.#{primary_key} = taggings.taggable_id " +&lt;br /&gt;            "AND taggings.taggable_type = ? " +&lt;br /&gt;            "AND taggings.tag_id = tags.id AND tags.name IN (?) ",&lt;br /&gt;            acts_as_taggable_options[:taggable_type], list&lt;br /&gt;            ]&lt;br /&gt;          add_conditions!(sql, conditions)&lt;br /&gt;&lt;br /&gt;          result = find_by_sql(sql)&lt;br /&gt;          (type == :count) ? result.first.cnt.to_i : result&lt;br /&gt;        end&lt;br /&gt;&lt;br /&gt;      end&lt;br /&gt;      &lt;br /&gt;      module InstanceMethods&lt;br /&gt;        def tag_with(list)&lt;br /&gt;          Tag.transaction do&lt;br /&gt;            taggings.destroy_all&lt;br /&gt;&lt;br /&gt;            Tag.parse(list).each do |name|&lt;br /&gt;              if acts_as_taggable_options[:from]&lt;br /&gt;                send(acts_as_taggable_options[:from]).tags.find_or_create_by_name(name).on(self)&lt;br /&gt;              else&lt;br /&gt;                Tag.find_or_create_by_name(name).on(self)&lt;br /&gt;              end&lt;br /&gt;            end&lt;br /&gt;          end&lt;br /&gt;        end&lt;br /&gt;&lt;br /&gt;        # allow using active record update_attributes() and others by using tag_list to set&lt;br /&gt;        # and read the tag list&lt;br /&gt;        alias tag_list= tag_with&lt;br /&gt;&lt;br /&gt;        def tag_list&lt;br /&gt;          tags.collect { |tag| tag.name.include?(" ") ? "'#{tag.name}'" : tag.name }.join(" ")&lt;br /&gt;        end&lt;br /&gt;      end&lt;br /&gt;    end&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;/code&gt;</description>
      <pubDate>Fri, 08 Dec 2006 21:49:54 GMT</pubDate>
      <guid>http://snippets.dzone.com/posts/show/3110</guid>
      <author>zlaj (mathieu l)</author>
    </item>
    <item>
      <title>Extending acts_as_taggable for real-world requirements</title>
      <link>http://snippets.dzone.com/posts/show/2918</link>
      <description>acts_as_taggable is cool, but it doesn't easily support limits, offsets, tag intersections, arbitrary conditions, etc.. so I created some unit tests and started to extend it. This is where I'm at so far:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;module ActiveRecord&lt;br /&gt;  module Acts #:nodoc:&lt;br /&gt;    module Taggable #:nodoc:&lt;br /&gt;      def self.included(base)&lt;br /&gt;        base.extend(ClassMethods)  &lt;br /&gt;      end&lt;br /&gt;      &lt;br /&gt;      module ClassMethods&lt;br /&gt;        def acts_as_taggable(options = {})&lt;br /&gt;          write_inheritable_attribute(:acts_as_taggable_options, {&lt;br /&gt;            :taggable_type =&gt; ActiveRecord::Base.send(:class_name_of_active_record_descendant, self).to_s,&lt;br /&gt;            :from =&gt; options[:from]&lt;br /&gt;          })&lt;br /&gt;          &lt;br /&gt;          class_inheritable_reader :acts_as_taggable_options&lt;br /&gt;&lt;br /&gt;          has_many :taggings, :as =&gt; :taggable, :dependent =&gt; true&lt;br /&gt;          has_many :tags, :through =&gt; :taggings&lt;br /&gt;&lt;br /&gt;          include ActiveRecord::Acts::Taggable::InstanceMethods&lt;br /&gt;          extend ActiveRecord::Acts::Taggable::SingletonMethods          &lt;br /&gt;        end&lt;br /&gt;      end&lt;br /&gt;      &lt;br /&gt;      module SingletonMethods&lt;br /&gt;        def find_tagged_with(list, options = {})&lt;br /&gt;          local_options = { :limit =&gt; 1000, :offset =&gt; 0 }.merge(options)&lt;br /&gt;          find_by_sql([&lt;br /&gt;            "SELECT DISTINCT #{table_name}.* FROM #{table_name}, tags, taggings " +&lt;br /&gt;            "WHERE #{table_name}.#{primary_key} = taggings.taggable_id " +&lt;br /&gt;            "AND taggings.taggable_type = ? " +&lt;br /&gt;            "AND taggings.tag_id = tags.id AND tags.name IN (?) #{"AND (#{local_options[:conditions]})" if local_options[:conditions]} LIMIT ? OFFSET ?",&lt;br /&gt;            acts_as_taggable_options[:taggable_type], list, local_options[:limit], local_options[:offset]&lt;br /&gt;          ])&lt;br /&gt;        end&lt;br /&gt;        &lt;br /&gt;        def count_tagged_with(list, options = {})&lt;br /&gt;          local_options = {}.merge(options)&lt;br /&gt;          find_by_sql([&lt;br /&gt;            "SELECT COUNT(DISTINCT #{table_name}.#{primary_key}) AS cnt FROM #{table_name}, tags, taggings " +&lt;br /&gt;            "WHERE #{table_name}.#{primary_key} = taggings.taggable_id " +&lt;br /&gt;            "AND taggings.taggable_type = ? " +&lt;br /&gt;            "AND taggings.tag_id = tags.id AND tags.name IN (?) #{"AND (#{local_options[:conditions]}) " if local_options[:conditions]}",&lt;br /&gt;            acts_as_taggable_options[:taggable_type], list&lt;br /&gt;          ]).first.cnt.to_i&lt;br /&gt;        end&lt;br /&gt;&lt;br /&gt;        def find_tagged_with_intersecting(list, options = {})&lt;br /&gt;          local_options = { :limit =&gt; 1000, :offset =&gt; 0 }.merge(options)&lt;br /&gt;          find_by_sql([&lt;br /&gt;            "SELECT DISTINCT #{table_name}.* FROM #{table_name}, tags, taggings " +&lt;br /&gt;            "WHERE #{table_name}.#{primary_key} = taggings.taggable_id " +&lt;br /&gt;            "AND taggings.taggable_type = ? " +&lt;br /&gt;            "AND taggings.tag_id = tags.id AND tags.name IN (?) #{"AND (#{local_options[:conditions]}) " if local_options[:conditions]} GROUP BY #{table_name}.id HAVING COUNT(#{table_name}.id) = #{list.size} LIMIT ? OFFSET ?",&lt;br /&gt;            acts_as_taggable_options[:taggable_type], list, local_options[:limit], local_options[:offset]&lt;br /&gt;          ])&lt;br /&gt;        end&lt;br /&gt;&lt;br /&gt;        def count_tagged_with_intersecting(list, options = {})&lt;br /&gt;          local_options = {}.merge(options)&lt;br /&gt;          find_by_sql([&lt;br /&gt;            "SELECT COUNT(*) AS cnt FROM (SELECT #{table_name}.#{primary_key} AS cnt FROM #{table_name}, tags, taggings " +&lt;br /&gt;            "WHERE #{table_name}.#{primary_key} = taggings.taggable_id " +&lt;br /&gt;            "AND taggings.taggable_type = ? " +&lt;br /&gt;            "AND taggings.tag_id = tags.id AND tags.name IN (?) " +&lt;br /&gt;            "#{"AND (#{local_options[:conditions]})" if local_options[:conditions]} " +&lt;br /&gt;            "GROUP BY taggings.taggable_id HAVING COUNT(taggings.taggable_id) = #{list.size}) AS x",&lt;br /&gt;            acts_as_taggable_options[:taggable_type], list&lt;br /&gt;          ]).first.cnt.to_i&lt;br /&gt;        end               &lt;br /&gt;      end&lt;br /&gt;      &lt;br /&gt;      module InstanceMethods&lt;br /&gt;        def tag_with(list)&lt;br /&gt;          Tag.transaction do&lt;br /&gt;            taggings.destroy_all&lt;br /&gt;&lt;br /&gt;            Tag.parse(list).each do |name|&lt;br /&gt;              if acts_as_taggable_options[:from]&lt;br /&gt;                send(acts_as_taggable_options[:from]).tags.find_or_create_by_name(name).on(self)&lt;br /&gt;              else&lt;br /&gt;                Tag.find_or_create_by_name(name).on(self)&lt;br /&gt;              end&lt;br /&gt;            end&lt;br /&gt;          end&lt;br /&gt;        end&lt;br /&gt;&lt;br /&gt;        def tag_list&lt;br /&gt;          tags.collect { |tag| tag.name.include?(" ") ? "'#{tag.name}'" : tag.name }.join(" ")&lt;br /&gt;        end&lt;br /&gt;      end&lt;br /&gt;    end&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;These are the sorts of tests I'm then doing:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;assert_equal 2, Post.find_tagged_with_intersecting(%w{TagOne TagTwo}).size&lt;br /&gt;assert_equal 2, Post.count_tagged_with_intersecting(%w{TagOne TagTwo})&lt;br /&gt;assert_equal 1, Post.find_tagged_with_intersecting(%w{TagOne TagTwo}, :conditions =&gt; 'status = 1').size&lt;br /&gt;assert_equal 1, Post.count_tagged_with_intersecting(%w{TagOne TagTwo}, :conditions =&gt; 'status = 1')&lt;br /&gt;p = Post.find_tagged_with("TagOne", :conditions =&gt; 'status = 1')&lt;br /&gt;&lt;/code&gt;</description>
      <pubDate>Sat, 28 Oct 2006 21:02:20 GMT</pubDate>
      <guid>http://snippets.dzone.com/posts/show/2918</guid>
      <author>peter (Peter Cooperx)</author>
    </item>
  </channel>
</rss>
