acts-as-taggable-on 8.1.0 → 9.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 (43) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/spec.yml +15 -34
  3. data/Appraisals +13 -13
  4. data/CHANGELOG.md +13 -3
  5. data/README.md +6 -6
  6. data/acts-as-taggable-on.gemspec +2 -2
  7. data/db/migrate/1_acts_as_taggable_on_migration.rb +5 -7
  8. data/db/migrate/2_add_missing_unique_indices.rb +6 -8
  9. data/db/migrate/3_add_taggings_counter_cache_to_tags.rb +3 -6
  10. data/db/migrate/4_add_missing_taggable_index.rb +5 -7
  11. data/db/migrate/5_change_collation_for_tag_names.rb +4 -6
  12. data/db/migrate/6_add_missing_indexes_on_taggings.rb +15 -13
  13. data/db/migrate/7_add_tenant_to_taggings.rb +7 -10
  14. data/docker-compose.yml +15 -0
  15. data/gemfiles/activerecord_6.0.gemfile +5 -8
  16. data/gemfiles/activerecord_6.1.gemfile +3 -8
  17. data/gemfiles/{activerecord_5.0.gemfile → activerecord_7.0.gemfile} +6 -9
  18. data/lib/acts_as_taggable_on/default_parser.rb +8 -10
  19. data/lib/acts_as_taggable_on/engine.rb +2 -0
  20. data/lib/acts_as_taggable_on/generic_parser.rb +2 -0
  21. data/lib/acts_as_taggable_on/tag.rb +30 -30
  22. data/lib/acts_as_taggable_on/tag_list.rb +8 -11
  23. data/lib/acts_as_taggable_on/taggable/cache.rb +64 -62
  24. data/lib/acts_as_taggable_on/taggable/collection.rb +178 -142
  25. data/lib/acts_as_taggable_on/taggable/core.rb +248 -244
  26. data/lib/acts_as_taggable_on/taggable/ownership.rb +110 -98
  27. data/lib/acts_as_taggable_on/taggable/related.rb +60 -47
  28. data/lib/acts_as_taggable_on/taggable/tag_list_type.rb +6 -2
  29. data/lib/acts_as_taggable_on/taggable/tagged_with_query/all_tags_query.rb +110 -106
  30. data/lib/acts_as_taggable_on/taggable/tagged_with_query/any_tags_query.rb +57 -53
  31. data/lib/acts_as_taggable_on/taggable/tagged_with_query/exclude_tags_query.rb +63 -60
  32. data/lib/acts_as_taggable_on/taggable/tagged_with_query/query_base.rb +54 -46
  33. data/lib/acts_as_taggable_on/taggable/tagged_with_query.rb +14 -8
  34. data/lib/acts_as_taggable_on/taggable.rb +14 -14
  35. data/lib/acts_as_taggable_on/tagger.rb +9 -5
  36. data/lib/acts_as_taggable_on/tagging.rb +6 -4
  37. data/lib/acts_as_taggable_on/tags_helper.rb +3 -1
  38. data/lib/acts_as_taggable_on/utils.rb +4 -2
  39. data/lib/acts_as_taggable_on/version.rb +3 -1
  40. data/spec/support/database.rb +36 -26
  41. metadata +13 -14
  42. data/gemfiles/activerecord_5.1.gemfile +0 -21
  43. data/gemfiles/activerecord_5.2.gemfile +0 -21
@@ -1,10 +1,10 @@
1
+ # frozen_string_literal: true
1
2
 
2
3
  require 'active_support/core_ext/module/delegation'
3
4
 
4
5
  module ActsAsTaggableOn
5
6
  class TagList < Array
6
- attr_accessor :owner
7
- attr_accessor :parser
7
+ attr_accessor :owner, :parser
8
8
 
9
9
  def initialize(*args)
10
10
  @parser = ActsAsTaggableOn.default_parser
@@ -34,8 +34,8 @@ module ActsAsTaggableOn
34
34
 
