acts-as-taggable-on 3.1.1 → 3.2.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.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -1
  3. data/.travis.yml +9 -7
  4. data/Appraisals +13 -8
  5. data/CHANGELOG.md +8 -0
  6. data/Gemfile +1 -2
  7. data/README.md +23 -13
  8. data/Rakefile +5 -17
  9. data/UPGRADING.md +6 -0
  10. data/acts-as-taggable-on.gemspec +13 -13
  11. data/db/migrate/1_acts_as_taggable_on_migration.rb +3 -3
  12. data/db/migrate/2_add_missing_unique_indices.rb +3 -5
  13. data/db/migrate/3_add_taggings_counter_cache_to_tags.rb +1 -1
  14. data/gemfiles/activerecord_3.2.gemfile +15 -0
  15. data/gemfiles/activerecord_4.0.gemfile +15 -0
  16. data/gemfiles/activerecord_4.1.gemfile +15 -0
  17. data/gemfiles/activerecord_edge.gemfile +16 -0
  18. data/lib/acts-as-taggable-on.rb +23 -21
  19. data/lib/acts_as_taggable_on/acts_as_taggable_on/cache.rb +1 -4
  20. data/lib/acts_as_taggable_on/acts_as_taggable_on/collection.rb +29 -20
  21. data/lib/acts_as_taggable_on/acts_as_taggable_on/compatibility.rb +11 -10
  22. data/lib/acts_as_taggable_on/acts_as_taggable_on/core.rb +98 -80
  23. data/lib/acts_as_taggable_on/acts_as_taggable_on/ownership.rb +5 -12
  24. data/lib/acts_as_taggable_on/acts_as_taggable_on/related.rb +7 -7
  25. data/lib/acts_as_taggable_on/engine.rb +0 -1
  26. data/lib/acts_as_taggable_on/tag.rb +24 -19
  27. data/lib/acts_as_taggable_on/tag_list.rb +95 -21
  28. data/lib/acts_as_taggable_on/taggable.rb +28 -30
  29. data/lib/acts_as_taggable_on/tagger.rb +30 -18
  30. data/lib/acts_as_taggable_on/tagging.rb +7 -8
  31. data/lib/acts_as_taggable_on/tags_helper.rb +1 -1
  32. data/lib/acts_as_taggable_on/utils.rb +25 -3
  33. data/lib/acts_as_taggable_on/version.rb +1 -1
  34. data/spec/acts_as_taggable_on/acts_as_taggable_on_spec.rb +133 -138
  35. data/spec/acts_as_taggable_on/acts_as_tagger_spec.rb +55 -58
  36. data/spec/acts_as_taggable_on/caching_spec.rb +34 -35
  37. data/spec/acts_as_taggable_on/related_spec.rb +59 -113
  38. data/spec/acts_as_taggable_on/single_table_inheritance_spec.rb +118 -95
  39. data/spec/acts_as_taggable_on/tag_list_spec.rb +89 -57
  40. data/spec/acts_as_taggable_on/tag_spec.rb +125 -114
  41. data/spec/acts_as_taggable_on/taggable_spec.rb +538 -352
  42. data/spec/acts_as_taggable_on/tagger_spec.rb +81 -78
  43. data/spec/acts_as_taggable_on/tagging_spec.rb +13 -14
  44. data/spec/acts_as_taggable_on/tags_helper_spec.rb +25 -25
  45. data/spec/acts_as_taggable_on/utils_spec.rb +9 -9
  46. data/spec/internal/app/models/altered_inheriting_taggable_model.rb +3 -0
  47. data/spec/internal/app/models/cached_model.rb +3 -0
  48. data/spec/internal/app/models/cached_model_with_array.rb +5 -0
  49. data/spec/internal/app/models/company.rb +15 -0
  50. data/spec/internal/app/models/inheriting_taggable_model.rb +2 -0
  51. data/spec/internal/app/models/market.rb +2 -0
  52. data/spec/{models.rb → internal/app/models/models.rb} +34 -2
  53. data/spec/internal/app/models/non_standard_id_taggable_model.rb +8 -0
  54. data/spec/internal/app/models/ordered_taggable_model.rb +4 -0
  55. data/spec/internal/app/models/other_cached_model.rb +3 -0
  56. data/spec/internal/app/models/other_taggable_model.rb +4 -0
  57. data/spec/internal/app/models/student.rb +2 -0
  58. data/spec/internal/app/models/taggable_model.rb +13 -0
  59. data/spec/internal/app/models/untaggable_model.rb +3 -0
  60. data/spec/internal/app/models/user.rb +3 -0
  61. data/spec/{database.yml.sample → internal/config/database.yml.sample} +2 -2
  62. data/spec/internal/db/schema.rb +97 -0
  63. data/spec/schema.rb +11 -0
  64. data/spec/spec_helper.rb +9 -62
  65. data/spec/support/array.rb +9 -0
  66. data/spec/support/database.rb +42 -0
  67. data/spec/support/database_cleaner.rb +17 -0
  68. metadata +101 -37
  69. data/gemfiles/rails_3.2.gemfile +0 -7
  70. data/gemfiles/rails_4.0.gemfile +0 -7
  71. data/gemfiles/rails_4.1.gemfile +0 -7
  72. data/gemfiles/rails_edge.gemfile +0 -7
