vincecima-acts-as-taggable-on 2.3.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. data/.gitignore +11 -0
  2. data/.rspec +2 -0
  3. data/.travis.yml +9 -0
  4. data/CHANGELOG +35 -0
  5. data/Gemfile +3 -0
  6. data/Guardfile +5 -0
  7. data/MIT-LICENSE +20 -0
  8. data/README.rdoc +248 -0
  9. data/Rakefile +13 -0
  10. data/acts-as-taggable-on.gemspec +28 -0
  11. data/lib/acts-as-taggable-on.rb +63 -0
  12. data/lib/acts-as-taggable-on/version.rb +4 -0
  13. data/lib/acts_as_taggable_on/acts_as_taggable_on/cache.rb +53 -0
  14. data/lib/acts_as_taggable_on/acts_as_taggable_on/collection.rb +127 -0
  15. data/lib/acts_as_taggable_on/acts_as_taggable_on/core.rb +352 -0
  16. data/lib/acts_as_taggable_on/acts_as_taggable_on/dirty.rb +37 -0
  17. data/lib/acts_as_taggable_on/acts_as_taggable_on/ownership.rb +126 -0
  18. data/lib/acts_as_taggable_on/acts_as_taggable_on/related.rb +73 -0
  19. data/lib/acts_as_taggable_on/tag.rb +94 -0
  20. data/lib/acts_as_taggable_on/tag_list.rb +101 -0
  21. data/lib/acts_as_taggable_on/taggable.rb +102 -0
  22. data/lib/acts_as_taggable_on/tagger.rb +67 -0
  23. data/lib/acts_as_taggable_on/tagging.rb +34 -0
  24. data/lib/acts_as_taggable_on/tags_helper.rb +17 -0
  25. data/lib/acts_as_taggable_on/utils.rb +34 -0
  26. data/lib/generators/acts_as_taggable_on/migration/migration_generator.rb +39 -0
  27. data/lib/generators/acts_as_taggable_on/migration/templates/active_record/migration.rb +30 -0
  28. data/rails/init.rb +1 -0
  29. data/spec/acts_as_taggable_on/acts_as_taggable_on_spec.rb +514 -0
  30. data/spec/acts_as_taggable_on/acts_as_tagger_spec.rb +114 -0
  31. data/spec/acts_as_taggable_on/tag_list_spec.rb +126 -0
  32. data/spec/acts_as_taggable_on/tag_spec.rb +185 -0
  33. data/spec/acts_as_taggable_on/taggable_spec.rb +543 -0
  34. data/spec/acts_as_taggable_on/tagger_spec.rb +138 -0
  35. data/spec/acts_as_taggable_on/tagging_spec.rb +28 -0
  36. data/spec/acts_as_taggable_on/tags_helper_spec.rb +44 -0
  37. data/spec/acts_as_taggable_on/utils_spec.rb +21 -0
  38. data/spec/bm.rb +52 -0
  39. data/spec/database.yml.sample +19 -0
  40. data/spec/generators/acts_as_taggable_on/migration/migration_generator_spec.rb +22 -0
  41. data/spec/models.rb +52 -0
  42. data/spec/schema.rb +61 -0
  43. data/spec/spec_helper.rb +83 -0
  44. data/uninstall.rb +1 -0
  45. metadata +217 -0