35
35
  # Concatenation --- Returns a new tag list built by concatenating the
36
36
  # two tag lists together to produce a third tag list.
37
- def +(other_tag_list)
38
- TagList.new.add(self).add(other_tag_list)
37
+ def +(other)
38
+ TagList.new.add(self).add(other)
39
39
  end
40
40
 
41
41
  # Appends the elements of +other_tag_list+ to +self+.
@@ -65,12 +65,12 @@ module ActsAsTaggableOn
65
65
  # tag_list = TagList.new("Round", "Square,Cube")
66
66
  # tag_list.to_s # 'Round, "Square,Cube"'
67
67
  def to_s
68
- tags = frozen? ? self.dup : self
68
+ tags = frozen? ? dup : self
69
69
  tags.send(:clean!)
70
70
 
71
71
  tags.map do |name|
72
72
  d = ActsAsTaggableOn.delimiter
73
- d = Regexp.new d.join('|') if d.kind_of? Array
73
+ d = Regexp.new d.join('|') if d.is_a? Array
74
74
  name.index(d) ? "\"#{name}\"" : name
75
75
  end.join(ActsAsTaggableOn.glue)
76
76
  end
@@ -85,22 +85,19 @@ module ActsAsTaggableOn
85
85
  map! { |tag| tag.mb_chars.downcase.to_s } if ActsAsTaggableOn.force_lowercase
86
86
  map!(&:parameterize) if ActsAsTaggableOn.force_parameterize
87
87
 
88
- ActsAsTaggableOn.strict_case_match ? uniq! : uniq!{ |tag| tag.downcase }
88
+ ActsAsTaggableOn.strict_case_match ? uniq! : uniq!(&:downcase)
89
89
  self
90
90
  end
91
91
 
92
-
93
92
  def extract_and_apply_options!(args)
94
93
  options = args.last.is_a?(Hash) ? args.pop : {}
95
94
  options.assert_valid_keys :parse, :parser
96
95
 
97
- parser = options[:parser] ? options[:parser] : @parser
96
+ parser = options[:parser] || @parser
98
97
 
99
98
  args.map! { |a| parser.new(a).parse } if options[:parse] || options[:parser]
100
99
 
101
100
  args.flatten!
102
101
  end
103
-
104
102
  end
105
103
  end
106
-
@@ -1,89 +1,91 @@
1
- module ActsAsTaggableOn::Taggable
2
- module Cache
3
- def self.included(base)
4
- # When included, conditionally adds tag caching methods when the model
5
- # has any "cached_#{tag_type}_list" column
6
- base.extend Columns
7
- end
1
+ # frozen_string_literal: true
8
2
 
9
- module Columns
10
- # ActiveRecord::Base.columns makes a database connection and caches the
11
- # calculated columns hash for the record as @columns. Since we don't
12
- # want to add caching methods until we confirm the presence of a
13
- # caching column, and we don't want to force opening a database
14
- # connection when the class is loaded, here we intercept and cache
15
- # the call to :columns as @acts_as_taggable_on_cache_columns
16
- # to mimic the underlying behavior. While processing this first
17
- # call to columns, we do the caching column check and dynamically add
18
- # the class and instance methods
19
- # FIXME: this method cannot compile in rubinius
20
- def columns
21
- @acts_as_taggable_on_cache_columns ||= begin
22
- db_columns = super
23
- _add_tags_caching_methods if _has_tags_cache_columns?(db_columns)
24
- db_columns
25
- end
3
+ module ActsAsTaggableOn
4
+ module Taggable
5
+ module Cache
6
+ def self.included(base)
7
+ # When included, conditionally adds tag caching methods when the model
8
+ # has any "cached_#{tag_type}_list" column
9
+ base.extend Columns
26
10
  end
27
11
 
