mongoid_slug 0.10.0 → 1.0.0.rc1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.md +47 -6
- data/lib/mongoid/slug.rb +184 -251
- data/lib/mongoid/slug/criteria.rb +90 -0
- data/lib/mongoid/slug/version.rb +1 -1
- data/lib/mongoid_slug.rb +3 -0
- data/spec/models/author.rb +5 -5
- data/spec/models/book.rb +3 -2
- data/spec/models/caption.rb +4 -4
- data/spec/models/friend.rb +2 -1
- data/spec/models/integer_id.rb +9 -0
- data/spec/models/person.rb +2 -2
- data/spec/models/string_id.rb +9 -0
- data/spec/models/without_slug.rb +5 -0
- data/spec/mongoid/slug_spec.rb +349 -116
- data/spec/spec_helper.rb +17 -9
- metadata +58 -37
@@ -0,0 +1,90 @@
|
|
1
|
+
# require 'moped/bson/object_id'
|
2
|
+
|
3
|
+
module Mongoid
|
4
|
+
module Slug
|
5
|
+
class Criteria < Mongoid::Criteria
|
6
|
+
# Find the matchind document(s) in the criteria for the provided ids or slugs.
|
7
|
+
#
|
8
|
+
# If the document _ids are of the type Moped::BSON::ObjectId, and all the supplied parameters are
|
9
|
+
# convertible to Moped::BSON::ObjectId (via Moped::BSON::ObjectId#from_string), finding will be
|
10
|
+
# performed via _ids.
|
11
|
+
#
|
12
|
+
# If the document has any other type of _id field, and all the supplied parameters are of the same
|
13
|
+
# type, finding will be performed via _ids.
|
14
|
+
#
|
15
|
+
# Otherwise finding will be performed via slugs.
|
16
|
+
#
|
17
|
+
# @example Find by an id.
|
18
|
+
# criteria.find(Moped::BSON::ObjectId.new)
|
19
|
+
#
|
20
|
+
# @example Find by multiple ids.
|
21
|
+
# criteria.find([ Moped::BSON::ObjectId.new, Moped::BSON::ObjectId.new ])
|
22
|
+
#
|
23
|
+
# @example Find by a slug.
|
24
|
+
# criteria.find('some-slug')
|
25
|
+
#
|
26
|
+
# @example Find by multiple slugs.
|
27
|
+
# criteria.find([ 'some-slug', 'some-other-slug' ])
|
28
|
+
#
|
29
|
+
# @param [ Array<Object> ] args The ids or slugs to search for.
|
30
|
+
#
|
31
|
+
# @return [ Array<Document>, Document ] The matching document(s).
|
32
|
+
def find(*args)
|
33
|
+
look_like_slugs?(args.__find_args__) ? find_by_slug!(*args) : super
|
34
|
+
end
|
35
|
+
|
36
|
+
# Find the matchind document(s) in the criteria for the provided slugs.
|
37
|
+
#
|
38
|
+
# @example Find by a slug.
|
39
|
+
# criteria.find('some-slug')
|
40
|
+
#
|
41
|
+
# @example Find by multiple slugs.
|
42
|
+
# criteria.find([ 'some-slug', 'some-other-slug' ])
|
43
|
+
#
|
44
|
+
# @param [ Array<Object> ] args The slugs to search for.
|
45
|
+
#
|
46
|
+
# @return [ Array<Document>, Document ] The matching document(s).
|
47
|
+
def find_by_slug!(*args)
|
48
|
+
slugs = args.__find_args__
|
49
|
+
raise_invalid if slugs.any?(&:nil?)
|
50
|
+
for_slugs(slugs).execute_or_raise_for_slugs(slugs, args.multi_arged?)
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
# True if all supplied args look like slugs. Will only attempt to type cast for Moped::BSON::ObjectId.
|
55
|
+
# Thus '123' will be interpreted as a slug even if the _id is an Integer field, etc.
|
56
|
+
def look_like_slugs?(args)
|
57
|
+
if args.all? { |id| id.is_a?(String) }
|
58
|
+
id_type = @klass.fields['_id'].type
|
59
|
+
case
|
60
|
+
when id_type == Moped::BSON::ObjectId
|
61
|
+
args.any? { |id| !Moped::BSON::ObjectId.legal?(id) }
|
62
|
+
else args.any? { |id| id.class != id_type }
|
63
|
+
end
|
64
|
+
else
|
65
|
+
false
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
protected
|
70
|
+
|
71
|
+
def for_slugs(slugs)
|
72
|
+
where({ _slugs: { '$in' => slugs } }).limit(slugs.length)
|
73
|
+
end
|
74
|
+
|
75
|
+
def execute_or_raise_for_slugs(slugs, multi)
|
76
|
+
result = uniq
|
77
|
+
check_for_missing_documents_for_slugs!(result, slugs)
|
78
|
+
multi ? result : result.first
|
79
|
+
end
|
80
|
+
|
81
|
+
def check_for_missing_documents_for_slugs!(result, slugs)
|
82
|
+
missing_slugs = slugs - result.map(&:slugs).flatten
|
83
|
+
|
84
|
+
if !missing_slugs.blank? && Mongoid.raise_not_found_error
|
85
|
+
raise Errors::DocumentNotFound.new(klass, slugs, missing_slugs)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
data/lib/mongoid/slug/version.rb
CHANGED
data/lib/mongoid_slug.rb
CHANGED
data/spec/models/author.rb
CHANGED
@@ -3,9 +3,9 @@ class Author
|
|
3
3
|
include Mongoid::Slug
|
4
4
|
field :first_name
|
5
5
|
field :last_name
|
6
|
-
slug :first_name, :last_name, :scope => :book
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
6
|
+
slug :first_name, :last_name, :scope => :book
|
7
|
+
belongs_to :book
|
8
|
+
has_many :characters,
|
9
|
+
:class_name => 'Person',
|
10
|
+
:foreign_key => :author_id
|
11
11
|
end
|
data/spec/models/book.rb
CHANGED
@@ -2,9 +2,10 @@ class Book
|
|
2
2
|
include Mongoid::Document
|
3
3
|
include Mongoid::Slug
|
4
4
|
field :title
|
5
|
-
|
5
|
+
|
6
|
+
slug :title, :history => true
|
6
7
|
embeds_many :subjects
|
7
|
-
|
8
|
+
has_many :authors
|
8
9
|
end
|
9
10
|
|
10
11
|
class ComicBook < Book
|
data/spec/models/caption.rb
CHANGED
@@ -1,17 +1,17 @@
|
|
1
1
|
class Caption
|
2
2
|
include Mongoid::Document
|
3
3
|
include Mongoid::Slug
|
4
|
-
field :
|
4
|
+
field :my_identity, :type => String
|
5
5
|
field :title
|
6
6
|
field :medium
|
7
7
|
|
8
8
|
# A fairly complex scenario, where we want to create a slug out of an
|
9
|
-
#
|
9
|
+
# my_identity field, which comprises name of artist and some more bibliographic
|
10
10
|
# info in parantheses, and the title of the work.
|
11
11
|
#
|
12
12
|
# We are only interested in the name of the artist so we remove the
|
13
13
|
# paranthesized details.
|
14
|
-
slug :
|
15
|
-
|
14
|
+
slug :my_identity, :title do |cur_object|
|
15
|
+
cur_object.slug_builder.gsub(/\s*\([^)]+\)/, '').to_url
|
16
16
|
end
|
17
17
|
end
|
data/spec/models/friend.rb
CHANGED
data/spec/models/person.rb
CHANGED
@@ -2,7 +2,7 @@ class Person
|
|
2
2
|
include Mongoid::Document
|
3
3
|
include Mongoid::Slug
|
4
4
|
field :name
|
5
|
-
slug :name, :
|
5
|
+
slug :name, :permanent => true, :scope => :author
|
6
6
|
embeds_many :relationships
|
7
|
-
|
7
|
+
belongs_to :author, :inverse_of => :characters
|
8
8
|
end
|
data/spec/mongoid/slug_spec.rb
CHANGED
@@ -25,6 +25,11 @@ module Mongoid
|
|
25
25
|
}
|
26
26
|
end
|
27
27
|
|
28
|
+
it "does not allow a Moped::BSON::ObjectId as use for a slug" do
|
29
|
+
bad = Book.create(:title => "4ea0389f0364313d79104fb3")
|
30
|
+
bad.slugs.should_not include("4ea0389f0364313d79104fb3")
|
31
|
+
end
|
32
|
+
|
28
33
|
it "does not update slug if slugged fields have not changed" do
|
29
34
|
book.save
|
30
35
|
book.to_param.should eql "a-thousand-plateaus"
|
@@ -36,8 +41,26 @@ module Mongoid
|
|
36
41
|
book.to_param.should eql "a-thousand-plateaus"
|
37
42
|
end
|
38
43
|
|
39
|
-
|
40
|
-
|
44
|
+
context "using find" do
|
45
|
+
it "finds by id as string" do
|
46
|
+
Book.find(book.id.to_s).should eql book
|
47
|
+
end
|
48
|
+
|
49
|
+
it "finds by id as array of strings" do
|
50
|
+
Book.find([book.id.to_s]).should eql [book]
|
51
|
+
end
|
52
|
+
|
53
|
+
it "finds by id as Moped::BSON::ObjectId" do
|
54
|
+
Book.find(book.id).should eql book
|
55
|
+
end
|
56
|
+
|
57
|
+
it "finds by id as an array of Moped::BSON::ObjectIds" do
|
58
|
+
Book.find([book.id]).should eql [book]
|
59
|
+
end
|
60
|
+
|
61
|
+
it "returns an empty array if given an empty array" do
|
62
|
+
Book.find([]).should eql []
|
63
|
+
end
|
41
64
|
end
|
42
65
|
end
|
43
66
|
|
@@ -61,6 +84,11 @@ module Mongoid
|
|
61
84
|
dup.to_param.should eql "psychoanalysis-1"
|
62
85
|
end
|
63
86
|
|
87
|
+
it "does not allow a Moped::BSON::ObjectId as use for a slug" do
|
88
|
+
bad = book.subjects.create(:name => "4ea0389f0364313d79104fb3")
|
89
|
+
bad.slugs.should_not eql "4ea0389f0364313d79104fb3"
|
90
|
+
end
|
91
|
+
|
64
92
|
it "does not update slug if slugged fields have not changed" do
|
65
93
|
subject.save
|
66
94
|
subject.to_param.should eql "psychoanalysis"
|
@@ -71,9 +99,28 @@ module Mongoid
|
|
71
99
|
subject.to_param.should eql "psychoanalysis"
|
72
100
|
end
|
73
101
|
|
74
|
-
|
75
|
-
|
102
|
+
context "using find" do
|
103
|
+
it "finds by id as string" do
|
104
|
+
book.subjects.find(subject.id.to_s).should eql subject
|
105
|
+
end
|
106
|
+
|
107
|
+
it "finds by id as array of strings" do
|
108
|
+
book.subjects.find([subject.id.to_s]).should eql [subject]
|
109
|
+
end
|
110
|
+
|
111
|
+
it "finds by id as Moped::BSON::ObjectId" do
|
112
|
+
book.subjects.find(subject.id).should eql subject
|
113
|
+
end
|
114
|
+
|
115
|
+
it "finds by id as an array of Moped::BSON::ObjectIds" do
|
116
|
+
book.subjects.find([subject.id]).should eql [subject]
|
117
|
+
end
|
118
|
+
|
119
|
+
it "returns an empty array if given an empty array" do
|
120
|
+
book.subjects.find([]).should eql []
|
121
|
+
end
|
76
122
|
end
|
123
|
+
|
77
124
|
end
|
78
125
|
|
79
126
|
context "when the object is embedded in another embedded object" do
|
@@ -104,6 +151,11 @@ module Mongoid
|
|
104
151
|
dup.to_param.should eql "jane-smith-1"
|
105
152
|
end
|
106
153
|
|
154
|
+
it "does not allow a Moped::BSON::ObjectId as use for a slug" do
|
155
|
+
bad = relationship.partners.create(:name => "4ea0389f0364313d79104fb3")
|
156
|
+
bad.slugs.should_not eql "4ea0389f0364313d79104fb3"
|
157
|
+
end
|
158
|
+
|
107
159
|
it "does not update slug if slugged fields have not changed" do
|
108
160
|
partner.save
|
109
161
|
partner.to_param.should eql "jane-smith"
|
@@ -120,9 +172,28 @@ module Mongoid
|
|
120
172
|
lover.to_param.should eql partner.to_param
|
121
173
|
end
|
122
174
|
|
123
|
-
|
124
|
-
|
175
|
+
context "using find" do
|
176
|
+
it "finds by id as string" do
|
177
|
+
relationship.partners.find(partner.id.to_s).should eql partner
|
178
|
+
end
|
179
|
+
|
180
|
+
it "finds by id as array of strings" do
|
181
|
+
relationship.partners.find([partner.id.to_s]).should eql [partner]
|
182
|
+
end
|
183
|
+
|
184
|
+
it "finds by id as Moped::BSON::ObjectId" do
|
185
|
+
relationship.partners.find(partner.id).should eql partner
|
186
|
+
end
|
187
|
+
|
188
|
+
it "finds by id as an array of Moped::BSON::ObjectIds" do
|
189
|
+
relationship.partners.find([partner.id]).should eql [partner]
|
190
|
+
end
|
191
|
+
|
192
|
+
it "returns an empty array if given an empty array" do
|
193
|
+
relationship.partners.find([]).should eql []
|
194
|
+
end
|
125
195
|
end
|
196
|
+
|
126
197
|
end
|
127
198
|
|
128
199
|
context "when the slug is composed of multiple fields" do
|
@@ -157,15 +228,17 @@ module Mongoid
|
|
157
228
|
dup2.to_param.should eql "gilles-deleuze-2"
|
158
229
|
end
|
159
230
|
|
231
|
+
it "does not allow a Moped::BSON::ObjectId as use for a slug" do
|
232
|
+
bad = Author.create(:first_name => "4ea0389f0364",
|
233
|
+
:last_name => "313d79104fb3")
|
234
|
+
bad.to_param.should_not eql "4ea0389f0364313d79104fb3"
|
235
|
+
end
|
236
|
+
|
160
237
|
it "does not update slug if slugged fields have changed but generated slug is identical" do
|
161
238
|
author.last_name = "DELEUZE"
|
162
239
|
author.save
|
163
240
|
author.to_param.should eql "gilles-deleuze"
|
164
241
|
end
|
165
|
-
|
166
|
-
it "finds by slug" do
|
167
|
-
Author.find_by_slug("gilles-deleuze").should eql author
|
168
|
-
end
|
169
242
|
end
|
170
243
|
|
171
244
|
context "when :as is passed as an argument" do
|
@@ -174,12 +247,20 @@ module Mongoid
|
|
174
247
|
end
|
175
248
|
|
176
249
|
it "sets an alternative slug field name" do
|
177
|
-
person.should respond_to(:
|
178
|
-
person.
|
250
|
+
person.should respond_to(:_slugs)
|
251
|
+
person.slugs.should eql ["john-doe"]
|
252
|
+
end
|
253
|
+
|
254
|
+
it 'defines #slug' do
|
255
|
+
person.should respond_to :slugs
|
179
256
|
end
|
180
257
|
|
181
|
-
it
|
182
|
-
|
258
|
+
it 'defines #slug_changed?' do
|
259
|
+
person.should respond_to :_slugs_changed?
|
260
|
+
end
|
261
|
+
|
262
|
+
it 'defines #slug_was' do
|
263
|
+
person.should respond_to :_slugs_was
|
183
264
|
end
|
184
265
|
end
|
185
266
|
|
@@ -206,15 +287,7 @@ module Mongoid
|
|
206
287
|
end
|
207
288
|
|
208
289
|
it "saves the old slug in the owner's history" do
|
209
|
-
book.
|
210
|
-
end
|
211
|
-
|
212
|
-
it "returns the document for the old slug" do
|
213
|
-
Book.find_by_slug("book-title").should == book
|
214
|
-
end
|
215
|
-
|
216
|
-
it "returns the document for the new slug" do
|
217
|
-
Book.find_by_slug("other-book-title").should == book
|
290
|
+
book.slugs.should include("book-title")
|
218
291
|
end
|
219
292
|
|
220
293
|
it "generates a unique slug by appending a counter to duplicate text" do
|
@@ -222,10 +295,15 @@ module Mongoid
|
|
222
295
|
dup.to_param.should eql "book-title-1"
|
223
296
|
end
|
224
297
|
|
298
|
+
it "does not allow a Moped::BSON::ObjectId as use for a slug" do
|
299
|
+
bad = Book.create(:title => "4ea0389f0364313d79104fb3")
|
300
|
+
bad.to_param.should_not eql "4ea0389f0364313d79104fb3"
|
301
|
+
end
|
302
|
+
|
225
303
|
it "ensures no duplicate values are stored in history" do
|
226
304
|
book.update_attributes :title => 'Book Title'
|
227
305
|
book.update_attributes :title => 'Foo'
|
228
|
-
book.
|
306
|
+
book.slugs.find_all { |slug| slug == 'book-title' }.size.should eql 1
|
229
307
|
end
|
230
308
|
end
|
231
309
|
|
@@ -250,6 +328,12 @@ module Mongoid
|
|
250
328
|
dup.to_param.should eql "gilles-deleuze-1"
|
251
329
|
end
|
252
330
|
|
331
|
+
it "does not allow a Moped::BSON::ObjectId as use for a slug" do
|
332
|
+
bad = book.authors.create(:first_name => "4ea0389f0364",
|
333
|
+
:last_name => "313d79104fb3")
|
334
|
+
bad.to_param.should_not eql "4ea0389f0364313d79104fb3"
|
335
|
+
end
|
336
|
+
|
253
337
|
context "with an irregular association name" do
|
254
338
|
let(:character) do
|
255
339
|
# well we've got to make up something... :-)
|
@@ -285,11 +369,11 @@ module Mongoid
|
|
285
369
|
end
|
286
370
|
|
287
371
|
it "allows using the slug" do
|
288
|
-
subject2.
|
372
|
+
subject2.slugs.should include("a-subject")
|
289
373
|
end
|
290
374
|
|
291
375
|
it "removes the slug from the old owner's history" do
|
292
|
-
subject1.
|
376
|
+
subject1.slugs.should_not include("a-subject")
|
293
377
|
end
|
294
378
|
end
|
295
379
|
end
|
@@ -309,11 +393,17 @@ module Mongoid
|
|
309
393
|
dup = Magazine.create(:title => "Big Weekly", :publisher_id => "abc123")
|
310
394
|
dup.to_param.should eql "big-weekly-1"
|
311
395
|
end
|
396
|
+
|
397
|
+
it "does not allow a Moped::BSON::ObjectId as use for a slug" do
|
398
|
+
bad = Magazine.create(:title => "4ea0389f0364313d79104fb3", :publisher_id => "abc123")
|
399
|
+
bad.to_param.should_not eql "4ea0389f0364313d79104fb3"
|
400
|
+
end
|
401
|
+
|
312
402
|
end
|
313
403
|
|
314
404
|
context "when #slug is given a block" do
|
315
405
|
let(:caption) do
|
316
|
-
Caption.create(:
|
406
|
+
Caption.create(:my_identity => "Edward Hopper (American, 1882-1967)",
|
317
407
|
:title => "Soir Bleu, 1914",
|
318
408
|
:medium => "Oil on Canvas")
|
319
409
|
end
|
@@ -329,14 +419,10 @@ module Mongoid
|
|
329
419
|
end
|
330
420
|
|
331
421
|
it "does not change slug if slugged fields have changed but generated slug is identical" do
|
332
|
-
caption.
|
422
|
+
caption.my_identity = "Edward Hopper"
|
333
423
|
caption.save
|
334
424
|
caption.to_param.should eql "edward-hopper-soir-bleu-1914"
|
335
425
|
end
|
336
|
-
|
337
|
-
it "finds by slug" do
|
338
|
-
Caption.find_by_slug(caption.to_param).should eql caption
|
339
|
-
end
|
340
426
|
end
|
341
427
|
|
342
428
|
context "when slugged field contains non-ASCII characters" do
|
@@ -365,59 +451,54 @@ module Mongoid
|
|
365
451
|
end
|
366
452
|
end
|
367
453
|
|
368
|
-
context "when
|
454
|
+
context "when indexes are created" do
|
369
455
|
before do
|
370
|
-
|
371
|
-
Author.collection.drop_indexes
|
372
|
-
end
|
373
|
-
|
374
|
-
it "defines an index on the slug in top-level objects" do
|
456
|
+
Author.create_indexes
|
375
457
|
Book.create_indexes
|
376
|
-
Book.collection.index_information.should have_key "slug_1"
|
377
458
|
end
|
378
459
|
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
Author.index_information["slug_1"]["unique"].should be_false
|
383
|
-
end
|
460
|
+
after do
|
461
|
+
Author.remove_indexes
|
462
|
+
Book.remove_indexes
|
384
463
|
end
|
385
464
|
|
386
465
|
context "when slug is not scoped by a reference association" do
|
466
|
+
it "defines an index on the slug" do
|
467
|
+
Book.index_options.should have_key( :_slugs => 1 )
|
468
|
+
end
|
469
|
+
|
387
470
|
it "defines a unique index" do
|
388
|
-
Book.
|
389
|
-
Book.index_information["slug_1"]["unique"].should be_true
|
471
|
+
Book.index_options[ :_slugs => 1 ][:unique].should be_true
|
390
472
|
end
|
391
473
|
end
|
392
|
-
end
|
393
474
|
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
475
|
+
context "when slug is scoped by a reference association" do
|
476
|
+
it "does not define an index on the slug" do
|
477
|
+
Author.index_options.should_not have_key(:_slugs => 1 )
|
478
|
+
end
|
398
479
|
end
|
399
480
|
end
|
400
|
-
|
481
|
+
|
401
482
|
context "when :reserve is passed" do
|
402
483
|
it "does not use the the reserved slugs" do
|
403
484
|
friend1 = Friend.create(:name => "foo")
|
404
|
-
friend1.
|
405
|
-
friend1.
|
485
|
+
friend1.slugs.should_not eql("foo")
|
486
|
+
friend1.slugs.should include("foo-1")
|
406
487
|
|
407
488
|
friend2 = Friend.create(:name => "bar")
|
408
|
-
friend2.
|
409
|
-
friend2.
|
489
|
+
friend2.slugs.should_not eql("bar")
|
490
|
+
friend2.slugs.should include("bar-1")
|
410
491
|
|
411
492
|
friend3 = Friend.create(:name => "en")
|
412
|
-
friend3.
|
413
|
-
friend3.
|
493
|
+
friend3.slugs.should_not eql("en")
|
494
|
+
friend3.slugs.should include("en-1")
|
414
495
|
end
|
415
496
|
|
416
497
|
it "should start with concatenation -1" do
|
417
498
|
friend1 = Friend.create(:name => "foo")
|
418
|
-
friend1.
|
499
|
+
friend1.slugs.should include("foo-1")
|
419
500
|
friend2 = Friend.create(:name => "foo")
|
420
|
-
friend2.
|
501
|
+
friend2.slugs.should include("foo-2")
|
421
502
|
end
|
422
503
|
end
|
423
504
|
|
@@ -425,112 +506,264 @@ module Mongoid
|
|
425
506
|
it "scopes by the superclass" do
|
426
507
|
book = Book.create(:title => "Anti Oedipus")
|
427
508
|
comic_book = ComicBook.create(:title => "Anti Oedipus")
|
428
|
-
comic_book.
|
509
|
+
comic_book.slugs.should_not eql(book.slugs)
|
429
510
|
end
|
430
511
|
end
|
431
512
|
|
432
513
|
context "when slug defined on alias of field" do
|
433
514
|
it "should use accessor, not alias" do
|
434
515
|
pseudonim = Alias.create(:author_name => "Max Stirner")
|
435
|
-
pseudonim.
|
516
|
+
pseudonim.slugs.should include("max-stirner")
|
436
517
|
end
|
437
518
|
end
|
438
519
|
|
439
|
-
describe ".
|
440
|
-
let!(:
|
441
|
-
|
442
|
-
|
443
|
-
|
520
|
+
describe ".find" do
|
521
|
+
let!(:book) { Book.create(:title => "A Working Title").tap { |d| d.update_attribute(:title, "A Thousand Plateaus") } }
|
522
|
+
let!(:book2) { Book.create(:title => "Difference and Repetition") }
|
523
|
+
let!(:friend) { Friend.create(:name => "Jim Bob") }
|
524
|
+
let!(:friend2) { Friend.create(:name => friend.id.to_s) }
|
525
|
+
let!(:integer_id) { IntegerId.new(:name => "I have integer ids").tap { |d| d.id = 123; d.save } }
|
526
|
+
let!(:integer_id2) { IntegerId.new(:name => integer_id.id.to_s).tap { |d| d.id = 456; d.save } }
|
527
|
+
let!(:string_id) { StringId.new(:name => "I have string ids").tap { |d| d.id = 'abc'; d.save } }
|
528
|
+
let!(:string_id2) { StringId.new(:name => string_id.id.to_s).tap { |d| d.id = 'def'; d.save } }
|
529
|
+
let!(:subject) { Subject.create(:title => "A Subject", :book => book) }
|
530
|
+
let!(:subject2) { Subject.create(:title => "A Subject", :book => book2) }
|
531
|
+
let!(:without_slug) { WithoutSlug.new().tap { |d| d.id = 456; d.save } }
|
532
|
+
|
533
|
+
context "when the model does not use mongoid slugs" do
|
534
|
+
it "should not use mongoid slug's custom find methods" do
|
535
|
+
Mongoid::Slug::Criteria.any_instance.should_not_receive(:find)
|
536
|
+
WithoutSlug.find(without_slug.id.to_s).should == without_slug
|
537
|
+
end
|
444
538
|
end
|
445
539
|
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
540
|
+
context "using slugs" do
|
541
|
+
context "(single)" do
|
542
|
+
context "and a document is found" do
|
543
|
+
it "returns the document as an object" do
|
544
|
+
Book.find(book.slugs.first).should == book
|
545
|
+
end
|
546
|
+
end
|
547
|
+
|
548
|
+
context "but no document is found" do
|
549
|
+
it "raises a Mongoid::Errors::DocumentNotFound error" do
|
550
|
+
lambda {
|
551
|
+
Book.find("Anti Oedipus")
|
552
|
+
}.should raise_error(Mongoid::Errors::DocumentNotFound)
|
553
|
+
end
|
554
|
+
end
|
555
|
+
end
|
556
|
+
|
557
|
+
context "(multiple)" do
|
558
|
+
context "and all documents are found" do
|
559
|
+
it "returns the documents as an array without duplication" do
|
560
|
+
Book.find(book.slugs + book2.slugs).should =~ [book, book2]
|
561
|
+
end
|
562
|
+
end
|
563
|
+
|
564
|
+
context "but not all documents are found" do
|
565
|
+
it "raises a Mongoid::Errors::DocumentNotFound error" do
|
566
|
+
lambda {
|
567
|
+
Book.find(book.slugs + ['something-nonexistent'])
|
568
|
+
}.should raise_error(Mongoid::Errors::DocumentNotFound)
|
569
|
+
end
|
570
|
+
end
|
571
|
+
end
|
572
|
+
|
573
|
+
context "when no documents match" do
|
574
|
+
it "raises a Mongoid::Errors::DocumentNotFound error" do
|
575
|
+
lambda {
|
576
|
+
Book.find("Anti Oedipus")
|
577
|
+
}.should raise_error(Mongoid::Errors::DocumentNotFound)
|
578
|
+
end
|
579
|
+
end
|
580
|
+
|
581
|
+
context "when ids are BSON::ObjectIds and the supplied argument looks like a BSON::ObjectId" do
|
582
|
+
it "it should find based on ids not slugs" do # i.e. it should type cast the argument
|
583
|
+
Friend.find(friend.id.to_s).should == friend
|
584
|
+
end
|
585
|
+
end
|
586
|
+
|
587
|
+
context "when ids are Strings" do
|
588
|
+
it "it should find based on ids not slugs" do # i.e. string ids should take precedence over string slugs
|
589
|
+
StringId.find(string_id.id.to_s).should == string_id
|
590
|
+
end
|
591
|
+
end
|
592
|
+
|
593
|
+
context "when ids are Integers and the supplied arguments looks like an Integer" do
|
594
|
+
it "it should find based on slugs not ids" do # i.e. it should not type cast the argument
|
595
|
+
IntegerId.find(integer_id.id.to_s).should == integer_id2
|
596
|
+
end
|
597
|
+
end
|
450
598
|
|
451
|
-
|
452
|
-
|
599
|
+
context "models that does not use slugs, should find using the original find" do
|
600
|
+
it "it should find based on ids" do # i.e. it should not type cast the argument
|
601
|
+
WithoutSlug.find(without_slug.id.to_s).should == without_slug
|
602
|
+
end
|
603
|
+
end
|
453
604
|
|
454
|
-
|
455
|
-
|
605
|
+
context "when scoped" do
|
606
|
+
context "and a document is found" do
|
607
|
+
it "returns the document as an object" do
|
608
|
+
book.subjects.find(subject.slugs.first).should == subject
|
609
|
+
book2.subjects.find(subject.slugs.first).should == subject2
|
610
|
+
end
|
611
|
+
end
|
612
|
+
|
613
|
+
context "but no document is found" do
|
614
|
+
it "raises a Mongoid::Errors::DocumentNotFound error" do
|
615
|
+
lambda {
|
616
|
+
book.subjects.find('Another Subject')
|
617
|
+
}.should raise_error(Mongoid::Errors::DocumentNotFound)
|
618
|
+
end
|
619
|
+
end
|
620
|
+
end
|
456
621
|
end
|
457
622
|
|
458
|
-
|
459
|
-
|
623
|
+
context "using ids" do
|
624
|
+
it "raises a Mongoid::Errors::DocumentNotFound error if no document is found" do
|
625
|
+
lambda {
|
626
|
+
Book.find(friend.id)
|
627
|
+
}.should raise_error(Mongoid::Errors::DocumentNotFound)
|
628
|
+
end
|
629
|
+
|
630
|
+
context "given a single document" do
|
631
|
+
it "returns the document" do
|
632
|
+
Friend.find(friend.id).should == friend
|
633
|
+
end
|
634
|
+
end
|
635
|
+
|
636
|
+
context "given multiple documents" do
|
637
|
+
it "returns the documents" do
|
638
|
+
Book.find([book.id, book2.id]).should =~ [book, book2]
|
639
|
+
end
|
640
|
+
end
|
460
641
|
end
|
461
642
|
end
|
462
643
|
|
463
644
|
describe ".find_by_slug!" do
|
464
|
-
let!(:book) { Book.create(:title => "A Thousand Plateaus") }
|
645
|
+
let!(:book) { Book.create(:title => "A Working Title").tap { |d| d.update_attribute(:title, "A Thousand Plateaus") } }
|
646
|
+
let!(:book2) { Book.create(:title => "Difference and Repetition") }
|
647
|
+
let!(:friend) { Friend.create(:name => "Jim Bob") }
|
648
|
+
let!(:friend2) { Friend.create(:name => friend.id.to_s) }
|
649
|
+
let!(:integer_id) { IntegerId.new(:name => "I have integer ids").tap { |d| d.id = 123; d.save } }
|
650
|
+
let!(:integer_id2) { IntegerId.new(:name => integer_id.id.to_s).tap { |d| d.id = 456; d.save } }
|
651
|
+
let!(:string_id) { StringId.new(:name => "I have string ids").tap { |d| d.id = 'abc'; d.save } }
|
652
|
+
let!(:string_id2) { StringId.new(:name => string_id.id.to_s).tap { |d| d.id = 'def'; d.save } }
|
653
|
+
let!(:subject) { Subject.create(:title => "A Subject", :book => book) }
|
654
|
+
let!(:subject2) { Subject.create(:title => "A Subject", :book => book2) }
|
655
|
+
|
656
|
+
context "(single)" do
|
657
|
+
context "and a document is found" do
|
658
|
+
it "returns the document as an object" do
|
659
|
+
Book.find_by_slug!(book.slugs.first).should == book
|
660
|
+
end
|
661
|
+
end
|
465
662
|
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
663
|
+
context "but no document is found" do
|
664
|
+
it "raises a Mongoid::Errors::DocumentNotFound error" do
|
665
|
+
lambda {
|
666
|
+
Book.find_by_slug!("Anti Oedipus")
|
667
|
+
}.should raise_error(Mongoid::Errors::DocumentNotFound)
|
668
|
+
end
|
669
|
+
end
|
470
670
|
end
|
471
671
|
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
672
|
+
context "(multiple)" do
|
673
|
+
context "and all documents are found" do
|
674
|
+
it "returns the documents as an array without duplication" do
|
675
|
+
Book.find_by_slug!(book.slugs + book2.slugs).should =~ [book, book2]
|
676
|
+
end
|
677
|
+
end
|
476
678
|
|
477
|
-
|
478
|
-
|
479
|
-
|
679
|
+
context "but not all documents are found" do
|
680
|
+
it "raises a Mongoid::Errors::DocumentNotFound error" do
|
681
|
+
lambda {
|
682
|
+
Book.find_by_slug!(book.slugs + ['something-nonexistent'])
|
683
|
+
}.should raise_error(Mongoid::Errors::DocumentNotFound)
|
684
|
+
end
|
685
|
+
end
|
480
686
|
end
|
481
687
|
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
688
|
+
context "when scoped" do
|
689
|
+
context "and a document is found" do
|
690
|
+
it "returns the document as an object" do
|
691
|
+
book.subjects.find_by_slug!(subject.slugs.first).should == subject
|
692
|
+
book2.subjects.find_by_slug!(subject.slugs.first).should == subject2
|
693
|
+
end
|
694
|
+
end
|
695
|
+
|
696
|
+
context "but no document is found" do
|
697
|
+
it "raises a Mongoid::Errors::DocumentNotFound error" do
|
698
|
+
lambda {
|
699
|
+
book.subjects.find_by_slug!('Another Subject')
|
700
|
+
}.should raise_error(Mongoid::Errors::DocumentNotFound)
|
701
|
+
end
|
702
|
+
end
|
486
703
|
end
|
487
704
|
end
|
488
705
|
|
489
|
-
describe "
|
490
|
-
|
491
|
-
|
706
|
+
describe "#to_param" do
|
707
|
+
context "when called on an existing record with no slug" do
|
708
|
+
before do
|
709
|
+
Book.collection.insert(:title => "Proust and Signs")
|
710
|
+
end
|
711
|
+
|
712
|
+
it "generates the missing slug" do
|
713
|
+
book = Book.first
|
714
|
+
book.to_param
|
715
|
+
book.reload.slugs.should include("proust-and-signs")
|
716
|
+
end
|
492
717
|
end
|
718
|
+
end
|
493
719
|
|
494
|
-
|
720
|
+
describe "#_slugs_changed?" do
|
721
|
+
before do
|
495
722
|
Book.create(:title => "A Thousand Plateaus")
|
496
|
-
Book.find_unique_slug_for("A Thousand Plateaus").should eq("a-thousand-plateaus-1")
|
497
723
|
end
|
498
724
|
|
499
|
-
|
500
|
-
|
501
|
-
|
725
|
+
let(:book) { Book.first }
|
726
|
+
|
727
|
+
it "is initially unchanged" do
|
728
|
+
book._slugs_changed?.should be_false
|
729
|
+
end
|
730
|
+
|
731
|
+
it "tracks changes" do
|
732
|
+
book.slugs = ["Anti Oedipus"]
|
733
|
+
book._slugs_changed?.should be_true
|
502
734
|
end
|
503
735
|
end
|
504
736
|
|
505
|
-
describe "
|
506
|
-
let!(:
|
737
|
+
describe "when regular expression matches, but document does not" do
|
738
|
+
let!(:book_1) { Book.create(:title => "book-1") }
|
739
|
+
let!(:book_2) { Book.create(:title => "book") }
|
740
|
+
let!(:book_3) { Book.create(:title => "book") }
|
507
741
|
|
508
|
-
it "
|
509
|
-
|
742
|
+
it "book_2 should have the user supplied title without -1 after it" do
|
743
|
+
book_2.to_param.should eql "book"
|
510
744
|
end
|
511
745
|
|
512
|
-
it "
|
513
|
-
|
514
|
-
book.find_unique_slug_for("Anti Oedipus").should eq("anti-oedipus-1")
|
746
|
+
it "book_3 should have a generated slug" do
|
747
|
+
book_3.to_param.should eql "book-2"
|
515
748
|
end
|
516
749
|
end
|
517
750
|
|
518
751
|
context "when the slugged field is set manually" do
|
519
752
|
context "when it set to a non-empty string" do
|
520
753
|
it "respects the provided slug" do
|
521
|
-
book = Book.create(:title => "A Thousand Plateaus", :
|
754
|
+
book = Book.create(:title => "A Thousand Plateaus", :slugs => ["not-what-you-expected"])
|
522
755
|
book.to_param.should eql "not-what-you-expected"
|
523
756
|
end
|
524
|
-
|
757
|
+
|
525
758
|
it "ensures uniqueness" do
|
526
|
-
book1 = Book.create(:title => "A Thousand Plateaus", :
|
527
|
-
book2 = Book.create(:title => "A Thousand Plateaus", :
|
759
|
+
book1 = Book.create(:title => "A Thousand Plateaus", :slugs => ["not-what-you-expected"])
|
760
|
+
book2 = Book.create(:title => "A Thousand Plateaus", :slugs => ["not-what-you-expected"])
|
528
761
|
book2.to_param.should eql "not-what-you-expected-1"
|
529
762
|
end
|
530
|
-
|
763
|
+
|
531
764
|
it "updates the slug when a new one is passed in" do
|
532
|
-
book = Book.create(:title => "A Thousand Plateaus", :
|
533
|
-
book.
|
765
|
+
book = Book.create(:title => "A Thousand Plateaus", :slugs => ["not-what-you-expected"])
|
766
|
+
book.slugs = ["not-it-either"]
|
534
767
|
book.save
|
535
768
|
book.to_param.should eql "not-it-either"
|
536
769
|
end
|
@@ -538,7 +771,7 @@ module Mongoid
|
|
538
771
|
|
539
772
|
context "when it is set to an empty string" do
|
540
773
|
it "generate a new one" do
|
541
|
-
book = Book.create(:title => "A Thousand Plateaus"
|
774
|
+
book = Book.create(:title => "A Thousand Plateaus")
|
542
775
|
book.to_param.should eql "a-thousand-plateaus"
|
543
776
|
end
|
544
777
|
end
|