yury-acts-as-taggable-on 1.0.3 → 1.0.4
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/acts_as_taggable_on/acts_as_taggable_on.rb +37 -12
- data/lib/acts_as_taggable_on/acts_as_tagger.rb +2 -2
- data/lib/acts_as_taggable_on/tag.rb +24 -3
- data/lib/acts_as_taggable_on/tag_list.rb +6 -2
- data/lib/acts_as_taggable_on/tagging.rb +2 -0
- data/rails/init.rb +1 -3
- data/spec/acts_as_taggable_on/acts_as_taggable_on_spec.rb +19 -0
- data/spec/acts_as_taggable_on/taggable_spec.rb +45 -0
- metadata +5 -3
@@ -19,9 +19,11 @@ module ActiveRecord
|
|
19
19
|
args.compact! if args
|
20
20
|
for tag_type in args
|
21
21
|
tag_type = tag_type.to_s
|
22
|
+
# use aliased_join_table_name for context condition so that sphix can join multiple
|
23
|
+
# tag references from same model without getting an ambiguous column error
|
22
24
|
self.class_eval do
|
23
25
|
has_many "#{tag_type.singularize}_taggings".to_sym, :as => :taggable, :dependent => :destroy,
|
24
|
-
:include => :tag, :conditions => [
|
26
|
+
:include => :tag, :conditions => ['#{aliased_join_table_name rescue "taggings"}.context = ?',tag_type], :class_name => "Tagging"
|
25
27
|
has_many "#{tag_type}".to_sym, :through => "#{tag_type.singularize}_taggings".to_sym, :source => :tag
|
26
28
|
end
|
27
29
|
|
@@ -45,6 +47,14 @@ module ActiveRecord
|
|
45
47
|
def #{tag_type.singularize}_list=(new_tags)
|
46
48
|
set_tag_list_on('#{tag_type}',new_tags)
|
47
49
|
end
|
50
|
+
|
51
|
+
def #{tag_type.singularize}_list_changed=(changed)
|
52
|
+
instance_variable_set("@#{tag_type.singularize}_list_changed", changed)
|
53
|
+
end
|
54
|
+
|
55
|
+
def #{tag_type.singularize}_list_changed?
|
56
|
+
instance_variable_get("@#{tag_type.singularize}_list_changed") || false
|
57
|
+
end
|
48
58
|
|
49
59
|
def #{tag_type.singularize}_counts(options = {})
|
50
60
|
tag_counts_on('#{tag_type}',options)
|
@@ -183,7 +193,7 @@ module ActiveRecord
|
|
183
193
|
conditions = merge_conditions(conditions, scope[:conditions]) if scope
|
184
194
|
|
185
195
|
joins = ["LEFT OUTER JOIN #{Tagging.table_name} ON #{Tag.table_name}.id = #{Tagging.table_name}.tag_id"]
|
186
|
-
joins << sanitize_sql(["AND context = ?",options.delete(:on).to_s]) unless options[:on].nil?
|
196
|
+
joins << sanitize_sql(["AND #{Tagging.table_name}.context = ?",options.delete(:on).to_s]) unless options[:on].nil?
|
187
197
|
joins << "LEFT OUTER JOIN #{table_name} ON #{table_name}.#{primary_key} = #{Tagging.table_name}.taggable_id"
|
188
198
|
joins << scope[:joins] if scope && scope[:joins]
|
189
199
|
|
@@ -226,21 +236,27 @@ module ActiveRecord
|
|
226
236
|
def tag_list_on(context, owner=nil)
|
227
237
|
var_name = context.to_s.singularize + "_list"
|
228
238
|
add_custom_context(context)
|
229
|
-
|
230
|
-
|
231
|
-
if
|
232
|
-
|
233
|
-
|
234
|
-
|
239
|
+
list = instance_variable_get("@#{var_name}")
|
240
|
+
|
241
|
+
if list.nil?
|
242
|
+
if !owner && self.class.caching_tag_list_on?(context) and !(cached_value = cached_tag_list_on(context)).nil?
|
243
|
+
list = instance_variable_set("@#{var_name}", TagList.from(self["cached_#{var_name}"]))
|
244
|
+
else
|
245
|
+
list = instance_variable_set("@#{var_name}", TagList.new(*tags_on(context, owner).map(&:name)))
|
246
|
+
end
|
235
247
|
end
|
248
|
+
|
249
|
+
list.context = context.to_s
|
250
|
+
list.taggable = self
|
251
|
+
list
|
236
252
|
end
|
237
253
|
|
238
254
|
def tags_on(context, owner=nil)
|
239
255
|
if owner
|
240
|
-
opts = {:conditions => ["
|
256
|
+
opts = {:conditions => ["context = ? AND tagger_id = ? AND tagger_type = ?",
|
241
257
|
context.to_s, owner.id, owner.class.to_s]}
|
242
258
|
else
|
243
|
-
opts = {:conditions => ["
|
259
|
+
opts = {:conditions => ["context = ?", context.to_s]}
|
244
260
|
end
|
245
261
|
base_tags.find(:all, opts)
|
246
262
|
end
|
@@ -250,8 +266,13 @@ module ActiveRecord
|
|
250
266
|
end
|
251
267
|
|
252
268
|
def set_tag_list_on(context,new_list, tagger=nil)
|
269
|
+
old_list = instance_variable_get("@#{context.to_s.singularize}_list")
|
253
270
|
instance_variable_set("@#{context.to_s.singularize}_list", TagList.from_owner(tagger, new_list))
|
254
271
|
add_custom_context(context)
|
272
|
+
|
273
|
+
if old_list != instance_variable_get("@#{context.to_s.singularize}_list")
|
274
|
+
instance_variable_set("@#{context.to_s.singularize}_list_changed", true)
|
275
|
+
end
|
255
276
|
end
|
256
277
|
|
257
278
|
def tag_counts_on(context,options={})
|
@@ -266,10 +287,12 @@ module ActiveRecord
|
|
266
287
|
|
267
288
|
def related_search_options(context, klass, options = {})
|
268
289
|
tags_to_find = self.tags_on(context).collect { |t| t.name }
|
290
|
+
|
291
|
+
exclude_self = "#{klass.table_name}.id != #{self.id} AND" if self.class == klass
|
269
292
|
|
270
293
|
{ :select => "#{klass.table_name}.*, COUNT(#{Tag.table_name}.id) AS count",
|
271
294
|
:from => "#{klass.table_name}, #{Tag.table_name}, #{Tagging.table_name}",
|
272
|
-
:conditions => ["#{klass.table_name}.id = #{Tagging.table_name}.taggable_id AND #{Tagging.table_name}.taggable_type = '#{klass.to_s}' AND #{Tagging.table_name}.tag_id = #{Tag.table_name}.id AND #{Tag.table_name}.name IN (?)", tags_to_find],
|
295
|
+
:conditions => ["#{exclude_self} #{klass.table_name}.id = #{Tagging.table_name}.taggable_id AND #{Tagging.table_name}.taggable_type = '#{klass.to_s}' AND #{Tagging.table_name}.tag_id = #{Tag.table_name}.id AND #{Tag.table_name}.name IN (?)", tags_to_find],
|
273
296
|
:group => "#{klass.table_name}.id",
|
274
297
|
:order => "count DESC"
|
275
298
|
}.update(options)
|
@@ -283,7 +306,7 @@ module ActiveRecord
|
|
283
306
|
end
|
284
307
|
end
|
285
308
|
|
286
|
-
def save_tags
|
309
|
+
def save_tags
|
287
310
|
(custom_contexts + self.class.tag_types.map(&:to_s)).each do |tag_type|
|
288
311
|
next unless instance_variable_get("@#{tag_type.singularize}_list")
|
289
312
|
owner = instance_variable_get("@#{tag_type.singularize}_list").owner
|
@@ -298,6 +321,8 @@ module ActiveRecord
|
|
298
321
|
:taggable => self, :tagger => owner)
|
299
322
|
end
|
300
323
|
end
|
324
|
+
|
325
|
+
instance_variable_set("@#{tag_type.singularize}_list_changed", false)
|
301
326
|
end
|
302
327
|
|
303
328
|
true
|
@@ -9,7 +9,7 @@ module ActiveRecord
|
|
9
9
|
def acts_as_tagger(opts={})
|
10
10
|
has_many :owned_taggings, opts.merge(:as => :tagger, :dependent => :destroy,
|
11
11
|
:include => :tag, :class_name => "Tagging")
|
12
|
-
has_many :owned_tags, :through => :owned_taggings, :source => :tag
|
12
|
+
has_many :owned_tags, :through => :owned_taggings, :source => :tag, :uniq => true
|
13
13
|
include ActiveRecord::Acts::Tagger::InstanceMethods
|
14
14
|
extend ActiveRecord::Acts::Tagger::SingletonMethods
|
15
15
|
end
|
@@ -49,4 +49,4 @@ module ActiveRecord
|
|
49
49
|
|
50
50
|
end
|
51
51
|
end
|
52
|
-
end
|
52
|
+
end
|
@@ -1,12 +1,29 @@
|
|
1
|
-
class Tag < ActiveRecord::Base
|
1
|
+
class Tag < ActiveRecord::Base
|
2
2
|
has_many :taggings
|
3
3
|
|
4
4
|
validates_presence_of :name
|
5
5
|
validates_uniqueness_of :name
|
6
6
|
|
7
|
+
def self.cleanup(name)
|
8
|
+
n = name.to_s.downcase.gsub(/[^a-z0-9_-]+/, '').strip
|
9
|
+
n.blank? ? nil : n
|
10
|
+
end
|
11
|
+
|
12
|
+
def name=(name)
|
13
|
+
#strip any non-alphanumeric and downcase
|
14
|
+
self["name"] = Tag.cleanup(name)
|
15
|
+
end
|
16
|
+
|
7
17
|
# LIKE is used for cross-database case-insensitivity
|
8
18
|
def self.find_or_create_with_like_by_name(name)
|
9
|
-
|
19
|
+
name = Tag.cleanup(name)
|
20
|
+
|
21
|
+
begin
|
22
|
+
find(:first, :conditions => ["name LIKE ?", name]) || create(:name => name)
|
23
|
+
rescue ActiveRecord::StatementInvalid
|
24
|
+
# If we can't insert the tag (perhaps a dupe), try to refetch.
|
25
|
+
find(:first, :conditions => ["name LIKE ?", name])
|
26
|
+
end
|
10
27
|
end
|
11
28
|
|
12
29
|
def ==(object)
|
@@ -17,7 +34,11 @@ class Tag < ActiveRecord::Base
|
|
17
34
|
name
|
18
35
|
end
|
19
36
|
|
20
|
-
def count
|
37
|
+
def count
|
21
38
|
read_attribute(:count).to_i
|
22
39
|
end
|
40
|
+
|
41
|
+
def self.per_page
|
42
|
+
10
|
43
|
+
end
|
23
44
|
end
|
@@ -7,6 +7,8 @@ class TagList < Array
|
|
7
7
|
end
|
8
8
|
|
9
9
|
attr_accessor :owner
|
10
|
+
attr :context, true
|
11
|
+
attr :taggable, true
|
10
12
|
|
11
13
|
# Add tags to the tag_list. Duplicate or blank tags will be ignored.
|
12
14
|
#
|
@@ -16,6 +18,7 @@ class TagList < Array
|
|
16
18
|
#
|
17
19
|
# tag_list.add("Fun, Happy", :parse => true)
|
18
20
|
def add(*names)
|
21
|
+
taggable.send("#{context.singularize}_list_changed=", true) if (taggable && context)
|
19
22
|
extract_and_apply_options!(names)
|
20
23
|
concat(names)
|
21
24
|
clean!
|
@@ -30,6 +33,7 @@ class TagList < Array
|
|
30
33
|
#
|
31
34
|
# tag_list.remove("Sad, Lonely", :parse => true)
|
32
35
|
def remove(*names)
|
36
|
+
taggable.send("#{context.singularize}_list_changed=", true) if (taggable && context)
|
33
37
|
extract_and_apply_options!(names)
|
34
38
|
delete_if { |name| names.include?(name) }
|
35
39
|
self
|
@@ -77,8 +81,8 @@ class TagList < Array
|
|
77
81
|
string = string.to_s.dup
|
78
82
|
|
79
83
|
# Parse the quoted tags
|
80
|
-
string.gsub!(/"(.*?)"\s*#{delimiter}?\s*/) { tag_list
|
81
|
-
string.gsub!(/'(.*?)'\s*#{delimiter}?\s*/) { tag_list
|
84
|
+
string.gsub!(/"(.*?)"\s*#{delimiter}?\s*/) { tag_list.add $1; "" }
|
85
|
+
string.gsub!(/'(.*?)'\s*#{delimiter}?\s*/) { tag_list.add $1; "" }
|
82
86
|
|
83
87
|
tag_list.add(string.split(delimiter))
|
84
88
|
end
|
data/rails/init.rb
CHANGED
@@ -1,6 +1,4 @@
|
|
1
1
|
require 'acts-as-taggable-on'
|
2
2
|
|
3
3
|
ActiveRecord::Base.send :include, ActiveRecord::Acts::TaggableOn
|
4
|
-
ActiveRecord::Base.send :include, ActiveRecord::Acts::Tagger
|
5
|
-
|
6
|
-
RAILS_DEFAULT_LOGGER.info "** acts_as_taggable_on: initialized properly."
|
4
|
+
ActiveRecord::Base.send :include, ActiveRecord::Acts::Tagger
|
@@ -40,6 +40,11 @@ describe "Acts As Taggable On" do
|
|
40
40
|
@taggable.should respond_to(:tag_list, :skill_list, :language_list)
|
41
41
|
@taggable.should respond_to(:tag_list=, :skill_list=, :language_list=)
|
42
42
|
end
|
43
|
+
|
44
|
+
it "should generate a dirty field tracking accessor/setter for each tag type" do
|
45
|
+
@taggable.should respond_to(:tag_list_changed?, :skill_list_changed?, :language_list_changed?)
|
46
|
+
@taggable.should respond_to(:tag_list_changed=, :skill_list_changed=, :language_list_changed=)
|
47
|
+
end
|
43
48
|
end
|
44
49
|
|
45
50
|
describe "Single Table Inheritance" do
|
@@ -103,6 +108,20 @@ describe "Acts As Taggable On" do
|
|
103
108
|
taggable1.find_related_tags_for(OtherTaggableModel).should include(taggable3)
|
104
109
|
taggable1.find_related_tags_for(OtherTaggableModel).should_not include(taggable2)
|
105
110
|
end
|
111
|
+
|
112
|
+
it "should not include the object itself in the list of related objects" do
|
113
|
+
taggable1 = TaggableModel.create!(:name => "Taggable 1")
|
114
|
+
taggable2 = TaggableModel.create!(:name => "Taggable 2")
|
115
|
+
|
116
|
+
taggable1.tag_list = "one"
|
117
|
+
taggable1.save
|
118
|
+
|
119
|
+
taggable2.tag_list = "one, two"
|
120
|
+
taggable2.save
|
121
|
+
|
122
|
+
taggable1.find_related_tags.should include(taggable2)
|
123
|
+
taggable1.find_related_tags.should_not include(taggable1)
|
124
|
+
end
|
106
125
|
end
|
107
126
|
|
108
127
|
describe 'Tagging Contexts' do
|
@@ -106,6 +106,51 @@ describe "Taggable" do
|
|
106
106
|
TaggableModel.find_tagged_with("spinning", :on => :rotors).should_not be_empty
|
107
107
|
end
|
108
108
|
|
109
|
+
it "should support dirty field tracking on assign" do
|
110
|
+
@taggable.tag_list_changed?.should == false
|
111
|
+
|
112
|
+
@taggable.tag_list = "bob, charlie"
|
113
|
+
@taggable.tag_list_changed?.should == true
|
114
|
+
|
115
|
+
@taggable.save
|
116
|
+
@taggable.tag_list_changed?.should == false
|
117
|
+
|
118
|
+
@taggable.tag_list = "bob, charlie"
|
119
|
+
@taggable.tag_list_changed?.should == false
|
120
|
+
end
|
121
|
+
|
122
|
+
it "should support dirty field tracking on append" do
|
123
|
+
@taggable.tag_list_changed?.should == false
|
124
|
+
|
125
|
+
@taggable.tag_list.add "bob, charlie"
|
126
|
+
@taggable.tag_list_changed?.should == true
|
127
|
+
|
128
|
+
@taggable.save
|
129
|
+
@taggable.tag_list_changed?.should == false
|
130
|
+
|
131
|
+
@taggable.tag_list.add "bob, charlie"
|
132
|
+
@taggable.tag_list_changed?.should == false
|
133
|
+
end
|
134
|
+
|
135
|
+
it "should support dirty field tracking on append" do
|
136
|
+
@taggable.tag_list_changed?.should == false
|
137
|
+
|
138
|
+
@taggable.tag_list = "bob, charlie"
|
139
|
+
@taggable.tag_list_changed?.should == true
|
140
|
+
|
141
|
+
@taggable.save
|
142
|
+
@taggable.tag_list_changed?.should == false
|
143
|
+
|
144
|
+
@taggable.tag_list.remove "bob"
|
145
|
+
@taggable.tag_list_changed?.should == true
|
146
|
+
|
147
|
+
@taggable.save
|
148
|
+
@taggable.tag_list_changed?.should == false
|
149
|
+
|
150
|
+
@taggable.tag_list.remove "bob"
|
151
|
+
@taggable.tag_list_changed?.should == false
|
152
|
+
end
|
153
|
+
|
109
154
|
describe "Single Table Inheritance" do
|
110
155
|
before do
|
111
156
|
[TaggableModel, Tag, Tagging, TaggableUser].each(&:delete_all)
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: yury-acts-as-taggable-on
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Michael Bleigh
|
@@ -9,12 +9,12 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date:
|
12
|
+
date: 2009-03-06 00:00:00 -08:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|
16
16
|
description: Acts As Taggable On provides the ability to have multiple tag contexts on a single model in ActiveRecord. It also has support for tag clouds, related items, taggers, and more.
|
17
|
-
email:
|
17
|
+
email: michael@intridea.com
|
18
18
|
executables: []
|
19
19
|
|
20
20
|
extensions: []
|
@@ -28,6 +28,7 @@ files:
|
|
28
28
|
- generators/acts_as_taggable_on_migration
|
29
29
|
- generators/acts_as_taggable_on_migration/acts_as_taggable_on_migration_generator.rb
|
30
30
|
- generators/acts_as_taggable_on_migration/templates
|
31
|
+
- generators/acts_as_taggable_on_migration/templates/add_users_migration.rb
|
31
32
|
- generators/acts_as_taggable_on_migration/templates/migration.rb
|
32
33
|
- init.rb
|
33
34
|
- lib/acts-as-taggable-on.rb
|
@@ -45,6 +46,7 @@ files:
|
|
45
46
|
- spec/acts_as_taggable_on/taggable_spec.rb
|
46
47
|
- spec/acts_as_taggable_on/tagger_spec.rb
|
47
48
|
- spec/acts_as_taggable_on/tagging_spec.rb
|
49
|
+
- spec/debug.log
|
48
50
|
- spec/schema.rb
|
49
51
|
- spec/spec_helper.rb
|
50
52
|
- uninstall.rb
|