acts-as-taggable-on 1.0.13 → 1.1.6

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/README.rdoc CHANGED
@@ -89,6 +89,9 @@ also improves compatibility with the will_paginate gem:
89
89
  User.tagged_with("awesome").by_date
90
90
  User.tagged_with("awesome").by_date.paginate(:page => params[:page], :per_page => 20)
91
91
 
92
+ #Find a user with matching all tags, not just one
93
+ User.tagged_with(["awesome", "cool"], :match_all => :true)
94
+
92
95
  === Relationships
93
96
 
94
97
  You can find objects of the same type based on similar tags on certain contexts.
@@ -151,6 +154,12 @@ A helper is included to assist with generating tag clouds.
151
154
 
152
155
  Here is an example that generates a tag cloud.
153
156
 
157
+ Helper:
158
+
159
+ module PostsHelper
160
+ include TagsHelper
161
+ end
162
+
154
163
  Controller:
155
164
 
156
165
  class PostController < ApplicationController
@@ -160,7 +169,8 @@ Controller:
160
169
  end
161
170
 
162
171
  View:
163
- <% tag_cloud @tags, %w(css1 css2 css3 css4) do |tag, css_class| %>
172
+
173
+ <% tag_cloud(@tags, %w(css1 css2 css3 css4)) do |tag, css_class| %>
164
174
  <%= link_to tag.name, { :action => :tag, :id => tag.name }, :class => css_class %>
165
175
  <% end %>
166
176
 
@@ -187,5 +197,6 @@ CSS:
187
197
  * slainer68 - STI fix
188
198
  * harrylove - migration instructions and fix-ups
189
199
  * lawrencepit - cached tag work
200
+ * sobrinho - fixed tag_cloud helper
190
201
 
191
202
  Copyright (c) 2007-2009 Michael Bleigh (http://mbleigh.com/) and Intridea Inc. (http://intridea.com/), released under the MIT license
data/Rakefile CHANGED
@@ -9,7 +9,7 @@ begin
9
9
  gemspec.email = "michael@intridea.com"
10
10
  gemspec.homepage = "http://github.com/mbleigh/acts-as-taggable-on"
11
11
  gemspec.authors = ["Michael Bleigh"]
12
- gemspec.files = FileList["[A-Z]*", "{lib,spec,rails}/**/*"] - FileList["**/*.log"]
12
+ gemspec.files = FileList["[A-Z]*", "{generators,lib,spec,rails}/**/*"] - FileList["**/*.log"]
13
13
  end
14
14
  Jeweler::GemcutterTasks.new
15
15
  rescue LoadError
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.0.13
1
+ 1.1.6
@@ -0,0 +1,7 @@
1
+ class ActsAsTaggableOnMigrationGenerator < Rails::Generator::Base
2
+ def manifest
3
+ record do |m|
4
+ m.migration_template 'migration.rb', 'db/migrate', :migration_file_name => "acts_as_taggable_on_migration"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,29 @@
1
+ class ActsAsTaggableOnMigration < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :tags do |t|
4
+ t.column :name, :string
5
+ end
6
+
7
+ create_table :taggings do |t|
8
+ t.column :tag_id, :integer
9
+ t.column :taggable_id, :integer
10
+ t.column :tagger_id, :integer
11
+ t.column :tagger_type, :string
12
+
13
+ # You should make sure that the column created is
14
+ # long enough to store the required class names.
15
+ t.column :taggable_type, :string
16
+ t.column :context, :string
17
+
18
+ t.column :created_at, :datetime
19
+ end
20
+
21
+ add_index :taggings, :tag_id
22
+ add_index :taggings, [:taggable_id, :taggable_type, :context]
23
+ end
24
+
25
+ def self.down
26
+ drop_table :taggings
27
+ drop_table :tags
28
+ end
29
+ end
@@ -19,15 +19,15 @@ 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
22
+ # use aliased_join_table_name for context condition so that sphinx can join multiple
23
23
  # tag references from same model without getting an ambiguous column error
24
- self.class_eval do
24
+ class_eval do
25
25
  has_many "#{tag_type.singularize}_taggings".to_sym, :as => :taggable, :dependent => :destroy,
26
- :include => :tag, :conditions => ['#{aliased_join_table_name rescue "taggings"}.context = ?',tag_type], :class_name => "Tagging"
26
+ :include => :tag, :conditions => ['#{aliased_join_table_name || Tagging.table_name rescue Tagging.table_name}.context = ?',tag_type], :class_name => "Tagging"
27
27
  has_many "#{tag_type}".to_sym, :through => "#{tag_type.singularize}_taggings".to_sym, :source => :tag
28
28
  end
29
29
 
30
- self.class_eval <<-RUBY
30
+ class_eval <<-RUBY
31
31
  def self.taggable?
32
32
  true
33
33
  end
@@ -82,11 +82,10 @@ module ActiveRecord
82
82
  end
83
83
  RUBY
84
84
  end
85
-
86
85
  if respond_to?(:tag_types)
87
86
  write_inheritable_attribute( :tag_types, (tag_types + args).uniq )
88
87
  else
89
- self.class_eval do
88
+ class_eval do
90
89
  write_inheritable_attribute(:tag_types, args.uniq)
91
90
  class_inheritable_reader :tag_types
92
91
 
@@ -117,6 +116,7 @@ module ActiveRecord
117
116
  # Pass either a tag string, or an array of strings or tags
118
117
  #
119
118
  # Options:
119
+ # :any - find models that match any of the given tags
120
120
  # :exclude - Find models that are not tagged with the given tags
121
121
  # :match_all - Find models that match all of the given tags, not just one
122
122
  # :conditions - A piece of SQL conditions to add to the query
@@ -139,9 +139,9 @@ module ActiveRecord
139
139
  end
140
140
 
141
141
  def find_options_for_find_tagged_with(tags, options = {})
142
- tags = TagList.from(tags)
142
+ tag_list = TagList.from(tags)
143
143
 
144
- return {} if tags.empty?
144
+ return {} if tag_list.empty?
145
145
 
146
146
  joins = []
147
147
  conditions = []
@@ -150,28 +150,30 @@ module ActiveRecord
150
150
 
151
151
 
152
152
  if options.delete(:exclude)
153
- tags_conditions = tags.map { |t| sanitize_sql(["#{Tag.table_name}.name LIKE ?", t]) }.join(" OR ")
153
+ tags_conditions = tag_list.map { |t| sanitize_sql(["#{Tag.table_name}.name LIKE ?", t]) }.join(" OR ")
154
154
  conditions << "#{table_name}.#{primary_key} NOT IN (SELECT #{Tagging.table_name}.taggable_id FROM #{Tagging.table_name} JOIN #{Tag.table_name} ON #{Tagging.table_name}.tag_id = #{Tag.table_name}.id AND (#{tags_conditions}) WHERE #{Tagging.table_name}.taggable_type = #{quote_value(base_class.name)})"
155
155
 
156
+ elsif options.delete(:any)
157
+ tags_conditions = tag_list.map { |t| sanitize_sql(["#{Tag.table_name}.name LIKE ?", t]) }.join(" OR ")
158
+ conditions << "#{table_name}.#{primary_key} IN (SELECT #{Tagging.table_name}.taggable_id FROM #{Tagging.table_name} JOIN #{Tag.table_name} ON #{Tagging.table_name}.tag_id = #{Tag.table_name}.id AND (#{tags_conditions}) WHERE #{Tagging.table_name}.taggable_type = #{quote_value(base_class.name)})"
159
+
156
160
  else
161
+ tags = Tag.named_any(tag_list)
162
+ return { :conditions => "1 = 0" } unless tags.length == tag_list.length
163
+
157
164
  tags.each do |tag|
158
- safe_tag = tag.gsub(/[^a-zA-Z0-9]/, '')
165
+ safe_tag = tag.name.gsub(/[^a-zA-Z0-9]/, '')
159
166
  prefix = "#{safe_tag}_#{rand(1024)}"
160
167
 
161
168
  taggings_alias = "#{table_name}_taggings_#{prefix}"
162
- tags_alias = "#{table_name}_tags_#{prefix}"
163
169
 
164
170
  tagging_join = "JOIN #{Tagging.table_name} #{taggings_alias}" +
165
171
  " ON #{taggings_alias}.taggable_id = #{table_name}.#{primary_key}" +
166
- " AND #{taggings_alias}.taggable_type = #{quote_value(base_class.name)}"
172
+ " AND #{taggings_alias}.taggable_type = #{quote_value(base_class.name)}" +
173
+ " AND #{taggings_alias}.tag_id = #{tag.id}"
167
174
  tagging_join << " AND " + sanitize_sql(["#{taggings_alias}.context = ?", context.to_s]) if context
168
175
 
169
- tag_join = "JOIN #{Tag.table_name} #{tags_alias}" +
170
- " ON #{tags_alias}.id = #{taggings_alias}.tag_id" +
171
- " AND " + sanitize_sql(["#{tags_alias}.name like ?", tag])
172
-
173
176
  joins << tagging_join
174
- joins << tag_join
175
177
  end
176
178
  end
177
179
 
@@ -187,7 +189,8 @@ module ActiveRecord
187
189
 
188
190
  { :joins => joins.join(" "),
189
191
  :group => group,
190
- :conditions => conditions.join(" AND ") }.update(options)
192
+ :conditions => conditions.join(" AND "),
193
+ :readonly => false }.update(options)
191
194
  end
