acts_as_taggable_on 3.0.0.rc1
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.
- checksums.yaml +15 -0
- data/.gitignore +11 -0
- data/.rspec +2 -0
- data/.travis.yml +9 -0
- data/Appraisals +7 -0
- data/Gemfile +5 -0
- data/Guardfile +5 -0
- data/LICENSE.md +20 -0
- data/README.md +309 -0
- data/Rakefile +13 -0
- data/UPGRADING +7 -0
- data/acts_as_taggable_on.gemspec +35 -0
- data/db/migrate/1_acts_as_taggable_on_migration.rb +30 -0
- data/db/migrate/2_add_missing_unique_indices.rb +21 -0
- data/gemfiles/rails_3.gemfile +8 -0
- data/gemfiles/rails_4.gemfile +8 -0
- data/lib/acts_as_taggable_on.rb +61 -0
- data/lib/acts_as_taggable_on/acts_as_taggable_on/cache.rb +82 -0
- data/lib/acts_as_taggable_on/acts_as_taggable_on/collection.rb +187 -0
- data/lib/acts_as_taggable_on/acts_as_taggable_on/compatibility.rb +34 -0
- data/lib/acts_as_taggable_on/acts_as_taggable_on/core.rb +394 -0
- data/lib/acts_as_taggable_on/acts_as_taggable_on/dirty.rb +37 -0
- data/lib/acts_as_taggable_on/acts_as_taggable_on/ownership.rb +135 -0
- data/lib/acts_as_taggable_on/acts_as_taggable_on/related.rb +84 -0
- data/lib/acts_as_taggable_on/engine.rb +6 -0
- data/lib/acts_as_taggable_on/tag.rb +119 -0
- data/lib/acts_as_taggable_on/tag_list.rb +101 -0
- data/lib/acts_as_taggable_on/taggable.rb +105 -0
- data/lib/acts_as_taggable_on/tagger.rb +76 -0
- data/lib/acts_as_taggable_on/tagging.rb +34 -0
- data/lib/acts_as_taggable_on/tags_helper.rb +15 -0
- data/lib/acts_as_taggable_on/utils.rb +34 -0
- data/lib/acts_as_taggable_on/version.rb +4 -0
- data/spec/acts_as_taggable_on/acts_as_taggable_on_spec.rb +265 -0
- data/spec/acts_as_taggable_on/acts_as_tagger_spec.rb +114 -0
- data/spec/acts_as_taggable_on/caching_spec.rb +77 -0
- data/spec/acts_as_taggable_on/related_spec.rb +143 -0
- data/spec/acts_as_taggable_on/single_table_inheritance_spec.rb +187 -0
- data/spec/acts_as_taggable_on/tag_list_spec.rb +126 -0
- data/spec/acts_as_taggable_on/tag_spec.rb +211 -0
- data/spec/acts_as_taggable_on/taggable_spec.rb +623 -0
- data/spec/acts_as_taggable_on/tagger_spec.rb +137 -0
- data/spec/acts_as_taggable_on/tagging_spec.rb +28 -0
- data/spec/acts_as_taggable_on/tags_helper_spec.rb +44 -0
- data/spec/acts_as_taggable_on/utils_spec.rb +21 -0
- data/spec/bm.rb +52 -0
- data/spec/database.yml.sample +19 -0
- data/spec/models.rb +58 -0
- data/spec/schema.rb +65 -0
- data/spec/spec_helper.rb +87 -0
- 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
|
+
|