@@ -34,9 +34,7 @@ module ActsAsTaggableOn::Taggable
34
34
  def columns
35
35
  @acts_as_taggable_on_cache_columns ||= begin
36
36
  db_columns = super
37
- if _has_tags_cache_columns?(db_columns)
38
- _add_tags_caching_methods
39
- end
37
+ _add_tags_caching_methods if _has_tags_cache_columns?(db_columns)
40
38
  db_columns
41
39
  end
42
40
  end
@@ -79,6 +77,5 @@ module ActsAsTaggableOn::Taggable
79
77
  true
80
78
  end
81
79
  end
82
-
83
80
  end
84
81
  end
@@ -18,11 +18,11 @@ module ActsAsTaggableOn::Taggable
18
18
  end
19
19
 
20
20
  def top_#{tag_type}(limit = 10)
21
- tag_counts_on('#{tag_type}', :order => 'count desc', :limit => limit.to_i)
21
+ tag_counts_on('#{tag_type}', order: 'count desc', limit: limit.to_i)
22
22
  end
23
23
 
24
24
  def self.top_#{tag_type}(limit = 10)
25
- tag_counts_on('#{tag_type}', :order => 'count desc', :limit => limit.to_i)
25
+ tag_counts_on('#{tag_type}', order: 'count desc', limit: limit.to_i)
26
26
  end
27
27
  RUBY
28
28
  end
@@ -34,11 +34,11 @@ module ActsAsTaggableOn::Taggable
34
34
  end
35
35
 
36
36
  def tag_counts_on(context, options = {})
37
- all_tag_counts(options.merge({:on => context.to_s}))
37
+ all_tag_counts(options.merge({on: context.to_s}))
38
38
  end
39
39
 
40
40
  def tags_on(context, options = {})
41
- all_tags(options.merge({:on => context.to_s}))
41
+ all_tags(options.merge({on: context.to_s}))
42
42
  end
43
43
 
44
44
  ##
@@ -64,18 +64,16 @@ module ActsAsTaggableOn::Taggable
64
64
 
65
65
  # Joins and conditions
66
66
  tagging_conditions(options).each { |condition| tagging_scope = tagging_scope.where(condition) }
67
- tag_scope = tag_scope.where(options[:conditions])
67
+ tag_scope = tag_scope.where(options[:conditions])
68
68
 
69
69
  group_columns = "#{ActsAsTaggableOn::Tagging.table_name}.tag_id"
70
70
 
71
71
  # Append the current scope to the scope, because we can't use scope(:find) in RoR 3.0 anymore:
72
- scoped_select = "#{table_name}.#{primary_key}"
73
- tagging_scope = tagging_scope.where("#{ActsAsTaggableOn::Tagging.table_name}.taggable_id IN(#{safe_to_sql(select(scoped_select))})").group(group_columns)
72
+ tagging_scope = generate_tagging_scope_in_clause(tagging_scope, table_name, primary_key).group(group_columns)
74
73
 
75
74
  tag_scope_joins(tag_scope, tagging_scope)
76
75
  end
77
76
 
78
-
79
77
  ##
80
78
  # Calculate the tag counts for all tags.
81
79
  #
@@ -98,7 +96,6 @@ module ActsAsTaggableOn::Taggable
98
96
  taggable_join = "INNER JOIN #{table_name} ON #{table_name}.#{primary_key} = #{ActsAsTaggableOn::Tagging.table_name}.taggable_id"
99
97
  taggable_join << " AND #{table_name}.#{inheritance_column} = '#{name}'" unless descends_from_active_record? # Current model is STI descendant, so add type checking to the join condition
100
98
 
101
-
102
99
  ## Generate scope:
