acts-as-taggable-on 3.1.1 → 3.2.1

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