acts-as-taggable-on 2.1.1 → 2.2.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.
Files changed (30) hide show
  1. data/.gitignore +1 -0
  2. data/.travis.yml +1 -2
  3. data/README.rdoc +27 -50
  4. data/acts-as-taggable-on.gemspec +4 -3
  5. data/lib/acts-as-taggable-on.rb +0 -4
  6. data/lib/acts-as-taggable-on/version.rb +1 -1
  7. data/lib/acts_as_taggable_on/acts_as_taggable_on.rb +4 -13
  8. data/lib/acts_as_taggable_on/acts_as_taggable_on/cache.rb +1 -1
  9. data/lib/acts_as_taggable_on/acts_as_taggable_on/collection.rb +1 -1
  10. data/lib/acts_as_taggable_on/acts_as_taggable_on/core.rb +29 -17
  11. data/lib/acts_as_taggable_on/acts_as_taggable_on/related.rb +15 -15
  12. data/lib/acts_as_taggable_on/tag.rb +12 -13
  13. data/lib/acts_as_taggable_on/tagging.rb +0 -2
  14. data/lib/acts_as_taggable_on/utils.rb +7 -8
  15. data/spec/acts_as_taggable_on/acts_as_taggable_on_spec.rb +228 -61
  16. data/spec/acts_as_taggable_on/tag_list_spec.rb +4 -0
  17. data/spec/acts_as_taggable_on/tag_spec.rb +11 -15
  18. data/spec/acts_as_taggable_on/taggable_spec.rb +89 -27
  19. data/spec/acts_as_taggable_on/tagger_spec.rb +14 -0
  20. data/spec/acts_as_taggable_on/utils_spec.rb +2 -3
  21. data/spec/database.yml.sample +1 -1
  22. data/spec/generators/acts_as_taggable_on/migration/migration_generator_spec.rb +22 -0
  23. data/spec/models.rb +11 -2
  24. data/spec/schema.rb +7 -0
  25. data/spec/spec_helper.rb +1 -0
  26. metadata +95 -122
  27. data/generators/acts_as_taggable_on_migration/acts_as_taggable_on_migration_generator.rb +0 -7
  28. data/generators/acts_as_taggable_on_migration/templates/migration.rb +0 -29
  29. data/lib/acts_as_taggable_on/compatibility/Gemfile +0 -8
  30. data/lib/acts_as_taggable_on/compatibility/active_record_backports.rb +0 -21
@@ -62,6 +62,10 @@ describe ActsAsTaggableOn::TagList do
62
62
  @tag_list.to_s.should == "awesome, radical, cool, \"rad,bodacious\""
63
63
  end
64
64
 
65
+ it "#from should return empty array if empty array is passed" do
66
+ ActsAsTaggableOn::TagList.from([]).should == []
67
+ end
68
+
65
69
  it "should be able to call to_s on a frozen tag list" do
66
70
  @tag_list.freeze
67
71
  lambda { @tag_list.add("cool","rad,bodacious") }.should raise_error
@@ -9,12 +9,13 @@ describe ActsAsTaggableOn::Tag do
9
9
 
10
10
  describe "named like any" do
11
11
  before(:each) do
12
+ ActsAsTaggableOn::Tag.create(:name => "Awesome")
12
13
  ActsAsTaggableOn::Tag.create(:name => "awesome")
13
14
  ActsAsTaggableOn::Tag.create(:name => "epic")
14
15
  end
15
16
 
16
17
  it "should find both tags" do
17
- ActsAsTaggableOn::Tag.named_like_any(["awesome", "epic"]).should have(2).items
18
+ ActsAsTaggableOn::Tag.named_like_any(["awesome", "epic"]).should have(3).items
18
19
  end
19
20
  end
20
21
 
@@ -72,7 +73,7 @@ describe ActsAsTaggableOn::Tag do
72
73
 
73
74
  it "should require a name" do
74
75
  @tag.valid?