103
100
  tagging_scope = ActsAsTaggableOn::Tagging.select("#{ActsAsTaggableOn::Tagging.table_name}.tag_id, COUNT(#{ActsAsTaggableOn::Tagging.table_name}.tag_id) AS tags_count")
104
101
  tag_scope = ActsAsTaggableOn::Tag.select("#{ActsAsTaggableOn::Tag.table_name}.*, #{ActsAsTaggableOn::Tagging.table_name}.tags_count AS count").order(options[:order]).limit(options[:limit])
@@ -106,7 +103,7 @@ module ActsAsTaggableOn::Taggable
106
103
  # Joins and conditions
107
104
  tagging_scope = tagging_scope.joins(taggable_join)
108
105
  tagging_conditions(options).each { |condition| tagging_scope = tagging_scope.where(condition) }
109
- tag_scope = tag_scope.where(options[:conditions])
106
+ tag_scope = tag_scope.where(options[:conditions])
110
107
 
111
108
  # GROUP BY and HAVING clauses:
112
109
  having = ["COUNT(#{ActsAsTaggableOn::Tagging.table_name}.tag_id) > 0"]
@@ -118,8 +115,7 @@ module ActsAsTaggableOn::Taggable
118
115
 
119
116
  unless options[:id]
120
117
  # Append the current scope to the scope, because we can't use scope(:find) in RoR 3.0 anymore:
121
- scoped_select = "#{table_name}.#{primary_key}"
122
- tagging_scope = tagging_scope.where("#{ActsAsTaggableOn::Tagging.table_name}.taggable_id IN(#{safe_to_sql(select(scoped_select))})")
118
+ tagging_scope = generate_tagging_scope_in_clause(tagging_scope, table_name, primary_key)
123
119
  end
124
120
 
125
121
  tagging_scope = tagging_scope.group(group_columns).having(having)
@@ -128,21 +124,34 @@ module ActsAsTaggableOn::Taggable
128
124
  end
129
125
 
130
126
  def safe_to_sql(relation)
131
- connection.respond_to?(:unprepared_statement) ? connection.unprepared_statement{relation.to_sql} : relation.to_sql
127
+ connection.respond_to?(:unprepared_statement) ? connection.unprepared_statement { relation.to_sql } : relation.to_sql
132
128
  end
133
129
 
134
130
  private
135
131
 
132
+ def generate_tagging_scope_in_clause(tagging_scope, table_name, primary_key)
133
+ table_name_pkey = "#{table_name}.#{primary_key}"
134
+ if ActsAsTaggableOn::Utils.using_mysql?
135
+ # See https://github.com/mbleigh/acts-as-taggable-on/pull/457 for details
136
+ scoped_ids = select(table_name_pkey).map(&:id)
137
+ tagging_scope = tagging_scope.where("#{ActsAsTaggableOn::Tagging.table_name}.taggable_id IN (?)", scoped_ids)
138
+ else
139
+ tagging_scope = tagging_scope.where("#{ActsAsTaggableOn::Tagging.table_name}.taggable_id IN(#{safe_to_sql(select(table_name_pkey))})")
140
+ end
141
+
142
+ tagging_scope
143
+ end
144
+
136
145
  def tagging_conditions(options)
137
146
  tagging_conditions = []
138
- tagging_conditions.push sanitize_sql(["#{ActsAsTaggableOn::Tagging.table_name}.created_at <= ?", options.delete(:end_at)]) if options[:end_at]
147
+ tagging_conditions.push sanitize_sql(["#{ActsAsTaggableOn::Tagging.table_name}.created_at <= ?", options.delete(:end_at)]) if options[:end_at]
139
148
  tagging_conditions.push sanitize_sql(["#{ActsAsTaggableOn::Tagging.table_name}.created_at >= ?", options.delete(:start_at)]) if options[:start_at]
140
149
 
141
- taggable_conditions = sanitize_sql(["#{ActsAsTaggableOn::Tagging.table_name}.taggable_type = ?", base_class.name])
150
+ taggable_conditions = sanitize_sql(["#{ActsAsTaggableOn::Tagging.table_name}.taggable_type = ?", base_class.name])
142
151
  taggable_conditions << sanitize_sql([" AND #{ActsAsTaggableOn::Tagging.table_name}.context = ?", options.delete(:on).to_s]) if options[:on]
