summaryrefslogtreecommitdiff
path: root/config/initializers/acts_as_taggable_on_patch.rb
blob: 0d535cb5cac5a2bd11f877f5cdbda1576442e5c7 (plain)
1
2
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
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# This is a patch to address the issue in https://github.com/mbleigh/acts-as-taggable-on/issues/427 caused by
# https://github.com/rails/rails/commit/31a43ebc107fbd50e7e62567e5208a05909ec76c
# gem 'acts-as-taggable-on' has the fix included https://github.com/mbleigh/acts-as-taggable-on/commit/89bbed3864a9252276fb8dd7d535fce280454b90
# but not in the currently used version of gem ('2.4.1')
# With replacement of 'acts-as-taggable-on' gem this file will become obsolete

module ActsAsTaggableOn::Taggable
  module Core
    module ClassMethods
      def tagged_with(tags, options = {})
        tag_list = ActsAsTaggableOn::TagList.from(tags)
        empty_result = where("1 = 0")

        return empty_result if tag_list.empty?

        joins = []
        conditions = []
        having = []
        select_clause = []

        context = options.delete(:on)
        owned_by = options.delete(:owned_by)
        alias_base_name = undecorated_table_name.gsub('.','_')
        quote = ActsAsTaggableOn::Tag.using_postgresql? ? '"' : ''

        if options.delete(:exclude)
          if options.delete(:wild)
            tags_conditions = tag_list.map { |t| sanitize_sql(["#{ActsAsTaggableOn::Tag.table_name}.name #{like_operator} ? ESCAPE '!'", "%#{escape_like(t)}%"]) }.join(" OR ")
          else
            tags_conditions = tag_list.map { |t| sanitize_sql(["#{ActsAsTaggableOn::Tag.table_name}.name #{like_operator} ?", t]) }.join(" OR ")
          end

          conditions << "#{table_name}.#{primary_key} NOT IN (SELECT #{ActsAsTaggableOn::Tagging.table_name}.taggable_id FROM #{ActsAsTaggableOn::Tagging.table_name} JOIN #{ActsAsTaggableOn::Tag.table_name} ON #{ActsAsTaggableOn::Tagging.table_name}.tag_id = #{ActsAsTaggableOn::Tag.table_name}.#{ActsAsTaggableOn::Tag.primary_key} AND (#{tags_conditions}) WHERE #{ActsAsTaggableOn::Tagging.table_name}.taggable_type = #{quote_value(base_class.name, nil)})"

          if owned_by
            joins <<  "JOIN #{ActsAsTaggableOn::Tagging.table_name}" +
                      "  ON #{ActsAsTaggableOn::Tagging.table_name}.taggable_id = #{quote}#{table_name}#{quote}.#{primary_key}" +
                      " AND #{ActsAsTaggableOn::Tagging.table_name}.taggable_type = #{quote_value(base_class.name, nil)}" +
                      " AND #{ActsAsTaggableOn::Tagging.table_name}.tagger_id = #{owned_by.id}" +
                      " AND #{ActsAsTaggableOn::Tagging.table_name}.tagger_type = #{quote_value(owned_by.class.base_class.to_s, nil)}"
          end

        elsif options.delete(:any)
          # get tags, drop out if nothing returned (we need at least one)
          tags =
            if options.delete(:wild)
              ActsAsTaggableOn::Tag.named_like_any(tag_list)
            else
              ActsAsTaggableOn::Tag.named_any(tag_list)
            end

          return empty_result unless tags.length > 0

          # setup taggings alias so we can chain, ex: items_locations_taggings_awesome_cool_123
          # avoid ambiguous column name
          taggings_context = context ? "_#{context}" : ''

          taggings_alias   = adjust_taggings_alias(
            "#{alias_base_name[0..4]}#{taggings_context[0..6]}_taggings_#{sha_prefix(tags.map(&:name).join('_'))}"
          )

          tagging_join  = "JOIN #{ActsAsTaggableOn::Tagging.table_name} #{taggings_alias}" +
                          "  ON #{taggings_alias}.taggable_id = #{quote}#{table_name}#{quote}.#{primary_key}" +
                          " AND #{taggings_alias}.taggable_type = #{quote_value(base_class.name, nil)}"
          tagging_join << " AND " + sanitize_sql(["#{taggings_alias}.context = ?", context.to_s]) if context

          # don't need to sanitize sql, map all ids and join with OR logic
          conditions << tags.map { |t| "#{taggings_alias}.tag_id = #{t.id}" }.join(" OR ")
          select_clause = "DISTINCT #{table_name}.*" unless context and tag_types.one?

          if owned_by
            tagging_join << " AND " +
              sanitize_sql([
                  "#{taggings_alias}.tagger_id = ? AND #{taggings_alias}.tagger_type = ?",
                  owned_by.id,
                  owned_by.class.base_class.to_s
              ])
          end

          joins << tagging_join
        else
          tags = ActsAsTaggableOn::Tag.named_any(tag_list)

          return empty_result unless tags.length == tag_list.length

          tags.each do |tag|
            taggings_alias = adjust_taggings_alias("#{alias_base_name[0..11]}_taggings_#{sha_prefix(tag.name)}")
            tagging_join  = "JOIN #{ActsAsTaggableOn::Tagging.table_name} #{taggings_alias}" +
                            "  ON #{taggings_alias}.taggable_id = #{quote}#{table_name}#{quote}.#{primary_key}" +
                            " AND #{taggings_alias}.taggable_type = #{quote_value(base_class.name, nil)}" +
                            " AND #{taggings_alias}.tag_id = #{tag.id}"

            tagging_join << " AND " + sanitize_sql(["#{taggings_alias}.context = ?", context.to_s]) if context

            if owned_by
              tagging_join << " AND " +
                sanitize_sql([
                  "#{taggings_alias}.tagger_id = ? AND #{taggings_alias}.tagger_type = ?",
                  owned_by.id,
                  owned_by.class.base_class.to_s
                ])
            end

            joins << tagging_join
          end
        end

        taggings_alias, tags_alias = adjust_taggings_alias("#{alias_base_name}_taggings_group"), "#{alias_base_name}_tags_group"

        if options.delete(:match_all)
          joins << "LEFT OUTER JOIN #{ActsAsTaggableOn::Tagging.table_name} #{taggings_alias}" +
                   "  ON #{taggings_alias}.taggable_id = #{quote}#{table_name}#{quote}.#{primary_key}" +
                   " AND #{taggings_alias}.taggable_type = #{quote_value(base_class.name, nil)}"


          group_columns = ActsAsTaggableOn::Tag.using_postgresql? ? grouped_column_names_for(self) : "#{table_name}.#{primary_key}"
          group = group_columns
          having = "COUNT(#{taggings_alias}.taggable_id) = #{tags.size}"
        end

        select(select_clause) \
          .joins(joins.join(" ")) \
          .where(conditions.join(" AND ")) \
          .group(group) \
          .having(having) \
          .order(options[:order]) \
          .readonly(false)
      end
    end
  end
end