litmus-acts-as-taggable-on 2.0.4

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 (39) hide show
  1. data/CHANGELOG +25 -0
  2. data/Gemfile +6 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +227 -0
  5. data/Rakefile +59 -0
  6. data/VERSION +1 -0
  7. data/generators/acts_as_taggable_on_migration/acts_as_taggable_on_migration_generator.rb +7 -0
  8. data/generators/acts_as_taggable_on_migration/templates/migration.rb +29 -0
  9. data/lib/acts-as-taggable-on.rb +32 -0
  10. data/lib/acts_as_taggable_on/acts_as_taggable_on.rb +53 -0
  11. data/lib/acts_as_taggable_on/acts_as_taggable_on/cache.rb +53 -0
  12. data/lib/acts_as_taggable_on/acts_as_taggable_on/collection.rb +130 -0
  13. data/lib/acts_as_taggable_on/acts_as_taggable_on/core.rb +237 -0
  14. data/lib/acts_as_taggable_on/acts_as_taggable_on/ownership.rb +101 -0
  15. data/lib/acts_as_taggable_on/acts_as_taggable_on/related.rb +65 -0
  16. data/lib/acts_as_taggable_on/acts_as_tagger.rb +67 -0
  17. data/lib/acts_as_taggable_on/compatibility/Gemfile +6 -0
  18. data/lib/acts_as_taggable_on/compatibility/active_record_backports.rb +17 -0
  19. data/lib/acts_as_taggable_on/tag.rb +67 -0
  20. data/lib/acts_as_taggable_on/tag_list.rb +95 -0
  21. data/lib/acts_as_taggable_on/tagging.rb +23 -0
  22. data/lib/acts_as_taggable_on/tags_helper.rb +17 -0
  23. data/lib/generators/acts_as_taggable_on/migration/migration_generator.rb +31 -0
  24. data/lib/generators/acts_as_taggable_on/migration/templates/active_record/migration.rb +28 -0
  25. data/rails/init.rb +1 -0
  26. data/spec/acts_as_taggable_on/acts_as_taggable_on_spec.rb +266 -0
  27. data/spec/acts_as_taggable_on/acts_as_tagger_spec.rb +114 -0
  28. data/spec/acts_as_taggable_on/tag_list_spec.rb +70 -0
  29. data/spec/acts_as_taggable_on/tag_spec.rb +115 -0
  30. data/spec/acts_as_taggable_on/taggable_spec.rb +306 -0
  31. data/spec/acts_as_taggable_on/tagger_spec.rb +91 -0
  32. data/spec/acts_as_taggable_on/tagging_spec.rb +31 -0
  33. data/spec/acts_as_taggable_on/tags_helper_spec.rb +28 -0
  34. data/spec/bm.rb +52 -0
  35. data/spec/models.rb +31 -0
  36. data/spec/schema.rb +43 -0
  37. data/spec/spec.opts +2 -0
  38. data/spec/spec_helper.rb +53 -0
  39. metadata +115 -0