192
195
 
193
196
  # Calculate the tag counts for all tags.
@@ -225,14 +228,32 @@ module ActiveRecord
225
228
 
226
229
  joins = ["LEFT OUTER JOIN #{Tagging.table_name} ON #{Tag.table_name}.id = #{Tagging.table_name}.tag_id"]
227
230
  joins << sanitize_sql(["AND #{Tagging.table_name}.context = ?",options.delete(:on).to_s]) unless options[:on].nil?
228
-
229
231
  joins << " INNER JOIN #{table_name} ON #{table_name}.#{primary_key} = #{Tagging.table_name}.taggable_id"
230
- unless self.descends_from_active_record?
232
+
233
+ unless descends_from_active_record?
231
234
  # Current model is STI descendant, so add type checking to the join condition
232
- joins << " AND #{table_name}.#{self.inheritance_column} = '#{self.name}'"
235
+ joins << " AND #{table_name}.#{inheritance_column} = '#{name}'"
233
236
  end
234
237
 
235
- joins << scope[:joins] if scope && scope[:joins]
238
+ # Based on a proposed patch by donV to ActiveRecord Base
239
+ # This is needed because merge_joins and construct_join are private in ActiveRecord Base
240
+ if scope && scope[:joins]
241
+ case scope[:joins]
242
+ when Array
243
+ scope_joins = scope[:joins].flatten
244
+ strings = scope_joins.select{|j| j.is_a? String}
245
+ joins << strings.join(' ') + " "
246
+ symbols = scope_joins - strings
247
+ join_dependency = ActiveRecord::Associations::ClassMethods::InnerJoinDependency.new(self, symbols, nil)
248
+ joins << " #{join_dependency.join_associations.collect { |assoc| assoc.association_join }.join} "
249
+ joins.flatten!
250
+ when Symbol, Hash
251
+ join_dependency = ActiveRecord::Associations::ClassMethods::InnerJoinDependency.new(self, scope[:joins], nil)
252
+ joins << " #{join_dependency.join_associations.collect { |assoc| assoc.association_join }.join} "
253
+ when String
254
+ joins << scope[:joins]
255
+ end
256
+ end
236
257
 
237
258
  at_least = sanitize_sql(['COUNT(*) >= ?', options.delete(:at_least)]) if options[:at_least]
238
259
  at_most = sanitize_sql(['COUNT(*) <= ?', options.delete(:at_most)]) if options[:at_most]
@@ -264,29 +285,40 @@ module ActiveRecord
264
285
  def is_taggable?
265
286
  self.class.is_taggable?
266
287
  end
267
-
288
+
268
289
  def add_custom_context(value)
269
290
  custom_contexts << value.to_s unless custom_contexts.include?(value.to_s) or self.class.tag_types.map(&:to_s).include?(value.to_s)
270
291
  end
271
292
 
272
- def tag_list_on(context, owner=nil)
273
- var_name = context.to_s.singularize + "_list"
293
+ def tag_list_on(context, owner = nil)
274
294
  add_custom_context(context)
275
- return instance_variable_get("@#{var_name}") unless instance_variable_get("@#{var_name}").nil?
276
-
295
+ cache = tag_list_cache_on(context)
296
+ return owner ? cache[owner] : cache[owner] if cache[owner]
297
+
277
298
  if !owner && self.class.caching_tag_list_on?(context) and !(cached_value = cached_tag_list_on(context)).nil?
278
- instance_variable_set("@#{var_name}", TagList.from(self["cached_#{var_name}"]))
299
+ cache[owner] = TagList.from(cached_tag_list_on(context))
279
300
  else
280
- instance_variable_set("@#{var_name}", TagList.new(*tags_on(context, owner).map(&:name)))
301
+ cache[owner] = TagList.new(*tags_on(context, owner).map(&:name))
281
302
  end
282
303
  end
304
+
305
+ def all_tags_list_on(context)
306
+ variable_name = "@all_#{context.to_s.singularize}_list"
307
+ return instance_variable_get(variable_name) if instance_variable_get(variable_name)
308
+ instance_variable_set(variable_name, TagList.new(all_tags_on(context).map(&:name)).freeze)
309
+ end
310
+
311
+ def all_tags_on(context)
312
+ opts = {:conditions => ["#{Tagging.table_name}.context = ?", context.to_s]}
313
+ base_tags.find(:all, opts.merge(:order => "#{Tagging.table_name}.created_at"))
314
+ end
283
315
 
284
- def tags_on(context, owner=nil)
316
+ def tags_on(context, owner = nil)
285
317
  if owner
286
318
  opts = {:conditions => ["#{Tagging.table_name}.context = ? AND #{Tagging.table_name}.tagger_id = ? AND #{Tagging.table_name}.tagger_type = ?",
287
319
  context.to_s, owner.id, owner.class.to_s]}
288
320
  else
289
- opts = {:conditions => ["#{Tagging.table_name}.context = ?", context.to_s]}
321
+ opts = {:conditions => ["#{Tagging.table_name}.context = ? AND #{Tagging.table_name}.tagger_id IS NULL", context.to_s]}
290
322
  end
