Module: IsTaggable

Included in:
RecurringTodo, Todo
Defined in:
lib/is_taggable.rb

Overview

These methods are adapted from has_many_polymorphs’ tagging_extensions

Class Method Summary collapse

Class Method Details

.included(klass) ⇒ Object



3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/is_taggable.rb', line 3

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
        to_a.map(&:name).sort.join(Tag::JOIN_DELIMITER)
      end

      def all_except_starred
        to_a.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.to_a.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)
        if tag_name.present?
          begin
            tag = user.tags.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(*(user.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)
      tag_array_from_obj(obj).flatten.compact.map(&:downcase).uniq
    end

    def tag_array_from_obj(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
    end
  end
end