acts_as_taggable_on 3.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +11 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +9 -0
  5. data/Appraisals +7 -0
  6. data/Gemfile +5 -0
  7. data/Guardfile +5 -0
  8. data/LICENSE.md +20 -0
  9. data/README.md +309 -0
  10. data/Rakefile +13 -0
  11. data/UPGRADING +7 -0
  12. data/acts_as_taggable_on.gemspec +35 -0
  13. data/db/migrate/1_acts_as_taggable_on_migration.rb +30 -0
  14. data/db/migrate/2_add_missing_unique_indices.rb +21 -0
  15. data/gemfiles/rails_3.gemfile +8 -0
  16. data/gemfiles/rails_4.gemfile +8 -0
  17. data/lib/acts_as_taggable_on.rb +61 -0
  18. data/lib/acts_as_taggable_on/acts_as_taggable_on/cache.rb +82 -0
  19. data/lib/acts_as_taggable_on/acts_as_taggable_on/collection.rb +187 -0
  20. data/lib/acts_as_taggable_on/acts_as_taggable_on/compatibility.rb +34 -0
  21. data/lib/acts_as_taggable_on/acts_as_taggable_on/core.rb +394 -0
  22. data/lib/acts_as_taggable_on/acts_as_taggable_on/dirty.rb +37 -0
  23. data/lib/acts_as_taggable_on/acts_as_taggable_on/ownership.rb +135 -0
  24. data/lib/acts_as_taggable_on/acts_as_taggable_on/related.rb +84 -0
  25. data/lib/acts_as_taggable_on/engine.rb +6 -0
  26. data/lib/acts_as_taggable_on/tag.rb +119 -0
  27. data/lib/acts_as_taggable_on/tag_list.rb +101 -0
  28. data/lib/acts_as_taggable_on/taggable.rb +105 -0
  29. data/lib/acts_as_taggable_on/tagger.rb +76 -0
  30. data/lib/acts_as_taggable_on/tagging.rb +34 -0
  31. data/lib/acts_as_taggable_on/tags_helper.rb +15 -0
  32. data/lib/acts_as_taggable_on/utils.rb +34 -0
  33. data/lib/acts_as_taggable_on/version.rb +4 -0
  34. data/spec/acts_as_taggable_on/acts_as_taggable_on_spec.rb +265 -0
  35. data/spec/acts_as_taggable_on/acts_as_tagger_spec.rb +114 -0
  36. data/spec/acts_as_taggable_on/caching_spec.rb +77 -0
  37. data/spec/acts_as_taggable_on/related_spec.rb +143 -0
  38. data/spec/acts_as_taggable_on/single_table_inheritance_spec.rb +187 -0
  39. data/spec/acts_as_taggable_on/tag_list_spec.rb +126 -0
  40. data/spec/acts_as_taggable_on/tag_spec.rb +211 -0
  41. data/spec/acts_as_taggable_on/taggable_spec.rb +623 -0
  42. data/spec/acts_as_taggable_on/tagger_spec.rb +137 -0
  43. data/spec/acts_as_taggable_on/tagging_spec.rb +28 -0
  44. data/spec/acts_as_taggable_on/tags_helper_spec.rb +44 -0
  45. data/spec/acts_as_taggable_on/utils_spec.rb +21 -0
  46. data/spec/bm.rb +52 -0
  47. data/spec/database.yml.sample +19 -0
  48. data/spec/models.rb +58 -0
  49. data/spec/schema.rb +65 -0
  50. data/spec/spec_helper.rb +87 -0
  51. metadata +248 -0