291
323
  base_tags.find(:all, opts)
292
324
  end
@@ -294,14 +326,21 @@ module ActiveRecord
294
326
  def cached_tag_list_on(context)
295
327
  self["cached_#{context.to_s.singularize}_list"]
296
328
  end
329
+
330
+ def tag_list_cache_on(context)
331
+ variable_name = "@#{context.to_s.singularize}_list"
332
+ cache = instance_variable_get(variable_name)
333
+ instance_variable_set(variable_name, cache = {}) unless cache
334
+ cache
335
+ end
297
336
 
298
- def set_tag_list_on(context,new_list, tagger=nil)
299
- instance_variable_set("@#{context.to_s.singularize}_list", TagList.from_owner(tagger, new_list))
337
+ def set_tag_list_on(context, new_list, tagger = nil)
338
+ tag_list_cache_on(context)[tagger] = TagList.from(new_list)
300
339
  add_custom_context(context)
301
340
  end
302
341
 
303
342
  def tag_counts_on(context, options={})
304
- self.class.tag_counts_on(context, options.merge(:id => self.id))
343
+ self.class.tag_counts_on(context, options.merge(:id => id))
305
344
  end
306
345
 
307
346
  def related_tags_for(context, klass, options = {})
@@ -311,9 +350,9 @@ module ActiveRecord
311
350
  end
312
351
 
313
352
  def related_search_options(context, klass, options = {})
314
- tags_to_find = self.tags_on(context).collect { |t| t.name }
353
+ tags_to_find = tags_on(context).collect { |t| t.name }
315
354
 
316
- exclude_self = "#{klass.table_name}.id != #{self.id} AND" if self.class == klass
355
+ exclude_self = "#{klass.table_name}.id != #{id} AND" if self.class == klass
317
356
 