143
- taggable_conditions << sanitize_sql([" AND #{ActsAsTaggableOn::Tagging.table_name}.taggable_id = ?", options[:id]]) if options[:id]
152
+ taggable_conditions << sanitize_sql([" AND #{ActsAsTaggableOn::Tagging.table_name}.taggable_id = ?", options[:id]]) if options[:id]
144
153
 
145
- tagging_conditions.push taggable_conditions
154
+ tagging_conditions.push taggable_conditions
146
155
 
147
156
  tagging_conditions
148
157
  end
@@ -154,13 +163,13 @@ module ActsAsTaggableOn::Taggable
154
163
  end
155
164
 
156
165
  def tag_counts_on(context, options={})
157
- self.class.tag_counts_on(context, options.merge(:id => id))
166
+ self.class.tag_counts_on(context, options.merge(id: id))
158
167
  end
159
168
 
160
169
  module CalculationMethods
161
- def count
170
+ def count(column_name=:all)
162
171
  # https://github.com/rails/rails/commit/da9b5d4a8435b744fcf278fffd6d7f1e36d4a4f2
163
- super(:all)
172
+ super
164
173
  end
165
174
  end
166
175
  end
@@ -1,26 +1,27 @@
1
1
  module ActsAsTaggableOn::Compatibility
2
- def has_many_with_compatibility(name, options = {}, &extention)
3
- if ActiveRecord::VERSION::MAJOR >= 4
4
- scope, opts = build_scope_and_options(options)
2
+ def has_many_with_taggable_compatibility(name, options = {}, &extention)
3
+ if ActsAsTaggableOn::Utils.active_record4?
4
+ scope, opts = build_taggable_scope_and_options(options)
5
5
  has_many(name, scope, opts, &extention)
6
6
  else
7
7
  has_many(name, options, &extention)
8
8
  end
9
9
  end
10
10
 
11
- def build_scope_and_options(opts)
12
- scope_opts, opts = parse_options(opts)
11
+ def build_taggable_scope_and_options(opts)
12
+ scope_opts, opts = parse_taggable_options(opts)
13
13
 
14
14
  unless scope_opts.empty?
15
- scope = lambda do
16
- scope_opts.inject(self) { |result, hash| result.send *hash }
17
- end
15
+ scope = -> {
16
+ scope_opts.inject(self) { |result, hash| result.send(*hash) }
17
+ }
18
+ return [scope, opts]
18
19
  end
19
20
 
20
- [defined?(scope) ? scope : nil, opts]
21
+ [nil, opts]
21
22
  end
22
23
 
23
- def parse_options(opts)
24
+ def parse_taggable_options(opts)
24
25
  scope_opts = {}
25
26
  [:order, :having, :select, :group, :limit, :offset, :readonly].each do |o|
26
27
  scope_opts[o] = opts.delete o if opts[o]
@@ -12,29 +12,28 @@ module ActsAsTaggableOn::Taggable
12
12
  end
13
13
 
14
14
  module ClassMethods
15
-
16
15
  def initialize_acts_as_taggable_on_core
17
16
  include taggable_mixin
18
17
  tag_types.map(&:to_s).each do |tags_type|
19
- tag_type = tags_type.to_s.singularize
18
+ tag_type = tags_type.to_s.singularize
20
19
  context_taggings = "#{tag_type}_taggings".to_sym
21
- context_tags = tags_type.to_sym
22
- taggings_order = (preserve_tag_order? ? "#{ActsAsTaggableOn::Tagging.table_name}.id" : [])
20
+ context_tags = tags_type.to_sym
21
+ taggings_order = (preserve_tag_order? ? "#{ActsAsTaggableOn::Tagging.table_name}.id" : [])
23
22
 
24
23
  class_eval do
25
24
  # when preserving tag order, include order option so that for a 'tags' context
26
25
  # the associations tag_taggings & tags are always returned in created order
27
- has_many_with_compatibility context_taggings, :as => :taggable,
28
- :dependent => :destroy,
29
- :class_name => "ActsAsTaggableOn::Tagging",
30
- :order => taggings_order,
31
- :conditions => ["#{ActsAsTaggableOn::Tagging.table_name}.context = (?)", tags_type],
32
- :include => :tag
33
-
34
- has_many_with_compatibility context_tags, :through => context_taggings,
35
- :source => :tag,
36
- :class_name => "ActsAsTaggableOn::Tag",
37
- :order => taggings_order
26
+ has_many_with_taggable_compatibility context_taggings, as: :taggable,
27
+ dependent: :destroy,
28
+ class_name: 'ActsAsTaggableOn::Tagging',
29
+ order: taggings_order,
30
+ conditions: ["#{ActsAsTaggableOn::Tagging.table_name}.context = (?)", tags_type],
31
+ include: :tag
32
+
33
+ has_many_with_taggable_compatibility context_tags, through: context_taggings,
34
+ source: :tag,
35
+ class_name: 'ActsAsTaggableOn::Tag',
36
+ order: taggings_order
38
37
 