@@ -0,0 +1,126 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ describe ActsAsTaggableOn::TagList do
5
+ let(:tag_list) { ActsAsTaggableOn::TagList.new("awesome","radical") }
6
+
7
+ it { should be_kind_of Array }
8
+
9
+ it "#from should return empty array if empty array is passed" do
10
+ ActsAsTaggableOn::TagList.from([]).should be_empty
11
+ end
12
+
13
+ describe "#add" do
14
+ it "should be able to be add a new tag word" do
15
+ tag_list.add("cool")
16
+ tag_list.include?("cool").should be_true
17
+ end
18
+
19
+ it "should be able to add delimited lists of words" do
20
+ tag_list.add("cool, wicked", :parse => true)
21
+ tag_list.should include("cool", "wicked")
22
+ end
23
+
24
+ it "should be able to add delimited list of words with quoted delimiters" do
25
+ tag_list.add("'cool, wicked', \"really cool, really wicked\"", :parse => true)
26
+ tag_list.should include("cool, wicked", "really cool, really wicked")
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.should include("john's cool car", "mary's wicked toy")
32
+ end
33
+
34
+ it "should be able to add an array of words" do
35
+ tag_list.add(["cool", "wicked"], :parse => true)
36
+ tag_list.should include("cool", "wicked")
37
+ end
38
+
39
+ it "should quote escape tags with commas in them" do
40
+ tag_list.add("cool","rad,bodacious")
41
+ tag_list.to_s.should == "awesome, radical, cool, \"rad,bodacious\""
42
+ end
43
+
44
+ end
45
+
46
+ describe "#remove" do
47
+ it "should be able to remove words" do
48
+ tag_list.remove("awesome")
49
+ tag_list.should_not include("awesome")
50
+ end
51
+
52
+ it "should be able to remove delimited lists of words" do
53
+ tag_list.remove("awesome, radical", :parse => true)
54
+ tag_list.should be_empty
55
+ end
56
+
57
+ it "should be able to remove an array of words" do
58
+ tag_list.remove(["awesome", "radical"], :parse => true)
59
+ tag_list.should be_empty
60
+ end
61
+ end
62
+
63
+ describe "#to_s" do
64
+ it "should give a delimited list of words when converted to string" do
65
+ tag_list.to_s.should == "awesome, radical"
66
+ end
67
+
68
+ it "should be able to call to_s on a frozen tag list" do
69
+ tag_list.freeze
70
+ lambda { tag_list.add("cool","rad,bodacious") }.should raise_error
71
+ lambda { tag_list.to_s }.should_not raise_error
72
+ end
73
+ end
74
+
75
+ describe "cleaning" do
76
+ it "should parameterize if force_parameterize is set to true" do
77
+ ActsAsTaggableOn.force_parameterize = true
78
+ tag_list = ActsAsTaggableOn::TagList.new("awesome()","radical)(cc")
79
+
80
+ tag_list.to_s.should == "awesome, radical-cc"
81
+ ActsAsTaggableOn.force_parameterize = false
82
+ end
83
+
84
+ it "should lowercase if force_lowercase is set to true" do
85
+ ActsAsTaggableOn.force_lowercase = true
86
+
87
+ tag_list = ActsAsTaggableOn::TagList.new("aweSomE","RaDicaL","Entrée")
88
+ tag_list.to_s.should == "awesome, radical, entrée"
89
+
90
+ ActsAsTaggableOn.force_lowercase = false
91
+ end
92
+
93
+ end
94
+
95
+ describe "Multiple Delimiter" do
96
+ before do
97
+ @old_delimiter = ActsAsTaggableOn.delimiter
98
+ end
99
+
100
+ after do
101
+ ActsAsTaggableOn.delimiter = @old_delimiter
102
+ end
103
+
104
+ it "should separate tags by delimiters" do
105
+ ActsAsTaggableOn.delimiter = [',', ' ', '\|']
106
+ tag_list = ActsAsTaggableOn::TagList.from "cool, data|I have"
107
+ tag_list.to_s.should == 'cool, data, I, have'
108
+ end
109
+
110
+ it "should escape quote" do
111
+ ActsAsTaggableOn.delimiter = [',', ' ', '\|']
112
+ tag_list = ActsAsTaggableOn::TagList.from "'I have'|cool, data"
113
+ tag_list.to_s.should == '"I have", cool, data'
114
+
115
+ tag_list = ActsAsTaggableOn::TagList.from '"I, have"|cool, data'
116
+ tag_list.to_s.should == '"I, have", cool, data'
117
+ end
118
+
119
+ it "should work for utf8 delimiter and long delimiter" do
120
+ ActsAsTaggableOn.delimiter = [',', '的', '可能是']
121
+ tag_list = ActsAsTaggableOn::TagList.from "我的东西可能是不见了,还好有备份"
122
+ tag_list.to_s.should == "我, 东西, 不见了, 还好有备份"
123
+ end
124
+ end
125
+
126
+ end
@@ -0,0 +1,211 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ describe ActsAsTaggableOn::Tag do
5
+ before(:each) do
6
+ clean_database!
7
+ @tag = ActsAsTaggableOn::Tag.new
8
+ @user = TaggableModel.create(:name => "Pablo")
9
+ end
10
+
11
+ describe "named like any" do
12
+ before(:each) do
13
+ ActsAsTaggableOn::Tag.create(:name => "Awesome")
14
+ ActsAsTaggableOn::Tag.create(:name => "awesome")
15
+ ActsAsTaggableOn::Tag.create(:name => "epic")
16
+ end
17
+
18
+ it "should find both tags" do
19
+ ActsAsTaggableOn::Tag.named_like_any(["awesome", "epic"]).should have(3).items
20
+ end
21
+ end
22
+
23
+ describe "find or create by name" do
24
+ before(:each) do
25
+ @tag.name = "awesome"
26
+ @tag.save
27
+ end
28
+
29
+ it "should find by name" do
30
+ ActsAsTaggableOn::Tag.find_or_create_with_like_by_name("awesome").should == @tag
31
+ end
32
+
33
+ it "should find by name case insensitive" do
34
+ ActsAsTaggableOn::Tag.find_or_create_with_like_by_name("AWESOME").should == @tag
35
+ end
36
+
37
+ it "should create by name" do
38
+ lambda {
39
+ ActsAsTaggableOn::Tag.find_or_create_with_like_by_name("epic")
40
+ }.should change(ActsAsTaggableOn::Tag, :count).by(1)
41
+ end
42
+ end
43
+
44
+ unless ActsAsTaggableOn::Tag.using_sqlite?
45
+ describe "find or create by unicode name" do
46
+ before(:each) do
47
+ @tag.name = "привет"
48
+ @tag.save
49
+ end
50
+
51
+ it "should find by name" do
52
+ ActsAsTaggableOn::Tag.find_or_create_with_like_by_name("привет").should == @tag
53
+ end
54
+
55
+ it "should find by name case insensitive" do
56
+ ActsAsTaggableOn::Tag.find_or_create_with_like_by_name("ПРИВЕТ").should == @tag
57
+ end
58
+ end
59
+ end
60
+
61
+ describe "find or create all by any name" do
62
+ before(:each) do
63
+ @tag.name = "awesome"
64
+ @tag.save
65
+ end
66
+
67
+ it "should find by name" do
68
+ ActsAsTaggableOn::Tag.find_or_create_all_with_like_by_name("awesome").should == [@tag]
69
+ end
70
+
71
+ it "should find by name case insensitive" do
72
+ ActsAsTaggableOn::Tag.find_or_create_all_with_like_by_name("AWESOME").should == [@tag]
73
+ end
74
+
75
+ it "should create by name" do
76
+ lambda {
77
+ ActsAsTaggableOn::Tag.find_or_create_all_with_like_by_name("epic")
78
+ }.should change(ActsAsTaggableOn::Tag, :count).by(1)
79
+ end
80
+
81
+ it "should find or create by name" do
82
+ lambda {
83
+ ActsAsTaggableOn::Tag.find_or_create_all_with_like_by_name("awesome", "epic").map(&:name).should == ["awesome", "epic"]
84
+ }.should change(ActsAsTaggableOn::Tag, :count).by(1)
85
+ end
86
+
87
+ it "should return an empty array if no tags are specified" do
88
+ ActsAsTaggableOn::Tag.find_or_create_all_with_like_by_name([]).should == []
89
+ end
90
+ end
91
+
92
+ it "should require a name" do
93
+ @tag.valid?
94
+
95
+ @tag.errors[:name].should == ["can't be blank"]
96
+
97
+ @tag.name = "something"
98
+ @tag.valid?
99
+
100
+ @tag.errors[:name].should == []
101
+ end
102
+
103
+ it "should limit the name length to 255 or less characters" do
104
+ @tag.name = "fgkgnkkgjymkypbuozmwwghblmzpqfsgjasflblywhgkwndnkzeifalfcpeaeqychjuuowlacmuidnnrkprgpcpybarbkrmziqihcrxirlokhnzfvmtzixgvhlxzncyywficpraxfnjptxxhkqmvicbcdcynkjvziefqzyndxkjmsjlvyvbwraklbalykyxoliqdlreeykuphdtmzfdwpphmrqvwvqffojkqhlzvinqajsxbszyvrqqyzusxranr"
105
+ @tag.valid?
106
+ @tag.errors[:name].should == ["is too long (maximum is 255 characters)"]
107
+
108
+ @tag.name = "fgkgnkkgjymkypbuozmwwghblmzpqfsgjasflblywhgkwndnkzeifalfcpeaeqychjuuowlacmuidnnrkprgpcpybarbkrmziqihcrxirlokhnzfvmtzixgvhlxzncyywficpraxfnjptxxhkqmvicbcdcynkjvziefqzyndxkjmsjlvyvbwraklbalykyxoliqdlreeykuphdtmzfdwpphmrqvwvqffojkqhlzvinqajsxbszyvrqqyzusxran"
109
+ @tag.valid?
110
+ @tag.errors[:name].should == []
111
+ end
112
+
113
+ it "should equal a tag with the same name" do
114
+ @tag.name = "awesome"
115
+ new_tag = ActsAsTaggableOn::Tag.new(:name => "awesome")
116
+ new_tag.should == @tag
117
+ end
118
+
119
+ it "should return its name when to_s is called" do
120
+ @tag.name = "cool"
121
+ @tag.to_s.should == "cool"
122
+ end
123
+
124
+ it "have named_scope named(something)" do
125
+ @tag.name = "cool"
126
+ @tag.save!
127
+ ActsAsTaggableOn::Tag.named('cool').should include(@tag)
128
+ end
129
+
130
+ it "have named_scope named_like(something)" do
131
+ @tag.name = "cool"
132
+ @tag.save!
133
+ @another_tag = ActsAsTaggableOn::Tag.create!(:name => "coolip")
134
+ ActsAsTaggableOn::Tag.named_like('cool').should include(@tag, @another_tag)
135
+ end
136
+
137
+ describe "escape wildcard symbols in like requests" do
138
+ before(:each) do
139
+ @tag.name = "cool"
140
+ @tag.save
141
+ @another_tag = ActsAsTaggableOn::Tag.create!(:name => "coo%")
142
+ @another_tag2 = ActsAsTaggableOn::Tag.create!(:name => "coolish")
143
+ end
144
+
145
+ it "return escaped result when '%' char present in tag" do
146
+ ActsAsTaggableOn::Tag.named_like('coo%').should_not include(@tag)
147
+ ActsAsTaggableOn::Tag.named_like('coo%').should include(@another_tag)
148
+ end
149
+
150
+ end
151
+
152
+ describe "when using strict_case_match" do
153
+ before do
154
+ ActsAsTaggableOn.strict_case_match = true
155
+ @tag.name = "awesome"
156
+ @tag.save!
157
+ end
158
+
159
+ after do
160
+ ActsAsTaggableOn.strict_case_match = false
161
+ end
162
+
163
+ it "should find by name" do
164
+ ActsAsTaggableOn::Tag.find_or_create_with_like_by_name("awesome").should == @tag
165
+ end
166
+
167
+ it "should find by name case sensitively" do
168
+ expect {
169
+ ActsAsTaggableOn::Tag.find_or_create_with_like_by_name("AWESOME")
170
+ }.to change(ActsAsTaggableOn::Tag, :count)
171
+
172
+ ActsAsTaggableOn::Tag.last.name.should == "AWESOME"
173
+ end
174
+
175
+ it "should have a named_scope named(something) that matches exactly" do
176
+ uppercase_tag = ActsAsTaggableOn::Tag.create(:name => "Cool")
177
+ @tag.name = "cool"
178
+ @tag.save!
179
+
180
+ ActsAsTaggableOn::Tag.named('cool').should include(@tag)
181
+ ActsAsTaggableOn::Tag.named('cool').should_not include(uppercase_tag)
182
+ end
183
+ end
184
+
185
+ describe "name uniqeness validation" do
186
+ let(:duplicate_tag) { ActsAsTaggableOn::Tag.new(:name => 'ror') }
187
+
188
+ before { ActsAsTaggableOn::Tag.create(:name => 'ror') }
189
+
190
+ context "when don't need unique names" do
191
+ it "should not run uniqueness validation" do
192
+ duplicate_tag.stub(:validates_name_uniqueness?).and_return(false)
193
+ duplicate_tag.save
194
+ duplicate_tag.should be_persisted
195
+ end
196
+ end
197
+
198
+ context "when do need unique names" do
199
+ it "should run uniqueness validation" do
200
+ duplicate_tag.should_not be_valid
201
+ end
202
+
203
+ it "add error to name" do
204
+ duplicate_tag.save
205
+
206
+ duplicate_tag.should have(1).errors
207
+ duplicate_tag.errors.messages[:name].should include('has already been taken')
208
+ end
209
+ end
210
+ end
211
+ end
@@ -0,0 +1,623 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Taggable To Preserve Order" do
4
+ before(:each) do
5
+ clean_database!
6
+ @taggable = OrderedTaggableModel.new(:name => "Bob Jones")
7
+ end
8
+
9
+ it "should have tag types" do
10
+ [:tags, :colours].each do |type|
11
+ OrderedTaggableModel.tag_types.should include type
12
+ end
13
+
14
+ @taggable.tag_types.should == OrderedTaggableModel.tag_types
15
+ end
16
+
17
+ it "should have tag associations" do
18
+ [:tags, :colours].each do |type|
19
+ @taggable.respond_to?(type).should be_true
20
+ @taggable.respond_to?("#{type.to_s.singularize}_taggings").should be_true
21
+ end
22
+ end
23
+
24
+ # it "should have tag associations ordered by id" do
25
+ # [:tags, :colours].each do |type|
26
+ # OrderedTaggableModel.reflect_on_association(type).options[:order].should include('id')
27
+ # OrderedTaggableModel.reflect_on_association("#{type.to_s.singularize}_taggings".to_sym).options[:order].should include('id')
28
+ # end
29
+ # end
30
+
31
+ it "should have tag methods" do
32
+ [:tags, :colours].each do |type|
33
+ @taggable.respond_to?("#{type.to_s.singularize}_list").should be_true
34
+ @taggable.respond_to?("#{type.to_s.singularize}_list=").should be_true
35
+ @taggable.respond_to?("all_#{type.to_s}_list").should be_true
36
+ end
37
+ end
38
+
39
+ it "should return tag list in the order the tags were created" do
40
+ # create
41
+ @taggable.tag_list = "rails, ruby, css"
42
+ @taggable.instance_variable_get("@tag_list").instance_of?(ActsAsTaggableOn::TagList).should be_true
43
+
44
+ lambda {
45
+ @taggable.save
46
+ }.should change(ActsAsTaggableOn::Tag, :count).by(3)
47
+
48
+ @taggable.reload
49
+ @taggable.tag_list.should == %w(rails ruby css)
50
+
51
+ # update
52
+ @taggable.tag_list = "pow, ruby, rails"
53
+ @taggable.save
54
+
55
+ @taggable.reload
56
+ @taggable.tag_list.should == %w(pow ruby rails)
57
+
58
+ # update with no change
59
+ @taggable.tag_list = "pow, ruby, rails"
60
+ @taggable.save
61
+
62
+ @taggable.reload
63
+ @taggable.tag_list.should == %w(pow ruby rails)
64
+
65
+ # update to clear tags
66
+ @taggable.tag_list = ""
67
+ @taggable.save
68
+
69
+ @taggable.reload
70
+ @taggable.tag_list.should == []
71
+ end
72
+
73
+ it "should return tag objects in the order the tags were created" do
74
+ # create
75
+ @taggable.tag_list = "pow, ruby, rails"
76
+ @taggable.instance_variable_get("@tag_list").instance_of?(ActsAsTaggableOn::TagList).should be_true
77
+
78
+ lambda {
79
+ @taggable.save
80
+ }.should change(ActsAsTaggableOn::Tag, :count).by(3)
81
+
82
+ @taggable.reload
83
+ @taggable.tags.map{|t| t.name}.should == %w(pow ruby rails)
84
+
85
+ # update
86
+ @taggable.tag_list = "rails, ruby, css, pow"
87
+ @taggable.save
88
+
89
+ @taggable.reload
90
+ @taggable.tags.map{|t| t.name}.should == %w(rails ruby css pow)
91
+ end
92
+
93
+ it "should return tag objects in tagging id order" do
94
+ # create
95
+ @taggable.tag_list = "pow, ruby, rails"
96
+ @taggable.save
97
+
98
+ @taggable.reload
99
+ ids = @taggable.tags.map{|t| t.taggings.first.id}
100
+ ids.should == ids.sort
101
+
102
+ # update
103
+ @taggable.tag_list = "rails, ruby, css, pow"
104
+ @taggable.save
105
+
106
+ @taggable.reload
107
+ ids = @taggable.tags.map{|t| t.taggings.first.id}
108
+ ids.should == ids.sort
109
+ end
110
+ end
111
+
112
+ describe "Taggable" do
113
+ before(:each) do
114
+ clean_database!
115
+ @taggable = TaggableModel.new(:name => "Bob Jones")
116
+ @taggables = [@taggable, TaggableModel.new(:name => "John Doe")]
117
+ end
118
+
119
+ it "should have tag types" do
120
+ [:tags, :languages, :skills, :needs, :offerings].each do |type|
121
+ TaggableModel.tag_types.should include type
122
+ end
123
+
124
+ @taggable.tag_types.should == TaggableModel.tag_types
125
+ end
126
+
127
+ it "should have tag_counts_on" do
128
+ TaggableModel.tag_counts_on(:tags).should be_empty
129
+
130
+ @taggable.tag_list = ["awesome", "epic"]
131
+ @taggable.save
132
+
133
+ TaggableModel.tag_counts_on(:tags).length.should == 2
134
+ @taggable.tag_counts_on(:tags).length.should == 2
135
+ end
136
+
137
+ it "should have tags_on" do
138
+ TaggableModel.tags_on(:tags).should be_empty
139
+
140
+ @taggable.tag_list = ["awesome", "epic"]
141
+ @taggable.save
142
+
143
+ TaggableModel.tags_on(:tags).length.should == 2
144
+ @taggable.tags_on(:tags).length.should == 2
145
+ end
146
+
147
+ it "should return [] right after create" do
148
+ blank_taggable = TaggableModel.new(:name => "Bob Jones")
149
+ blank_taggable.tag_list.should == []
150
+ end
151
+
152
+ it "should be able to create tags" do
153
+ @taggable.skill_list = "ruby, rails, css"
154
+ @taggable.instance_variable_get("@skill_list").instance_of?(ActsAsTaggableOn::TagList).should be_true
155
+
156
+ lambda {
157
+ @taggable.save
158
+ }.should change(ActsAsTaggableOn::Tag, :count).by(3)
159
+
160
+ @taggable.reload
161
+ @taggable.skill_list.sort.should == %w(ruby rails css).sort
162
+ end
163
+
164
+ it "should be able to create tags through the tag list directly" do
165
+ @taggable.tag_list_on(:test).add("hello")
166
+ @taggable.tag_list_cache_on(:test).should_not be_empty
167
+ @taggable.tag_list_on(:test).should == ["hello"]
168
+
169
+ @taggable.save
170
+ @taggable.save_tags
171
+
172
+ @taggable.reload
173
+ @taggable.tag_list_on(:test).should == ["hello"]
174
+ end
175
+
176
+ it "should differentiate between contexts" do
177
+ @taggable.skill_list = "ruby, rails, css"
178
+ @taggable.tag_list = "ruby, bob, charlie"
179
+ @taggable.save
180
+ @taggable.reload
181
+ @taggable.skill_list.should include("ruby")
182
+ @taggable.skill_list.should_not include("bob")
183
+ end
184
+
185
+ it "should be able to remove tags through list alone" do
186
+ @taggable.skill_list = "ruby, rails, css"
187
+ @taggable.save
188
+ @taggable.reload
189
+ @taggable.should have(3).skills
190
+ @taggable.skill_list = "ruby, rails"
191
+ @taggable.save
192
+ @taggable.reload
193
+ @taggable.should have(2).skills
194
+ end
195
+
196
+ it "should be able to select taggables by subset of tags using ActiveRelation methods" do
197
+ @taggables[0].tag_list = "bob"
198
+ @taggables[1].tag_list = "charlie"
199
+ @taggables[0].skill_list = "ruby"
200
+ @taggables[1].skill_list = "css"
201
+ @taggables.each{|taggable| taggable.save}
202
+
203
+ @found_taggables_by_tag = TaggableModel.joins(:tags).where(:tags => {:name => ["bob"]})
204
+ @found_taggables_by_skill = TaggableModel.joins(:skills).where(:tags => {:name => ["ruby"]})
205
+
206
+ @found_taggables_by_tag.should include @taggables[0]
207
+ @found_taggables_by_tag.should_not include @taggables[1]
208
+ @found_taggables_by_skill.should include @taggables[0]
209
+ @found_taggables_by_skill.should_not include @taggables[1]
210
+ end
211
+
212
+ it "should be able to find by tag" do
213
+ @taggable.skill_list = "ruby, rails, css"
214
+ @taggable.save
215
+
216
+ TaggableModel.tagged_with("ruby").first.should == @taggable
217
+ end
218
+
219
+ it "should be able to find by tag with context" do
220
+ @taggable.skill_list = "ruby, rails, css"
221
+ @taggable.tag_list = "bob, charlie"
222
+ @taggable.save
223
+
224
+ TaggableModel.tagged_with("ruby").first.should == @taggable
225
+ TaggableModel.tagged_with("ruby, css").first.should == @taggable
226
+ TaggableModel.tagged_with("bob", :on => :skills).first.should_not == @taggable
227
+ TaggableModel.tagged_with("bob", :on => :tags).first.should == @taggable
228
+ end
229
+
230
+ it "should not care about case" do
231
+ bob = TaggableModel.create(:name => "Bob", :tag_list => "ruby")
232
+ frank = TaggableModel.create(:name => "Frank", :tag_list => "Ruby")
233
+
234
+ ActsAsTaggableOn::Tag.all.size.should == 1
235
+ TaggableModel.tagged_with("ruby").to_a.should == TaggableModel.tagged_with("Ruby").to_a
236
+ end
237
+
238
+ it "should be able to get tag counts on model as a whole" do
239
+ bob = TaggableModel.create(:name => "Bob", :tag_list => "ruby, rails, css")
240
+ frank = TaggableModel.create(:name => "Frank", :tag_list => "ruby, rails")
241
+ charlie = TaggableModel.create(:name => "Charlie", :skill_list => "ruby")
242
+ TaggableModel.tag_counts.should_not be_empty
243
+ TaggableModel.skill_counts.should_not be_empty
244
+ end
245
+
246
+ it "should be able to get all tag counts on model as whole" do
247
+ bob = TaggableModel.create(:name => "Bob", :tag_list => "ruby, rails, css")
248
+ frank = TaggableModel.create(:name => "Frank", :tag_list => "ruby, rails")
249
+ charlie = TaggableModel.create(:name => "Charlie", :skill_list => "ruby")
250
+
251
+ TaggableModel.all_tag_counts.should_not be_empty
252
+ TaggableModel.all_tag_counts(:order => 'tags.id').first.count.should == 3 # ruby
253
+ end
254
+
255
+ it "should be able to get all tags on model as whole" do
256
+ bob = TaggableModel.create(:name => "Bob", :tag_list => "ruby, rails, css")
257
+ frank = TaggableModel.create(:name => "Frank", :tag_list => "ruby, rails")
258
+ charlie = TaggableModel.create(:name => "Charlie", :skill_list => "ruby")
259
+
260
+ TaggableModel.all_tags.should_not be_empty
261
+ TaggableModel.all_tags(:order => 'tags.id').first.name.should == "ruby"
262
+ end
263
+
264
+ it "should be able to use named scopes to chain tag finds by any tags by context" do
265
+ bob = TaggableModel.create(:name => "Bob", :need_list => "rails", :offering_list => "c++")
266
+ frank = TaggableModel.create(:name => "Frank", :need_list => "css", :offering_list => "css")
267
+ steve = TaggableModel.create(:name => 'Steve', :need_list => "c++", :offering_list => "java")
268
+
269
+ # Let's only find those who need rails or css and are offering c++ or java
270
+ TaggableModel.tagged_with(['rails, css'], :on => :needs, :any => true).tagged_with(['c++', 'java'], :on => :offerings, :any => true).to_a.should == [bob]
271
+ end
272
+
273
+ it "should not return read-only records" do
274
+ TaggableModel.create(:name => "Bob", :tag_list => "ruby, rails, css")
275
+ TaggableModel.tagged_with("ruby").first.should_not be_readonly
276
+ end
277
+
278
+ it "should be able to get scoped tag counts" do
279
+ bob = TaggableModel.create(:name => "Bob", :tag_list => "ruby, rails, css")
280
+ frank = TaggableModel.create(:name => "Frank", :tag_list => "ruby, rails")
281
+ charlie = TaggableModel.create(:name => "Charlie", :skill_list => "ruby")
282
+
283
+ TaggableModel.tagged_with("ruby").tag_counts(:order => 'tags.id').first.count.should == 2 # ruby
284
+ TaggableModel.tagged_with("ruby").skill_counts.first.count.should == 1 # ruby
285
+ end
286
+
287
+ it "should be able to get all scoped tag counts" do
288
+ bob = TaggableModel.create(:name => "Bob", :tag_list => "ruby, rails, css")
289
+ frank = TaggableModel.create(:name => "Frank", :tag_list => "ruby, rails")
290
+ charlie = TaggableModel.create(:name => "Charlie", :skill_list => "ruby")
291
+
292
+ TaggableModel.tagged_with("ruby").all_tag_counts(:order => 'tags.id').first.count.should == 3 # ruby
293
+ end
294
+
295
+ it "should be able to get all scoped tags" do
296
+ bob = TaggableModel.create(:name => "Bob", :tag_list => "ruby, rails, css")
297
+ frank = TaggableModel.create(:name => "Frank", :tag_list => "ruby, rails")
298
+ charlie = TaggableModel.create(:name => "Charlie", :skill_list => "ruby")
299
+
300
+ TaggableModel.tagged_with("ruby").all_tags(:order => 'tags.id').first.name.should == "ruby"
301
+ end
302
+
303
+ it 'should only return tag counts for the available scope' do
304
+ bob = TaggableModel.create(:name => "Bob", :tag_list => "ruby, rails, css")
305
+ frank = TaggableModel.create(:name => "Frank", :tag_list => "ruby, rails")
306
+ charlie = TaggableModel.create(:name => "Charlie", :skill_list => "ruby, java")
307
+
308
+ TaggableModel.tagged_with('rails').all_tag_counts.should have(3).items
309
+ TaggableModel.tagged_with('rails').all_tag_counts.any? { |tag| tag.name == 'java' }.should be_false
310
+
311
+ # Test specific join syntaxes:
312
+ frank.untaggable_models.create!
313
+ TaggableModel.tagged_with('rails').joins(:untaggable_models).all_tag_counts.should have(2).items
314
+ TaggableModel.tagged_with('rails').joins(:untaggable_models => :taggable_model).all_tag_counts.should have(2).items
315
+ TaggableModel.tagged_with('rails').joins([:untaggable_models]).all_tag_counts.should have(2).items
316
+ end
317
+
318
+ it 'should only return tags for the available scope' do
319
+ bob = TaggableModel.create(:name => "Bob", :tag_list => "ruby, rails, css")
320
+ frank = TaggableModel.create(:name => "Frank", :tag_list => "ruby, rails")
321
+ charlie = TaggableModel.create(:name => "Charlie", :skill_list => "ruby, java")
322
+
323
+ TaggableModel.tagged_with('rails').all_tags.should have(3).items
324
+ TaggableModel.tagged_with('rails').all_tags.any? { |tag| tag.name == 'java' }.should be_false
325
+
326
+ # Test specific join syntaxes:
327
+ frank.untaggable_models.create!
328
+ TaggableModel.tagged_with('rails').joins(:untaggable_models).all_tags.should have(2).items
329
+ TaggableModel.tagged_with('rails').joins({ :untaggable_models => :taggable_model }).all_tags.should have(2).items
330
+ TaggableModel.tagged_with('rails').joins([:untaggable_models]).all_tags.should have(2).items
331
+ end
332
+
333
+ it "should be able to set a custom tag context list" do
334
+ bob = TaggableModel.create(:name => "Bob")
335
+ bob.set_tag_list_on(:rotors, "spinning, jumping")
336
+ bob.tag_list_on(:rotors).should == ["spinning","jumping"]
337
+ bob.save
338
+ bob.reload
339
+ bob.tags_on(:rotors).should_not be_empty
340
+ end
341
+
342
+ it "should be able to find tagged" do
343
+ bob = TaggableModel.create(:name => "Bob", :tag_list => "fitter, happier, more productive", :skill_list => "ruby, rails, css")
344
+ frank = TaggableModel.create(:name => "Frank", :tag_list => "weaker, depressed, inefficient", :skill_list => "ruby, rails, css")
345
+ steve = TaggableModel.create(:name => 'Steve', :tag_list => 'fitter, happier, more productive', :skill_list => 'c++, java, ruby')
346
+
347
+ TaggableModel.tagged_with("ruby", :order => 'taggable_models.name').to_a.should == [bob, frank, steve]
348
+ TaggableModel.tagged_with("ruby, rails", :order => 'taggable_models.name').to_a.should == [bob, frank]
349
+ TaggableModel.tagged_with(["ruby", "rails"], :order => 'taggable_models.name').to_a.should == [bob, frank]
350
+ end
351
+
352
+ it "should be able to find tagged with quotation marks" do
353
+ bob = TaggableModel.create(:name => "Bob", :tag_list => "fitter, happier, more productive, 'I love the ,comma,'")
354
+ TaggableModel.tagged_with("'I love the ,comma,'").should include(bob)
355
+ end
356
+
357
+ it "should be able to find tagged with invalid tags" do
358
+ bob = TaggableModel.create(:name => "Bob", :tag_list => "fitter, happier, more productive")
359
+ TaggableModel.tagged_with("sad, happier").should_not include(bob)
360
+ end
361
+
362
+ it "should be able to find tagged with any tag" do
363
+ bob = TaggableModel.create(:name => "Bob", :tag_list => "fitter, happier, more productive", :skill_list => "ruby, rails, css")
364
+ frank = TaggableModel.create(:name => "Frank", :tag_list => "weaker, depressed, inefficient", :skill_list => "ruby, rails, css")
365
+ steve = TaggableModel.create(:name => 'Steve', :tag_list => 'fitter, happier, more productive', :skill_list => 'c++, java, ruby')
366
+
367
+ TaggableModel.tagged_with(["ruby", "java"], :order => 'taggable_models.name', :any => true).to_a.should == [bob, frank, steve]
368
+ TaggableModel.tagged_with(["c++", "fitter"], :order => 'taggable_models.name', :any => true).to_a.should == [bob, steve]
369
+ TaggableModel.tagged_with(["depressed", "css"], :order => 'taggable_models.name', :any => true).to_a.should == [bob, frank]
370
+ end
371
+
372
+ context "wild: true" do
373
+ it "should use params as wildcards" do
374
+ bob = TaggableModel.create(:name => "Bob", :tag_list => "bob, tricia")
375
+ frank = TaggableModel.create(:name => "Frank", :tag_list => "bobby, jim")
376
+ steve = TaggableModel.create(:name => "Steve", :tag_list => "john, patricia")
377
+ jim = TaggableModel.create(:name => "Jim", :tag_list => "jim, steve")
378
+
379
+
380
+ TaggableModel.tagged_with(["bob", "tricia"], :wild => true, :any => true).to_a.sort_by{|o| o.id}.should == [bob, frank, steve]
381
+ TaggableModel.tagged_with(["bob", "tricia"], :wild => true, :exclude => true).to_a.should == [jim]
382
+ end
383
+ end
384
+
385
+ it "should be able to find tagged on a custom tag context" do
386
+ bob = TaggableModel.create(:name => "Bob")
387
+ bob.set_tag_list_on(:rotors, "spinning, jumping")
388
+ bob.tag_list_on(:rotors).should == ["spinning","jumping"]
389
+ bob.save
390
+
391
+ TaggableModel.tagged_with("spinning", :on => :rotors).to_a.should == [bob]
392
+ end
393
+
394
+ it "should be able to use named scopes to chain tag finds" do
395
+ bob = TaggableModel.create(:name => "Bob", :tag_list => "fitter, happier, more productive", :skill_list => "ruby, rails, css")
396
+ frank = TaggableModel.create(:name => "Frank", :tag_list => "weaker, depressed, inefficient", :skill_list => "ruby, rails, css")
397
+ steve = TaggableModel.create(:name => 'Steve', :tag_list => 'fitter, happier, more productive', :skill_list => 'c++, java, python')
398
+
399
+ # Let's only find those productive Rails developers
400
+ TaggableModel.tagged_with('rails', :on => :skills, :order => 'taggable_models.name').to_a.should == [bob, frank]
401
+ TaggableModel.tagged_with('happier', :on => :tags, :order => 'taggable_models.name').to_a.should == [bob, steve]
402
+ TaggableModel.tagged_with('rails', :on => :skills).tagged_with('happier', :on => :tags).to_a.should == [bob]
403
+ TaggableModel.tagged_with('rails').tagged_with('happier', :on => :tags).to_a.should == [bob]
404
+ end
405
+
406
+ it "should be able to find tagged with only the matching tags" do
407
+ bob = TaggableModel.create(:name => "Bob", :tag_list => "lazy, happier")
408
+ frank = TaggableModel.create(:name => "Frank", :tag_list => "fitter, happier, inefficient")
409
+ steve = TaggableModel.create(:name => 'Steve', :tag_list => "fitter, happier")
410
+
411
+ TaggableModel.tagged_with("fitter, happier", :match_all => true).to_a.should == [steve]
412
+ end
413
+
414
+ it "should be able to find tagged with some excluded tags" do
415
+ bob = TaggableModel.create(:name => "Bob", :tag_list => "happier, lazy")
416
+ frank = TaggableModel.create(:name => "Frank", :tag_list => "happier")
417
+ steve = TaggableModel.create(:name => 'Steve', :tag_list => "happier")
418
+
419
+ TaggableModel.tagged_with("lazy", :exclude => true).to_a.should == [frank, steve]
420
+ end
421
+
422
+ it "should return an empty scope for empty tags" do
423
+ TaggableModel.tagged_with('').should == []
424
+ TaggableModel.tagged_with(' ').should == []
425
+ TaggableModel.tagged_with(nil).should == []
426
+ TaggableModel.tagged_with([]).should == []
427
+ end
428
+
429
+ it "should not create duplicate taggings" do
430
+ bob = TaggableModel.create(:name => "Bob")
431
+ lambda {
432
+ bob.tag_list << "happier"
433
+ bob.tag_list << "happier"
434
+ bob.save
435
+ }.should change(ActsAsTaggableOn::Tagging, :count).by(1)
436
+ end
437
+
438
+ describe "Associations" do
439
+ before(:each) do
440
+ @taggable = TaggableModel.create(:tag_list => "awesome, epic")
441
+ end
442
+
443
+ it "should not remove tags when creating associated objects" do
444
+ @taggable.untaggable_models.create!
445
+ @taggable.reload
446
+ @taggable.tag_list.should have(2).items
447
+ end
448
+ end
449
+
450
+ describe "grouped_column_names_for method" do
451
+ it "should return all column names joined for Tag GROUP clause" do
452
+ @taggable.grouped_column_names_for(ActsAsTaggableOn::Tag).should == "tags.id, tags.name"
453
+ end
454
+
455
+ it "should return all column names joined for TaggableModel GROUP clause" do
456
+ @taggable.grouped_column_names_for(TaggableModel).should == "taggable_models.id, taggable_models.name, taggable_models.type"
457
+ end
458
+
459
+ it "should return all column names joined for NonStandardIdTaggableModel GROUP clause" do
460
+ @taggable.grouped_column_names_for(TaggableModel).should == "taggable_models.#{TaggableModel.primary_key}, taggable_models.name, taggable_models.type"
461
+ end
462
+ end
463
+
464
+ describe "NonStandardIdTaggable" do
465
+ before(:each) do
466
+ clean_database!
467
+ @taggable = NonStandardIdTaggableModel.new(:name => "Bob Jones")
468
+ @taggables = [@taggable, NonStandardIdTaggableModel.new(:name => "John Doe")]
469
+ end
470
+
471
+ it "should have tag types" do
472
+ [:tags, :languages, :skills, :needs, :offerings].each do |type|
473
+ NonStandardIdTaggableModel.tag_types.should include type
474
+ end
475
+
476
+ @taggable.tag_types.should == NonStandardIdTaggableModel.tag_types
477
+ end
478
+
479
+ it "should have tag_counts_on" do
480
+ NonStandardIdTaggableModel.tag_counts_on(:tags).should be_empty
481
+
482
+ @taggable.tag_list = ["awesome", "epic"]
483
+ @taggable.save
484
+
485
+ NonStandardIdTaggableModel.tag_counts_on(:tags).length.should == 2
486
+ @taggable.tag_counts_on(:tags).length.should == 2
487
+ end
488
+
489
+ it "should have tags_on" do
490
+ NonStandardIdTaggableModel.tags_on(:tags).should be_empty
491
+
492
+ @taggable.tag_list = ["awesome", "epic"]
493
+ @taggable.save
494
+
495
+ NonStandardIdTaggableModel.tags_on(:tags).length.should == 2
496
+ @taggable.tags_on(:tags).length.should == 2
497
+ end
498
+
499
+ it "should be able to create tags" do
500
+ @taggable.skill_list = "ruby, rails, css"
501
+ @taggable.instance_variable_get("@skill_list").instance_of?(ActsAsTaggableOn::TagList).should be_true
502
+
503
+ lambda {
504
+ @taggable.save
505
+ }.should change(ActsAsTaggableOn::Tag, :count).by(3)
506
+
507
+ @taggable.reload
508
+ @taggable.skill_list.sort.should == %w(ruby rails css).sort
509
+ end
510
+
511
+ it "should be able to create tags through the tag list directly" do
512
+ @taggable.tag_list_on(:test).add("hello")
513
+ @taggable.tag_list_cache_on(:test).should_not be_empty
514
+ @taggable.tag_list_on(:test).should == ["hello"]
515
+
516
+ @taggable.save
517
+ @taggable.save_tags
518
+
519
+ @taggable.reload
520
+ @taggable.tag_list_on(:test).should == ["hello"]
521
+ end
522
+ end
523
+
524
+ describe "Dirty Objects" do
525
+ context "with un-contexted tags" do
526
+ before(:each) do
527
+ @taggable = TaggableModel.create(:tag_list => "awesome, epic")
528
+ end
529
+
530
+ context "when tag_list changed" do
531
+ before(:each) do
532
+ @taggable.changes.should == {}
533
+ @taggable.tag_list = 'one'
534
+ end
535
+
536
+ it 'should show changes of dirty object' do
537
+ @taggable.changes.should == {"tag_list"=>["awesome, epic", ["one"]]}
538
+ end
539
+
540
+ it 'flags tag_list as changed' do
541
+ @taggable.tag_list_changed?.should be_true
542
+ end
543
+
544
+ it 'preserves original value' do
545
+ @taggable.tag_list_was.should == "awesome, epic"
546
+ end
547
+
548
+ it 'shows what the change was' do
549
+ @taggable.tag_list_change.should == ["awesome, epic", ["one"]]
550
+ end
551
+ end
552
+
553
+ context 'when tag_list is the same' do
554
+ before(:each) do
555
+ @taggable.tag_list = "awesome, epic"
556
+ end
557
+
558
+ it 'is not flagged as changed' do
559
+ @taggable.tag_list_changed?.should be_false
560
+ end
561
+
562
+ it 'does not show any changes to the taggable item' do
563
+ @taggable.changes.should == {}
564
+ end
565
+ end
566
+ end
567
+
568
+ context "with context tags" do
569
+ before(:each) do
570
+ @taggable = TaggableModel.create(:language_list => "awesome, epic")
571
+ end
572
+
573
+ context "when language_list changed" do
574
+ before(:each) do
575
+ @taggable.changes.should == {}
576
+ @taggable.language_list = 'one'
577
+ end
578
+
579
+ it 'should show changes of dirty object' do
580
+ @taggable.changes.should == {"language_list"=>["awesome, epic", ["one"]]}
581
+ end
582
+
583
+ it 'flags language_list as changed' do
584
+ @taggable.language_list_changed?.should be_true
585
+ end
586
+
587
+ it 'preserves original value' do
588
+ @taggable.language_list_was.should == "awesome, epic"
589
+ end
590
+
591
+ it 'shows what the change was' do
592
+ @taggable.language_list_change.should == ["awesome, epic", ["one"]]
593
+ end
594
+
595
+ it 'shows what the changes were' do
596
+ @taggable.language_list_changes.should == ["awesome, epic", ["one"]]
597
+ end
598
+ end
599
+
600
+ context 'when language_list is the same' do
601
+ before(:each) do
602
+ @taggable.language_list = "awesome, epic"
603
+ end
604
+
605
+ it 'is not flagged as changed' do
606
+ @taggable.language_list_changed?.should be_false
607
+ end
608
+
609
+ it 'does not show any changes to the taggable item' do
610
+ @taggable.changes.should == {}
611
+ end
612
+ end
613
+ end
614
+ end
615
+
616
+ describe "Autogenerated methods" do
617
+ it "should be overridable" do
618
+ TaggableModel.create(:tag_list=>'woo').tag_list_submethod_called.should be_true
619
+ end
620
+ end
621
+ end
622
+
623
+