318
357
  { :select => "#{klass.table_name}.*, COUNT(#{Tag.table_name}.id) AS count",
319
358
  :from => "#{klass.table_name}, #{Tag.table_name}, #{Tagging.table_name}",
@@ -330,9 +369,9 @@ module ActiveRecord
330
369
  end
331
370
 
332
371
  def matching_context_search_options(search_context, result_context, klass, options = {})
333
- tags_to_find = self.tags_on(search_context).collect { |t| t.name }
372
+ tags_to_find = tags_on(search_context).collect { |t| t.name }
334
373
 
335
- exclude_self = "#{klass.table_name}.id != #{self.id} AND" if self.class == klass
374
+ exclude_self = "#{klass.table_name}.id != #{id} AND" if self.class == klass
336
375
 
337
376
  { :select => "#{klass.table_name}.*, COUNT(#{Tag.table_name}.id) AS count",
338
377
  :from => "#{klass.table_name}, #{Tag.table_name}, #{Tagging.table_name}",
@@ -341,38 +380,59 @@ module ActiveRecord
341
380
  :order => "count DESC"
342
381
  }.update(options)
343
382
  end
344
-
383
+
345
384
  def save_cached_tag_list
346
385
  self.class.tag_types.map(&:to_s).each do |tag_type|
347
- if self.class.send("caching_#{tag_type.singularize}_list?")
348
- self["cached_#{tag_type.singularize}_list"] = send("#{tag_type.singularize}_list").to_s
386
+ if self.class.send("caching_#{tag_type.singularize}_list?")
387
+ self["cached_#{tag_type.singularize}_list"] = tag_list_cache_on(tag_type.singularize).to_a.flatten.compact.join(', ')
349
388
  end
350
389
  end
351
390
  end
352
391
 
353
392
  def save_tags
354
- (custom_contexts + self.class.tag_types.map(&:to_s)).each do |tag_type|
355
- next unless instance_variable_get("@#{tag_type.singularize}_list")
356
- owner = instance_variable_get("@#{tag_type.singularize}_list").owner
357
- new_tag_names = instance_variable_get("@#{tag_type.singularize}_list") - tags_on(tag_type).map(&:name)
358
- old_tags = tags_on(tag_type, owner).reject { |tag| instance_variable_get("@#{tag_type.singularize}_list").include?(tag.name) }
359
-
360
- self.class.transaction do
361
- base_tags.delete(*old_tags) if old_tags.any?
362
- new_tag_names.each do |new_tag_name|
363
- new_tag = Tag.find_or_create_with_like_by_name(new_tag_name)
364
- Tagging.create(:tag_id => new_tag.id, :context => tag_type,
365
- :taggable => self, :tagger => owner)
393
+ contexts = custom_contexts + self.class.tag_types.map(&:to_s)
394
+
395
+ transaction do
396
+ contexts.each do |context|
397
+ cache = tag_list_cache_on(context)
398
+
399
+ cache.each do |owner, list|
400
+ new_tags = Tag.find_or_create_all_with_like_by_name(list.uniq)
401
+ taggings = Tagging.find(:all, :conditions => { :taggable_id => self.id, :taggable_type => self.class.base_class.to_s })
402
+
403
+ # Destroy old taggings:
404
+ if owner
405
+ old_tags = tags_on(context, owner) - new_tags
406
+ old_taggings = Tagging.find(:all, :conditions => { :taggable_id => self.id, :taggable_type => self.class.base_class.to_s, :tag_id => old_tags, :tagger_id => owner.id, :tagger_type => owner.class.to_s, :context => context })
407
+
408
+ Tagging.destroy_all :id => old_taggings.map(&:id)
409
+ else
410
+ old_tags = tags_on(context) - new_tags
411
+ base_tags.delete(*old_tags)
412
+ end
413
+
414
+ new_tags.reject! { |tag| taggings.any? { |tagging|
415
+ tagging.tag_id == tag.id &&
416
+ tagging.tagger_id == (owner ? owner.id : nil) &&
417
+ tagging.tagger_type == (owner ? owner.class.to_s : nil) &&
418
+ tagging.context == context
419
+ }
420
+ }
421
+
422
+ # create new taggings:
423
+ new_tags.each do |tag|
424
+ Tagging.create!(:tag_id => tag.id, :context => context, :tagger => owner, :taggable => self)
425
+ end
366
426
  end
367
427
  end
368
- end
428
+ end
369
429
 
370
430
  true
371
431
  end
372
432
 
373
433
  def reload_with_tag_list(*args)
374
434
  self.class.tag_types.each do |tag_type|
375
- self.instance_variable_set("@#{tag_type.to_s.singularize}_list", nil)
435
+ instance_variable_set("@#{tag_type.to_s.singularize}_list", nil)
376
436
  end
377
437
 
378
438
  reload_without_tag_list(*args)
@@ -1,17 +1,43 @@
1
1
  class Tag < ActiveRecord::Base
2
+
3
+ attr_accessible :name
4
+
5
+ ### ASSOCIATIONS:
6
+
2
7
  has_many :taggings, :dependent => :destroy
3
8
 
9
+ ### VALIDATIONS:
10
+
4
11
  validates_presence_of :name
5
12
  validates_uniqueness_of :name
6
13
 
7
- named_scope :named, lambda { |name| { :conditions => ["name = ?", name] } }
14
+ ### NAMED SCOPES:
15
+
16
+ named_scope :named, lambda { |name| { :conditions => ["name LIKE ?", name] } }
17
+ named_scope :named_any, lambda { |list| { :conditions => list.map { |tag| sanitize_sql(["name LIKE ?", tag.to_s]) }.join(" OR ") } }
8
18
  named_scope :named_like, lambda { |name| { :conditions => ["name LIKE ?", "%#{name}%"] } }
19
+ named_scope :named_like_any, lambda { |list| { :conditions => list.map { |tag| sanitize_sql(["name LIKE ?", "%#{tag.to_s}%"]) }.join(" OR ") } }
20
+
21
+ ### CLASS METHODS:
9
22
 
10
- # LIKE is used for cross-database case-insensitivity
11
23
  def self.find_or_create_with_like_by_name(name)
12
- find(:first, :conditions => ["name LIKE ?", name]) || create(:name => name)
24
+ named_like(name).first || create(:name => name)
13
25
  end
14
26
 
27
+ def self.find_or_create_all_with_like_by_name(*list)
28
+ list = [list].flatten
29
+
30
+ return [] if list.empty?
31
+
32
+ existing_tags = Tag.named_any(list).all
33
+ new_tag_names = list.reject { |name| existing_tags.any? { |tag| tag.name.downcase == name.downcase } }
34
+ created_tags = new_tag_names.map { |name| Tag.create(:name => name) }
35
+
36
+ existing_tags + created_tags
37
+ end
38
+
39
+ ### INSTANCE METHODS:
40
+
15
41
  def ==(object)
16
42
  super || (object.is_a?(Tag) && name == object.name)
17
43
  end
@@ -41,9 +41,10 @@ class TagList < Array
41
41
  # tag_list = TagList.new("Round", "Square,Cube")
42
42
  # tag_list.to_s # 'Round, "Square,Cube"'
43
43
  def to_s
44
- clean!
44
+ tags = frozen? ? self.dup : self
45
+ tags.send(:clean!)
45
46
 
46
- map do |name|
47
+ tags.map do |name|
47
48
  name.include?(delimiter) ? "\"#{name}\"" : name
48
49
  end.join(delimiter.ends_with?(" ") ? delimiter : "#{delimiter} ")
49
50
  end
@@ -55,7 +56,7 @@ class TagList < Array
55
56
  map!(&:strip)
56
57
  uniq!
57
58
  end
58
-
59
+
59
60
  def extract_and_apply_options!(args)
60
61
  options = args.last.is_a?(Hash) ? args.pop : {}
61
62
  options.assert_valid_keys :parse
@@ -79,17 +80,11 @@ class TagList < Array
79
80
  string = string.to_s.dup
80
81
 
81
82
  # Parse the quoted tags
82
- string.gsub!(/"(.*?)"\s*#{delimiter}?\s*/) { tag_list << $1; "" }
83
- string.gsub!(/'(.*?)'\s*#{delimiter}?\s*/) { tag_list << $1; "" }
83
+ string.gsub!(/(\A|#{delimiter})\s*"(.*?)"\s*(#{delimiter}\s*|\z)/) { tag_list << $2; $3 }
84
+ string.gsub!(/(\A|#{delimiter})\s*'(.*?)'\s*(#{delimiter}\s*|\z)/) { tag_list << $2; $3 }
84
85
 
85
86
  tag_list.add(string.split(delimiter))
86
87
  end
87
88
  end
88
-
89
- def from_owner(owner, *tags)
90
- returning from(*tags) do |taglist|
91
- taglist.owner = owner
92
- end
93
- end
94
89
  end
95
90
  end
@@ -1,8 +1,14 @@
1
1
  class Tagging < ActiveRecord::Base #:nodoc:
2
+ attr_accessible :tag, :tag_id, :context,
3
+ :taggable, :taggable_type, :taggable_id,
4
+ :tagger, :tagger_type, :tagger_id
5
+
2
6
  belongs_to :tag
3
7
  belongs_to :taggable, :polymorphic => true
4
8
  belongs_to :tagger, :polymorphic => true
5
9
 
6
10
  validates_presence_of :context
7
11
  validates_presence_of :tag_id
12
+
13
+ validates_uniqueness_of :tag_id, :scope => [:taggable_type, :taggable_id, :context, :tagger_id, :tagger_type]
8
14
  end
@@ -1,6 +1,8 @@
1
1
  module TagsHelper
2
2
  # See the README for an example using tag_cloud.
3
3
  def tag_cloud(tags, classes)
4
+ return [] if tags.empty?
5
+
4
6
  max_count = tags.sort_by(&:count).last.count.to_f
5
7
 
6
8
  tags.each do |tag|
data/rails/init.rb CHANGED
@@ -2,6 +2,4 @@ require 'acts-as-taggable-on'
2
2
 
3
3
  ActiveRecord::Base.send :include, ActiveRecord::Acts::TaggableOn
4
4
  ActiveRecord::Base.send :include, ActiveRecord::Acts::Tagger
5
- ActionView::Base.send :include, TagsHelper if defined?(ActionView::Base)
6
-
7
- RAILS_DEFAULT_LOGGER.info "** acts_as_taggable_on: initialized properly."
5
+ ActionView::Base.send :include, TagsHelper if defined?(ActionView::Base)
@@ -1,6 +1,10 @@
1
1
  require File.dirname(__FILE__) + '/../spec_helper'
2
2
 
3
3
  describe "Acts As Taggable On" do
4
+ before(:each) do
5
+ clean_database!
6
+ end
7
+
4
8
  it "should provide a class method 'taggable?' that is false for untaggable models" do
5
9
  UntaggableModel.should_not be_taggable
6
10
  end
@@ -176,16 +180,6 @@ describe "Acts As Taggable On" do
176
180
  end
177
181
 
178
182
  describe 'Tagging Contexts' do
179
- before(:all) do
180
- class Array
181
- def freq
182
- k=Hash.new(0)
183
- self.each {|e| k[e]+=1}
184
- k
185
- end
186
- end
187
- end
188
-
189
183
  it 'should eliminate duplicate tagging contexts ' do
190
184
  TaggableModel.acts_as_taggable_on(:skills, :skills)
191
185
  TaggableModel.tag_types.freq[:skills].should_not == 3
@@ -212,10 +206,6 @@ describe "Acts As Taggable On" do
212
206
  TaggableModel.acts_as_taggable_on([nil])
213
207
  }.should_not raise_error
214
208
  end
215
-
216
- after(:all) do
217
- class Array; remove_method :freq; end
218
- end
219
209
  end
220
210
 
221
211
  end
@@ -1,8 +1,11 @@
1
1
  require File.dirname(__FILE__) + '/../spec_helper'
2
2
 
3
3
  describe "acts_as_tagger" do
4
+ before(:each) do
5
+ clean_database!
6
+ end
7
+
4
8
  context "Tagger Method Generation" do
5
-
6
9
  before(:each) do
7
10
  @tagger = TaggableUser.new()
8
11
  end
@@ -48,8 +51,22 @@ describe "acts_as_tagger" do
48
51
 
49
52
  it 'should by default create the tag context on-the-fly' do
50
53
  @taggable.tag_list_on(:here_ond_now).should be_empty
51
- @tagger.tag(@taggable, :with=>'that', :on=>:here_ond_now)
52
- @taggable.tag_list_on(:here_ond_now).should include('that')
54
+ @tagger.tag(@taggable, :with=>'that', :on => :here_ond_now)
55
+ @taggable.tag_list_on(:here_ond_now).should_not include('that')
56
+ @taggable.all_tags_list_on(:here_ond_now).should include('that')
57
+ end
58
+
59
+ it "should show all the tag list when both public and owned tags exist" do
60
+ @taggable.tag_list = 'ruby, python'
61
+ @tagger.tag(@taggable, :with => 'java, lisp', :on => :tags)
62
+ @taggable.all_tags_on(:tags).map(&:name).sort.should == %w(ruby python java lisp).sort
63
+ end
64
+
65
+ it "should not add owned tags to the common list" do
66
+ @taggable.tag_list = 'ruby, python'
67
+ @tagger.tag(@taggable, :with => 'java, lisp', :on => :foo)
68
+ @tagger.tag(@taggable, :with => '', :on => :foo)
69
+ @taggable.tag_list.should == %w(ruby python)
53
70
  end
54
71
 
55
72
  it "should throw an exception when the default is over-ridden" do
@@ -64,9 +81,33 @@ describe "acts_as_tagger" do
64
81
  @tagger.tag(@taggable, :with=>'this, and, that', :on=>:foo_boo, :force=>false) rescue
65
82
  @taggable.tag_list_on(:foo_boo).should be_empty
66
83
  end
84
+ end
85
+
86
+ context "when called by multiple tagger's" do
87
+ before(:each) do
88
+ @user_x = TaggableUser.create(:name => "User X")
89
+ @user_y = TaggableUser.create(:name => "User Y")
90
+ @taggable = TaggableModel.create(:name => 'acts_as_taggable_on', :tag_list => 'plugin')
91
+
92
+ @user_x.tag(@taggable, :with => 'ruby, rails', :on => :tags)
93
+ @user_y.tag(@taggable, :with => 'ruby, plugin', :on => :tags)
67
94
 
95
+ @user_y.tag(@taggable, :with => '', :on => :tags)
96
+ @user_y.tag(@taggable, :with => '', :on => :tags)
97
+ end
98
+
99
+ it "should delete owned tags" do
100
+ @user_y.owned_tags.should == []
101
+ end
102
+
103
+ it "should not delete other taggers tags" do
104
+ @user_x.owned_tags.should have(2).items
105
+ end
106
+
107
+ it "should not delete original tags" do
108
+ @taggable.all_tags_list_on(:tags).should include('plugin')
109
+ end
68
110
  end
69
-
70
111
  end
71
112
 
72
113
  end
@@ -1,6 +1,9 @@
1
1
  require File.dirname(__FILE__) + '/../spec_helper'
2
2
 
3
3
  describe "Group Helper" do
4
+ before(:each) do
5
+ clean_database!
6
+ end
4
7
 
5
8
  describe "grouped_column_names_for method" do
6
9
  before(:each) do
@@ -20,6 +20,18 @@ describe TagList do
20
20
  @tag_list.include?("wicked").should be_true
21
21
  end
22
22
 
23
+ it "should be able to add delimited list of words with quoted delimiters" do
24
+ @tag_list.add("'cool, wicked', \"really cool, really wicked\"", :parse => true)
25
+ @tag_list.include?("cool, wicked").should be_true
26
+ @tag_list.include?("really cool, really wicked").should be_true
27
+ end
28
+
29
+ it "should be able to handle other uses of quotation marks correctly" do
30
+ @tag_list.add("john's cool car, mary's wicked toy", :parse => true)
31
+ @tag_list.include?("john's cool car").should be_true
32
+ @tag_list.include?("mary's wicked toy").should be_true
33
+ end
34
+
23
35
  it "should be able to add an array of words" do
24
36
  @tag_list.add(["cool", "wicked"], :parse => true)
25
37
  @tag_list.include?("cool").should be_true
@@ -49,4 +61,10 @@ describe TagList do
49
61
  @tag_list.add("cool","rad,bodacious")
50
62
  @tag_list.to_s.should == "awesome, radical, cool, \"rad,bodacious\""
51
63
  end
64
+
65
+ it "should be able to call to_s on a frozen tag list" do
66
+ @tag_list.freeze
67
+ lambda { @tag_list.add("cool","rad,bodacious") }.should raise_error
68
+ lambda { @tag_list.to_s }.should_not raise_error
69
+ end
52
70
  end
@@ -2,9 +2,20 @@ require File.dirname(__FILE__) + '/../spec_helper'
2
2
 
3
3
  describe Tag do
4
4
  before(:each) do
5
+ clean_database!
5
6
  @tag = Tag.new
6
- @user = TaggableModel.create(:name => "Pablo")
7
- Tag.delete_all
7
+ @user = TaggableModel.create(:name => "Pablo")
8
+ end
9
+
10
+ describe "named like any" do
11
+ before(:each) do
12
+ Tag.create(:name => "awesome")
13
+ Tag.create(:name => "epic")
14
+ end
15
+
16
+ it "should find both tags" do
17
+ Tag.named_like_any(["awesome", "epic"]).should have(2).items
18
+ end
8
19
  end
9
20
 
10
21
  describe "find or create by name" do
@@ -27,6 +38,37 @@ describe Tag do
27
38
  }.should change(Tag, :count).by(1)
28
39
  end
29
40
  end
41
+
42
+ describe "find or create all by any name" do
43
+ before(:each) do
44
+ @tag.name = "awesome"
45
+ @tag.save
46
+ end
47
+
48
+ it "should find by name" do
49
+ Tag.find_or_create_all_with_like_by_name("awesome").should == [@tag]
50
+ end
51
+
52
+ it "should find by name case insensitive" do
53
+ Tag.find_or_create_all_with_like_by_name("AWESOME").should == [@tag]
54
+ end
55
+
56
+ it "should create by name" do
57
+ lambda {
58
+ Tag.find_or_create_all_with_like_by_name("epic")
59
+ }.should change(Tag, :count).by(1)
60
+ end
61
+
62
+ it "should find or create by name" do
63
+ lambda {
64
+ Tag.find_or_create_all_with_like_by_name("awesome", "epic").map(&:name).should == ["awesome", "epic"]
65
+ }.should change(Tag, :count).by(1)
66
+ end
67
+
68
+ it "should return an empty array if no tags are specified" do
69
+ Tag.find_or_create_all_with_like_by_name([]).should == []
70
+ end
71
+ end
30
72
 
31
73
  it "should require a name" do
32
74
  @tag.valid?
@@ -2,49 +2,54 @@ require File.dirname(__FILE__) + '/../spec_helper'
2
2
 
3
3
  describe "Taggable" do
4
4
  before(:each) do
5
- [TaggableModel, Tag, Tagging, TaggableUser].each(&:delete_all)
5
+ clean_database!
6
6
  @taggable = TaggableModel.new(:name => "Bob Jones")
7
7
  end
8
8
 
9
9
  it "should have tag types" do
10
- TaggableModel.tag_types.should == [:tags, :languages, :skills, :needs, :offerings]
11
- @taggable.tag_types.should == TaggableModel.tag_types
10
+ for type in [:tags, :languages, :skills, :needs, :offerings]
11
+ TaggableModel.tag_types.should include type
12
+ end
13
+ @taggable.tag_types.should == TaggableModel.tag_types
12
14
  end
13
15
 
14
16
  it "should have tag_counts_on" do
15
17
  TaggableModel.tag_counts_on(:tags).should be_empty
16
-
18
+
17
19
  @taggable.tag_list = ["awesome", "epic"]
18
20
  @taggable.save
19
21
 
20
22
  TaggableModel.tag_counts_on(:tags).count.should == 2
21
23
  @taggable.tag_counts_on(:tags).count.should == 2
22
24
  end
23
-
25
+
24
26
  it "should be able to create tags" do
25
27
  @taggable.skill_list = "ruby, rails, css"
26
- @taggable.instance_variable_get("@skill_list").instance_of?(TagList).should be_true
28
+ @taggable.instance_variable_get("@skill_list").instance_of?(Hash).should be_true
29
+ @taggable.instance_variable_get("@skill_list")[nil].instance_of?(TagList).should be_true
27
30
  @taggable.save
28
-
31
+
29
32
  Tag.find(:all).size.should == 3
30
33
  end
31
-
34
+
32
35
  it "should be able to create tags through the tag list directly" do
33
36
  @taggable.tag_list_on(:test).add("hello")
34
- @taggable.save
37
+ @taggable.tag_list_cache_on(:test).should_not be_empty
38
+ @taggable.save
39
+ @taggable.save_tags
35
40
  @taggable.reload
36
41
  @taggable.tag_list_on(:test).should == ["hello"]
37
42
  end
38
-
43
+
39
44
  it "should differentiate between contexts" do
40
45
  @taggable.skill_list = "ruby, rails, css"
41
46
  @taggable.tag_list = "ruby, bob, charlie"
42
47
  @taggable.save
43
48
  @taggable.reload
44
- @taggable.skill_list.include?("ruby").should be_true
45
- @taggable.skill_list.include?("bob").should be_false
49
+ @taggable.skill_list.should include("ruby")
50
+ @taggable.skill_list.should_not include("bob")
46
51
  end
47
-
52
+
48
53
  it "should be able to remove tags through list alone" do
49
54
  @taggable.skill_list = "ruby, rails, css"
50
55
  @taggable.save
@@ -55,13 +60,13 @@ describe "Taggable" do
55
60
  @taggable.reload
56
61
  @taggable.should have(2).skills
57
62
  end
58
-
63
+
59
64
  it "should be able to find by tag" do
60
65
  @taggable.skill_list = "ruby, rails, css"
61
66
  @taggable.save
62
67
  TaggableModel.find_tagged_with("ruby").first.should == @taggable
63
68
  end
64
-
69
+
65
70
  it "should be able to find by tag with context" do
66
71
  @taggable.skill_list = "ruby, rails, css"
67
72
  @taggable.tag_list = "bob, charlie"
@@ -70,25 +75,27 @@ describe "Taggable" do
70
75
  TaggableModel.find_tagged_with("bob", :on => :skills).first.should_not == @taggable
71
76
  TaggableModel.find_tagged_with("bob", :on => :tags).first.should == @taggable
72
77
  end
73
-
78
+
74
79
  it "should be able to use the tagged_with named scope" do
75
80
  @taggable.skill_list = "ruby, rails, css"
76
81
  @taggable.tag_list = "bob, charlie"
77
82
  @taggable.save
78
-
83
+
79
84
  TaggableModel.tagged_with("ruby").first.should == @taggable
85
+ TaggableModel.tagged_with("ruby, css").first.should == @taggable
86
+ TaggableModel.tagged_with("ruby, nonexistingtag").should be_empty
80
87
  TaggableModel.tagged_with("bob", :on => :skills).first.should_not == @taggable
81
88
  TaggableModel.tagged_with("bob", :on => :tags).first.should == @taggable
82
89
  end
83
-
90
+
84
91
  it "should not care about case" do
85
92
  bob = TaggableModel.create(:name => "Bob", :tag_list => "ruby")
86
93
  frank = TaggableModel.create(:name => "Frank", :tag_list => "Ruby")
87
-
94
+
88
95
  Tag.find(:all).size.should == 1
89
96
  TaggableModel.find_tagged_with("ruby").should == TaggableModel.find_tagged_with("Ruby")
90
97
  end
91
-
98
+
92
99
  it "should be able to get tag counts on model as a whole" do
93
100
  bob = TaggableModel.create(:name => "Bob", :tag_list => "ruby, rails, css")
94
101
  frank = TaggableModel.create(:name => "Frank", :tag_list => "ruby, rails")
@@ -96,33 +103,39 @@ describe "Taggable" do
96
103
  TaggableModel.tag_counts.should_not be_empty
97
104
  TaggableModel.skill_counts.should_not be_empty
98
105
  end
99
-
106
+
100
107
  it "should be able to get all tag counts on model as whole" do
101
108
  bob = TaggableModel.create(:name => "Bob", :tag_list => "ruby, rails, css")
102
109
  frank = TaggableModel.create(:name => "Frank", :tag_list => "ruby, rails")
103
110
  charlie = TaggableModel.create(:name => "Charlie", :skill_list => "ruby")
104
-
111
+
105
112
  TaggableModel.all_tag_counts.should_not be_empty
106
113
  TaggableModel.all_tag_counts.first.count.should == 3 # ruby
107
114
  end
108
-
115
+
116
+ it "should not return read-only records" do
117
+ TaggableModel.create(:name => "Bob", :tag_list => "ruby, rails, css")
118
+
119
+ TaggableModel.tagged_with("ruby").first.should_not be_readonly
120
+ end
121
+
109
122
  it "should be able to get scoped tag counts" do
110
123
  bob = TaggableModel.create(:name => "Bob", :tag_list => "ruby, rails, css")
111
124
  frank = TaggableModel.create(:name => "Frank", :tag_list => "ruby, rails")
112
125
  charlie = TaggableModel.create(:name => "Charlie", :skill_list => "ruby")
113
-
126
+
114
127
  TaggableModel.tagged_with("ruby").tag_counts.first.count.should == 2 # ruby
115
128
  TaggableModel.tagged_with("ruby").skill_counts.first.count.should == 1 # ruby
116
129
  end
117
-
130
+
118
131
  it "should be able to get all scoped tag counts" do
119
132
  bob = TaggableModel.create(:name => "Bob", :tag_list => "ruby, rails, css")
120
133
  frank = TaggableModel.create(:name => "Frank", :tag_list => "ruby, rails")
121
134
  charlie = TaggableModel.create(:name => "Charlie", :skill_list => "ruby")
122
-
135
+
123
136
  TaggableModel.tagged_with("ruby").all_tag_counts.first.count.should == 3 # ruby
124
137
  end
125
-
138
+
126
139
  it "should be able to set a custom tag context list" do
127
140
  bob = TaggableModel.create(:name => "Bob")
128
141
  bob.set_tag_list_on(:rotors, "spinning, jumping")
@@ -131,17 +144,27 @@ describe "Taggable" do
131
144
  bob.reload
132
145
  bob.tags_on(:rotors).should_not be_empty
133
146
  end
134
-
147
+
135
148
  it "should be able to find tagged" do
136
149
  bob = TaggableModel.create(:name => "Bob", :tag_list => "fitter, happier, more productive", :skill_list => "ruby, rails, css")
137
150
  frank = TaggableModel.create(:name => "Frank", :tag_list => "weaker, depressed, inefficient", :skill_list => "ruby, rails, css")
138
151
  steve = TaggableModel.create(:name => 'Steve', :tag_list => 'fitter, happier, more productive', :skill_list => 'c++, java, ruby')
139
-
152
+
140
153
  TaggableModel.find_tagged_with("ruby", :order => 'taggable_models.name').should == [bob, frank, steve]
141
154
  TaggableModel.find_tagged_with("ruby, rails", :order => 'taggable_models.name').should == [bob, frank]
142
155
  TaggableModel.find_tagged_with(["ruby", "rails"], :order => 'taggable_models.name').should == [bob, frank]
143
156
  end
144
-
157
+
158
+ it "should be able to find tagged with any tag" do
159
+ bob = TaggableModel.create(:name => "Bob", :tag_list => "fitter, happier, more productive", :skill_list => "ruby, rails, css")
160
+ frank = TaggableModel.create(:name => "Frank", :tag_list => "weaker, depressed, inefficient", :skill_list => "ruby, rails, css")
161
+ steve = TaggableModel.create(:name => 'Steve', :tag_list => 'fitter, happier, more productive', :skill_list => 'c++, java, ruby')
162
+
163
+ TaggableModel.find_tagged_with(["ruby", "java"], :order => 'taggable_models.name', :any => true).should == [bob, frank, steve]
164
+ TaggableModel.find_tagged_with(["c++", "fitter"], :order => 'taggable_models.name', :any => true).should == [bob, steve]
165
+ TaggableModel.find_tagged_with(["depressed", "css"], :order => 'taggable_models.name', :any => true).should == [bob, frank]
166
+ end
167
+
145
168
  it "should be able to find tagged on a custom tag context" do
146
169
  bob = TaggableModel.create(:name => "Bob")
147
170
  bob.set_tag_list_on(:rotors, "spinning, jumping")
@@ -154,29 +177,38 @@ describe "Taggable" do
154
177
  bob = TaggableModel.create(:name => "Bob", :tag_list => "fitter, happier, more productive", :skill_list => "ruby, rails, css")
155
178
  frank = TaggableModel.create(:name => "Frank", :tag_list => "weaker, depressed, inefficient", :skill_list => "ruby, rails, css")
156
179
  steve = TaggableModel.create(:name => 'Steve', :tag_list => 'fitter, happier, more productive', :skill_list => 'c++, java, python')
157
-
180
+
158
181
  # Let's only find those productive Rails developers
159
182
  TaggableModel.tagged_with('rails', :on => :skills, :order => 'taggable_models.name').should == [bob, frank]
160
183
  TaggableModel.tagged_with('happier', :on => :tags, :order => 'taggable_models.name').should == [bob, steve]
161
184
  TaggableModel.tagged_with('rails', :on => :skills).tagged_with('happier', :on => :tags).should == [bob]
162
185
  TaggableModel.tagged_with('rails').tagged_with('happier', :on => :tags).should == [bob]
163
186
  end
164
-
187
+
165
188
  it "should be able to find tagged with only the matching tags" do
166
189
  bob = TaggableModel.create(:name => "Bob", :tag_list => "lazy, happier")
167
190
  frank = TaggableModel.create(:name => "Frank", :tag_list => "fitter, happier, inefficient")
168
191
  steve = TaggableModel.create(:name => 'Steve', :tag_list => "fitter, happier")
169
-
192
+
170
193
  TaggableModel.find_tagged_with("fitter, happier", :match_all => true).should == [steve]
171
194
  end
172
-
195
+
173
196
  it "should be able to find tagged with some excluded tags" do
174
197
  bob = TaggableModel.create(:name => "Bob", :tag_list => "happier, lazy")
175
198
  frank = TaggableModel.create(:name => "Frank", :tag_list => "happier")
176
199
  steve = TaggableModel.create(:name => 'Steve', :tag_list => "happier")
177
-
200
+
178
201
  TaggableModel.find_tagged_with("lazy", :exclude => true).should == [frank, steve]
179
202
  end
203
+
204
+ it "should not create duplicate taggings" do
205
+ bob = TaggableModel.create(:name => "Bob")
206
+ lambda {
207
+ bob.tag_list << "happier"
208
+ bob.tag_list << "happier"
209
+ bob.save
210
+ }.should change(Tagging, :count).by(1)
211
+ end
180
212
 
181
213
  describe "Single Table Inheritance" do
182
214
  before do
@@ -185,35 +217,45 @@ describe "Taggable" do
185
217
  @inherited_same = InheritingTaggableModel.new(:name => "inherited same")
186
218
  @inherited_different = AlteredInheritingTaggableModel.new(:name => "inherited different")
187
219
  end
188
-
220
+
189
221
  it "should be able to save tags for inherited models" do
190
222
  @inherited_same.tag_list = "bob, kelso"
191
223
  @inherited_same.save
192
224
  InheritingTaggableModel.find_tagged_with("bob").first.should == @inherited_same
193
225
  end
194
-
226
+
195
227
  it "should find STI tagged models on the superclass" do
196
228
  @inherited_same.tag_list = "bob, kelso"
197
229
  @inherited_same.save
198
230
  TaggableModel.find_tagged_with("bob").first.should == @inherited_same
199
231
  end
200
-
232
+
201
233
  it "should be able to add on contexts only to some subclasses" do
202
234
  @inherited_different.part_list = "fork, spoon"
203
235
  @inherited_different.save
204
236
  InheritingTaggableModel.find_tagged_with("fork", :on => :parts).should be_empty
205
237
  AlteredInheritingTaggableModel.find_tagged_with("fork", :on => :parts).first.should == @inherited_different
206
238
  end
207
-
239
+
208
240
  it "should have different tag_counts_on for inherited models" do
209
241
  @inherited_same.tag_list = "bob, kelso"
210
242
  @inherited_same.save!
211
243
  @inherited_different.tag_list = "fork, spoon"
212
244
  @inherited_different.save!
213
-
245
+
214
246
  InheritingTaggableModel.tag_counts_on(:tags).map(&:name).should == %w(bob kelso)
215
247
  AlteredInheritingTaggableModel.tag_counts_on(:tags).map(&:name).should == %w(fork spoon)
216
248
  TaggableModel.tag_counts_on(:tags).map(&:name).should == %w(bob kelso fork spoon)
217
249
  end
250
+
251
+ it 'should store same tag without validation conflict' do
252
+ @taggable.tag_list = 'one'
253
+ @taggable.save!
254
+
255
+ @inherited_same.tag_list = 'one'
256
+ @inherited_same.save!
257
+
258
+ @inherited_same.update_attributes! :name => 'foo'
259
+ end
218
260
  end
219
261
  end
@@ -2,21 +2,38 @@ require File.dirname(__FILE__) + '/../spec_helper'
2
2
 
3
3
  describe "Tagger" do
4
4
  before(:each) do
5
- [TaggableModel, Tag, Tagging, TaggableUser].each(&:delete_all)
5
+ clean_database!
6
6
  @user = TaggableUser.new
7
7
  @taggable = TaggableModel.new(:name => "Bob Jones")
8
8
  end
9
-
9
+
10
10
  it "should have taggings" do
11
11
  @user.tag(@taggable, :with=>'ruby,scheme', :on=>:tags)
12
12
  @user.owned_taggings.size == 2
13
13
  end
14
-
14
+
15
15
  it "should have tags" do
16
16
  @user.tag(@taggable, :with=>'ruby,scheme', :on=>:tags)
17
17
  @user.owned_tags.size == 2
18
18
  end
19
19
 
20
+ it "should not overlap or lose tags from different users" do
21
+ @user2 = TaggableUser.new
22
+ lambda{
23
+ @user.tag(@taggable, :with => 'ruby, scheme', :on => :tags)
24
+ @user2.tag(@taggable, :with => 'java, python, lisp, ruby', :on => :tags)
25
+ }.should change(Tagging, :count).by(6)
26
+
27
+ @user.owned_tags.map(&:name).sort.should == %w(ruby scheme).sort
28
+ @user2.owned_tags.map(&:name).sort.should == %w(java python lisp ruby).sort
29
+
30
+ @taggable.tags_from(@user).sort.should == %w(ruby scheme).sort
31
+ @taggable.tags_from(@user2).sort.should == %w(java lisp python ruby).sort
32
+
33
+ @taggable.all_tags_list_on(:tags).sort.should == %w(ruby scheme java python lisp).sort
34
+ @taggable.all_tags_on(:tags).size.should == 6
35
+ end
36
+
20
37
  it "is tagger" do
21
38
  @user.is_tagger?.should(be_true)
22
39
  end
@@ -2,6 +2,7 @@ require File.dirname(__FILE__) + '/../spec_helper'
2
2
 
3
3
  describe Tagging do
4
4
  before(:each) do
5
+ clean_database!
5
6
  @tagging = Tagging.new
6
7
  end
7
8
 
@@ -13,4 +14,13 @@ describe Tagging do
13
14
  @tagging.should_not be_valid
14
15
  @tagging.errors.on(:tag_id).should == "can't be blank"
15
16
  end
17
+
18
+ it "should not create duplicate taggings" do
19
+ @taggable = TaggableModel.create(:name => "Bob Jones")
20
+ @tag = Tag.create(:name => "awesome")
21
+
22
+ lambda {
23
+ 2.times { Tagging.create(:taggable => @taggable, :tag => @tag, :context => 'tags') }
24
+ }.should change(Tagging, :count).by(1)
25
+ end
16
26
  end
@@ -2,7 +2,7 @@ require File.dirname(__FILE__) + '/../spec_helper'
2
2
 
3
3
  describe TagsHelper do
4
4
  before(:each) do
5
- [TaggableModel, Tag, Tagging].each(&:delete_all)
5
+ clean_database!
6
6
 
7
7
  @bob = TaggableModel.create(:name => "Bob Jones", :language_list => "ruby, php")
8
8
  @tom = TaggableModel.create(:name => "Tom Marley", :language_list => "ruby, java")
data/spec/spec_helper.rb CHANGED
@@ -1,12 +1,20 @@
1
1
  # require File.dirname(__FILE__) + '/../../../../spec/spec_helper'
2
2
  require 'rubygems'
3
- require 'activerecord'
3
+ require 'active_record'
4
4
  require 'spec'
5
5
 
6
6
  module Spec::Example::ExampleGroupMethods
7
7
  alias :context :describe
8
8
  end
9
9
 
10
+ class Array
11
+ def freq
12
+ k=Hash.new(0)
13
+ each {|e| k[e]+=1}
14
+ k
15
+ end
16
+ end
17
+
10
18
  TEST_DATABASE_FILE = File.join(File.dirname(__FILE__), '..', 'test.sqlite3')
11
19
 
12
20
  File.unlink(TEST_DATABASE_FILE) if File.exist?(TEST_DATABASE_FILE)
@@ -16,7 +24,10 @@ ActiveRecord::Base.establish_connection(
16
24
 
17
25
  RAILS_DEFAULT_LOGGER = Logger.new(File.join(File.dirname(__FILE__), "debug.log"))
18
26
 
19
- load(File.dirname(__FILE__) + '/schema.rb')
27
+ ActiveRecord::Base.silence do
28
+ ActiveRecord::Migration.verbose = false
29
+ load(File.dirname(__FILE__) + '/schema.rb')
30
+ end
20
31
 
21
32
  $: << File.join(File.dirname(__FILE__), '..', 'lib')
22
33
  require File.join(File.dirname(__FILE__), '..', 'init')
@@ -45,4 +56,13 @@ class TaggableUser < ActiveRecord::Base
45
56
  end
46
57
 
47
58
  class UntaggableModel < ActiveRecord::Base
59
+ end
60
+
61
+ def clean_database!
62
+ $debug = false
63
+ models = [Tag, Tagging, TaggableModel, OtherTaggableModel, InheritingTaggableModel,
64
+ AlteredInheritingTaggableModel, TaggableUser]
65
+ models.each do |model|
66
+ model.destroy_all
67
+ end
48
68
  end
metadata CHANGED
@@ -1,7 +1,12 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: acts-as-taggable-on
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.13
4
+ prerelease: false
5
+ segments:
6
+ - 1
7
+ - 1
8
+ - 6
9
+ version: 1.1.6
5
10
  platform: ruby
6
11
  authors:
7
12
  - Michael Bleigh
@@ -9,7 +14,7 @@ autorequire:
9
14
  bindir: bin
10
15
  cert_chain: []
11
16
 
12
- date: 2009-12-11 00:00:00 +01:00
17
+ date: 2010-02-27 00:00:00 +01:00
13
18
  default_executable:
14
19
  dependencies: []
15
20
 
@@ -27,6 +32,8 @@ files:
27
32
  - README.rdoc
28
33
  - Rakefile
29
34
  - VERSION
35
+ - generators/acts_as_taggable_on_migration/acts_as_taggable_on_migration_generator.rb
36
+ - generators/acts_as_taggable_on_migration/templates/migration.rb
30
37
  - lib/acts-as-taggable-on.rb
31
38
  - lib/acts_as_taggable_on/acts_as_taggable_on.rb
32
39
  - lib/acts_as_taggable_on/acts_as_tagger.rb
@@ -61,18 +68,20 @@ required_ruby_version: !ruby/object:Gem::Requirement
61
68
  requirements:
62
69
  - - ">="
63
70
  - !ruby/object:Gem::Version
71
+ segments:
72
+ - 0
64
73
  version: "0"
65
- version:
66
74
  required_rubygems_version: !ruby/object:Gem::Requirement
67
75
  requirements:
68
76
  - - ">="
69
77
  - !ruby/object:Gem::Version
78
+ segments:
79
+ - 0
70
80
  version: "0"
71
- version:
72
81
  requirements: []
73
82
 
74
83
  rubyforge_project:
75
- rubygems_version: 1.3.5
84
+ rubygems_version: 1.3.6
76
85
  signing_key:
77
86
  specification_version: 3
78
87
  summary: ActsAsTaggableOn is a tagging plugin for Rails that provides multiple tagging contexts on a single model.