acts-as-taggable-on 4.0.0 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.travis.yml +4 -11
- data/Appraisals +7 -13
- data/CHANGELOG.md +82 -0
- data/CONTRIBUTING.md +13 -0
- data/Gemfile +1 -1
- data/README.md +42 -10
- data/acts-as-taggable-on.gemspec +2 -2
- data/db/migrate/1_acts_as_taggable_on_migration.rb +6 -1
- data/db/migrate/2_add_missing_unique_indices.rb +6 -1
- data/db/migrate/3_add_taggings_counter_cache_to_tags.rb +6 -1
- data/db/migrate/4_add_missing_taggable_index.rb +6 -1
- data/db/migrate/5_change_collation_for_tag_names.rb +6 -1
- data/db/migrate/6_add_missing_indexes_on_taggings.rb +22 -0
- data/gemfiles/activerecord_4.2.gemfile +3 -3
- data/gemfiles/activerecord_5.0.gemfile +3 -3
- data/gemfiles/{activerecord_4.0.gemfile → activerecord_5.1.gemfile} +3 -4
- data/lib/acts_as_taggable_on/tag.rb +10 -7
- data/lib/acts_as_taggable_on/tag_list.rb +1 -0
- data/lib/acts_as_taggable_on/taggable/core.rb +10 -157
- data/lib/acts_as_taggable_on/taggable/ownership.rb +16 -5
- data/lib/acts_as_taggable_on/taggable/tagged_with_query.rb +16 -0
- data/lib/acts_as_taggable_on/taggable/tagged_with_query/all_tags_query.rb +113 -0
- data/lib/acts_as_taggable_on/taggable/tagged_with_query/any_tags_query.rb +75 -0
- data/lib/acts_as_taggable_on/taggable/tagged_with_query/exclude_tags_query.rb +82 -0
- data/lib/acts_as_taggable_on/taggable/tagged_with_query/query_base.rb +61 -0
- data/lib/acts_as_taggable_on/tagger.rb +2 -2
- data/lib/acts_as_taggable_on/tagging.rb +3 -2
- data/lib/acts_as_taggable_on/version.rb +1 -1
- data/spec/acts_as_taggable_on/caching_spec.rb +18 -0
- data/spec/acts_as_taggable_on/single_table_inheritance_spec.rb +16 -1
- data/spec/acts_as_taggable_on/taggable_spec.rb +1 -1
- data/spec/internal/db/schema.rb +3 -0
- metadata +13 -9
- data/db/migrate/6_add_missing_indexes.rb +0 -12
- data/gemfiles/activerecord_4.1.gemfile +0 -16
@@ -1,3 +1,5 @@
|
|
1
|
+
require_relative 'tagged_with_query'
|
2
|
+
|
1
3
|
module ActsAsTaggableOn::Taggable
|
2
4
|
module Core
|
3
5
|
def self.included(base)
|
@@ -25,11 +27,11 @@ module ActsAsTaggableOn::Taggable
|
|
25
27
|
# the associations tag_taggings & tags are always returned in created order
|
26
28
|
has_many context_taggings, -> { includes(:tag).order(taggings_order).where(context: tags_type) },
|
27
29
|
as: :taggable,
|
28
|
-
class_name: ActsAsTaggableOn::Tagging,
|
30
|
+
class_name: 'ActsAsTaggableOn::Tagging',
|
29
31
|
dependent: :destroy
|
30
32
|
|
31
33
|
has_many context_tags, -> { order(taggings_order) },
|
32
|
-
class_name: ActsAsTaggableOn::Tag,
|
34
|
+
class_name: 'ActsAsTaggableOn::Tag',
|
33
35
|
through: context_taggings,
|
34
36
|
source: :tag
|
35
37
|
end
|
@@ -88,169 +90,16 @@ module ActsAsTaggableOn::Taggable
|
|
88
90
|
|
89
91
|
return empty_result if tag_list.empty?
|
90
92
|
|
91
|
-
|
92
|
-
conditions = []
|
93
|
-
having = []
|
94
|
-
select_clause = []
|
95
|
-
order_by = []
|
96
|
-
|
97
|
-
context = options.delete(:on)
|
98
|
-
owned_by = options.delete(:owned_by)
|
99
|
-
alias_base_name = undecorated_table_name.gsub('.', '_')
|
100
|
-
# FIXME use ActiveRecord's connection quote_column_name
|
101
|
-
quote = ActsAsTaggableOn::Utils.using_postgresql? ? '"' : ''
|
102
|
-
|
103
|
-
if options.delete(:exclude)
|
104
|
-
if options.delete(:wild)
|
105
|
-
tags_conditions = tag_list.map { |t| sanitize_sql(["#{ActsAsTaggableOn::Tag.table_name}.name #{ActsAsTaggableOn::Utils.like_operator} ? ESCAPE '!'", "%#{ActsAsTaggableOn::Utils.escape_like(t)}%"]) }.join(' OR ')
|
106
|
-
else
|
107
|
-
tags_conditions = tag_list.map { |t| sanitize_sql(["#{ActsAsTaggableOn::Tag.table_name}.name #{ActsAsTaggableOn::Utils.like_operator} ?", t]) }.join(' OR ')
|
108
|
-
end
|
109
|
-
|
110
|
-
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)})"
|
111
|
-
|
112
|
-
if owned_by
|
113
|
-
joins << "JOIN #{ActsAsTaggableOn::Tagging.table_name}" +
|
114
|
-
" ON #{ActsAsTaggableOn::Tagging.table_name}.taggable_id = #{quote}#{table_name}#{quote}.#{primary_key}" +
|
115
|
-
" AND #{ActsAsTaggableOn::Tagging.table_name}.taggable_type = #{quote_value(base_class.name, nil)}" +
|
116
|
-
" AND #{ActsAsTaggableOn::Tagging.table_name}.tagger_id = #{quote_value(owned_by.id, nil)}" +
|
117
|
-
" AND #{ActsAsTaggableOn::Tagging.table_name}.tagger_type = #{quote_value(owned_by.class.base_class.to_s, nil)}"
|
118
|
-
|
119
|
-
joins << " AND " + sanitize_sql(["#{ActsAsTaggableOn::Tagging.table_name}.created_at >= ?", options.delete(:start_at)]) if options[:start_at]
|
120
|
-
joins << " AND " + sanitize_sql(["#{ActsAsTaggableOn::Tagging.table_name}.created_at <= ?", options.delete(:end_at)]) if options[:end_at]
|
121
|
-
end
|
122
|
-
|
123
|
-
elsif any = options.delete(:any)
|
124
|
-
# get tags, drop out if nothing returned (we need at least one)
|
125
|
-
tags = if options.delete(:wild)
|
126
|
-
ActsAsTaggableOn::Tag.named_like_any(tag_list)
|
127
|
-
else
|
128
|
-
ActsAsTaggableOn::Tag.named_any(tag_list)
|
129
|
-
end
|
130
|
-
|
131
|
-
return empty_result if tags.length == 0
|
132
|
-
|
133
|
-
# setup taggings alias so we can chain, ex: items_locations_taggings_awesome_cool_123
|
134
|
-
# avoid ambiguous column name
|
135
|
-
taggings_context = context ? "_#{context}" : ''
|
136
|
-
|
137
|
-
taggings_alias = adjust_taggings_alias(
|
138
|
-
"#{alias_base_name[0..4]}#{taggings_context[0..6]}_taggings_#{ActsAsTaggableOn::Utils.sha_prefix(tags.map(&:name).join('_'))}"
|
139
|
-
)
|
140
|
-
|
141
|
-
tagging_cond = "#{ActsAsTaggableOn::Tagging.table_name} #{taggings_alias}" +
|
142
|
-
" WHERE #{taggings_alias}.taggable_id = #{quote}#{table_name}#{quote}.#{primary_key}" +
|
143
|
-
" AND #{taggings_alias}.taggable_type = #{quote_value(base_class.name, nil)}"
|
144
|
-
|
145
|
-
tagging_cond << " AND " + sanitize_sql(["#{taggings_alias}.created_at >= ?", options.delete(:start_at)]) if options[:start_at]
|
146
|
-
tagging_cond << " AND " + sanitize_sql(["#{taggings_alias}.created_at <= ?", options.delete(:end_at)]) if options[:end_at]
|
147
|
-
|
148
|
-
tagging_cond << " AND " + sanitize_sql(["#{taggings_alias}.context = ?", context.to_s]) if context
|
149
|
-
|
150
|
-
# don't need to sanitize sql, map all ids and join with OR logic
|
151
|
-
tag_ids = tags.map { |t| quote_value(t.id, nil) }.join(', ')
|
152
|
-
tagging_cond << " AND #{taggings_alias}.tag_id in (#{tag_ids})"
|
153
|
-
select_clause << " #{table_name}.*" unless context and tag_types.one?
|
154
|
-
|
155
|
-
if owned_by
|
156
|
-
tagging_cond << ' AND ' +
|
157
|
-
sanitize_sql([
|
158
|
-
"#{taggings_alias}.tagger_id = ? AND #{taggings_alias}.tagger_type = ?",
|
159
|
-
owned_by.id,
|
160
|
-
owned_by.class.base_class.to_s
|
161
|
-
])
|
162
|
-
end
|
163
|
-
|
164
|
-
conditions << "EXISTS (SELECT 1 FROM #{tagging_cond})"
|
165
|
-
if options.delete(:order_by_matching_tag_count)
|
166
|
-
order_by << "(SELECT count(*) FROM #{tagging_cond}) desc"
|
167
|
-
end
|
168
|
-
else
|
169
|
-
tags = ActsAsTaggableOn::Tag.named_any(tag_list)
|
170
|
-
|
171
|
-
return empty_result unless tags.length == tag_list.length
|
172
|
-
|
173
|
-
tags.each do |tag|
|
174
|
-
taggings_alias = adjust_taggings_alias("#{alias_base_name[0..11]}_taggings_#{ActsAsTaggableOn::Utils.sha_prefix(tag.name)}")
|
175
|
-
tagging_join = "JOIN #{ActsAsTaggableOn::Tagging.table_name} #{taggings_alias}" \
|
176
|
-
" ON #{taggings_alias}.taggable_id = #{quote}#{table_name}#{quote}.#{primary_key}" +
|
177
|
-
" AND #{taggings_alias}.taggable_type = #{quote_value(base_class.name, nil)}" +
|
178
|
-
" AND #{taggings_alias}.tag_id = #{quote_value(tag.id, nil)}"
|
179
|
-
|
180
|
-
tagging_join << " AND " + sanitize_sql(["#{taggings_alias}.created_at >= ?", options.delete(:start_at)]) if options[:start_at]
|
181
|
-
tagging_join << " AND " + sanitize_sql(["#{taggings_alias}.created_at <= ?", options.delete(:end_at)]) if options[:end_at]
|
182
|
-
|
183
|
-
tagging_join << " AND " + sanitize_sql(["#{taggings_alias}.context = ?", context.to_s]) if context
|
184
|
-
|
185
|
-
if owned_by
|
186
|
-
tagging_join << ' AND ' +
|
187
|
-
sanitize_sql([
|
188
|
-
"#{taggings_alias}.tagger_id = ? AND #{taggings_alias}.tagger_type = ?",
|
189
|
-
owned_by.id,
|
190
|
-
owned_by.class.base_class.to_s
|
191
|
-
])
|
192
|
-
end
|
193
|
-
|
194
|
-
joins << tagging_join
|
195
|
-
end
|
196
|
-
end
|
197
|
-
|
198
|
-
group ||= [] # Rails interprets this as a no-op in the group() call below
|
199
|
-
if options.delete(:order_by_matching_tag_count)
|
200
|
-
select_clause << "#{table_name}.*, COUNT(#{taggings_alias}.tag_id) AS #{taggings_alias}_count"
|
201
|
-
group_columns = ActsAsTaggableOn::Utils.using_postgresql? ? grouped_column_names_for(self) : "#{table_name}.#{primary_key}"
|
202
|
-
group = group_columns
|
203
|
-
order_by << "#{taggings_alias}_count DESC"
|
204
|
-
|
205
|
-
elsif options.delete(:match_all)
|
206
|
-
taggings_alias, _ = adjust_taggings_alias("#{alias_base_name}_taggings_group"), "#{alias_base_name}_tags_group"
|
207
|
-
joins << "LEFT OUTER JOIN #{ActsAsTaggableOn::Tagging.table_name} #{taggings_alias}" \
|
208
|
-
" ON #{taggings_alias}.taggable_id = #{quote}#{table_name}#{quote}.#{primary_key}" \
|
209
|
-
" AND #{taggings_alias}.taggable_type = #{quote_value(base_class.name, nil)}"
|
210
|
-
|
211
|
-
joins << " AND " + sanitize_sql(["#{taggings_alias}.context = ?", context.to_s]) if context
|
212
|
-
joins << " AND " + sanitize_sql(["#{ActsAsTaggableOn::Tagging.table_name}.created_at >= ?", options.delete(:start_at)]) if options[:start_at]
|
213
|
-
joins << " AND " + sanitize_sql(["#{ActsAsTaggableOn::Tagging.table_name}.created_at <= ?", options.delete(:end_at)]) if options[:end_at]
|
214
|
-
|
215
|
-
group_columns = ActsAsTaggableOn::Utils.using_postgresql? ? grouped_column_names_for(self) : "#{table_name}.#{primary_key}"
|
216
|
-
group = group_columns
|
217
|
-
having = "COUNT(#{taggings_alias}.taggable_id) = #{tags.size}"
|
218
|
-
end
|
219
|
-
|
220
|
-
order_by << options[:order] if options[:order].present?
|
221
|
-
|
222
|
-
query = self
|
223
|
-
query = self.select(select_clause.join(',')) unless select_clause.empty?
|
224
|
-
query.joins(joins.join(' '))
|
225
|
-
.where(conditions.join(' AND '))
|
226
|
-
.group(group)
|
227
|
-
.having(having)
|
228
|
-
.order(order_by.join(', '))
|
229
|
-
.readonly(false)
|
93
|
+
::ActsAsTaggableOn::Taggable::TaggedWithQuery.build(self, ActsAsTaggableOn::Tag, ActsAsTaggableOn::Tagging, tag_list, options)
|
230
94
|
end
|
231
95
|
|
232
96
|
def is_taggable?
|
233
97
|
true
|
234
98
|
end
|
235
99
|
|
236
|
-
def adjust_taggings_alias(taggings_alias)
|
237
|
-
if taggings_alias.size > 75
|
238
|
-
taggings_alias = 'taggings_alias_' + Digest::SHA1.hexdigest(taggings_alias)
|
239
|
-
end
|
240
|
-
taggings_alias
|
241
|
-
end
|
242
|
-
|
243
100
|
def taggable_mixin
|
244
101
|
@taggable_mixin ||= Module.new
|
245
102
|
end
|
246
|
-
|
247
|
-
private
|
248
|
-
|
249
|
-
# Rails 5 has merged sanitize and quote_value
|
250
|
-
# See https://github.com/rails/rails/blob/master/activerecord/lib/active_record/sanitization.rb#L10
|
251
|
-
def quote_value(value, column = nil)
|
252
|
-
ActsAsTaggableOn::Utils.active_record5? ? super(value) : super(value, column)
|
253
|
-
end
|
254
103
|
end
|
255
104
|
|
256
105
|
# all column names are necessary for PostgreSQL group clause
|
@@ -283,7 +132,7 @@ module ActsAsTaggableOn::Taggable
|
|
283
132
|
variable_name = "@#{context.to_s.singularize}_list"
|
284
133
|
if instance_variable_get(variable_name)
|
285
134
|
instance_variable_get(variable_name)
|
286
|
-
elsif cached_tag_list_on(context) && self.class.caching_tag_list_on?(context)
|
135
|
+
elsif cached_tag_list_on(context) && ensure_included_cache_methods! && self.class.caching_tag_list_on?(context)
|
287
136
|
instance_variable_set(variable_name, ActsAsTaggableOn.default_parser.new(cached_tag_list_on(context)).parse)
|
288
137
|
else
|
289
138
|
instance_variable_set(variable_name, ActsAsTaggableOn::TagList.new(tags_on(context).map(&:name)))
|
@@ -426,6 +275,10 @@ module ActsAsTaggableOn::Taggable
|
|
426
275
|
|
427
276
|
private
|
428
277
|
|
278
|
+
def ensure_included_cache_methods!
|
279
|
+
self.class.columns
|
280
|
+
end
|
281
|
+
|
429
282
|
# Filters the tag lists from the attribute names.
|
430
283
|
def attributes_for_update(attribute_names)
|
431
284
|
tag_lists = tag_types.map {|tags_type| "#{tags_type.to_s.singularize}_list"}
|
@@ -27,13 +27,16 @@ module ActsAsTaggableOn::Taggable
|
|
27
27
|
end
|
28
28
|
end
|
29
29
|
|
30
|
-
def
|
30
|
+
def owner_tags(owner)
|
31
31
|
if owner.nil?
|
32
|
-
scope = base_tags
|
32
|
+
scope = base_tags
|
33
33
|
else
|
34
|
-
scope = base_tags.where(
|
35
|
-
|
36
|
-
|
34
|
+
scope = base_tags.where(
|
35
|
+
"#{ActsAsTaggableOn::Tagging.table_name}" => {
|
36
|
+
tagger_id: owner.id,
|
37
|
+
tagger_type: owner.class.base_class.to_s
|
38
|
+
}
|
39
|
+
)
|
37
40
|
end
|
38
41
|
|
39
42
|
# when preserving tag order, return tags in created order
|
@@ -45,6 +48,14 @@ module ActsAsTaggableOn::Taggable
|
|
45
48
|
end
|
46
49
|
end
|
47
50
|
|
51
|
+
def owner_tags_on(owner, context)
|
52
|
+
scope = owner_tags(owner).where(
|
53
|
+
"#{ActsAsTaggableOn::Tagging.table_name}" => {
|
54
|
+
context: context
|
55
|
+
}
|
56
|
+
)
|
57
|
+
end
|
58
|
+
|
48
59
|
def cached_owned_tag_list_on(context)
|
49
60
|
variable_name = "@owned_#{context}_list"
|
50
61
|
(instance_variable_defined?(variable_name) && instance_variable_get(variable_name)) || instance_variable_set(variable_name, {})
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require_relative 'tagged_with_query/query_base'
|
2
|
+
require_relative 'tagged_with_query/exclude_tags_query'
|
3
|
+
require_relative 'tagged_with_query/any_tags_query'
|
4
|
+
require_relative 'tagged_with_query/all_tags_query'
|
5
|
+
|
6
|
+
module ActsAsTaggableOn::Taggable::TaggedWithQuery
|
7
|
+
def self.build(taggable_model, tag_model, tagging_model, tag_list, options)
|
8
|
+
if options[:exclude].present?
|
9
|
+
ExcludeTagsQuery.new(taggable_model, tag_model, tagging_model, tag_list, options).build
|
10
|
+
elsif options[:any].present?
|
11
|
+
AnyTagsQuery.new(taggable_model, tag_model, tagging_model, tag_list, options).build
|
12
|
+
else
|
13
|
+
AllTagsQuery.new(taggable_model, tag_model, tagging_model, tag_list, options).build
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
module ActsAsTaggableOn::Taggable::TaggedWithQuery
|
2
|
+
class AllTagsQuery < QueryBase
|
3
|
+
def build
|
4
|
+
taggable_model.joins(each_tag_in_list)
|
5
|
+
.group(by_taggable)
|
6
|
+
.having(tags_that_matches_count)
|
7
|
+
.order(order_conditions)
|
8
|
+
.readonly(false)
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def each_tag_in_list
|
14
|
+
arel_join = taggable_arel_table
|
15
|
+
|
16
|
+
tag_list.each do |tag|
|
17
|
+
tagging_alias = tagging_arel_table.alias(tagging_alias(tag))
|
18
|
+
arel_join = arel_join
|
19
|
+
.join(tagging_alias)
|
20
|
+
.on(on_conditions(tag, tagging_alias))
|
21
|
+
end
|
22
|
+
|
23
|
+
if options[:match_all].present?
|
24
|
+
arel_join = arel_join
|
25
|
+
.join(tagging_arel_table, Arel::Nodes::OuterJoin)
|
26
|
+
.on(
|
27
|
+
match_all_on_conditions
|
28
|
+
)
|
29
|
+
end
|
30
|
+
|
31
|
+
return arel_join.join_sources
|
32
|
+
end
|
33
|
+
|
34
|
+
def on_conditions(tag, tagging_alias)
|
35
|
+
on_condition = tagging_alias[:taggable_id].eq(taggable_arel_table[taggable_model.primary_key])
|
36
|
+
.and(tagging_alias[:taggable_type].eq(taggable_model.base_class.name))
|
37
|
+
.and(
|
38
|
+
tagging_alias[:tag_id].in(
|
39
|
+
tag_arel_table.project(tag_arel_table[:id]).where(tag_match_type(tag))
|
40
|
+
)
|
41
|
+
)
|
42
|
+
|
43
|
+
if options[:start_at].present?
|
44
|
+
on_condition = on_condition.and(tagging_alias[:created_at].gteq(options[:start_at]))
|
45
|
+
end
|
46
|
+
|
47
|
+
if options[:end_at].present?
|
48
|
+
on_condition = on_condition.and(tagging_alias[:created_at].lteq(options[:end_at]))
|
49
|
+
end
|
50
|
+
|
51
|
+
if options[:on].present?
|
52
|
+
on_condition = on_condition.and(tagging_alias[:context].lteq(options[:on]))
|
53
|
+
end
|
54
|
+
|
55
|
+
if (owner = options[:owned_by]).present?
|
56
|
+
owner_table = owner.class.base_class.arel_table
|
57
|
+
|
58
|
+
on_condition = on_condition.and(tagging_alias[:tagger_id].eq(owner.id))
|
59
|
+
.and(tagging_alias[:tagger_type].eq(owner.class.base_class.to_s))
|
60
|
+
end
|
61
|
+
|
62
|
+
on_condition
|
63
|
+
end
|
64
|
+
|
65
|
+
def match_all_on_conditions
|
66
|
+
on_condition = tagging_arel_table[:taggable_id].eq(taggable_arel_table[taggable_model.primary_key])
|
67
|
+
.and(tagging_arel_table[:taggable_type].eq(taggable_model.base_class.name))
|
68
|
+
|
69
|
+
if options[:start_at].present?
|
70
|
+
on_condition = on_condition.and(tagging_arel_table[:created_at].gteq(options[:start_at]))
|
71
|
+
end
|
72
|
+
|
73
|
+
if options[:end_at].present?
|
74
|
+
on_condition = on_condition.and(tagging_arel_table[:created_at].lteq(options[:end_at]))
|
75
|
+
end
|
76
|
+
|
77
|
+
if options[:on].present?
|
78
|
+
on_condition = on_condition.and(tagging_arel_table[:context].lteq(options[:on]))
|
79
|
+
end
|
80
|
+
|
81
|
+
on_condition
|
82
|
+
end
|
83
|
+
|
84
|
+
def by_taggable
|
85
|
+
return [] unless options[:match_all].present?
|
86
|
+
|
87
|
+
taggable_arel_table[taggable_model.primary_key]
|
88
|
+
end
|
89
|
+
|
90
|
+
def tags_that_matches_count
|
91
|
+
return [] unless options[:match_all].present?
|
92
|
+
|
93
|
+
taggable_model.find_by_sql(tag_arel_table.project(Arel.star.count).where(tags_match_type).to_sql)
|
94
|
+
|
95
|
+
tagging_arel_table[:taggable_id].count.eq(
|
96
|
+
tag_arel_table.project(Arel.star.count).where(tags_match_type)
|
97
|
+
)
|
98
|
+
end
|
99
|
+
|
100
|
+
def order_conditions
|
101
|
+
order_by = []
|
102
|
+
order_by << tagging_arel_table.project(tagging_arel_table[Arel.star].count.as('taggings_count')).order('taggings_count DESC').to_sql if options[:order_by_matching_tag_count].present? && options[:match_all].blank?
|
103
|
+
|
104
|
+
order_by << options[:order] if options[:order].present?
|
105
|
+
order_by.join(', ')
|
106
|
+
end
|
107
|
+
|
108
|
+
def tagging_alias(tag)
|
109
|
+
alias_base_name = taggable_model.base_class.name.downcase
|
110
|
+
adjust_taggings_alias("#{alias_base_name[0..11]}_taggings_#{ActsAsTaggableOn::Utils.sha_prefix(tag)}")
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module ActsAsTaggableOn::Taggable::TaggedWithQuery
|
2
|
+
class AnyTagsQuery < QueryBase
|
3
|
+
def build
|
4
|
+
taggable_model.select(all_fields)
|
5
|
+
.where(model_has_at_least_one_tag)
|
6
|
+
.order(order_conditions)
|
7
|
+
.readonly(false)
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def all_fields
|
13
|
+
taggable_arel_table[Arel.star]
|
14
|
+
end
|
15
|
+
|
16
|
+
def model_has_at_least_one_tag
|
17
|
+
tagging_alias = tagging_arel_table.alias(alias_name(tag_list))
|
18
|
+
|
19
|
+
|
20
|
+
tagging_arel_table.project(Arel.star).where(at_least_one_tag).exists
|
21
|
+
end
|
22
|
+
|
23
|
+
def at_least_one_tag
|
24
|
+
exists_contition = tagging_arel_table[:taggable_id].eq(taggable_arel_table[taggable_model.primary_key])
|
25
|
+
.and(tagging_arel_table[:taggable_type].eq(taggable_model.base_class.name))
|
26
|
+
.and(
|
27
|
+
tagging_arel_table[:tag_id].in(
|
28
|
+
tag_arel_table.project(tag_arel_table[:id]).where(tags_match_type)
|
29
|
+
)
|
30
|
+
)
|
31
|
+
|
32
|
+
if options[:start_at].present?
|
33
|
+
exists_contition = exists_contition.and(tagging_arel_table[:created_at].gteq(options[:start_at]))
|
34
|
+
end
|
35
|
+
|
36
|
+
if options[:end_at].present?
|
37
|
+
exists_contition = exists_contition.and(tagging_arel_table[:created_at].lteq(options[:end_at]))
|
38
|
+
end
|
39
|
+
|
40
|
+
if options[:on].present?
|
41
|
+
exists_contition = exists_contition.and(tagging_arel_table[:context].lteq(options[:on]))
|
42
|
+
end
|
43
|
+
|
44
|
+
if (owner = options[:owned_by]).present?
|
45
|
+
owner_table = owner.class.base_class.arel_table
|
46
|
+
|
47
|
+
exists_contition = exists_contition.and(tagging_arel_table[:tagger_id].eq(owner.id))
|
48
|
+
.and(tagging_arel_table[:tagger_type].eq(owner.class.base_class.to_s))
|
49
|
+
end
|
50
|
+
|
51
|
+
exists_contition
|
52
|
+
end
|
53
|
+
|
54
|
+
def order_conditions
|
55
|
+
order_by = []
|
56
|
+
if options[:order_by_matching_tag_count].present?
|
57
|
+
order_by << "(SELECT count(*) FROM #{tagging_model.table_name} WHERE #{at_least_one_tag.to_sql}) desc"
|
58
|
+
end
|
59
|
+
|
60
|
+
order_by << options[:order] if options[:order].present?
|
61
|
+
order_by.join(', ')
|
62
|
+
end
|
63
|
+
|
64
|
+
def alias_name(tag_list)
|
65
|
+
alias_base_name = taggable_model.base_class.name.downcase
|
66
|
+
taggings_context = options[:on] ? "_#{options[:on]}" : ''
|
67
|
+
|
68
|
+
taggings_alias = adjust_taggings_alias(
|
69
|
+
"#{alias_base_name[0..4]}#{taggings_context[0..6]}_taggings_#{ActsAsTaggableOn::Utils.sha_prefix(tag_list.join('_'))}"
|
70
|
+
)
|
71
|
+
|
72
|
+
taggings_alias
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|