75
-
76
+
76
77
  if ActiveRecord::VERSION::MAJOR >= 3
77
78
  @tag.errors[:name].should == ["can't be blank"]
78
79
  else
@@ -81,8 +82,8 @@ describe ActsAsTaggableOn::Tag do
81
82
 
82
83
  @tag.name = "something"
83
84
  @tag.valid?
84
-
85
- if ActiveRecord::VERSION::MAJOR >= 3
85
+
86
+ if ActiveRecord::VERSION::MAJOR >= 3
86
87
  @tag.errors[:name].should == []
87
88
  else
88
89
  @tag.errors[:name].should be_nil
@@ -112,24 +113,19 @@ describe ActsAsTaggableOn::Tag do
112
113
  @another_tag = ActsAsTaggableOn::Tag.create!(:name => "coolip")
113
114
  ActsAsTaggableOn::Tag.named_like('cool').should include(@tag, @another_tag)
114
115
  end
115
-
116
+
116
117
  describe "escape wildcard symbols in like requests" do
117
118
  before(:each) do
118
119
  @tag.name = "cool"
119
120
  @tag.save
120
- @another_tag = ActsAsTaggableOn::Tag.create!(:name => "coo%")
121
- @another_tag2 = ActsAsTaggableOn::Tag.create!(:name => "coolish")
121
+ @another_tag = ActsAsTaggableOn::Tag.create!(:name => "coo%")
122
+ @another_tag2 = ActsAsTaggableOn::Tag.create!(:name => "coolish")
122
123
  end
123
-
124
+
124
125
  it "return escaped result when '%' char present in tag" do
125
- if @tag.using_sqlite?
126
- ActsAsTaggableOn::Tag.named_like('coo%').should include(@tag)
127
- ActsAsTaggableOn::Tag.named_like('coo%').should include(@another_tag)
128
- else
129
- ActsAsTaggableOn::Tag.named_like('coo%').should_not include(@tag)
126
+ ActsAsTaggableOn::Tag.named_like('coo%').should_not include(@tag)
130
127
  ActsAsTaggableOn::Tag.named_like('coo%').should include(@another_tag)
131
- end
132
128
  end
133
-
129
+
134
130
  end
135
131
  end
@@ -25,14 +25,19 @@ describe "Taggable" do
25
25
  @taggable.tag_counts_on(:tags).length.should == 2
26
26
  end
27
27
 
28
+ it "should return [] right after create" do
29
+ blank_taggable = TaggableModel.new(:name => "Bob Jones")
30
+ blank_taggable.tag_list.should == []
31
+ end
32
+
28
33
  it "should be able to create tags" do
29
34
  @taggable.skill_list = "ruby, rails, css"
30
35
  @taggable.instance_variable_get("@skill_list").instance_of?(ActsAsTaggableOn::TagList).should be_true
31
-
36
+
32
37
  lambda {
33
38
  @taggable.save
34
39
  }.should change(ActsAsTaggableOn::Tag, :count).by(3)
35
-
40
+
36
41
  @taggable.reload
37
42
  @taggable.skill_list.sort.should == %w(ruby rails css).sort
38
43
  end
@@ -41,10 +46,10 @@ describe "Taggable" do
41
46
  @taggable.tag_list_on(:test).add("hello")
42
47
  @taggable.tag_list_cache_on(:test).should_not be_empty
43
48
  @taggable.tag_list_on(:test).should == ["hello"]
44
-
49
+
45
50
  @taggable.save
46
51
  @taggable.save_tags
47
-
52
+
48
53
  @taggable.reload
49
54
  @taggable.tag_list_on(:test).should == ["hello"]
50
55
  end
@@ -75,7 +80,7 @@ describe "Taggable" do
75
80
  @taggables[0].skill_list = "ruby"
76
81
  @taggables[1].skill_list = "css"
77
82
  @taggables.each{|taggable| taggable.save}
