acts-as-taggable-on 2.2.2 → 2.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
|