28
- def reset_column_information
29
- super
30
- @acts_as_taggable_on_cache_columns = nil
31
- end
12
+ module Columns
13
+ # ActiveRecord::Base.columns makes a database connection and caches the
14
+ # calculated columns hash for the record as @columns. Since we don't
15
+ # want to add caching methods until we confirm the presence of a
16
+ # caching column, and we don't want to force opening a database
17
+ # connection when the class is loaded, here we intercept and cache
18
+ # the call to :columns as @acts_as_taggable_on_cache_columns
19
+ # to mimic the underlying behavior. While processing this first
20
+ # call to columns, we do the caching column check and dynamically add
21
+ # the class and instance methods
22
+ # FIXME: this method cannot compile in rubinius
23
+ def columns
24
+ @acts_as_taggable_on_cache_columns ||= begin
25
+ db_columns = super
26
+ _add_tags_caching_methods if _has_tags_cache_columns?(db_columns)
27
+ db_columns
28
+ end
29
+ end
32
30
 
33
- private
31
+ def reset_column_information
32
+ super
33
+ @acts_as_taggable_on_cache_columns = nil
34
+ end
35
+
36
+ private
34
37
 
35
- # @private
36
- def _has_tags_cache_columns?(db_columns)
37
- db_column_names = db_columns.map(&:name)
38
- tag_types.any? do |context|
39
- db_column_names.include?("cached_#{context.to_s.singularize}_list")
38
+ # @private
39
+ def _has_tags_cache_columns?(db_columns)
40
+ db_column_names = db_columns.map(&:name)
41
+ tag_types.any? do |context|
42
+ db_column_names.include?("cached_#{context.to_s.singularize}_list")
43
+ end
40
44
  end
41
- end
42
45
 
43
- # @private
44
- def _add_tags_caching_methods
45
- send :include, ActsAsTaggableOn::Taggable::Cache::InstanceMethods
46
- extend ActsAsTaggableOn::Taggable::Cache::ClassMethods
46
+ # @private
47
+ def _add_tags_caching_methods
48
+ send :include, ActsAsTaggableOn::Taggable::Cache::InstanceMethods
49
+ extend ActsAsTaggableOn::Taggable::Cache::ClassMethods
47
50
 
48
- before_save :save_cached_tag_list
51
+ before_save :save_cached_tag_list
49
52
 
50
- initialize_tags_cache
53
+ initialize_tags_cache
54
+ end
51
55
  end
52
- end
53
56
 
54
- module ClassMethods
55
- def initialize_tags_cache
56
- tag_types.map(&:to_s).each do |tag_type|
57
- class_eval <<-RUBY, __FILE__, __LINE__ + 1
57
+ module ClassMethods
58
+ def initialize_tags_cache
59
+ tag_types.map(&:to_s).each do |tag_type|
60
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
58
61
  def self.caching_#{tag_type.singularize}_list?
59
62
  caching_tag_list_on?("#{tag_type}")
60
63
  end
61
- RUBY
64
+ RUBY
65
+ end
62
66
  end
63
- end
64
67
 
65
- def acts_as_taggable_on(*args)
66
- super(*args)
67
- initialize_tags_cache
68
- end
68
+ def acts_as_taggable_on(*args)
69
+ super(*args)
70
+ initialize_tags_cache
71
+ end
69
72
 
70
- def caching_tag_list_on?(context)
71
- column_names.include?("cached_#{context.to_s.singularize}_list")
73
+ def caching_tag_list_on?(context)
74
+ column_names.include?("cached_#{context.to_s.singularize}_list")
75
+ end
72
76
  end
73
- end
74
77
 
75
- module InstanceMethods
76
- def save_cached_tag_list
77
- tag_types.map(&:to_s).each do |tag_type|
78
- if self.class.send("caching_#{tag_type.singularize}_list?")
79
- if tag_list_cache_set_on(tag_type)
78
+ module InstanceMethods
79
+ def save_cached_tag_list
80
+ tag_types.map(&:to_s).each do |tag_type|
81
+ if self.class.send("caching_#{tag_type.singularize}_list?") && tag_list_cache_set_on(tag_type)
80
82
  list = tag_list_cache_on(tag_type).to_a.flatten.compact.join("#{ActsAsTaggableOn.delimiter} ")
