yury-acts-as-taggable-on 1.0.3 → 1.0.4

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.
@@ -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 => ["#{Tagging.table_name}.context = ?",tag_type], :class_name => "Tagging"
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
- return instance_variable_get("@#{var_name}") unless instance_variable_get("@#{var_name}").nil?
230
-
231
- if !owner && self.class.caching_tag_list_on?(context) and !(cached_value = cached_tag_list_on(context)).nil?
232
- instance_variable_set("@#{var_name}", TagList.from(self["cached_#{var_name}"]))
233
- else
234
- instance_variable_set("@#{var_name}", TagList.new(*tags_on(context, owner).map(&:name)))
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 => ["#{Tagging.table_name}.context = ? AND tagger_id = ? AND tagger_type = ?",
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 => ["#{Tagging.table_name}.context = ?", context.to_s]}
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
- find(:first, :conditions => ["name LIKE ?", name]) || create(:name => name)
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 << $1; "" }
81
- string.gsub!(/'(.*?)'\s*#{delimiter}?\s*/) { tag_list << $1; "" }
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
@@ -3,4 +3,6 @@ class Tagging < ActiveRecord::Base #:nodoc:
3
3
  belongs_to :taggable, :polymorphic => true
4
4
  belongs_to :tagger, :polymorphic => true
5
5
  validates_presence_of :context
6
+
7
+ named_scope :type_pictures, :conditions => "taggable_type = 'Picture'"
6
8
  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.3
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: 2008-06-10 00:00:00 -07:00
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: yury.korolev@gmail.com
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