tagtical 1.0.6

Sign up to get free protection for your applications and to get access to all the features.
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