78
-
83
+
79
84
  @found_taggables_by_tag = TaggableModel.joins(:tags).where(:tags => {:name => ["bob"]})
80
85
  @found_taggables_by_skill = TaggableModel.joins(:skills).where(:tags => {:name => ["ruby"]})
81
86
 
@@ -84,7 +89,7 @@ describe "Taggable" do
84
89
  @found_taggables_by_skill.should include @taggables[0]
85
90
  @found_taggables_by_skill.should_not include @taggables[1]
86
91
  end
87
-
92
+
88
93
  it "should be able to find by tag" do
89
94
  @taggable.skill_list = "ruby, rails, css"
90
95
  @taggable.save
@@ -136,7 +141,7 @@ describe "Taggable" do
136
141
  # Let's only find those who need rails or css and are offering c++ or java
137
142
  TaggableModel.tagged_with(['rails, css'], :on => :needs, :any => true).tagged_with(['c++', 'java'], :on => :offerings, :any => true).to_a.should == [bob]
138
143
  end
139
-
144
+
140
145
  if ActiveRecord::VERSION::MAJOR >= 3
141
146
  it "should not return read-only records" do
142
147
  TaggableModel.create(:name => "Bob", :tag_list => "ruby, rails, css")
@@ -146,10 +151,10 @@ describe "Taggable" do
146
151
  xit "should not return read-only records" do
147
152
  # apparantly, there is no way to set readonly to false in a scope if joins are made
148
153
  end
149
-
154
+
150
155
  it "should be possible to return writable records" do
151
156
  TaggableModel.create(:name => "Bob", :tag_list => "ruby, rails, css")
152
- TaggableModel.tagged_with("ruby").first(:readonly => false).should_not be_readonly
157
+ TaggableModel.tagged_with("ruby").first(:readonly => false).should_not be_readonly
153
158
  end
154
159
  end
155
160
 
@@ -174,10 +179,10 @@ describe "Taggable" do
174
179
  bob = TaggableModel.create(:name => "Bob", :tag_list => "ruby, rails, css")
175
180
  frank = TaggableModel.create(:name => "Frank", :tag_list => "ruby, rails")
176
181
  charlie = TaggableModel.create(:name => "Charlie", :skill_list => "ruby, java")
177
-
182
+
178
183
  TaggableModel.tagged_with('rails').all_tag_counts.should have(3).items
179
184
  TaggableModel.tagged_with('rails').all_tag_counts.any? { |tag| tag.name == 'java' }.should be_false
180
-
185
+
181
186
  # Test specific join syntaxes:
182
187
  frank.untaggable_models.create!
183
188
  TaggableModel.tagged_with('rails').scoped(:joins => :untaggable_models).all_tag_counts.should have(2).items
@@ -203,15 +208,15 @@ describe "Taggable" do
203
208
  TaggableModel.tagged_with("ruby, rails", :order => 'taggable_models.name').to_a.should == [bob, frank]
204
209
  TaggableModel.tagged_with(["ruby", "rails"], :order => 'taggable_models.name').to_a.should == [bob, frank]
205
210
  end
206
-
211
+
207
212
  it "should be able to find tagged with quotation marks" do
208
213
  bob = TaggableModel.create(:name => "Bob", :tag_list => "fitter, happier, more productive, 'I love the ,comma,'")
209
214
  TaggableModel.tagged_with("'I love the ,comma,'").should include(bob)
210
215
  end
211
-
216
+
212
217
  it "should be able to find tagged with invalid tags" do
213
- bob = TaggableModel.create(:name => "Bob", :tag_list => "fitter, happier, more productive")
214
- TaggableModel.tagged_with("sad, happier").should_not include(bob)
218
+ bob = TaggableModel.create(:name => "Bob", :tag_list => "fitter, happier, more productive")
219
+ TaggableModel.tagged_with("sad, happier").should_not include(bob)
215
220
  end
216
221
 
217
222
  it "should be able to find tagged with any tag" do
@@ -260,11 +265,12 @@ describe "Taggable" do
260
265
 