@@ -0,0 +1,114 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe "acts_as_tagger" do
4
+ before(:each) do
5
+ clean_database!
6
+ end
7
+
8
+ context "Tagger Method Generation" do
9
+ before(:each) do
10
+ @tagger = TaggableUser.new()
11
+ end
12
+
13
+ it "should add #is_tagger? query method to the class-side" do
14
+ TaggableUser.should respond_to(:is_tagger?)
15
+ end
16
+
17
+ it "should return true from the class-side #is_tagger?" do
18
+ TaggableUser.is_tagger?.should be_true
19
+ end
20
+
21
+ it "should return false from the base #is_tagger?" do
22
+ ActiveRecord::Base.is_tagger?.should be_false
23
+ end
24
+
25
+ it "should add #is_tagger? query method to the singleton" do
26
+ @tagger.should respond_to(:is_tagger?)
27
+ end
28
+
29
+ it "should add #tag method on the instance-side" do
30
+ @tagger.should respond_to(:tag)
31
+ end
32
+
33
+ it "should generate an association for #owned_taggings and #owned_tags" do
34
+ @tagger.should respond_to(:owned_taggings, :owned_tags)
35
+ end
36
+ end
37
+
38
+ describe "#tag" do
39
+ context 'when called with a non-existent tag context' do
40
+ before(:each) do
41
+ @tagger = TaggableUser.new()
42
+ @taggable = TaggableModel.new(:name=>"Richard Prior")
43
+ end
44
+
45
+ it "should by default not throw an exception " do
46
+ @taggable.tag_list_on(:foo).should be_empty
47
+ lambda {
48
+ @tagger.tag(@taggable, :with=>'this, and, that', :on=>:foo)
49
+ }.should_not raise_error
50
+ end
51
+
52
+ it 'should by default create the tag context on-the-fly' do
53
+ @taggable.tag_list_on(:here_ond_now).should be_empty
54
+ @tagger.tag(@taggable, :with=>'that', :on => :here_ond_now)
55
+ @taggable.tag_list_on(:here_ond_now).should_not include('that')
56
+ @taggable.all_tags_list_on(:here_ond_now).should include('that')
57
+ end
58
+
59
+ it "should show all the tag list when both public and owned tags exist" do
60
+ @taggable.tag_list = 'ruby, python'
61
+ @tagger.tag(@taggable, :with => 'java, lisp', :on => :tags)
62
+ @taggable.all_tags_on(:tags).map(&:name).sort.should == %w(ruby python java lisp).sort
63
+ end
64
+
65
+ it "should not add owned tags to the common list" do
66
+ @taggable.tag_list = 'ruby, python'
67
+ @tagger.tag(@taggable, :with => 'java, lisp', :on => :tags)
68
+ @taggable.tag_list.should == %w(ruby python)
69
+ @tagger.tag(@taggable, :with => '', :on => :tags)
70
+ @taggable.tag_list.should == %w(ruby python)
71
+ end
72
+
73
+ it "should throw an exception when the default is over-ridden" do
74
+ @taggable.tag_list_on(:foo_boo).should be_empty
75
+ lambda {
76
+ @tagger.tag(@taggable, :with=>'this, and, that', :on=>:foo_boo, :force=>false)
77
+ }.should raise_error
78
+ end
79
+
80
+ it "should not create the tag context on-the-fly when the default is over-ridden" do
81
+ @taggable.tag_list_on(:foo_boo).should be_empty
82
+ @tagger.tag(@taggable, :with=>'this, and, that', :on=>:foo_boo, :force=>false) rescue
83
+ @taggable.tag_list_on(:foo_boo).should be_empty
84
+ end
85
+ end
86
+
87
+ context "when called by multiple tagger's" do
88
+ before(:each) do
89
+ @user_x = TaggableUser.create(:name => "User X")
90
+ @user_y = TaggableUser.create(:name => "User Y")
91
+ @taggable = TaggableModel.create(:name => 'acts_as_taggable_on', :tag_list => 'plugin')
92
+
93
+ @user_x.tag(@taggable, :with => 'ruby, rails', :on => :tags)
94
+ @user_y.tag(@taggable, :with => 'ruby, plugin', :on => :tags)
95
+
96
+ @user_y.tag(@taggable, :with => '', :on => :tags)
97
+ @user_y.tag(@taggable, :with => '', :on => :tags)
98
+ end
99
+
100
+ it "should delete owned tags" do
101
+ @user_y.owned_tags.should == []
102
+ end
103
+
104
+ it "should not delete other taggers tags" do
105
+ @user_x.owned_tags.should have(2).items
106
+ end
107
+
108
+ it "should not delete original tags" do
109
+ @taggable.all_tags_list_on(:tags).should include('plugin')
110
+ end
111
+ end
112
+ end
113
+
114
+ end
@@ -0,0 +1,70 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe TagList do
4
+ before(:each) do
5
+ @tag_list = TagList.new("awesome","radical")
6
+ end
7
+
8
+ it "should be an array" do
9
+ @tag_list.is_a?(Array).should be_true
10
+ end
11
+
12
+ it "should be able to be add a new tag word" do
13
+ @tag_list.add("cool")
14
+ @tag_list.include?("cool").should be_true
15
+ end
16
+
17
+ it "should be able to add delimited lists of words" do
18
+ @tag_list.add("cool, wicked", :parse => true)
19
+ @tag_list.include?("cool").should be_true
20
+ @tag_list.include?("wicked").should be_true
21
+ end
22
+
23
+ it "should be able to add delimited list of words with quoted delimiters" do
24
+ @tag_list.add("'cool, wicked', \"really cool, really wicked\"", :parse => true)
25
+ @tag_list.include?("cool, wicked").should be_true
26
+ @tag_list.include?("really cool, really wicked").should be_true
27
+ end
28
+
29
+ it "should be able to handle other uses of quotation marks correctly" do
30
+ @tag_list.add("john's cool car, mary's wicked toy", :parse => true)
31
+ @tag_list.include?("john's cool car").should be_true
32
+ @tag_list.include?("mary's wicked toy").should be_true
33
+ end
34
+
35
+ it "should be able to add an array of words" do
36
+ @tag_list.add(["cool", "wicked"], :parse => true)
37
+ @tag_list.include?("cool").should be_true
38
+ @tag_list.include?("wicked").should be_true
39
+ end
40
+
41
+ it "should be able to remove words" do
42
+ @tag_list.remove("awesome")
43
+ @tag_list.include?("awesome").should be_false
44
+ end
45
+
46
+ it "should be able to remove delimited lists of words" do
47
+ @tag_list.remove("awesome, radical", :parse => true)
48
+ @tag_list.should be_empty
49
+ end
50
+
51
+ it "should be able to remove an array of words" do
52
+ @tag_list.remove(["awesome", "radical"], :parse => true)
53
+ @tag_list.should be_empty
54
+ end
55
+
56
+ it "should give a delimited list of words when converted to string" do
57
+ @tag_list.to_s.should == "awesome, radical"
58
+ end
59
+
60
+ it "should quote escape tags with commas in them" do
61
+ @tag_list.add("cool","rad,bodacious")
62
+ @tag_list.to_s.should == "awesome, radical, cool, \"rad,bodacious\""
63
+ end
64
+
65
+ it "should be able to call to_s on a frozen tag list" do
66
+ @tag_list.freeze
67
+ lambda { @tag_list.add("cool","rad,bodacious") }.should raise_error
68
+ lambda { @tag_list.to_s }.should_not raise_error
69
+ end
70
+ end
@@ -0,0 +1,115 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe Tag do
4
+ before(:each) do
5
+ clean_database!
6
+ @tag = Tag.new
7
+ @user = TaggableModel.create(:name => "Pablo")
8
+ end
9
+
10
+ describe "named like any" do
11
+ before(:each) do
12
+ Tag.create(:name => "awesome")
13
+ Tag.create(:name => "epic")
14
+ end
15
+
16
+ it "should find both tags" do
17
+ Tag.named_like_any(["awesome", "epic"]).should have(2).items
18
+ end
19
+ end
20
+
21
+ describe "find or create by name" do
22
+ before(:each) do
23
+ @tag.name = "awesome"
24
+ @tag.save
25
+ end
26
+
27
+ it "should find by name" do
28
+ Tag.find_or_create_with_like_by_name("awesome").should == @tag
29
+ end
30
+
31
+ it "should find by name case insensitive" do
32
+ Tag.find_or_create_with_like_by_name("AWESOME").should == @tag
33
+ end
34
+
35
+ it "should create by name" do
36
+ lambda {
37
+ Tag.find_or_create_with_like_by_name("epic")
38
+ }.should change(Tag, :count).by(1)
39
+ end
40
+ end
41
+
42
+ describe "find or create all by any name" do
43
+ before(:each) do
44
+ @tag.name = "awesome"
45
+ @tag.save
46
+ end
47
+
48
+ it "should find by name" do
49
+ Tag.find_or_create_all_with_like_by_name("awesome").should == [@tag]
50
+ end
51
+
52
+ it "should find by name case insensitive" do
53
+ Tag.find_or_create_all_with_like_by_name("AWESOME").should == [@tag]
54
+ end
55
+
56
+ it "should create by name" do
57
+ lambda {
58
+ Tag.find_or_create_all_with_like_by_name("epic")
59
+ }.should change(Tag, :count).by(1)
60
+ end
61
+
62
+ it "should find or create by name" do
63
+ lambda {
64
+ Tag.find_or_create_all_with_like_by_name("awesome", "epic").map(&:name).should == ["awesome", "epic"]
65
+ }.should change(Tag, :count).by(1)
66
+ end
67
+
68
+ it "should return an empty array if no tags are specified" do
69
+ Tag.find_or_create_all_with_like_by_name([]).should == []
70
+ end
71
+ end
72
+
73
+ it "should require a name" do
74
+ @tag.valid?
75
+
76
+ if ActiveRecord::VERSION::MAJOR >= 3
77
+ @tag.errors[:name].should == ["can't be blank"]
78
+ else
79
+ @tag.errors[:name].should == "can't be blank"
80
+ end
81
+
82
+ @tag.name = "something"
83
+ @tag.valid?
84
+
85
+ if ActiveRecord::VERSION::MAJOR >= 3
86
+ @tag.errors[:name].should == []
87
+ else
88
+ @tag.errors[:name].should be_nil
89
+ end
90
+ end
91
+
92
+ it "should equal a tag with the same name" do
93
+ @tag.name = "awesome"
94
+ new_tag = Tag.new(:name => "awesome")
95
+ new_tag.should == @tag
96
+ end
97
+
98
+ it "should return its name when to_s is called" do
99
+ @tag.name = "cool"
100
+ @tag.to_s.should == "cool"
101
+ end
102
+
103
+ it "have named_scope named(something)" do
104
+ @tag.name = "cool"
105
+ @tag.save!
106
+ Tag.named('cool').should include(@tag)
107
+ end
108
+
109
+ it "have named_scope named_like(something)" do
110
+ @tag.name = "cool"
111
+ @tag.save!
112
+ @another_tag = Tag.create!(:name => "coolip")
113
+ Tag.named_like('cool').should include(@tag, @another_tag)
114
+ end
115
+ end
@@ -0,0 +1,306 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe "Taggable" do
4
+ before(:each) do
5
+ clean_database!
6
+ @taggable = TaggableModel.new(:name => "Bob Jones")
7
+ end
8
+
9
+ it "should have tag types" do
10
+ [:tags, :languages, :skills, :needs, :offerings].each do |type|
11
+ TaggableModel.tag_types.should include type
12
+ end
13
+
14
+ @taggable.tag_types.should == TaggableModel.tag_types
15
+ end
16
+
17
+ it "should have tag_counts_on" do
18
+ TaggableModel.tag_counts_on(:tags).all.should be_empty
19
+
20
+ @taggable.tag_list = ["awesome", "epic"]
21
+ @taggable.save
22
+
23
+ TaggableModel.tag_counts_on(:tags).length.should == 2
24
+ @taggable.tag_counts_on(:tags).length.should == 2
25
+ end
26
+
27
+ it "should be able to create tags" do
28
+ @taggable.skill_list = "ruby, rails, css"
29
+ @taggable.instance_variable_get("@skill_list").instance_of?(TagList).should be_true
30
+
31
+ lambda {
32
+ @taggable.save
33
+ }.should change(Tag, :count).by(3)
34
+
35
+ @taggable.reload
36
+ @taggable.skill_list.sort.should == %w(ruby rails css).sort
37
+ end
38
+
39
+ it "should be able to create tags through the tag list directly" do
40
+ @taggable.tag_list_on(:test).add("hello")
41
+ @taggable.tag_list_cache_on(:test).should_not be_empty
42
+ @taggable.tag_list_on(:test).should == ["hello"]
43
+
44
+ @taggable.save
45
+ @taggable.save_tags
46
+
47
+ @taggable.reload
48
+ @taggable.tag_list_on(:test).should == ["hello"]
49
+ end
50
+
51
+ it "should differentiate between contexts" do
52
+ @taggable.skill_list = "ruby, rails, css"
53
+ @taggable.tag_list = "ruby, bob, charlie"
54
+ @taggable.save
55
+ @taggable.reload
56
+ @taggable.skill_list.should include("ruby")
57
+ @taggable.skill_list.should_not include("bob")
58
+ end
59
+
60
+ it "should be able to remove tags through list alone" do
61
+ @taggable.skill_list = "ruby, rails, css"
62
+ @taggable.save
63
+ @taggable.reload
64
+ @taggable.should have(3).skills
65
+ @taggable.skill_list = "ruby, rails"
66
+ @taggable.save
67
+ @taggable.reload
68
+ @taggable.should have(2).skills
69
+ end
70
+
71
+ it "should be able to find by tag" do
72
+ @taggable.skill_list = "ruby, rails, css"
73
+ @taggable.save
74
+
75
+ TaggableModel.tagged_with("ruby").first.should == @taggable
76
+ end
77
+
78
+ it "should be able to find by tag with context" do
79
+ @taggable.skill_list = "ruby, rails, css"
80
+ @taggable.tag_list = "bob, charlie"
81
+ @taggable.save
82
+
83
+ TaggableModel.tagged_with("ruby").first.should == @taggable
84
+ TaggableModel.tagged_with("ruby, css").first.should == @taggable
85
+ TaggableModel.tagged_with("bob", :on => :skills).first.should_not == @taggable
86
+ TaggableModel.tagged_with("bob", :on => :tags).first.should == @taggable
87
+ end
88
+
89
+ it "should not care about case" do
90
+ bob = TaggableModel.create(:name => "Bob", :tag_list => "ruby")
91
+ frank = TaggableModel.create(:name => "Frank", :tag_list => "Ruby")
92
+
93
+ Tag.find(:all).size.should == 1
94
+ TaggableModel.tagged_with("ruby").to_a.should == TaggableModel.tagged_with("Ruby").to_a
95
+ end
96
+
97
+ it "should be able to get tag counts on model as a whole" do
98
+ bob = TaggableModel.create(:name => "Bob", :tag_list => "ruby, rails, css")
99
+ frank = TaggableModel.create(:name => "Frank", :tag_list => "ruby, rails")
100
+ charlie = TaggableModel.create(:name => "Charlie", :skill_list => "ruby")
101
+ TaggableModel.tag_counts.all.should_not be_empty
102
+ TaggableModel.skill_counts.all.should_not be_empty
103
+ end
104
+
105
+ it "should be able to get all tag counts on model as whole" do
106
+ bob = TaggableModel.create(:name => "Bob", :tag_list => "ruby, rails, css")
107
+ frank = TaggableModel.create(:name => "Frank", :tag_list => "ruby, rails")
108
+ charlie = TaggableModel.create(:name => "Charlie", :skill_list => "ruby")
109
+
110
+ TaggableModel.all_tag_counts.all.should_not be_empty
111
+ TaggableModel.all_tag_counts.first.count.should == 3 # ruby
112
+ end
113
+
114
+ if ActiveRecord::VERSION::MAJOR >= 3
115
+ it "should not return read-only records" do
116
+ TaggableModel.create(:name => "Bob", :tag_list => "ruby, rails, css")
117
+ TaggableModel.tagged_with("ruby").first.should_not be_readonly
118
+ end
119
+ else
120
+ xit "should not return read-only records" do
121
+ # apparantly, there is no way to set readonly to false in a scope if joins are made
122
+ end
123
+
124
+ it "should be possible to return writable records" do
125
+ TaggableModel.create(:name => "Bob", :tag_list => "ruby, rails, css")
126
+ TaggableModel.tagged_with("ruby").first(:readonly => false).should_not be_readonly
127
+ end
128
+ end
129
+
130
+ it "should be able to get scoped tag counts" do
131
+ bob = TaggableModel.create(:name => "Bob", :tag_list => "ruby, rails, css")
132
+ frank = TaggableModel.create(:name => "Frank", :tag_list => "ruby, rails")
133
+ charlie = TaggableModel.create(:name => "Charlie", :skill_list => "ruby")
134
+
135
+ TaggableModel.tagged_with("ruby").tag_counts.first.count.should == 2 # ruby
136
+ TaggableModel.tagged_with("ruby").skill_counts.first.count.should == 1 # ruby
137
+ end
138
+
139
+ it "should be able to get all scoped tag counts" do
140
+ bob = TaggableModel.create(:name => "Bob", :tag_list => "ruby, rails, css")
141
+ frank = TaggableModel.create(:name => "Frank", :tag_list => "ruby, rails")
142
+ charlie = TaggableModel.create(:name => "Charlie", :skill_list => "ruby")
143
+
144
+ TaggableModel.tagged_with("ruby").all_tag_counts.first.count.should == 3 # ruby
145
+ end
146
+
147
+ it 'should only return tag counts for the available scope' do
148
+ bob = TaggableModel.create(:name => "Bob", :tag_list => "ruby, rails, css")
149
+ frank = TaggableModel.create(:name => "Frank", :tag_list => "ruby, rails")
150
+ charlie = TaggableModel.create(:name => "Charlie", :skill_list => "ruby, java")
151
+
152
+ TaggableModel.tagged_with('rails').all_tag_counts.should have(3).items
153
+ TaggableModel.tagged_with('rails').all_tag_counts.any? { |tag| tag.name == 'java' }.should be_false
154
+
155
+ # Test specific join syntaxes:
156
+ frank.untaggable_models.create!
157
+ TaggableModel.tagged_with('rails').scoped(:joins => :untaggable_models).all_tag_counts.should have(2).items
158
+ TaggableModel.tagged_with('rails').scoped(:joins => { :untaggable_models => :taggable_model }).all_tag_counts.should have(2).items
159
+ TaggableModel.tagged_with('rails').scoped(:joins => [:untaggable_models]).all_tag_counts.should have(2).items
160
+ end
161
+
162
+ it "should be able to set a custom tag context list" do
163
+ bob = TaggableModel.create(:name => "Bob")
164
+ bob.set_tag_list_on(:rotors, "spinning, jumping")
165
+ bob.tag_list_on(:rotors).should == ["spinning","jumping"]
166
+ bob.save
167
+ bob.reload
168
+ bob.tags_on(:rotors).should_not be_empty
169
+ end
170
+
171
+ it "should be able to find tagged" do
172
+ bob = TaggableModel.create(:name => "Bob", :tag_list => "fitter, happier, more productive", :skill_list => "ruby, rails, css")
173
+ frank = TaggableModel.create(:name => "Frank", :tag_list => "weaker, depressed, inefficient", :skill_list => "ruby, rails, css")
174
+ steve = TaggableModel.create(:name => 'Steve', :tag_list => 'fitter, happier, more productive', :skill_list => 'c++, java, ruby')
175
+
176
+ TaggableModel.tagged_with("ruby", :order => 'taggable_models.name').to_a.should == [bob, frank, steve]
177
+ TaggableModel.tagged_with("ruby, rails", :order => 'taggable_models.name').to_a.should == [bob, frank]
178
+ TaggableModel.tagged_with(["ruby", "rails"], :order => 'taggable_models.name').to_a.should == [bob, frank]
179
+ end
180
+
181
+ it "should be able to find tagged with any tag" do
182
+ bob = TaggableModel.create(:name => "Bob", :tag_list => "fitter, happier, more productive", :skill_list => "ruby, rails, css")
183
+ frank = TaggableModel.create(:name => "Frank", :tag_list => "weaker, depressed, inefficient", :skill_list => "ruby, rails, css")
184
+ steve = TaggableModel.create(:name => 'Steve', :tag_list => 'fitter, happier, more productive', :skill_list => 'c++, java, ruby')
185
+
186
+ TaggableModel.tagged_with(["ruby", "java"], :order => 'taggable_models.name', :any => true).to_a.should == [bob, frank, steve]
187
+ TaggableModel.tagged_with(["c++", "fitter"], :order => 'taggable_models.name', :any => true).to_a.should == [bob, steve]
188
+ TaggableModel.tagged_with(["depressed", "css"], :order => 'taggable_models.name', :any => true).to_a.should == [bob, frank]
189
+ end
190
+
191
+ it "should be able to find tagged on a custom tag context" do
192
+ bob = TaggableModel.create(:name => "Bob")
193
+ bob.set_tag_list_on(:rotors, "spinning, jumping")
194
+ bob.tag_list_on(:rotors).should == ["spinning","jumping"]
195
+ bob.save
196
+
197
+ TaggableModel.tagged_with("spinning", :on => :rotors).to_a.should == [bob]
198
+ end
199
+
200
+ it "should be able to use named scopes to chain tag finds" do
201
+ bob = TaggableModel.create(:name => "Bob", :tag_list => "fitter, happier, more productive", :skill_list => "ruby, rails, css")
202
+ frank = TaggableModel.create(:name => "Frank", :tag_list => "weaker, depressed, inefficient", :skill_list => "ruby, rails, css")
203
+ steve = TaggableModel.create(:name => 'Steve', :tag_list => 'fitter, happier, more productive', :skill_list => 'c++, java, python')
204
+
205
+ # Let's only find those productive Rails developers
206
+ TaggableModel.tagged_with('rails', :on => :skills, :order => 'taggable_models.name').to_a.should == [bob, frank]
207
+ TaggableModel.tagged_with('happier', :on => :tags, :order => 'taggable_models.name').to_a.should == [bob, steve]
208
+ TaggableModel.tagged_with('rails', :on => :skills).tagged_with('happier', :on => :tags).to_a.should == [bob]
209
+ TaggableModel.tagged_with('rails').tagged_with('happier', :on => :tags).to_a.should == [bob]
210
+ end
211
+
212
+ it "should be able to find tagged with only the matching tags" do
213
+ bob = TaggableModel.create(:name => "Bob", :tag_list => "lazy, happier")
214
+ frank = TaggableModel.create(:name => "Frank", :tag_list => "fitter, happier, inefficient")
215
+ steve = TaggableModel.create(:name => 'Steve', :tag_list => "fitter, happier")
216
+
217
+ TaggableModel.tagged_with("fitter, happier", :match_all => true).to_a.should == [steve]
218
+ end
219
+
220
+ it "should be able to find tagged with some excluded tags" do
221
+ bob = TaggableModel.create(:name => "Bob", :tag_list => "happier, lazy")
222
+ frank = TaggableModel.create(:name => "Frank", :tag_list => "happier")
223
+ steve = TaggableModel.create(:name => 'Steve', :tag_list => "happier")
224
+
225
+ TaggableModel.tagged_with("lazy", :exclude => true).to_a.should == [frank, steve]
226
+ end
227
+
228
+ it "should not create duplicate taggings" do
229
+ bob = TaggableModel.create(:name => "Bob")
230
+ lambda {
231
+ bob.tag_list << "happier"
232
+ bob.tag_list << "happier"
233
+ bob.save
234
+ }.should change(Tagging, :count).by(1)
235
+ end
236
+
237
+ describe "Associations" do
238
+ before(:each) do
239
+ @taggable = TaggableModel.create(:tag_list => "awesome, epic")
240
+ end
241
+
242
+ it "should not remove tags when creating associated objects" do
243
+ @taggable.untaggable_models.create!
244
+ @taggable.reload
245
+ @taggable.tag_list.should have(2).items
246
+ end
247
+ end
248
+
249
+ describe "grouped_column_names_for method" do
250
+ it "should return all column names joined for Tag GROUP clause" do
251
+ @taggable.grouped_column_names_for(Tag).should == "tags.id, tags.name"
252
+ end
253
+
254
+ it "should return all column names joined for TaggableModel GROUP clause" do
255
+ @taggable.grouped_column_names_for(TaggableModel).should == "taggable_models.id, taggable_models.name, taggable_models.type"
256
+ end
257
+ end
258
+
259
+ describe "Single Table Inheritance" do
260
+ before do
261
+ @taggable = TaggableModel.new(:name => "taggable")
262
+ @inherited_same = InheritingTaggableModel.new(:name => "inherited same")
263
+ @inherited_different = AlteredInheritingTaggableModel.new(:name => "inherited different")
264
+ end
265
+
266
+ it "should be able to save tags for inherited models" do
267
+ @inherited_same.tag_list = "bob, kelso"
268
+ @inherited_same.save
269
+ InheritingTaggableModel.tagged_with("bob").first.should == @inherited_same
270
+ end
271
+
272
+ it "should find STI tagged models on the superclass" do
273
+ @inherited_same.tag_list = "bob, kelso"
274
+ @inherited_same.save
275
+ TaggableModel.tagged_with("bob").first.should == @inherited_same
276
+ end
277
+
278
+ it "should be able to add on contexts only to some subclasses" do
279
+ @inherited_different.part_list = "fork, spoon"
280
+ @inherited_different.save
281
+ InheritingTaggableModel.tagged_with("fork", :on => :parts).should be_empty
282
+ AlteredInheritingTaggableModel.tagged_with("fork", :on => :parts).first.should == @inherited_different
283
+ end
284
+
285
+ it "should have different tag_counts_on for inherited models" do
286
+ @inherited_same.tag_list = "bob, kelso"
287
+ @inherited_same.save!
288
+ @inherited_different.tag_list = "fork, spoon"
289
+ @inherited_different.save!
290
+
291
+ InheritingTaggableModel.tag_counts_on(:tags).map(&:name).should == %w(bob kelso)
292
+ AlteredInheritingTaggableModel.tag_counts_on(:tags).map(&:name).should == %w(fork spoon)
293
+ TaggableModel.tag_counts_on(:tags).map(&:name).should == %w(bob kelso fork spoon)
294
+ end
295
+
296
+ it 'should store same tag without validation conflict' do
297
+ @taggable.tag_list = 'one'
298
+ @taggable.save!
299
+
300
+ @inherited_same.tag_list = 'one'
301
+ @inherited_same.save!
302
+
303
+ @inherited_same.update_attributes! :name => 'foo'
304
+ end
305
+ end
306
+ end