tagtical 1.0.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.
Files changed (41) hide show
  1. data/CHANGELOG +25 -0
  2. data/Gemfile +20 -0
  3. data/Gemfile.lock +25 -0
  4. data/MIT-LICENSE +20 -0
  5. data/README.rdoc +306 -0
  6. data/Rakefile +59 -0
  7. data/VERSION +1 -0
  8. data/generators/tagtical_migration/tagtical_migration_generator.rb +7 -0
  9. data/generators/tagtical_migration/templates/migration.rb +34 -0
  10. data/lib/generators/tagtical/migration/migration_generator.rb +32 -0
  11. data/lib/generators/tagtical/migration/templates/active_record/migration.rb +35 -0
  12. data/lib/tagtical/acts_as_tagger.rb +69 -0
  13. data/lib/tagtical/compatibility/Gemfile +8 -0
  14. data/lib/tagtical/compatibility/active_record_backports.rb +21 -0
  15. data/lib/tagtical/tag.rb +314 -0
  16. data/lib/tagtical/tag_list.rb +133 -0
  17. data/lib/tagtical/taggable/cache.rb +53 -0
  18. data/lib/tagtical/taggable/collection.rb +141 -0
  19. data/lib/tagtical/taggable/core.rb +317 -0
  20. data/lib/tagtical/taggable/ownership.rb +110 -0
  21. data/lib/tagtical/taggable/related.rb +60 -0
  22. data/lib/tagtical/taggable.rb +51 -0
  23. data/lib/tagtical/tagging.rb +42 -0
  24. data/lib/tagtical/tags_helper.rb +17 -0
  25. data/lib/tagtical.rb +47 -0
  26. data/rails/init.rb +1 -0
  27. data/spec/bm.rb +53 -0
  28. data/spec/database.yml +17 -0
  29. data/spec/database.yml.sample +17 -0
  30. data/spec/models.rb +60 -0
  31. data/spec/schema.rb +46 -0
  32. data/spec/spec_helper.rb +159 -0
  33. data/spec/tagtical/acts_as_tagger_spec.rb +94 -0
  34. data/spec/tagtical/tag_list_spec.rb +102 -0
  35. data/spec/tagtical/tag_spec.rb +301 -0
  36. data/spec/tagtical/taggable_spec.rb +460 -0
  37. data/spec/tagtical/tagger_spec.rb +76 -0
  38. data/spec/tagtical/tagging_spec.rb +52 -0
  39. data/spec/tagtical/tags_helper_spec.rb +28 -0
  40. data/spec/tagtical/tagtical_spec.rb +340 -0
  41. metadata +132 -0