261
266
  TaggableModel.tagged_with("lazy", :exclude => true).to_a.should == [frank, steve]
262
267
  end
263
-
268
+
264
269
  it "should return an empty scope for empty tags" do
265
270
  TaggableModel.tagged_with('').should == []
266
271
  TaggableModel.tagged_with(' ').should == []
267
- TaggableModel.tagged_with(nil).should == []
272
+ TaggableModel.tagged_with(nil).should == []
273
+ TaggableModel.tagged_with([]).should == []
268
274
  end
269
275
 
270
276
  it "should not create duplicate taggings" do
@@ -275,12 +281,12 @@ describe "Taggable" do
275
281
  bob.save
276
282
  }.should change(ActsAsTaggableOn::Tagging, :count).by(1)
277
283
  end
278
-
284
+
279
285
  describe "Associations" do
280
286
  before(:each) do
281
287
  @taggable = TaggableModel.create(:tag_list => "awesome, epic")
282
288
  end
283
-
289
+
284
290
  it "should not remove tags when creating associated objects" do
285
291
  @taggable.untaggable_models.create!
286
292
  @taggable.reload
@@ -296,6 +302,10 @@ describe "Taggable" do
296
302
  it "should return all column names joined for TaggableModel GROUP clause" do
297
303
  @taggable.grouped_column_names_for(TaggableModel).should == "taggable_models.id, taggable_models.name, taggable_models.type"
298
304
  end
305
+
306
+ it "should return all column names joined for NonStandardIdTaggableModel GROUP clause" do
307
+ @taggable.grouped_column_names_for(TaggableModel).should == "taggable_models.#{TaggableModel.primary_key}, taggable_models.name, taggable_models.type"
308
+ end
299
309
  end
300
310
 
301
311
  describe "Single Table Inheritance" do
@@ -304,45 +314,97 @@ describe "Taggable" do
304
314
  @inherited_same = InheritingTaggableModel.new(:name => "inherited same")
305
315
  @inherited_different = AlteredInheritingTaggableModel.new(:name => "inherited different")
306
316
  end
307
-
317
+
308
318
  it "should be able to save tags for inherited models" do
309
319
  @inherited_same.tag_list = "bob, kelso"
310
320
  @inherited_same.save
311
321
  InheritingTaggableModel.tagged_with("bob").first.should == @inherited_same
312
322
  end
313
-
323
+
314
324
  it "should find STI tagged models on the superclass" do
315
325
  @inherited_same.tag_list = "bob, kelso"
316
326
  @inherited_same.save
317
327
  TaggableModel.tagged_with("bob").first.should == @inherited_same
318
328
  end
319
-
329
+
320
330
  it "should be able to add on contexts only to some subclasses" do
321
331
  @inherited_different.part_list = "fork, spoon"
322
332
  @inherited_different.save
323
333
  InheritingTaggableModel.tagged_with("fork", :on => :parts).should be_empty
324
334
  AlteredInheritingTaggableModel.tagged_with("fork", :on => :parts).first.should == @inherited_different
325
335
  end
326
-
336
+
327
337
  it "should have different tag_counts_on for inherited models" do
328
338
  @inherited_same.tag_list = "bob, kelso"
329
339
  @inherited_same.save!
330
340
  @inherited_different.tag_list = "fork, spoon"
331
341
  @inherited_different.save!
332
-
342
+
333
343
  InheritingTaggableModel.tag_counts_on(:tags, :order => 'tags.id').map(&:name).should == %w(bob kelso)
334
344
  AlteredInheritingTaggableModel.tag_counts_on(:tags, :order => 'tags.id').map(&:name).should == %w(fork spoon)
335
345
  TaggableModel.tag_counts_on(:tags, :order => 'tags.id').map(&:name).should == %w(bob kelso fork spoon)
336
346
  end
337
-
347
+
338
348
  it 'should store same tag without validation conflict' do