39
38
  end
40
39
 
@@ -61,7 +60,7 @@ module ActsAsTaggableOn::Taggable
61
60
 
62
61
  # all column names are necessary for PostgreSQL group clause
63
62
  def grouped_column_names_for(object)
64
- object.column_names.map { |column| "#{object.table_name}.#{column}" }.join(", ")
63
+ object.column_names.map { |column| "#{object.table_name}.#{column}" }.join(', ')
65
64
  end
66
65
 
67
66
  ##
@@ -84,7 +83,7 @@ module ActsAsTaggableOn::Taggable
84
83
  # User.tagged_with("awesome", "cool", :owned_by => foo ) # Users that are tagged with just awesome and cool by 'foo'
85
84
  def tagged_with(tags, options = {})
86
85
  tag_list = ActsAsTaggableOn::TagList.from(tags)
87
- empty_result = where("1 = 0")
86
+ empty_result = where('1 = 0')
88
87
 
89
88
  return empty_result if tag_list.empty?
90
89
 
@@ -96,33 +95,33 @@ module ActsAsTaggableOn::Taggable
96
95
 
97
96
  context = options.delete(:on)
98
97
  owned_by = options.delete(:owned_by)
99
- alias_base_name = undecorated_table_name.gsub('.','_')
100
- quote = ActsAsTaggableOn::Tag.using_postgresql? ? '"' : ''
98
+ alias_base_name = undecorated_table_name.gsub('.', '_')
99
+ quote = ActsAsTaggableOn::Utils.using_postgresql? ? '"' : ''
101
100
 
102
101
  if options.delete(:exclude)
103
102
  if options.delete(:wild)
104
- tags_conditions = tag_list.map { |t| sanitize_sql(["#{ActsAsTaggableOn::Tag.table_name}.name #{like_operator} ? ESCAPE '!'", "%#{escape_like(t)}%"]) }.join(" OR ")
103
+ 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 ')
105
104
  else
106
- tags_conditions = tag_list.map { |t| sanitize_sql(["#{ActsAsTaggableOn::Tag.table_name}.name #{like_operator} ?", t]) }.join(" OR ")
105
+ tags_conditions = tag_list.map { |t| sanitize_sql(["#{ActsAsTaggableOn::Tag.table_name}.name #{ActsAsTaggableOn::Utils.like_operator} ?", t]) }.join(' OR ')
107
106
  end
108
107
 
109
108
  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)})"
110
109
 
111
110
  if owned_by
112
- joins << "JOIN #{ActsAsTaggableOn::Tagging.table_name}" +
113
- " ON #{ActsAsTaggableOn::Tagging.table_name}.taggable_id = #{quote}#{table_name}#{quote}.#{primary_key}" +
114
- " AND #{ActsAsTaggableOn::Tagging.table_name}.taggable_type = #{quote_value(base_class.name, nil)}" +
115
- " AND #{ActsAsTaggableOn::Tagging.table_name}.tagger_id = #{quote_value(owned_by.id, nil)}" +
116
- " AND #{ActsAsTaggableOn::Tagging.table_name}.tagger_type = #{quote_value(owned_by.class.base_class.to_s, nil)}"
111
+ joins << "JOIN #{ActsAsTaggableOn::Tagging.table_name}" \
112
+ " ON #{ActsAsTaggableOn::Tagging.table_name}.taggable_id = #{quote}#{table_name}#{quote}.#{primary_key}" \
113
+ " AND #{ActsAsTaggableOn::Tagging.table_name}.taggable_type = #{quote_value(base_class.name, nil)}" \
114
+ " AND #{ActsAsTaggableOn::Tagging.table_name}.tagger_id = #{quote_value(owned_by.id, nil)}" \
115
+ " AND #{ActsAsTaggableOn::Tagging.table_name}.tagger_type = #{quote_value(owned_by.class.base_class.to_s, nil)}"
117
116
  end
118
117
 
119
118
  elsif options.delete(:any)
120
119
  # get tags, drop out if nothing returned (we need at least one)
121
120
  tags = if options.delete(:wild)
