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.
- data/CHANGELOG +25 -0
- data/Gemfile +20 -0
- data/Gemfile.lock +25 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +306 -0
- data/Rakefile +59 -0
- data/VERSION +1 -0
- data/generators/tagtical_migration/tagtical_migration_generator.rb +7 -0
- data/generators/tagtical_migration/templates/migration.rb +34 -0
- data/lib/generators/tagtical/migration/migration_generator.rb +32 -0
- data/lib/generators/tagtical/migration/templates/active_record/migration.rb +35 -0
- data/lib/tagtical/acts_as_tagger.rb +69 -0
- data/lib/tagtical/compatibility/Gemfile +8 -0
- data/lib/tagtical/compatibility/active_record_backports.rb +21 -0
- data/lib/tagtical/tag.rb +314 -0
- data/lib/tagtical/tag_list.rb +133 -0
- data/lib/tagtical/taggable/cache.rb +53 -0
- data/lib/tagtical/taggable/collection.rb +141 -0
- data/lib/tagtical/taggable/core.rb +317 -0
- data/lib/tagtical/taggable/ownership.rb +110 -0
- data/lib/tagtical/taggable/related.rb +60 -0
- data/lib/tagtical/taggable.rb +51 -0
- data/lib/tagtical/tagging.rb +42 -0
- data/lib/tagtical/tags_helper.rb +17 -0
- data/lib/tagtical.rb +47 -0
- data/rails/init.rb +1 -0
- data/spec/bm.rb +53 -0
- data/spec/database.yml +17 -0
- data/spec/database.yml.sample +17 -0
- data/spec/models.rb +60 -0
- data/spec/schema.rb +46 -0
- data/spec/spec_helper.rb +159 -0
- data/spec/tagtical/acts_as_tagger_spec.rb +94 -0
- data/spec/tagtical/tag_list_spec.rb +102 -0
- data/spec/tagtical/tag_spec.rb +301 -0
- data/spec/tagtical/taggable_spec.rb +460 -0
- data/spec/tagtical/tagger_spec.rb +76 -0
- data/spec/tagtical/tagging_spec.rb +52 -0
- data/spec/tagtical/tags_helper_spec.rb +28 -0
- data/spec/tagtical/tagtical_spec.rb +340 -0
- 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
|