339
349
  @taggable.tag_list = 'one'
340
350
  @taggable.save!
341
-
351
+
342
352
  @inherited_same.tag_list = 'one'
343
353
  @inherited_same.save!
344
-
354
+
345
355
  @inherited_same.update_attributes! :name => 'foo'
346
356
  end
347
357
  end
358
+
359
+ describe "NonStandardIdTaggable" do
360
+ before(:each) do
361
+ clean_database!
362
+ @taggable = NonStandardIdTaggableModel.new(:name => "Bob Jones")
363
+ @taggables = [@taggable, NonStandardIdTaggableModel.new(:name => "John Doe")]
364
+ end
365
+
366
+ it "should have tag types" do
367
+ [:tags, :languages, :skills, :needs, :offerings].each do |type|
368
+ NonStandardIdTaggableModel.tag_types.should include type
369
+ end
370
+
371
+ @taggable.tag_types.should == NonStandardIdTaggableModel.tag_types
372
+ end
373
+
374
+ it "should have tag_counts_on" do
375
+ NonStandardIdTaggableModel.tag_counts_on(:tags).all.should be_empty
376
+
377
+ @taggable.tag_list = ["awesome", "epic"]
378
+ @taggable.save
379
+
380
+ NonStandardIdTaggableModel.tag_counts_on(:tags).length.should == 2
381
+ @taggable.tag_counts_on(:tags).length.should == 2
382
+ end
383
+
384
+ it "should be able to create tags" do
385
+ @taggable.skill_list = "ruby, rails, css"
386
+ @taggable.instance_variable_get("@skill_list").instance_of?(ActsAsTaggableOn::TagList).should be_true
387
+
388
+ lambda {
389
+ @taggable.save
390
+ }.should change(ActsAsTaggableOn::Tag, :count).by(3)
391
+
392
+ @taggable.reload
393
+ @taggable.skill_list.sort.should == %w(ruby rails css).sort
394
+ end
395
+
396
+ it "should be able to create tags through the tag list directly" do
397
+ @taggable.tag_list_on(:test).add("hello")
398
+ @taggable.tag_list_cache_on(:test).should_not be_empty
399
+ @taggable.tag_list_on(:test).should == ["hello"]
400
+
401
+ @taggable.save
402
+ @taggable.save_tags
403
+
404
+ @taggable.reload
405
+ @taggable.tag_list_on(:test).should == ["hello"]
406
+ end
407
+ end
348
408
  end
409
+
410
+
@@ -17,6 +17,20 @@ describe "Tagger" do
17
17
  @user.owned_tags.size == 2
18
18
  end
19
19
 
20
+ it "should scope objects returned by tagged_with by owners" do
21
+ @taggable2 = TaggableModel.create(:name => "Jim Jones")
22
+ @taggable3 = TaggableModel.create(:name => "Jane Doe")
23
+
24
+ @user2 = TaggableUser.new
25
+ @user.tag(@taggable, :with => 'ruby, scheme', :on => :tags)
26
+ @user2.tag(@taggable2, :with => 'ruby, scheme', :on => :tags)
27
+ @user2.tag(@taggable3, :with => 'ruby, scheme', :on => :tags)
28
+
29
+ TaggableModel.tagged_with(%w(ruby scheme), :owned_by => @user).count.should == 1
30
+ TaggableModel.tagged_with(%w(ruby scheme), :owned_by => @user2).count.should == 2
31
+
32
+ end
33
+
20
34
  it "should not overlap tags from different taggers" do
21
35
  @user2 = TaggableUser.new
