mongoid-slug 5.3.0 → 6.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +5 -5
  2. data/LICENSE +20 -20
  3. data/README.md +361 -360
  4. data/lib/mongoid/slug.rb +328 -366
  5. data/lib/mongoid/slug/criteria.rb +107 -107
  6. data/lib/mongoid/slug/{index.rb → index_builder.rb} +67 -51
  7. data/lib/mongoid/slug/railtie.rb +9 -9
  8. data/lib/mongoid/slug/slug_id_strategy.rb +3 -3
  9. data/lib/mongoid/slug/unique_slug.rb +173 -154
  10. data/lib/mongoid/slug/version.rb +5 -5
  11. data/lib/mongoid_slug.rb +2 -2
  12. data/lib/tasks/mongoid_slug.rake +15 -19
  13. data/spec/models/alias.rb +6 -6
  14. data/spec/models/article.rb +9 -9
  15. data/spec/models/artist.rb +8 -8
  16. data/spec/models/artwork.rb +10 -10
  17. data/spec/models/author.rb +15 -15
  18. data/spec/models/author_polymorphic.rb +15 -15
  19. data/spec/models/book.rb +12 -12
  20. data/spec/models/book_polymorphic.rb +12 -12
  21. data/spec/models/caption.rb +17 -17
  22. data/spec/models/entity.rb +11 -12
  23. data/spec/models/friend.rb +7 -7
  24. data/spec/models/incorrect_slug_persistence.rb +9 -9
  25. data/spec/models/integer_id.rb +9 -9
  26. data/spec/models/magazine.rb +7 -7
  27. data/spec/models/page.rb +9 -9
  28. data/spec/models/page_localize.rb +9 -9
  29. data/spec/models/page_slug_localized.rb +9 -9
  30. data/spec/models/page_slug_localized_custom.rb +10 -10
  31. data/spec/models/page_slug_localized_history.rb +9 -9
  32. data/spec/models/partner.rb +7 -7
  33. data/spec/models/person.rb +12 -12
  34. data/spec/models/relationship.rb +8 -8
  35. data/spec/models/string_id.rb +9 -9
  36. data/spec/models/subject.rb +7 -7
  37. data/spec/models/without_slug.rb +5 -5
  38. data/spec/mongoid/criteria_spec.rb +207 -208
  39. data/spec/mongoid/index_builder_spec.rb +105 -0
  40. data/spec/mongoid/slug_spec.rb +1175 -1170
  41. data/spec/shared/indexes.rb +41 -41
  42. data/spec/spec_helper.rb +61 -67
  43. data/spec/tasks/mongoid_slug_rake_spec.rb +73 -73
  44. metadata +14 -24
  45. data/lib/mongoid/slug/paranoia.rb +0 -20
  46. data/spec/models/document_paranoid.rb +0 -9
  47. data/spec/models/paranoid_document.rb +0 -8
  48. data/spec/models/paranoid_permanent.rb +0 -10
  49. data/spec/mongoid/index_spec.rb +0 -34
  50. data/spec/mongoid/paranoia_spec.rb +0 -230
