acts-as-taggable-on 7.0.0 → 9.0.1
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/.github/workflows/spec.yml +76 -0
- data/Appraisals +13 -13
- data/CHANGELOG.md +27 -2
- data/Gemfile +1 -0
- data/README.md +32 -7
- data/acts-as-taggable-on.gemspec +2 -2
- data/db/migrate/1_acts_as_taggable_on_migration.rb +5 -8
- data/db/migrate/2_add_missing_unique_indices.rb +6 -8
- data/db/migrate/3_add_taggings_counter_cache_to_tags.rb +3 -6
- data/db/migrate/4_add_missing_taggable_index.rb +5 -7
- data/db/migrate/5_change_collation_for_tag_names.rb +4 -6
- data/db/migrate/6_add_missing_indexes_on_taggings.rb +15 -13
- data/db/migrate/7_add_tenant_to_taggings.rb +13 -0
- data/docker-compose.yml +15 -0
- data/gemfiles/activerecord_6.0.gemfile +5 -8
- data/gemfiles/activerecord_6.1.gemfile +3 -8
- data/gemfiles/{activerecord_5.2.gemfile → activerecord_7.0.gemfile} +6 -9
- data/lib/acts_as_taggable_on/default_parser.rb +8 -10
- data/lib/acts_as_taggable_on/engine.rb +2 -0
- data/lib/acts_as_taggable_on/generic_parser.rb +2 -0
- data/lib/acts_as_taggable_on/tag.rb +33 -27
- data/lib/acts_as_taggable_on/tag_list.rb +8 -11
- data/lib/acts_as_taggable_on/taggable/cache.rb +64 -62
- data/lib/acts_as_taggable_on/taggable/collection.rb +178 -142
- data/lib/acts_as_taggable_on/taggable/core.rb +250 -236
- data/lib/acts_as_taggable_on/taggable/ownership.rb +110 -98
- data/lib/acts_as_taggable_on/taggable/related.rb +60 -47
- data/lib/acts_as_taggable_on/taggable/tag_list_type.rb +6 -2
- data/lib/acts_as_taggable_on/taggable/tagged_with_query/all_tags_query.rb +110 -106
- data/lib/acts_as_taggable_on/taggable/tagged_with_query/any_tags_query.rb +57 -53
- data/lib/acts_as_taggable_on/taggable/tagged_with_query/exclude_tags_query.rb +63 -60
- data/lib/acts_as_taggable_on/taggable/tagged_with_query/query_base.rb +54 -46
- data/lib/acts_as_taggable_on/taggable/tagged_with_query.rb +14 -8
- data/lib/acts_as_taggable_on/taggable.rb +30 -12
- data/lib/acts_as_taggable_on/tagger.rb +9 -5
- data/lib/acts_as_taggable_on/tagging.rb +8 -4
- data/lib/acts_as_taggable_on/tags_helper.rb +3 -1
- data/lib/acts_as_taggable_on/utils.rb +4 -2
- data/lib/acts_as_taggable_on/version.rb +3 -1
- data/spec/acts_as_taggable_on/tag_spec.rb +16 -1
- data/spec/acts_as_taggable_on/taggable_spec.rb +6 -2
- data/spec/acts_as_taggable_on/tagging_spec.rb +26 -0
- data/spec/internal/app/models/taggable_model.rb +2 -0
- data/spec/internal/config/database.yml.sample +4 -8
- data/spec/internal/db/schema.rb +3 -0
- data/spec/support/database.rb +36 -26
- metadata +13 -22
- data/.travis.yml +0 -49
- data/UPGRADING.md +0 -8
- data/gemfiles/activerecord_5.0.gemfile +0 -21
- data/gemfiles/activerecord_5.1.gemfile +0 -21
@@ -1,14 +1,17 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActsAsTaggableOn
|
4
|
+
module Taggable
|
5
|
+
module Collection
|
6
|
+
def self.included(base)
|
7
|
+
base.extend ActsAsTaggableOn::Taggable::Collection::ClassMethods
|
8
|
+
base.initialize_acts_as_taggable_on_collection
|
9
|
+
end
|
7
10
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
11
|
+
module ClassMethods
|
12
|
+
def initialize_acts_as_taggable_on_collection
|
13
|
+
tag_types.map(&:to_s).each do |tag_type|
|
14
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
12
15
|
def self.#{tag_type.singularize}_counts(options={})
|
13
16
|
tag_counts_on('#{tag_type}', options)
|
14
17
|
end
|
@@ -24,159 +27,192 @@ module ActsAsTaggableOn::Taggable
|
|
24
27
|
def self.top_#{tag_type}(limit = 10)
|
25
28
|
tag_counts_on('#{tag_type}', order: 'count desc', limit: limit.to_i)
|
26
29
|
end
|
27
|
-
|
30
|
+
RUBY
|
31
|
+
end
|
28
32
|
end
|
29
|
-
end
|
30
|
-
|
31
|
-
def acts_as_taggable_on(*args)
|
32
|
-
super(*args)
|
33
|
-
initialize_acts_as_taggable_on_collection
|
34
|
-
end
|
35
|
-
|
36
|
-
def tag_counts_on(context, options = {})
|
37
|
-
all_tag_counts(options.merge({on: context.to_s}))
|
38
|
-
end
|
39
33
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
##
|
45
|
-
# Calculate the tag names.
|
46
|
-
# To be used when you don't need tag counts and want to avoid the taggable joins.
|
47
|
-
#
|
48
|
-
# @param [Hash] options Options:
|
49
|
-
# * :start_at - Restrict the tags to those created after a certain time
|
50
|
-
# * :end_at - Restrict the tags to those created before a certain time
|
51
|
-
# * :conditions - A piece of SQL conditions to add to the query. Note we don't join the taggable objects for performance reasons.
|
52
|
-
# * :limit - The maximum number of tags to return
|
53
|
-
# * :order - A piece of SQL to order by. Eg 'tags.count desc' or 'taggings.created_at desc'
|
54
|
-
# * :on - Scope the find to only include a certain context
|
55
|
-
def all_tags(options = {})
|
56
|
-
options = options.dup
|
57
|
-
options.assert_valid_keys :start_at, :end_at, :conditions, :order, :limit, :on
|
58
|
-
|
59
|
-
## Generate conditions:
|
60
|
-
options[:conditions] = sanitize_sql(options[:conditions]) if options[:conditions]
|
61
|
-
|
62
|
-
## Generate scope:
|
63
|
-
tagging_scope = ActsAsTaggableOn::Tagging.select("#{ActsAsTaggableOn::Tagging.table_name}.tag_id")
|
64
|
-
tag_scope = ActsAsTaggableOn::Tag.select("#{ActsAsTaggableOn::Tag.table_name}.*").order(options[:order]).limit(options[:limit])
|
65
|
-
|
66
|
-
# Joins and conditions
|
67
|
-
tagging_conditions(options).each { |condition| tagging_scope = tagging_scope.where(condition) }
|
68
|
-
tag_scope = tag_scope.where(options[:conditions])
|
69
|
-
|
70
|
-
group_columns = "#{ActsAsTaggableOn::Tagging.table_name}.tag_id"
|
71
|
-
|
72
|
-
# Append the current scope to the scope, because we can't use scope(:find) in RoR 3.0 anymore:
|
73
|
-
tagging_scope = generate_tagging_scope_in_clause(tagging_scope, table_name, primary_key).group(group_columns)
|
74
|
-
|
75
|
-
tag_scope_joins(tag_scope, tagging_scope)
|
76
|
-
end
|
77
|
-
|
78
|
-
##
|
79
|
-
# Calculate the tag counts for all tags.
|
80
|
-
#
|
81
|
-
# @param [Hash] options Options:
|
82
|
-
# * :start_at - Restrict the tags to those created after a certain time
|
83
|
-
# * :end_at - Restrict the tags to those created before a certain time
|
84
|
-
# * :conditions - A piece of SQL conditions to add to the query
|
85
|
-
# * :limit - The maximum number of tags to return
|
86
|
-
# * :order - A piece of SQL to order by. Eg 'tags.count desc' or 'taggings.created_at desc'
|
87
|
-
# * :at_least - Exclude tags with a frequency less than the given value
|
88
|
-
# * :at_most - Exclude tags with a frequency greater than the given value
|
89
|
-
# * :on - Scope the find to only include a certain context
|
90
|
-
def all_tag_counts(options = {})
|
91
|
-
options = options.dup
|
92
|
-
options.assert_valid_keys :start_at, :end_at, :conditions, :at_least, :at_most, :order, :limit, :on, :id
|
93
|
-
|
94
|
-
## Generate conditions:
|
95
|
-
options[:conditions] = sanitize_sql(options[:conditions]) if options[:conditions]
|
96
|
-
|
97
|
-
## Generate scope:
|
98
|
-
tagging_scope = ActsAsTaggableOn::Tagging.select("#{ActsAsTaggableOn::Tagging.table_name}.tag_id, COUNT(#{ActsAsTaggableOn::Tagging.table_name}.tag_id) AS tags_count")
|
99
|
-
tag_scope = ActsAsTaggableOn::Tag.select("#{ActsAsTaggableOn::Tag.table_name}.*, #{ActsAsTaggableOn::Tagging.table_name}.tags_count AS count").order(options[:order]).limit(options[:limit])
|
100
|
-
|
101
|
-
# Current model is STI descendant, so add type checking to the join condition
|
102
|
-
unless descends_from_active_record?
|
103
|
-
taggable_join = "INNER JOIN #{table_name} ON #{table_name}.#{primary_key} = #{ActsAsTaggableOn::Tagging.table_name}.taggable_id"
|
104
|
-
taggable_join << " AND #{table_name}.#{inheritance_column} = '#{name}'"
|
105
|
-
tagging_scope = tagging_scope.joins(taggable_join)
|
34
|
+
def acts_as_taggable_on(*args)
|
35
|
+
super(*args)
|
36
|
+
initialize_acts_as_taggable_on_collection
|
106
37
|
end
|
107
38
|
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
# GROUP BY and HAVING clauses:
|
113
|
-
having = ["COUNT(#{ActsAsTaggableOn::Tagging.table_name}.tag_id) > 0"]
|
114
|
-
having.push sanitize_sql(["COUNT(#{ActsAsTaggableOn::Tagging.table_name}.tag_id) >= ?", options.delete(:at_least)]) if options[:at_least]
|
115
|
-
having.push sanitize_sql(["COUNT(#{ActsAsTaggableOn::Tagging.table_name}.tag_id) <= ?", options.delete(:at_most)]) if options[:at_most]
|
116
|
-
having = having.compact.join(' AND ')
|
117
|
-
|
118
|
-
group_columns = "#{ActsAsTaggableOn::Tagging.table_name}.tag_id"
|
39
|
+
def tag_counts_on(context, options = {})
|
40
|
+
all_tag_counts(options.merge({ on: context.to_s }))
|
41
|
+
end
|
119
42
|
|
120
|
-
|
121
|
-
|
122
|
-
tagging_scope = generate_tagging_scope_in_clause(tagging_scope, table_name, primary_key)
|
43
|
+
def tags_on(context, options = {})
|
44
|
+
all_tags(options.merge({ on: context.to_s }))
|
123
45
|
end
|
124
46
|
|
125
|
-
|
47
|
+
##
|
48
|
+
# Calculate the tag names.
|
49
|
+
# To be used when you don't need tag counts and want to avoid the taggable joins.
|
50
|
+
#
|
51
|
+
# @param [Hash] options Options:
|
52
|
+
# * :start_at - Restrict the tags to those created after a certain time
|
53
|
+
# * :end_at - Restrict the tags to those created before a certain time
|
54
|
+
# * :conditions - A piece of SQL conditions to add to the query. Note we don't join the taggable objects for performance reasons.
|
55
|
+
# * :limit - The maximum number of tags to return
|
56
|
+
# * :order - A piece of SQL to order by. Eg 'tags.count desc' or 'taggings.created_at desc'
|
57
|
+
# * :on - Scope the find to only include a certain context
|
58
|
+
def all_tags(options = {})
|
59
|
+
options = options.dup
|
60
|
+
options.assert_valid_keys :start_at, :end_at, :conditions, :order, :limit, :on
|
61
|
+
|
62
|
+
## Generate conditions:
|
63
|
+
options[:conditions] = sanitize_sql(options[:conditions]) if options[:conditions]
|
64
|
+
|
65
|
+
## Generate scope:
|
66
|
+
tagging_scope = ActsAsTaggableOn::Tagging.select("#{ActsAsTaggableOn::Tagging.table_name}.tag_id")
|
67
|
+
tag_scope = ActsAsTaggableOn::Tag.select("#{ActsAsTaggableOn::Tag.table_name}.*").order(options[:order]).limit(options[:limit])
|
68
|
+
|
69
|
+
# Joins and conditions
|
70
|
+
tagging_conditions(options).each { |condition| tagging_scope = tagging_scope.where(condition) }
|
71
|
+
tag_scope = tag_scope.where(options[:conditions])
|
72
|
+
|
73
|
+
group_columns = "#{ActsAsTaggableOn::Tagging.table_name}.tag_id"
|
126
74
|
|
127
|
-
|
128
|
-
|
75
|
+
# Append the current scope to the scope, because we can't use scope(:find) in RoR 3.0 anymore:
|
76
|
+
tagging_scope = generate_tagging_scope_in_clause(tagging_scope, table_name, primary_key).group(group_columns)
|
129
77
|
|
130
|
-
|
131
|
-
|
132
|
-
end
|
78
|
+
tag_scope_joins(tag_scope, tagging_scope)
|
79
|
+
end
|
133
80
|
|
134
|
-
|
81
|
+
##
|
82
|
+
# Calculate the tag counts for all tags.
|
83
|
+
#
|
84
|
+
# @param [Hash] options Options:
|
85
|
+
# * :start_at - Restrict the tags to those created after a certain time
|
86
|
+
# * :end_at - Restrict the tags to those created before a certain time
|
87
|
+
# * :conditions - A piece of SQL conditions to add to the query
|
88
|
+
# * :limit - The maximum number of tags to return
|
89
|
+
# * :order - A piece of SQL to order by. Eg 'tags.count desc' or 'taggings.created_at desc'
|
90
|
+
# * :at_least - Exclude tags with a frequency less than the given value
|
91
|
+
# * :at_most - Exclude tags with a frequency greater than the given value
|
92
|
+
# * :on - Scope the find to only include a certain context
|
93
|
+
def all_tag_counts(options = {})
|
94
|
+
options = options.dup
|
95
|
+
options.assert_valid_keys :start_at, :end_at, :conditions, :at_least, :at_most, :order, :limit, :on, :id
|
96
|
+
|
97
|
+
## Generate conditions:
|
98
|
+
options[:conditions] = sanitize_sql(options[:conditions]) if options[:conditions]
|
99
|
+
|
100
|
+
## Generate scope:
|
101
|
+
tagging_scope = ActsAsTaggableOn::Tagging.select("#{ActsAsTaggableOn::Tagging.table_name}.tag_id, COUNT(#{ActsAsTaggableOn::Tagging.table_name}.tag_id) AS tags_count")
|
102
|
+
tag_scope = ActsAsTaggableOn::Tag.select("#{ActsAsTaggableOn::Tag.table_name}.*, #{ActsAsTaggableOn::Tagging.table_name}.tags_count AS count").order(options[:order]).limit(options[:limit])
|
103
|
+
|
104
|
+
# Current model is STI descendant, so add type checking to the join condition
|
105
|
+
unless descends_from_active_record?
|
106
|
+
taggable_join = "INNER JOIN #{table_name} ON #{table_name}.#{primary_key} = #{ActsAsTaggableOn::Tagging.table_name}.taggable_id"
|
107
|
+
taggable_join = taggable_join + " AND #{table_name}.#{inheritance_column} = '#{name}'"
|
108
|
+
tagging_scope = tagging_scope.joins(taggable_join)
|
109
|
+
end
|
110
|
+
|
111
|
+
# Conditions
|
112
|
+
tagging_conditions(options).each { |condition| tagging_scope = tagging_scope.where(condition) }
|
113
|
+
tag_scope = tag_scope.where(options[:conditions])
|
114
|
+
|
115
|
+
# GROUP BY and HAVING clauses:
|
116
|
+
having = ["COUNT(#{ActsAsTaggableOn::Tagging.table_name}.tag_id) > 0"]
|
117
|
+
if options[:at_least]
|
118
|
+
having.push sanitize_sql(["COUNT(#{ActsAsTaggableOn::Tagging.table_name}.tag_id) >= ?",
|
119
|
+
options.delete(:at_least)])
|
120
|
+
end
|
121
|
+
if options[:at_most]
|
122
|
+
having.push sanitize_sql(["COUNT(#{ActsAsTaggableOn::Tagging.table_name}.tag_id) <= ?",
|
123
|
+
options.delete(:at_most)])
|
124
|
+
end
|
125
|
+
having = having.compact.join(' AND ')
|
126
|
+
|
127
|
+
group_columns = "#{ActsAsTaggableOn::Tagging.table_name}.tag_id"
|
128
|
+
|
129
|
+
unless options[:id]
|
130
|
+
# Append the current scope to the scope, because we can't use scope(:find) in RoR 3.0 anymore:
|
131
|
+
tagging_scope = generate_tagging_scope_in_clause(tagging_scope, table_name, primary_key)
|
132
|
+
end
|
133
|
+
|
134
|
+
tagging_scope = tagging_scope.group(group_columns).having(having)
|
135
|
+
|
136
|
+
tag_scope_joins(tag_scope, tagging_scope)
|
137
|
+
end
|
135
138
|
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
139
|
+
def safe_to_sql(relation)
|
140
|
+
if connection.respond_to?(:unprepared_statement)
|
141
|
+
connection.unprepared_statement do
|
142
|
+
relation.to_sql
|
143
|
+
end
|
144
|
+
else
|
145
|
+
relation.to_sql
|
146
|
+
end
|
144
147
|
end
|
145
148
|
|
146
|
-
|
147
|
-
end
|
149
|
+
private
|
148
150
|
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
151
|
+
def generate_tagging_scope_in_clause(tagging_scope, table_name, primary_key)
|
152
|
+
table_name_pkey = "#{table_name}.#{primary_key}"
|
153
|
+
if ActsAsTaggableOn::Utils.using_mysql?
|
154
|
+
# See https://github.com/mbleigh/acts-as-taggable-on/pull/457 for details
|
155
|
+
scoped_ids = pluck(table_name_pkey)
|
156
|
+
tagging_scope = tagging_scope.where("#{ActsAsTaggableOn::Tagging.table_name}.taggable_id IN (?)",
|
157
|
+
scoped_ids)
|
158
|
+
else
|
159
|
+
tagging_scope = tagging_scope.where("#{ActsAsTaggableOn::Tagging.table_name}.taggable_id IN(#{safe_to_sql(except(:select).select(table_name_pkey))})")
|
160
|
+
end
|
153
161
|
|
154
|
-
|
155
|
-
|
156
|
-
taggable_conditions << sanitize_sql([" AND #{ActsAsTaggableOn::Tagging.table_name}.taggable_id = ?", options[:id]]) if options[:id]
|
162
|
+
tagging_scope
|
163
|
+
end
|
157
164
|
|
158
|
-
tagging_conditions
|
165
|
+
def tagging_conditions(options)
|
166
|
+
tagging_conditions = []
|
167
|
+
if options[:end_at]
|
168
|
+
tagging_conditions.push sanitize_sql(["#{ActsAsTaggableOn::Tagging.table_name}.created_at <= ?",
|
169
|
+
options.delete(:end_at)])
|
170
|
+
end
|
171
|
+
if options[:start_at]
|
172
|
+
tagging_conditions.push sanitize_sql(["#{ActsAsTaggableOn::Tagging.table_name}.created_at >= ?",
|
173
|
+
options.delete(:start_at)])
|
174
|
+
end
|
175
|
+
|
176
|
+
taggable_conditions = sanitize_sql(["#{ActsAsTaggableOn::Tagging.table_name}.taggable_type = ?",
|
177
|
+
base_class.name])
|
178
|
+
if options[:on]
|
179
|
+
taggable_conditions << sanitize_sql([" AND #{ActsAsTaggableOn::Tagging.table_name}.context = ?",
|
180
|
+
options.delete(:on).to_s])
|
181
|
+
end
|
182
|
+
|
183
|
+
if options[:id]
|
184
|
+
taggable_conditions << if options[:id].is_a? Array
|
185
|
+
sanitize_sql([" AND #{ActsAsTaggableOn::Tagging.table_name}.taggable_id IN (?)",
|
186
|
+
options[:id]])
|
187
|
+
else
|
188
|
+
sanitize_sql([" AND #{ActsAsTaggableOn::Tagging.table_name}.taggable_id = ?",
|
189
|
+
options[:id]])
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
tagging_conditions.push taggable_conditions
|
194
|
+
|
195
|
+
tagging_conditions
|
196
|
+
end
|
159
197
|
|
160
|
-
|
198
|
+
def tag_scope_joins(tag_scope, tagging_scope)
|
199
|
+
tag_scope = tag_scope.joins("JOIN (#{safe_to_sql(tagging_scope)}) AS #{ActsAsTaggableOn::Tagging.table_name} ON #{ActsAsTaggableOn::Tagging.table_name}.tag_id = #{ActsAsTaggableOn::Tag.table_name}.id")
|
200
|
+
tag_scope.extending(CalculationMethods)
|
201
|
+
end
|
161
202
|
end
|
162
203
|
|
163
|
-
def
|
164
|
-
|
165
|
-
tag_scope.extending(CalculationMethods)
|
204
|
+
def tag_counts_on(context, options = {})
|
205
|
+
self.class.tag_counts_on(context, options.merge(id: id))
|
166
206
|
end
|
167
|
-
end
|
168
207
|
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
def count(column_name = :all, options = {})
|
178
|
-
# https://github.com/rails/rails/commit/da9b5d4a8435b744fcf278fffd6d7f1e36d4a4f2
|
179
|
-
super(column_name)
|
208
|
+
module CalculationMethods
|
209
|
+
# Rails 5 TODO: Remove options argument as soon we remove support to
|
210
|
+
# activerecord-deprecated_finders.
|
211
|
+
# See https://github.com/rails/rails/blob/master/activerecord/lib/active_record/relation/calculations.rb#L38
|
212
|
+
def count(column_name = :all, _options = {})
|
213
|
+
# https://github.com/rails/rails/commit/da9b5d4a8435b744fcf278fffd6d7f1e36d4a4f2
|
214
|
+
super(column_name)
|
215
|
+
end
|
180
216
|
end
|
181
217
|
end
|
182
218
|
end
|