JumpstartLab/tracks

View on GitHub
lib/is_taggable.rb

Summary

Maintainability
B
5 hrs
Test Coverage
# These methods are adapted from has_many_polymorphs' tagging_extensions

module IsTaggable

  def self.included(klass)
    klass.class_eval do

      # Add tags associations
      has_many :taggings, :as => :taggable
      has_many :tags, :through => :taggings do
        def to_s
          self.map(&:name).sort.join(Tag::JOIN_DELIMITER)
        end
        def all_except_starred
          self.reject{|tag| tag.name == Todo::STARRED_TAG_NAME}
        end
      end
      
      def tag_list
        tags.reload
        tags.to_s
      end
            
      def tag_list=(value)
        tag_with(value)
      end
      
      # Replace the existing tags on <tt>self</tt>. Accepts a string of tagnames, an array of tagnames, or an array of Tags.
      def tag_with list
        list = tag_cast_to_string(list)
  
        # Transactions may not be ideal for you here; be aware.
        Tag.transaction do
          current = tags.map(&:name)
          _add_tags(list - current)
          _remove_tags(current - list)
        end
  
        self
      end

      def has_tag?(tag_name)
        return tags.any? {|tag| tag.name == tag_name}
      end
      
      # Add tags to <tt>self</tt>. Accepts a string of tagnames, an array of tagnames, or an array of Tags.
      #
      # We need to avoid name conflicts with the built-in ActiveRecord association methods, thus the underscores.
      def _add_tags incoming
        tag_cast_to_string(incoming).each do |tag_name|
          # added following check to prevent empty tags from being saved (which will fail)
          unless tag_name.blank?
            begin
              tag = Tag.where(:name => tag_name).first_or_create
              raise Tag::Error, "tag could not be saved: #{tag_name}" if tag.new_record?
              tags << tag
            rescue ActiveRecord::StatementInvalid => e
              raise unless e.to_s =~ /duplicate/i
            end
          end
        end
      end

      # Removes tags from <tt>self</tt>. Accepts a string of tagnames, an array of tagnames, or an array of Tags.
      def _remove_tags outgoing
        outgoing = tag_cast_to_string(outgoing)
        tags.destroy(*(tags.select{|tag| outgoing.include? tag.name}))
      end
      
      def get_tag_name_from_item(item)
        case item
        # removed next line as it prevents using numbers as tags
        # when /^\d+$/, Fixnum then Tag.find(item).name # This will be slow if you use ids a lot.
        when Tag
          item.name
        when String
          item
        else
          raise "Invalid type"
        end
      end

      def tag_cast_to_string obj
        case obj
        when Array
          obj.map! { |item| get_tag_name_from_item(item) }
        when String
          obj.split(Tag::DELIMITER).map { |tag_name| tag_name.strip.squeeze(" ") }
        else
          raise "Invalid object of class #{obj.class} as tagging method parameter"
        end.flatten.compact.map(&:downcase).uniq
      end
            
    end
  end
  
end