acts-as-taggable-on 8.1.0 → 9.0.0

Sign up to get free protection for your applications and to get access to all the features.
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