22
36
  lambda{
@@ -4,16 +4,15 @@ describe ActsAsTaggableOn::Utils do
4
4
  describe "like_operator" do
5
5
  before(:each) do
6
6
  clean_database!
7
- TaggableModel.write_inheritable_attribute(:tag_types, [])
8
7
  TaggableModel.acts_as_taggable_on(:tags, :languages, :skills, :needs, :offerings)
9
8
  @taggable = TaggableModel.new(:name => "Bob Jones")
10
9
  end
11
-
10
+
12
11
  it "should return 'ILIKE' when the adapter is PostgreSQL" do
13
12
  TaggableModel.connection.stub(:adapter_name).and_return("PostgreSQL")
14
13
  TaggableModel.send(:like_operator).should == "ILIKE"
15
14
  end
16
-
15
+
17
16
  it "should return 'LIKE' when the adapter is not PostgreSQL" do
18
17
  TaggableModel.connection.stub(:adapter_name).and_return("MySQL")
19
18
  TaggableModel.send(:like_operator).should == "LIKE"
@@ -16,4 +16,4 @@ postgresql:
16
16
  username: postgres
17
17
  password:
18
18
  database: acts_as_taggable_on
19
- encoding: utf8
19
+ encoding: utf8
@@ -0,0 +1,22 @@
1
+ require 'spec_helper'
2
+
3
+ # Generators are not automatically loaded by Rails
4
+ require 'generators/acts_as_taggable_on/migration/migration_generator'
5
+
6
+ describe ActsAsTaggableOn::MigrationGenerator do
7
+ # Tell the generator where to put its output (what it thinks of as Rails.root)
8
+ destination File.expand_path("../../../../../tmp", __FILE__)
9
+
10
+ before do
11
+ prepare_destination
12
+ Rails::Generators.options[:rails][:orm] = :active_record
13
+ end
14
+ describe 'no arguments' do
15
+ before { run_generator }
16
+
17
+ describe 'db/migrate/acts_as_taggable_on_migration.rb' do
18
+ subject { file('db/migrate/acts_as_taggable_on_migration.rb') }
19
+ it { should be_a_migration }
20
+ end
21
+ end
22
+ end
@@ -11,7 +11,7 @@ class CachedModel < ActiveRecord::Base
11
11
  end
12
12
 
13
13
  class OtherCachedModel < ActiveRecord::Base
14
- acts_as_taggable_on :languages
14
+ acts_as_taggable_on :languages, :statuses, :glasses
15
15
  end
16
16
 
17
17
  class OtherTaggableModel < ActiveRecord::Base
@@ -32,4 +32,13 @@ end
32
32
 
33
33
  class UntaggableModel < ActiveRecord::Base
34
34
  belongs_to :taggable_model
35
- end
35
+ end
36
+
37
+ class NonStandardIdTaggableModel < ActiveRecord::Base
38
+ set_primary_key "an_id"
39
+ acts_as_taggable
40
+ acts_as_taggable_on :languages
41
+ acts_as_taggable_on :skills
42
+ acts_as_taggable_on :needs, :offerings
43
+ has_many :untaggable_models
44
+ end
@@ -21,6 +21,11 @@ ActiveRecord::Schema.define :version => 0 do
21
21
  t.column :type, :string
22
22
  end
23
23
 
24
+ create_table :non_standard_id_taggable_models, :primary_key => "an_id", :force => true do |t|
25
+ t.column :name, :string
26
+ t.column :type, :string
27
+ end
28
+
24
29
  create_table :untaggable_models, :force => true do |t|
25
30
  t.column :taggable_model_id, :integer
26
31
  t.column :name, :string
@@ -36,6 +41,8 @@ ActiveRecord::Schema.define :version => 0 do
36
41
  t.column :name, :string
37
42
  t.column :type, :string
38
43
  t.column :cached_language_list, :string
44
+ t.column :cached_status_list, :string
45
+ t.column :cached_glass_list, :string
39
46
  end
40
47
 
41
48
  create_table :taggable_users, :force => true do |t|
@@ -19,6 +19,7 @@ end
19
19
 
20
20
  Bundler.require
21
21
  require File.expand_path('../../lib/acts-as-taggable-on', __FILE__)
22
+ require 'ammeter/init'
22
23
 
23
24
  unless [].respond_to?(:freq)
24
25
  class Array