81
83
  self["cached_#{tag_type.singularize}_list"] = list
82
84
  end
83
85
  end
84
- end
85
86
 
86
- true
87
+ true
88
+ end
87
89
  end
88
90
  end
89
91
  end
@@ -1,14 +1,17 @@
1
- module ActsAsTaggableOn::Taggable
2
- module Collection
3
- def self.included(base)
4
- base.extend ActsAsTaggableOn::Taggable::Collection::ClassMethods
5
- base.initialize_acts_as_taggable_on_collection
6
- end
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
- module ClassMethods
9
- def initialize_acts_as_taggable_on_collection
10
- tag_types.map(&:to_s).each do |tag_type|
11
- class_eval <<-RUBY, __FILE__, __LINE__ + 1
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
- RUBY
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
- def tags_on(context, options = {})
41
- all_tags(options.merge({on: context.to_s}))
42
- end
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
- # Conditions
109
- tagging_conditions(options).each { |condition| tagging_scope = tagging_scope.where(condition) }
110
- tag_scope = tag_scope.where(options[:conditions])
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
- unless options[:id]
121
- # Append the current scope to the scope, because we can't use scope(:find) in RoR 3.0 anymore:
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
- tagging_scope = tagging_scope.group(group_columns).having(having)
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
- tag_scope_joins(tag_scope, tagging_scope)
128
- end
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
- def safe_to_sql(relation)
131
- connection.respond_to?(:unprepared_statement) ? connection.unprepared_statement { relation.to_sql } : relation.to_sql
132
- end
78
+ tag_scope_joins(tag_scope, tagging_scope)
79
+ end
133
80
 
134
- private
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
- def generate_tagging_scope_in_clause(tagging_scope, table_name, primary_key)
137
- table_name_pkey = "#{table_name}.#{primary_key}"
138
- if ActsAsTaggableOn::Utils.using_mysql?
139
- # See https://github.com/mbleigh/acts-as-taggable-on/pull/457 for details
140
- scoped_ids = pluck(table_name_pkey)
141
- tagging_scope = tagging_scope.where("#{ActsAsTaggableOn::Tagging.table_name}.taggable_id IN (?)", scoped_ids)
142
- else
143
- tagging_scope = tagging_scope.where("#{ActsAsTaggableOn::Tagging.table_name}.taggable_id IN(#{safe_to_sql(except(:select).select(table_name_pkey))})")
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
- tagging_scope
147
- end
149
+ private
148
150
 
149
- def tagging_conditions(options)
150
- tagging_conditions = []
151
- tagging_conditions.push sanitize_sql(["#{ActsAsTaggableOn::Tagging.table_name}.created_at <= ?", options.delete(:end_at)]) if options[:end_at]
152
- tagging_conditions.push sanitize_sql(["#{ActsAsTaggableOn::Tagging.table_name}.created_at >= ?", options.delete(:start_at)]) if options[:start_at]
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
- taggable_conditions = sanitize_sql(["#{ActsAsTaggableOn::Tagging.table_name}.taggable_type = ?", base_class.name])
155
- taggable_conditions << sanitize_sql([" AND #{ActsAsTaggableOn::Tagging.table_name}.context = ?", options.delete(:on).to_s]) if options[:on]
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.push taggable_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
- tagging_conditions
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 tag_scope_joins(tag_scope, tagging_scope)
164
- 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")
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
- def tag_counts_on(context, options={})
170
- self.class.tag_counts_on(context, options.merge(id: id))
171
- end
172
-
173
- module CalculationMethods
174
- # Rails 5 TODO: Remove options argument as soon we remove support to
175
- # activerecord-deprecated_finders.
176
- # See https://github.com/rails/rails/blob/master/activerecord/lib/active_record/relation/calculations.rb#L38
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