crowdint_acts-as-taggable-on 2.3.2

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.
Files changed (45) hide show
  1. data/.gitignore +10 -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 +250 -0
  9. data/Rakefile +13 -0
  10. data/acts-as-taggable-on.gemspec +28 -0
  11. data/lib/acts-as-taggable-on.rb +59 -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 +349 -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 +99 -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 +77 -0
  20. data/lib/acts_as_taggable_on/tag_list.rb +97 -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 +93 -0
  32. data/spec/acts_as_taggable_on/tag_spec.rb +153 -0
  33. data/spec/acts_as_taggable_on/taggable_spec.rb +543 -0
  34. data/spec/acts_as_taggable_on/tagger_spec.rb +112 -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 +49 -0
  42. data/spec/schema.rb +61 -0
  43. data/spec/spec_helper.rb +83 -0
  44. data/uninstall.rb +1 -0
  45. metadata +240 -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 o index lenght for myisam table type.
16
+ # 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
data/rails/init.rb ADDED
@@ -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