122
- ActsAsTaggableOn::Tag.named_like_any(tag_list)
123
- else
124
- ActsAsTaggableOn::Tag.named_any(tag_list)
125
- end
121
+ ActsAsTaggableOn::Tag.named_like_any(tag_list)
122
+ else
123
+ ActsAsTaggableOn::Tag.named_any(tag_list)
124
+ end
126
125
 
127
126
  return empty_result unless tags.length > 0
128
127
 
@@ -130,29 +129,30 @@ module ActsAsTaggableOn::Taggable
130
129
  # avoid ambiguous column name
131
130
  taggings_context = context ? "_#{context}" : ''
132
131
 
133
- taggings_alias = adjust_taggings_alias(
134
- "#{alias_base_name[0..4]}#{taggings_context[0..6]}_taggings_#{sha_prefix(tags.map(&:name).join('_'))}"
132
+ taggings_alias = adjust_taggings_alias(
133
+ "#{alias_base_name[0..4]}#{taggings_context[0..6]}_taggings_#{sha_prefix(tags.map(&:name).join('_'))}"
135
134
  )
136
135
 
137
- tagging_join = "JOIN #{ActsAsTaggableOn::Tagging.table_name} #{taggings_alias}" +
138
- " ON #{taggings_alias}.taggable_id = #{quote}#{table_name}#{quote}.#{primary_key}" +
139
- " AND #{taggings_alias}.taggable_type = #{quote_value(base_class.name, nil)}"
140
- tagging_join << " AND " + sanitize_sql(["#{taggings_alias}.context = ?", context.to_s]) if context
136
+ tagging_join = "JOIN #{ActsAsTaggableOn::Tagging.table_name} #{taggings_alias}" \
137
+ " ON #{taggings_alias}.taggable_id = #{quote}#{table_name}#{quote}.#{primary_key}" \
138
+ " AND #{taggings_alias}.taggable_type = #{quote_value(base_class.name, nil)}"
139
+ tagging_join << ' AND ' + sanitize_sql(["#{taggings_alias}.context = ?", context.to_s]) if context
141
140
 
142
141
  # don't need to sanitize sql, map all ids and join with OR logic
143
- conditions << tags.map { |t| "#{taggings_alias}.tag_id = #{quote_value(t.id, nil)}" }.join(" OR ")
142
+ conditions << tags.map { |t| "#{taggings_alias}.tag_id = #{quote_value(t.id, nil)}" }.join(' OR ')
144
143
  select_clause = " #{table_name}.*" unless context and tag_types.one?
145
144
 
146
145
  if owned_by
147
- tagging_join << " AND " +
148
- sanitize_sql([
149
- "#{taggings_alias}.tagger_id = ? AND #{taggings_alias}.tagger_type = ?",
150
- owned_by.id,
151
- owned_by.class.base_class.to_s
152
- ])
146
+ tagging_join << ' AND ' +
147
+ sanitize_sql([
148
+ "#{taggings_alias}.tagger_id = ? AND #{taggings_alias}.tagger_type = ?",
149
+ owned_by.id,
150
+ owned_by.class.base_class.to_s
151
+ ])
153
152
  end
154
153
 
155
154
  joins << tagging_join
155
+ group = "#{table_name}.#{primary_key}"
156
156
  else
157
157
  tags = ActsAsTaggableOn::Tag.named_any(tag_list)
158
158
 
@@ -160,42 +160,42 @@ module ActsAsTaggableOn::Taggable
160
160
 
161
161
  tags.each do |tag|
162
162
  taggings_alias = adjust_taggings_alias("#{alias_base_name[0..11]}_taggings_#{sha_prefix(tag.name)}")
163
- tagging_join = "JOIN #{ActsAsTaggableOn::Tagging.table_name} #{taggings_alias}" +
164
- " ON #{taggings_alias}.taggable_id = #{quote}#{table_name}#{quote}.#{primary_key}" +
165
- " AND #{taggings_alias}.taggable_type = #{quote_value(base_class.name, nil)}" +
166
- " AND #{taggings_alias}.tag_id = #{quote_value(tag.id, nil)}"
163
+ tagging_join = "JOIN #{ActsAsTaggableOn::Tagging.table_name} #{taggings_alias}" \
164
+ " ON #{taggings_alias}.taggable_id = #{quote}#{table_name}#{quote}.#{primary_key}" +
165
+ " AND #{taggings_alias}.taggable_type = #{quote_value(base_class.name, nil)}" +
166
+ " AND #{taggings_alias}.tag_id = #{quote_value(tag.id, nil)}"
167
167
 
168
- tagging_join << " AND " + sanitize_sql(["#{taggings_alias}.context = ?", context.to_s]) if context
168
+ tagging_join << ' AND ' + sanitize_sql(["#{taggings_alias}.context = ?", context.to_s]) if context
169
169
 
170
170
  if owned_by
171
- tagging_join << " AND " +
171
+ tagging_join << ' AND ' +
172
172
  sanitize_sql([
173
173
  "#{taggings_alias}.tagger_id = ? AND #{taggings_alias}.tagger_type = ?",
174
174
  owned_by.id,
175
175
  owned_by.class.base_class.to_s
176
- ])
176
+ ])
177
177
  end
