mongoid-slug 4.0.0

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