@@ -0,0 +1,105 @@
1
+ require 'spec_helper'
2
+
3
+ describe Mongoid::Slug::IndexBuilder do
4
+ let(:doc) { Class.new.include(Mongoid::Document) }
5
+ let(:scope_key) { nil }
6
+ let(:by_model_type) { false }
7
+ let(:default_locale) { :en }
8
+ let(:locales) { nil }
9
+
10
+ subject do
11
+ if Mongoid::Compatibility::Version.mongoid3?
12
+ doc.index_options.to_a
13
+ else
14
+ doc.index_specifications.map { |spec| [spec.key, spec.options] }
15
+ end
16
+ end
17
+
18
+ before do
19
+ allow(I18n).to receive(:default_locale).and_return(default_locale)
20
+ Mongoid::Slug::IndexBuilder.build_indexes(doc, scope_key, by_model_type, locales)
21
+ end
22
+
23
+ context 'when scope_key is set' do
24
+ let(:scope_key) { :foo }
25
+
26
+ context 'when by_model_type is true' do
27
+ let(:by_model_type) { true }
28
+
29
+ it { is_expected.to eq [[{ _slugs: 1, foo: 1, _type: 1 }, {}]] }
30
+ end
31
+
32
+ context 'when by_model_type is false' do
33
+ it { is_expected.to eq [[{ _slugs: 1, foo: 1 }, {}]] }
34
+ end
35
+
36
+ context 'when locale is set' do
37
+ let(:default_locale) { :de }
38
+ let(:locales) { true }
39
+
40
+ it { is_expected.to eq [[{ :'_slugs.de' => 1, foo: 1 }, {}]] }
41
+ end
42
+
43
+ context 'when locale is not set' do
44
+ it { is_expected.to eq [[{ _slugs: 1, foo: 1 }, {}]] }
45
+ end
46
+
47
+ context 'when locales is an Array' do
48
+ let(:locales) { %i[es de fr] }
49
+
50
+ it do
51
+ is_expected.to eq [[{ :'_slugs.es' => 1, foo: 1 }, {}],
52
+ [{ :'_slugs.de' => 1, foo: 1 }, {}],
53
+ [{ :'_slugs.fr' => 1, foo: 1 }, {}]]
54
+ end
55
+ end
56
+ end
57
+
58
+ context 'when scope_key is not set' do
59
+ context 'when by_model_type is true' do
60
+ let(:by_model_type) { true }
61
+
62
+ it { is_expected.to eq [[{ _slugs: 1, _type: 1 }, {}]] }
63
+ end
64
+
65
+ context 'when by_model_type is false' do
66
+ it { is_expected.to eq [[{ _slugs: 1 }, { unique: true, sparse: true }]] }
67
+ end
68
+
69
+ context 'when locales is true' do
70
+ let(:locales) { true }
71
+
72
+ it { is_expected.to eq [[{ :'_slugs.en' => 1 }, { unique: true, sparse: true }]] }
73
+ end
74
+
75
+ context 'when locales is a String' do
76
+ let(:locales) { 'de' }
77
+
78
+ it { is_expected.to eq [[{ :'_slugs.de' => 1 }, { unique: true, sparse: true }]] }
79
+ end
80
+
81
+ context 'when locales is a Symbol' do
82
+ let(:locales) { :de }
83
+
84
+ it { is_expected.to eq [[{ :'_slugs.de' => 1 }, { unique: true, sparse: true }]] }
85
+ end
86
+
87
+ context 'when locales is an Array' do
88
+ let(:locales) { %i[es de fr] }
89
+
90
+ it do
91
+ is_expected.to eq [[{ :'_slugs.es' => 1 }, { unique: true, sparse: true }],
92
+ [{ :'_slugs.de' => 1 }, { unique: true, sparse: true }],
93
+ [{ :'_slugs.fr' => 1 }, { unique: true, sparse: true }]]
94
+ end
95
+ end
96
+
97
+ context 'when locale is set and by_model_type is true' do
98
+ let(:locales) { true }
99
+ let(:default_locale) { :fr }
100
+ let(:by_model_type) { true }
101
+
102
+ it { is_expected.to eq [[{ :'_slugs.fr' => 1, _type: 1 }, {}]] }
103
+ end
104
+ end
105
+ end
@@ -1,1170 +1,1175 @@
1
- # encoding: utf-8
2
- require 'spec_helper'
3
-
4
- module Mongoid
5
- describe Slug do
6
- let(:book) do
7
- Book.create(title: 'A Thousand Plateaus')
8
- end
9
-
10
- context 'special cases' do
11
- it 'slugs are not be generated from invalid documents' do
12
- # this will fail now
13
- x = IncorrectSlugPersistence.create!(name: 'test')
14
- expect(x.slug).to eq('test')
15
-
16
- # I believe this will now fail
17
- x.name = 'te'
18
- x.valid?
19
- expect(x.slug).not_to eq('te')
20
-
21
- # I believe this will persist the 'te'
22
- x.name = 'testb'
23
- x.save!
24
- end
25
-
26
- it 'has no slugs for blank strings' do
27
- book = Book.create!(title: '')
28
- expect(book.reload.slugs).to be nil
29
- end
30
-
31
- it 'has no slugs for dashes' do
32
- book = Book.create!(title: '-')
33
- expect(book.reload.slugs).to be nil
34
- end
35
-
36
- it 'has no slugs for underscores' do
37
- book = Book.create!(title: '_')
38
- expect(book.reload.slugs).to be nil
39
- end
40
-
41
- it 'has no slugs for nil strings' do
42
- book = Book.create!
43
- expect(book.reload.slugs).to be nil
44
- end
45
-
46
- it 'works for multiple nils' do
47
- expect do
48
- 2.times do
49
- Book.create!
50
- end
51
- end.to_not raise_error # Mongo::Error::OperationFailure
52
- expect(Book.all.map(&:slug)).to eq Book.all.map(&:id).map(&:to_s)
53
- end
54
- end
55
-
56
- context 'when option skip_id_check is used with UUID _id ' do
57
- let(:entity0) do
58
- Entity.create(_id: UUID.generate, name: 'Pelham 1 2 3', user_edited_variation: 'pelham-1-2-3')
59
- end
60
- let(:entity1) do
61
- Entity.create(_id: UUID.generate, name: 'Jackson 5', user_edited_variation: 'jackson-5')
62
- end
63
- let(:entity2) do
64
- Entity.create(_id: UUID.generate, name: 'Jackson 5', user_edited_variation: 'jackson-5')
65
- end
66
-
67
- it 'generates a unique slug by appending a counter to duplicate text' do
68
- expect(entity0.to_param).to eql 'pelham-1-2-3'
69
-
70
- 5.times do |x|
71
- dup = Entity.create(_id: UUID.generate, name: entity0.name, user_edited_variation: entity0.user_edited_variation)
72
- expect(dup.to_param).to eql "pelham-1-2-3-#{x.succ}"
73
- end
74
- end
75
-
76
- it 'allows the user to edit the sluggable field' do
77
- expect(entity1.to_param).to eql 'jackson-5'
78
- expect(entity2.to_param).to eql 'jackson-5-1'
79
- entity2.user_edited_variation = 'jackson-5-indiana'
80
- entity2.save
81
- expect(entity2.to_param).to eql 'jackson-5-indiana'
82
- end
83
-
84
- it 'allows users to edit the sluggable field' do
85
- expect(entity1.to_param).to eql 'jackson-5'
86
- expect(entity2.to_param).to eql 'jackson-5-1'
87
- entity2.user_edited_variation = 'jackson-5-indiana'
88
- entity2.save
89
- expect(entity2.to_param).to eql 'jackson-5-indiana'
90
- end
91
-
92
- it 'it restores the slug if the editing user tries to use an existing slug' do
93
- expect(entity1.to_param).to eql 'jackson-5'
94
- expect(entity2.to_param).to eql 'jackson-5-1'
95
- entity2.user_edited_variation = 'jackson-5'
96
- entity2.save
97
- expect(entity2.to_param).to eql 'jackson-5-1'
98
- end
99
-
100
- it 'does not force an appended counter on a plain string' do
101
- entity = Entity.create(_id: UUID.generate, name: 'Adele', user_edited_variation: 'adele')
102
- expect(entity.to_param).to eql 'adele'
103
- end
104
- end
105
-
106
- context 'when the object is top-level' do
107
- it 'generates a slug' do
108
- expect(book.to_param).to eql 'a-thousand-plateaus'
109
- end
110
-
111
- it 'updates the slug' do
112
- book.title = 'Anti Oedipus'
113
- book.save
114
- expect(book.to_param).to eql 'anti-oedipus'
115
- end
116
-
117
- it 'generates a unique slug by appending a counter to duplicate text' do
118
- 15.times do |x|
119
- dup = Book.create(title: book.title)
120
- expect(dup.to_param).to eql "a-thousand-plateaus-#{x + 1}"
121
- end
122
- end
123
-
124
- it 'does not allow a BSON::ObjectId as use for a slug' do
125
- bson_id = Mongoid::Compatibility::Version.mongoid3? ? Moped::BSON::ObjectId.new.to_s : BSON::ObjectId.new.to_s
126
- bad = Book.create(title: bson_id)
127
- expect(bad.slugs).not_to include(bson_id)
128
- end
129
-
130
- it 'does not update slug if slugged fields have not changed' do
131
- book.save
132
- expect(book.to_param).to eql 'a-thousand-plateaus'
133
- end
134
-
135
- it 'does not change slug if slugged fields have changed but generated slug is identical' do
136
- book.title = 'a thousand plateaus'
137
- book.save
138
- expect(book.to_param).to eql 'a-thousand-plateaus'
139
- end
140
-
141
- context 'using find' do
142
- it 'finds by id as string' do
143
- expect(Book.find(book.id.to_s)).to eql book
144
- end
145
-
146
- it 'finds by id as array of strings' do
147
- expect(Book.find([book.id.to_s])).to eql [book]
148
- end
149
-
150
- it 'finds by id as BSON::ObjectId' do
151
- expect(Book.find(book.id)).to eql book
152
- end
153
-
154
- it 'finds by id as an array of BSON::ObjectIds' do
155
- expect(Book.find([book.id])).to eql [book]
156
- end
157
-
158
- it 'returns an empty array if given an empty array' do
159
- expect(Book.find([])).to eql []
160
- end
161
- end
162
- end
163
-
164
- context 'when the object is embedded' do
165
- let(:subject) do
166
- book.subjects.create(name: 'Psychoanalysis')
167
- end
168
-
169
- it 'generates a slug' do
170
- expect(subject.to_param).to eql 'psychoanalysis'
171
- end
172
-
173
- it 'updates the slug' do
174
- subject.name = 'Schizoanalysis'
175
- subject.save
176
- expect(subject.to_param).to eql 'schizoanalysis'
177
- end
178
-
179
- it 'generates a unique slug by appending a counter to duplicate text' do
180
- dup = book.subjects.create(name: subject.name)
181
- expect(dup.to_param).to eql 'psychoanalysis-1'
182
- end
183
-
184
- it 'does not allow a BSON::ObjectId as use for a slug' do
185
- bad = book.subjects.create(name: '4ea0389f0364313d79104fb3')
186
- expect(bad.slugs).not_to eql '4ea0389f0364313d79104fb3'
187
- end
188
-
189
- it 'does not update slug if slugged fields have not changed' do
190
- subject.save
191
- expect(subject.to_param).to eql 'psychoanalysis'
192
- end
193
-
194
- it 'does not change slug if slugged fields have changed but generated slug is identical' do
195
- subject.name = 'PSYCHOANALYSIS'
196
- expect(subject.to_param).to eql 'psychoanalysis'
197
- end
198
-
199
- context 'using find' do
200
- it 'finds by id as string' do
201
- expect(book.subjects.find(subject.id.to_s)).to eql subject
202
- end
203
-
204
- it 'finds by id as array of strings' do
205
- expect(book.subjects.find([subject.id.to_s])).to eql [subject]
206
- end
207
-
208
- it 'finds by id as BSON::ObjectId' do
209
- expect(book.subjects.find(subject.id)).to eql subject
210
- end
211
-
212
- it 'finds by id as an array of BSON::ObjectIds' do
213
- expect(book.subjects.find([subject.id])).to eql [subject]
214
- end
215
-
216
- it 'returns an empty array if given an empty array' do
217
- expect(book.subjects.find([])).to eql []
218
- end
219
- end
220
- end
221
-
222
- context 'when the object is embedded in another embedded object' do
223
- let(:person) do
224
- Person.create(name: 'John Doe')
225
- end
226
-
227
- let(:relationship) do
228
- person.relationships.create(name: 'Engagement')
229
- end
230
-
231
- let(:partner) do
232
- relationship.partners.create(name: 'Jane Smith')
233
- end
234
-
235
- it 'generates a slug' do
236
- expect(partner.to_param).to eql 'jane-smith'
237
- end
238
-
239
- it 'updates the slug' do
240
- partner.name = 'Jane Doe'
241
- partner.save
242
- expect(partner.to_param).to eql 'jane-doe'
243
- end
244
-
245
- it 'generates a unique slug by appending a counter to duplicate text' do
246
- dup = relationship.partners.create(name: partner.name)
247
- expect(dup.to_param).to eql 'jane-smith-1'
248
- end
249
-
250
- it 'does not allow a BSON::ObjectId as use for a slug' do
251
- bad = relationship.partners.create(name: '4ea0389f0364313d79104fb3')
252
- expect(bad.slugs).not_to eql '4ea0389f0364313d79104fb3'
253
- end
254
-
255
- it 'does not update slug if slugged fields have not changed' do
256
- partner.save
257
- expect(partner.to_param).to eql 'jane-smith'
258
- end
259
-
260
- it 'does not change slug if slugged fields have changed but generated slug is identical' do
261
- partner.name = 'JANE SMITH'
262
- expect(partner.to_param).to eql 'jane-smith'
263
- end
264
-
265
- it 'scopes by parent object' do
266
- affair = person.relationships.create(name: 'Affair')
267
- lover = affair.partners.create(name: partner.name)
268
- expect(lover.to_param).to eql partner.to_param
269
- end
270
-
271
- context 'using find' do
272
- it 'finds by id as string' do
273
- expect(relationship.partners.find(partner.id.to_s)).to eql partner
274
- end
275
-
276
- it 'finds by id as array of strings' do
277
- expect(relationship.partners.find([partner.id.to_s])).to eql [partner]
278
- end
279
-
280
- it 'finds by id as BSON::ObjectId' do
281
- expect(relationship.partners.find(partner.id)).to eql partner
282
- end
283
-
284
- it 'finds by id as an array of BSON::ObjectIds' do
285
- expect(relationship.partners.find([partner.id])).to eql [partner]
286
- end
287
-
288
- it 'returns an empty array if given an empty array' do
289
- expect(relationship.partners.find([])).to eql []
290
- end
291
- end
292
- end
293
-
294
- context 'when the slug is composed of multiple fields' do
295
- let!(:author) do
296
- Author.create(
297
- first_name: 'Gilles',
298
- last_name: 'Deleuze'
299
- )
300
- end
301
-
302
- it 'generates a slug' do
303
- expect(author.to_param).to eql 'gilles-deleuze'
304
- end
305
-
306
- it 'updates the slug' do
307
- author.first_name = 'Félix'
308
- author.last_name = 'Guattari'
309
- author.save
310
- expect(author.to_param).to eql 'felix-guattari'
311
- end
312
-
313
- it 'generates a unique slug by appending a counter to duplicate text' do
314
- dup = Author.create(
315
- first_name: author.first_name,
316
- last_name: author.last_name
317
- )
318
- expect(dup.to_param).to eql 'gilles-deleuze-1'
319
-
320
- dup2 = Author.create(
321
- first_name: author.first_name,
322
- last_name: author.last_name
323
- )
324
-
325
- dup.save
326
- expect(dup2.to_param).to eql 'gilles-deleuze-2'
327
- end
328
-
329
- it 'does not allow a BSON::ObjectId as use for a slug' do
330
- bad = Author.create(first_name: '4ea0389f0364',
331
- last_name: '313d79104fb3')
332
- expect(bad.to_param).not_to eql '4ea0389f0364313d79104fb3'
333
- end
334
-
335
- it 'does not update slug if slugged fields have changed but generated slug is identical' do
336
- author.last_name = 'DELEUZE'
337
- author.save
338
- expect(author.to_param).to eql 'gilles-deleuze'
339
- end
340
- end
341
-
342
- context 'when :as is passed as an argument' do
343
- let!(:person) do
344
- Person.create(name: 'John Doe')
345
- end
346
-
347
- it 'sets an alternative slug field name' do
348
- expect(person).to respond_to(:_slugs)
349
- expect(person.slugs).to eql ['john-doe']
350
- end
351
-
352
- it 'defines #slug' do
353
- expect(person).to respond_to :slugs
354
- end
355
-
356
- it 'defines #slug_changed?' do
357
- expect(person).to respond_to :_slugs_changed?
358
- end
359
-
360
- it 'defines #slug_was' do
361
- expect(person).to respond_to :_slugs_was
362
- end
363
- end
364
-
365
- context 'when :permanent is passed as an argument' do
366
- let(:person) do
367
- Person.create(name: 'John Doe')
368
- end
369
-
370
- it 'does not update the slug when the slugged fields change' do
371
- person.name = 'Jane Doe'
372
- person.save
373
- expect(person.to_param).to eql 'john-doe'
374
- end
375
- end
376
-
377
- context 'when :history is passed as an argument' do
378
- context 'true' do
379
- let(:book) do
380
- Book.create(title: 'Book Title')
381
- end
382
-
383
- before(:each) do
384
- book.title = 'Other Book Title'
385
- book.save
386
- end
387
-
388
- it "saves the old slug in the owner's history" do
389
- expect(book.slugs).to include('book-title')
390
- end
391
-
392
- it 'generates a unique slug by appending a counter to duplicate text' do
393
- dup = Book.create(title: 'Book Title')
394
- expect(dup.to_param).to eql 'book-title-1'
395
- end
396
-
397
- it 'does not allow a BSON::ObjectId as use for a slug' do
398
- bad = Book.create(title: '4ea0389f0364313d79104fb3')
399
- expect(bad.to_param).not_to eql '4ea0389f0364313d79104fb3'
400
- end
401
-
402
- it 'ensures no duplicate values are stored in history' do
403
- book.update_attributes title: 'Book Title'
404
- book.update_attributes title: 'Foo'
405
- expect(book.slugs.find_all { |slug| slug == 'book-title' }.size).to eql 1
406
- end
407
- end
408
- context 'false' do
409
- let(:author) do
410
- Author.create(first_name: 'Gilles', last_name: 'Deleuze')
411
- end
412
-
413
- before(:each) do
414
- author.first_name = 'John'
415
- author.save
416
- end
417
-
418
- it "does not save the old slug in the owner's history" do
419
- expect(author.slugs.count).to eq 1
420
- expect(author.slugs).to_not include('gilles-deleuze')
421
- end
422
- end
423
- end
424
-
425
- context 'when slug is scoped by a reference association' do
426
- let(:author) do
427
- book.authors.create(first_name: 'Gilles', last_name: 'Deleuze')
428
- end
429
-
430
- it 'scopes by parent object' do
431
- book2 = Book.create(title: 'Anti Oedipus')
432
- dup = book2.authors.create(
433
- first_name: author.first_name,
434
- last_name: author.last_name
435
- )
436
- expect(dup.to_param).to eql author.to_param
437
- end
438
-
439
- it 'generates a unique slug by appending a counter to duplicate text' do
440
- dup = book.authors.create(
441
- first_name: author.first_name,
442
- last_name: author.last_name
443
- )
444
- expect(dup.to_param).to eql 'gilles-deleuze-1'
445
- end
446
-
447
- it 'does not allow a BSON::ObjectId as use for a slug' do
448
- bad = book.authors.create(first_name: '4ea0389f0364',
449
- last_name: '313d79104fb3')
450
- expect(bad.to_param).not_to eql '4ea0389f0364313d79104fb3'
451
- end
452
-
453
- context 'with an irregular association name' do
454
- let(:character) do
455
- # well we've got to make up something... :-)
456
- author.characters.create(name: 'Oedipus')
457
- end
458
-
459
- let!(:author2) do
460
- Author.create(
461
- first_name: 'Sophocles',
462
- last_name: 'son of Sophilos'
463
- )
464
- end
465
-
466
- it 'scopes by parent object provided that inverse_of is specified' do
467
- dup = author2.characters.create(name: character.name)
468
- expect(dup.to_param).to eql character.to_param
469
- end
470
- end
471
- end
472
-
473
- context "when slug is scoped by one of the class's own fields" do
474
- let!(:magazine) do
475
- Magazine.create(title: 'Big Weekly', publisher_id: 'abc123')
476
- end
477
-
478
- it 'should scope by local field' do
479
- expect(magazine.to_param).to eql 'big-weekly'
480
- magazine2 = Magazine.create(title: 'Big Weekly', publisher_id: 'def456')
481
- expect(magazine2.to_param).to eql magazine.to_param
482
- end
483
-
484
- it 'should generate a unique slug by appending a counter to duplicate text' do
485
- dup = Magazine.create(title: 'Big Weekly', publisher_id: 'abc123')
486
- expect(dup.to_param).to eql 'big-weekly-1'
487
- end
488
-
489
- it 'does not allow a BSON::ObjectId as use for a slug' do
490
- bad = Magazine.create(title: '4ea0389f0364313d79104fb3', publisher_id: 'abc123')
491
- expect(bad.to_param).not_to eql '4ea0389f0364313d79104fb3'
492
- end
493
- end
494
-
495
- context 'when #slug is given a block' do
496
- let(:caption) do
497
- Caption.create(my_identity: 'Edward Hopper (American, 1882-1967)',
498
- title: 'Soir Bleu, 1914',
499
- medium: 'Oil on Canvas')
500
- end
501
-
502
- it 'generates a slug' do
503
- expect(caption.to_param).to eql 'edward-hopper-soir-bleu-1914'
504
- end
505
-
506
- it 'updates the slug' do
507
- caption.title = 'Road in Maine, 1914'
508
- caption.save
509
- expect(caption.to_param).to eql 'edward-hopper-road-in-maine-1914'
510
- end
511
-
512
- it 'does not change slug if slugged fields have changed but generated slug is identical' do
513
- caption.my_identity = 'Edward Hopper'
514
- caption.save
515
- expect(caption.to_param).to eql 'edward-hopper-soir-bleu-1914'
516
- end
517
- end
518
-
519
- context 'when block is configured globally' do
520
- before do
521
- Mongoid::Slug.configure do |c|
522
- c.slug do |cur_obj|
523
- slug = cur_obj.slug_builder
524
- "#{slug}-#{cur_obj.id}".to_url
525
- end
526
- end
527
- end
528
-
529
- after do
530
- # Remove global configuration to avoid affect on
531
- # other specs run after this spec
532
- Mongoid::Slug.default_slug = nil
533
- expect(Mongoid::Slug.default_slug).to be_nil
534
- end
535
-
536
- it 'generates a slug' do
537
- expect(Mongoid::Slug.default_slug).to be_present
538
- class Person
539
- include Mongoid::Document
540
- include Mongoid::Slug
541
-
542
- field :name
543
- slug :name
544
- end
545
-
546
- person = Person.create(name: 'John')
547
- expect(person.to_param).to eql "john-#{person.id}"
548
- end
549
-
550
- it 'can be overridden at model level' do
551
- expect(Mongoid::Slug.default_slug).to be_present
552
- class Person
553
- include Mongoid::Document
554
- include Mongoid::Slug
555
-
556
- field :name
557
- slug :name do |cur_object|
558
- cur_object.slug_builder.to_url
559
- end
560
- end
561
-
562
- person = Person.create(name: 'John')
563
- expect(person.to_param).to eql 'john'
564
- end
565
- end
566
-
567
- context 'when slugged field contains non-ASCII characters' do
568
- it 'slugs Cyrillic characters' do
569
- book.title = 'Капитал'
570
- book.save
571
- expect(book.to_param).to eql 'kapital'
572
- end
573
-
574
- it 'slugs Greek characters' do
575
- book.title = 'Ελλάδα'
576
- book.save
577
- expect(book.to_param).to eql 'ellada'
578
- end
579
-
580
- it 'slugs Chinese characters' do
581
- book.title = '中文'
582
- book.save
583
- expect(book.to_param).to eql 'zhong-wen'
584
- end
585
-
586
- it 'slugs non-ASCII Latin characters' do
587
- book.title = 'Paul Cézanne'
588
- book.save
589
- expect(book.to_param).to eql 'paul-cezanne'
590
- end
591
- end
592
-
593
- context 'when slug is not scoped by a reference association' do
594
- subject { Book }
595
- it_should_behave_like 'has an index', { _slugs: 1 }, unique: true, sparse: true
596
- end
597
-
598
- context 'with a value exceeding mongodb max index key' do
599
- if Mongoid::Compatibility::Version.mongoid5? || Mongoid::Compatibility::Version.mongoid6?
600
- it 'errors with a model without a max length' do
601
- expect do
602
- Book.create!(title: 't' * 1025)
603
- end.to raise_error Mongo::Error::OperationFailure, /key too large to index/
604
- end
605
- elsif Mongoid::Compatibility::Version.mongoid4?
606
- it 'errors with a model without a max length' do
607
- expect do
608
- Book.create!(title: 't' * 1025)
609
- end.to raise_error Moped::Errors::OperationFailure, /key too large to index/
610
- end
611
- end
612
- it 'succeeds with a model with a max length' do
613
- expect do
614
- author = Author.create!(last_name: 't' * 1025)
615
- expect(author.slug.length).to eq 256
616
- end.to_not raise_error
617
- end
618
- end
619
-
620
- context 'when slug is scoped by a reference association' do
621
- subject { Author }
622
- it_should_behave_like 'does not have an index', _slugs: 1
623
- end
624
-
625
- context 'for subclass scope' do
626
- context 'when slug is not scoped by a reference association' do
627
- subject { BookPolymorphic }
628
- it_should_behave_like 'has an index', { _type: 1, _slugs: 1 }, unique: nil, sparse: nil
629
- end
630
-
631
- context 'when slug is scoped by a reference association' do
632
- subject { AuthorPolymorphic }
633
- it_should_behave_like 'does not have an index', _type: 1, _slugs: 1
634
- end
635
-
636
- context 'when the object has STI' do
637
- it 'scopes by the subclass' do
638
- b = BookPolymorphic.create!(title: 'Book')
639
- expect(b.slug).to eq('book')
640
-
641
- b2 = BookPolymorphic.create!(title: 'Book')
642
- expect(b2.slug).to eq('book-1')
643
-
644
- c = ComicBookPolymorphic.create!(title: 'Book')
645
- expect(c.slug).to eq('book')
646
-
647
- c2 = ComicBookPolymorphic.create!(title: 'Book')
648
- expect(c2.slug).to eq('book-1')
649
-
650
- expect(BookPolymorphic.find('book')).to eq(b)
651
- expect(BookPolymorphic.find('book-1')).to eq(b2)
652
- expect(ComicBookPolymorphic.find('book')).to eq(c)
653
- expect(ComicBookPolymorphic.find('book-1')).to eq(c2)
654
- end
655
- end
656
- end
657
- end
658
-
659
- context 'for reserved words' do
660
- context 'when the :reserve option is used on the model' do
661
- it 'does not use the reserved slugs' do
662
- friend1 = Friend.create(name: 'foo')
663
- expect(friend1.slugs).not_to include('foo')
664
- expect(friend1.slugs).to include('foo-1')
665
-
666
- friend2 = Friend.create(name: 'bar')
667
- expect(friend2.slugs).not_to include('bar')
668
- expect(friend2.slugs).to include('bar-1')
669
-
670
- friend3 = Friend.create(name: 'en')
671
- expect(friend3.slugs).not_to include('en')
672
- expect(friend3.slugs).to include('en-1')
673
- end
674
-
675
- it 'should start with concatenation -1' do
676
- friend1 = Friend.create(name: 'foo')
677
- expect(friend1.slugs).to include('foo-1')
678
- friend2 = Friend.create(name: 'foo')
679
- expect(friend2.slugs).to include('foo-2')
680
- end
681
-
682
- %w(new edit).each do |word|
683
- it "should overwrite the default reserved words allowing the word '#{word}'" do
684
- friend = Friend.create(name: word)
685
- expect(friend.slugs).to include word
686
- end
687
- end
688
- end
689
- context 'when the model does not have any reserved words set' do
690
- %w(new edit).each do |word|
691
- it "does not use the default reserved word '#{word}'" do
692
- book = Book.create(title: word)
693
- expect(book.slugs).not_to include word
694
- expect(book.slugs).to include("#{word}-1")
695
- end
696
- end
697
- end
698
-
699
- context 'when the object has STI' do
700
- it 'scopes by the superclass' do
701
- book = Book.create(title: 'Anti Oedipus')
702
- comic_book = ComicBook.create(title: 'Anti Oedipus')
703
- expect(comic_book.slugs).not_to eql(book.slugs)
704
- end
705
-
706
- it 'scopes by the subclass' do
707
- book = BookPolymorphic.create(title: 'Anti Oedipus')
708
- comic_book = ComicBookPolymorphic.create(title: 'Anti Oedipus')
709
- expect(comic_book.slugs).to eql(book.slugs)
710
-
711
- expect(BookPolymorphic.find(book.slug)).to eq(book)
712
- expect(ComicBookPolymorphic.find(comic_book.slug)).to eq(comic_book)
713
- end
714
- end
715
-
716
- context 'when slug defined on alias of field' do
717
- it 'should use accessor, not alias' do
718
- pseudonim = Alias.create(author_name: 'Max Stirner')
719
- expect(pseudonim.slugs).to include('max-stirner')
720
- end
721
- end
722
-
723
- describe '#to_param' do
724
- context 'when called on a new record' do
725
- let(:book) { Book.new }
726
-
727
- it 'should return nil' do
728
- expect(book.to_param).to eq book._id.to_s
729
- end
730
-
731
- it 'should not persist the record' do
732
- book.to_param
733
- expect(book).not_to be_persisted
734
- end
735
- end
736
-
737
- context 'when called on an existing record with no slug' do
738
- let!(:book_no_slug) do
739
- if Mongoid::Compatibility::Version.mongoid5? || Mongoid::Compatibility::Version.mongoid6?
740
- Book.collection.insert_one(title: 'Proust and Signs')
741
- else
742
- Book.collection.insert(title: 'Proust and Signs')
743
- end
744
- Book.where(title: 'Proust and Signs').first
745
- end
746
-
747
- it 'should return the id if there is no slug' do
748
- expect(book_no_slug.to_param).to eq(book_no_slug.id.to_s)
749
- expect(book_no_slug.slugs).to be_nil
750
- end
751
-
752
- it 'should not persist the record' do
753
- expect(book_no_slug.to_param).to eq(book_no_slug._id.to_s)
754
- end
755
- end
756
- end
757
-
758
- describe '#_slugs_changed?' do
759
- before do
760
- Book.create(title: 'A Thousand Plateaus')
761
- end
762
-
763
- let(:book) { Book.first }
764
-
765
- it 'is initially unchanged' do
766
- expect(book._slugs_changed?).to be_falsey
767
- end
768
-
769
- it 'tracks changes' do
770
- book.slugs = ['Anti Oedipus']
771
- expect(book._slugs_changed?).to be_truthy
772
- end
773
- end
774
-
775
- describe 'when regular expression matches, but document does not' do
776
- let!(:book_1) { Book.create(title: 'book-1') }
777
- let!(:book_2) { Book.create(title: 'book') }
778
- let!(:book_3) { Book.create(title: 'book') }
779
-
780
- it 'book_2 should have the user supplied title without -1 after it' do
781
- expect(book_2.to_param).to eql 'book'
782
- end
783
-
784
- it 'book_3 should have a generated slug' do
785
- expect(book_3.to_param).to eql 'book-2'
786
- end
787
- end
788
-
789
- context 'when the slugged field is set manually' do
790
- context 'when it set to a non-empty string' do
791
- it 'respects the provided slug' do
792
- book = Book.create(title: 'A Thousand Plateaus', slugs: ['not-what-you-expected'])
793
- expect(book.to_param).to eql 'not-what-you-expected'
794
- end
795
-
796
- it 'ensures uniqueness' do
797
- book1 = Book.create(title: 'A Thousand Plateaus', slugs: ['not-what-you-expected'])
798
- expect(book1.to_param).to eql 'not-what-you-expected'
799
- if Mongoid::Compatibility::Version.mongoid5? || Mongoid::Compatibility::Version.mongoid6?
800
- expect do
801
- Book.create(title: 'A Thousand Plateaus', slugs: ['not-what-you-expected'])
802
- end.to raise_error Mongo::Error::OperationFailure, /duplicate/
803
- elsif Mongoid::Compatibility::Version.mongoid4?
804
- expect do
805
- Book.create(title: 'A Thousand Plateaus', slugs: ['not-what-you-expected'])
806
- end.to raise_error Moped::Errors::OperationFailure, /duplicate/
807
- end
808
- end
809
-
810
- it 'updates the slug when a new one is passed in' do
811
- book = Book.create(title: 'A Thousand Plateaus', slugs: ['not-what-you-expected'])
812
- book.slugs = ['not-it-either']
813
- book.save
814
- expect(book.to_param).to eql 'not-it-either'
815
- end
816
-
817
- it 'updates the slug when a new one is appended' do
818
- book = Book.create(title: 'A Thousand Plateaus', slugs: ['not-what-you-expected'])
819
- book.slugs.push 'not-it-either'
820
- book.save
821
- expect(book.to_param).to eql 'not-it-either'
822
- end
823
-
824
- it 'updates the slug to a unique slug when a new one is appended' do
825
- Book.create(title: 'Sleepyhead')
826
- book2 = Book.create(title: 'A Thousand Plateaus')
827
- book2.slugs.push 'sleepyhead'
828
- if Mongoid::Compatibility::Version.mongoid5? || Mongoid::Compatibility::Version.mongoid6?
829
- expect do
830
- book2.save
831
- end.to raise_error Mongo::Error::OperationFailure, /duplicate/
832
- elsif Mongoid::Compatibility::Version.mongoid4?
833
- expect do
834
- book2.save
835
- end.to raise_error Moped::Errors::OperationFailure, /duplicate/
836
- end
837
- end
838
- end
839
-
840
- context 'when it is set to an empty string' do
841
- it 'generate a new one' do
842
- book = Book.create(title: 'A Thousand Plateaus')
843
- expect(book.to_param).to eql 'a-thousand-plateaus'
844
- end
845
- end
846
- end
847
-
848
- context 'slug can be localized' do
849
- before(:each) do
850
- @old_locale = I18n.locale
851
- end
852
-
853
- after(:each) do
854
- I18n.locale = @old_locale
855
- end
856
-
857
- it 'generates a new slug for each localization' do
858
- page = PageSlugLocalized.new
859
- page.title = 'Title on English'
860
- page.save
861
- expect(page.slug).to eql 'title-on-english'
862
- I18n.locale = :nl
863
- page.title = 'Title on Netherlands'
864
- page.save
865
- expect(page.slug).to eql 'title-on-netherlands'
866
- end
867
-
868
- it 'returns _id if no slug' do
869
- page = PageSlugLocalized.new
870
- page.title = 'Title on English'
871
- page.save
872
- expect(page.slug).to eql 'title-on-english'
873
- I18n.locale = :nl
874
- expect(page.slug).to eql page._id.to_s
875
- end
876
-
877
- it 'fallbacks if slug not localized yet' do
878
- page = PageSlugLocalized.new
879
- page.title = 'Title on English'
880
- page.save
881
- expect(page.slug).to eql 'title-on-english'
882
- I18n.locale = :nl
883
- expect(page.slug).to eql page._id.to_s
884
-
885
- # Turn on i18n fallback
886
- require 'i18n/backend/fallbacks'
887
- I18n::Backend::Simple.send(:include, I18n::Backend::Fallbacks)
888
- ::I18n.fallbacks[:nl] = [:nl, :en]
889
- expect(page.slug).to eql 'title-on-english'
890
- fallback_slug = page.slug
891
-
892
- fallback_page = begin
893
- PageSlugLocalized.find(fallback_slug)
894
- rescue
895
- nil
896
- end
897
- expect(fallback_page).to eq(page)
898
-
899
- # Restore fallback for next tests
900
- ::I18n.fallbacks[:nl] = [:nl]
901
- end
902
-
903
- it 'returns a default slug if not localized' do
904
- page = PageLocalize.new
905
- page.title = 'Title on English'
906
- page.save
907
- expect(page.slug).to eql 'title-on-english'
908
- I18n.locale = :nl
909
- page.title = 'Title on Netherlands'
910
- expect(page.slug).to eql 'title-on-english'
911
- page.save
912
- expect(page.slug).to eql 'title-on-netherlands'
913
- end
914
-
915
- it 'slugs properly when translations are set directly' do
916
- page = PageSlugLocalized.new
917
- page.title_translations = { 'en' => 'Title on English', 'nl' => 'Title on Netherlands' }
918
- page.save
919
- expect(page['_slugs']).to eq('en' => ['title-on-english'], 'nl' => ['title-on-netherlands'])
920
- end
921
-
922
- it 'exact same title multiple langauges' do
923
- page = PageSlugLocalized.new
924
- page.title_translations = { 'en' => 'Title on English', 'nl' => 'Title on English' }
925
- page.save
926
- expect(page['_slugs']).to eq('en' => ['title-on-english'], 'nl' => ['title-on-english'])
927
-
928
- page = PageSlugLocalized.create(title_translations: { 'en' => 'Title on English2', 'nl' => 'Title on English2' })
929
- expect(page['_slugs']).to eq('en' => ['title-on-english2'], 'nl' => ['title-on-english2'])
930
- end
931
-
932
- it 'does not produce duplicate slugs' do
933
- old_locale = I18n.locale
934
-
935
- # Using a default locale of en.
936
- page = PageSlugLocalized.new
937
- page.title = 'Title on English'
938
- page.save
939
- I18n.locale = 'nl'
940
- page.title = 'Title on Netherlands'
941
- page.save
942
- expect(page.title_translations).to eq('en' => 'Title on English', 'nl' => 'Title on Netherlands')
943
-
944
- I18n.locale = old_locale
945
- page.title = 'Title on English'
946
- expect(page.title_translations).to eq('en' => 'Title on English', 'nl' => 'Title on Netherlands')
947
- expect(page['_slugs']).to eq('en' => ['title-on-english'], 'nl' => ['title-on-netherlands'])
948
- end
949
-
950
- it 'does not produce duplicate slugs when one has changed' do
951
- old_locale = I18n.locale
952
-
953
- # Using a default locale of en.
954
- page = PageSlugLocalized.new
955
- page.title = 'Title on English'
956
- page.save
957
- I18n.locale = 'nl'
958
- page.title = 'Title on Netherlands'
959
- page.save
960
- expect(page.title_translations).to eq('en' => 'Title on English', 'nl' => 'Title on Netherlands')
961
-
962
- I18n.locale = old_locale
963
- page.title = 'Modified Title on English'
964
- page.save
965
- expect(page.title_translations).to eq('en' => 'Modified Title on English',
966
- 'nl' => 'Title on Netherlands')
967
- expect(page['_slugs']).to eq('en' => ['modified-title-on-english'],
968
- 'nl' => ['title-on-netherlands'])
969
- end
970
-
971
- it 'does not produce duplicate slugs when transactions are set directly' do
972
- page = PageSlugLocalized.new
973
- page.title_translations = { 'en' => 'Title on English', 'nl' => 'Title on Netherlands' }
974
- page.save
975
- page.title_translations = { 'en' => 'Title on English', 'nl' => 'Title on Netherlands' }
976
- page.save
977
- expect(page['_slugs']).to eq('en' => ['title-on-english'], 'nl' => ['title-on-netherlands'])
978
- end
979
-
980
- it 'does not produce duplicate slugs when transactions are set directly and one has changed' do
981
- page = PageSlugLocalized.new
982
- page.title_translations = { 'en' => 'Title on English', 'nl' => 'Title on Netherlands' }
983
- page.save
984
- page.title_translations = { 'en' => 'Modified Title on English',
985
- 'nl' => 'Title on Netherlands' }
986
- page.save
987
- expect(page['_slugs']).to eq('en' => ['modified-title-on-english'],
988
- 'nl' => ['title-on-netherlands'])
989
- end
990
-
991
- it 'works with a custom slug strategy' do
992
- page = PageSlugLocalizedCustom.new
993
- page.title = 'a title for the slug'
994
- page.save
995
- expect(page['_slugs']).to eq('en' => ['a-title-for-the-slug'], 'nl' => ['a-title-for-the-slug'])
996
- end
997
- end
998
-
999
- context 'slug can be localized when using history' do
1000
- before(:each) do
1001
- @old_locale = I18n.locale
1002
- end
1003
-
1004
- after(:each) do
1005
- I18n.locale = @old_locale
1006
- end
1007
-
1008
- it 'generate a new slug for each localization and keep history' do
1009
- old_locale = I18n.locale
1010
-
1011
- page = PageSlugLocalizedHistory.new
1012
- page.title = 'Title on English'
1013
- page.save
1014
- expect(page.slug).to eql 'title-on-english'
1015
- I18n.locale = :nl
1016
- page.title = 'Title on Netherlands'
1017
- page.save
1018
- expect(page.slug).to eql 'title-on-netherlands'
1019
- I18n.locale = old_locale
1020
- page.title = 'Modified title on English'
1021
- page.save
1022
- expect(page.slug).to eql 'modified-title-on-english'
1023
- expect(page.slug).to include('title-on-english')
1024
- I18n.locale = :nl
1025
- page.title = 'Modified title on Netherlands'
1026
- page.save
1027
- expect(page.slug).to eql 'modified-title-on-netherlands'
1028
- expect(page.slug).to include('title-on-netherlands')
1029
- end
1030
-
1031
- it 'returns _id if no slug' do
1032
- page = PageSlugLocalizedHistory.new
1033
- page.title = 'Title on English'
1034
- page.save
1035
- expect(page.slug).to eql 'title-on-english'
1036
- I18n.locale = :nl
1037
- expect(page.slug).to eql page._id.to_s
1038
- end
1039
-
1040
- it 'fallbacks if slug not localized yet' do
1041
- page = PageSlugLocalizedHistory.new
1042
- page.title = 'Title on English'
1043
- page.save
1044
- expect(page.slug).to eql 'title-on-english'
1045
- I18n.locale = :nl
1046
- expect(page.slug).to eql page._id.to_s
1047
-
1048
- # Turn on i18n fallback
1049
- require 'i18n/backend/fallbacks'
1050
- I18n::Backend::Simple.send(:include, I18n::Backend::Fallbacks)
1051
- ::I18n.fallbacks[:nl] = [:nl, :en]
1052
- expect(page.slug).to eql 'title-on-english'
1053
- fallback_slug = page.slug
1054
-
1055
- fallback_page = begin
1056
- PageSlugLocalizedHistory.find(fallback_slug)
1057
- rescue
1058
- nil
1059
- end
1060
- expect(fallback_page).to eq(page)
1061
- end
1062
-
1063
- it 'slugs properly when translations are set directly' do
1064
- page = PageSlugLocalizedHistory.new
1065
- page.title_translations = { 'en' => 'Title on English', 'nl' => 'Title on Netherlands' }
1066
- page.save
1067
- page.title_translations = { 'en' => 'Modified Title on English',
1068
- 'nl' => 'Modified Title on Netherlands' }
1069
- page.save
1070
- expect(page['_slugs']).to eq('en' => ['title-on-english', 'modified-title-on-english'],
1071
- 'nl' => ['title-on-netherlands', 'modified-title-on-netherlands'])
1072
- end
1073
-
1074
- it 'does not produce duplicate slugs' do
1075
- old_locale = I18n.locale
1076
-
1077
- # Using a default locale of en.
1078
- page = PageSlugLocalizedHistory.new
1079
- page.title = 'Title on English'
1080
- page.save
1081
- I18n.locale = 'nl'
1082
- page.title = 'Title on Netherlands'
1083
- page.save
1084
- expect(page.title_translations).to eq('en' => 'Title on English', 'nl' => 'Title on Netherlands')
1085
-
1086
- I18n.locale = old_locale
1087
- page.title = 'Title on English'
1088
- expect(page.title_translations).to eq('en' => 'Title on English', 'nl' => 'Title on Netherlands')
1089
- expect(page['_slugs']).to eq('en' => ['title-on-english'], 'nl' => ['title-on-netherlands'])
1090
- end
1091
-
1092
- it 'does not produce duplicate slugs when one has changed' do
1093
- old_locale = I18n.locale
1094
-
1095
- # Using a default locale of en.
1096
- page = PageSlugLocalizedHistory.new
1097
- page.title = 'Title on English'
1098
- page.save
1099
- I18n.locale = 'nl'
1100
- page.title = 'Title on Netherlands'
1101
- page.save
1102
- expect(page.title_translations).to eq('en' => 'Title on English', 'nl' => 'Title on Netherlands')
1103
-
1104
- I18n.locale = old_locale
1105
- page.title = 'Modified Title on English'
1106
- page.save
1107
- expect(page.title_translations).to eq('en' => 'Modified Title on English',
1108
- 'nl' => 'Title on Netherlands')
1109
- expect(page['_slugs']).to eq('en' => ['title-on-english', 'modified-title-on-english'],
1110
- 'nl' => ['title-on-netherlands'])
1111
- end
1112
-
1113
- it 'does not produce duplicate slugs when transactions are set directly' do
1114
- page = PageSlugLocalizedHistory.new
1115
- page.title_translations = { 'en' => 'Title on English', 'nl' => 'Title on Netherlands' }
1116
- page.save
1117
- page.title_translations = { 'en' => 'Title on English', 'nl' => 'Title on Netherlands' }
1118
- page.save
1119
- expect(page['_slugs']).to eq('en' => ['title-on-english'], 'nl' => ['title-on-netherlands'])
1120
- end
1121
-
1122
- it 'does not produce duplicate slugs when transactions are set directly and one has changed' do
1123
- page = PageSlugLocalizedHistory.new
1124
- page.title_translations = { 'en' => 'Title on English', 'nl' => 'Title on Netherlands' }
1125
- page.save
1126
- page.title_translations = { 'en' => 'Modified Title on English', 'nl' => 'Title on Netherlands' }
1127
- page.save
1128
- expect(page['_slugs']).to eq('en' => ['title-on-english', 'modified-title-on-english'],
1129
- 'nl' => ['title-on-netherlands'])
1130
- end
1131
- end
1132
-
1133
- describe 'slug_max_length' do
1134
- before do
1135
- Author.create_indexes
1136
- end
1137
- after do
1138
- Author.remove_indexes
1139
- end
1140
- it 'can be assigned to nil' do
1141
- expect(Book.slug_max_length).to be nil
1142
- end
1143
- it 'defaults to MONGO_INDEX_KEY_LIMIT_BYTES - 32' do
1144
- expect(Article.slug_max_length).to eq Mongoid::Slug::MONGO_INDEX_KEY_LIMIT_BYTES - 32
1145
- end
1146
- it 'is assigned via max_length' do
1147
- expect(Author.slug_max_length).to eq 256
1148
- end
1149
- it 'enforces max length of slug' do
1150
- author1 = Author.create!(last_name: 't' * 1024)
1151
- expect(author1.slug.length).to eq 256
1152
- expect(author1.slug.ends_with?('ttt')).to be true
1153
- author2 = Author.create!(last_name: 't' * 1024)
1154
- expect(author2.slug.length).to eq 258
1155
- expect(author2.slug.ends_with?('tt-1')).to be true
1156
- author3 = Author.create!(last_name: 't' * 1024)
1157
- expect(author3.slug.length).to eq 258
1158
- expect(author3.slug.ends_with?('tt-2')).to be true
1159
- end
1160
- end
1161
-
1162
- context 'has_many / belongs_to' do
1163
- let(:book) { Book.create!(title: 'War and Peace') }
1164
- it 'allows for duplicates with different slugs' do
1165
- Author.create!(first_name: 'Leo', last_name: 'Tostoy')
1166
- expect { book.authors.create!(first_name: 'Leo', last_name: 'Tostoy') }.to_not raise_error
1167
- end
1168
- end
1169
- end
1170
- end
1
+ require 'spec_helper'
2
+
3
+ module Mongoid
4
+ describe Slug do
5
+ let(:book) do
6
+ Book.create(title: 'A Thousand Plateaus')
7
+ end
8
+
9
+ context 'special cases' do
10
+ it 'slugs are not be generated from invalid documents' do
11
+ # this will fail now
12
+ x = IncorrectSlugPersistence.create!(name: 'test')
13
+ expect(x.slug).to eq('test')
14
+
15
+ # I believe this will now fail
16
+ x.name = 'te'
17
+ x.valid?
18
+ expect(x.slug).not_to eq('te')
19
+
20
+ # I believe this will persist the 'te'
21
+ x.name = 'testb'
22
+ x.save!
23
+ end
24
+
25
+ it 'has no slugs for blank strings' do
26
+ book = Book.create!(title: '')
27
+ expect(book.reload.slugs).to be nil
28
+ end
29
+
30
+ it 'has no slugs for dashes' do
31
+ book = Book.create!(title: '-')
32
+ expect(book.reload.slugs).to be nil
33
+ end
34
+
35
+ it 'has no slugs for underscores' do
36
+ book = Book.create!(title: '_')
37
+ expect(book.reload.slugs).to be nil
38
+ end
39
+
40
+ it 'has no slugs for nil strings' do
41
+ book = Book.create!
42
+ expect(book.reload.slugs).to be nil
43
+ end
44
+
45
+ it 'works for multiple nils' do
46
+ expect do
47
+ 2.times do
48
+ Book.create!
49
+ end
50
+ end.to_not raise_error # Mongo::Error::OperationFailure
51
+ expect(Book.all.map(&:slug)).to eq Book.all.map(&:id).map(&:to_s)
52
+ end
53
+ end
54
+
55
+ context 'when option skip_id_check is used with UUID _id ' do
56
+ let(:entity0) do
57
+ Entity.create(_id: UUID.generate, name: 'Pelham 1 2 3', user_edited_variation: 'pelham-1-2-3')
58
+ end
59
+ let(:entity1) do
60
+ Entity.create(_id: UUID.generate, name: 'Jackson 5', user_edited_variation: 'jackson-5')
61
+ end
62
+ let(:entity2) do
63
+ Entity.create(_id: UUID.generate, name: 'Jackson 5', user_edited_variation: 'jackson-5')
64
+ end
65
+
66
+ it 'generates a unique slug by appending a counter to duplicate text' do
67
+ expect(entity0.to_param).to eql 'pelham-1-2-3'
68
+
69
+ 5.times do |x|
70
+ dup = Entity.create(_id: UUID.generate, name: entity0.name, user_edited_variation: entity0.user_edited_variation)
71
+ expect(dup.to_param).to eql "pelham-1-2-3-#{x.succ}"
72
+ end
73
+ end
74
+
75
+ it 'allows the user to edit the sluggable field' do
76
+ expect(entity1.to_param).to eql 'jackson-5'
77
+ expect(entity2.to_param).to eql 'jackson-5-1'
78
+ entity2.user_edited_variation = 'jackson-5-indiana'
79
+ entity2.save
80
+ expect(entity2.to_param).to eql 'jackson-5-indiana'
81
+ end
82
+
83
+ it 'allows users to edit the sluggable field' do
84
+ expect(entity1.to_param).to eql 'jackson-5'
85
+ expect(entity2.to_param).to eql 'jackson-5-1'
86
+ entity2.user_edited_variation = 'jackson-5-indiana'
87
+ entity2.save
88
+ expect(entity2.to_param).to eql 'jackson-5-indiana'
89
+ end
90
+
91
+ it 'it restores the slug if the editing user tries to use an existing slug' do
92
+ expect(entity1.to_param).to eql 'jackson-5'
93
+ expect(entity2.to_param).to eql 'jackson-5-1'
94
+ entity2.user_edited_variation = 'jackson-5'
95
+ entity2.save
96
+ expect(entity2.to_param).to eql 'jackson-5-1'
97
+ end
98
+
99
+ it 'does not force an appended counter on a plain string' do
100
+ entity = Entity.create(_id: UUID.generate, name: 'Adele', user_edited_variation: 'adele')
101
+ expect(entity.to_param).to eql 'adele'
102
+ end
103
+ end
104
+
105
+ context 'when the object is top-level' do
106
+ it 'generates a slug' do
107
+ expect(book.to_param).to eql 'a-thousand-plateaus'
108
+ end
109
+
110
+ it 'updates the slug' do
111
+ book.title = 'Anti Oedipus'
112
+ book.save
113
+ expect(book.to_param).to eql 'anti-oedipus'
114
+ end
115
+
116
+ it 'generates a unique slug by appending a counter to duplicate text' do
117
+ 15.times do |x|
118
+ dup = Book.create(title: book.title)
119
+ expect(dup.to_param).to eql "a-thousand-plateaus-#{x + 1}"
120
+ end
121
+ end
122
+
123
+ it 'does not allow a BSON::ObjectId as use for a slug' do
124
+ bson_id = Mongoid::Compatibility::Version.mongoid3? ? Moped::BSON::ObjectId.new.to_s : BSON::ObjectId.new.to_s
125
+ bad = Book.create(title: bson_id)
126
+ expect(bad.slugs).not_to include(bson_id)
127
+ end
128
+
129
+ it 'does not update slug if slugged fields have not changed' do
130
+ book.save
131
+ expect(book.to_param).to eql 'a-thousand-plateaus'
132
+ end
133
+
134
+ it 'does not change slug if slugged fields have changed but generated slug is identical' do
135
+ book.title = 'a thousand plateaus'
136
+ book.save
137
+ expect(book.to_param).to eql 'a-thousand-plateaus'
138
+ end
139
+
140
+ context 'using find' do
141
+ it 'finds by id as string' do
142
+ expect(Book.find(book.id.to_s)).to eql book
143
+ end
144
+
145
+ it 'finds by id as array of strings' do
146
+ expect(Book.find([book.id.to_s])).to eql [book]
147
+ end
148
+
149
+ it 'finds by id as BSON::ObjectId' do
150
+ expect(Book.find(book.id)).to eql book
151
+ end
152
+
153
+ it 'finds by id as an array of BSON::ObjectIds' do
154
+ expect(Book.find([book.id])).to eql [book]
155
+ end
156
+
157
+ it 'returns an empty array if given an empty array' do
158
+ expect(Book.find([])).to eql []
159
+ end
160
+ end
161
+ end
162
+
163
+ context 'when the object is embedded' do
164
+ let(:subject) do
165
+ book.subjects.create(name: 'Psychoanalysis')
166
+ end
167
+
168
+ it 'generates a slug' do
169
+ expect(subject.to_param).to eql 'psychoanalysis'
170
+ end
171
+
172
+ it 'updates the slug' do
173
+ subject.name = 'Schizoanalysis'
174
+ subject.save
175
+ expect(subject.to_param).to eql 'schizoanalysis'
176
+ end
177
+
178
+ it 'generates a unique slug by appending a counter to duplicate text' do
179
+ dup = book.subjects.create(name: subject.name)
180
+ expect(dup.to_param).to eql 'psychoanalysis-1'
181
+ end
182
+
183
+ it 'does not allow a BSON::ObjectId as use for a slug' do
184
+ bad = book.subjects.create(name: '4ea0389f0364313d79104fb3')
185
+ expect(bad.slugs).not_to eql '4ea0389f0364313d79104fb3'
186
+ end
187
+
188
+ it 'does not update slug if slugged fields have not changed' do
189
+ subject.save
190
+ expect(subject.to_param).to eql 'psychoanalysis'
191
+ end
192
+
193
+ it 'does not change slug if slugged fields have changed but generated slug is identical' do
194
+ subject.name = 'PSYCHOANALYSIS'
195
+ expect(subject.to_param).to eql 'psychoanalysis'
196
+ end
197
+
198
+ context 'using find' do
199
+ it 'finds by id as string' do
200
+ expect(book.subjects.find(subject.id.to_s)).to eql subject
201
+ end
202
+
203
+ it 'finds by id as array of strings' do
204
+ expect(book.subjects.find([subject.id.to_s])).to eql [subject]
205
+ end
206
+
207
+ it 'finds by id as BSON::ObjectId' do
208
+ expect(book.subjects.find(subject.id)).to eql subject
209
+ end
210
+
211
+ it 'finds by id as an array of BSON::ObjectIds' do
212
+ expect(book.subjects.find([subject.id])).to eql [subject]
213
+ end
214
+
215
+ it 'returns an empty array if given an empty array' do
216
+ expect(book.subjects.find([])).to eql []
217
+ end
218
+ end
219
+ end
220
+
221
+ context 'when the object is embedded in another embedded object' do
222
+ let(:person) do
223
+ Person.create(name: 'John Doe')
224
+ end
225
+
226
+ let(:relationship) do
227
+ person.relationships.create(name: 'Engagement')
228
+ end
229
+
230
+ let(:partner) do
231
+ relationship.partners.create(name: 'Jane Smith')
232
+ end
233
+
234
+ it 'generates a slug' do
235
+ expect(partner.to_param).to eql 'jane-smith'
236
+ end
237
+
238
+ it 'updates the slug' do
239
+ partner.name = 'Jane Doe'
240
+ partner.save
241
+ expect(partner.to_param).to eql 'jane-doe'
242
+ end
243
+
244
+ it 'generates a unique slug by appending a counter to duplicate text' do
245
+ dup = relationship.partners.create(name: partner.name)
246
+ expect(dup.to_param).to eql 'jane-smith-1'
247
+ end
248
+
249
+ it 'does not allow a BSON::ObjectId as use for a slug' do
250
+ bad = relationship.partners.create(name: '4ea0389f0364313d79104fb3')
251
+ expect(bad.slugs).not_to eql '4ea0389f0364313d79104fb3'
252
+ end
253
+
254
+ it 'does not update slug if slugged fields have not changed' do
255
+ partner.save
256
+ expect(partner.to_param).to eql 'jane-smith'
257
+ end
258
+
259
+ it 'does not change slug if slugged fields have changed but generated slug is identical' do
260
+ partner.name = 'JANE SMITH'
261
+ expect(partner.to_param).to eql 'jane-smith'
262
+ end
263
+
264
+ it 'scopes by parent object' do
265
+ affair = person.relationships.create(name: 'Affair')
266
+ lover = affair.partners.create(name: partner.name)
267
+ expect(lover.to_param).to eql partner.to_param
268
+ end
269
+
270
+ context 'using find' do
271
+ it 'finds by id as string' do
272
+ expect(relationship.partners.find(partner.id.to_s)).to eql partner
273
+ end
274
+
275
+ it 'finds by id as array of strings' do
276
+ expect(relationship.partners.find([partner.id.to_s])).to eql [partner]
277
+ end
278
+
279
+ it 'finds by id as BSON::ObjectId' do
280
+ expect(relationship.partners.find(partner.id)).to eql partner
281
+ end
282
+
283
+ it 'finds by id as an array of BSON::ObjectIds' do
284
+ expect(relationship.partners.find([partner.id])).to eql [partner]
285
+ end
286
+
287
+ it 'returns an empty array if given an empty array' do
288
+ expect(relationship.partners.find([])).to eql []
289
+ end
290
+ end
291
+ end
292
+
293
+ context 'when the slug is composed of multiple fields' do
294
+ let!(:author) do
295
+ Author.create(
296
+ first_name: 'Gilles',
297
+ last_name: 'Deleuze'
298
+ )
299
+ end
300
+
301
+ it 'generates a slug' do
302
+ expect(author.to_param).to eql 'gilles-deleuze'
303
+ end
304
+
305
+ it 'updates the slug' do
306
+ author.first_name = 'Félix'
307
+ author.last_name = 'Guattari'
308
+ author.save
309
+ expect(author.to_param).to eql 'felix-guattari'
310
+ end
311
+
312
+ it 'generates a unique slug by appending a counter to duplicate text' do
313
+ dup = Author.create(
314
+ first_name: author.first_name,
315
+ last_name: author.last_name
316
+ )
317
+ expect(dup.to_param).to eql 'gilles-deleuze-1'
318
+
319
+ dup2 = Author.create(
320
+ first_name: author.first_name,
321
+ last_name: author.last_name
322
+ )
323
+
324
+ dup.save
325
+ expect(dup2.to_param).to eql 'gilles-deleuze-2'
326
+ end
327
+
328
+ it 'does not allow a BSON::ObjectId as use for a slug' do
329
+ bad = Author.create(first_name: '4ea0389f0364',
330
+ last_name: '313d79104fb3')
331
+ expect(bad.to_param).not_to eql '4ea0389f0364313d79104fb3'
332
+ end
333
+
334
+ it 'does not update slug if slugged fields have changed but generated slug is identical' do
335
+ author.last_name = 'DELEUZE'
336
+ author.save
337
+ expect(author.to_param).to eql 'gilles-deleuze'
338
+ end
339
+ end
340
+
341
+ context 'when :as is passed as an argument' do
342
+ let!(:person) do
343
+ Person.create(name: 'John Doe')
344
+ end
345
+
346
+ it 'sets an alternative slug field name' do
347
+ expect(person).to respond_to(:_slugs)
348
+ expect(person.slugs).to eql ['john-doe']
349
+ end
350
+
351
+ it 'defines #slug' do
352
+ expect(person).to respond_to :slugs
353
+ end
354
+
355
+ it 'defines #slug_changed?' do
356
+ expect(person).to respond_to :_slugs_changed?
357
+ end
358
+
359
+ it 'defines #slug_was' do
360
+ expect(person).to respond_to :_slugs_was
361
+ end
362
+ end
363
+
364
+ context 'when :permanent is passed as an argument' do
365
+ let(:person) do
366
+ Person.create(name: 'John Doe')
367
+ end
368
+
369
+ it 'does not update the slug when the slugged fields change' do
370
+ person.name = 'Jane Doe'
371
+ person.save
372
+ expect(person.to_param).to eql 'john-doe'
373
+ end
374
+ end
375
+
376
+ context 'when :history is passed as an argument' do
377
+ context 'true' do
378
+ let(:book) do
379
+ Book.create(title: 'Book Title')
380
+ end
381
+
382
+ before(:each) do
383
+ book.title = 'Other Book Title'
384
+ book.save
385
+ end
386
+
387
+ it "saves the old slug in the owner's history" do
388
+ expect(book.slugs).to include('book-title')
389
+ end
390
+
391
+ it 'generates a unique slug by appending a counter to duplicate text' do
392
+ dup = Book.create(title: 'Book Title')
393
+ expect(dup.to_param).to eql 'book-title-1'
394
+ end
395
+
396
+ it 'does not allow a BSON::ObjectId as use for a slug' do
397
+ bad = Book.create(title: '4ea0389f0364313d79104fb3')
398
+ expect(bad.to_param).not_to eql '4ea0389f0364313d79104fb3'
399
+ end
400
+
401
+ it 'ensures no duplicate values are stored in history' do
402
+ book.update_attributes title: 'Book Title'
403
+ book.update_attributes title: 'Foo'
404
+ expect(book.slugs.find_all { |slug| slug == 'book-title' }.size).to eql 1
405
+ end
406
+ end
407
+ context 'false' do
408
+ let(:author) do
409
+ Author.create(first_name: 'Gilles', last_name: 'Deleuze')
410
+ end
411
+
412
+ before(:each) do
413
+ author.first_name = 'John'
414
+ author.save
415
+ end
416
+
417
+ it "does not save the old slug in the owner's history" do
418
+ expect(author.slugs.count).to eq 1
419
+ expect(author.slugs).to_not include('gilles-deleuze')
420
+ end
421
+ end
422
+ end
423
+
424
+ context 'when slug is scoped by a reference association' do
425
+ let(:author) do
426
+ book.authors.create(first_name: 'Gilles', last_name: 'Deleuze')
427
+ end
428
+
429
+ it 'scopes by parent object' do
430
+ book2 = Book.create(title: 'Anti Oedipus')
431
+ dup = book2.authors.create(
432
+ first_name: author.first_name,
433
+ last_name: author.last_name
434
+ )
435
+ expect(dup.to_param).to eql author.to_param
436
+ end
437
+
438
+ it 'generates a unique slug by appending a counter to duplicate text' do
439
+ dup = book.authors.create(
440
+ first_name: author.first_name,
441
+ last_name: author.last_name
442
+ )
443
+ expect(dup.to_param).to eql 'gilles-deleuze-1'
444
+ end
445
+
446
+ it 'does not allow a BSON::ObjectId as use for a slug' do
447
+ bad = book.authors.create(first_name: '4ea0389f0364',
448
+ last_name: '313d79104fb3')
449
+ expect(bad.to_param).not_to eql '4ea0389f0364313d79104fb3'
450
+ end
451
+
452
+ context 'with an irregular association name' do
453
+ let(:character) do
454
+ # well we've got to make up something... :-)
455
+ author.characters.create(name: 'Oedipus')
456
+ end
457
+
458
+ let!(:author2) do
459
+ Author.create(
460
+ first_name: 'Sophocles',
461
+ last_name: 'son of Sophilos'
462
+ )
463
+ end
464
+
465
+ it 'scopes by parent object provided that inverse_of is specified' do
466
+ dup = author2.characters.create(name: character.name)
467
+ expect(dup.to_param).to eql character.to_param
468
+ end
469
+ end
470
+ end
471
+
472
+ context "when slug is scoped by one of the class's own fields" do
473
+ let!(:magazine) do
474
+ Magazine.create(title: 'Big Weekly', publisher_id: 'abc123')
475
+ end
476
+
477
+ it 'should scope by local field' do
478
+ expect(magazine.to_param).to eql 'big-weekly'
479
+ magazine2 = Magazine.create(title: 'Big Weekly', publisher_id: 'def456')
480
+ expect(magazine2.to_param).to eql magazine.to_param
481
+ end
482
+
483
+ it 'should generate a unique slug by appending a counter to duplicate text' do
484
+ dup = Magazine.create(title: 'Big Weekly', publisher_id: 'abc123')
485
+ expect(dup.to_param).to eql 'big-weekly-1'
486
+ end
487
+
488
+ it 'does not allow a BSON::ObjectId as use for a slug' do
489
+ bad = Magazine.create(title: '4ea0389f0364313d79104fb3', publisher_id: 'abc123')
490
+ expect(bad.to_param).not_to eql '4ea0389f0364313d79104fb3'
491
+ end
492
+ end
493
+
494
+ context 'when #slug is given a block' do
495
+ let(:caption) do
496
+ Caption.create(my_identity: 'Edward Hopper (American, 1882-1967)',
497
+ title: 'Soir Bleu, 1914',
498
+ medium: 'Oil on Canvas')
499
+ end
500
+
501
+ it 'generates a slug' do
502
+ expect(caption.to_param).to eql 'edward-hopper-soir-bleu-1914'
503
+ end
504
+
505
+ it 'updates the slug' do
506
+ caption.title = 'Road in Maine, 1914'
507
+ caption.save
508
+ expect(caption.to_param).to eql 'edward-hopper-road-in-maine-1914'
509
+ end
510
+
511
+ it 'does not change slug if slugged fields have changed but generated slug is identical' do
512
+ caption.my_identity = 'Edward Hopper'
513
+ caption.save
514
+ expect(caption.to_param).to eql 'edward-hopper-soir-bleu-1914'
515
+ end
516
+ end
517
+
518
+ context 'when block is configured globally' do
519
+ before do
520
+ Mongoid::Slug.configure do |c|
521
+ c.slug do |cur_obj|
522
+ slug = cur_obj.slug_builder
523
+ "#{slug}-#{cur_obj.id}".to_url
524
+ end
525
+ end
526
+ end
527
+
528
+ after do
529
+ # Remove global configuration to avoid affect on
530
+ # other specs run after this spec
531
+ Mongoid::Slug.default_slug = nil
532
+ expect(Mongoid::Slug.default_slug).to be_nil
533
+ end
534
+
535
+ it 'generates a slug' do
536
+ expect(Mongoid::Slug.default_slug).to be_present
537
+ class Person
538
+ include Mongoid::Document
539
+ include Mongoid::Slug
540
+
541
+ field :name
542
+ slug :name
543
+ end
544
+
545
+ person = Person.create(name: 'John')
546
+ expect(person.to_param).to eql "john-#{person.id}"
547
+ end
548
+
549
+ it 'can be overridden at model level' do
550
+ expect(Mongoid::Slug.default_slug).to be_present
551
+ class Person
552
+ include Mongoid::Document
553
+ include Mongoid::Slug
554
+
555
+ field :name
556
+ slug :name do |cur_object|
557
+ cur_object.slug_builder.to_url
558
+ end
559
+ end
560
+
561
+ person = Person.create(name: 'John')
562
+ expect(person.to_param).to eql 'john'
563
+ end
564
+ end
565
+
566
+ context 'when slugged field contains non-ASCII characters' do
567
+ it 'slugs Cyrillic characters' do
568
+ book.title = 'Капитал'
569
+ book.save
570
+ expect(book.to_param).to eql 'kapital'
571
+ end
572
+
573
+ it 'slugs Greek characters' do
574
+ book.title = 'Ελλάδα'
575
+ book.save
576
+ expect(book.to_param).to eql 'ellada'
577
+ end
578
+
579
+ it 'slugs Chinese characters' do
580
+ book.title = '中文'
581
+ book.save
582
+ expect(book.to_param).to eql 'zhong-wen'
583
+ end
584
+
585
+ it 'slugs non-ASCII Latin characters' do
586
+ book.title = 'Paul Cézanne'
587
+ book.save
588
+ expect(book.to_param).to eql 'paul-cezanne'
589
+ end
590
+ end
591
+
592
+ context 'when slug is not scoped by a reference association' do
593
+ subject { Book }
594
+ it_should_behave_like 'has an index', { _slugs: 1 }, unique: true, sparse: true
595
+ end
596
+
597
+ context 'with a value exceeding mongodb max index key' do
598
+ if Mongoid::Compatibility::Version.mongoid5_or_newer?
599
+ xit 'errors with a model without a max length' do
600
+ expect do
601
+ Book.create!(title: 't' * 1025)
602
+ end.to raise_error Mongo::Error::OperationFailure, /key too large to index/
603
+ end
604
+ elsif Mongoid::Compatibility::Version.mongoid4?
605
+ xit 'errors with a model without a max length' do
606
+ expect do
607
+ Book.create!(title: 't' * 1025)
608
+ end.to raise_error Moped::Errors::OperationFailure, /key too large to index/
609
+ end
610
+ end
611
+
612
+ it 'succeeds with a model with a max length' do
613
+ expect do
614
+ author = Author.create!(last_name: 't' * 1025)
615
+ expect(author.slug.length).to eq 256
616
+ end.to_not raise_error
617
+ end
618
+ end
619
+
620
+ context 'when slug is scoped by a reference association' do
621
+ subject { Author }
622
+ it_should_behave_like 'does not have an index', _slugs: 1
623
+ end
624
+
625
+ context 'for subclass scope' do
626
+ context 'when slug is not scoped by a reference association' do
627
+ subject { BookPolymorphic }
628
+ it_should_behave_like 'has an index', { _type: 1, _slugs: 1 }, unique: nil, sparse: nil
629
+ end
630
+
631
+ context 'when slug is scoped by a reference association' do
632
+ subject { AuthorPolymorphic }
633
+ it_should_behave_like 'does not have an index', _type: 1, _slugs: 1
634
+ end
635
+
636
+ context 'when the object has STI' do
637
+ it 'scopes by the subclass' do
638
+ b = BookPolymorphic.create!(title: 'Book')
639
+ expect(b.slug).to eq('book')
640
+
641
+ b2 = BookPolymorphic.create!(title: 'Book')
642
+ expect(b2.slug).to eq('book-1')
643
+
644
+ c = ComicBookPolymorphic.create!(title: 'Book')
645
+ expect(c.slug).to eq('book')
646
+
647
+ c2 = ComicBookPolymorphic.create!(title: 'Book')
648
+ expect(c2.slug).to eq('book-1')
649
+
650
+ expect(BookPolymorphic.find('book')).to eq(b)
651
+ expect(BookPolymorphic.find('book-1')).to eq(b2)
652
+ expect(ComicBookPolymorphic.find('book')).to eq(c)
653
+ expect(ComicBookPolymorphic.find('book-1')).to eq(c2)
654
+ end
655
+ end
656
+ end
657
+ end
658
+
659
+ context 'for reserved words' do
660
+ context 'when the :reserve option is used on the model' do
661
+ it 'does not use the reserved slugs' do
662
+ friend1 = Friend.create(name: 'foo')
663
+ expect(friend1.slugs).not_to include('foo')
664
+ expect(friend1.slugs).to include('foo-1')
665
+
666
+ friend2 = Friend.create(name: 'bar')
667
+ expect(friend2.slugs).not_to include('bar')
668
+ expect(friend2.slugs).to include('bar-1')
669
+
670
+ friend3 = Friend.create(name: 'en')
671
+ expect(friend3.slugs).not_to include('en')
672
+ expect(friend3.slugs).to include('en-1')
673
+ end
674
+
675
+ it 'should start with concatenation -1' do
676
+ friend1 = Friend.create(name: 'foo')
677
+ expect(friend1.slugs).to include('foo-1')
678
+ friend2 = Friend.create(name: 'foo')
679
+ expect(friend2.slugs).to include('foo-2')
680
+ end
681
+
682
+ %w[new edit].each do |word|
683
+ it "should overwrite the default reserved words allowing the word '#{word}'" do
684
+ friend = Friend.create(name: word)
685
+ expect(friend.slugs).to include word
686
+ end
687
+ end
688
+ end
689
+ context 'when the model does not have any reserved words set' do
690
+ %w[new edit].each do |word|
691
+ it "does not use the default reserved word '#{word}'" do
692
+ book = Book.create(title: word)
693
+ expect(book.slugs).not_to include word
694
+ expect(book.slugs).to include("#{word}-1")
695
+ end
696
+ end
697
+ end
698
+
699
+ context 'when the object has STI' do
700
+ it 'scopes by the superclass' do
701
+ book = Book.create(title: 'Anti Oedipus')
702
+ comic_book = ComicBook.create(title: 'Anti Oedipus')
703
+ expect(comic_book.slugs).not_to eql(book.slugs)
704
+ end
705
+
706
+ it 'scopes by the subclass' do
707
+ book = BookPolymorphic.create(title: 'Anti Oedipus')
708
+ comic_book = ComicBookPolymorphic.create(title: 'Anti Oedipus')
709
+ expect(comic_book.slugs).to eql(book.slugs)
710
+
711
+ expect(BookPolymorphic.find(book.slug)).to eq(book)
712
+ expect(ComicBookPolymorphic.find(comic_book.slug)).to eq(comic_book)
713
+ end
714
+ end
715
+
716
+ context 'when slug defined on alias of field' do
717
+ it 'should use accessor, not alias' do
718
+ pseudonim = Alias.create(author_name: 'Max Stirner')
719
+ expect(pseudonim.slugs).to include('max-stirner')
720
+ end
721
+ end
722
+
723
+ describe '#to_param' do
724
+ context 'when called on a new record' do
725
+ let(:book) { Book.new }
726
+
727
+ it 'should return nil' do
728
+ expect(book.to_param).to eq book._id.to_s
729
+ end
730
+
731
+ it 'should not persist the record' do
732
+ book.to_param
733
+ expect(book).not_to be_persisted
734
+ end
735
+ end
736
+
737
+ context 'when called on an existing record with no slug' do
738
+ let!(:book_no_slug) do
739
+ if Mongoid::Compatibility::Version.mongoid5_or_newer?
740
+ Book.collection.insert_one(title: 'Proust and Signs')
741
+ else
742
+ Book.collection.insert(title: 'Proust and Signs')
743
+ end
744
+ Book.where(title: 'Proust and Signs').first
745
+ end
746
+
747
+ it 'should return the id if there is no slug' do
748
+ expect(book_no_slug.to_param).to eq(book_no_slug.id.to_s)
749
+ expect(book_no_slug.slugs).to be_nil
750
+ end
751
+
752
+ it 'should not persist the record' do
753
+ expect(book_no_slug.to_param).to eq(book_no_slug._id.to_s)
754
+ end
755
+ end
756
+ end
757
+
758
+ describe '#_slugs_changed?' do
759
+ before do
760
+ Book.create(title: 'A Thousand Plateaus')
761
+ end
762
+
763
+ let(:book) { Book.first }
764
+
765
+ it 'is initially unchanged' do
766
+ expect(book._slugs_changed?).to be_falsey
767
+ end
768
+
769
+ it 'tracks changes' do
770
+ book.slugs = ['Anti Oedipus']
771
+ expect(book._slugs_changed?).to be_truthy
772
+ end
773
+ end
774
+
775
+ describe 'when regular expression matches, but document does not' do
776
+ let!(:book_1) { Book.create(title: 'book-1') }
777
+ let!(:book_2) { Book.create(title: 'book') }
778
+ let!(:book_3) { Book.create(title: 'book') }
779
+
780
+ it 'book_2 should have the user supplied title without -1 after it' do
781
+ expect(book_2.to_param).to eql 'book'
782
+ end
783
+
784
+ it 'book_3 should have a generated slug' do
785
+ expect(book_3.to_param).to eql 'book-2'
786
+ end
787
+ end
788
+
789
+ context 'when the slugged field is set manually' do
790
+ context 'when it set to a non-empty string' do
791
+ it 'respects the provided slug' do
792
+ book = Book.create(title: 'A Thousand Plateaus', slugs: ['not-what-you-expected'])
793
+ expect(book.to_param).to eql 'not-what-you-expected'
794
+ end
795
+
796
+ it 'ensures uniqueness' do
797
+ book1 = Book.create(title: 'A Thousand Plateaus', slugs: ['not-what-you-expected'])
798
+ expect(book1.to_param).to eql 'not-what-you-expected'
799
+ if Mongoid::Compatibility::Version.mongoid5_or_newer?
800
+ expect do
801
+ Book.create(title: 'A Thousand Plateaus', slugs: ['not-what-you-expected'])
802
+ end.to raise_error Mongo::Error::OperationFailure, /duplicate/
803
+ elsif Mongoid::Compatibility::Version.mongoid4?
804
+ expect do
805
+ Book.create(title: 'A Thousand Plateaus', slugs: ['not-what-you-expected'])
806
+ end.to raise_error Moped::Errors::OperationFailure, /duplicate/
807
+ end
808
+ end
809
+
810
+ it 'updates the slug when a new one is passed in' do
811
+ book = Book.create(title: 'A Thousand Plateaus', slugs: ['not-what-you-expected'])
812
+ book.slugs = ['not-it-either']
813
+ book.save
814
+ expect(book.to_param).to eql 'not-it-either'
815
+ end
816
+
817
+ it 'updates the slug when a new one is appended' do
818
+ book = Book.create(title: 'A Thousand Plateaus', slugs: ['not-what-you-expected'])
819
+ book.slugs.push 'not-it-either'
820
+ book.save
821
+ expect(book.to_param).to eql 'not-it-either'
822
+ end
823
+
824
+ it 'updates the slug to a unique slug when a new one is appended' do
825
+ Book.create(title: 'Sleepyhead')
826
+ book2 = Book.create(title: 'A Thousand Plateaus')
827
+ book2.slugs.push 'sleepyhead'
828
+ if Mongoid::Compatibility::Version.mongoid5_or_newer?
829
+ expect do
830
+ book2.save
831
+ end.to raise_error Mongo::Error::OperationFailure, /duplicate/
832
+ elsif Mongoid::Compatibility::Version.mongoid4?
833
+ expect do
834
+ book2.save
835
+ end.to raise_error Moped::Errors::OperationFailure, /duplicate/
836
+ end
837
+ end
838
+ end
839
+
840
+ context 'when it is set to an empty string' do
841
+ it 'generate a new one' do
842
+ book = Book.create(title: 'A Thousand Plateaus')
843
+ expect(book.to_param).to eql 'a-thousand-plateaus'
844
+ end
845
+ end
846
+ end
847
+
848
+ context 'slug can be localized' do
849
+ before(:each) do
850
+ @old_locale = I18n.locale
851
+ end
852
+
853
+ after(:each) do
854
+ I18n.locale = @old_locale
855
+ end
856
+
857
+ it 'generates a new slug for each localization' do
858
+ page = PageSlugLocalized.new
859
+ page.title = 'Title on English'
860
+ page.save
861
+ expect(page.slug).to eql 'title-on-english'
862
+ I18n.locale = :nl
863
+ page.title = 'Title on Netherlands'
864
+ page.save
865
+ expect(page.slug).to eql 'title-on-netherlands'
866
+ end
867
+
868
+ it 'returns _id if no slug' do
869
+ page = PageSlugLocalized.new
870
+ page.title = 'Title on English'
871
+ page.save
872
+ expect(page.slug).to eql 'title-on-english'
873
+ I18n.locale = :nl
874
+ expect(page.slug).to eql page._id.to_s
875
+ end
876
+
877
+ it 'fallbacks if slug not localized yet' do
878
+ page = PageSlugLocalized.new
879
+ page.title = 'Title on English'
880
+ page.save
881
+ expect(page.slug).to eql 'title-on-english'
882
+ I18n.locale = :nl
883
+ expect(page.slug).to eql page._id.to_s
884
+
885
+ # Turn on i18n fallback
886
+ require 'i18n/backend/fallbacks'
887
+ I18n::Backend::Simple.send(:include, I18n::Backend::Fallbacks)
888
+ ::I18n.fallbacks[:nl] = %i[nl en]
889
+ expect(page.slug).to eql 'title-on-english'
890
+ fallback_slug = page.slug
891
+
892
+ fallback_page = begin
893
+ PageSlugLocalized.find(fallback_slug)
894
+ rescue StandardError
895
+ nil
896
+ end
897
+ expect(fallback_page).to eq(page)
898
+
899
+ # Restore fallback for next tests
900
+ ::I18n.fallbacks[:nl] = [:nl]
901
+ end
902
+
903
+ it 'returns a default slug if not localized' do
904
+ page = PageLocalize.new
905
+ page.title = 'Title on English'
906
+ page.save
907
+ expect(page.slug).to eql 'title-on-english'
908
+ I18n.locale = :nl
909
+ page.title = 'Title on Netherlands'
910
+ expect(page.slug).to eql 'title-on-english'
911
+ page.save
912
+ expect(page.slug).to eql 'title-on-netherlands'
913
+ end
914
+
915
+ it 'slugs properly when translations are set directly' do
916
+ page = PageSlugLocalized.new
917
+ page.title_translations = { 'en' => 'Title on English', 'nl' => 'Title on Netherlands' }
918
+ page.save
919
+ expect(page._slugs_translations).to eq('en' => ['title-on-english'], 'nl' => ['title-on-netherlands'])
920
+ end
921
+
922
+ it 'exact same title multiple langauges' do
923
+ page = PageSlugLocalized.new
924
+ page.title_translations = { 'en' => 'Title on English', 'nl' => 'Title on English' }
925
+ page.save
926
+ expect(page._slugs_translations).to eq('en' => ['title-on-english'], 'nl' => ['title-on-english'])
927
+
928
+ page = PageSlugLocalized.create(title_translations: { 'en' => 'Title on English2', 'nl' => 'Title on English2' })
929
+ expect(page._slugs_translations).to eq('en' => ['title-on-english2'], 'nl' => ['title-on-english2'])
930
+ end
931
+
932
+ it 'does not produce duplicate slugs' do
933
+ old_locale = I18n.locale
934
+
935
+ # Using a default locale of en.
936
+ page = PageSlugLocalized.new
937
+ page.title = 'Title on English'
938
+ page.save
939
+ I18n.locale = 'nl'
940
+ page.title = 'Title on Netherlands'
941
+ page.save
942
+ expect(page.title_translations).to eq('en' => 'Title on English', 'nl' => 'Title on Netherlands')
943
+
944
+ I18n.locale = old_locale
945
+ page.title = 'Title on English'
946
+ expect(page.title_translations).to eq('en' => 'Title on English', 'nl' => 'Title on Netherlands')
947
+ expect(page._slugs_translations).to eq('en' => ['title-on-english'], 'nl' => ['title-on-netherlands'])
948
+ end
949
+
950
+ it 'does not produce duplicate slugs when one has changed' do
951
+ old_locale = I18n.locale
952
+
953
+ # Using a default locale of en.
954
+ page = PageSlugLocalized.new
955
+ page.title = 'Title on English'
956
+ page.save
957
+ I18n.locale = 'nl'
958
+ page.title = 'Title on Netherlands'
959
+ page.save
960
+ expect(page.title_translations).to eq('en' => 'Title on English', 'nl' => 'Title on Netherlands')
961
+
962
+ I18n.locale = old_locale
963
+ page.title = 'Modified Title on English'
964
+ page.save
965
+ expect(page.title_translations).to eq('en' => 'Modified Title on English',
966
+ 'nl' => 'Title on Netherlands')
967
+ expect(page._slugs_translations).to eq('en' => ['modified-title-on-english'],
968
+ 'nl' => ['title-on-netherlands'])
969
+ end
970
+
971
+ it 'does not produce duplicate slugs when transactions are set directly' do
972
+ page = PageSlugLocalized.new
973
+ page.title_translations = { 'en' => 'Title on English', 'nl' => 'Title on Netherlands' }
974
+ page.save
975
+ page.title_translations = { 'en' => 'Title on English', 'nl' => 'Title on Netherlands' }
976
+ page.save
977
+ expect(page._slugs_translations).to eq('en' => ['title-on-english'], 'nl' => ['title-on-netherlands'])
978
+ end
979
+
980
+ it 'does not produce duplicate slugs when transactions are set directly and one has changed' do
981
+ page = PageSlugLocalized.new
982
+ page.title_translations = { 'en' => 'Title on English', 'nl' => 'Title on Netherlands' }
983
+ page.save
984
+ page.title_translations = { 'en' => 'Modified Title on English',
985
+ 'nl' => 'Title on Netherlands' }
986
+ page.save
987
+ expect(page._slugs_translations).to eq('en' => ['modified-title-on-english'],
988
+ 'nl' => ['title-on-netherlands'])
989
+ end
990
+
991
+ it 'works with a custom slug strategy' do
992
+ page = PageSlugLocalizedCustom.new
993
+ page.title = 'a title for the slug'
994
+ page.save
995
+ expect(page._slugs_translations).to eq('en' => ['a-title-for-the-slug'], 'nl' => ['a-title-for-the-slug'])
996
+ end
997
+ end
998
+
999
+ context 'slug can be localized when using history' do
1000
+ before(:each) do
1001
+ @old_locale = I18n.locale
1002
+ end
1003
+
1004
+ after(:each) do
1005
+ I18n.locale = @old_locale
1006
+ end
1007
+
1008
+ it 'generate a new slug for each localization and keep history' do
1009
+ old_locale = I18n.locale
1010
+
1011
+ page = PageSlugLocalizedHistory.new
1012
+ page.title = 'Title on English'
1013
+ page.save
1014
+ expect(page.slug).to eql 'title-on-english'
1015
+ I18n.locale = :nl
1016
+ page.title = 'Title on Netherlands'
1017
+ page.save
1018
+ expect(page.slug).to eql 'title-on-netherlands'
1019
+ I18n.locale = old_locale
1020
+ page.title = 'Modified title on English'
1021
+ page.save
1022
+ expect(page.slug).to eql 'modified-title-on-english'
1023
+ expect(page.slug).to include('title-on-english')
1024
+ I18n.locale = :nl
1025
+ page.title = 'Modified title on Netherlands'
1026
+ page.save
1027
+ expect(page.slug).to eql 'modified-title-on-netherlands'
1028
+ expect(page.slug).to include('title-on-netherlands')
1029
+ end
1030
+
1031
+ it 'returns _id if no slug' do
1032
+ page = PageSlugLocalizedHistory.new
1033
+ page.title = 'Title on English'
1034
+ page.save
1035
+ expect(page.slug).to eql 'title-on-english'
1036
+ I18n.locale = :nl
1037
+ expect(page.slug).to eql page._id.to_s
1038
+ end
1039
+
1040
+ it 'fallbacks if slug not localized yet' do
1041
+ page = PageSlugLocalizedHistory.new
1042
+ page.title = 'Title on English'
1043
+ page.save
1044
+ expect(page.slug).to eql 'title-on-english'
1045
+ I18n.locale = :nl
1046
+ expect(page.slug).to eql page._id.to_s
1047
+
1048
+ # Turn on i18n fallback
1049
+ require 'i18n/backend/fallbacks'
1050
+ I18n::Backend::Simple.send(:include, I18n::Backend::Fallbacks)
1051
+ ::I18n.fallbacks[:nl] = %i[nl en]
1052
+ expect(page.slug).to eql 'title-on-english'
1053
+ fallback_slug = page.slug
1054
+
1055
+ fallback_page = begin
1056
+ PageSlugLocalizedHistory.find(fallback_slug)
1057
+ rescue StandardError
1058
+ nil
1059
+ end
1060
+ expect(fallback_page).to eq(page)
1061
+ end
1062
+
1063
+ it 'slugs properly when translations are set directly' do
1064
+ page = PageSlugLocalizedHistory.new
1065
+ page.title_translations = { 'en' => 'Title on English', 'nl' => 'Title on Netherlands' }
1066
+ page.save
1067
+ page.title_translations = { 'en' => 'Modified Title on English',
1068
+ 'nl' => 'Modified Title on Netherlands' }
1069
+ page.save
1070
+ expect(page._slugs_translations).to eq('en' => ['title-on-english', 'modified-title-on-english'],
1071
+ 'nl' => ['title-on-netherlands', 'modified-title-on-netherlands'])
1072
+ end
1073
+
1074
+ it 'does not produce duplicate slugs' do
1075
+ old_locale = I18n.locale
1076
+
1077
+ # Using a default locale of en.
1078
+ page = PageSlugLocalizedHistory.new
1079
+ page.title = 'Title on English'
1080
+ page.save
1081
+ I18n.locale = 'nl'
1082
+ page.title = 'Title on Netherlands'
1083
+ page.save
1084
+ expect(page.title_translations).to eq('en' => 'Title on English', 'nl' => 'Title on Netherlands')
1085
+
1086
+ I18n.locale = old_locale
1087
+ page.title = 'Title on English'
1088
+ expect(page.title_translations).to eq('en' => 'Title on English', 'nl' => 'Title on Netherlands')
1089
+ expect(page._slugs_translations).to eq('en' => ['title-on-english'], 'nl' => ['title-on-netherlands'])
1090
+ end
1091
+
1092
+ it 'does not produce duplicate slugs when one has changed' do
1093
+ old_locale = I18n.locale
1094
+
1095
+ # Using a default locale of en.
1096
+ page = PageSlugLocalizedHistory.new
1097
+ page.title = 'Title on English'
1098
+ page.save
1099
+ I18n.locale = 'nl'
1100
+ page.title = 'Title on Netherlands'
1101
+ page.save
1102
+ expect(page.title_translations).to eq('en' => 'Title on English', 'nl' => 'Title on Netherlands')
1103
+
1104
+ I18n.locale = old_locale
1105
+ page.title = 'Modified Title on English'
1106
+ page.save
1107
+ expect(page.title_translations).to eq('en' => 'Modified Title on English',
1108
+ 'nl' => 'Title on Netherlands')
1109
+ expect(page._slugs_translations).to eq('en' => ['title-on-english', 'modified-title-on-english'],
1110
+ 'nl' => ['title-on-netherlands'])
1111
+ end
1112
+
1113
+ it 'does not produce duplicate slugs when transactions are set directly' do
1114
+ page = PageSlugLocalizedHistory.new
1115
+ page.title_translations = { 'en' => 'Title on English', 'nl' => 'Title on Netherlands' }
1116
+ page.save
1117
+ page.title_translations = { 'en' => 'Title on English', 'nl' => 'Title on Netherlands' }
1118
+ page.save
1119
+ expect(page._slugs_translations).to eq('en' => ['title-on-english'], 'nl' => ['title-on-netherlands'])
1120
+ end
1121
+
1122
+ it 'does not produce duplicate slugs when transactions are set directly and one has changed' do
1123
+ page = PageSlugLocalizedHistory.new
1124
+ page.title_translations = { 'en' => 'Title on English', 'nl' => 'Title on Netherlands' }
1125
+ page.save
1126
+ page.title_translations = { 'en' => 'Modified Title on English', 'nl' => 'Title on Netherlands' }
1127
+ page.save
1128
+ expect(page._slugs_translations).to eq('en' => ['title-on-english', 'modified-title-on-english'],
1129
+ 'nl' => ['title-on-netherlands'])
1130
+ end
1131
+ end
1132
+
1133
+ describe 'slug_max_length' do
1134
+ before do
1135
+ Author.create_indexes
1136
+ end
1137
+
1138
+ after do
1139
+ Author.remove_indexes
1140
+ end
1141
+
1142
+ it 'can be assigned to nil' do
1143
+ expect(Book.slug_max_length).to be nil
1144
+ end
1145
+
1146
+ it 'defaults to MONGO_INDEX_KEY_LIMIT_BYTES - 32' do
1147
+ expect(Article.slug_max_length).to eq Mongoid::Slug::MONGO_INDEX_KEY_LIMIT_BYTES - 32
1148
+ end
1149
+
1150
+ it 'is assigned via max_length' do
1151
+ expect(Author.slug_max_length).to eq 256
1152
+ end
1153
+
1154
+ it 'enforces max length of slug' do
1155
+ author1 = Author.create!(last_name: 't' * 1024)
1156
+ expect(author1.slug.length).to eq 256
1157
+ expect(author1.slug.ends_with?('ttt')).to be true
1158
+ author2 = Author.create!(last_name: 't' * 1024)
1159
+ expect(author2.slug.length).to eq 258
1160
+ expect(author2.slug.ends_with?('tt-1')).to be true
1161
+ author3 = Author.create!(last_name: 't' * 1024)
1162
+ expect(author3.slug.length).to eq 258
1163
+ expect(author3.slug.ends_with?('tt-2')).to be true
1164
+ end
1165
+ end
1166
+
1167
+ context 'has_many / belongs_to' do
1168
+ let(:book) { Book.create!(title: 'War and Peace') }
1169
+ it 'allows for duplicates with different slugs' do
1170
+ Author.create!(first_name: 'Leo', last_name: 'Tostoy')
1171
+ expect { book.authors.create!(first_name: 'Leo', last_name: 'Tostoy') }.to_not raise_error
1172
+ end
1173
+ end
1174
+ end
1175
+ end