178
178
 
179
179
  joins << tagging_join
180
180
  end
181
181
  end
182
182
 
183
- group = [] # Rails interprets this as a no-op in the group() call below
183
+ group ||= [] # Rails interprets this as a no-op in the group() call below
184
184
  if options.delete(:order_by_matching_tag_count)
185
185
  select_clause = "#{table_name}.*, COUNT(#{taggings_alias}.tag_id) AS #{taggings_alias}_count"
186
- group_columns = ActsAsTaggableOn::Tag.using_postgresql? ? grouped_column_names_for(self) : "#{table_name}.#{primary_key}"
186
+ group_columns = ActsAsTaggableOn::Utils.using_postgresql? ? grouped_column_names_for(self) : "#{table_name}.#{primary_key}"
187
187
  group = group_columns
188
188
  order_by << "#{taggings_alias}_count DESC"
189
189
 
190
190
  elsif options.delete(:match_all)
191
191
  taggings_alias, _ = adjust_taggings_alias("#{alias_base_name}_taggings_group"), "#{alias_base_name}_tags_group"
192
- joins << "LEFT OUTER JOIN #{ActsAsTaggableOn::Tagging.table_name} #{taggings_alias}" +
193
- " ON #{taggings_alias}.taggable_id = #{quote}#{table_name}#{quote}.#{primary_key}" +
194
- " AND #{taggings_alias}.taggable_type = #{quote_value(base_class.name, nil)}"
192
+ joins << "LEFT OUTER JOIN #{ActsAsTaggableOn::Tagging.table_name} #{taggings_alias}" \
193
+ " ON #{taggings_alias}.taggable_id = #{quote}#{table_name}#{quote}.#{primary_key}" \
194
+ " AND #{taggings_alias}.taggable_type = #{quote_value(base_class.name, nil)}"
195
195
 
196
- joins << " AND " + sanitize_sql(["#{taggings_alias}.context = ?", context.to_s]) if context
196
+ joins << ' AND ' + sanitize_sql(["#{taggings_alias}.context = ?", context.to_s]) if context
197
197
 
198
- group_columns = ActsAsTaggableOn::Tag.using_postgresql? ? grouped_column_names_for(self) : "#{table_name}.#{primary_key}"
198
+ group_columns = ActsAsTaggableOn::Utils.using_postgresql? ? grouped_column_names_for(self) : "#{table_name}.#{primary_key}"
199
199
  group = group_columns
200
200
  having = "COUNT(#{taggings_alias}.taggable_id) = #{tags.size}"
201
201
  end
@@ -203,14 +203,12 @@ module ActsAsTaggableOn::Taggable
203
203
  order_by << options[:order] if options[:order].present?
204
204
 
205
205
  request = select(select_clause).
206
- joins(joins.join(" ")).
207
- where(conditions.join(" AND ")).
208
- group(group).
209
- having(having).
210
- order(order_by.join(", ")).
211
- readonly(false)
212
-
213
- ((context and tag_types.one?) && options.delete(:any)) ? request : request.uniq
206
+ joins(joins.join(' ')).
207
+ where(conditions.join(' AND ')).
208
+ group(group).
209
+ having(having).
210
+ order(order_by.join(', ')).
211
+ readonly(false)
214
212
  end
215
213
 
216
214
  def is_taggable?
@@ -252,7 +250,7 @@ module ActsAsTaggableOn::Taggable
252
250
 
253
251
  def tag_list_cache_set_on(context)
254
252
  variable_name = "@#{context.to_s.singularize}_list"
255
- instance_variable_defined?(variable_name) && !instance_variable_get(variable_name).nil?
253
+ instance_variable_defined?(variable_name) && instance_variable_get(variable_name)
256
254
  end
257
255
 
258
256
  def tag_list_cache_on(context)
