taglish 0.1.0

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.
@@ -0,0 +1,72 @@
1
+ ActiveRecord::Schema.define :version => 0 do
2
+ create_table "taggings", :force => true do |t|
3
+ t.integer "tag_id", :limit => 11
4
+ t.integer "taggable_id", :limit => 11
5
+ t.string "taggable_type"
6
+ t.string "context"
7
+ t.datetime "created_at"
8
+ t.integer "tagger_id", :limit => 11
9
+ t.integer "score"
10
+ t.string "tagger_type"
11
+ end
12
+
13
+ add_index "taggings", ["tag_id"], :name => "index_taggings_on_tag_id"
14
+ add_index "taggings", ["taggable_id", "taggable_type", "context"], :name => "index_taggings_on_taggable_id_and_taggable_type_and_context"
15
+
16
+ create_table "tags", :force => true do |t|
17
+ t.string "name"
18
+ end
19
+
20
+ create_table :taggable_models, :force => true do |t|
21
+ t.column :name, :string
22
+ t.column :type, :string
23
+ end
24
+
25
+ create_table :non_standard_id_taggable_models, :primary_key => "an_id", :force => true do |t|
26
+ t.column :name, :string
27
+ t.column :type, :string
28
+ end
29
+
30
+ create_table :untaggable_models, :force => true do |t|
31
+ t.column :taggable_model_id, :integer
32
+ t.column :name, :string
33
+ end
34
+
35
+ create_table :cached_models, :force => true do |t|
36
+ t.column :name, :string
37
+ t.column :type, :string
38
+ t.column :cached_tag_list, :string
39
+ end
40
+
41
+ create_table :other_cached_models, :force => true do |t|
42
+ t.column :name, :string
43
+ t.column :type, :string
44
+ t.column :cached_language_list, :string
45
+ t.column :cached_status_list, :string
46
+ t.column :cached_glass_list, :string
47
+ end
48
+
49
+ create_table :taggable_users, :force => true do |t|
50
+ t.column :name, :string
51
+ end
52
+
53
+ create_table :other_taggable_models, :force => true do |t|
54
+ t.column :name, :string
55
+ t.column :type, :string
56
+ end
57
+
58
+ create_table :ordered_taggable_models, :force => true do |t|
59
+ t.column :name, :string
60
+ t.column :type, :string
61
+ end
62
+
63
+ create_table :mixed_taggable_models, :force => true do |t|
64
+ t.column :name, :string
65
+ t.column :type, :string
66
+ end
67
+
68
+ create_table :scored_taggable_models, :force => true do |t|
69
+ t.column :name, :string
70
+ t.column :type, :string
71
+ end
72
+ end
@@ -0,0 +1,82 @@
1
+ $LOAD_PATH << "." unless $LOAD_PATH.include?(".")
2
+ require 'logger'
3
+
4
+ begin
5
+ require "rubygems"
6
+ require "bundler"
7
+
8
+ if Gem::Version.new(Bundler::VERSION) <= Gem::Version.new("0.9.5")
9
+ raise RuntimeError, "Your bundler version is too old." +
10
+ "Run `gem install bundler` to upgrade."
11
+ end
12
+
13
+ # Set up load paths for all bundled gems
14
+ Bundler.setup
15
+ rescue Bundler::GemNotFound
16
+ raise RuntimeError, "Bundler couldn't find some gems." +
17
+ "Did you run \`bundle install\`?"
18
+ end
19
+
20
+ Bundler.require
21
+ require File.expand_path('../../lib/taglish', __FILE__)
22
+
23
+ unless [].respond_to?(:freq)
24
+ class Array
25
+ def freq
26
+ k=Hash.new(0)
27
+ each {|e| k[e]+=1}
28
+ k
29
+ end
30
+ end
31
+ end
32
+
33
+ # set adapter to use, default is sqlite3
34
+ # to use an alternative adapter run => rake spec DB='postgresql'
35
+ db_name = ENV['DB'] || 'sqlite3'
36
+ database_yml = File.expand_path('../database.yml', __FILE__)
37
+
38
+ if File.exists?(database_yml)
39
+ active_record_configuration = YAML.load_file(database_yml)
40
+
41
+ ActiveRecord::Base.configurations = active_record_configuration
42
+ config = ActiveRecord::Base.configurations[db_name]
43
+
44
+ begin
45
+ ActiveRecord::Base.establish_connection(db_name)
46
+ ActiveRecord::Base.connection
47
+ rescue
48
+ case db_name
49
+ when /mysql/
50
+ ActiveRecord::Base.establish_connection(config.merge('database' => nil))
51
+ ActiveRecord::Base.connection.create_database(config['database'], {:charset => 'utf8', :collation => 'utf8_unicode_ci'})
52
+ when 'postgresql'
53
+ ActiveRecord::Base.establish_connection(config.merge('database' => 'postgres', 'schema_search_path' => 'public'))
54
+ ActiveRecord::Base.connection.create_database(config['database'], config.merge('encoding' => 'utf8'))
55
+ end
56
+
57
+ ActiveRecord::Base.establish_connection(config)
58
+ end
59
+
60
+ ActiveRecord::Base.logger = Logger.new(File.join(File.dirname(__FILE__), "debug.log"))
61
+ ActiveRecord::Base.default_timezone = :utc
62
+
63
+ ActiveRecord::Base.silence do
64
+ ActiveRecord::Migration.verbose = false
65
+
66
+ load(File.dirname(__FILE__) + '/schema.rb')
67
+ load(File.dirname(__FILE__) + '/models.rb')
68
+ end
69
+
70
+ else
71
+ raise "Please create #{database_yml} first to configure your database. Take a look at: #{database_yml}.sample"
72
+ end
73
+
74
+ def clean_database!
75
+ models = [Taglish::Tag, Taglish::Tagging, TaggableModel, OtherTaggableModel, InheritingTaggableModel,
76
+ AlteredInheritingTaggableModel, TaggableUser, UntaggableModel, OrderedTaggableModel, MixedTaggableModel, ScoredTaggableModel]
77
+ models.each do |model|
78
+ ActiveRecord::Base.connection.execute "DELETE FROM #{model.table_name}"
79
+ end
80
+ end
81
+
82
+ clean_database!
@@ -0,0 +1,695 @@
1
+ require File.expand_path('../../spec_helper', __FILE__)
2
+
3
+ =begin
4
+ describe "Taggable To Preserve Order" do
5
+ before(:each) do
6
+ clean_database!
7
+ @taggable = OrderedTaggableModel.new(:name => "Bob Jones")
8
+ end
9
+
10
+ it "should have tag types" do
11
+ [:tags, :colours].each do |type|
12
+ OrderedTaggableModel.tag_types.should include type
13
+ end
14
+
15
+ @taggable.tag_types.should == OrderedTaggableModel.tag_types
16
+ end
17
+
18
+ it "should have tag associations" do
19
+ [:tags, :colours].each do |type|
20
+ @taggable.respond_to?(type).should be_true
21
+ @taggable.respond_to?("#{type.to_s.singularize}_tags").should be_true
22
+ end
23
+ end
24
+
25
+ it "should have tag associations ordered by id" do
26
+ [:tags, :colours].each do |type|
27
+ OrderedTaggableModel.reflect_on_association(type).options[:order].should include('id')
28
+ OrderedTaggableModel.reflect_on_association("#{type.to_s.singularize}_taggings".to_sym).options[:order].should include('id')
29
+ end
30
+ end
31
+
32
+ it "should have tag methods" do
33
+ [:tags, :colours].each do |type|
34
+ @taggable.respond_to?("#{type.to_s.singularize}_list").should be_true
35
+ @taggable.respond_to?("#{type.to_s.singularize}_list=").should be_true
36
+ @taggable.respond_to?("all_#{type.to_s}_list").should be_true
37
+ end
38
+ end
39
+
40
+ it "should return tag list in the order the tags were created" do
41
+ # create
42
+ @taggable.tag_list = "rails, ruby, css"
43
+ @taggable.instance_variable_get("@tag_list").instance_of?(ActsAsTaggableOn::TagList).should be_true
44
+
45
+ lambda {
46
+ @taggable.save
47
+ }.should change(ActsAsTaggableOn::Tag, :count).by(3)
48
+
49
+ @taggable.reload
50
+ @taggable.tag_list.should == %w(rails ruby css)
51
+
52
+ # update
53
+ @taggable.tag_list = "pow, ruby, rails"
54
+ @taggable.save
55
+
56
+ @taggable.reload
57
+ @taggable.tag_list.should == %w(pow ruby rails)
58
+
59
+ # update with no change
60
+ @taggable.tag_list = "pow, ruby, rails"
61
+ @taggable.save
62
+
63
+ @taggable.reload
64
+ @taggable.tag_list.should == %w(pow ruby rails)
65
+
66
+ # update to clear tags
67
+ @taggable.tag_list = ""
68
+ @taggable.save
69
+
70
+ @taggable.reload
71
+ @taggable.tag_list.should == []
72
+ end
73
+
74
+ it "should return tag objects in the order the tags were created" do
75
+ # create
76
+ @taggable.tag_list = "pow, ruby, rails"
77
+ @taggable.instance_variable_get("@tag_list").instance_of?(ActsAsTaggableOn::TagList).should be_true
78
+
79
+ lambda {
80
+ @taggable.save
81
+ }.should change(ActsAsTaggableOn::Tag, :count).by(3)
82
+
83
+ @taggable.reload
84
+ @taggable.tags.map{|t| t.name}.should == %w(pow ruby rails)
85
+
86
+ # update
87
+ @taggable.tag_list = "rails, ruby, css, pow"
88
+ @taggable.save
89
+
90
+ @taggable.reload
91
+ @taggable.tags.map{|t| t.name}.should == %w(rails ruby css pow)
92
+ end
93
+
94
+ it "should return tag objects in tagging id order" do
95
+ # create
96
+ @taggable.tag_list = "pow, ruby, rails"
97
+ @taggable.save
98
+
99
+ @taggable.reload
100
+ ids = @taggable.tags.map{|t| t.taggings.first.id}
101
+ ids.should == ids.sort
102
+
103
+ # update
104
+ @taggable.tag_list = "rails, ruby, css, pow"
105
+ @taggable.save
106
+
107
+ @taggable.reload
108
+ ids = @taggable.tags.map{|t| t.taggings.first.id}
109
+ ids.should == ids.sort
110
+ end
111
+ end
112
+ =end
113
+
114
+ describe "Taggable" do
115
+ before(:each) do
116
+ clean_database!
117
+ @taggable = TaggableModel.new(:name => "Bob Jones")
118
+ @taggables = [@taggable, TaggableModel.new(:name => "John Doe")]
119
+ end
120
+
121
+ it "should have tag types" do
122
+ [:tags, :languages, :skills, :needs, :offerings].each do |type|
123
+ TaggableModel.tag_types.should include type
124
+ end
125
+
126
+ @taggable.class.should be_taggable
127
+ @taggable.should be_taggable
128
+ @taggable.tag_types.should == TaggableModel.tag_types
129
+ end
130
+
131
+ it "should have tag associations" do
132
+ [:tags, :languages, :skills, :needs, :offerings].each do |type|
133
+ @taggable.respond_to?(type).should be_true
134
+ @taggable.respond_to?("#{type.to_s.singularize}_tags").should be_true
135
+ end
136
+ end
137
+
138
+ it "should have tag methods" do
139
+ [:tags, :languages, :skills, :needs, :offerings].each do |type|
140
+ @taggable.respond_to?("#{type.to_s.singularize}_list").should be_true
141
+ @taggable.respond_to?("#{type.to_s.singularize}_list=").should be_true
142
+ # @taggable.respond_to?("all_#{type.to_s}_list").should be_true
143
+ end
144
+ end
145
+
146
+ it "should return [] right after create" do
147
+ blank_taggable = TaggableModel.new(:name => "Bob Jones")
148
+ blank_taggable.tag_list.should == []
149
+ end
150
+
151
+ it "should save taggings" do
152
+ @taggable.skill_list = "ruby, rails, css"
153
+
154
+ @taggable.instance_variable_get("@skills_list").instance_of?(Taglish::TagList).should be_true
155
+ lambda {
156
+ @taggable.save
157
+ }.should change(Taglish::Tagging, :count).by(3)
158
+
159
+ @taggable.reload
160
+ @taggable.skill_list.sort.should == %w(ruby rails css).sort
161
+ end
162
+
163
+ it "should be able to create tags" do
164
+ @taggable.skill_list = "ruby, rails, css"
165
+
166
+ lambda {
167
+ @taggable.save
168
+ }.should change(Taglish::Tag, :count).by(3)
169
+ end
170
+
171
+ context "with scored tag types" do
172
+ before do
173
+ @taggable = ScoredTaggableModel.new(:name => "Bob Jones")
174
+ end
175
+
176
+ it "should save taggings" do
177
+ @taggable.question_count_list = "ruby:9, sql:5"
178
+
179
+ @taggable.instance_variable_get("@question_counts_list").instance_of?(Taglish::TagList).should be_true
180
+ lambda {
181
+ @taggable.save
182
+ }.should change(Taglish::Tagging, :count).by(2)
183
+
184
+ @taggable.reload
185
+ @taggable.question_count_list.sort.should == %w(ruby:9 sql:5).sort
186
+ @taggable.question_counts.map{|tg| tg.name}.sort.should == %w(ruby sql)
187
+ @taggable.question_counts.map{|tg| tg.score}.sort.should == [5, 9]
188
+ end
189
+
190
+ it "should remove old tags" do
191
+ @taggable.question_count_list = "ruby:9, sql:5, js:4"
192
+ @taggable.save!
193
+ @taggable.reload
194
+
195
+ # Change the score of one, remove one, add one, leave one unchanged:
196
+ @taggable.question_count_list = "ruby:12, sql:5, chef:2"
197
+ @taggable.instance_variable_get("@question_counts_list").instance_of?(Taglish::TagList).should be_true
198
+ lambda {
199
+ @taggable.save
200
+ }.should change(Taglish::Tagging, :count).by(0)
201
+ @taggable.reload
202
+ @taggable.question_count_list.sort.should == %w(ruby:12 sql:5 chef:2).sort
203
+ @taggable.question_counts.map{|tg| tg.name}.sort.should == %w(chef ruby sql)
204
+ @taggable.question_counts.map{|tg| tg.score}.sort.should == [2, 5, 12]
205
+ end
206
+
207
+ it "should save tags with zero and negative scores" do
208
+ @taggable.question_count_list = "ruby:0, sql:-5"
209
+
210
+ @taggable.instance_variable_get("@question_counts_list").instance_of?(Taglish::TagList).should be_true
211
+ lambda {
212
+ @taggable.save
213
+ }.should change(Taglish::Tagging, :count).by(2)
214
+
215
+ @taggable.reload
216
+ @taggable.question_count_list.sort.should == %w(ruby:0 sql:-5).sort
217
+ @taggable.question_counts.map{|tg| tg.name}.sort.should == %w(ruby sql)
218
+ @taggable.question_counts.map{|tg| tg.score}.sort.should == [-5, 0]
219
+ end
220
+ end
221
+
222
+ context "with mixed tag types" do
223
+ before do
224
+ @taggable = MixedTaggableModel.new(:name => "Bob Jones")
225
+ end
226
+
227
+ it "should remember which tag types are ordered" do
228
+ @taggable.tags_have_score?(:skills).should be_false
229
+ @taggable.tags_have_score?(:colors).should be_false
230
+ @taggable.tags_have_score?(:question_counts).should be_true
231
+ @taggable.tags_have_score?(:needs).should be_false
232
+ @taggable.tags_have_score?(:offerings).should be_false
233
+ end
234
+
235
+ it "should remember which tag types are scored" do
236
+ @taggable.tags_have_order?(:skills).should be_false
237
+ @taggable.tags_have_order?(:colors).should be_true
238
+ @taggable.tags_have_order?(:question_counts).should be_false
239
+ @taggable.tags_have_order?(:needs).should be_false
240
+ @taggable.tags_have_order?(:offerings).should be_false
241
+ end
242
+ end
243
+
244
+ =begin
245
+ it "should have tag_counts_on" do
246
+ TaggableModel.tag_counts_on(:tags).all.should be_empty
247
+
248
+ @taggable.tag_list = ["awesome", "epic"]
249
+ @taggable.save
250
+
251
+ TaggableModel.tag_counts_on(:tags).length.should == 2
252
+ @taggable.tag_counts_on(:tags).length.should == 2
253
+ end
254
+
255
+ it "should be able to create tags through the tag list directly" do
256
+ @taggable.tag_list_on(:test).add("hello")
257
+ @taggable.tag_list_cache_on(:test).should_not be_empty
258
+ @taggable.tag_list_on(:test).should == ["hello"]
259
+
260
+ @taggable.save
261
+ @taggable.save_tags
262
+
263
+ @taggable.reload
264
+ @taggable.tag_list_on(:test).should == ["hello"]
265
+ end
266
+
267
+ it "should differentiate between contexts" do
268
+ @taggable.skill_list = "ruby, rails, css"
269
+ @taggable.tag_list = "ruby, bob, charlie"
270
+ @taggable.save
271
+ @taggable.reload
272
+ @taggable.skill_list.should include("ruby")
273
+ @taggable.skill_list.should_not include("bob")
274
+ end
275
+
276
+ it "should be able to remove tags through list alone" do
277
+ @taggable.skill_list = "ruby, rails, css"
278
+ @taggable.save
279
+ @taggable.reload
280
+ @taggable.should have(3).skills
281
+ @taggable.skill_list = "ruby, rails"
282
+ @taggable.save
283
+ @taggable.reload
284
+ @taggable.should have(2).skills
285
+ end
286
+
287
+ it "should be able to select taggables by subset of tags using ActiveRelation methods" do
288
+ @taggables[0].tag_list = "bob"
289
+ @taggables[1].tag_list = "charlie"
290
+ @taggables[0].skill_list = "ruby"
291
+ @taggables[1].skill_list = "css"
292
+ @taggables.each{|taggable| taggable.save}
293
+
294
+ @found_taggables_by_tag = TaggableModel.joins(:tags).where(:tags => {:name => ["bob"]})
295
+ @found_taggables_by_skill = TaggableModel.joins(:skills).where(:tags => {:name => ["ruby"]})
296
+
297
+ @found_taggables_by_tag.should include @taggables[0]
298
+ @found_taggables_by_tag.should_not include @taggables[1]
299
+ @found_taggables_by_skill.should include @taggables[0]
300
+ @found_taggables_by_skill.should_not include @taggables[1]
301
+ end
302
+
303
+ it "should be able to find by tag" do
304
+ @taggable.skill_list = "ruby, rails, css"
305
+ @taggable.save
306
+
307
+ TaggableModel.tagged_with("ruby").first.should == @taggable
308
+ end
309
+
310
+ it "should be able to find by tag with context" do
311
+ @taggable.skill_list = "ruby, rails, css"
312
+ @taggable.tag_list = "bob, charlie"
313
+ @taggable.save
314
+
315
+ TaggableModel.tagged_with("ruby").first.should == @taggable
316
+ TaggableModel.tagged_with("ruby, css").first.should == @taggable
317
+ TaggableModel.tagged_with("bob", :on => :skills).first.should_not == @taggable
318
+ TaggableModel.tagged_with("bob", :on => :tags).first.should == @taggable
319
+ end
320
+
321
+ it "should not care about case" do
322
+ bob = TaggableModel.create(:name => "Bob", :tag_list => "ruby")
323
+ frank = TaggableModel.create(:name => "Frank", :tag_list => "Ruby")
324
+
325
+ ActsAsTaggableOn::Tag.find(:all).size.should == 1
326
+ TaggableModel.tagged_with("ruby").to_a.should == TaggableModel.tagged_with("Ruby").to_a
327
+ end
328
+
329
+ it "should be able to get tag counts on model as a whole" do
330
+ bob = TaggableModel.create(:name => "Bob", :tag_list => "ruby, rails, css")
331
+ frank = TaggableModel.create(:name => "Frank", :tag_list => "ruby, rails")
332
+ charlie = TaggableModel.create(:name => "Charlie", :skill_list => "ruby")
333
+ TaggableModel.tag_counts.all.should_not be_empty
334
+ TaggableModel.skill_counts.all.should_not be_empty
335
+ end
336
+
337
+ it "should be able to get all tag counts on model as whole" do
338
+ bob = TaggableModel.create(:name => "Bob", :tag_list => "ruby, rails, css")
339
+ frank = TaggableModel.create(:name => "Frank", :tag_list => "ruby, rails")
340
+ charlie = TaggableModel.create(:name => "Charlie", :skill_list => "ruby")
341
+
342
+ TaggableModel.all_tag_counts.all.should_not be_empty
343
+ TaggableModel.all_tag_counts(:order => 'tags.id').first.count.should == 3 # ruby
344
+ end
345
+
346
+ it "should be able to use named scopes to chain tag finds by any tags by context" do
347
+ bob = TaggableModel.create(:name => "Bob", :need_list => "rails", :offering_list => "c++")
348
+ frank = TaggableModel.create(:name => "Frank", :need_list => "css", :offering_list => "css")
349
+ steve = TaggableModel.create(:name => 'Steve', :need_list => "c++", :offering_list => "java")
350
+
351
+ # Let's only find those who need rails or css and are offering c++ or java
352
+ TaggableModel.tagged_with(['rails, css'], :on => :needs, :any => true).tagged_with(['c++', 'java'], :on => :offerings, :any => true).to_a.should == [bob]
353
+ end
354
+
355
+ it "should not return read-only records" do
356
+ TaggableModel.create(:name => "Bob", :tag_list => "ruby, rails, css")
357
+ TaggableModel.tagged_with("ruby").first.should_not be_readonly
358
+ end
359
+
360
+ it "should be able to get scoped tag counts" do
361
+ bob = TaggableModel.create(:name => "Bob", :tag_list => "ruby, rails, css")
362
+ frank = TaggableModel.create(:name => "Frank", :tag_list => "ruby, rails")
363
+ charlie = TaggableModel.create(:name => "Charlie", :skill_list => "ruby")
364
+
365
+ TaggableModel.tagged_with("ruby").tag_counts(:order => 'tags.id').first.count.should == 2 # ruby
366
+ TaggableModel.tagged_with("ruby").skill_counts.first.count.should == 1 # ruby
367
+ end
368
+
369
+ it "should be able to get all scoped tag counts" do
370
+ bob = TaggableModel.create(:name => "Bob", :tag_list => "ruby, rails, css")
371
+ frank = TaggableModel.create(:name => "Frank", :tag_list => "ruby, rails")
372
+ charlie = TaggableModel.create(:name => "Charlie", :skill_list => "ruby")
373
+
374
+ TaggableModel.tagged_with("ruby").all_tag_counts(:order => 'tags.id').first.count.should == 3 # ruby
375
+ end
376
+
377
+ it 'should only return tag counts for the available scope' do
378
+ bob = TaggableModel.create(:name => "Bob", :tag_list => "ruby, rails, css")
379
+ frank = TaggableModel.create(:name => "Frank", :tag_list => "ruby, rails")
380
+ charlie = TaggableModel.create(:name => "Charlie", :skill_list => "ruby, java")
381
+
382
+ TaggableModel.tagged_with('rails').all_tag_counts.should have(3).items
383
+ TaggableModel.tagged_with('rails').all_tag_counts.any? { |tag| tag.name == 'java' }.should be_false
384
+
385
+ # Test specific join syntaxes:
386
+ frank.untaggable_models.create!
387
+ TaggableModel.tagged_with('rails').scoped(:joins => :untaggable_models).all_tag_counts.should have(2).items
388
+ TaggableModel.tagged_with('rails').scoped(:joins => { :untaggable_models => :taggable_model }).all_tag_counts.should have(2).items
389
+ TaggableModel.tagged_with('rails').scoped(:joins => [:untaggable_models]).all_tag_counts.should have(2).items
390
+ end
391
+
392
+ it "should be able to set a custom tag context list" do
393
+ bob = TaggableModel.create(:name => "Bob")
394
+ bob.set_tag_list_on(:rotors, "spinning, jumping")
395
+ bob.tag_list_on(:rotors).should == ["spinning","jumping"]
396
+ bob.save
397
+ bob.reload
398
+ bob.tags_on(:rotors).should_not be_empty
399
+ end
400
+
401
+ it "should be able to find tagged" do
402
+ bob = TaggableModel.create(:name => "Bob", :tag_list => "fitter, happier, more productive", :skill_list => "ruby, rails, css")
403
+ frank = TaggableModel.create(:name => "Frank", :tag_list => "weaker, depressed, inefficient", :skill_list => "ruby, rails, css")
404
+ steve = TaggableModel.create(:name => 'Steve', :tag_list => 'fitter, happier, more productive', :skill_list => 'c++, java, ruby')
405
+
406
+ TaggableModel.tagged_with("ruby", :order => 'taggable_models.name').to_a.should == [bob, frank, steve]
407
+ TaggableModel.tagged_with("ruby, rails", :order => 'taggable_models.name').to_a.should == [bob, frank]
408
+ TaggableModel.tagged_with(["ruby", "rails"], :order => 'taggable_models.name').to_a.should == [bob, frank]
409
+ end
410
+
411
+ it "should be able to find tagged with quotation marks" do
412
+ bob = TaggableModel.create(:name => "Bob", :tag_list => "fitter, happier, more productive, 'I love the ,comma,'")
413
+ TaggableModel.tagged_with("'I love the ,comma,'").should include(bob)
414
+ end
415
+
416
+ it "should be able to find tagged with invalid tags" do
417
+ bob = TaggableModel.create(:name => "Bob", :tag_list => "fitter, happier, more productive")
418
+ TaggableModel.tagged_with("sad, happier").should_not include(bob)
419
+ end
420
+
421
+ it "should be able to find tagged with any tag" do
422
+ bob = TaggableModel.create(:name => "Bob", :tag_list => "fitter, happier, more productive", :skill_list => "ruby, rails, css")
423
+ frank = TaggableModel.create(:name => "Frank", :tag_list => "weaker, depressed, inefficient", :skill_list => "ruby, rails, css")
424
+ steve = TaggableModel.create(:name => 'Steve', :tag_list => 'fitter, happier, more productive', :skill_list => 'c++, java, ruby')
425
+
426
+ TaggableModel.tagged_with(["ruby", "java"], :order => 'taggable_models.name', :any => true).to_a.should == [bob, frank, steve]
427
+ TaggableModel.tagged_with(["c++", "fitter"], :order => 'taggable_models.name', :any => true).to_a.should == [bob, steve]
428
+ TaggableModel.tagged_with(["depressed", "css"], :order => 'taggable_models.name', :any => true).to_a.should == [bob, frank]
429
+ end
430
+
431
+ context "wild: true" do
432
+ it "should use params as wildcards" do
433
+ bob = TaggableModel.create(:name => "Bob", :tag_list => "bob, tricia")
434
+ frank = TaggableModel.create(:name => "Frank", :tag_list => "bobby, jim")
435
+ steve = TaggableModel.create(:name => "Steve", :tag_list => "john, patricia")
436
+ jim = TaggableModel.create(:name => "Jim", :tag_list => "jim, steve")
437
+
438
+
439
+ TaggableModel.tagged_with(["bob", "tricia"], :wild => true, :any => true).to_a.sort_by{|o| o.id}.should == [bob, frank, steve]
440
+ TaggableModel.tagged_with(["bob", "tricia"], :wild => true, :exclude => true).to_a.should == [jim]
441
+ end
442
+ end
443
+
444
+ it "should be able to find tagged on a custom tag context" do
445
+ bob = TaggableModel.create(:name => "Bob")
446
+ bob.set_tag_list_on(:rotors, "spinning, jumping")
447
+ bob.tag_list_on(:rotors).should == ["spinning","jumping"]
448
+ bob.save
449
+
450
+ TaggableModel.tagged_with("spinning", :on => :rotors).to_a.should == [bob]
451
+ end
452
+
453
+ it "should be able to use named scopes to chain tag finds" do
454
+ bob = TaggableModel.create(:name => "Bob", :tag_list => "fitter, happier, more productive", :skill_list => "ruby, rails, css")
455
+ frank = TaggableModel.create(:name => "Frank", :tag_list => "weaker, depressed, inefficient", :skill_list => "ruby, rails, css")
456
+ steve = TaggableModel.create(:name => 'Steve', :tag_list => 'fitter, happier, more productive', :skill_list => 'c++, java, python')
457
+
458
+ # Let's only find those productive Rails developers
459
+ TaggableModel.tagged_with('rails', :on => :skills, :order => 'taggable_models.name').to_a.should == [bob, frank]
460
+ TaggableModel.tagged_with('happier', :on => :tags, :order => 'taggable_models.name').to_a.should == [bob, steve]
461
+ TaggableModel.tagged_with('rails', :on => :skills).tagged_with('happier', :on => :tags).to_a.should == [bob]
462
+ TaggableModel.tagged_with('rails').tagged_with('happier', :on => :tags).to_a.should == [bob]
463
+ end
464
+
465
+ it "should be able to find tagged with only the matching tags" do
466
+ bob = TaggableModel.create(:name => "Bob", :tag_list => "lazy, happier")
467
+ frank = TaggableModel.create(:name => "Frank", :tag_list => "fitter, happier, inefficient")
468
+ steve = TaggableModel.create(:name => 'Steve', :tag_list => "fitter, happier")
469
+
470
+ TaggableModel.tagged_with("fitter, happier", :match_all => true).to_a.should == [steve]
471
+ end
472
+
473
+ it "should be able to find tagged with some excluded tags" do
474
+ bob = TaggableModel.create(:name => "Bob", :tag_list => "happier, lazy")
475
+ frank = TaggableModel.create(:name => "Frank", :tag_list => "happier")
476
+ steve = TaggableModel.create(:name => 'Steve', :tag_list => "happier")
477
+
478
+ TaggableModel.tagged_with("lazy", :exclude => true).to_a.should == [frank, steve]
479
+ end
480
+
481
+ it "should return an empty scope for empty tags" do
482
+ TaggableModel.tagged_with('').should == []
483
+ TaggableModel.tagged_with(' ').should == []
484
+ TaggableModel.tagged_with(nil).should == []
485
+ TaggableModel.tagged_with([]).should == []
486
+ end
487
+
488
+ it "should not create duplicate taggings" do
489
+ bob = TaggableModel.create(:name => "Bob")
490
+ lambda {
491
+ bob.tag_list << "happier"
492
+ bob.tag_list << "happier"
493
+ bob.save
494
+ }.should change(ActsAsTaggableOn::Tagging, :count).by(1)
495
+ end
496
+
497
+ describe "Associations" do
498
+ before(:each) do
499
+ @taggable = TaggableModel.create(:tag_list => "awesome, epic")
500
+ end
501
+
502
+ it "should not remove tags when creating associated objects" do
503
+ @taggable.untaggable_models.create!
504
+ @taggable.reload
505
+ @taggable.tag_list.should have(2).items
506
+ end
507
+ end
508
+
509
+ describe "grouped_column_names_for method" do
510
+ it "should return all column names joined for Tag GROUP clause" do
511
+ @taggable.grouped_column_names_for(ActsAsTaggableOn::Tag).should == "tags.id, tags.name"
512
+ end
513
+
514
+ it "should return all column names joined for TaggableModel GROUP clause" do
515
+ @taggable.grouped_column_names_for(TaggableModel).should == "taggable_models.id, taggable_models.name, taggable_models.type"
516
+ end
517
+
518
+ it "should return all column names joined for NonStandardIdTaggableModel GROUP clause" do
519
+ @taggable.grouped_column_names_for(TaggableModel).should == "taggable_models.#{TaggableModel.primary_key}, taggable_models.name, taggable_models.type"
520
+ end
521
+ end
522
+
523
+ describe "Single Table Inheritance" do
524
+ before do
525
+ @taggable = TaggableModel.new(:name => "taggable")
526
+ @inherited_same = InheritingTaggableModel.new(:name => "inherited same")
527
+ @inherited_different = AlteredInheritingTaggableModel.new(:name => "inherited different")
528
+ end
529
+
530
+ it "should be able to save tags for inherited models" do
531
+ @inherited_same.tag_list = "bob, kelso"
532
+ @inherited_same.save
533
+ InheritingTaggableModel.tagged_with("bob").first.should == @inherited_same
534
+ end
535
+
536
+ it "should find STI tagged models on the superclass" do
537
+ @inherited_same.tag_list = "bob, kelso"
538
+ @inherited_same.save
539
+ TaggableModel.tagged_with("bob").first.should == @inherited_same
540
+ end
541
+
542
+ it "should be able to add on contexts only to some subclasses" do
543
+ @inherited_different.part_list = "fork, spoon"
544
+ @inherited_different.save
545
+ InheritingTaggableModel.tagged_with("fork", :on => :parts).should be_empty
546
+ AlteredInheritingTaggableModel.tagged_with("fork", :on => :parts).first.should == @inherited_different
547
+ end
548
+
549
+ it "should have different tag_counts_on for inherited models" do
550
+ @inherited_same.tag_list = "bob, kelso"
551
+ @inherited_same.save!
552
+ @inherited_different.tag_list = "fork, spoon"
553
+ @inherited_different.save!
554
+
555
+ InheritingTaggableModel.tag_counts_on(:tags, :order => 'tags.id').map(&:name).should == %w(bob kelso)
556
+ AlteredInheritingTaggableModel.tag_counts_on(:tags, :order => 'tags.id').map(&:name).should == %w(fork spoon)
557
+ TaggableModel.tag_counts_on(:tags, :order => 'tags.id').map(&:name).should == %w(bob kelso fork spoon)
558
+ end
559
+
560
+ it 'should store same tag without validation conflict' do
561
+ @taggable.tag_list = 'one'
562
+ @taggable.save!
563
+
564
+ @inherited_same.tag_list = 'one'
565
+ @inherited_same.save!
566
+
567
+ @inherited_same.update_attributes! :name => 'foo'
568
+ end
569
+ end
570
+
571
+ describe "NonStandardIdTaggable" do
572
+ before(:each) do
573
+ clean_database!
574
+ @taggable = NonStandardIdTaggableModel.new(:name => "Bob Jones")
575
+ @taggables = [@taggable, NonStandardIdTaggableModel.new(:name => "John Doe")]
576
+ end
577
+
578
+ it "should have tag types" do
579
+ [:tags, :languages, :skills, :needs, :offerings].each do |type|
580
+ NonStandardIdTaggableModel.tag_types.should include type
581
+ end
582
+
583
+ @taggable.tag_types.should == NonStandardIdTaggableModel.tag_types
584
+ end
585
+
586
+ it "should have tag_counts_on" do
587
+ NonStandardIdTaggableModel.tag_counts_on(:tags).all.should be_empty
588
+
589
+ @taggable.tag_list = ["awesome", "epic"]
590
+ @taggable.save
591
+
592
+ NonStandardIdTaggableModel.tag_counts_on(:tags).length.should == 2
593
+ @taggable.tag_counts_on(:tags).length.should == 2
594
+ end
595
+
596
+ it "should be able to create tags" do
597
+ @taggable.skill_list = "ruby, rails, css"
598
+ @taggable.instance_variable_get("@skill_list").instance_of?(ActsAsTaggableOn::TagList).should be_true
599
+
600
+ lambda {
601
+ @taggable.save
602
+ }.should change(ActsAsTaggableOn::Tag, :count).by(3)
603
+
604
+ @taggable.reload
605
+ @taggable.skill_list.sort.should == %w(ruby rails css).sort
606
+ end
607
+
608
+ it "should be able to create tags through the tag list directly" do
609
+ @taggable.tag_list_on(:test).add("hello")
610
+ @taggable.tag_list_cache_on(:test).should_not be_empty
611
+ @taggable.tag_list_on(:test).should == ["hello"]
612
+
613
+ @taggable.save
614
+ @taggable.save_tags
615
+
616
+ @taggable.reload
617
+ @taggable.tag_list_on(:test).should == ["hello"]
618
+ end
619
+ end
620
+
621
+ describe "Dirty Objects" do
622
+ before(:each) do
623
+ @taggable = TaggableModel.create(:tag_list => "awesome, epic")
624
+ end
625
+
626
+ it 'should show changes of dirty object' do
627
+ @taggable.changes.should == {}
628
+ @taggable.tag_list = 'one'
629
+ @taggable.changes.should == {"tag_list"=>["awesome, epic", ["one"]]}
630
+
631
+ @taggable.tag_list_changed?.should be_true
632
+ @taggable.tag_list_was.should == "awesome, epic"
633
+ @taggable.tag_list_change.should == ["awesome, epic", ["one"]]
634
+ end
635
+
636
+ it 'should show no changes if the same tag_list' do
637
+ @taggable.tag_list = "awesome, epic"
638
+ @taggable.tag_list_changed?.should be_false
639
+ @taggable.changes.should == {}
640
+ end
641
+ end
642
+
643
+ describe "Scored taggings" do
644
+ before(:each) do
645
+ @taggable = ScoredTaggableModel.create
646
+ end
647
+
648
+ it "should have tag types" do
649
+ [:question_counts].each do |type|
650
+ ScoredTaggableModel.tag_types.should include type
651
+ end
652
+
653
+ @taggable.tag_types.should == ScoredTaggableModel.tag_types
654
+ end
655
+
656
+ it "should have tag_counts_on" do
657
+ ScoredTaggableModel.tag_counts_on(:question_counts).all.should be_empty
658
+
659
+ @taggable.question_count_list = ["ruby:5", "sql:4"]
660
+ @taggable.save
661
+
662
+ ScoredTaggableModel.tag_counts_on(:question_counts).length.should == 2
663
+ @taggable.tag_counts_on(:question_counts).length.should == 2
664
+ end
665
+
666
+ it "should be able to create tags" do
667
+ @taggable.question_count_list = 'ruby:5, sql:4'
668
+ @taggable.instance_variable_get("@question_count_list").instance_of?(ActsAsTaggableOn::TagList).should be_true
669
+
670
+ lambda {
671
+ @taggable.save
672
+ }.should change(ActsAsTaggableOn::Tag, :count).by(2)
673
+
674
+ @taggable.reload
675
+ @taggable.question_count_list.sort.should == %w(ruby:5 sql:4).sort
676
+ end
677
+
678
+ it "should be able to create tags through the tag list directly" do
679
+ @taggable.tag_list_on(:question_counts).add("ruby:9")
680
+ @taggable.tag_list_cache_on(:question_counts).should_not be_empty
681
+ @taggable.tag_list_on(:question_counts).should == ["ruby:9"]
682
+
683
+ @taggable.save
684
+ @taggable.save_tags
685
+
686
+ @taggable.reload
687
+ @taggable.tag_list_on(:question_counts).should == ["ruby:9"]
688
+ end
689
+
690
+ end
691
+ =end
692
+
693
+ end
694
+
695
+