acts-as-taggable-on 2.2.2 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +3 -1
- data/README.rdoc +54 -15
- data/acts-as-taggable-on.gemspec +1 -1
- data/lib/acts-as-taggable-on.rb +26 -1
- data/lib/acts-as-taggable-on/version.rb +1 -1
- data/lib/acts_as_taggable_on/acts_as_taggable_on/core.rb +80 -23
- data/lib/acts_as_taggable_on/acts_as_taggable_on/dirty.rb +37 -0
- data/lib/acts_as_taggable_on/acts_as_taggable_on/ownership.rb +33 -12
- data/lib/acts_as_taggable_on/tag.rb +5 -11
- data/lib/acts_as_taggable_on/tag_list.rb +13 -12
- data/lib/acts_as_taggable_on/taggable.rb +64 -16
- data/lib/acts_as_taggable_on/tagger.rb +3 -3
- data/lib/acts_as_taggable_on/tagging.rb +1 -1
- data/lib/acts_as_taggable_on/utils.rb +1 -1
- data/spec/acts_as_taggable_on/acts_as_taggable_on_spec.rb +58 -0
- data/spec/acts_as_taggable_on/tag_list_spec.rb +84 -65
- data/spec/acts_as_taggable_on/tag_spec.rb +29 -30
- data/spec/acts_as_taggable_on/taggable_spec.rb +137 -6
- data/spec/acts_as_taggable_on/tagger_spec.rb +23 -16
- data/spec/models.rb +5 -0
- data/spec/schema.rb +5 -0
- data/spec/spec_helper.rb +3 -1
- metadata +21 -20
data/.gitignore
CHANGED
data/README.rdoc
CHANGED
@@ -16,17 +16,15 @@ was used.
|
|
16
16
|
|
17
17
|
== Installation
|
18
18
|
|
19
|
-
=== Rails 2.
|
19
|
+
=== Rails 2.x
|
20
20
|
|
21
|
-
|
22
|
-
|
23
|
-
gem 'acts-as-taggable-on', '~>2.1.0'
|
21
|
+
Not supported any more! It is time for update guys.
|
24
22
|
|
25
23
|
=== Rails 3.x
|
26
24
|
|
27
25
|
To use it, add it to your Gemfile:
|
28
26
|
|
29
|
-
gem 'acts-as-taggable-on', '~>2.2.
|
27
|
+
gem 'acts-as-taggable-on', '~> 2.2.2'
|
30
28
|
|
31
29
|
==== Post Installation
|
32
30
|
|
@@ -61,6 +59,25 @@ directory, you can run the specs for RoR 3.x with:
|
|
61
59
|
User.skill_counts # => [<Tag name="joking" count=2>,<Tag name="clowning" count=1>...]
|
62
60
|
@frankie.skill_counts
|
63
61
|
|
62
|
+
To preserve the order in which tags are created use acts_as_ordered_taggable:
|
63
|
+
|
64
|
+
class User < ActiveRecord::Base
|
65
|
+
# Alias for <tt>acts_as_ordered_taggable_on :tags</tt>:
|
66
|
+
acts_as_ordered_taggable
|
67
|
+
acts_as_ordered_taggable_on :skills, :interests
|
68
|
+
end
|
69
|
+
|
70
|
+
@user = User.new(:name => "Bobby")
|
71
|
+
@user.tag_list = "east, south"
|
72
|
+
@user.save
|
73
|
+
|
74
|
+
@user.tag_list = "north, east, south, west"
|
75
|
+
@user.save
|
76
|
+
|
77
|
+
@user.reload
|
78
|
+
@user.tag_list # => ["north", "east", "south", "west"]
|
79
|
+
|
80
|
+
|
64
81
|
=== Finding Tagged Objects
|
65
82
|
|
66
83
|
Acts As Taggable On utilizes named_scopes to create an association for tags.
|
@@ -76,19 +93,19 @@ compatibility with the will_paginate gem:
|
|
76
93
|
User.tagged_with("awesome").by_date.paginate(:page => params[:page], :per_page => 20)
|
77
94
|
|
78
95
|
# Find a user with matching all tags, not just one
|
79
|
-
User.tagged_with(["awesome", "cool"], :match_all =>
|
96
|
+
User.tagged_with(["awesome", "cool"], :match_all => true)
|
80
97
|
|
81
98
|
# Find a user with any of the tags:
|
82
99
|
User.tagged_with(["awesome", "cool"], :any => true)
|
83
100
|
|
84
101
|
# Find a user that not tags with awesome or cool:
|
85
102
|
User.tagged_with(["awesome", "cool"], :exclude => true)
|
86
|
-
|
103
|
+
|
87
104
|
# Find a user with any of tags based on context:
|
88
105
|
User.tagged_with(['awesome, cool'], :on => :tags, :any => true).tagged_with(['smart', 'shy'], :on => :skills, :any => true)
|
89
106
|
|
90
107
|
You can also use :wild => true option along with :any or :exclude option. It will looking for %awesome% and %cool% in sql.
|
91
|
-
|
108
|
+
|
92
109
|
Tip: User.tagged_with([]) or '' will return [], but not all records.
|
93
110
|
|
94
111
|
=== Relationships
|
@@ -141,6 +158,21 @@ Tags can have owners:
|
|
141
158
|
@some_photo.locations_from(@some_user) # => ["paris", "normandy"]
|
142
159
|
@some_photo.owner_tags_on(@some_user, :locations) # => [#<ActsAsTaggableOn::Tag id: 1, name: "paris">...]
|
143
160
|
@some_photo.owner_tags_on(nil, :locations) # => Ownerships equivalent to saying @some_photo.locations
|
161
|
+
@some_user.tag(@some_photo, :with => "paris, normandy", :on => :locations, :skip_save => true) #won't save @some_photo object
|
162
|
+
|
163
|
+
=== Dirty objects
|
164
|
+
|
165
|
+
@bobby = User.find_by_name("Bobby")
|
166
|
+
@bobby.skill_list # => ["jogging", "diving"]
|
167
|
+
|
168
|
+
@boddy.skill_list_changed? #=> false
|
169
|
+
@boddy.changes #=> {}
|
170
|
+
|
171
|
+
@bobby.skill_list = "swimming"
|
172
|
+
@bobby.changes.should == {"skill_list"=>["jogging, diving", ["swimming"]]}
|
173
|
+
@boddy.skill_list_changed? #=> true
|
174
|
+
|
175
|
+
@bobby.skill_list_change.should == ["jogging, diving", ["swimming"]]
|
144
176
|
|
145
177
|
=== Tag cloud calculations
|
146
178
|
|
@@ -182,14 +214,21 @@ CSS:
|
|
182
214
|
.css3 { font-size: 1.4em; }
|
183
215
|
.css4 { font-size: 1.6em; }
|
184
216
|
|
185
|
-
==
|
217
|
+
== Configuration
|
218
|
+
|
219
|
+
If you would like to remove unused tag objects after removing taggings, add
|
220
|
+
|
221
|
+
ActsAsTaggableOn.remove_unused_tags = true
|
222
|
+
|
223
|
+
If you want force tags to be saved downcased:
|
224
|
+
|
225
|
+
ActsAsTaggableOn.force_lowercase = true
|
226
|
+
|
227
|
+
If you want tags to be saved parametrized (you can redefine to_param as well):
|
228
|
+
|
229
|
+
ActsAsTaggableOn.force_parameterize = true
|
230
|
+
|
186
231
|
|
187
|
-
If you would like to remove unused tag objects after removing taggings, add
|
188
|
-
|
189
|
-
ActsAsTaggableOn::Tag.remove_unused = true
|
190
|
-
|
191
|
-
to initializer file.
|
192
|
-
|
193
232
|
== Contributors
|
194
233
|
|
195
234
|
We have a long list of valued contributors. {Check them all}[https://github.com/mbleigh/acts-as-taggable-on/contributors]
|
data/acts-as-taggable-on.gemspec
CHANGED
@@ -11,7 +11,7 @@ Gem::Specification.new do |gem|
|
|
11
11
|
gem.homepage = ''
|
12
12
|
|
13
13
|
gem.add_runtime_dependency 'rails', '~> 3.0'
|
14
|
-
gem.add_development_dependency 'rspec', '~> 2.
|
14
|
+
gem.add_development_dependency 'rspec', '~> 2.6'
|
15
15
|
gem.add_development_dependency 'ammeter', '~> 0.1.3'
|
16
16
|
gem.add_development_dependency 'sqlite3'
|
17
17
|
gem.add_development_dependency 'mysql2', '~> 0.3.7'
|
data/lib/acts-as-taggable-on.rb
CHANGED
@@ -6,6 +6,29 @@ require "digest/sha1"
|
|
6
6
|
|
7
7
|
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
8
8
|
|
9
|
+
module ActsAsTaggableOn
|
10
|
+
mattr_accessor :delimiter
|
11
|
+
@@delimiter = ','
|
12
|
+
|
13
|
+
mattr_accessor :force_lowercase
|
14
|
+
@@force_lowercase = false
|
15
|
+
|
16
|
+
mattr_accessor :force_parameterize
|
17
|
+
@@force_parameterize = false
|
18
|
+
|
19
|
+
mattr_accessor :remove_unused_tags
|
20
|
+
self.remove_unused_tags = false
|
21
|
+
|
22
|
+
def self.glue
|
23
|
+
@@delimiter.ends_with?(" ") ? @@delimiter : "#{@@delimiter} "
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.setup
|
27
|
+
yield self
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
|
9
32
|
require "acts_as_taggable_on/utils"
|
10
33
|
|
11
34
|
require "acts_as_taggable_on/taggable"
|
@@ -14,6 +37,7 @@ require "acts_as_taggable_on/acts_as_taggable_on/collection"
|
|
14
37
|
require "acts_as_taggable_on/acts_as_taggable_on/cache"
|
15
38
|
require "acts_as_taggable_on/acts_as_taggable_on/ownership"
|
16
39
|
require "acts_as_taggable_on/acts_as_taggable_on/related"
|
40
|
+
require "acts_as_taggable_on/acts_as_taggable_on/dirty"
|
17
41
|
|
18
42
|
require "acts_as_taggable_on/tagger"
|
19
43
|
require "acts_as_taggable_on/tag"
|
@@ -31,4 +55,5 @@ end
|
|
31
55
|
|
32
56
|
if defined?(ActionView::Base)
|
33
57
|
ActionView::Base.send :include, ActsAsTaggableOn::TagsHelper
|
34
|
-
end
|
58
|
+
end
|
59
|
+
|
@@ -18,11 +18,22 @@ module ActsAsTaggableOn::Taggable
|
|
18
18
|
tag_type = tags_type.to_s.singularize
|
19
19
|
context_taggings = "#{tag_type}_taggings".to_sym
|
20
20
|
context_tags = tags_type.to_sym
|
21
|
-
|
21
|
+
taggings_order = (preserve_tag_order? ? "#{ActsAsTaggableOn::Tagging.table_name}.id" : nil)
|
22
|
+
|
22
23
|
class_eval do
|
23
|
-
|
24
|
-
|
25
|
-
|
24
|
+
# when preserving tag order, include order option so that for a 'tags' context
|
25
|
+
# the associations tag_taggings & tags are always returned in created order
|
26
|
+
has_many context_taggings, :as => :taggable,
|
27
|
+
:dependent => :destroy,
|
28
|
+
:include => :tag,
|
29
|
+
:class_name => "ActsAsTaggableOn::Tagging",
|
30
|
+
:conditions => ["#{ActsAsTaggableOn::Tagging.table_name}.context = ?", tags_type],
|
31
|
+
:order => taggings_order
|
32
|
+
|
33
|
+
has_many context_tags, :through => context_taggings,
|
34
|
+
:source => :tag,
|
35
|
+
:class_name => "ActsAsTaggableOn::Tag",
|
36
|
+
:order => taggings_order
|
26
37
|
end
|
27
38
|
|
28
39
|
class_eval %(
|
@@ -41,11 +52,11 @@ module ActsAsTaggableOn::Taggable
|
|
41
52
|
end
|
42
53
|
end
|
43
54
|
|
44
|
-
def
|
45
|
-
super(*
|
55
|
+
def taggable_on(preserve_tag_order, *tag_types)
|
56
|
+
super(preserve_tag_order, *tag_types)
|
46
57
|
initialize_acts_as_taggable_on_core
|
47
58
|
end
|
48
|
-
|
59
|
+
|
49
60
|
# all column names are necessary for PostgreSQL group clause
|
50
61
|
def grouped_column_names_for(object)
|
51
62
|
object.column_names.map { |column| "#{object.table_name}.#{column}" }.join(", ")
|
@@ -85,8 +96,8 @@ module ActsAsTaggableOn::Taggable
|
|
85
96
|
tags_conditions = tag_list.map { |t| sanitize_sql(["#{ActsAsTaggableOn::Tag.table_name}.name #{like_operator} ? ESCAPE '!'", "%#{escape_like(t)}%"]) }.join(" OR ")
|
86
97
|
else
|
87
98
|
tags_conditions = tag_list.map { |t| sanitize_sql(["#{ActsAsTaggableOn::Tag.table_name}.name #{like_operator} ?", t]) }.join(" OR ")
|
88
|
-
end
|
89
|
-
|
99
|
+
end
|
100
|
+
|
90
101
|
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)})"
|
91
102
|
|
92
103
|
elsif options.delete(:any)
|
@@ -94,16 +105,18 @@ module ActsAsTaggableOn::Taggable
|
|
94
105
|
if options.delete(:wild)
|
95
106
|
tags = ActsAsTaggableOn::Tag.named_like_any(tag_list)
|
96
107
|
else
|
97
|
-
tags = ActsAsTaggableOn::Tag.named_any(tag_list)
|
108
|
+
tags = ActsAsTaggableOn::Tag.named_any(tag_list)
|
98
109
|
end
|
99
|
-
|
110
|
+
|
100
111
|
return scoped(:conditions => "1 = 0") unless tags.length > 0
|
101
112
|
|
102
113
|
# setup taggings alias so we can chain, ex: items_locations_taggings_awesome_cool_123
|
103
114
|
# avoid ambiguous column name
|
104
115
|
taggings_context = context ? "_#{context}" : ''
|
105
116
|
|
106
|
-
taggings_alias =
|
117
|
+
taggings_alias = adjust_taggings_alias(
|
118
|
+
"#{alias_base_name[0..4]}#{taggings_context[0..6]}_taggings_#{sha_prefix(tags.map(&:name).join('_'))}"
|
119
|
+
)
|
107
120
|
|
108
121
|
tagging_join = "JOIN #{ActsAsTaggableOn::Tagging.table_name} #{taggings_alias}" +
|
109
122
|
" ON #{taggings_alias}.taggable_id = #{table_name}.#{primary_key}" +
|
@@ -117,12 +130,12 @@ module ActsAsTaggableOn::Taggable
|
|
117
130
|
joins << tagging_join
|
118
131
|
|
119
132
|
else
|
120
|
-
tags = ActsAsTaggableOn::Tag.named_any(tag_list)
|
121
|
-
return empty_result unless tags.length == tag_list.length
|
133
|
+
tags = ActsAsTaggableOn::Tag.named_any(tag_list)
|
134
|
+
return empty_result unless tags.length == tag_list.length
|
122
135
|
|
123
136
|
tags.each do |tag|
|
124
137
|
|
125
|
-
taggings_alias = "#{alias_base_name[0..11]}_taggings_#{sha_prefix(tag.
|
138
|
+
taggings_alias = adjust_taggings_alias("#{alias_base_name[0..11]}_taggings_#{sha_prefix(tag.name)}")
|
126
139
|
|
127
140
|
tagging_join = "JOIN #{ActsAsTaggableOn::Tagging.table_name} #{taggings_alias}" +
|
128
141
|
" ON #{taggings_alias}.taggable_id = #{table_name}.#{primary_key}" +
|
@@ -143,7 +156,7 @@ module ActsAsTaggableOn::Taggable
|
|
143
156
|
end
|
144
157
|
end
|
145
158
|
|
146
|
-
taggings_alias, tags_alias = "#{alias_base_name}_taggings_group", "#{alias_base_name}_tags_group"
|
159
|
+
taggings_alias, tags_alias = adjust_taggings_alias("#{alias_base_name}_taggings_group"), "#{alias_base_name}_tags_group"
|
147
160
|
|
148
161
|
if options.delete(:match_all)
|
149
162
|
joins << "LEFT OUTER JOIN #{ActsAsTaggableOn::Tagging.table_name} #{taggings_alias}" +
|
@@ -166,6 +179,13 @@ module ActsAsTaggableOn::Taggable
|
|
166
179
|
def is_taggable?
|
167
180
|
true
|
168
181
|
end
|
182
|
+
|
183
|
+
def adjust_taggings_alias(taggings_alias)
|
184
|
+
if taggings_alias.size > 75
|
185
|
+
taggings_alias = 'taggings_alias_' + Digest::SHA1.hexdigest(taggings_alias)
|
186
|
+
end
|
187
|
+
taggings_alias
|
188
|
+
end
|
169
189
|
end
|
170
190
|
|
171
191
|
module InstanceMethods
|
@@ -234,13 +254,19 @@ module ActsAsTaggableOn::Taggable
|
|
234
254
|
##
|
235
255
|
# Returns all tags that are not owned of a given context
|
236
256
|
def tags_on(context)
|
237
|
-
base_tags.where(["#{ActsAsTaggableOn::Tagging.table_name}.context = ? AND #{ActsAsTaggableOn::Tagging.table_name}.tagger_id IS NULL", context.to_s])
|
257
|
+
scope = base_tags.where(["#{ActsAsTaggableOn::Tagging.table_name}.context = ? AND #{ActsAsTaggableOn::Tagging.table_name}.tagger_id IS NULL", context.to_s])
|
258
|
+
# when preserving tag order, return tags in created order
|
259
|
+
# if we added the order to the association this would always apply
|
260
|
+
scope = scope.order("#{ActsAsTaggableOn::Tagging.table_name}.id") if self.class.preserve_tag_order?
|
261
|
+
scope.all
|
238
262
|
end
|
239
263
|
|
240
264
|
def set_tag_list_on(context, new_list)
|
241
265
|
add_custom_context(context)
|
242
266
|
|
243
267
|
variable_name = "@#{context.to_s.singularize}_list"
|
268
|
+
process_dirty_object(context, new_list) unless custom_contexts.include?(context.to_s)
|
269
|
+
|
244
270
|
instance_variable_set(variable_name, ActsAsTaggableOn::TagList.from(new_list))
|
245
271
|
end
|
246
272
|
|
@@ -248,6 +274,20 @@ module ActsAsTaggableOn::Taggable
|
|
248
274
|
custom_contexts + self.class.tag_types.map(&:to_s)
|
249
275
|
end
|
250
276
|
|
277
|
+
def process_dirty_object(context,new_list)
|
278
|
+
value = new_list.is_a?(Array) ? new_list.join(', ') : new_list
|
279
|
+
attrib = "#{context.to_s.singularize}_list"
|
280
|
+
|
281
|
+
if changed_attributes.include?(attrib)
|
282
|
+
# The attribute already has an unsaved change.
|
283
|
+
old = changed_attributes[attrib]
|
284
|
+
changed_attributes.delete(attrib) if (old.to_s == value.to_s)
|
285
|
+
else
|
286
|
+
old = tag_list_on(context).to_s
|
287
|
+
changed_attributes[attrib] = old if (old.to_s != value.to_s)
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
251
291
|
def reload(*args)
|
252
292
|
self.class.tag_types.each do |context|
|
253
293
|
instance_variable_set("@#{context.to_s.singularize}_list", nil)
|
@@ -261,21 +301,38 @@ module ActsAsTaggableOn::Taggable
|
|
261
301
|
tagging_contexts.each do |context|
|
262
302
|
next unless tag_list_cache_set_on(context)
|
263
303
|
|
304
|
+
# List of currently assigned tag names
|
264
305
|
tag_list = tag_list_cache_on(context).uniq
|
265
306
|
|
266
307
|
# Find existing tags or create non-existing tags:
|
267
|
-
|
308
|
+
tags = ActsAsTaggableOn::Tag.find_or_create_all_with_like_by_name(tag_list)
|
268
309
|
|
310
|
+
# Tag objects for currently assigned tags
|
269
311
|
current_tags = tags_on(context)
|
270
|
-
|
271
|
-
|
312
|
+
|
313
|
+
# Tag maintenance based on whether preserving the created order of tags
|
314
|
+
if self.class.preserve_tag_order?
|
315
|
+
# First off order the array of tag objects to match the tag list
|
316
|
+
# rather than existing tags followed by new tags
|
317
|
+
tags = tag_list.map{|l| tags.detect{|t| t.name.downcase == l.downcase}}
|
318
|
+
# To preserve tags in the order in which they were added
|
319
|
+
# delete all current tags and create new tags if the content or order has changed
|
320
|
+
old_tags = (tags == current_tags ? [] : current_tags)
|
321
|
+
new_tags = (tags == current_tags ? [] : tags)
|
322
|
+
else
|
323
|
+
# Delete discarded tags and create new tags
|
324
|
+
old_tags = current_tags - tags
|
325
|
+
new_tags = tags - current_tags
|
326
|
+
end
|
272
327
|
|
273
328
|
# Find taggings to remove:
|
274
|
-
|
275
|
-
|
329
|
+
if old_tags.present?
|
330
|
+
old_taggings = taggings.where(:tagger_type => nil, :tagger_id => nil,
|
331
|
+
:context => context.to_s, :tag_id => old_tags).all
|
332
|
+
end
|
276
333
|
|
334
|
+
# Destroy old taggings:
|
277
335
|
if old_taggings.present?
|
278
|
-
# Destroy old taggings:
|
279
336
|
ActsAsTaggableOn::Tagging.destroy_all "#{ActsAsTaggableOn::Tagging.primary_key}".to_sym => old_taggings.map(&:id)
|
280
337
|
end
|
281
338
|
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module ActsAsTaggableOn::Taggable
|
2
|
+
module Dirty
|
3
|
+
def self.included(base)
|
4
|
+
base.extend ActsAsTaggableOn::Taggable::Dirty::ClassMethods
|
5
|
+
|
6
|
+
base.initialize_acts_as_taggable_on_dirty
|
7
|
+
end
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
def initialize_acts_as_taggable_on_dirty
|
11
|
+
tag_types.map(&:to_s).each do |tags_type|
|
12
|
+
tag_type = tags_type.to_s.singularize
|
13
|
+
context_tags = tags_type.to_sym
|
14
|
+
|
15
|
+
class_eval %(
|
16
|
+
def #{tag_type}_list_changed?
|
17
|
+
changed_attributes.include?("#{tag_type}_list")
|
18
|
+
end
|
19
|
+
|
20
|
+
def #{tag_type}_list_was
|
21
|
+
changed_attributes.include?("#{tag_type}_list") ? changed_attributes["#{tag_type}_list"] : __send__("#{tag_type}_list")
|
22
|
+
end
|
23
|
+
|
24
|
+
def #{tag_type}_list_change
|
25
|
+
[changed_attributes['#{tag_type}_list'], __send__('#{tag_type}_list')] if changed_attributes.include?("#{tag_type}_list")
|
26
|
+
end
|
27
|
+
|
28
|
+
def #{tag_type}_list_changes
|
29
|
+
[changed_attributes['#{tag_type}_list'], __send__('#{tag_type}_list')] if changed_attributes.include?("#{tag_type}_list")
|
30
|
+
end
|
31
|
+
)
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -31,12 +31,16 @@ module ActsAsTaggableOn::Taggable
|
|
31
31
|
module InstanceMethods
|
32
32
|
def owner_tags_on(owner, context)
|
33
33
|
if owner.nil?
|
34
|
-
base_tags.where([%(#{ActsAsTaggableOn::Tagging.table_name}.context = ?), context.to_s])
|
34
|
+
scope = base_tags.where([%(#{ActsAsTaggableOn::Tagging.table_name}.context = ?), context.to_s])
|
35
35
|
else
|
36
|
-
base_tags.where([%(#{ActsAsTaggableOn::Tagging.table_name}.context = ? AND
|
37
|
-
|
38
|
-
|
36
|
+
scope = base_tags.where([%(#{ActsAsTaggableOn::Tagging.table_name}.context = ? AND
|
37
|
+
#{ActsAsTaggableOn::Tagging.table_name}.tagger_id = ? AND
|
38
|
+
#{ActsAsTaggableOn::Tagging.table_name}.tagger_type = ?), context.to_s, owner.id, owner.class.to_s])
|
39
39
|
end
|
40
|
+
# when preserving tag order, return tags in created order
|
41
|
+
# if we added the order to the association this would always apply
|
42
|
+
scope = scope.order("#{ActsAsTaggableOn::Tagging.table_name}.id") if self.class.preserve_tag_order?
|
43
|
+
scope.all
|
40
44
|
end
|
41
45
|
|
42
46
|
def cached_owned_tag_list_on(context)
|
@@ -73,21 +77,38 @@ module ActsAsTaggableOn::Taggable
|
|
73
77
|
def save_owned_tags
|
74
78
|
tagging_contexts.each do |context|
|
75
79
|
cached_owned_tag_list_on(context).each do |owner, tag_list|
|
80
|
+
|
76
81
|
# Find existing tags or create non-existing tags:
|
77
|
-
|
82
|
+
tags = ActsAsTaggableOn::Tag.find_or_create_all_with_like_by_name(tag_list.uniq)
|
78
83
|
|
79
|
-
|
80
|
-
|
81
|
-
|
84
|
+
# Tag objects for owned tags
|
85
|
+
owned_tags = owner_tags_on(owner, context)
|
86
|
+
|
87
|
+
# Tag maintenance based on whether preserving the created order of tags
|
88
|
+
if self.class.preserve_tag_order?
|
89
|
+
# First off order the array of tag objects to match the tag list
|
90
|
+
# rather than existing tags followed by new tags
|
91
|
+
tags = tag_list.uniq.map{|s| tags.detect{|t| t.name.downcase == s.downcase}}
|
92
|
+
# To preserve tags in the order in which they were added
|
93
|
+
# delete all owned tags and create new tags if the content or order has changed
|
94
|
+
old_tags = (tags == owned_tags ? [] : owned_tags)
|
95
|
+
new_tags = (tags == owned_tags ? [] : tags)
|
96
|
+
else
|
97
|
+
# Delete discarded tags and create new tags
|
98
|
+
old_tags = owned_tags - tags
|
99
|
+
new_tags = tags - owned_tags
|
100
|
+
end
|
82
101
|
|
83
102
|
# Find all taggings that belong to the taggable (self), are owned by the owner,
|
84
103
|
# have the correct context, and are removed from the list.
|
85
|
-
|
86
|
-
|
87
|
-
|
104
|
+
if old_tags.present?
|
105
|
+
old_taggings = ActsAsTaggableOn::Tagging.where(:taggable_id => id, :taggable_type => self.class.base_class.to_s,
|
106
|
+
:tagger_type => owner.class.to_s, :tagger_id => owner.id,
|
107
|
+
:tag_id => old_tags, :context => context).all
|
108
|
+
end
|
88
109
|
|
110
|
+
# Destroy old taggings:
|
89
111
|
if old_taggings.present?
|
90
|
-
# Destroy old taggings:
|
91
112
|
ActsAsTaggableOn::Tagging.destroy_all(:id => old_taggings.map(&:id))
|
92
113
|
end
|
93
114
|
|