@@ -283,10 +281,10 @@ module ActsAsTaggableOn::Taggable
283
281
  def all_tags_on(context)
284
282
  tagging_table_name = ActsAsTaggableOn::Tagging.table_name
285
283
 
286
- opts = ["#{tagging_table_name}.context = ?", context.to_s]
284
+ opts = ["#{tagging_table_name}.context = ?", context.to_s]
287
285
  scope = base_tags.where(opts)
288
286
 
289
- if ActsAsTaggableOn::Tag.using_postgresql?
287
+ if ActsAsTaggableOn::Utils.using_postgresql?
290
288
  group_columns = grouped_column_names_for(ActsAsTaggableOn::Tag)
291
289
  scope.order("max(#{tagging_table_name}.created_at)").group(group_columns)
292
290
  else
@@ -317,17 +315,17 @@ module ActsAsTaggableOn::Taggable
317
315
  custom_contexts + self.class.tag_types.map(&:to_s)
318
316
  end
319
317
 
320
- def process_dirty_object(context,new_list)
318
+ def process_dirty_object(context, new_list)
321
319
  value = new_list.is_a?(Array) ? ActsAsTaggableOn::TagList.new(new_list) : new_list
322
320
  attrib = "#{context.to_s.singularize}_list"
323
321
 
324
322
  if changed_attributes.include?(attrib)
325
323
  # The attribute already has an unsaved change.
326
324
  old = changed_attributes[attrib]
327
- changed_attributes.delete(attrib) if (old.to_s == value.to_s)
325
+ changed_attributes.delete(attrib) if old.to_s == value.to_s
328
326
  else
329
327
  old = tag_list_on(context).to_s
330
- changed_attributes[attrib] = old if (old.to_s != value.to_s)
328
+ changed_attributes[attrib] = old if old.to_s != value.to_s
331
329
  end
332
330
  end
333
331
 
@@ -353,7 +351,7 @@ module ActsAsTaggableOn::Taggable
353
351
  tag_list = tag_list_cache_on(context).uniq
354
352
 
355
353
  # Find existing tags or create non-existing tags:
356
- tags = load_tags(tag_list)
354
+ tags = find_or_create_tags_from_list_with_context(tag_list, context)
357
355
 
358
356
  # Tag objects for currently assigned tags
359
357
  current_tags = tags_on(context)
@@ -382,23 +380,43 @@ module ActsAsTaggableOn::Taggable
382
380
  new_tags = tags - current_tags
383
381
  end
384
382
 
385
- # Find taggings to remove:
386
- if old_tags.present?
387
- old_taggings = taggings.where(:tagger_type => nil, :tagger_id => nil, :context => context.to_s, :tag_id => old_tags)
388
- end
389
-
390
383
  # Destroy old taggings:
391
- if old_taggings.present?
392
- ActsAsTaggableOn::Tagging.destroy_all "#{ActsAsTaggableOn::Tagging.primary_key}".to_sym => old_taggings.map(&:id)
384
+ if old_tags.present?
385
+ ActsAsTaggableOn::Tagging.destroy_all(tagger_type: nil, tagger_id: nil, context: context.to_s, tag_id: old_tags)
393
386
  end
394
387
 
395
388
  # Create new taggings:
396
389
  new_tags.each do |tag|
397
- taggings.create!(:tag_id => tag.id, :context => context.to_s, :taggable => self)
390
+ taggings.create!(tag_id: tag.id, context: context.to_s, taggable: self)
398
391
  end
399
392
  end
400
393
 
401
394
  true
402
395
  end
396
+
397
+ private
398
+
399
+ ##
400
+ # Override this hook if you wish to subclass {ActsAsTaggableOn::Tag} --
401
+ # context is provided so that you may conditionally use a Tag subclass
402
+ # only for some contexts.
403
+ #
404
+ # @example Custom Tag class for one context
405
+ # class Company < ActiveRecord::Base
406
+ # acts_as_taggable_on :markets, :locations
407
+ #
408
+ # def find_or_create_tags_from_list_with_context(tag_list, context)
409
+ # if context.to_sym == :markets
410
+ # MarketTag.find_or_create_all_with_like_by_name(tag_list)
411
+ # else
412
+ # super
413
+ # end
414
+ # end
415
+ #
416
+ # @param [Array<String>] tag_list Tags to find or create
417
+ # @param [Symbol] context The tag context for the tag_list
418
+ def find_or_create_tags_from_list_with_context(tag_list, _context)
419
+ load_tags(tag_list)
420
+ end
403
421
  end
404
422
  end