mongoid-slug 6.0.0 → 6.0.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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +20 -20
  3. data/README.md +361 -336
  4. data/lib/mongoid/slug.rb +328 -328
  5. data/lib/mongoid/slug/criteria.rb +107 -107
  6. data/lib/mongoid/slug/{index.rb → index_builder.rb} +67 -45
  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 -173
  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 -11
  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 -207
  39. data/spec/mongoid/index_builder_spec.rb +105 -0
  40. data/spec/mongoid/slug_spec.rb +1175 -1169
  41. data/spec/shared/indexes.rb +41 -41
  42. data/spec/spec_helper.rb +61 -61
  43. data/spec/tasks/mongoid_slug_rake_spec.rb +73 -73
  44. metadata +31 -32
  45. data/spec/mongoid/index_spec.rb +0 -33
@@ -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,1169 +1,1175 @@
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
- it '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
- it '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
- it 'succeeds with a model with a max length' do
612
- expect do
613
- author = Author.create!(last_name: 't' * 1025)
614
- expect(author.slug.length).to eq 256
615
- end.to_not raise_error
616
- end
617
- end
618
-
619
- context 'when slug is scoped by a reference association' do
620
- subject { Author }
621
- it_should_behave_like 'does not have an index', _slugs: 1
622
- end
623
-
624
- context 'for subclass scope' do
625
- context 'when slug is not scoped by a reference association' do
626
- subject { BookPolymorphic }
627
- it_should_behave_like 'has an index', { _type: 1, _slugs: 1 }, unique: nil, sparse: nil
628
- end
629
-
630
- context 'when slug is scoped by a reference association' do
631
- subject { AuthorPolymorphic }
632
- it_should_behave_like 'does not have an index', _type: 1, _slugs: 1
633
- end
634
-
635
- context 'when the object has STI' do
636
- it 'scopes by the subclass' do
637
- b = BookPolymorphic.create!(title: 'Book')
638
- expect(b.slug).to eq('book')
639
-
640
- b2 = BookPolymorphic.create!(title: 'Book')
641
- expect(b2.slug).to eq('book-1')
642
-
643
- c = ComicBookPolymorphic.create!(title: 'Book')
644
- expect(c.slug).to eq('book')
645
-
646
- c2 = ComicBookPolymorphic.create!(title: 'Book')
647
- expect(c2.slug).to eq('book-1')
648
-
649
- expect(BookPolymorphic.find('book')).to eq(b)
650
- expect(BookPolymorphic.find('book-1')).to eq(b2)
651
- expect(ComicBookPolymorphic.find('book')).to eq(c)
652
- expect(ComicBookPolymorphic.find('book-1')).to eq(c2)
653
- end
654
- end
655
- end
656
- end
657
-
658
- context 'for reserved words' do
659
- context 'when the :reserve option is used on the model' do
660
- it 'does not use the reserved slugs' do
661
- friend1 = Friend.create(name: 'foo')
662
- expect(friend1.slugs).not_to include('foo')
663
- expect(friend1.slugs).to include('foo-1')
664
-
665
- friend2 = Friend.create(name: 'bar')
666
- expect(friend2.slugs).not_to include('bar')
667
- expect(friend2.slugs).to include('bar-1')
668
-
669
- friend3 = Friend.create(name: 'en')
670
- expect(friend3.slugs).not_to include('en')
671
- expect(friend3.slugs).to include('en-1')
672
- end
673
-
674
- it 'should start with concatenation -1' do
675
- friend1 = Friend.create(name: 'foo')
676
- expect(friend1.slugs).to include('foo-1')
677
- friend2 = Friend.create(name: 'foo')
678
- expect(friend2.slugs).to include('foo-2')
679
- end
680
-
681
- %w[new edit].each do |word|
682
- it "should overwrite the default reserved words allowing the word '#{word}'" do
683
- friend = Friend.create(name: word)
684
- expect(friend.slugs).to include word
685
- end
686
- end
687
- end
688
- context 'when the model does not have any reserved words set' do
689
- %w[new edit].each do |word|
690
- it "does not use the default reserved word '#{word}'" do
691
- book = Book.create(title: word)
692
- expect(book.slugs).not_to include word
693
- expect(book.slugs).to include("#{word}-1")
694
- end
695
- end
696
- end
697
-
698
- context 'when the object has STI' do
699
- it 'scopes by the superclass' do
700
- book = Book.create(title: 'Anti Oedipus')
701
- comic_book = ComicBook.create(title: 'Anti Oedipus')
702
- expect(comic_book.slugs).not_to eql(book.slugs)
703
- end
704
-
705
- it 'scopes by the subclass' do
706
- book = BookPolymorphic.create(title: 'Anti Oedipus')
707
- comic_book = ComicBookPolymorphic.create(title: 'Anti Oedipus')
708
- expect(comic_book.slugs).to eql(book.slugs)
709
-
710
- expect(BookPolymorphic.find(book.slug)).to eq(book)
711
- expect(ComicBookPolymorphic.find(comic_book.slug)).to eq(comic_book)
712
- end
713
- end
714
-
715
- context 'when slug defined on alias of field' do
716
- it 'should use accessor, not alias' do
717
- pseudonim = Alias.create(author_name: 'Max Stirner')
718
- expect(pseudonim.slugs).to include('max-stirner')
719
- end
720
- end
721
-
722
- describe '#to_param' do
723
- context 'when called on a new record' do
724
- let(:book) { Book.new }
725
-
726
- it 'should return nil' do
727
- expect(book.to_param).to eq book._id.to_s
728
- end
729
-
730
- it 'should not persist the record' do
731
- book.to_param
732
- expect(book).not_to be_persisted
733
- end
734
- end
735
-
736
- context 'when called on an existing record with no slug' do
737
- let!(:book_no_slug) do
738
- if Mongoid::Compatibility::Version.mongoid5_or_newer?
739
- Book.collection.insert_one(title: 'Proust and Signs')
740
- else
741
- Book.collection.insert(title: 'Proust and Signs')
742
- end
743
- Book.where(title: 'Proust and Signs').first
744
- end
745
-
746
- it 'should return the id if there is no slug' do
747
- expect(book_no_slug.to_param).to eq(book_no_slug.id.to_s)
748
- expect(book_no_slug.slugs).to be_nil
749
- end
750
-
751
- it 'should not persist the record' do
752
- expect(book_no_slug.to_param).to eq(book_no_slug._id.to_s)
753
- end
754
- end
755
- end
756
-
757
- describe '#_slugs_changed?' do
758
- before do
759
- Book.create(title: 'A Thousand Plateaus')
760
- end
761
-
762
- let(:book) { Book.first }
763
-
764
- it 'is initially unchanged' do
765
- expect(book._slugs_changed?).to be_falsey
766
- end
767
-
768
- it 'tracks changes' do
769
- book.slugs = ['Anti Oedipus']
770
- expect(book._slugs_changed?).to be_truthy
771
- end
772
- end
773
-
774
- describe 'when regular expression matches, but document does not' do
775
- let!(:book_1) { Book.create(title: 'book-1') }
776
- let!(:book_2) { Book.create(title: 'book') }
777
- let!(:book_3) { Book.create(title: 'book') }
778
-
779
- it 'book_2 should have the user supplied title without -1 after it' do
780
- expect(book_2.to_param).to eql 'book'
781
- end
782
-
783
- it 'book_3 should have a generated slug' do
784
- expect(book_3.to_param).to eql 'book-2'
785
- end
786
- end
787
-
788
- context 'when the slugged field is set manually' do
789
- context 'when it set to a non-empty string' do
790
- it 'respects the provided slug' do
791
- book = Book.create(title: 'A Thousand Plateaus', slugs: ['not-what-you-expected'])
792
- expect(book.to_param).to eql 'not-what-you-expected'
793
- end
794
-
795
- it 'ensures uniqueness' do
796
- book1 = Book.create(title: 'A Thousand Plateaus', slugs: ['not-what-you-expected'])
797
- expect(book1.to_param).to eql 'not-what-you-expected'
798
- if Mongoid::Compatibility::Version.mongoid5_or_newer?
799
- expect do
800
- Book.create(title: 'A Thousand Plateaus', slugs: ['not-what-you-expected'])
801
- end.to raise_error Mongo::Error::OperationFailure, /duplicate/
802
- elsif Mongoid::Compatibility::Version.mongoid4?
803
- expect do
804
- Book.create(title: 'A Thousand Plateaus', slugs: ['not-what-you-expected'])
805
- end.to raise_error Moped::Errors::OperationFailure, /duplicate/
806
- end
807
- end
808
-
809
- it 'updates the slug when a new one is passed in' do
810
- book = Book.create(title: 'A Thousand Plateaus', slugs: ['not-what-you-expected'])
811
- book.slugs = ['not-it-either']
812
- book.save
813
- expect(book.to_param).to eql 'not-it-either'
814
- end
815
-
816
- it 'updates the slug when a new one is appended' do
817
- book = Book.create(title: 'A Thousand Plateaus', slugs: ['not-what-you-expected'])
818
- book.slugs.push 'not-it-either'
819
- book.save
820
- expect(book.to_param).to eql 'not-it-either'
821
- end
822
-
823
- it 'updates the slug to a unique slug when a new one is appended' do
824
- Book.create(title: 'Sleepyhead')
825
- book2 = Book.create(title: 'A Thousand Plateaus')
826
- book2.slugs.push 'sleepyhead'
827
- if Mongoid::Compatibility::Version.mongoid5_or_newer?
828
- expect do
829
- book2.save
830
- end.to raise_error Mongo::Error::OperationFailure, /duplicate/
831
- elsif Mongoid::Compatibility::Version.mongoid4?
832
- expect do
833
- book2.save
834
- end.to raise_error Moped::Errors::OperationFailure, /duplicate/
835
- end
836
- end
837
- end
838
-
839
- context 'when it is set to an empty string' do
840
- it 'generate a new one' do
841
- book = Book.create(title: 'A Thousand Plateaus')
842
- expect(book.to_param).to eql 'a-thousand-plateaus'
843
- end
844
- end
845
- end
846
-
847
- context 'slug can be localized' do
848
- before(:each) do
849
- @old_locale = I18n.locale
850
- end
851
-
852
- after(:each) do
853
- I18n.locale = @old_locale
854
- end
855
-
856
- it 'generates a new slug for each localization' do
857
- page = PageSlugLocalized.new
858
- page.title = 'Title on English'
859
- page.save
860
- expect(page.slug).to eql 'title-on-english'
861
- I18n.locale = :nl
862
- page.title = 'Title on Netherlands'
863
- page.save
864
- expect(page.slug).to eql 'title-on-netherlands'
865
- end
866
-
867
- it 'returns _id if no slug' do
868
- page = PageSlugLocalized.new
869
- page.title = 'Title on English'
870
- page.save
871
- expect(page.slug).to eql 'title-on-english'
872
- I18n.locale = :nl
873
- expect(page.slug).to eql page._id.to_s
874
- end
875
-
876
- it 'fallbacks if slug not localized yet' do
877
- page = PageSlugLocalized.new
878
- page.title = 'Title on English'
879
- page.save
880
- expect(page.slug).to eql 'title-on-english'
881
- I18n.locale = :nl
882
- expect(page.slug).to eql page._id.to_s
883
-
884
- # Turn on i18n fallback
885
- require 'i18n/backend/fallbacks'
886
- I18n::Backend::Simple.send(:include, I18n::Backend::Fallbacks)
887
- ::I18n.fallbacks[:nl] = %i[nl en]
888
- expect(page.slug).to eql 'title-on-english'
889
- fallback_slug = page.slug
890
-
891
- fallback_page = begin
892
- PageSlugLocalized.find(fallback_slug)
893
- rescue StandardError
894
- nil
895
- end
896
- expect(fallback_page).to eq(page)
897
-
898
- # Restore fallback for next tests
899
- ::I18n.fallbacks[:nl] = [:nl]
900
- end
901
-
902
- it 'returns a default slug if not localized' do
903
- page = PageLocalize.new
904
- page.title = 'Title on English'
905
- page.save
906
- expect(page.slug).to eql 'title-on-english'
907
- I18n.locale = :nl
908
- page.title = 'Title on Netherlands'
909
- expect(page.slug).to eql 'title-on-english'
910
- page.save
911
- expect(page.slug).to eql 'title-on-netherlands'
912
- end
913
-
914
- it 'slugs properly when translations are set directly' do
915
- page = PageSlugLocalized.new
916
- page.title_translations = { 'en' => 'Title on English', 'nl' => 'Title on Netherlands' }
917
- page.save
918
- expect(page._slugs_translations).to eq('en' => ['title-on-english'], 'nl' => ['title-on-netherlands'])
919
- end
920
-
921
- it 'exact same title multiple langauges' do
922
- page = PageSlugLocalized.new
923
- page.title_translations = { 'en' => 'Title on English', 'nl' => 'Title on English' }
924
- page.save
925
- expect(page._slugs_translations).to eq('en' => ['title-on-english'], 'nl' => ['title-on-english'])
926
-
927
- page = PageSlugLocalized.create(title_translations: { 'en' => 'Title on English2', 'nl' => 'Title on English2' })
928
- expect(page._slugs_translations).to eq('en' => ['title-on-english2'], 'nl' => ['title-on-english2'])
929
- end
930
-
931
- it 'does not produce duplicate slugs' do
932
- old_locale = I18n.locale
933
-
934
- # Using a default locale of en.
935
- page = PageSlugLocalized.new
936
- page.title = 'Title on English'
937
- page.save
938
- I18n.locale = 'nl'
939
- page.title = 'Title on Netherlands'
940
- page.save
941
- expect(page.title_translations).to eq('en' => 'Title on English', 'nl' => 'Title on Netherlands')
942
-
943
- I18n.locale = old_locale
944
- page.title = 'Title on English'
945
- expect(page.title_translations).to eq('en' => 'Title on English', 'nl' => 'Title on Netherlands')
946
- expect(page._slugs_translations).to eq('en' => ['title-on-english'], 'nl' => ['title-on-netherlands'])
947
- end
948
-
949
- it 'does not produce duplicate slugs when one has changed' do
950
- old_locale = I18n.locale
951
-
952
- # Using a default locale of en.
953
- page = PageSlugLocalized.new
954
- page.title = 'Title on English'
955
- page.save
956
- I18n.locale = 'nl'
957
- page.title = 'Title on Netherlands'
958
- page.save
959
- expect(page.title_translations).to eq('en' => 'Title on English', 'nl' => 'Title on Netherlands')
960
-
961
- I18n.locale = old_locale
962
- page.title = 'Modified Title on English'
963
- page.save
964
- expect(page.title_translations).to eq('en' => 'Modified Title on English',
965
- 'nl' => 'Title on Netherlands')
966
- expect(page._slugs_translations).to eq('en' => ['modified-title-on-english'],
967
- 'nl' => ['title-on-netherlands'])
968
- end
969
-
970
- it 'does not produce duplicate slugs when transactions are set directly' do
971
- page = PageSlugLocalized.new
972
- page.title_translations = { 'en' => 'Title on English', 'nl' => 'Title on Netherlands' }
973
- page.save
974
- page.title_translations = { 'en' => 'Title on English', 'nl' => 'Title on Netherlands' }
975
- page.save
976
- expect(page._slugs_translations).to eq('en' => ['title-on-english'], 'nl' => ['title-on-netherlands'])
977
- end
978
-
979
- it 'does not produce duplicate slugs when transactions are set directly and one has changed' do
980
- page = PageSlugLocalized.new
981
- page.title_translations = { 'en' => 'Title on English', 'nl' => 'Title on Netherlands' }
982
- page.save
983
- page.title_translations = { 'en' => 'Modified Title on English',
984
- 'nl' => 'Title on Netherlands' }
985
- page.save
986
- expect(page._slugs_translations).to eq('en' => ['modified-title-on-english'],
987
- 'nl' => ['title-on-netherlands'])
988
- end
989
-
990
- it 'works with a custom slug strategy' do
991
- page = PageSlugLocalizedCustom.new
992
- page.title = 'a title for the slug'
993
- page.save
994
- expect(page._slugs_translations).to eq('en' => ['a-title-for-the-slug'], 'nl' => ['a-title-for-the-slug'])
995
- end
996
- end
997
-
998
- context 'slug can be localized when using history' do
999
- before(:each) do
1000
- @old_locale = I18n.locale
1001
- end
1002
-
1003
- after(:each) do
1004
- I18n.locale = @old_locale
1005
- end
1006
-
1007
- it 'generate a new slug for each localization and keep history' do
1008
- old_locale = I18n.locale
1009
-
1010
- page = PageSlugLocalizedHistory.new
1011
- page.title = 'Title on English'
1012
- page.save
1013
- expect(page.slug).to eql 'title-on-english'
1014
- I18n.locale = :nl
1015
- page.title = 'Title on Netherlands'
1016
- page.save
1017
- expect(page.slug).to eql 'title-on-netherlands'
1018
- I18n.locale = old_locale
1019
- page.title = 'Modified title on English'
1020
- page.save
1021
- expect(page.slug).to eql 'modified-title-on-english'
1022
- expect(page.slug).to include('title-on-english')
1023
- I18n.locale = :nl
1024
- page.title = 'Modified title on Netherlands'
1025
- page.save
1026
- expect(page.slug).to eql 'modified-title-on-netherlands'
1027
- expect(page.slug).to include('title-on-netherlands')
1028
- end
1029
-
1030
- it 'returns _id if no slug' do
1031
- page = PageSlugLocalizedHistory.new
1032
- page.title = 'Title on English'
1033
- page.save
1034
- expect(page.slug).to eql 'title-on-english'
1035
- I18n.locale = :nl
1036
- expect(page.slug).to eql page._id.to_s
1037
- end
1038
-
1039
- it 'fallbacks if slug not localized yet' do
1040
- page = PageSlugLocalizedHistory.new
1041
- page.title = 'Title on English'
1042
- page.save
1043
- expect(page.slug).to eql 'title-on-english'
1044
- I18n.locale = :nl
1045
- expect(page.slug).to eql page._id.to_s
1046
-
1047
- # Turn on i18n fallback
1048
- require 'i18n/backend/fallbacks'
1049
- I18n::Backend::Simple.send(:include, I18n::Backend::Fallbacks)
1050
- ::I18n.fallbacks[:nl] = %i[nl en]
1051
- expect(page.slug).to eql 'title-on-english'
1052
- fallback_slug = page.slug
1053
-
1054
- fallback_page = begin
1055
- PageSlugLocalizedHistory.find(fallback_slug)
1056
- rescue StandardError
1057
- nil
1058
- end
1059
- expect(fallback_page).to eq(page)
1060
- end
1061
-
1062
- it 'slugs properly when translations are set directly' do
1063
- page = PageSlugLocalizedHistory.new
1064
- page.title_translations = { 'en' => 'Title on English', 'nl' => 'Title on Netherlands' }
1065
- page.save
1066
- page.title_translations = { 'en' => 'Modified Title on English',
1067
- 'nl' => 'Modified Title on Netherlands' }
1068
- page.save
1069
- expect(page._slugs_translations).to eq('en' => ['title-on-english', 'modified-title-on-english'],
1070
- 'nl' => ['title-on-netherlands', 'modified-title-on-netherlands'])
1071
- end
1072
-
1073
- it 'does not produce duplicate slugs' do
1074
- old_locale = I18n.locale
1075
-
1076
- # Using a default locale of en.
1077
- page = PageSlugLocalizedHistory.new
1078
- page.title = 'Title on English'
1079
- page.save
1080
- I18n.locale = 'nl'
1081
- page.title = 'Title on Netherlands'
1082
- page.save
1083
- expect(page.title_translations).to eq('en' => 'Title on English', 'nl' => 'Title on Netherlands')
1084
-
1085
- I18n.locale = old_locale
1086
- page.title = 'Title on English'
1087
- expect(page.title_translations).to eq('en' => 'Title on English', 'nl' => 'Title on Netherlands')
1088
- expect(page._slugs_translations).to eq('en' => ['title-on-english'], 'nl' => ['title-on-netherlands'])
1089
- end
1090
-
1091
- it 'does not produce duplicate slugs when one has changed' do
1092
- old_locale = I18n.locale
1093
-
1094
- # Using a default locale of en.
1095
- page = PageSlugLocalizedHistory.new
1096
- page.title = 'Title on English'
1097
- page.save
1098
- I18n.locale = 'nl'
1099
- page.title = 'Title on Netherlands'
1100
- page.save
1101
- expect(page.title_translations).to eq('en' => 'Title on English', 'nl' => 'Title on Netherlands')
1102
-
1103
- I18n.locale = old_locale
1104
- page.title = 'Modified Title on English'
1105
- page.save
1106
- expect(page.title_translations).to eq('en' => 'Modified Title on English',
1107
- 'nl' => 'Title on Netherlands')
1108
- expect(page._slugs_translations).to eq('en' => ['title-on-english', 'modified-title-on-english'],
1109
- 'nl' => ['title-on-netherlands'])
1110
- end
1111
-
1112
- it 'does not produce duplicate slugs when transactions are set directly' do
1113
- page = PageSlugLocalizedHistory.new
1114
- page.title_translations = { 'en' => 'Title on English', 'nl' => 'Title on Netherlands' }
1115
- page.save
1116
- page.title_translations = { 'en' => 'Title on English', 'nl' => 'Title on Netherlands' }
1117
- page.save
1118
- expect(page._slugs_translations).to eq('en' => ['title-on-english'], 'nl' => ['title-on-netherlands'])
1119
- end
1120
-
1121
- it 'does not produce duplicate slugs when transactions are set directly and one has changed' do
1122
- page = PageSlugLocalizedHistory.new
1123
- page.title_translations = { 'en' => 'Title on English', 'nl' => 'Title on Netherlands' }
1124
- page.save
1125
- page.title_translations = { 'en' => 'Modified Title on English', 'nl' => 'Title on Netherlands' }
1126
- page.save
1127
- expect(page._slugs_translations).to eq('en' => ['title-on-english', 'modified-title-on-english'],
1128
- 'nl' => ['title-on-netherlands'])
1129
- end
1130
- end
1131
-
1132
- describe 'slug_max_length' do
1133
- before do
1134
- Author.create_indexes
1135
- end
1136
- after do
1137
- Author.remove_indexes
1138
- end
1139
- it 'can be assigned to nil' do
1140
- expect(Book.slug_max_length).to be nil
1141
- end
1142
- it 'defaults to MONGO_INDEX_KEY_LIMIT_BYTES - 32' do
1143
- expect(Article.slug_max_length).to eq Mongoid::Slug::MONGO_INDEX_KEY_LIMIT_BYTES - 32
1144
- end
1145
- it 'is assigned via max_length' do
1146
- expect(Author.slug_max_length).to eq 256
1147
- end
1148
- it 'enforces max length of slug' do
1149
- author1 = Author.create!(last_name: 't' * 1024)
1150
- expect(author1.slug.length).to eq 256
1151
- expect(author1.slug.ends_with?('ttt')).to be true
1152
- author2 = Author.create!(last_name: 't' * 1024)
1153
- expect(author2.slug.length).to eq 258
1154
- expect(author2.slug.ends_with?('tt-1')).to be true
1155
- author3 = Author.create!(last_name: 't' * 1024)
1156
- expect(author3.slug.length).to eq 258
1157
- expect(author3.slug.ends_with?('tt-2')).to be true
1158
- end
1159
- end
1160
-
1161
- context 'has_many / belongs_to' do
1162
- let(:book) { Book.create!(title: 'War and Peace') }
1163
- it 'allows for duplicates with different slugs' do
1164
- Author.create!(first_name: 'Leo', last_name: 'Tostoy')
1165
- expect { book.authors.create!(first_name: 'Leo', last_name: 'Tostoy') }.to_not raise_error
1166
- end
1167
- end
1168
- end
1169
- 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