@@ -0,0 +1,67 @@
1
+ module ActsAsTaggableOn
2
+ module Tagger
3
+ def self.included(base)
4
+ base.extend ClassMethods
5
+ end
6
+
7
+ module ClassMethods
8
+ ##
9
+ # Make a model a tagger. This allows an instance of a model to claim ownership
10
+ # of tags.
11
+ #
12
+ # Example:
13
+ # class User < ActiveRecord::Base
14
+ # acts_as_tagger
15
+ # end
16
+ def acts_as_tagger(opts={})
17
+ class_eval do
18
+ has_many :owned_taggings, opts.merge(:as => :tagger, :dependent => :destroy,
19
+ :include => :tag, :class_name => "ActsAsTaggableOn::Tagging")
20
+ has_many :owned_tags, :through => :owned_taggings, :source => :tag, :uniq => true, :class_name => "ActsAsTaggableOn::Tag"
21
+ end
22
+
23
+ include ActsAsTaggableOn::Tagger::InstanceMethods
24
+ extend ActsAsTaggableOn::Tagger::SingletonMethods
25
+ end
26
+
27
+ def is_tagger?
28
+ false
29
+ end
30
+ end
31
+
32
+ module InstanceMethods
33
+ ##
34
+ # Tag a taggable model with tags that are owned by the tagger.
35
+ #
36
+ # @param taggable The object that will be tagged
37
+ # @param [Hash] options An hash with options. Available options are:
38
+ # * <tt>:with</tt> - The tags that you want to
39
+ # * <tt>:on</tt> - The context on which you want to tag
40
+ #
41
+ # Example:
42
+ # @user.tag(@photo, :with => "paris, normandy", :on => :locations)
43
+ def tag(taggable, opts={})
44
+ opts.reverse_merge!(:force => true)
45
+ skip_save = opts.delete(:skip_save)
46
+ return false unless taggable.respond_to?(:is_taggable?) && taggable.is_taggable?
47
+
48
+ raise "You need to specify a tag context using :on" unless opts.has_key?(:on)
49
+ raise "You need to specify some tags using :with" unless opts.has_key?(:with)
50
+ raise "No context :#{opts[:on]} defined in #{taggable.class.to_s}" unless (opts[:force] || taggable.tag_types.include?(opts[:on]))
51
+
52
+ taggable.set_owner_tag_list_on(self, opts[:on].to_s, opts[:with])
53
+ taggable.save unless skip_save
54
+ end
55
+
56
+ def is_tagger?
57
+ self.class.is_tagger?
58
+ end
59
+ end
60
+
61
+ module SingletonMethods
62
+ def is_tagger?
63
+ true
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,34 @@
1
+ module ActsAsTaggableOn
2
+ class Tagging < ::ActiveRecord::Base #:nodoc:
3
+ attr_accessible :tag,
4
+ :tag_id,
5
+ :context,
6
+ :taggable,
7
+ :taggable_type,
8
+ :taggable_id,
9
+ :tagger,
10
+ :tagger_type,
11
+ :tagger_id
12
+
13
+ belongs_to :tag, :class_name => 'ActsAsTaggableOn::Tag'
14
+ belongs_to :taggable, :polymorphic => true
15
+ belongs_to :tagger, :polymorphic => true
16
+
17
+ validates_presence_of :context
18
+ validates_presence_of :tag_id
19
+
20
+ validates_uniqueness_of :tag_id, :scope => [ :taggable_type, :taggable_id, :context, :tagger_id, :tagger_type ]
21
+
22
+ after_destroy :remove_unused_tags
23
+
24
+ private
25
+
26
+ def remove_unused_tags
27
+ if ActsAsTaggableOn.remove_unused_tags
28
+ if tag.taggings.count.zero?
29
+ tag.destroy
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,17 @@
1
+ module ActsAsTaggableOn
2
+ module TagsHelper
3
+ # See the README for an example using tag_cloud.
4
+ def tag_cloud(tags, classes)
5
+ tags = tags.all if tags.respond_to?(:all)
6
+
7
+ return [] if tags.empty?
8
+
9
+ max_count = tags.sort_by(&:count).last.count.to_f
10
+
11
+ tags.each do |tag|
12
+ index = ((tag.count / max_count) * (classes.size - 1))
13
+ yield tag, classes[index.nan? ? 0 : index.round]
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,34 @@
1
+ module ActsAsTaggableOn
2
+ module Utils
3
+ def self.included(base)
4
+
5
+ base.send :include, ActsAsTaggableOn::Utils::OverallMethods
6
+ base.extend ActsAsTaggableOn::Utils::OverallMethods
7
+ end
8
+
9
+ module OverallMethods
10
+ def using_postgresql?
11
+ ::ActiveRecord::Base.connection && ::ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
12
+ end
13
+
14
+ def using_sqlite?
15
+ ::ActiveRecord::Base.connection && ::ActiveRecord::Base.connection.adapter_name == 'SQLite'
16
+ end
17
+
18
+ def sha_prefix(string)
19
+ Digest::SHA1.hexdigest("#{string}#{rand}")[0..6]
20
+ end
21
+
22
+ private
23
+ def like_operator
24
+ using_postgresql? ? 'ILIKE' : 'LIKE'
25
+ end
26
+
27
+ # escape _ and % characters in strings, since these are wildcards in SQL.
28
+ def escape_like(str)
29
+ str.gsub(/[!%_]/){ |x| '!' + x }
30
+ end
31
+ end
32
+
33
+ end
34
+ end
@@ -0,0 +1,39 @@
1
+ require 'rails/generators'
2
+ require 'rails/generators/migration'
3
+
4
+ module ActsAsTaggableOn
5
+ class MigrationGenerator < Rails::Generators::Base
6
+ include Rails::Generators::Migration
7
+
8
+ desc "Generates migration for Tag and Tagging models"
9
+
10
+ def self.orm
11
+ Rails::Generators.options[:rails][:orm]
12
+ end
13
+
14
+ def self.source_root
15
+ File.join(File.dirname(__FILE__), 'templates', (orm.to_s unless orm.class.eql?(String)) )
16
+ end
17
+
18
+ def self.orm_has_migration?
19
+ [:active_record].include? orm
20
+ end
21
+
22
+ def self.next_migration_number(dirname)
23
+ if ActiveRecord::Base.timestamped_migrations
24
+ migration_number = Time.now.utc.strftime("%Y%m%d%H%M%S").to_i
25
+ migration_number += 1
26
+ migration_number.to_s
27
+ else
28
+ "%.3d" % (current_migration_number(dirname) + 1)
29
+ end
30
+ end
31
+
32
+ def create_migration_file
33
+ if self.class.orm_has_migration?
34
+ migration_template 'migration.rb', 'db/migrate/acts_as_taggable_on_migration'
35
+ end
36
+ end
37
+ end
38
+ end
39
+
@@ -0,0 +1,30 @@
1
+ class ActsAsTaggableOnMigration < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :tags do |t|
4
+ t.string :name
5
+ end
6
+
7
+ create_table :taggings do |t|
8
+ t.references :tag
9
+
10
+ # You should make sure that the column created is
11
+ # long enough to store the required class names.
12
+ t.references :taggable, :polymorphic => true
13
+ t.references :tagger, :polymorphic => true
14
+
15
+ # Limit is created to prevent MySQL error on index
16
+ # length for MyISAM table type: http://bit.ly/vgW2Ql
17
+ t.string :context, :limit => 128
18
+
19
+ t.datetime :created_at
20
+ end
21
+
22
+ add_index :taggings, :tag_id
23
+ add_index :taggings, [:taggable_id, :taggable_type, :context]
24
+ end
25
+
26
+ def self.down
27
+ drop_table :taggings
28
+ drop_table :tags
29
+ end
30
+ end
@@ -0,0 +1 @@
1
+ require 'acts-as-taggable-on'
@@ -0,0 +1,514 @@
1
+ require File.expand_path('../../spec_helper', __FILE__)
2
+
3
+ describe "Acts As Taggable On" do
4
+ before(:each) do
5
+ clean_database!
6
+ end
7
+
8
+ it "should provide a class method 'taggable?' that is false for untaggable models" do
9
+ UntaggableModel.should_not be_taggable
10
+ end
11
+
12
+ describe "Taggable Method Generation To Preserve Order" do
13
+ before(:each) do
14
+ clean_database!
15
+ TaggableModel.tag_types = []
16
+ TaggableModel.preserve_tag_order = false
17
+ TaggableModel.acts_as_ordered_taggable_on(:ordered_tags)
18
+ @taggable = TaggableModel.new(:name => "Bob Jones")
19
+ end
20
+
21
+ it "should respond 'true' to preserve_tag_order?" do
22
+ @taggable.class.preserve_tag_order?.should be_true
23
+ end
24
+ end
25
+
26
+ describe "Taggable Method Generation" do
27
+ before(:each) do
28
+ clean_database!
29
+ TaggableModel.tag_types = []
30
+ TaggableModel.acts_as_taggable_on(:tags, :languages, :skills, :needs, :offerings)
31
+ @taggable = TaggableModel.new(:name => "Bob Jones")
32
+ end
33
+
34
+ it "should respond 'true' to taggable?" do
35
+ @taggable.class.should be_taggable
36
+ end
37
+
38
+ it "should create a class attribute for tag types" do
39
+ @taggable.class.should respond_to(:tag_types)
40
+ end
41
+
42
+ it "should create an instance attribute for tag types" do
43
+ @taggable.should respond_to(:tag_types)
44
+ end
45
+
46
+ it "should have all tag types" do
47
+ @taggable.tag_types.should == [:tags, :languages, :skills, :needs, :offerings]
48
+ end
49
+
50
+ it "should create a class attribute for preserve tag order" do
51
+ @taggable.class.should respond_to(:preserve_tag_order?)
52
+ end
53
+
54
+ it "should create an instance attribute for preserve tag order" do
55
+ @taggable.should respond_to(:preserve_tag_order?)
56
+ end
57
+
58
+ it "should respond 'false' to preserve_tag_order?" do
59
+ @taggable.class.preserve_tag_order?.should be_false
60
+ end
61
+
62
+ it "should generate an association for each tag type" do
63
+ @taggable.should respond_to(:tags, :skills, :languages)
64
+ end
65
+
66
+ it "should add tagged_with and tag_counts to singleton" do
67
+ TaggableModel.should respond_to(:tagged_with, :tag_counts)
68
+ end
69
+
70
+ it "should generate a tag_list accessor/setter for each tag type" do
71
+ @taggable.should respond_to(:tag_list, :skill_list, :language_list)
72
+ @taggable.should respond_to(:tag_list=, :skill_list=, :language_list=)
73
+ end
74
+
75
+ it "should generate a tag_list accessor, that includes owned tags, for each tag type" do
76
+ @taggable.should respond_to(:all_tags_list, :all_skills_list, :all_languages_list)
77
+ end
78
+ end
79
+
80
+ describe "Single Table Inheritance" do
81
+ before do
82
+ @taggable = TaggableModel.new(:name => "taggable")
83
+ @inherited_same = InheritingTaggableModel.new(:name => "inherited same")
84
+ @inherited_different = AlteredInheritingTaggableModel.new(:name => "inherited different")
85
+ end
86
+
87
+ it "should pass on tag contexts to STI-inherited models" do
88
+ @inherited_same.should respond_to(:tag_list, :skill_list, :language_list)
89
+ @inherited_different.should respond_to(:tag_list, :skill_list, :language_list)
90
+ end
91
+
92
+ it "should have tag contexts added in altered STI models" do
93
+ @inherited_different.should respond_to(:part_list)
94
+ end
95
+ end
96
+
97
+ describe "Reloading" do
98
+ it "should save a model instantiated by Model.find" do
99
+ taggable = TaggableModel.create!(:name => "Taggable")
100
+ found_taggable = TaggableModel.find(taggable.id)
101
+ found_taggable.save
102
+ end
103
+ end
104
+
105
+ describe "Related Objects" do
106
+ it "should find related objects based on tag names on context" do
107
+ taggable1 = TaggableModel.create!(:name => "Taggable 1")
108
+ taggable2 = TaggableModel.create!(:name => "Taggable 2")
109
+ taggable3 = TaggableModel.create!(:name => "Taggable 3")
110
+
111
+ taggable1.tag_list = "one, two"
112
+ taggable1.save
113
+
114
+ taggable2.tag_list = "three, four"
115
+ taggable2.save
116
+
117
+ taggable3.tag_list = "one, four"
118
+ taggable3.save
119
+
120
+ taggable1.find_related_tags.should include(taggable3)
121
+ taggable1.find_related_tags.should_not include(taggable2)
122
+ end
123
+
124
+ it "should find related objects based on tag names on context - non standard id" do
125
+ taggable1 = NonStandardIdTaggableModel.create!(:name => "Taggable 1")
126
+ taggable2 = NonStandardIdTaggableModel.create!(:name => "Taggable 2")
127
+ taggable3 = NonStandardIdTaggableModel.create!(:name => "Taggable 3")
128
+
129
+ taggable1.tag_list = "one, two"
130
+ taggable1.save
131
+
132
+ taggable2.tag_list = "three, four"
133
+ taggable2.save
134
+
135
+ taggable3.tag_list = "one, four"
136
+ taggable3.save
137
+
138
+ taggable1.find_related_tags.should include(taggable3)
139
+ taggable1.find_related_tags.should_not include(taggable2)
140
+ end
141
+
142
+ it "should find other related objects based on tag names on context" do
143
+ taggable1 = TaggableModel.create!(:name => "Taggable 1")
144
+ taggable2 = OtherTaggableModel.create!(:name => "Taggable 2")
145
+ taggable3 = OtherTaggableModel.create!(:name => "Taggable 3")
146
+
147
+ taggable1.tag_list = "one, two"
148
+ taggable1.save
149
+
150
+ taggable2.tag_list = "three, four"
151
+ taggable2.save
152
+
153
+ taggable3.tag_list = "one, four"
154
+ taggable3.save
155
+
156
+ taggable1.find_related_tags_for(OtherTaggableModel).should include(taggable3)
157
+ taggable1.find_related_tags_for(OtherTaggableModel).should_not include(taggable2)
158
+ end
159
+
160
+ it "should not include the object itself in the list of related objects" do
161
+ taggable1 = TaggableModel.create!(:name => "Taggable 1")
162
+ taggable2 = TaggableModel.create!(:name => "Taggable 2")
163
+
164
+ taggable1.tag_list = "one"
165
+ taggable1.save
166
+
167
+ taggable2.tag_list = "one, two"
168
+ taggable2.save
169
+
170
+ taggable1.find_related_tags.should include(taggable2)
171
+ taggable1.find_related_tags.should_not include(taggable1)
172
+ end
173
+
174
+ it "should not include the object itself in the list of related objects - non standard id" do
175
+ taggable1 = NonStandardIdTaggableModel.create!(:name => "Taggable 1")
176
+ taggable2 = NonStandardIdTaggableModel.create!(:name => "Taggable 2")
177
+
178
+ taggable1.tag_list = "one"
179
+ taggable1.save
180
+
181
+ taggable2.tag_list = "one, two"
182
+ taggable2.save
183
+
184
+ taggable1.find_related_tags.should include(taggable2)
185
+ taggable1.find_related_tags.should_not include(taggable1)
186
+ end
187
+
188
+ context "Inherited Models" do
189
+ before do
190
+ @taggable1 = InheritingTaggableModel.create!(:name => "InheritingTaggable 1")
191
+ @taggable2 = InheritingTaggableModel.create!(:name => "InheritingTaggable 2")
192
+ @taggable3 = InheritingTaggableModel.create!(:name => "InheritingTaggable 3")
193
+ @taggable4 = TaggableModel.create!(:name => "Taggable 4")
194
+
195
+ @taggable1.tag_list = "one, two"
196
+ @taggable1.save
197
+
198
+ @taggable2.tag_list = "three, four"
199
+ @taggable2.save
200
+
201
+ @taggable3.tag_list = "one, four"
202
+ @taggable3.save
203
+
204
+ @taggable4.tag_list = "one, two, three, four"
205
+ @taggable4.save
206
+ end
207
+
208
+ it "should find related objects based on tag names on context" do
209
+ @taggable1.find_related_tags.should include(@taggable3)
210
+ @taggable1.find_related_tags.should_not include(@taggable2)
211
+ @taggable1.find_related_tags.should_not include(@taggable4)
212
+
213
+ @taggable1.find_related_tags_for(TaggableModel).should include(@taggable3)
214
+ @taggable1.find_related_tags_for(TaggableModel).should_not include(@taggable2)
215
+ @taggable1.find_related_tags_for(TaggableModel).should include(@taggable4)
216
+ end
217
+
218
+ it "should not include the object itself in the list of related objects" do
219
+ @taggable1.find_related_tags.should_not include(@taggable1)
220
+ @taggable1.find_related_tags_for(InheritingTaggableModel).should_not include(@taggable1)
221
+ @taggable1.find_related_tags_for(TaggableModel).should_not include(@taggable1)
222
+ end
223
+ end
224
+
225
+ end
226
+
227
+ describe "Matching Contexts" do
228
+ it "should find objects with tags of matching contexts" do
229
+ taggable1 = TaggableModel.create!(:name => "Taggable 1")
230
+ taggable2 = TaggableModel.create!(:name => "Taggable 2")
231
+ taggable3 = TaggableModel.create!(:name => "Taggable 3")
232
+
233
+ taggable1.offering_list = "one, two"
234
+ taggable1.save!
235
+
236
+ taggable2.need_list = "one, two"
237
+ taggable2.save!
238
+
239
+ taggable3.offering_list = "one, two"
240
+ taggable3.save!
241
+
242
+ taggable1.find_matching_contexts(:offerings, :needs).should include(taggable2)
243
+ taggable1.find_matching_contexts(:offerings, :needs).should_not include(taggable3)
244
+ end
245
+
246
+ it "should find other related objects with tags of matching contexts" do
247
+ taggable1 = TaggableModel.create!(:name => "Taggable 1")
248
+ taggable2 = OtherTaggableModel.create!(:name => "Taggable 2")
249
+ taggable3 = OtherTaggableModel.create!(:name => "Taggable 3")
250
+
251
+ taggable1.offering_list = "one, two"
252
+ taggable1.save
253
+
254
+ taggable2.need_list = "one, two"
255
+ taggable2.save
256
+
257
+ taggable3.offering_list = "one, two"
258
+ taggable3.save
259
+
260
+ taggable1.find_matching_contexts_for(OtherTaggableModel, :offerings, :needs).should include(taggable2)
261
+ taggable1.find_matching_contexts_for(OtherTaggableModel, :offerings, :needs).should_not include(taggable3)
262
+ end
263
+
264
+ it "should not include the object itself in the list of related objects with tags of matching contexts" do
265
+ taggable1 = TaggableModel.create!(:name => "Taggable 1")
266
+ taggable2 = TaggableModel.create!(:name => "Taggable 2")
267
+
268
+ taggable1.offering_list = "one, two"
269
+ taggable1.need_list = "one, two"
270
+ taggable1.save
271
+
272
+ taggable2.need_list = "one, two"
273
+ taggable2.save
274
+
275
+ taggable1.find_matching_contexts_for(TaggableModel, :offerings, :needs).should include(taggable2)
276
+ taggable1.find_matching_contexts_for(TaggableModel, :offerings, :needs).should_not include(taggable1)
277
+ end
278
+
279
+ context "Inherited Models" do
280
+ before do
281
+ @taggable1 = InheritingTaggableModel.create!(:name => "InheritingTaggable 1")
282
+ @taggable2 = InheritingTaggableModel.create!(:name => "InheritingTaggable 2")
283
+ @taggable3 = InheritingTaggableModel.create!(:name => "InheritingTaggable 3")
284
+ @taggable4 = InheritingTaggableModel.create!(:name => "InheritingTaggable 4")
285
+ @taggable5 = TaggableModel.create!(:name => "Taggable 5")
286
+
287
+ @taggable1.offering_list = "one, two"
288
+ @taggable1.need_list = "one, two"
289
+ @taggable1.save!
290
+
291
+ @taggable2.need_list = "one, two"
292
+ @taggable2.save!
293
+
294
+ @taggable3.offering_list = "one, two"
295
+ @taggable3.save!
296
+
297
+ @taggable4.tag_list = "one, two, three, four"
298
+ @taggable4.save!
299
+
300
+ @taggable5.need_list = "one, two"
301
+ @taggable5.save!
302
+ end
303
+
304
+ it "should find objects with tags of matching contexts" do
305
+ @taggable1.find_matching_contexts(:offerings, :needs).should include(@taggable2)
306
+ @taggable1.find_matching_contexts(:offerings, :needs).should_not include(@taggable3)
307
+ @taggable1.find_matching_contexts(:offerings, :needs).should_not include(@taggable4)
308
+ @taggable1.find_matching_contexts(:offerings, :needs).should_not include(@taggable5)
309
+
310
+ @taggable1.find_matching_contexts_for(TaggableModel, :offerings, :needs).should include(@taggable2)
311
+ @taggable1.find_matching_contexts_for(TaggableModel, :offerings, :needs).should_not include(@taggable3)
312
+ @taggable1.find_matching_contexts_for(TaggableModel, :offerings, :needs).should_not include(@taggable4)
313
+ @taggable1.find_matching_contexts_for(TaggableModel, :offerings, :needs).should include(@taggable5)
314
+ end
315
+
316
+ it "should not include the object itself in the list of related objects with tags of matching contexts" do
317
+ @taggable1.find_matching_contexts(:offerings, :needs).should_not include(@taggable1)
318
+ @taggable1.find_matching_contexts_for(InheritingTaggableModel, :offerings, :needs).should_not include(@taggable1)
319
+ @taggable1.find_matching_contexts_for(TaggableModel, :offerings, :needs).should_not include(@taggable1)
320
+ end
321
+ end
322
+ end
323
+
324
+ describe 'Tagging Contexts' do
325
+ it 'should eliminate duplicate tagging contexts ' do
326
+ TaggableModel.acts_as_taggable_on(:skills, :skills)
327
+ TaggableModel.tag_types.freq[:skills].should_not == 3
328
+ end
329
+
330
+ it "should not contain embedded/nested arrays" do
331
+ TaggableModel.acts_as_taggable_on([:array], [:array])
332
+ TaggableModel.tag_types.freq[[:array]].should == 0
333
+ end
334
+
335
+ it "should _flatten_ the content of arrays" do
336
+ TaggableModel.acts_as_taggable_on([:array], [:array])
337
+ TaggableModel.tag_types.freq[:array].should == 1
338
+ end
339
+
340
+ it "should not raise an error when passed nil" do
341
+ lambda {
342
+ TaggableModel.acts_as_taggable_on()
343
+ }.should_not raise_error
344
+ end
345
+
346
+ it "should not raise an error when passed [nil]" do
347
+ lambda {
348
+ TaggableModel.acts_as_taggable_on([nil])
349
+ }.should_not raise_error
350
+ end
351
+ end
352
+
353
+ describe 'Caching' do
354
+ before(:each) do
355
+ @taggable = CachedModel.new(:name => "Bob Jones")
356
+ @another_taggable = OtherCachedModel.new(:name => "John Smith")
357
+ end
358
+
359
+ it "should add saving of tag lists and cached tag lists to the instance" do
360
+ @taggable.should respond_to(:save_cached_tag_list)
361
+ @another_taggable.should respond_to(:save_cached_tag_list)
362
+
363
+ @taggable.should respond_to(:save_tags)
364
+ end
365
+
366
+ it "should add cached tag lists to the instance if cached column is not present" do
367
+ TaggableModel.new(:name => "Art Kram").should_not respond_to(:save_cached_tag_list)
368
+ end
369
+
370
+ it "should generate a cached column checker for each tag type" do
371
+ CachedModel.should respond_to(:caching_tag_list?)
372
+ OtherCachedModel.should respond_to(:caching_language_list?)
373
+ end
374
+
375
+ it 'should not have cached tags' do
376
+ @taggable.cached_tag_list.should be_blank
377
+ @another_taggable.cached_language_list.should be_blank
378
+ end
379
+
380
+ it 'should cache tags' do
381
+ @taggable.update_attributes(:tag_list => 'awesome, epic')
382
+ @taggable.cached_tag_list.should == 'awesome, epic'
383
+
384
+ @another_taggable.update_attributes(:language_list => 'ruby, .net')
385
+ @another_taggable.cached_language_list.should == 'ruby, .net'
386
+ end
387
+
388
+ it 'should keep the cache' do
389
+ @taggable.update_attributes(:tag_list => 'awesome, epic')
390
+ @taggable = CachedModel.find(@taggable)
391
+ @taggable.save!
392
+ @taggable.cached_tag_list.should == 'awesome, epic'
393
+ end
394
+
395
+ it 'should update the cache' do
396
+ @taggable.update_attributes(:tag_list => 'awesome, epic')
397
+ @taggable.update_attributes(:tag_list => 'awesome')
398
+ @taggable.cached_tag_list.should == 'awesome'
399
+ end
400
+
401
+ it 'should remove the cache' do
402
+ @taggable.update_attributes(:tag_list => 'awesome, epic')
403
+ @taggable.update_attributes(:tag_list => '')
404
+ @taggable.cached_tag_list.should be_blank
405
+ end
406
+
407
+ it 'should have a tag list' do
408
+ @taggable.update_attributes(:tag_list => 'awesome, epic')
409
+ @taggable = CachedModel.find(@taggable.id)
410
+ @taggable.tag_list.sort.should == %w(awesome epic).sort
411
+ end
412
+
413
+ it 'should keep the tag list' do
414
+ @taggable.update_attributes(:tag_list => 'awesome, epic')
415
+ @taggable = CachedModel.find(@taggable.id)
416
+ @taggable.save!
417
+ @taggable.tag_list.sort.should == %w(awesome epic).sort
418
+ end
419
+ end
420
+
421
+ context 'when tagging context ends in an "s" when singular (ex. "status", "glass", etc.)' do
422
+ describe 'caching' do
423
+ before { @taggable = OtherCachedModel.new(:name => "John Smith") }
424
+ subject { @taggable }
425
+
426
+ it { should respond_to(:save_cached_tag_list) }
427
+ its(:cached_language_list) { should be_blank }
428
+ its(:cached_status_list) { should be_blank }
429
+ its(:cached_glass_list) { should be_blank }
430
+
431
+ context 'language taggings cache after update' do
432
+ before { @taggable.update_attributes(:language_list => 'ruby, .net') }
433
+ subject { @taggable }
434
+
435
+ its(:language_list) { should == ['ruby', '.net']}
436
+ its(:cached_language_list) { should == 'ruby, .net' } # passes
437
+ its(:instance_variables) { should include((RUBY_VERSION < '1.9' ? '@language_list' : :@language_list)) }
438
+ end
439
+
440
+ context 'status taggings cache after update' do
441
+ before { @taggable.update_attributes(:status_list => 'happy, married') }
442
+ subject { @taggable }
443
+
444
+ its(:status_list) { should == ['happy', 'married'] }
445
+ its(:cached_status_list) { should == 'happy, married' } # fails
446
+ its(:cached_status_list) { should_not == '' } # fails, is blank
447
+ its(:instance_variables) { should include((RUBY_VERSION < '1.9' ? '@status_list' : :@status_list)) }
448
+ its(:instance_variables) { should_not include((RUBY_VERSION < '1.9' ? '@statu_list' : :@statu_list)) } # fails, note: one "s"
449
+
450
+ end
451
+
452
+ context 'glass taggings cache after update' do
453
+ before do
454
+ @taggable.update_attributes(:glass_list => 'rectangle, aviator')
455
+ end
456
+
457
+ subject { @taggable }
458
+ its(:glass_list) { should == ['rectangle', 'aviator'] }
459
+ its(:cached_glass_list) { should == 'rectangle, aviator' } # fails
460
+ its(:cached_glass_list) { should_not == '' } # fails, is blank
461
+ if RUBY_VERSION < '1.9'
462
+ its(:instance_variables) { should include('@glass_list') }
463
+ its(:instance_variables) { should_not include('@glas_list') } # fails, note: one "s"
464
+ else
465
+ its(:instance_variables) { should include(:@glass_list) }
466
+ its(:instance_variables) { should_not include(:@glas_list) } # fails, note: one "s"
467
+ end
468
+
469
+ end
470
+ end
471
+ end
472
+
473
+ describe "taggings" do
474
+ before(:each) do
475
+ @taggable = TaggableModel.new(:name => "Art Kram")
476
+ end
477
+
478
+ it 'should return [] taggings' do
479
+ @taggable.taggings.should == []
480
+ end
481
+ end
482
+
483
+ describe "@@remove_unused_tags" do
484
+ before do
485
+ @taggable = TaggableModel.create(:name => "Bob Jones")
486
+ @tag = ActsAsTaggableOn::Tag.create(:name => "awesome")
487
+
488
+ @tagging = ActsAsTaggableOn::Tagging.create(:taggable => @taggable, :tag => @tag, :context => 'tags')
489
+ end
490
+
491
+ context "if set to true" do
492
+ before do
493
+ ActsAsTaggableOn.remove_unused_tags = true
494
+ end
495
+
496
+ it "should remove unused tags after removing taggings" do
497
+ @tagging.destroy
498
+ ActsAsTaggableOn::Tag.find_by_name("awesome").should be_nil
499
+ end
500
+ end
501
+
502
+ context "if set to false" do
503
+ before do
504
+ ActsAsTaggableOn.remove_unused_tags = false
505
+ end
506
+
507
+ it "should not remove unused tags after removing taggings" do
508
+ @tagging.destroy
509
+ ActsAsTaggableOn::Tag.find_by_name("awesome").should == @tag
510
+ end
511
+ end
512
+ end
513
+
514
+ end