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.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +4 -11
  3. data/Appraisals +7 -13
  4. data/CHANGELOG.md +82 -0
  5. data/CONTRIBUTING.md +13 -0
  6. data/Gemfile +1 -1
  7. data/README.md +42 -10
  8. data/acts-as-taggable-on.gemspec +2 -2
  9. data/db/migrate/1_acts_as_taggable_on_migration.rb +6 -1
  10. data/db/migrate/2_add_missing_unique_indices.rb +6 -1
  11. data/db/migrate/3_add_taggings_counter_cache_to_tags.rb +6 -1
  12. data/db/migrate/4_add_missing_taggable_index.rb +6 -1
  13. data/db/migrate/5_change_collation_for_tag_names.rb +6 -1
  14. data/db/migrate/6_add_missing_indexes_on_taggings.rb +22 -0
  15. data/gemfiles/activerecord_4.2.gemfile +3 -3
  16. data/gemfiles/activerecord_5.0.gemfile +3 -3
  17. data/gemfiles/{activerecord_4.0.gemfile → activerecord_5.1.gemfile} +3 -4
  18. data/lib/acts_as_taggable_on/tag.rb +10 -7
  19. data/lib/acts_as_taggable_on/tag_list.rb +1 -0
  20. data/lib/acts_as_taggable_on/taggable/core.rb +10 -157
  21. data/lib/acts_as_taggable_on/taggable/ownership.rb +16 -5
  22. data/lib/acts_as_taggable_on/taggable/tagged_with_query.rb +16 -0
  23. data/lib/acts_as_taggable_on/taggable/tagged_with_query/all_tags_query.rb +113 -0
  24. data/lib/acts_as_taggable_on/taggable/tagged_with_query/any_tags_query.rb +75 -0
  25. data/lib/acts_as_taggable_on/taggable/tagged_with_query/exclude_tags_query.rb +82 -0
  26. data/lib/acts_as_taggable_on/taggable/tagged_with_query/query_base.rb +61 -0
  27. data/lib/acts_as_taggable_on/tagger.rb +2 -2
  28. data/lib/acts_as_taggable_on/tagging.rb +3 -2
  29. data/lib/acts_as_taggable_on/version.rb +1 -1
  30. data/spec/acts_as_taggable_on/caching_spec.rb +18 -0
  31. data/spec/acts_as_taggable_on/single_table_inheritance_spec.rb +16 -1
  32. data/spec/acts_as_taggable_on/taggable_spec.rb +1 -1
  33. data/spec/internal/db/schema.rb +3 -0
  34. metadata +13 -9
  35. data/db/migrate/6_add_missing_indexes.rb +0 -12
  36. data/gemfiles/activerecord_4.1.gemfile +0 -16
@@ -41,6 +41,7 @@ module ActsAsTaggableOn
41
41
  # Appends the elements of +other_tag_list+ to +self+.
42
42
  def concat(other_tag_list)
43
43
  super(other_tag_list).send(:clean!)
44
+ self
44
45
  end
45
46
 
46
47
  ##
@@ -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
- joins = []
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 owner_tags_on(owner, context)
30
+ def owner_tags(owner)
31
31
  if owner.nil?
32
- scope = base_tags.where([%(#{ActsAsTaggableOn::Tagging.table_name}.context = ?), context.to_s])
32
+ scope = base_tags
33
33
  else
34
- scope = base_tags.where([%(#{ActsAsTaggableOn::Tagging.table_name}.context = ? AND
35
- #{ActsAsTaggableOn::Tagging.table_name}.tagger_id = ? AND
36
- #{ActsAsTaggableOn::Tagging.table_name}.tagger_type = ?), context.to_s, owner.id, owner.class.base_class.to_s])
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