acts-as-taggable-on 4.0.0 → 5.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|