@@ -0,0 +1,460 @@
1
+ require File.expand_path('../../spec_helper', __FILE__)
2
+ describe Tagtical::Taggable do
3
+ before do
4
+ clean_database!
5
+ @taggable = TaggableModel.new(:name => "Bob Jones")
6
+ @taggables = [@taggable, TaggableModel.new(:name => "John Doe")]
7
+ end
8
+
9
+ it "should have tag types" do
10
+ TaggableModel.tag_types.should include("tag", "language", "skill", "craft", "need", "offering")
11
+ @taggable.tag_types.should == TaggableModel.tag_types
12
+ end
13
+
14
+ it "should have tag_counts_on" do
15
+ TaggableModel.tag_counts_on(:tags).all.should be_empty
16
+
17
+ @taggable.tag_list = ["awesome", "epic"]
18
+ @taggable.save
19
+
20
+ TaggableModel.tag_counts_on(:tags).length.should == 2
21
+ @taggable.tag_counts_on(:tags).length.should == 2
22
+ end
23
+
24
+ it "should be able to create tags" do
25
+ @taggable.skill_list = "ruby, rails, css"
26
+ @taggable.instance_variable_get("@skill_list").should be_an_instance_of(Tagtical::TagList)
27
+
28
+ lambda { @taggable.save }.should change(Tagtical::Tag, :count).by(3)
29
+
30
+ @taggable.reload
31
+ @taggable.skill_list.sort.should == %w(ruby rails css).sort
32
+ @taggable.tag_list.sort.should == %w(ruby rails css).sort
33
+ end
34
+
35
+ it "should differentiate between contexts" do
36
+ @taggable.skill_list = "ruby, rails, css"
37
+ @taggable.tag_list = "ruby, bob, charlie"
38
+ @taggable.save
39
+ @taggable.reload
40
+ @taggable.skill_list.should include("ruby")
41
+ @taggable.skill_list.should_not include("bob")
42
+ end
43
+
44
+ it "should be able to remove tags through list alone" do
45
+ @taggable.skill_list = "ruby, rails, css"
46
+ @taggable.save
47
+ @taggable.reload
48
+ @taggable.should have(3).skills
49
+ @taggable.skill_list = "ruby, rails"
50
+ @taggable.save
51
+ @taggable.reload
52
+ @taggable.should have(2).skills
53
+ end
54
+
55
+ describe "tag retrieval with finder type conditions" do
56
+ before do
57
+ @taggables[0].tag_list = "bob"
58
+ @taggables[1].tag_list = "charlie"
59
+ @taggables[0].skill_list = "ruby"
60
+ @taggables[1].skill_list = "css"
61
+ @taggables[0].craft_list = "knitting"
62
+ @taggables[1].craft_list = "pottery"
63
+ @taggables.each(&:save!)
64
+ end
65
+
66
+ it "should be able to query tags" do
67
+ @taggables[0].tags(:only => :current).should have_tag_values %w{bob}
68
+ @taggables[0].tags.should have_tag_values %w{bob knitting ruby}
69
+ @taggables[0].tags(:only => :children).should have_tag_values %w{knitting ruby}
70
+ @taggables[1].crafts(:only => :parents).should have_tag_values %w{charlie css}
71
+ @taggables[1].crafts(:only => [:parents, :current]).should have_tag_values %w{charlie css pottery}
72
+ @taggables[1].skills(:only => [:parents, :children]).should have_tag_values %w{charlie pottery}
73
+ end
74
+
75
+ it "should be able to select taggables by subset of tags using ActiveRelation methods" do
76
+ TaggableModel.with_tags("bob").should == [@taggables[0]]
77
+ TaggableModel.with_skills("ruby").should == [@taggables[0]]
78
+ TaggableModel.with_tags("rUBy").should == [@taggables[0]]
79
+ TaggableModel.with_tags("ruby", :only => :current).should == []
80
+ TaggableModel.with_skills("knitting").should == [@taggables[0]]
81
+ TaggableModel.with_skills("KNITTING", :only => :current).should == []
82
+ TaggableModel.with_skills("knitting", :only => :parents).should == []
83
+ TaggableModel.with_tags("bob", :only => :current).should == [@taggables[0]]
84
+ TaggableModel.with_skills("bob", :only => :parents).should == [@taggables[0]]
85
+ TaggableModel.with_crafts("knitting").should == [@taggables[0]]
86
+ end
87
+ end
88
+
89
+ it "should be able to find by tag" do
90
+ @taggable.skill_list = "ruby, rails, css"
91
+ @taggable.save
92
+
93
+ TaggableModel.tagged_with("ruby").first.should == @taggable
94
+ end
95
+
96
+ it "should be able to find by tag with context" do
97
+ @taggable.skill_list = "ruby, rails, css"
98
+ @taggable.tag_list = "bob, charlie"
99
+ @taggable.save
100
+
101
+ TaggableModel.tagged_with("ruby").first.should == @taggable
102
+ TaggableModel.tagged_with("ruby, css").first.should == @taggable
103
+ TaggableModel.tagged_with("bob", :on => :skills).first.should_not == @taggable
104
+ TaggableModel.tagged_with("bob", :on => :tags).first.should == @taggable
105
+ end
106
+
107
+ it "should be able to search by tag type" do
108
+ TaggableModel.create!(:name => "Ted", :skill_list => "ruby")
109
+ TaggableModel.create!(:name => "Tom", :skill_list => "ruby, rails, css")
110
+ TaggableModel.create!(:name => "Fiona", :skill_list => "html, ruby, rails, css")
111
+
112
+ TaggableModel.tagged_with("ruby", :on => :skills).sort_by(&:id).should == TaggableModel.with_skills("ruby").sort_by(&:id)
113
+ TaggableModel.tagged_with(["ruby", "rails", "css"], :on => :skills).sort_by(&:id).should == TaggableModel.with_skills("ruby", "rails", "css").sort_by(&:id)
114
+ TaggableModel.with_skills("ruby", "rails").should have(2).items
115
+ end
116
+
117
+ it "should not duplicate tags" do
118
+ @taggable = TaggableModel.create!(:name => "Gary", :skill_list => ["Ruby", "ruby", "RUBY", "rails"])
119
+
120
+ @taggable.skill_list.should have(2).item
121
+ end
122
+
123
+ describe "Tag Scope" do
124
+ it "should proxy argument from tag scope to tagged_with" do
125
+ { ["ruby", "rails", {:any => true}] => [['ruby', 'rails'], {:any => true, :on => :skill}],
126
+ ["ruby", "rails"] => [['ruby', 'rails'], {:on => :skill}],
127
+ [] => [[], {:on => :skill}],
128
+ [["ruby", "rails"]] => [['ruby', 'rails'], {:on => :skill}]
129
+ }.each do |input, output|
130
+ TaggableModel.expects(:tagged_with).with(*output)
131
+ TaggableModel.with_skills(*input)
132
+ end
133
+ end
134
+ end
135
+
136
+ it "should not care about case" do
137
+ bob = TaggableModel.create!(:name => "Bob", :tag_list => "ruby")
138
+ frank = TaggableModel.create!(:name => "Frank", :tag_list => "Ruby")
139
+
140
+ Tagtical::Tag.find(:all).size.should == 1
141
+ TaggableModel.tagged_with("ruby").to_a.should == TaggableModel.tagged_with("Ruby").to_a
142
+ end
143
+
144
+ it "should be able to get tag counts on model as a whole" do
145
+ bob = TaggableModel.create(:name => "Bob", :tag_list => "ruby, rails, css")
146
+ frank = TaggableModel.create(:name => "Frank", :tag_list => "ruby, rails")
147
+ charlie = TaggableModel.create(:name => "Charlie", :skill_list => "ruby")
148
+ TaggableModel.tag_counts.all.should_not be_empty
149
+ TaggableModel.skill_counts.all.should_not be_empty
150
+ end
151
+
152
+ it "should be able to get all tag counts on model as whole" do
153
+ bob = TaggableModel.create(:name => "Bob", :tag_list => "ruby, rails, css")
154
+ frank = TaggableModel.create(:name => "Frank", :tag_list => "ruby, rails")
155
+ charlie = TaggableModel.create(:name => "Charlie", :skill_list => "ruby")
156
+
157
+ TaggableModel.all_tag_counts.all.should_not be_empty
158
+ TaggableModel.all_tag_counts(:order => 'tags.id').map { |tag| [tag.class, tag.value, tag.count] }.should == [
159
+ [Tagtical::Tag, "ruby", 2],
160
+ [Tagtical::Tag, "rails", 2],
161
+ [Tagtical::Tag, "css", 1],
162
+ [Tag::Skill, "ruby", 1] ]
163
+ end
164
+
165
+ if ActiveRecord::VERSION::MAJOR >= 3
166
+ it "should not return read-only records" do
167
+ TaggableModel.create(:name => "Bob", :tag_list => "ruby, rails, css")
168
+ TaggableModel.tagged_with("ruby").first.should_not be_readonly
169
+ end
170
+ else
171
+ it "should not return read-only records" do
172
+ # apparantly, there is no way to set readonly to false in a scope if joins are made
173
+ end
174
+
175
+ it "should be possible to return writable records" do
176
+ TaggableModel.create(:name => "Bob", :tag_list => "ruby, rails, css")
177
+ TaggableModel.tagged_with("ruby").first(:readonly => false).should_not be_readonly
178
+ end
179
+ end
180
+
181
+ context "with inheriting tags classes" do
182
+ before do
183
+ @top_level = Tagtical::Tag
184
+ @second_level = Tag::Skill
185
+ @third_level = Tag::Craft
186
+ end
187
+
188
+ it "should not create tags on parent if children have the value" do
189
+ lambda {
190
+ @taggable.skill_list = "pottery"
191
+ @taggable.save!
192
+ @taggable.reload
193
+ @taggable.craft_list = "pottery"
194
+ @taggable.save!
195
+ }.should change(Tagtical::Tagging, :count).by(1)
196
+
197
+ @taggable.reload
198
+ @taggable.skills.should have(1).item
199
+ @taggable.skills.first.should be_an_instance_of Tag::Craft
200
+ end
201
+
202
+ end
203
+
204
+ context "with multiple taggable models" do
205
+
206
+ before do
207
+ @bob = TaggableModel.create(:name => "Bob", :tag_list => "ruby, rails, css")
208
+ @frank = TaggableModel.create(:name => "Frank", :tag_list => "ruby, rails")
209
+ @charlie = TaggableModel.create(:name => "Charlie", :skill_list => "ruby, java")
210
+ end
211
+
212
+ RSpec::Matchers.define :have_tags_counts_of do |expected|
213
+ def breakdown(tags)
214
+ tags.map { |tag| [tag.class, tag.value, tag.count] }
215
+ end
216
+ match do |actual|
217
+ breakdown(actual) == expected
218
+ end
219
+ failure_message_for_should do |actual|
220
+ "expected #{breakdown(actual)} to have the breakdown #{expected}"
221
+ end
222
+ end
223
+
224
+ it "should be able to get scoped tag counts" do
225
+ TaggableModel.tagged_with("ruby").tag_counts(:order => 'tags.id').should have_tags_counts_of [
226
+ [Tagtical::Tag, "ruby", 2],
227
+ [Tagtical::Tag, "rails", 2],
228
+ [Tagtical::Tag, "css", 1],
229
+ [Tag::Skill, "ruby", 1],
230
+ [Tag::Skill, "java", 1] ]
231
+ TaggableModel.tagged_with("ruby").skill_counts.first.count.should == 1 # ruby
232
+ end
233
+
234
+ it "should be able to get all scoped tag counts" do
235
+ TaggableModel.tagged_with("ruby").all_tag_counts(:order => 'tags.id').should have_tags_counts_of [
236
+ [Tagtical::Tag, "ruby", 2],
237
+ [Tagtical::Tag, "rails", 2],
238
+ [Tagtical::Tag, "css", 1],
239
+ [Tag::Skill, "ruby", 1],
240
+ [Tag::Skill, "java", 1] ]
241
+ end
242
+
243
+ it 'should only return tag counts for the available scope' do
244
+ TaggableModel.tagged_with('rails').all_tag_counts.should have_tags_counts_of [
245
+ [Tagtical::Tag, "ruby", 2],
246
+ [Tagtical::Tag, "rails", 2],
247
+ [Tagtical::Tag, "css", 1]]
248
+ TaggableModel.tagged_with('rails').all_tag_counts.any? { |tag| tag.value == 'java' }.should be_false
249
+
250
+ # Test specific join syntaxes:
251
+ @frank.untaggable_models.create!
252
+ TaggableModel.tagged_with('rails').scoped(:joins => :untaggable_models).all_tag_counts.should have(2).items
253
+ TaggableModel.tagged_with('rails').scoped(:joins => {:untaggable_models => :taggable_model }).all_tag_counts.should have(2).items
254
+ TaggableModel.tagged_with('rails').scoped(:joins => [:untaggable_models]).all_tag_counts.should have(2).items
255
+ end
256
+ end
257
+
258
+ it "should be able to find tagged with quotation marks" do
259
+ bob = TaggableModel.create(:name => "Bob", :tag_list => "fitter, happier, more productive, 'I love the ,comma,'")
260
+ TaggableModel.tagged_with("'I love the ,comma,'").should include(bob)
261
+ end
262
+
263
+ it "should be able to find tagged with invalid tags" do
264
+ bob = TaggableModel.create(:name => "Bob", :tag_list => "fitter, happier, more productive")
265
+ TaggableModel.tagged_with("sad, happier").should_not include(bob)
266
+ end
267
+
268
+ context "with multiple tag lists per taggable model" do
269
+ before do
270
+ @bob = TaggableModel.create(:name => "Bob", :tag_list => "fitter, happier, more productive", :skill_list => "ruby, rails, css")
271
+ @frank = TaggableModel.create(:name => "Frank", :tag_list => "weaker, depressed, inefficient", :skill_list => "ruby, rails, css")
272
+ @steve = TaggableModel.create(:name => 'Steve', :tag_list => 'fitter, happier, more productive', :skill_list => 'c++, java, ruby')
273
+ end
274
+
275
+ it "should be able to find tagged" do
276
+ TaggableModel.tagged_with("ruby", :order => 'taggable_models.name').to_a.should == [@bob, @frank, @steve]
277
+ TaggableModel.tagged_with("ruby, rails", :order => 'taggable_models.name').to_a.should == [@bob, @frank]
278
+ TaggableModel.tagged_with(["ruby", "rails"], :order => 'taggable_models.name').to_a.should == [@bob, @frank]
279
+ end
280
+
281
+ it "should be able to find tagged with any tag" do
282
+ TaggableModel.tagged_with(["ruby", "java"], :order => 'taggable_models.name', :any => true).to_a.should == [@bob, @frank, @steve]
283
+ TaggableModel.tagged_with(["c++", "fitter"], :order => 'taggable_models.name', :any => true).to_a.should == [@bob, @steve]
284
+ TaggableModel.tagged_with(["fitter", "css"], :order => 'taggable_models.name', :any => true, :on => :skills).to_a.should == [@bob, @frank]
285
+ end
286
+
287
+ it "should be able to use named scopes to chain tag finds" do
288
+ # Let's only find those productive Rails developers
289
+ TaggableModel.tagged_with('rails', :on => :skills, :order => 'taggable_models.name').to_a.should == [@bob, @frank]
290
+ TaggableModel.tagged_with('happier', :on => :tags, :order => 'taggable_models.name').to_a.should == [@bob, @steve]
291
+ TaggableModel.tagged_with('rails', :on => :skills).tagged_with('happier', :on => :tags).to_a.should == [@bob]
292
+ TaggableModel.tagged_with('rails').tagged_with('happier', :on => :tags).to_a.should == [@bob]
293
+ end
294
+ end
295
+
296
+ it "should be able to find tagged with only the matching tags" do
297
+ bob = TaggableModel.create(:name => "Bob", :tag_list => "lazy, happier")
298
+ frank = TaggableModel.create(:name => "Frank", :tag_list => "fitter, happier, inefficient")
299
+ steve = TaggableModel.create(:name => 'Steve', :tag_list => "fitter, happier")
300
+ TaggableModel.tagged_with("fitter, happier", :match_all => true).to_a.should == [steve]
301
+ end
302
+
303
+ it "should be able to find tagged with some excluded tags" do
304
+ bob = TaggableModel.create(:name => "Bob", :tag_list => "happier, lazy")
305
+ frank = TaggableModel.create(:name => "Frank", :tag_list => "happier")
306
+ steve = TaggableModel.create(:name => 'Steve', :tag_list => "happier")
307
+
308
+ TaggableModel.tagged_with("lazy", :exclude => true).to_a.should == [frank, steve]
309
+ end
310
+
311
+ it "should not create duplicate taggings" do
312
+ bob = TaggableModel.create(:name => "Bob")
313
+ lambda {
314
+ bob.tag_list << "happier"
315
+ bob.tag_list << "happier"
316
+ bob.save
317
+ }.should change(Tagtical::Tagging, :count).by(1)
318
+ end
319
+
320
+ describe "Associations" do
321
+ before(:each) do
322
+ @taggable = TaggableModel.create(:tag_list => "awesome, epic")
323
+ end
324
+
325
+ it "should not remove tags when creating associated objects" do
326
+ @taggable.untaggable_models.create!
327
+ @taggable.reload
328
+ @taggable.tag_list.should have(2).items
329
+ end
330
+ end
331
+
332
+ describe "grouped_column_names_for method" do
333
+ it "should return all column names joined for Tag GROUP clause" do
334
+ @taggable.grouped_column_names_for(Tagtical::Tag).should == "tags.id, tags.value, tags.type"
335
+ end
336
+
337
+ it "should return all column names joined for TaggableModel GROUP clause" do
338
+ @taggable.grouped_column_names_for(TaggableModel).should == "taggable_models.id, taggable_models.name, taggable_models.type"
339
+ end
340
+ end
341
+
342
+ describe "Single Table Inheritance for tags" do
343
+ before do
344
+ @taggable = TaggableModel.new(:name => "taggable")
345
+ end
346
+
347
+ end
348
+
349
+ describe "Single Table Inheritance" do
350
+ before do
351
+ @taggable = TaggableModel.new(:name => "taggable")
352
+ @inherited_same = InheritingTaggableModel.new(:name => "inherited same")
353
+ @inherited_different = AlteredInheritingTaggableModel.new(:name => "inherited different")
354
+ end
355
+
356
+ it "should be able to save tags for inherited models" do
357
+ @inherited_same.tag_list = "bob, kelso"
358
+ @inherited_same.save
359
+ InheritingTaggableModel.tagged_with("bob").first.should == @inherited_same
360
+ end
361
+
362
+ it "should find STI tagged models on the superclass" do
363
+ @inherited_same.tag_list = "bob, kelso"
364
+ @inherited_same.save
365
+ TaggableModel.tagged_with("bob").first.should == @inherited_same
366
+ end
367
+
368
+ it "should be able to add on contexts only to some subclasses" do
369
+ @inherited_different.part_list = "fork, spoon"
370
+ @inherited_different.save
371
+ InheritingTaggableModel.tagged_with("fork", :on => :parts).should be_empty
372
+ AlteredInheritingTaggableModel.tagged_with("fork", :on => :parts).first.should == @inherited_different
373
+ end
374
+
375
+ it "should have different tag_counts_on for inherited models" do
376
+ @inherited_same.tag_list = "bob, kelso"
377
+ @inherited_same.save!
378
+ @inherited_different.tag_list = "fork, spoon"
379
+ @inherited_different.save!
380
+
381
+ InheritingTaggableModel.tag_counts_on(:tags, :order => 'tags.id').map(&:value).should == %w(bob kelso)
382
+ AlteredInheritingTaggableModel.tag_counts_on(:tags, :order => 'tags.id').map(&:value).should == %w(fork spoon)
383
+ TaggableModel.tag_counts_on(:tags, :order => 'tags.id').map(&:value).should == %w(bob kelso fork spoon)
384
+ end
385
+
386
+ it 'should store same tag without validation conflict' do
387
+ @taggable.tag_list = 'one'
388
+ @taggable.save!
389
+
390
+ @inherited_same.tag_list = 'one'
391
+ @inherited_same.save!
392
+
393
+ @inherited_same.update_attributes! :name => 'foo'
394
+ end
395
+ end
396
+
397
+ describe "#owner_tags_on" do
398
+ before do
399
+ @user = TaggableUser.create!
400
+ @user1 = TaggableUser.create!
401
+ @model = TaggableModel.create!(:name => "Bob", :tag_list => "fitter, happier, more productive")
402
+ @user.tag(@model, :with => "martial arts", :on => :skills)
403
+ @user1.tag(@model, :with => "pottery", :on => :crafts)
404
+ @user1.tag(@model, :with => ["spoon", "pottery"], :on => :tags)
405
+ end
406
+
407
+ it "should ignore different contexts" do
408
+ @model.owner_tags_on(@user, :languages).should be_empty
409
+ end
410
+
411
+ it "should return for only the specified context" do
412
+ @model.owner_tags_on(@user, :skills).should have(1).items
413
+
414
+ @model.owner_tags_on(@user, :tags).should have(1).items
415
+ @model.owner_tags_on(@user1, :tags).should have(2).items
416
+ end
417
+
418
+ it "should preserve the tag type even though we tag on :tags" do
419
+ @model.tags.find_by_value("pottery").should be_an_instance_of(Tag::Craft)
420
+ end
421
+
422
+ it "should support STI" do
423
+ tag = @model.crafts.find_by_value("pottery")
424
+ @model.owner_tags_on(@user1, :crafts).should == [tag]
425
+ @model.owner_tags_on(@user1, :skills).should == [tag]
426
+ @model.owner_tags_on(@user1, :tags).should include(tag)
427
+ end
428
+ end
429
+
430
+ it "should be able to set a custom tag context list" do
431
+ bob = TaggableModel.create(:name => "Bob")
432
+ bob.set_tag_list_on(:rotors, "spinning, jumping")
433
+ bob.tag_list_on(:rotors).should == ["spinning","jumping"]
434
+ bob.save
435
+ bob.reload
436
+ bob.tags_on(:rotors).should_not be_empty
437
+ end
438
+
439
+ it "should be able to create tags through the tag list directly" do
440
+ @taggable.tag_list_on(:test).add("hello")
441
+ @taggable.tag_list_cache_on(:test).should_not be_empty
442
+ @taggable.tag_list_on(:test).should == ["hello"]
443
+
444
+ @taggable.save
445
+ @taggable.save_tags
446
+
447
+ @taggable.reload
448
+ @taggable.tag_list_on(:test).should == ["hello"]
449
+ end
450
+
451
+ it "should be able to find tagged on a custom tag context" do
452
+ bob = TaggableModel.create(:name => "Bob")
453
+ bob.set_tag_list_on(:rotors, "spinning, jumping")
454
+ bob.tag_list_on(:rotors).should == ["spinning","jumping"]
455
+ bob.save
456
+
457
+ TaggableModel.tagged_with("spinning", :on => :rotors).to_a.should == [bob]
458
+ end
459
+
460
+ end
@@ -0,0 +1,76 @@
1
+ require File.expand_path('../../spec_helper', __FILE__)
2
+
3
+ describe "Tagger" do
4
+ before(:each) do
5
+ clean_database!
6
+ @user = TaggableUser.create
7
+ @taggable = TaggableModel.create(:name => "Bob Jones")
8
+ end
9
+
10
+ it "should have taggings" do
11
+ @user.tag(@taggable, :with=>'ruby,scheme', :on=>:tags)
12
+ @user.owned_taggings.size == 2
13
+ end
14
+
15
+ it "should have tags" do
16
+ @user.tag(@taggable, :with=>'ruby,scheme', :on=>:tags)
17
+ @user.owned_tags.size == 2
18
+ end
19
+
20
+ it "should not overlap tags from different taggers" 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(Tagtical::Tagging, :count).by(6)
26
+
27
+ [@user, @user2, @taggable].each(&:reload)
28
+
29
+ @user.owned_tags.map(&:value).sort.should == %w(ruby scheme).sort
30
+ @user2.owned_tags.map(&:value).sort.should == %w(java python lisp ruby).sort
31
+
32
+ @taggable.tags_from(@user).sort.should == %w(ruby scheme).sort
33
+ @taggable.tags_from(@user2).sort.should == %w(java lisp python ruby).sort
34
+
35
+ @taggable.all_tags_list.sort.should == %w(ruby scheme java python lisp).sort
36
+ @taggable.all_tags_on(:tags).size.should == 5
37
+ end
38
+
39
+ it "should not lose tags from different taggers" do
40
+ @user2 = TaggableUser.create
41
+ @user2.tag(@taggable, :with => 'java, python, lisp, ruby', :on => :tags)
42
+ @user.tag(@taggable, :with => 'ruby, scheme', :on => :tags)
43
+
44
+ lambda {
45
+ @user2.tag(@taggable, :with => 'java, python, lisp', :on => :tags)
46
+ }.should change(Tagtical::Tagging, :count).by(-1)
47
+
48
+ [@user, @user2, @taggable].each(&:reload)
49
+
50
+ @taggable.tags_from(@user).sort.should == %w(ruby scheme).sort
51
+ @taggable.tags_from(@user2).sort.should == %w(java python lisp).sort
52
+
53
+ @taggable.all_tags_list.sort.should == %w(ruby scheme java python lisp).sort
54
+ @taggable.all_tags_on(:tags).length.should == 5
55
+ end
56
+
57
+ it "should not lose tags" do
58
+ @user2 = TaggableUser.create
59
+
60
+ @user.tag(@taggable, :with => 'awesome', :on => :tags)
61
+ @user2.tag(@taggable, :with => 'awesome, epic', :on => :tags)
62
+
63
+ lambda {
64
+ @user2.tag(@taggable, :with => 'epic', :on => :tags)
65
+ }.should change(Tagtical::Tagging, :count).by(-1)
66
+
67
+ @taggable.reload
68
+ @taggable.all_tags_list.should include('awesome')
69
+ @taggable.all_tags_list.should include('epic')
70
+ end
71
+
72
+ it "is tagger" do
73
+ @user.is_tagger?.should be_true
74
+ end
75
+
76
+ end
@@ -0,0 +1,52 @@
1
+ require File.expand_path('../../spec_helper', __FILE__)
2
+
3
+ describe Tagtical::Tagging do
4
+ before(:each) do
5
+ clean_database!
6
+ @klass = Tagtical::Tagging
7
+ @tagging = @klass.new
8
+ end
9
+ subject { @tagging }
10
+
11
+ describe "#before_create" do
12
+ context "when no relevance set" do
13
+ before do
14
+ @tagging.relevance = nil
15
+ @tagging.run_callbacks(:create)
16
+ end
17
+ its(:relevance) { should == @klass.default_relevance }
18
+ end
19
+ context "when relevance set" do
20
+ before { @tagging.run_callbacks(:create) }
21
+ its(:relevance) { should == @tagging.relevance }
22
+ end
23
+ end
24
+
25
+ it "should sort by relevance" do
26
+ @taggings = [3.454, 2.3, 6, 3.2].map { |relevance| @klass.new(:relevance => relevance) }
27
+ @taggings.sort.map(&:relevance).should == [2.3, 3.2, 3.454, 6.0]
28
+ end
29
+
30
+ it "should not be valid with a invalid tag" do
31
+ @tagging.taggable = TaggableModel.create(:name => "Bob Jones")
32
+ @tagging.tag = Tagtical::Tag.new(:value => "")
33
+
34
+ @tagging.should_not be_valid
35
+
36
+ if ActiveRecord::VERSION::MAJOR >= 3
37
+ @tagging.errors[:tag_id].should == ["can't be blank"]
38
+ else
39
+ @tagging.errors[:tag_id].should == "can't be blank"
40
+ end
41
+ end
42
+
43
+ it "should not create duplicate taggings" do
44
+ @taggable = TaggableModel.create(:name => "Bob Jones")
45
+ @tag = Tagtical::Tag.create(:value => "awesome")
46
+
47
+ lambda {
48
+ 2.times { @klass.create(:taggable => @taggable, :tag => @tag, :context => 'tags') }
49
+ }.should change(@klass, :count).by(1)
50
+ end
51
+
52
+ end
@@ -0,0 +1,28 @@
1
+ require File.expand_path('../../spec_helper', __FILE__)
2
+
3
+ describe Tagtical::TagsHelper do
4
+ before(:each) do
5
+ clean_database!
6
+
7
+ @bob = TaggableModel.create(:name => "Bob Jones", :language_list => "ruby, php")
8
+ @tom = TaggableModel.create(:name => "Tom Marley", :language_list => "ruby, java")
9
+ @eve = TaggableModel.create(:name => "Eve Nodd", :language_list => "ruby, c++")
10
+
11
+ @helper = class Helper
12
+ include Tagtical::TagsHelper
13
+ end.new
14
+ end
15
+
16
+ it "should yield the proper css classes" do
17
+ tags = { }
18
+
19
+ @helper.tag_cloud(TaggableModel.tag_counts_on(:languages), ["sucky", "awesome"]) do |tag, css_class|
20
+ tags[tag.value] = css_class
21
+ end
22
+
23
+ tags["ruby"].should == "awesome"
24
+ tags["java"].should == "sucky"
25
+ tags["c++"].should == "sucky"
26
+ tags["php"].should == "sucky"
27
+ end
28
+ end