acts-as-taggable-on 3.1.1 → 3.2.1
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 +4 -4
- data/.gitignore +1 -1
- data/.travis.yml +9 -7
- data/Appraisals +13 -8
- data/CHANGELOG.md +8 -0
- data/Gemfile +1 -2
- data/README.md +23 -13
- data/Rakefile +5 -17
- data/UPGRADING.md +6 -0
- data/acts-as-taggable-on.gemspec +13 -13
- data/db/migrate/1_acts_as_taggable_on_migration.rb +3 -3
- data/db/migrate/2_add_missing_unique_indices.rb +3 -5
- data/db/migrate/3_add_taggings_counter_cache_to_tags.rb +1 -1
- data/gemfiles/activerecord_3.2.gemfile +15 -0
- data/gemfiles/activerecord_4.0.gemfile +15 -0
- data/gemfiles/activerecord_4.1.gemfile +15 -0
- data/gemfiles/activerecord_edge.gemfile +16 -0
- data/lib/acts-as-taggable-on.rb +23 -21
- data/lib/acts_as_taggable_on/acts_as_taggable_on/cache.rb +1 -4
- data/lib/acts_as_taggable_on/acts_as_taggable_on/collection.rb +29 -20
- data/lib/acts_as_taggable_on/acts_as_taggable_on/compatibility.rb +11 -10
- data/lib/acts_as_taggable_on/acts_as_taggable_on/core.rb +98 -80
- data/lib/acts_as_taggable_on/acts_as_taggable_on/ownership.rb +5 -12
- data/lib/acts_as_taggable_on/acts_as_taggable_on/related.rb +7 -7
- data/lib/acts_as_taggable_on/engine.rb +0 -1
- data/lib/acts_as_taggable_on/tag.rb +24 -19
- data/lib/acts_as_taggable_on/tag_list.rb +95 -21
- data/lib/acts_as_taggable_on/taggable.rb +28 -30
- data/lib/acts_as_taggable_on/tagger.rb +30 -18
- data/lib/acts_as_taggable_on/tagging.rb +7 -8
- data/lib/acts_as_taggable_on/tags_helper.rb +1 -1
- data/lib/acts_as_taggable_on/utils.rb +25 -3
- data/lib/acts_as_taggable_on/version.rb +1 -1
- data/spec/acts_as_taggable_on/acts_as_taggable_on_spec.rb +133 -138
- data/spec/acts_as_taggable_on/acts_as_tagger_spec.rb +55 -58
- data/spec/acts_as_taggable_on/caching_spec.rb +34 -35
- data/spec/acts_as_taggable_on/related_spec.rb +59 -113
- data/spec/acts_as_taggable_on/single_table_inheritance_spec.rb +118 -95
- data/spec/acts_as_taggable_on/tag_list_spec.rb +89 -57
- data/spec/acts_as_taggable_on/tag_spec.rb +125 -114
- data/spec/acts_as_taggable_on/taggable_spec.rb +538 -352
- data/spec/acts_as_taggable_on/tagger_spec.rb +81 -78
- data/spec/acts_as_taggable_on/tagging_spec.rb +13 -14
- data/spec/acts_as_taggable_on/tags_helper_spec.rb +25 -25
- data/spec/acts_as_taggable_on/utils_spec.rb +9 -9
- data/spec/internal/app/models/altered_inheriting_taggable_model.rb +3 -0
- data/spec/internal/app/models/cached_model.rb +3 -0
- data/spec/internal/app/models/cached_model_with_array.rb +5 -0
- data/spec/internal/app/models/company.rb +15 -0
- data/spec/internal/app/models/inheriting_taggable_model.rb +2 -0
- data/spec/internal/app/models/market.rb +2 -0
- data/spec/{models.rb → internal/app/models/models.rb} +34 -2
- data/spec/internal/app/models/non_standard_id_taggable_model.rb +8 -0
- data/spec/internal/app/models/ordered_taggable_model.rb +4 -0
- data/spec/internal/app/models/other_cached_model.rb +3 -0
- data/spec/internal/app/models/other_taggable_model.rb +4 -0
- data/spec/internal/app/models/student.rb +2 -0
- data/spec/internal/app/models/taggable_model.rb +13 -0
- data/spec/internal/app/models/untaggable_model.rb +3 -0
- data/spec/internal/app/models/user.rb +3 -0
- data/spec/{database.yml.sample → internal/config/database.yml.sample} +2 -2
- data/spec/internal/db/schema.rb +97 -0
- data/spec/schema.rb +11 -0
- data/spec/spec_helper.rb +9 -62
- data/spec/support/array.rb +9 -0
- data/spec/support/database.rb +42 -0
- data/spec/support/database_cleaner.rb +17 -0
- metadata +101 -37
- data/gemfiles/rails_3.2.gemfile +0 -7
- data/gemfiles/rails_4.0.gemfile +0 -7
- data/gemfiles/rails_4.1.gemfile +0 -7
- data/gemfiles/rails_edge.gemfile +0 -7
@@ -9,201 +9,211 @@ end
|
|
9
9
|
|
10
10
|
describe ActsAsTaggableOn::Tag do
|
11
11
|
before(:each) do
|
12
|
-
clean_database!
|
13
12
|
@tag = ActsAsTaggableOn::Tag.new
|
14
|
-
@user = TaggableModel.create(:
|
13
|
+
@user = TaggableModel.create(name: 'Pablo')
|
15
14
|
end
|
16
15
|
|
17
|
-
|
18
|
-
|
19
|
-
|
16
|
+
|
17
|
+
|
18
|
+
describe 'named like any' do
|
19
|
+
context 'case insensitive collation and unique index on tag name' do
|
20
|
+
if ActsAsTaggableOn::Utils.using_case_insensitive_collation?
|
20
21
|
before(:each) do
|
21
|
-
ActsAsTaggableOn::Tag.create(:
|
22
|
-
ActsAsTaggableOn::Tag.create(:
|
22
|
+
ActsAsTaggableOn::Tag.create(name: 'Awesome')
|
23
|
+
ActsAsTaggableOn::Tag.create(name: 'epic')
|
23
24
|
end
|
24
25
|
|
25
|
-
it
|
26
|
-
ActsAsTaggableOn::Tag.named_like_any(
|
26
|
+
it 'should find both tags' do
|
27
|
+
expect(ActsAsTaggableOn::Tag.named_like_any(%w(awesome epic)).count).to eq(2)
|
27
28
|
end
|
28
29
|
end
|
29
30
|
end
|
30
31
|
|
31
|
-
context
|
32
|
-
if
|
32
|
+
context 'case insensitive collation without indexes or case sensitive collation with indexes' do
|
33
|
+
if ActsAsTaggableOn::Utils.using_case_insensitive_collation?
|
33
34
|
include_context 'without unique index'
|
34
35
|
end
|
35
36
|
|
36
37
|
before(:each) do
|
37
|
-
ActsAsTaggableOn::Tag.create(:
|
38
|
-
ActsAsTaggableOn::Tag.create(:
|
39
|
-
ActsAsTaggableOn::Tag.create(:
|
38
|
+
ActsAsTaggableOn::Tag.create(name: 'Awesome')
|
39
|
+
ActsAsTaggableOn::Tag.create(name: 'awesome')
|
40
|
+
ActsAsTaggableOn::Tag.create(name: 'epic')
|
40
41
|
end
|
41
42
|
|
42
|
-
it
|
43
|
-
ActsAsTaggableOn::Tag.named_like_any(
|
43
|
+
it 'should find both tags' do
|
44
|
+
expect(ActsAsTaggableOn::Tag.named_like_any(%w(awesome epic)).count).to eq(3)
|
44
45
|
end
|
45
46
|
end
|
46
47
|
end
|
47
48
|
|
48
|
-
describe
|
49
|
+
describe 'find or create by name' do
|
49
50
|
before(:each) do
|
50
|
-
@tag.name =
|
51
|
+
@tag.name = 'awesome'
|
51
52
|
@tag.save
|
52
53
|
end
|
53
54
|
|
54
|
-
it
|
55
|
-
ActsAsTaggableOn::Tag.find_or_create_with_like_by_name(
|
55
|
+
it 'should find by name' do
|
56
|
+
expect(ActsAsTaggableOn::Tag.find_or_create_with_like_by_name('awesome')).to eq(@tag)
|
56
57
|
end
|
57
58
|
|
58
|
-
it
|
59
|
-
ActsAsTaggableOn::Tag.find_or_create_with_like_by_name(
|
59
|
+
it 'should find by name case insensitive' do
|
60
|
+
expect(ActsAsTaggableOn::Tag.find_or_create_with_like_by_name('AWESOME')).to eq(@tag)
|
60
61
|
end
|
61
62
|
|
62
|
-
it
|
63
|
-
|
64
|
-
ActsAsTaggableOn::Tag.find_or_create_with_like_by_name(
|
65
|
-
}.
|
63
|
+
it 'should create by name' do
|
64
|
+
expect(-> {
|
65
|
+
ActsAsTaggableOn::Tag.find_or_create_with_like_by_name('epic')
|
66
|
+
}).to change(ActsAsTaggableOn::Tag, :count).by(1)
|
66
67
|
end
|
67
68
|
end
|
68
69
|
|
69
|
-
unless ActsAsTaggableOn::
|
70
|
-
describe
|
70
|
+
unless ActsAsTaggableOn::Utils.using_sqlite?
|
71
|
+
describe 'find or create by unicode name' do
|
71
72
|
before(:each) do
|
72
|
-
@tag.name =
|
73
|
+
@tag.name = 'привет'
|
73
74
|
@tag.save
|
74
75
|
end
|
75
76
|
|
76
|
-
it
|
77
|
-
ActsAsTaggableOn::Tag.find_or_create_with_like_by_name(
|
77
|
+
it 'should find by name' do
|
78
|
+
expect(ActsAsTaggableOn::Tag.find_or_create_with_like_by_name('привет')).to eq(@tag)
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'should find by name case insensitive' do
|
82
|
+
expect(ActsAsTaggableOn::Tag.find_or_create_with_like_by_name('ПРИВЕТ')).to eq(@tag)
|
78
83
|
end
|
79
84
|
|
80
|
-
|
81
|
-
|
85
|
+
if ActsAsTaggableOn::Utils.using_case_insensitive_collation?
|
86
|
+
it 'should find by name accent insensitive' do
|
87
|
+
@tag.name = 'inupiat'
|
88
|
+
@tag.save
|
89
|
+
expect(ActsAsTaggableOn::Tag.find_or_create_with_like_by_name('Iñupiat')).to eq(@tag)
|
90
|
+
end
|
82
91
|
end
|
83
92
|
end
|
84
93
|
end
|
85
94
|
|
86
|
-
describe
|
95
|
+
describe 'find or create all by any name' do
|
87
96
|
before(:each) do
|
88
|
-
@tag.name =
|
97
|
+
@tag.name = 'awesome'
|
89
98
|
@tag.save
|
90
99
|
end
|
91
100
|
|
92
|
-
it
|
93
|
-
ActsAsTaggableOn::Tag.find_or_create_all_with_like_by_name(
|
101
|
+
it 'should find by name' do
|
102
|
+
expect(ActsAsTaggableOn::Tag.find_or_create_all_with_like_by_name('awesome')).to eq([@tag])
|
94
103
|
end
|
95
104
|
|
96
|
-
it
|
97
|
-
ActsAsTaggableOn::Tag.find_or_create_all_with_like_by_name(
|
105
|
+
it 'should find by name case insensitive' do
|
106
|
+
expect(ActsAsTaggableOn::Tag.find_or_create_all_with_like_by_name('AWESOME')).to eq([@tag])
|
98
107
|
end
|
99
108
|
|
100
|
-
context
|
101
|
-
if
|
109
|
+
context 'case sensitive' do
|
110
|
+
if ActsAsTaggableOn::Utils.using_case_insensitive_collation?
|
102
111
|
include_context 'without unique index'
|
103
112
|
end
|
104
113
|
|
105
|
-
it
|
114
|
+
it 'should find by name case sensitive' do
|
106
115
|
ActsAsTaggableOn.strict_case_match = true
|
107
116
|
expect {
|
108
|
-
ActsAsTaggableOn::Tag.find_or_create_all_with_like_by_name(
|
117
|
+
ActsAsTaggableOn::Tag.find_or_create_all_with_like_by_name('AWESOME')
|
109
118
|
}.to change(ActsAsTaggableOn::Tag, :count).by(1)
|
110
119
|
end
|
111
120
|
end
|
112
121
|
|
113
|
-
it
|
122
|
+
it 'should create by name' do
|
114
123
|
expect {
|
115
|
-
ActsAsTaggableOn::Tag.find_or_create_all_with_like_by_name(
|
124
|
+
ActsAsTaggableOn::Tag.find_or_create_all_with_like_by_name('epic')
|
116
125
|
}.to change(ActsAsTaggableOn::Tag, :count).by(1)
|
117
126
|
end
|
118
127
|
|
119
|
-
context
|
120
|
-
if
|
128
|
+
context 'case sensitive' do
|
129
|
+
if ActsAsTaggableOn::Utils.using_case_insensitive_collation?
|
121
130
|
include_context 'without unique index'
|
122
131
|
end
|
123
132
|
|
124
|
-
it
|
133
|
+
it 'should find or create by name case sensitive' do
|
125
134
|
ActsAsTaggableOn.strict_case_match = true
|
126
135
|
expect {
|
127
|
-
ActsAsTaggableOn::Tag.find_or_create_all_with_like_by_name(
|
136
|
+
expect(ActsAsTaggableOn::Tag.find_or_create_all_with_like_by_name('AWESOME', 'awesome').map(&:name)).to eq(%w(AWESOME awesome))
|
128
137
|
}.to change(ActsAsTaggableOn::Tag, :count).by(1)
|
129
138
|
end
|
130
139
|
end
|
131
140
|
|
132
|
-
it
|
141
|
+
it 'should find or create by name' do
|
133
142
|
expect {
|
134
|
-
ActsAsTaggableOn::Tag.find_or_create_all_with_like_by_name(
|
143
|
+
expect(ActsAsTaggableOn::Tag.find_or_create_all_with_like_by_name('awesome', 'epic').map(&:name)).to eq(%w(awesome epic))
|
135
144
|
}.to change(ActsAsTaggableOn::Tag, :count).by(1)
|
136
145
|
end
|
137
146
|
|
138
|
-
it
|
139
|
-
ActsAsTaggableOn::Tag.find_or_create_all_with_like_by_name([]).
|
147
|
+
it 'should return an empty array if no tags are specified' do
|
148
|
+
expect(ActsAsTaggableOn::Tag.find_or_create_all_with_like_by_name([])).to be_empty
|
140
149
|
end
|
141
150
|
end
|
142
151
|
|
143
|
-
it
|
152
|
+
it 'should require a name' do
|
144
153
|
@tag.valid?
|
154
|
+
#TODO, we should find another way to check this
|
155
|
+
expect(@tag.errors[:name]).to eq(["can't be blank"])
|
145
156
|
|
146
|
-
@tag.
|
147
|
-
|
148
|
-
@tag.name = "something"
|
157
|
+
@tag.name = 'something'
|
149
158
|
@tag.valid?
|
150
159
|
|
151
|
-
@tag.errors[:name].
|
160
|
+
expect(@tag.errors[:name]).to be_empty
|
152
161
|
end
|
153
162
|
|
154
|
-
it
|
155
|
-
@tag.name =
|
163
|
+
it 'should limit the name length to 255 or less characters' do
|
164
|
+
@tag.name = 'fgkgnkkgjymkypbuozmwwghblmzpqfsgjasflblywhgkwndnkzeifalfcpeaeqychjuuowlacmuidnnrkprgpcpybarbkrmziqihcrxirlokhnzfvmtzixgvhlxzncyywficpraxfnjptxxhkqmvicbcdcynkjvziefqzyndxkjmsjlvyvbwraklbalykyxoliqdlreeykuphdtmzfdwpphmrqvwvqffojkqhlzvinqajsxbszyvrqqyzusxranr'
|
156
165
|
@tag.valid?
|
157
|
-
|
166
|
+
#TODO, we should find another way to check this
|
167
|
+
expect(@tag.errors[:name]).to eq(['is too long (maximum is 255 characters)'])
|
158
168
|
|
159
|
-
@tag.name =
|
169
|
+
@tag.name = 'fgkgnkkgjymkypbuozmwwghblmzpqfsgjasflblywhgkwndnkzeifalfcpeaeqychjuuowlacmuidnnrkprgpcpybarbkrmziqihcrxirlokhnzfvmtzixgvhlxzncyywficpraxfnjptxxhkqmvicbcdcynkjvziefqzyndxkjmsjlvyvbwraklbalykyxoliqdlreeykuphdtmzfdwpphmrqvwvqffojkqhlzvinqajsxbszyvrqqyzusxran'
|
160
170
|
@tag.valid?
|
161
|
-
@tag.errors[:name].
|
171
|
+
expect(@tag.errors[:name]).to be_empty
|
162
172
|
end
|
163
173
|
|
164
|
-
it
|
165
|
-
@tag.name =
|
166
|
-
new_tag = ActsAsTaggableOn::Tag.new(:
|
167
|
-
new_tag.
|
174
|
+
it 'should equal a tag with the same name' do
|
175
|
+
@tag.name = 'awesome'
|
176
|
+
new_tag = ActsAsTaggableOn::Tag.new(name: 'awesome')
|
177
|
+
expect(new_tag).to eq(@tag)
|
168
178
|
end
|
169
179
|
|
170
|
-
it
|
171
|
-
@tag.name =
|
172
|
-
@tag.to_s.
|
180
|
+
it 'should return its name when to_s is called' do
|
181
|
+
@tag.name = 'cool'
|
182
|
+
expect(@tag.to_s).to eq('cool')
|
173
183
|
end
|
174
184
|
|
175
|
-
it
|
176
|
-
@tag.name =
|
185
|
+
it 'have named_scope named(something)' do
|
186
|
+
@tag.name = 'cool'
|
177
187
|
@tag.save!
|
178
|
-
ActsAsTaggableOn::Tag.named('cool').
|
188
|
+
expect(ActsAsTaggableOn::Tag.named('cool')).to include(@tag)
|
179
189
|
end
|
180
190
|
|
181
|
-
it
|
182
|
-
@tag.name =
|
191
|
+
it 'have named_scope named_like(something)' do
|
192
|
+
@tag.name = 'cool'
|
183
193
|
@tag.save!
|
184
|
-
@another_tag = ActsAsTaggableOn::Tag.create!(:
|
185
|
-
ActsAsTaggableOn::Tag.named_like('cool').
|
194
|
+
@another_tag = ActsAsTaggableOn::Tag.create!(name: 'coolip')
|
195
|
+
expect(ActsAsTaggableOn::Tag.named_like('cool')).to include(@tag, @another_tag)
|
186
196
|
end
|
187
197
|
|
188
|
-
describe
|
198
|
+
describe 'escape wildcard symbols in like requests' do
|
189
199
|
before(:each) do
|
190
|
-
@tag.name =
|
200
|
+
@tag.name = 'cool'
|
191
201
|
@tag.save
|
192
|
-
@another_tag = ActsAsTaggableOn::Tag.create!(:
|
193
|
-
@another_tag2 = ActsAsTaggableOn::Tag.create!(:
|
202
|
+
@another_tag = ActsAsTaggableOn::Tag.create!(name: 'coo%')
|
203
|
+
@another_tag2 = ActsAsTaggableOn::Tag.create!(name: 'coolish')
|
194
204
|
end
|
195
205
|
|
196
206
|
it "return escaped result when '%' char present in tag" do
|
197
|
-
|
198
|
-
|
207
|
+
expect(ActsAsTaggableOn::Tag.named_like('coo%')).to_not include(@tag)
|
208
|
+
expect(ActsAsTaggableOn::Tag.named_like('coo%')).to include(@another_tag)
|
199
209
|
end
|
200
210
|
|
201
211
|
end
|
202
212
|
|
203
|
-
describe
|
213
|
+
describe 'when using strict_case_match' do
|
204
214
|
before do
|
205
215
|
ActsAsTaggableOn.strict_case_match = true
|
206
|
-
@tag.name =
|
216
|
+
@tag.name = 'awesome'
|
207
217
|
@tag.save!
|
208
218
|
end
|
209
219
|
|
@@ -211,71 +221,72 @@ describe ActsAsTaggableOn::Tag do
|
|
211
221
|
ActsAsTaggableOn.strict_case_match = false
|
212
222
|
end
|
213
223
|
|
214
|
-
it
|
215
|
-
ActsAsTaggableOn::Tag.find_or_create_with_like_by_name(
|
224
|
+
it 'should find by name' do
|
225
|
+
expect(ActsAsTaggableOn::Tag.find_or_create_with_like_by_name('awesome')).to eq(@tag)
|
216
226
|
end
|
217
227
|
|
218
|
-
context
|
219
|
-
if
|
228
|
+
context 'case sensitive' do
|
229
|
+
if ActsAsTaggableOn::Utils.using_case_insensitive_collation?
|
220
230
|
include_context 'without unique index'
|
221
231
|
end
|
222
232
|
|
223
|
-
it
|
233
|
+
it 'should find by name case sensitively' do
|
224
234
|
expect {
|
225
|
-
ActsAsTaggableOn::Tag.find_or_create_with_like_by_name(
|
235
|
+
ActsAsTaggableOn::Tag.find_or_create_with_like_by_name('AWESOME')
|
226
236
|
}.to change(ActsAsTaggableOn::Tag, :count)
|
227
237
|
|
228
|
-
ActsAsTaggableOn::Tag.last.name.
|
238
|
+
expect(ActsAsTaggableOn::Tag.last.name).to eq('AWESOME')
|
229
239
|
end
|
230
240
|
end
|
231
241
|
|
232
|
-
context
|
233
|
-
if
|
242
|
+
context 'case sensitive' do
|
243
|
+
if ActsAsTaggableOn::Utils.using_case_insensitive_collation?
|
234
244
|
include_context 'without unique index'
|
235
245
|
end
|
236
246
|
|
237
|
-
it
|
238
|
-
uppercase_tag = ActsAsTaggableOn::Tag.create(:
|
239
|
-
@tag.name
|
247
|
+
it 'should have a named_scope named(something) that matches exactly' do
|
248
|
+
uppercase_tag = ActsAsTaggableOn::Tag.create(name: 'Cool')
|
249
|
+
@tag.name = 'cool'
|
240
250
|
@tag.save!
|
241
251
|
|
242
|
-
ActsAsTaggableOn::Tag.named('cool').
|
243
|
-
ActsAsTaggableOn::Tag.named('cool').
|
252
|
+
expect(ActsAsTaggableOn::Tag.named('cool')).to include(@tag)
|
253
|
+
expect(ActsAsTaggableOn::Tag.named('cool')).to_not include(uppercase_tag)
|
244
254
|
end
|
245
255
|
end
|
246
256
|
|
247
|
-
it
|
257
|
+
it 'should not change enconding' do
|
248
258
|
name = "\u3042"
|
249
259
|
original_encoding = name.encoding
|
250
|
-
ActsAsTaggableOn::Tag.find_or_create_with_like_by_name(name)
|
251
|
-
|
260
|
+
record = ActsAsTaggableOn::Tag.find_or_create_with_like_by_name(name)
|
261
|
+
record.reload
|
262
|
+
expect(record.name.encoding).to eq(original_encoding)
|
252
263
|
end
|
253
264
|
end
|
254
265
|
|
255
|
-
describe
|
256
|
-
let(:duplicate_tag) { ActsAsTaggableOn::Tag.new(:
|
266
|
+
describe 'name uniqeness validation' do
|
267
|
+
let(:duplicate_tag) { ActsAsTaggableOn::Tag.new(name: 'ror') }
|
257
268
|
|
258
|
-
before { ActsAsTaggableOn::Tag.create(:
|
269
|
+
before { ActsAsTaggableOn::Tag.create(name: 'ror') }
|
259
270
|
|
260
271
|
context "when don't need unique names" do
|
261
272
|
include_context 'without unique index'
|
262
|
-
it
|
263
|
-
duplicate_tag.
|
273
|
+
it 'should not run uniqueness validation' do
|
274
|
+
allow(duplicate_tag).to receive(:validates_name_uniqueness?) { false }
|
264
275
|
duplicate_tag.save
|
265
|
-
duplicate_tag.
|
276
|
+
expect(duplicate_tag).to be_persisted
|
266
277
|
end
|
267
278
|
end
|
268
279
|
|
269
|
-
context
|
270
|
-
it
|
271
|
-
duplicate_tag.
|
280
|
+
context 'when do need unique names' do
|
281
|
+
it 'should run uniqueness validation' do
|
282
|
+
expect(duplicate_tag).to_not be_valid
|
272
283
|
end
|
273
284
|
|
274
|
-
it
|
285
|
+
it 'add error to name' do
|
275
286
|
duplicate_tag.save
|
276
287
|
|
277
|
-
duplicate_tag.
|
278
|
-
duplicate_tag.errors.messages[:name].
|
288
|
+
expect(duplicate_tag.errors.size).to eq(1)
|
289
|
+
expect(duplicate_tag.errors.messages[:name]).to include('has already been taken')
|
279
290
|
end
|
280
291
|
end
|
281
292
|
end
|
@@ -1,622 +1,738 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
require 'spec_helper'
|
3
3
|
|
4
|
-
describe
|
4
|
+
describe 'Taggable To Preserve Order' do
|
5
5
|
before(:each) do
|
6
|
-
|
7
|
-
@taggable = OrderedTaggableModel.new(:name => "Bob Jones")
|
6
|
+
@taggable = OrderedTaggableModel.new(name: 'Bob Jones')
|
8
7
|
end
|
9
8
|
|
10
|
-
it "should have tag types" do
|
11
|
-
[:tags, :colours].each do |type|
|
12
|
-
OrderedTaggableModel.tag_types.should include type
|
13
|
-
end
|
14
|
-
|
15
|
-
@taggable.tag_types.should == OrderedTaggableModel.tag_types
|
16
|
-
end
|
17
9
|
|
18
|
-
it
|
10
|
+
it 'should have tag associations' do
|
19
11
|
[:tags, :colours].each do |type|
|
20
|
-
@taggable.respond_to?(type).
|
21
|
-
@taggable.respond_to?("#{type.to_s.singularize}_taggings").
|
12
|
+
expect(@taggable.respond_to?(type)).to be_truthy
|
13
|
+
expect(@taggable.respond_to?("#{type.to_s.singularize}_taggings")).to be_truthy
|
22
14
|
end
|
23
15
|
end
|
24
16
|
|
25
|
-
|
26
|
-
# [:tags, :colours].each do |type|
|
27
|
-
# OrderedTaggableModel.reflect_on_association(type).options[:order].should include('id')
|
28
|
-
# OrderedTaggableModel.reflect_on_association("#{type.to_s.singularize}_taggings".to_sym).options[:order].should include('id')
|
29
|
-
# end
|
30
|
-
# end
|
31
|
-
|
32
|
-
it "should have tag methods" do
|
17
|
+
it 'should have tag methods' do
|
33
18
|
[:tags, :colours].each do |type|
|
34
|
-
@taggable.respond_to?("#{type.to_s.singularize}_list").
|
35
|
-
@taggable.respond_to?("#{type.to_s.singularize}_list=").
|
36
|
-
@taggable.respond_to?("all_#{type
|
19
|
+
expect(@taggable.respond_to?("#{type.to_s.singularize}_list")).to be_truthy
|
20
|
+
expect(@taggable.respond_to?("#{type.to_s.singularize}_list=")).to be_truthy
|
21
|
+
expect(@taggable.respond_to?("all_#{type}_list")).to be_truthy
|
37
22
|
end
|
38
23
|
end
|
39
24
|
|
40
|
-
it
|
25
|
+
it 'should return tag list in the order the tags were created' do
|
41
26
|
# create
|
42
|
-
@taggable.tag_list =
|
43
|
-
@taggable.instance_variable_get(
|
27
|
+
@taggable.tag_list = 'rails, ruby, css'
|
28
|
+
expect(@taggable.instance_variable_get('@tag_list').instance_of?(ActsAsTaggableOn::TagList)).to be_truthy
|
44
29
|
|
45
|
-
|
30
|
+
expect(-> {
|
46
31
|
@taggable.save
|
47
|
-
}.
|
32
|
+
}).to change(ActsAsTaggableOn::Tag, :count).by(3)
|
48
33
|
|
49
34
|
@taggable.reload
|
50
|
-
@taggable.tag_list.
|
35
|
+
expect(@taggable.tag_list).to eq(%w(rails ruby css))
|
51
36
|
|
52
37
|
# update
|
53
|
-
@taggable.tag_list =
|
38
|
+
@taggable.tag_list = 'pow, ruby, rails'
|
54
39
|
@taggable.save
|
55
40
|
|
56
41
|
@taggable.reload
|
57
|
-
@taggable.tag_list.
|
42
|
+
expect(@taggable.tag_list).to eq(%w(pow ruby rails))
|
58
43
|
|
59
44
|
# update with no change
|
60
|
-
@taggable.tag_list =
|
45
|
+
@taggable.tag_list = 'pow, ruby, rails'
|
61
46
|
@taggable.save
|
62
47
|
|
63
48
|
@taggable.reload
|
64
|
-
@taggable.tag_list.
|
49
|
+
expect(@taggable.tag_list).to eq(%w(pow ruby rails))
|
65
50
|
|
66
51
|
# update to clear tags
|
67
|
-
@taggable.tag_list =
|
52
|
+
@taggable.tag_list = ''
|
68
53
|
@taggable.save
|
69
54
|
|
70
55
|
@taggable.reload
|
71
|
-
@taggable.tag_list.
|
56
|
+
expect(@taggable.tag_list).to be_empty
|
72
57
|
end
|
73
58
|
|
74
|
-
it
|
59
|
+
it 'should return tag objects in the order the tags were created' do
|
75
60
|
# create
|
76
|
-
@taggable.tag_list =
|
77
|
-
@taggable.instance_variable_get(
|
61
|
+
@taggable.tag_list = 'pow, ruby, rails'
|
62
|
+
expect(@taggable.instance_variable_get('@tag_list').instance_of?(ActsAsTaggableOn::TagList)).to be_truthy
|
78
63
|
|
79
|
-
|
64
|
+
expect(-> {
|
80
65
|
@taggable.save
|
81
|
-
}.
|
66
|
+
}).to change(ActsAsTaggableOn::Tag, :count).by(3)
|
82
67
|
|
83
68
|
@taggable.reload
|
84
|
-
@taggable.tags.map{|t| t.name}.
|
69
|
+
expect(@taggable.tags.map { |t| t.name }).to eq(%w(pow ruby rails))
|
85
70
|
|
86
71
|
# update
|
87
|
-
@taggable.tag_list =
|
72
|
+
@taggable.tag_list = 'rails, ruby, css, pow'
|
88
73
|
@taggable.save
|
89
74
|
|
90
75
|
@taggable.reload
|
91
|
-
@taggable.tags.map{|t| t.name}.
|
76
|
+
expect(@taggable.tags.map { |t| t.name }).to eq(%w(rails ruby css pow))
|
92
77
|
end
|
93
78
|
|
94
|
-
it
|
79
|
+
it 'should return tag objects in tagging id order' do
|
95
80
|
# create
|
96
|
-
@taggable.tag_list =
|
81
|
+
@taggable.tag_list = 'pow, ruby, rails'
|
97
82
|
@taggable.save
|
98
83
|
|
99
84
|
@taggable.reload
|
100
|
-
ids = @taggable.tags.map{|t| t.taggings.first.id}
|
101
|
-
ids.
|
85
|
+
ids = @taggable.tags.map { |t| t.taggings.first.id }
|
86
|
+
expect(ids).to eq(ids.sort)
|
102
87
|
|
103
88
|
# update
|
104
|
-
@taggable.tag_list =
|
89
|
+
@taggable.tag_list = 'rails, ruby, css, pow'
|
105
90
|
@taggable.save
|
106
91
|
|
107
92
|
@taggable.reload
|
108
|
-
ids = @taggable.tags.map{|t| t.taggings.first.id}
|
109
|
-
ids.
|
93
|
+
ids = @taggable.tags.map { |t| t.taggings.first.id }
|
94
|
+
expect(ids).to eq(ids.sort)
|
110
95
|
end
|
111
96
|
end
|
112
97
|
|
113
|
-
describe
|
98
|
+
describe 'Taggable' do
|
114
99
|
before(:each) do
|
115
|
-
|
116
|
-
@
|
117
|
-
@taggables = [@taggable, TaggableModel.new(:name => "John Doe")]
|
100
|
+
@taggable = TaggableModel.new(name: 'Bob Jones')
|
101
|
+
@taggables = [@taggable, TaggableModel.new(name: 'John Doe')]
|
118
102
|
end
|
119
103
|
|
120
|
-
it
|
104
|
+
it 'should have tag types' do
|
121
105
|
[:tags, :languages, :skills, :needs, :offerings].each do |type|
|
122
|
-
TaggableModel.tag_types.
|
106
|
+
expect(TaggableModel.tag_types).to include type
|
123
107
|
end
|
124
108
|
|
125
|
-
@taggable.tag_types.
|
109
|
+
expect(@taggable.tag_types).to eq(TaggableModel.tag_types)
|
126
110
|
end
|
127
111
|
|
128
|
-
it
|
129
|
-
TaggableModel.tag_counts_on(:tags).
|
112
|
+
it 'should have tag_counts_on' do
|
113
|
+
expect(TaggableModel.tag_counts_on(:tags)).to be_empty
|
130
114
|
|
131
|
-
@taggable.tag_list =
|
115
|
+
@taggable.tag_list = %w(awesome epic)
|
132
116
|
@taggable.save
|
133
117
|
|
134
|
-
TaggableModel.tag_counts_on(:tags).length.
|
135
|
-
@taggable.tag_counts_on(:tags).length.
|
118
|
+
expect(TaggableModel.tag_counts_on(:tags).length).to eq(2)
|
119
|
+
expect(@taggable.tag_counts_on(:tags).length).to eq(2)
|
136
120
|
end
|
137
121
|
|
138
|
-
it
|
139
|
-
TaggableModel.tags_on(:tags).
|
122
|
+
it 'should have tags_on' do
|
123
|
+
expect(TaggableModel.tags_on(:tags)).to be_empty
|
140
124
|
|
141
|
-
@taggable.tag_list =
|
125
|
+
@taggable.tag_list = %w(awesome epic)
|
142
126
|
@taggable.save
|
143
127
|
|
144
|
-
TaggableModel.tags_on(:tags).length.
|
145
|
-
@taggable.tags_on(:tags).length.
|
128
|
+
expect(TaggableModel.tags_on(:tags).length).to eq(2)
|
129
|
+
expect(@taggable.tags_on(:tags).length).to eq(2)
|
146
130
|
end
|
147
131
|
|
148
|
-
it
|
149
|
-
blank_taggable = TaggableModel.new(:
|
150
|
-
blank_taggable.tag_list.
|
132
|
+
it 'should return [] right after create' do
|
133
|
+
blank_taggable = TaggableModel.new(name: 'Bob Jones')
|
134
|
+
expect(blank_taggable.tag_list).to be_empty
|
151
135
|
end
|
152
136
|
|
153
|
-
it
|
154
|
-
@taggable.skill_list =
|
155
|
-
@taggable.instance_variable_get(
|
137
|
+
it 'should be able to create tags' do
|
138
|
+
@taggable.skill_list = 'ruby, rails, css'
|
139
|
+
expect(@taggable.instance_variable_get('@skill_list').instance_of?(ActsAsTaggableOn::TagList)).to be_truthy
|
156
140
|
|
157
|
-
|
141
|
+
expect(-> {
|
158
142
|
@taggable.save
|
159
|
-
}.
|
143
|
+
}).to change(ActsAsTaggableOn::Tag, :count).by(3)
|
160
144
|
|
161
145
|
@taggable.reload
|
162
|
-
@taggable.skill_list.sort.
|
146
|
+
expect(@taggable.skill_list.sort).to eq(%w(ruby rails css).sort)
|
163
147
|
end
|
164
148
|
|
165
|
-
it
|
166
|
-
@taggable.tag_list_on(:test).add(
|
167
|
-
@taggable.tag_list_cache_on(:test).
|
168
|
-
@taggable.tag_list_on(:test).
|
149
|
+
it 'should be able to create tags through the tag list directly' do
|
150
|
+
@taggable.tag_list_on(:test).add('hello')
|
151
|
+
expect(@taggable.tag_list_cache_on(:test)).to_not be_empty
|
152
|
+
expect(@taggable.tag_list_on(:test)).to eq(['hello'])
|
169
153
|
|
170
154
|
@taggable.save
|
171
155
|
@taggable.save_tags
|
172
156
|
|
173
157
|
@taggable.reload
|
174
|
-
@taggable.tag_list_on(:test).
|
158
|
+
expect(@taggable.tag_list_on(:test)).to eq(['hello'])
|
175
159
|
end
|
176
160
|
|
177
|
-
it
|
178
|
-
@taggable.skill_list =
|
179
|
-
@taggable.tag_list =
|
161
|
+
it 'should differentiate between contexts' do
|
162
|
+
@taggable.skill_list = 'ruby, rails, css'
|
163
|
+
@taggable.tag_list = 'ruby, bob, charlie'
|
180
164
|
@taggable.save
|
181
165
|
@taggable.reload
|
182
|
-
@taggable.skill_list.
|
183
|
-
@taggable.skill_list.
|
166
|
+
expect(@taggable.skill_list).to include('ruby')
|
167
|
+
expect(@taggable.skill_list).to_not include('bob')
|
184
168
|
end
|
185
169
|
|
186
|
-
it
|
187
|
-
@taggable.skill_list =
|
170
|
+
it 'should be able to remove tags through list alone' do
|
171
|
+
@taggable.skill_list = 'ruby, rails, css'
|
188
172
|
@taggable.save
|
189
173
|
@taggable.reload
|
190
|
-
@taggable.
|
191
|
-
@taggable.skill_list =
|
174
|
+
expect(@taggable.skills.count).to eq(3)
|
175
|
+
@taggable.skill_list = 'ruby, rails'
|
192
176
|
@taggable.save
|
193
177
|
@taggable.reload
|
194
|
-
@taggable.
|
178
|
+
expect(@taggable.skills.count).to eq(2)
|
195
179
|
end
|
196
180
|
|
197
|
-
it
|
198
|
-
@taggables[0].tag_list =
|
199
|
-
@taggables[1].tag_list =
|
200
|
-
@taggables[0].skill_list =
|
201
|
-
@taggables[1].skill_list =
|
202
|
-
@taggables.each{|taggable| taggable.save}
|
181
|
+
it 'should be able to select taggables by subset of tags using ActiveRelation methods' do
|
182
|
+
@taggables[0].tag_list = 'bob'
|
183
|
+
@taggables[1].tag_list = 'charlie'
|
184
|
+
@taggables[0].skill_list = 'ruby'
|
185
|
+
@taggables[1].skill_list = 'css'
|
186
|
+
@taggables.each { |taggable| taggable.save }
|
203
187
|
|
204
|
-
@found_taggables_by_tag = TaggableModel.joins(:tags).where(:
|
205
|
-
@found_taggables_by_skill = TaggableModel.joins(:skills).where(:
|
188
|
+
@found_taggables_by_tag = TaggableModel.joins(:tags).where(tags: {name: ['bob']})
|
189
|
+
@found_taggables_by_skill = TaggableModel.joins(:skills).where(tags: {name: ['ruby']})
|
206
190
|
|
207
|
-
@found_taggables_by_tag.
|
208
|
-
@found_taggables_by_tag.
|
209
|
-
@found_taggables_by_skill.
|
210
|
-
@found_taggables_by_skill.
|
191
|
+
expect(@found_taggables_by_tag).to include @taggables[0]
|
192
|
+
expect(@found_taggables_by_tag).to_not include @taggables[1]
|
193
|
+
expect(@found_taggables_by_skill).to include @taggables[0]
|
194
|
+
expect(@found_taggables_by_skill).to_not include @taggables[1]
|
211
195
|
end
|
212
196
|
|
213
|
-
it
|
214
|
-
@taggable.skill_list =
|
197
|
+
it 'should be able to find by tag' do
|
198
|
+
@taggable.skill_list = 'ruby, rails, css'
|
215
199
|
@taggable.save
|
216
200
|
|
217
|
-
TaggableModel.tagged_with(
|
201
|
+
expect(TaggableModel.tagged_with('ruby').first).to eq(@taggable)
|
218
202
|
end
|
219
203
|
|
220
|
-
it
|
221
|
-
@taggable.skill_list =
|
204
|
+
it 'should be able to get a count with find by tag when using a group by' do
|
205
|
+
@taggable.skill_list = 'ruby'
|
222
206
|
@taggable.save
|
223
207
|
|
224
|
-
expect(TaggableModel.tagged_with(
|
208
|
+
expect(TaggableModel.tagged_with('ruby').group(:created_at).count.count).to eq(1)
|
225
209
|
end
|
226
210
|
|
227
|
-
it "
|
228
|
-
@taggable.skill_list =
|
229
|
-
@taggable.tag_list = "bob, charlie"
|
211
|
+
it "shouldn't generate a query with DISTINCT by default" do
|
212
|
+
@taggable.skill_list = 'ruby, rails, css'
|
230
213
|
@taggable.save
|
231
214
|
|
232
|
-
TaggableModel.tagged_with(
|
233
|
-
TaggableModel.tagged_with("ruby, css").first.should == @taggable
|
234
|
-
TaggableModel.tagged_with("bob", :on => :skills).first.should_not == @taggable
|
235
|
-
TaggableModel.tagged_with("bob", :on => :tags).first.should == @taggable
|
215
|
+
expect(TaggableModel.tagged_with('ruby').to_sql).to_not match /DISTINCT/
|
236
216
|
end
|
237
217
|
|
238
|
-
it
|
239
|
-
|
240
|
-
|
218
|
+
it 'should be able to find by tag with context' do
|
219
|
+
@taggable.skill_list = 'ruby, rails, css'
|
220
|
+
@taggable.tag_list = 'bob, charlie'
|
221
|
+
@taggable.save
|
241
222
|
|
242
|
-
|
243
|
-
TaggableModel.tagged_with(
|
223
|
+
expect(TaggableModel.tagged_with('ruby').first).to eq(@taggable)
|
224
|
+
expect(TaggableModel.tagged_with('ruby, css').first).to eq(@taggable)
|
225
|
+
expect(TaggableModel.tagged_with('bob', on: :skills).first).to_not eq(@taggable)
|
226
|
+
expect(TaggableModel.tagged_with('bob', on: :tags).first).to eq(@taggable)
|
244
227
|
end
|
245
228
|
|
246
|
-
|
247
|
-
|
229
|
+
it 'should not care about case' do
|
230
|
+
TaggableModel.create(name: 'Bob', tag_list: 'ruby')
|
231
|
+
TaggableModel.create(name: 'Frank', tag_list: 'Ruby')
|
232
|
+
|
233
|
+
expect(ActsAsTaggableOn::Tag.all.size).to eq(1)
|
234
|
+
expect(TaggableModel.tagged_with('ruby').to_a).to eq(TaggableModel.tagged_with('Ruby').to_a)
|
235
|
+
end
|
236
|
+
|
237
|
+
it 'should be able to find by tags with other joins in the query' do
|
238
|
+
@taggable.skill_list = 'ruby, rails, css'
|
239
|
+
@taggable.tag_list = 'bob, charlie'
|
240
|
+
@taggable.save
|
241
|
+
|
242
|
+
expect(TaggableModel.group(:created_at).tagged_with(['bob', 'css'], :any => true).first).to eq(@taggable)
|
243
|
+
|
244
|
+
bob = TaggableModel.create(:name => 'Bob', :tag_list => 'ruby, rails, css')
|
245
|
+
frank = TaggableModel.create(:name => 'Frank', :tag_list => 'ruby, rails')
|
246
|
+
charlie = TaggableModel.create(:name => 'Charlie', :skill_list => 'ruby, java')
|
247
|
+
|
248
|
+
# Test for explicit distinct in select
|
249
|
+
bob.untaggable_models.create!
|
250
|
+
frank.untaggable_models.create!
|
251
|
+
charlie.untaggable_models.create!
|
252
|
+
|
253
|
+
expect(TaggableModel.select('distinct(taggable_models.id), taggable_models.*').joins(:untaggable_models).tagged_with(['css', 'java'], :any => true).to_a.sort).to eq([bob, charlie].sort)
|
254
|
+
|
255
|
+
expect(TaggableModel.select('distinct(taggable_models.id), taggable_models.*').joins(:untaggable_models).tagged_with(['rails', 'ruby'], :any => false).to_a.sort).to eq([bob, frank].sort)
|
256
|
+
end
|
257
|
+
|
258
|
+
unless ActsAsTaggableOn::Utils.using_sqlite?
|
259
|
+
it 'should not care about case for unicode names' do
|
248
260
|
ActsAsTaggableOn.strict_case_match = false
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
katia = TaggableModel.create(:name => "Katia", :tag_list => "ПРИВЕТ")
|
261
|
+
TaggableModel.create(name: 'Anya', tag_list: 'ПРИВЕТ')
|
262
|
+
TaggableModel.create(name: 'Igor', tag_list: 'привет')
|
263
|
+
TaggableModel.create(name: 'Katia', tag_list: 'ПРИВЕТ')
|
253
264
|
|
254
|
-
ActsAsTaggableOn::Tag.all.size.
|
255
|
-
TaggableModel.tagged_with(
|
265
|
+
expect(ActsAsTaggableOn::Tag.all.size).to eq(1)
|
266
|
+
expect(TaggableModel.tagged_with('привет').to_a).to eq(TaggableModel.tagged_with('ПРИВЕТ').to_a)
|
256
267
|
end
|
257
268
|
end
|
258
269
|
|
259
|
-
|
270
|
+
context 'should be able to create and find tags in languages without capitalization :' do
|
260
271
|
ActsAsTaggableOn.strict_case_match = false
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
272
|
+
{
|
273
|
+
japanese: {name: 'Chihiro', tag_list: '日本の'},
|
274
|
+
hebrew: {name: 'Salim', tag_list: 'עברית'},
|
275
|
+
chinese: {name: 'Ieie', tag_list: '中国的'},
|
276
|
+
arabic: {name: 'Yasser', tag_list: 'العربية'},
|
277
|
+
emo: {name: 'Emo', tag_list: '✏'}
|
278
|
+
}.each do |language, values|
|
279
|
+
|
280
|
+
it language do
|
281
|
+
TaggableModel.create(values)
|
282
|
+
expect(TaggableModel.tagged_with(values[:tag_list]).count).to eq(1)
|
283
|
+
end
|
284
|
+
end
|
285
|
+
end
|
273
286
|
|
274
|
-
it
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
TaggableModel.tag_counts.
|
279
|
-
TaggableModel.skill_counts.
|
287
|
+
it 'should be able to get tag counts on model as a whole' do
|
288
|
+
TaggableModel.create(name: 'Bob', tag_list: 'ruby, rails, css')
|
289
|
+
TaggableModel.create(name: 'Frank', tag_list: 'ruby, rails')
|
290
|
+
TaggableModel.create(name: 'Charlie', skill_list: 'ruby')
|
291
|
+
expect(TaggableModel.tag_counts).to_not be_empty
|
292
|
+
expect(TaggableModel.skill_counts).to_not be_empty
|
280
293
|
end
|
281
294
|
|
282
|
-
it
|
283
|
-
|
284
|
-
|
285
|
-
|
295
|
+
it 'should be able to get all tag counts on model as whole' do
|
296
|
+
TaggableModel.create(name: 'Bob', tag_list: 'ruby, rails, css')
|
297
|
+
TaggableModel.create(name: 'Frank', tag_list: 'ruby, rails')
|
298
|
+
TaggableModel.create(name: 'Charlie', skill_list: 'ruby')
|
286
299
|
|
287
|
-
TaggableModel.all_tag_counts.
|
288
|
-
TaggableModel.all_tag_counts(:
|
300
|
+
expect(TaggableModel.all_tag_counts).to_not be_empty
|
301
|
+
expect(TaggableModel.all_tag_counts(order: 'tags.id').first.count).to eq(3) # ruby
|
289
302
|
end
|
290
303
|
|
291
|
-
it
|
292
|
-
|
293
|
-
|
294
|
-
|
304
|
+
it 'should be able to get all tags on model as whole' do
|
305
|
+
TaggableModel.create(name: 'Bob', tag_list: 'ruby, rails, css')
|
306
|
+
TaggableModel.create(name: 'Frank', tag_list: 'ruby, rails')
|
307
|
+
TaggableModel.create(name: 'Charlie', skill_list: 'ruby')
|
295
308
|
|
296
|
-
TaggableModel.all_tags.
|
297
|
-
TaggableModel.all_tags(:
|
309
|
+
expect(TaggableModel.all_tags).to_not be_empty
|
310
|
+
expect(TaggableModel.all_tags(order: 'tags.id').first.name).to eq('ruby')
|
298
311
|
end
|
299
312
|
|
300
|
-
it
|
301
|
-
bob
|
302
|
-
|
303
|
-
|
313
|
+
it 'should be able to use named scopes to chain tag finds by any tags by context' do
|
314
|
+
bob = TaggableModel.create(name: 'Bob', need_list: 'rails', offering_list: 'c++')
|
315
|
+
TaggableModel.create(name: 'Frank', need_list: 'css', offering_list: 'css')
|
316
|
+
TaggableModel.create(name: 'Steve', need_list: 'c++', offering_list: 'java')
|
304
317
|
|
305
318
|
# Let's only find those who need rails or css and are offering c++ or java
|
306
|
-
TaggableModel.tagged_with(['rails, css'], :
|
319
|
+
expect(TaggableModel.tagged_with(['rails, css'], on: :needs, any: true).tagged_with(['c++', 'java'], on: :offerings, any: true).to_a).to eq([bob])
|
307
320
|
end
|
308
321
|
|
309
|
-
it
|
310
|
-
TaggableModel.create(:
|
311
|
-
TaggableModel.tagged_with(
|
322
|
+
it 'should not return read-only records' do
|
323
|
+
TaggableModel.create(name: 'Bob', tag_list: 'ruby, rails, css')
|
324
|
+
expect(TaggableModel.tagged_with('ruby').first).to_not be_readonly
|
312
325
|
end
|
313
326
|
|
314
|
-
it
|
315
|
-
|
316
|
-
|
317
|
-
|
327
|
+
it 'should be able to get scoped tag counts' do
|
328
|
+
TaggableModel.create(name: 'Bob', tag_list: 'ruby, rails, css')
|
329
|
+
TaggableModel.create(name: 'Frank', tag_list: 'ruby, rails')
|
330
|
+
TaggableModel.create(name: 'Charlie', skill_list: 'ruby')
|
318
331
|
|
319
|
-
TaggableModel.tagged_with(
|
320
|
-
TaggableModel.tagged_with(
|
332
|
+
expect(TaggableModel.tagged_with('ruby').tag_counts(order: 'tags.id').first.count).to eq(2) # ruby
|
333
|
+
expect(TaggableModel.tagged_with('ruby').skill_counts.first.count).to eq(1) # ruby
|
321
334
|
end
|
322
335
|
|
323
|
-
it
|
324
|
-
|
325
|
-
|
326
|
-
|
336
|
+
it 'should be able to get all scoped tag counts' do
|
337
|
+
TaggableModel.create(name: 'Bob', tag_list: 'ruby, rails, css')
|
338
|
+
TaggableModel.create(name: 'Frank', tag_list: 'ruby, rails')
|
339
|
+
TaggableModel.create(name: 'Charlie', skill_list: 'ruby')
|
327
340
|
|
328
|
-
TaggableModel.tagged_with(
|
341
|
+
expect(TaggableModel.tagged_with('ruby').all_tag_counts(order: 'tags.id').first.count).to eq(3) # ruby
|
329
342
|
end
|
330
343
|
|
331
|
-
it
|
332
|
-
|
333
|
-
|
334
|
-
|
344
|
+
it 'should be able to get all scoped tags' do
|
345
|
+
TaggableModel.create(name: 'Bob', tag_list: 'ruby, rails, css')
|
346
|
+
TaggableModel.create(name: 'Frank', tag_list: 'ruby, rails')
|
347
|
+
TaggableModel.create(name: 'Charlie', skill_list: 'ruby')
|
335
348
|
|
336
|
-
TaggableModel.tagged_with(
|
349
|
+
expect(TaggableModel.tagged_with('ruby').all_tags(order: 'tags.id').first.name).to eq('ruby')
|
337
350
|
end
|
338
351
|
|
339
352
|
it 'should only return tag counts for the available scope' do
|
340
|
-
|
341
|
-
|
342
|
-
|
353
|
+
frank = TaggableModel.create(name: 'Frank', tag_list: 'ruby, rails')
|
354
|
+
TaggableModel.create(name: 'Bob', tag_list: 'ruby, rails, css')
|
355
|
+
TaggableModel.create(name: 'Charlie', skill_list: 'ruby, java')
|
343
356
|
|
344
|
-
TaggableModel.tagged_with('rails').all_tag_counts.
|
345
|
-
TaggableModel.tagged_with('rails').all_tag_counts.any? { |tag| tag.name == 'java' }.
|
357
|
+
expect(TaggableModel.tagged_with('rails').all_tag_counts.size).to eq(3)
|
358
|
+
expect(TaggableModel.tagged_with('rails').all_tag_counts.any? { |tag| tag.name == 'java' }).to be_falsy
|
346
359
|
|
347
360
|
# Test specific join syntaxes:
|
348
361
|
frank.untaggable_models.create!
|
349
|
-
TaggableModel.tagged_with('rails').joins(:untaggable_models).all_tag_counts.
|
350
|
-
TaggableModel.tagged_with('rails').joins(:
|
351
|
-
TaggableModel.tagged_with('rails').joins([:untaggable_models]).all_tag_counts.
|
362
|
+
expect(TaggableModel.tagged_with('rails').joins(:untaggable_models).all_tag_counts.size).to eq(2)
|
363
|
+
expect(TaggableModel.tagged_with('rails').joins(untaggable_models: :taggable_model).all_tag_counts.size).to eq(2)
|
364
|
+
expect(TaggableModel.tagged_with('rails').joins([:untaggable_models]).all_tag_counts.size).to eq(2)
|
352
365
|
end
|
353
366
|
|
354
367
|
it 'should only return tags for the available scope' do
|
355
|
-
|
356
|
-
|
357
|
-
|
368
|
+
frank = TaggableModel.create(name: 'Frank', tag_list: 'ruby, rails')
|
369
|
+
TaggableModel.create(name: 'Bob', tag_list: 'ruby, rails, css')
|
370
|
+
TaggableModel.create(name: 'Charlie', skill_list: 'ruby, java')
|
358
371
|
|
359
|
-
TaggableModel.tagged_with('rails').all_tags.
|
360
|
-
TaggableModel.tagged_with('rails').all_tags.any? { |tag| tag.name == 'java' }.
|
372
|
+
expect(TaggableModel.tagged_with('rails').all_tags.count).to eq(3)
|
373
|
+
expect(TaggableModel.tagged_with('rails').all_tags.any? { |tag| tag.name == 'java' }).to be_falsy
|
361
374
|
|
362
375
|
# Test specific join syntaxes:
|
363
376
|
frank.untaggable_models.create!
|
364
|
-
TaggableModel.tagged_with('rails').joins(:untaggable_models).all_tags.
|
365
|
-
TaggableModel.tagged_with('rails').joins(
|
366
|
-
TaggableModel.tagged_with('rails').joins([:untaggable_models]).all_tags.
|
377
|
+
expect(TaggableModel.tagged_with('rails').joins(:untaggable_models).all_tags.size).to eq(2)
|
378
|
+
expect(TaggableModel.tagged_with('rails').joins(untaggable_models: :taggable_model).all_tags.size).to eq(2)
|
379
|
+
expect(TaggableModel.tagged_with('rails').joins([:untaggable_models]).all_tags.size).to eq(2)
|
367
380
|
end
|
368
381
|
|
369
|
-
it
|
370
|
-
bob = TaggableModel.create(:
|
371
|
-
bob.set_tag_list_on(:rotors,
|
372
|
-
bob.tag_list_on(:rotors).
|
382
|
+
it 'should be able to set a custom tag context list' do
|
383
|
+
bob = TaggableModel.create(name: 'Bob')
|
384
|
+
bob.set_tag_list_on(:rotors, 'spinning, jumping')
|
385
|
+
expect(bob.tag_list_on(:rotors)).to eq(%w(spinning jumping))
|
373
386
|
bob.save
|
374
387
|
bob.reload
|
375
|
-
bob.tags_on(:rotors).
|
388
|
+
expect(bob.tags_on(:rotors)).to_not be_empty
|
376
389
|
end
|
377
390
|
|
378
|
-
it
|
379
|
-
bob = TaggableModel.create(:
|
380
|
-
frank = TaggableModel.create(:
|
381
|
-
steve = TaggableModel.create(:
|
391
|
+
it 'should be able to find tagged' do
|
392
|
+
bob = TaggableModel.create(name: 'Bob', tag_list: 'fitter, happier, more productive', skill_list: 'ruby, rails, css')
|
393
|
+
frank = TaggableModel.create(name: 'Frank', tag_list: 'weaker, depressed, inefficient', skill_list: 'ruby, rails, css')
|
394
|
+
steve = TaggableModel.create(name: 'Steve', tag_list: 'fitter, happier, more productive', skill_list: 'c++, java, ruby')
|
382
395
|
|
383
|
-
TaggableModel.tagged_with(
|
384
|
-
TaggableModel.tagged_with(
|
385
|
-
TaggableModel.tagged_with(
|
396
|
+
expect(TaggableModel.tagged_with('ruby', order: 'taggable_models.name').to_a).to eq([bob, frank, steve])
|
397
|
+
expect(TaggableModel.tagged_with('ruby, rails', order: 'taggable_models.name').to_a).to eq([bob, frank])
|
398
|
+
expect(TaggableModel.tagged_with(%w(ruby rails), order: 'taggable_models.name').to_a).to eq([bob, frank])
|
386
399
|
end
|
387
400
|
|
388
|
-
it
|
389
|
-
bob = TaggableModel.create(:
|
390
|
-
TaggableModel.tagged_with("'I love the ,comma,'").
|
401
|
+
it 'should be able to find tagged with quotation marks' do
|
402
|
+
bob = TaggableModel.create(name: 'Bob', tag_list: "fitter, happier, more productive, 'I love the ,comma,'")
|
403
|
+
expect(TaggableModel.tagged_with("'I love the ,comma,'")).to include(bob)
|
391
404
|
end
|
392
405
|
|
393
|
-
it
|
394
|
-
bob = TaggableModel.create(:
|
395
|
-
TaggableModel.tagged_with(
|
406
|
+
it 'should be able to find tagged with invalid tags' do
|
407
|
+
bob = TaggableModel.create(name: 'Bob', tag_list: 'fitter, happier, more productive')
|
408
|
+
expect(TaggableModel.tagged_with('sad, happier')).to_not include(bob)
|
396
409
|
end
|
397
410
|
|
398
|
-
it
|
399
|
-
bob = TaggableModel.create(:
|
400
|
-
frank = TaggableModel.create(:
|
401
|
-
steve = TaggableModel.create(:
|
411
|
+
it 'should be able to find tagged with any tag' do
|
412
|
+
bob = TaggableModel.create(name: 'Bob', tag_list: 'fitter, happier, more productive', skill_list: 'ruby, rails, css')
|
413
|
+
frank = TaggableModel.create(name: 'Frank', tag_list: 'weaker, depressed, inefficient', skill_list: 'ruby, rails, css')
|
414
|
+
steve = TaggableModel.create(name: 'Steve', tag_list: 'fitter, happier, more productive', skill_list: 'c++, java, ruby')
|
402
415
|
|
403
|
-
TaggableModel.tagged_with(
|
404
|
-
TaggableModel.tagged_with(
|
405
|
-
TaggableModel.tagged_with(
|
416
|
+
expect(TaggableModel.tagged_with(%w(ruby java), order: 'taggable_models.name', any: true).to_a).to eq([bob, frank, steve])
|
417
|
+
expect(TaggableModel.tagged_with(%w(c++ fitter), order: 'taggable_models.name', any: true).to_a).to eq([bob, steve])
|
418
|
+
expect(TaggableModel.tagged_with(%w(depressed css), order: 'taggable_models.name', any: true).to_a).to eq([bob, frank])
|
406
419
|
end
|
407
420
|
|
408
|
-
it
|
409
|
-
bob = TaggableModel.create(:
|
410
|
-
frank = TaggableModel.create(:
|
411
|
-
steve = TaggableModel.create(:
|
421
|
+
it 'should be able to order by number of matching tags when matching any' do
|
422
|
+
bob = TaggableModel.create(name: 'Bob', tag_list: 'fitter, happier, more productive', skill_list: 'ruby, rails, css')
|
423
|
+
frank = TaggableModel.create(name: 'Frank', tag_list: 'weaker, depressed, inefficient', skill_list: 'ruby, rails, css')
|
424
|
+
steve = TaggableModel.create(name: 'Steve', tag_list: 'fitter, happier, more productive', skill_list: 'c++, java, ruby')
|
412
425
|
|
413
|
-
TaggableModel.tagged_with(
|
414
|
-
TaggableModel.tagged_with(
|
415
|
-
TaggableModel.tagged_with(
|
416
|
-
TaggableModel.tagged_with([
|
417
|
-
TaggableModel.tagged_with(
|
426
|
+
expect(TaggableModel.tagged_with(%w(ruby java), any: true, order_by_matching_tag_count: true, order: 'taggable_models.name').to_a).to eq([steve, bob, frank])
|
427
|
+
expect(TaggableModel.tagged_with(%w(c++ fitter), any: true, order_by_matching_tag_count: true, order: 'taggable_models.name').to_a).to eq([steve, bob])
|
428
|
+
expect(TaggableModel.tagged_with(%w(depressed css), any: true, order_by_matching_tag_count: true, order: 'taggable_models.name').to_a).to eq([frank, bob])
|
429
|
+
expect(TaggableModel.tagged_with(['fitter', 'happier', 'more productive', 'c++', 'java', 'ruby'], any: true, order_by_matching_tag_count: true, order: 'taggable_models.name').to_a).to eq([steve, bob, frank])
|
430
|
+
expect(TaggableModel.tagged_with(%w(c++ java ruby fitter), any: true, order_by_matching_tag_count: true, order: 'taggable_models.name').to_a).to eq([steve, bob, frank])
|
418
431
|
end
|
419
432
|
|
420
|
-
context
|
421
|
-
it
|
422
|
-
bob = TaggableModel.create(:
|
423
|
-
frank = TaggableModel.create(:
|
424
|
-
steve = TaggableModel.create(:
|
425
|
-
jim = TaggableModel.create(:
|
426
|
-
|
433
|
+
context 'wild: true' do
|
434
|
+
it 'should use params as wildcards' do
|
435
|
+
bob = TaggableModel.create(name: 'Bob', tag_list: 'bob, tricia')
|
436
|
+
frank = TaggableModel.create(name: 'Frank', tag_list: 'bobby, jim')
|
437
|
+
steve = TaggableModel.create(name: 'Steve', tag_list: 'john, patricia')
|
438
|
+
jim = TaggableModel.create(name: 'Jim', tag_list: 'jim, steve')
|
427
439
|
|
428
|
-
TaggableModel.tagged_with(
|
429
|
-
TaggableModel.tagged_with(
|
440
|
+
expect(TaggableModel.tagged_with(%w(bob tricia), wild: true, any: true).to_a.sort_by { |o| o.id }).to eq([bob, frank, steve])
|
441
|
+
expect(TaggableModel.tagged_with(%w(bob tricia), wild: true, exclude: true).to_a).to eq([jim])
|
430
442
|
end
|
431
443
|
end
|
432
444
|
|
433
|
-
it
|
434
|
-
bob = TaggableModel.create(:
|
435
|
-
bob.set_tag_list_on(:rotors,
|
436
|
-
bob.tag_list_on(:rotors).
|
445
|
+
it 'should be able to find tagged on a custom tag context' do
|
446
|
+
bob = TaggableModel.create(name: 'Bob')
|
447
|
+
bob.set_tag_list_on(:rotors, 'spinning, jumping')
|
448
|
+
expect(bob.tag_list_on(:rotors)).to eq(%w(spinning jumping))
|
437
449
|
bob.save
|
438
450
|
|
439
|
-
TaggableModel.tagged_with(
|
451
|
+
expect(TaggableModel.tagged_with('spinning', on: :rotors).to_a).to eq([bob])
|
440
452
|
end
|
441
453
|
|
442
|
-
it
|
443
|
-
bob = TaggableModel.create(:
|
444
|
-
frank = TaggableModel.create(:
|
445
|
-
steve = TaggableModel.create(:
|
454
|
+
it 'should be able to use named scopes to chain tag finds' do
|
455
|
+
bob = TaggableModel.create(name: 'Bob', tag_list: 'fitter, happier, more productive', skill_list: 'ruby, rails, css')
|
456
|
+
frank = TaggableModel.create(name: 'Frank', tag_list: 'weaker, depressed, inefficient', skill_list: 'ruby, rails, css')
|
457
|
+
steve = TaggableModel.create(name: 'Steve', tag_list: 'fitter, happier, more productive', skill_list: 'c++, java, python')
|
446
458
|
|
447
459
|
# Let's only find those productive Rails developers
|
448
|
-
TaggableModel.tagged_with('rails', :
|
449
|
-
TaggableModel.tagged_with('happier', :
|
450
|
-
TaggableModel.tagged_with('rails', :
|
451
|
-
TaggableModel.tagged_with('rails').tagged_with('happier', :
|
460
|
+
expect(TaggableModel.tagged_with('rails', on: :skills, order: 'taggable_models.name').to_a).to eq([bob, frank])
|
461
|
+
expect(TaggableModel.tagged_with('happier', on: :tags, order: 'taggable_models.name').to_a).to eq([bob, steve])
|
462
|
+
expect(TaggableModel.tagged_with('rails', on: :skills).tagged_with('happier', on: :tags).to_a).to eq([bob])
|
463
|
+
expect(TaggableModel.tagged_with('rails').tagged_with('happier', on: :tags).to_a).to eq([bob])
|
452
464
|
end
|
453
465
|
|
454
|
-
it
|
455
|
-
|
456
|
-
|
457
|
-
steve = TaggableModel.create(:
|
466
|
+
it 'should be able to find tagged with only the matching tags' do
|
467
|
+
TaggableModel.create(name: 'Bob', tag_list: 'lazy, happier')
|
468
|
+
TaggableModel.create(name: 'Frank', tag_list: 'fitter, happier, inefficient')
|
469
|
+
steve = TaggableModel.create(name: 'Steve', tag_list: 'fitter, happier')
|
458
470
|
|
459
|
-
TaggableModel.tagged_with(
|
471
|
+
expect(TaggableModel.tagged_with('fitter, happier', match_all: true).to_a).to eq([steve])
|
460
472
|
end
|
461
473
|
|
462
|
-
it
|
463
|
-
|
464
|
-
frank = TaggableModel.create(:
|
465
|
-
|
474
|
+
it 'should be able to find tagged with only the matching tags for a context' do
|
475
|
+
TaggableModel.create(name: 'Bob', tag_list: 'lazy, happier', skill_list: 'ruby, rails, css')
|
476
|
+
frank = TaggableModel.create(name: 'Frank', tag_list: 'fitter, happier, inefficient', skill_list: 'css')
|
477
|
+
TaggableModel.create(name: 'Steve', tag_list: 'fitter, happier', skill_list: 'ruby, rails, css')
|
466
478
|
|
467
|
-
TaggableModel.tagged_with(
|
479
|
+
expect(TaggableModel.tagged_with('css', on: :skills, match_all: true).to_a).to eq([frank])
|
468
480
|
end
|
469
481
|
|
470
|
-
it
|
471
|
-
|
472
|
-
frank = TaggableModel.create(:
|
473
|
-
steve = TaggableModel.create(:
|
482
|
+
it 'should be able to find tagged with some excluded tags' do
|
483
|
+
TaggableModel.create(name: 'Bob', tag_list: 'happier, lazy')
|
484
|
+
frank = TaggableModel.create(name: 'Frank', tag_list: 'happier')
|
485
|
+
steve = TaggableModel.create(name: 'Steve', tag_list: 'happier')
|
474
486
|
|
475
|
-
TaggableModel.tagged_with(
|
487
|
+
expect(TaggableModel.tagged_with('lazy', exclude: true)).to include(frank, steve)
|
488
|
+
expect(TaggableModel.tagged_with('lazy', exclude: true).size).to eq(2)
|
476
489
|
end
|
477
490
|
|
478
|
-
it
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
TaggableModel.tagged_with([]).should == []
|
491
|
+
it 'should return an empty scope for empty tags' do
|
492
|
+
['', ' ', nil, []].each do |tag|
|
493
|
+
expect(TaggableModel.tagged_with(tag)).to be_empty
|
494
|
+
end
|
483
495
|
end
|
484
496
|
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
497
|
+
context 'Duplicates' do
|
498
|
+
context 'should not create duplicate taggings' do
|
499
|
+
let(:bob) { TaggableModel.create(name: 'Bob') }
|
500
|
+
context 'case sensitive' do
|
501
|
+
it '#add' do
|
502
|
+
expect(lambda {
|
503
|
+
bob.tag_list.add 'happier'
|
504
|
+
bob.tag_list.add 'happier'
|
505
|
+
bob.tag_list.add 'happier', 'rich', 'funny'
|
506
|
+
bob.save
|
507
|
+
}).to change(ActsAsTaggableOn::Tagging, :count).by(3)
|
508
|
+
end
|
509
|
+
it '#<<' do
|
510
|
+
expect(lambda {
|
511
|
+
bob.tag_list << 'social'
|
512
|
+
bob.tag_list << 'social'
|
513
|
+
bob.tag_list << 'social' << 'wow'
|
514
|
+
bob.save
|
515
|
+
}).to change(ActsAsTaggableOn::Tagging, :count).by(2)
|
516
|
+
|
517
|
+
end
|
518
|
+
|
519
|
+
it 'unicode' do
|
520
|
+
|
521
|
+
expect(lambda {
|
522
|
+
bob.tag_list.add 'ПРИВЕТ'
|
523
|
+
bob.tag_list.add 'ПРИВЕТ'
|
524
|
+
bob.tag_list.add 'ПРИВЕТ', 'ПРИВЕТ'
|
525
|
+
bob.save
|
526
|
+
}).to change(ActsAsTaggableOn::Tagging, :count).by(1)
|
527
|
+
|
528
|
+
end
|
529
|
+
|
530
|
+
it '#=' do
|
531
|
+
expect(lambda {
|
532
|
+
bob.tag_list = ['Happy', 'Happy']
|
533
|
+
bob.save
|
534
|
+
}).to change(ActsAsTaggableOn::Tagging, :count).by(1)
|
535
|
+
end
|
536
|
+
end
|
537
|
+
context 'case insensitive' do
|
538
|
+
before(:all) { ActsAsTaggableOn.force_lowercase = true }
|
539
|
+
after(:all) { ActsAsTaggableOn.force_lowercase = false }
|
540
|
+
|
541
|
+
it '#<<' do
|
542
|
+
expect(lambda {
|
543
|
+
bob.tag_list << 'Alone'
|
544
|
+
bob.tag_list << 'AloNe'
|
545
|
+
bob.tag_list << 'ALONE' << 'In The dark'
|
546
|
+
bob.save
|
547
|
+
}).to change(ActsAsTaggableOn::Tagging, :count).by(2)
|
548
|
+
|
549
|
+
end
|
550
|
+
|
551
|
+
it '#add' do
|
552
|
+
expect(lambda {
|
553
|
+
bob.tag_list.add 'forever'
|
554
|
+
bob.tag_list.add 'ForEver'
|
555
|
+
bob.tag_list.add 'FOREVER', 'ALONE'
|
556
|
+
bob.save
|
557
|
+
}).to change(ActsAsTaggableOn::Tagging, :count).by(2)
|
558
|
+
end
|
559
|
+
|
560
|
+
it 'unicode' do
|
561
|
+
|
562
|
+
expect(lambda {
|
563
|
+
bob.tag_list.add 'ПРИВЕТ'
|
564
|
+
bob.tag_list.add 'привет', 'Привет'
|
565
|
+
bob.save
|
566
|
+
}).to change(ActsAsTaggableOn::Tagging, :count).by(1)
|
567
|
+
|
568
|
+
end
|
569
|
+
|
570
|
+
it '#=' do
|
571
|
+
expect(lambda {
|
572
|
+
bob.tag_list = ['Happy', 'HAPPY']
|
573
|
+
bob.save
|
574
|
+
}).to change(ActsAsTaggableOn::Tagging, :count).by(1)
|
575
|
+
end
|
576
|
+
|
577
|
+
|
578
|
+
end
|
579
|
+
|
580
|
+
|
581
|
+
end
|
582
|
+
|
583
|
+
if ActsAsTaggableOn::Utils.supports_concurrency?
|
584
|
+
xit 'should not duplicate tags added on different threads' do
|
585
|
+
#TODO, try with more threads and fix deadlock
|
586
|
+
thread_count = 4
|
587
|
+
barrier = Barrier.new thread_count
|
588
|
+
|
589
|
+
expect {
|
590
|
+
thread_count.times.map do |idx|
|
591
|
+
Thread.start do
|
592
|
+
connor = TaggableModel.first_or_create(name: 'Connor')
|
593
|
+
connor.tag_list = 'There, can, be, only, one'
|
594
|
+
barrier.wait
|
595
|
+
begin
|
596
|
+
connor.save
|
597
|
+
rescue ActsAsTaggableOn::DuplicateTagError
|
598
|
+
# second save should succeed
|
599
|
+
connor.save
|
600
|
+
end
|
601
|
+
end
|
602
|
+
end.map(&:join)
|
603
|
+
}.to change(ActsAsTaggableOn::Tag, :count).by(5)
|
604
|
+
end
|
605
|
+
end
|
492
606
|
end
|
493
607
|
|
494
|
-
describe
|
608
|
+
describe 'Associations' do
|
495
609
|
before(:each) do
|
496
|
-
@taggable = TaggableModel.create(:
|
610
|
+
@taggable = TaggableModel.create(tag_list: 'awesome, epic')
|
497
611
|
end
|
498
612
|
|
499
|
-
it
|
613
|
+
it 'should not remove tags when creating associated objects' do
|
500
614
|
@taggable.untaggable_models.create!
|
501
615
|
@taggable.reload
|
502
|
-
@taggable.tag_list.
|
616
|
+
expect(@taggable.tag_list.size).to eq(2)
|
503
617
|
end
|
504
618
|
end
|
505
619
|
|
506
|
-
describe
|
507
|
-
it
|
508
|
-
|
620
|
+
describe 'grouped_column_names_for method' do
|
621
|
+
it 'should return all column names joined for Tag GROUP clause' do
|
622
|
+
# NOTE: type column supports an STI Tag subclass in the test suite, though
|
623
|
+
# isn't included by default in the migration generator
|
624
|
+
expect(@taggable.grouped_column_names_for(ActsAsTaggableOn::Tag)).
|
625
|
+
to eq('tags.id, tags.name, tags.taggings_count, tags.type')
|
509
626
|
end
|
510
627
|
|
511
|
-
it
|
512
|
-
@taggable.grouped_column_names_for(TaggableModel).
|
628
|
+
it 'should return all column names joined for TaggableModel GROUP clause' do
|
629
|
+
expect(@taggable.grouped_column_names_for(TaggableModel)).to eq('taggable_models.id, taggable_models.name, taggable_models.type')
|
513
630
|
end
|
514
631
|
|
515
|
-
it
|
516
|
-
@taggable.grouped_column_names_for(TaggableModel).
|
632
|
+
it 'should return all column names joined for NonStandardIdTaggableModel GROUP clause' do
|
633
|
+
expect(@taggable.grouped_column_names_for(TaggableModel)).to eq("taggable_models.#{TaggableModel.primary_key}, taggable_models.name, taggable_models.type")
|
517
634
|
end
|
518
635
|
end
|
519
636
|
|
520
|
-
describe
|
637
|
+
describe 'NonStandardIdTaggable' do
|
521
638
|
before(:each) do
|
522
|
-
|
523
|
-
@
|
524
|
-
@taggables = [@taggable, NonStandardIdTaggableModel.new(:name => "John Doe")]
|
639
|
+
@taggable = NonStandardIdTaggableModel.new(name: 'Bob Jones')
|
640
|
+
@taggables = [@taggable, NonStandardIdTaggableModel.new(name: 'John Doe')]
|
525
641
|
end
|
526
642
|
|
527
|
-
it
|
643
|
+
it 'should have tag types' do
|
528
644
|
[:tags, :languages, :skills, :needs, :offerings].each do |type|
|
529
|
-
NonStandardIdTaggableModel.tag_types.
|
645
|
+
expect(NonStandardIdTaggableModel.tag_types).to include type
|
530
646
|
end
|
531
647
|
|
532
|
-
@taggable.tag_types.
|
648
|
+
expect(@taggable.tag_types).to eq(NonStandardIdTaggableModel.tag_types)
|
533
649
|
end
|
534
650
|
|
535
|
-
it
|
536
|
-
NonStandardIdTaggableModel.tag_counts_on(:tags).
|
651
|
+
it 'should have tag_counts_on' do
|
652
|
+
expect(NonStandardIdTaggableModel.tag_counts_on(:tags)).to be_empty
|
537
653
|
|
538
|
-
@taggable.tag_list =
|
654
|
+
@taggable.tag_list = %w(awesome epic)
|
539
655
|
@taggable.save
|
540
656
|
|
541
|
-
NonStandardIdTaggableModel.tag_counts_on(:tags).length.
|
542
|
-
@taggable.tag_counts_on(:tags).length.
|
657
|
+
expect(NonStandardIdTaggableModel.tag_counts_on(:tags).length).to eq(2)
|
658
|
+
expect(@taggable.tag_counts_on(:tags).length).to eq(2)
|
543
659
|
end
|
544
660
|
|
545
|
-
it
|
546
|
-
NonStandardIdTaggableModel.tags_on(:tags).
|
661
|
+
it 'should have tags_on' do
|
662
|
+
expect(NonStandardIdTaggableModel.tags_on(:tags)).to be_empty
|
547
663
|
|
548
|
-
@taggable.tag_list =
|
664
|
+
@taggable.tag_list = %w(awesome epic)
|
549
665
|
@taggable.save
|
550
666
|
|
551
|
-
NonStandardIdTaggableModel.tags_on(:tags).length.
|
552
|
-
@taggable.tags_on(:tags).length.
|
667
|
+
expect(NonStandardIdTaggableModel.tags_on(:tags).length).to eq(2)
|
668
|
+
expect(@taggable.tags_on(:tags).length).to eq(2)
|
553
669
|
end
|
554
670
|
|
555
|
-
it
|
556
|
-
@taggable.skill_list =
|
557
|
-
@taggable.instance_variable_get(
|
671
|
+
it 'should be able to create tags' do
|
672
|
+
@taggable.skill_list = 'ruby, rails, css'
|
673
|
+
expect(@taggable.instance_variable_get('@skill_list').instance_of?(ActsAsTaggableOn::TagList)).to be_truthy
|
558
674
|
|
559
|
-
|
675
|
+
expect(-> {
|
560
676
|
@taggable.save
|
561
|
-
}.
|
677
|
+
}).to change(ActsAsTaggableOn::Tag, :count).by(3)
|
562
678
|
|
563
679
|
@taggable.reload
|
564
|
-
@taggable.skill_list.sort.
|
680
|
+
expect(@taggable.skill_list.sort).to eq(%w(ruby rails css).sort)
|
565
681
|
end
|
566
682
|
|
567
|
-
it
|
568
|
-
@taggable.tag_list_on(:test).add(
|
569
|
-
@taggable.tag_list_cache_on(:test).
|
570
|
-
@taggable.tag_list_on(:test).
|
683
|
+
it 'should be able to create tags through the tag list directly' do
|
684
|
+
@taggable.tag_list_on(:test).add('hello')
|
685
|
+
expect(@taggable.tag_list_cache_on(:test)).to_not be_empty
|
686
|
+
expect(@taggable.tag_list_on(:test)).to eq(['hello'])
|
571
687
|
|
572
688
|
@taggable.save
|
573
689
|
@taggable.save_tags
|
574
690
|
|
575
691
|
@taggable.reload
|
576
|
-
@taggable.tag_list_on(:test).
|
692
|
+
expect(@taggable.tag_list_on(:test)).to eq(['hello'])
|
577
693
|
end
|
578
694
|
end
|
579
695
|
|
580
|
-
describe
|
581
|
-
context
|
696
|
+
describe 'Dirty Objects' do
|
697
|
+
context 'with un-contexted tags' do
|
582
698
|
before(:each) do
|
583
|
-
@taggable = TaggableModel.create(:
|
699
|
+
@taggable = TaggableModel.create(tag_list: 'awesome, epic')
|
584
700
|
end
|
585
701
|
|
586
|
-
context
|
702
|
+
context 'when tag_list changed' do
|
587
703
|
before(:each) do
|
588
|
-
@taggable.changes.
|
704
|
+
expect(@taggable.changes).to be_empty
|
589
705
|
@taggable.tag_list = 'one'
|
590
706
|
end
|
591
707
|
|
592
708
|
it 'should show changes of dirty object' do
|
593
|
-
@taggable.changes.
|
709
|
+
expect(@taggable.changes).to eq({'tag_list' => ['awesome, epic', ['one']]})
|
594
710
|
end
|
595
711
|
|
596
712
|
it 'flags tag_list as changed' do
|
597
|
-
@taggable.tag_list_changed
|
713
|
+
expect(@taggable.tag_list_changed?).to be_truthy
|
598
714
|
end
|
599
715
|
|
600
716
|
it 'preserves original value' do
|
601
|
-
@taggable.tag_list_was.
|
717
|
+
expect(@taggable.tag_list_was).to eq('awesome, epic')
|
602
718
|
end
|
603
719
|
|
604
720
|
it 'shows what the change was' do
|
605
|
-
@taggable.tag_list_change.
|
721
|
+
expect(@taggable.tag_list_change).to eq(['awesome, epic', ['one']])
|
606
722
|
end
|
607
723
|
end
|
608
724
|
|
609
725
|
context 'when tag_list is the same' do
|
610
726
|
before(:each) do
|
611
|
-
@taggable.tag_list =
|
727
|
+
@taggable.tag_list = 'awesome, epic'
|
612
728
|
end
|
613
729
|
|
614
730
|
it 'is not flagged as changed' do
|
615
|
-
@taggable.tag_list_changed
|
731
|
+
expect(@taggable.tag_list_changed?).to be_falsy
|
616
732
|
end
|
617
733
|
|
618
734
|
it 'does not show any changes to the taggable item' do
|
619
|
-
@taggable.changes.
|
735
|
+
expect(@taggable.changes).to be_empty
|
620
736
|
end
|
621
737
|
|
622
738
|
context "and using a delimiter different from a ','" do
|
@@ -630,64 +746,134 @@ describe "Taggable" do
|
|
630
746
|
end
|
631
747
|
|
632
748
|
it 'does not show any changes to the taggable item when using array assignments' do
|
633
|
-
@taggable.tag_list =
|
634
|
-
@taggable.changes.
|
749
|
+
@taggable.tag_list = %w(awesome epic)
|
750
|
+
expect(@taggable.changes).to be_empty
|
635
751
|
end
|
636
752
|
end
|
637
753
|
end
|
638
754
|
end
|
639
755
|
|
640
|
-
context
|
756
|
+
context 'with context tags' do
|
641
757
|
before(:each) do
|
642
|
-
@taggable = TaggableModel.create(
|
758
|
+
@taggable = TaggableModel.create('language_list' => 'awesome, epic')
|
643
759
|
end
|
644
760
|
|
645
|
-
context
|
761
|
+
context 'when language_list changed' do
|
646
762
|
before(:each) do
|
647
|
-
@taggable.changes.
|
763
|
+
expect(@taggable.changes).to be_empty
|
648
764
|
@taggable.language_list = 'one'
|
649
765
|
end
|
650
766
|
|
651
767
|
it 'should show changes of dirty object' do
|
652
|
-
@taggable.changes.
|
768
|
+
expect(@taggable.changes).to eq({'language_list' => ['awesome, epic', ['one']]})
|
653
769
|
end
|
654
770
|
|
655
771
|
it 'flags language_list as changed' do
|
656
|
-
@taggable.language_list_changed
|
772
|
+
expect(@taggable.language_list_changed?).to be_truthy
|
657
773
|
end
|
658
774
|
|
659
775
|
it 'preserves original value' do
|
660
|
-
@taggable.language_list_was.
|
776
|
+
expect(@taggable.language_list_was).to eq('awesome, epic')
|
661
777
|
end
|
662
778
|
|
663
779
|
it 'shows what the change was' do
|
664
|
-
@taggable.language_list_change.
|
780
|
+
expect(@taggable.language_list_change).to eq(['awesome, epic', ['one']])
|
665
781
|
end
|
666
782
|
|
667
783
|
it 'shows what the changes were' do
|
668
|
-
@taggable.language_list_changes.
|
784
|
+
expect(@taggable.language_list_changes).to eq(['awesome, epic', ['one']])
|
669
785
|
end
|
670
786
|
end
|
671
787
|
|
672
788
|
context 'when language_list is the same' do
|
673
789
|
before(:each) do
|
674
|
-
@taggable.language_list =
|
790
|
+
@taggable.language_list = 'awesome, epic'
|
675
791
|
end
|
676
792
|
|
677
793
|
it 'is not flagged as changed' do
|
678
|
-
@taggable.language_list_changed
|
794
|
+
expect(@taggable.language_list_changed?).to be_falsy
|
679
795
|
end
|
680
796
|
|
681
797
|
it 'does not show any changes to the taggable item' do
|
682
|
-
@taggable.changes.
|
798
|
+
expect(@taggable.changes).to be_empty
|
799
|
+
end
|
800
|
+
end
|
801
|
+
end
|
802
|
+
end
|
803
|
+
|
804
|
+
describe 'Autogenerated methods' do
|
805
|
+
it 'should be overridable' do
|
806
|
+
expect(TaggableModel.create(tag_list: 'woo').tag_list_submethod_called).to be_truthy
|
807
|
+
end
|
808
|
+
end
|
809
|
+
|
810
|
+
# See https://github.com/mbleigh/acts-as-taggable-on/pull/457 for details
|
811
|
+
context 'tag_counts and aggreating scopes, compatability with MySQL ' do
|
812
|
+
before(:each) do
|
813
|
+
TaggableModel.new(:name => 'Barb Jones').tap { |t| t.tag_list = %w(awesome fun) }.save
|
814
|
+
TaggableModel.new(:name => 'John Doe').tap { |t| t.tag_list = %w(cool fun hella) }.save
|
815
|
+
TaggableModel.new(:name => 'Jo Doe').tap { |t| t.tag_list = %w(curious young naive sharp) }.save
|
816
|
+
|
817
|
+
TaggableModel.all.each { |t| t.save }
|
818
|
+
end
|
819
|
+
|
820
|
+
context 'Model.limit(x).tag_counts.sum(:tags_count)' do
|
821
|
+
it 'should not break on Mysql' do
|
822
|
+
# Activerecord 3.2 return a string
|
823
|
+
expect(TaggableModel.limit(2).tag_counts.sum('tags_count').to_i).to eq(5)
|
824
|
+
end
|
825
|
+
end
|
826
|
+
|
827
|
+
context 'regression prevention, just making sure these esoteric queries still work' do
|
828
|
+
context 'Model.tag_counts.limit(x)' do
|
829
|
+
it 'should limit the tag objects (not very useful, of course)' do
|
830
|
+
array_of_tag_counts = TaggableModel.tag_counts.limit(2)
|
831
|
+
expect(array_of_tag_counts.count).to eq(2)
|
832
|
+
end
|
833
|
+
end
|
834
|
+
|
835
|
+
context 'Model.tag_counts.sum(:tags_count)' do
|
836
|
+
it 'should limit the total tags used' do
|
837
|
+
expect(TaggableModel.tag_counts.sum(:tags_count).to_i).to eq(9)
|
838
|
+
end
|
839
|
+
end
|
840
|
+
|
841
|
+
context 'Model.tag_counts.limit(2).sum(:tags_count)' do
|
842
|
+
it 'limit should have no effect; this is just a sanity check' do
|
843
|
+
expect(TaggableModel.tag_counts.limit(2).sum(:tags_count).to_i).to eq(9)
|
683
844
|
end
|
684
845
|
end
|
685
846
|
end
|
686
847
|
end
|
848
|
+
end
|
849
|
+
|
850
|
+
|
851
|
+
if ActsAsTaggableOn::Utils.using_postgresql?
|
852
|
+
describe 'Taggable model with json columns' do
|
853
|
+
before(:each) do
|
854
|
+
@taggable = TaggableModelWithJson.new(:name => 'Bob Jones')
|
855
|
+
@taggables = [@taggable, TaggableModelWithJson.new(:name => 'John Doe')]
|
856
|
+
end
|
857
|
+
|
858
|
+
it 'should be able to find by tag with context' do
|
859
|
+
@taggable.skill_list = 'ruby, rails, css'
|
860
|
+
@taggable.tag_list = 'bob, charlie'
|
861
|
+
@taggable.save
|
862
|
+
|
863
|
+
expect(TaggableModelWithJson.tagged_with('ruby').first).to eq(@taggable)
|
864
|
+
expect(TaggableModelWithJson.tagged_with('ruby, css').first).to eq(@taggable)
|
865
|
+
expect(TaggableModelWithJson.tagged_with('bob', :on => :skills).first).to_not eq(@taggable)
|
866
|
+
expect(TaggableModelWithJson.tagged_with('bob', :on => :tags).first).to eq(@taggable)
|
867
|
+
end
|
868
|
+
|
869
|
+
it 'should be able to find tagged with any tag' do
|
870
|
+
bob = TaggableModelWithJson.create(:name => 'Bob', :tag_list => 'fitter, happier, more productive', :skill_list => 'ruby, rails, css')
|
871
|
+
frank = TaggableModelWithJson.create(:name => 'Frank', :tag_list => 'weaker, depressed, inefficient', :skill_list => 'ruby, rails, css')
|
872
|
+
steve = TaggableModelWithJson.create(:name => 'Steve', :tag_list => 'fitter, happier, more productive', :skill_list => 'c++, java, ruby')
|
687
873
|
|
688
|
-
|
689
|
-
|
690
|
-
|
874
|
+
expect(TaggableModelWithJson.tagged_with(%w(ruby java), :order => 'taggable_model_with_jsons.name', :any => true).to_a).to eq([bob, frank, steve])
|
875
|
+
expect(TaggableModelWithJson.tagged_with(%w(c++ fitter), :order => 'taggable_model_with_jsons.name', :any => true).to_a).to eq([bob, steve])
|
876
|
+
expect(TaggableModelWithJson.tagged_with(%w(depressed css), :order => 'taggable_model_with_jsons.name', :any => true).to_a).to eq([bob, frank])
|
691
877
|
end
|
692
878
|
end
|
693
879
|
end
|