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.
- checksums.yaml +4 -4
- data/.gitignore +1 -1
- data/.travis.yml +9 -7
- data/Appraisals +13 -8
- data/CHANGELOG.md +8 -0
- data/Gemfile +1 -2
- data/README.md +23 -13
- data/Rakefile +5 -17
- data/UPGRADING.md +6 -0
- data/acts-as-taggable-on.gemspec +13 -13
- data/db/migrate/1_acts_as_taggable_on_migration.rb +3 -3
- data/db/migrate/2_add_missing_unique_indices.rb +3 -5
- data/db/migrate/3_add_taggings_counter_cache_to_tags.rb +1 -1
- data/gemfiles/activerecord_3.2.gemfile +15 -0
- data/gemfiles/activerecord_4.0.gemfile +15 -0
- data/gemfiles/activerecord_4.1.gemfile +15 -0
- data/gemfiles/activerecord_edge.gemfile +16 -0
- data/lib/acts-as-taggable-on.rb +23 -21
- data/lib/acts_as_taggable_on/acts_as_taggable_on/cache.rb +1 -4
- data/lib/acts_as_taggable_on/acts_as_taggable_on/collection.rb +29 -20
- data/lib/acts_as_taggable_on/acts_as_taggable_on/compatibility.rb +11 -10
- data/lib/acts_as_taggable_on/acts_as_taggable_on/core.rb +98 -80
- data/lib/acts_as_taggable_on/acts_as_taggable_on/ownership.rb +5 -12
- data/lib/acts_as_taggable_on/acts_as_taggable_on/related.rb +7 -7
- data/lib/acts_as_taggable_on/engine.rb +0 -1
- data/lib/acts_as_taggable_on/tag.rb +24 -19
- data/lib/acts_as_taggable_on/tag_list.rb +95 -21
- data/lib/acts_as_taggable_on/taggable.rb +28 -30
- data/lib/acts_as_taggable_on/tagger.rb +30 -18
- data/lib/acts_as_taggable_on/tagging.rb +7 -8
- data/lib/acts_as_taggable_on/tags_helper.rb +1 -1
- data/lib/acts_as_taggable_on/utils.rb +25 -3
- data/lib/acts_as_taggable_on/version.rb +1 -1
- data/spec/acts_as_taggable_on/acts_as_taggable_on_spec.rb +133 -138
- data/spec/acts_as_taggable_on/acts_as_tagger_spec.rb +55 -58
- data/spec/acts_as_taggable_on/caching_spec.rb +34 -35
- data/spec/acts_as_taggable_on/related_spec.rb +59 -113
- data/spec/acts_as_taggable_on/single_table_inheritance_spec.rb +118 -95
- data/spec/acts_as_taggable_on/tag_list_spec.rb +89 -57
- data/spec/acts_as_taggable_on/tag_spec.rb +125 -114
- data/spec/acts_as_taggable_on/taggable_spec.rb +538 -352
- data/spec/acts_as_taggable_on/tagger_spec.rb +81 -78
- data/spec/acts_as_taggable_on/tagging_spec.rb +13 -14
- data/spec/acts_as_taggable_on/tags_helper_spec.rb +25 -25
- data/spec/acts_as_taggable_on/utils_spec.rb +9 -9
- data/spec/internal/app/models/altered_inheriting_taggable_model.rb +3 -0
- data/spec/internal/app/models/cached_model.rb +3 -0
- data/spec/internal/app/models/cached_model_with_array.rb +5 -0
- data/spec/internal/app/models/company.rb +15 -0
- data/spec/internal/app/models/inheriting_taggable_model.rb +2 -0
- data/spec/internal/app/models/market.rb +2 -0
- data/spec/{models.rb → internal/app/models/models.rb} +34 -2
- data/spec/internal/app/models/non_standard_id_taggable_model.rb +8 -0
- data/spec/internal/app/models/ordered_taggable_model.rb +4 -0
- data/spec/internal/app/models/other_cached_model.rb +3 -0
- data/spec/internal/app/models/other_taggable_model.rb +4 -0
- data/spec/internal/app/models/student.rb +2 -0
- data/spec/internal/app/models/taggable_model.rb +13 -0
- data/spec/internal/app/models/untaggable_model.rb +3 -0
- data/spec/internal/app/models/user.rb +3 -0
- data/spec/{database.yml.sample → internal/config/database.yml.sample} +2 -2
- data/spec/internal/db/schema.rb +97 -0
- data/spec/schema.rb +11 -0
- data/spec/spec_helper.rb +9 -62
- data/spec/support/array.rb +9 -0
- data/spec/support/database.rb +42 -0
- data/spec/support/database_cleaner.rb +17 -0
- metadata +101 -37
- data/gemfiles/rails_3.2.gemfile +0 -7
- data/gemfiles/rails_4.0.gemfile +0 -7
- data/gemfiles/rails_4.1.gemfile +0 -7
- 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}', :
|
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}', :
|
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({:
|
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({:
|
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
|
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
|
-
|
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
|
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
|
-
|
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)])
|
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
|
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]])
|
152
|
+
taggable_conditions << sanitize_sql([" AND #{ActsAsTaggableOn::Tagging.table_name}.taggable_id = ?", options[:id]]) if options[:id]
|
144
153
|
|
145
|
-
tagging_conditions.push
|
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(:
|
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
|
172
|
+
super
|
164
173
|
end
|
165
174
|
end
|
166
175
|
end
|
@@ -1,26 +1,27 @@
|
|
1
1
|
module ActsAsTaggableOn::Compatibility
|
2
|
-
def
|
3
|
-
if
|
4
|
-
scope, opts =
|
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
|
12
|
-
scope_opts, 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 =
|
16
|
-
scope_opts.inject(self) { |result, hash| result.send
|
17
|
-
|
15
|
+
scope = -> {
|
16
|
+
scope_opts.inject(self) { |result, hash| result.send(*hash) }
|
17
|
+
}
|
18
|
+
return [scope, opts]
|
18
19
|
end
|
19
20
|
|
20
|
-
[
|
21
|
+
[nil, opts]
|
21
22
|
end
|
22
23
|
|
23
|
-
def
|
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
|
18
|
+
tag_type = tags_type.to_s.singularize
|
20
19
|
context_taggings = "#{tag_type}_taggings".to_sym
|
21
|
-
context_tags
|
22
|
-
taggings_order
|
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
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
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(
|
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::
|
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(
|
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(
|
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 <<
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
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
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
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
|
134
|
-
|
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
|
138
|
-
|
139
|
-
|
140
|
-
tagging_join <<
|
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(
|
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
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
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
|
164
|
-
|
165
|
-
|
166
|
-
|
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 <<
|
168
|
+
tagging_join << ' AND ' + sanitize_sql(["#{taggings_alias}.context = ?", context.to_s]) if context
|
169
169
|
|
170
170
|
if owned_by
|
171
|
-
|
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
|
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::
|
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
|
-
|
194
|
-
|
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 <<
|
196
|
+
joins << ' AND ' + sanitize_sql(["#{taggings_alias}.context = ?", context.to_s]) if context
|
197
197
|
|
198
|
-
group_columns = ActsAsTaggableOn::
|
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
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
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) &&
|
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
|
284
|
+
opts = ["#{tagging_table_name}.context = ?", context.to_s]
|
287
285
|
scope = base_tags.where(opts)
|
288
286
|
|
289
|
-
if ActsAsTaggableOn::
|
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
|
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
|
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 =
|
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
|
392
|
-
ActsAsTaggableOn::Tagging.destroy_all
|
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!(:
|
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
|