mongoid-slug 5.2.0 → 5.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +68 -67
- data/lib/mongoid/slug.rb +34 -22
- data/lib/mongoid/slug/criteria.rb +3 -5
- data/lib/mongoid/slug/index.rb +35 -8
- data/lib/mongoid/slug/unique_slug.rb +5 -5
- data/lib/mongoid/slug/version.rb +1 -1
- data/lib/tasks/mongoid_slug.rake +2 -1
- data/spec/models/author.rb +6 -2
- data/spec/models/author_polymorphic.rb +5 -1
- data/spec/models/paranoid_permanent.rb +3 -1
- data/spec/models/person.rb +5 -1
- data/spec/mongoid/index_spec.rb +3 -1
- data/spec/mongoid/paranoia_spec.rb +80 -12
- data/spec/mongoid/slug_spec.rb +197 -118
- data/spec/shared/indexes.rb +32 -18
- data/spec/spec_helper.rb +15 -1
- data/spec/tasks/mongoid_slug_rake_spec.rb +1 -1
- metadata +4 -32
data/lib/mongoid/slug/index.rb
CHANGED
@@ -6,15 +6,42 @@ module Mongoid
|
|
6
6
|
#
|
7
7
|
# @return [ Array(Hash, Hash) ] the indexable fields and index options.
|
8
8
|
def self.build_index(scope_key = nil, by_model_type = false)
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
fields
|
9
|
+
# The order of field keys is intentional.
|
10
|
+
# See: http://docs.mongodb.org/manual/core/index-compound/
|
11
|
+
fields = {}
|
12
|
+
fields[:_type] = 1 if by_model_type
|
13
|
+
fields[scope_key] = 1 if scope_key
|
14
|
+
fields[:_slugs] = 1
|
13
15
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
16
|
+
# By design, we use the unique index constraint when possible to enforce slug uniqueness.
|
17
|
+
# When migrating legacy data to Mongoid slug, the _slugs field may be null on many records,
|
18
|
+
# hence we set the sparse index option to ignore these from the unique index.
|
19
|
+
# See: http://docs.mongodb.org/manual/core/index-sparse/
|
20
|
+
#
|
21
|
+
# There are three edge cases where the index must not be unique:
|
22
|
+
#
|
23
|
+
# 1) Legacy tables with `scope_key`. The sparse indexes on compound keys (scope + _slugs) are
|
24
|
+
# whenever ANY of the key values are present (e.g. when scope is set and _slugs is unset),
|
25
|
+
# and collisions will occur when multiple records have the same scope but null slugs.
|
26
|
+
#
|
27
|
+
# 2) Single Table Inheritance (`by_model_type`). MongoDB creates indexes on the parent collection,
|
28
|
+
# irrespective of how STI is defined in Mongoid, i.e. ANY child index will be applied to EVERY child.
|
29
|
+
# This can cause collisions using various combinations of scopes.
|
30
|
+
#
|
31
|
+
# 3) Paranoid docs rely on sparse indexes to exclude paranoid-deleted records
|
32
|
+
# from the unique index constraint (i.e. when _slugs is unset.) However, when
|
33
|
+
# using compound keys (`by_model_type` or `scope_key`), paranoid-deleted records
|
34
|
+
# can become inadvertently indexed when _slugs is unset, causing duplicates. This
|
35
|
+
# is already covered by #1 and #2 above.
|
36
|
+
#
|
37
|
+
# In the future, MongoDB may implement partial indexes or improve sparse index behavior.
|
38
|
+
# See: https://jira.mongodb.org/browse/SERVER-785
|
39
|
+
# https://jira.mongodb.org/browse/SERVER-13780
|
40
|
+
# https://jira.mongodb.org/browse/SERVER-10403
|
41
|
+
options = {}
|
42
|
+
unless scope_key || by_model_type
|
43
|
+
options[:unique] = true
|
44
|
+
options[:sparse] = true
|
18
45
|
end
|
19
46
|
|
20
47
|
[fields, options]
|
@@ -62,8 +62,8 @@ module Mongoid
|
|
62
62
|
attr_reader :model, :_slug
|
63
63
|
|
64
64
|
def_delegators :@model, :slug_scope, :reflect_on_association, :read_attribute,
|
65
|
-
:check_against_id, :
|
66
|
-
:embedded?, :reflect_on_all_associations, :
|
65
|
+
:check_against_id, :slug_reserved_words, :slug_url_builder, :collection_name,
|
66
|
+
:embedded?, :reflect_on_all_associations, :slug_by_model_type, :slug_max_length
|
67
67
|
|
68
68
|
def initialize(model)
|
69
69
|
@model = model
|
@@ -80,7 +80,7 @@ module Mongoid
|
|
80
80
|
@_slug = if attempt
|
81
81
|
attempt.to_url
|
82
82
|
else
|
83
|
-
|
83
|
+
slug_url_builder.call(model)
|
84
84
|
end
|
85
85
|
|
86
86
|
@_slug = @_slug[0...slug_max_length] if slug_max_length
|
@@ -100,7 +100,7 @@ module Mongoid
|
|
100
100
|
where_hash[scope] = model.try(:read_attribute, scope)
|
101
101
|
end
|
102
102
|
|
103
|
-
if
|
103
|
+
if slug_by_model_type == true
|
104
104
|
where_hash[:_type] = model.try(:read_attribute, :_type)
|
105
105
|
end
|
106
106
|
|
@@ -110,7 +110,7 @@ module Mongoid
|
|
110
110
|
@state.include_slug unless model.class.look_like_slugs?([@_slug])
|
111
111
|
|
112
112
|
# make sure that the slug is not equal to a reserved word
|
113
|
-
@state.include_slug if
|
113
|
+
@state.include_slug if slug_reserved_words.any? { |word| word === @_slug }
|
114
114
|
|
115
115
|
# only look for a new unique slug if the existing slugs contains the current slug
|
116
116
|
# - e.g if the slug 'foo-2' is taken, but 'foo' is available, the user can use 'foo'.
|
data/lib/mongoid/slug/version.rb
CHANGED
data/lib/tasks/mongoid_slug.rake
CHANGED
@@ -3,6 +3,7 @@ namespace :mongoid_slug do
|
|
3
3
|
task set: :environment do |_, args|
|
4
4
|
::Rails.application.eager_load! if defined?(Rails)
|
5
5
|
klasses = Module.constants.find_all do |const|
|
6
|
+
next if const == :MissingSourceFile
|
6
7
|
const != const.upcase ? Mongoid::Slug > (Object.const_get const) : nil
|
7
8
|
end
|
8
9
|
klasses.map! { |klass| klass.to_s.constantize }
|
@@ -11,7 +12,7 @@ namespace :mongoid_slug do
|
|
11
12
|
klasses = (klasses.map(&:to_s) & models.map(&:classify)).map(&:constantize) if models.any?
|
12
13
|
klasses.each do |klass|
|
13
14
|
# set slug for objects having blank slug
|
14
|
-
klass.each { |object| object.set_slug! unless object.slugs? }
|
15
|
+
klass.each { |object| object.set_slug! unless object.slugs? && object.slugs.any? }
|
15
16
|
end
|
16
17
|
end
|
17
18
|
end
|
data/spec/models/author.rb
CHANGED
@@ -3,9 +3,13 @@ class Author
|
|
3
3
|
include Mongoid::Slug
|
4
4
|
field :first_name
|
5
5
|
field :last_name
|
6
|
-
|
7
|
-
|
6
|
+
if Mongoid::Compatibility::Version.mongoid6?
|
7
|
+
belongs_to :book, required: false
|
8
|
+
else
|
9
|
+
belongs_to :book
|
10
|
+
end
|
8
11
|
has_many :characters,
|
9
12
|
class_name: 'Person',
|
10
13
|
foreign_key: :author_id
|
14
|
+
slug :first_name, :last_name, scope: :book, history: false, max_length: 256
|
11
15
|
end
|
@@ -4,7 +4,11 @@ class AuthorPolymorphic
|
|
4
4
|
field :first_name
|
5
5
|
field :last_name
|
6
6
|
slug :first_name, :last_name, scope: :book_polymorphic
|
7
|
-
|
7
|
+
if Mongoid::Compatibility::Version.mongoid6?
|
8
|
+
belongs_to :book_polymorphic, required: false
|
9
|
+
else
|
10
|
+
belongs_to :book_polymorphic
|
11
|
+
end
|
8
12
|
has_many :characters,
|
9
13
|
class_name: 'Person',
|
10
14
|
foreign_key: :author_id
|
data/spec/models/person.rb
CHANGED
@@ -4,5 +4,9 @@ class Person
|
|
4
4
|
field :name
|
5
5
|
slug :name, permanent: true, scope: :author
|
6
6
|
embeds_many :relationships
|
7
|
-
|
7
|
+
if Mongoid::Compatibility::Version.mongoid6?
|
8
|
+
belongs_to :author, inverse_of: :characters, required: false
|
9
|
+
else
|
10
|
+
belongs_to :author, inverse_of: :characters
|
11
|
+
end
|
8
12
|
end
|
data/spec/mongoid/index_spec.rb
CHANGED
@@ -11,17 +11,19 @@ describe Mongoid::Slug::Index do
|
|
11
11
|
|
12
12
|
context 'when by_model_type is true' do
|
13
13
|
let(:by_model_type) { true }
|
14
|
+
|
14
15
|
it { is_expected.to eq [{ _slugs: 1, foo: 1, _type: 1 }, {}] }
|
15
16
|
end
|
16
17
|
|
17
18
|
context 'when by_model_type is false' do
|
18
|
-
it { is_expected.to eq [{ _slugs: 1, foo: 1 }, {
|
19
|
+
it { is_expected.to eq [{ _slugs: 1, foo: 1 }, {}] }
|
19
20
|
end
|
20
21
|
end
|
21
22
|
|
22
23
|
context 'when scope_key is not set' do
|
23
24
|
context 'when by_model_type is true' do
|
24
25
|
let(:by_model_type) { true }
|
26
|
+
|
25
27
|
it { is_expected.to eq [{ _slugs: 1, _type: 1 }, {}] }
|
26
28
|
end
|
27
29
|
|
@@ -2,10 +2,78 @@
|
|
2
2
|
require 'spec_helper'
|
3
3
|
|
4
4
|
describe 'Mongoid::Paranoia with Mongoid::Slug' do
|
5
|
+
let(:paranoid_doc) { ParanoidDocument.create!(title: 'slug') }
|
6
|
+
let(:paranoid_doc_2) { ParanoidDocument.create!(title: 'slug') }
|
7
|
+
let(:paranoid_perm) { ParanoidPermanent.create!(title: 'slug') }
|
8
|
+
let(:paranoid_perm_2) { ParanoidPermanent.create!(title: 'slug') }
|
9
|
+
let(:non_paranoid_doc) { Article.create!(title: 'slug') }
|
10
|
+
subject { paranoid_doc }
|
11
|
+
|
12
|
+
describe '.paranoid?' do
|
13
|
+
context 'when Mongoid::Paranoia is included' do
|
14
|
+
subject { paranoid_doc.class }
|
15
|
+
specify { expect(subject.is_paranoid_doc?).to be true }
|
16
|
+
end
|
17
|
+
|
18
|
+
context 'when Mongoid::Paranoia not included' do
|
19
|
+
subject { non_paranoid_doc.class }
|
20
|
+
specify { expect(subject.is_paranoid_doc?).to be false }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe '#paranoid_deleted?' do
|
25
|
+
context 'when Mongoid::Paranoia is included' do
|
26
|
+
context 'when not destroyed' do
|
27
|
+
specify { expect(subject.paranoid_deleted?).to be false }
|
28
|
+
end
|
29
|
+
|
30
|
+
context 'when destroyed' do
|
31
|
+
before { subject.destroy }
|
32
|
+
specify { expect(subject.paranoid_deleted?).to be true }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
context 'when Mongoid::Paranoia not included' do
|
37
|
+
subject { non_paranoid_doc }
|
38
|
+
specify { expect(subject.paranoid_deleted?).to be false }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe 'restore callbacks' do
|
43
|
+
context 'when Mongoid::Paranoia is included' do
|
44
|
+
subject { paranoid_doc.class }
|
45
|
+
it { is_expected.to respond_to(:before_restore) }
|
46
|
+
it { is_expected.to respond_to(:after_restore) }
|
47
|
+
end
|
48
|
+
|
49
|
+
context 'when Mongoid::Paranoia not included' do
|
50
|
+
it { is_expected.to_not respond_to(:before_restore) }
|
51
|
+
it { is_expected.to_not respond_to(:after_restore) }
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe 'index' do
|
56
|
+
context 'simple index' do
|
57
|
+
before { ParanoidDocument.create_indexes }
|
58
|
+
after { ParanoidDocument.remove_indexes }
|
59
|
+
subject { ParanoidDocument }
|
60
|
+
|
61
|
+
it_should_behave_like 'has an index', { _slugs: 1 }, unique: true, sparse: true
|
62
|
+
end
|
63
|
+
|
64
|
+
context 'compound index' do
|
65
|
+
before { ParanoidPermanent.create_indexes }
|
66
|
+
after { ParanoidPermanent.remove_indexes }
|
67
|
+
subject { ParanoidPermanent }
|
68
|
+
|
69
|
+
it_should_behave_like 'has an index', { foo: 1, _slugs: 1 }, unique: nil, sparse: nil
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
5
73
|
shared_examples_for 'paranoid slugs' do
|
6
74
|
context 'querying' do
|
7
75
|
it 'returns paranoid_doc for correct slug' do
|
8
|
-
expect(subject.class.find(subject.slug)).to eq
|
76
|
+
expect(subject.class.find(subject.slug)).to eq subject
|
9
77
|
end
|
10
78
|
end
|
11
79
|
|
@@ -27,8 +95,8 @@ describe 'Mongoid::Paranoia with Mongoid::Slug' do
|
|
27
95
|
end
|
28
96
|
|
29
97
|
it 'persists the removed slug' do
|
30
|
-
expect(subject.reload._slugs).to
|
31
|
-
expect(subject.reload.slug).to
|
98
|
+
expect(subject.reload._slugs).to be nil
|
99
|
+
expect(subject.reload.slug).to eq subject._id.to_s
|
32
100
|
end
|
33
101
|
|
34
102
|
it 'persists the removed slug in the database' do
|
@@ -67,7 +135,7 @@ describe 'Mongoid::Paranoia with Mongoid::Slug' do
|
|
67
135
|
it 'new documents should be able to use the slug of destroyed documents' do
|
68
136
|
expect(subject.slug).to eq 'slug'
|
69
137
|
subject.destroy
|
70
|
-
expect(subject.reload.slug).to
|
138
|
+
expect(subject.reload.slug).to eq subject._id.to_s
|
71
139
|
expect(other_doc.slug).to eq 'slug'
|
72
140
|
subject.restore
|
73
141
|
expect(subject.slug).to eq 'slug-1'
|
@@ -77,16 +145,16 @@ describe 'Mongoid::Paranoia with Mongoid::Slug' do
|
|
77
145
|
it 'should allow multiple documents to be destroyed without index conflict' do
|
78
146
|
expect(subject.slug).to eq 'slug'
|
79
147
|
subject.destroy
|
80
|
-
expect(subject.reload.slug).to
|
148
|
+
expect(subject.reload.slug).to eq subject._id.to_s
|
81
149
|
expect(other_doc.slug).to eq 'slug'
|
82
150
|
other_doc.destroy
|
83
|
-
expect(other_doc.reload.slug).to
|
151
|
+
expect(other_doc.reload.slug).to eq other_doc._id.to_s
|
84
152
|
end
|
85
153
|
end
|
86
154
|
end
|
87
155
|
|
88
156
|
[ParanoidDocument, DocumentParanoid].each do |paranoid_klass|
|
89
|
-
context
|
157
|
+
context paranoid_klass.to_s do
|
90
158
|
let(:paranoid_doc) { paranoid_klass.create!(title: 'slug') }
|
91
159
|
let(:non_paranoid_doc) { Article.create!(title: 'slug') }
|
92
160
|
|
@@ -95,30 +163,30 @@ describe 'Mongoid::Paranoia with Mongoid::Slug' do
|
|
95
163
|
describe '.paranoid?' do
|
96
164
|
context 'when Mongoid::Paranoia is included' do
|
97
165
|
subject { paranoid_doc.class }
|
98
|
-
its(:is_paranoid_doc?) {
|
166
|
+
its(:is_paranoid_doc?) { is_expected.to be_truthy }
|
99
167
|
end
|
100
168
|
|
101
169
|
context 'when Mongoid::Paranoia not included' do
|
102
170
|
subject { non_paranoid_doc.class }
|
103
|
-
its(:is_paranoid_doc?) {
|
171
|
+
its(:is_paranoid_doc?) { is_expected.to be_falsey }
|
104
172
|
end
|
105
173
|
end
|
106
174
|
|
107
175
|
describe '#paranoid_deleted?' do
|
108
176
|
context 'when Mongoid::Paranoia is included' do
|
109
177
|
context 'when not destroyed' do
|
110
|
-
its(:paranoid_deleted?) {
|
178
|
+
its(:paranoid_deleted?) { is_expected.to be_falsey }
|
111
179
|
end
|
112
180
|
|
113
181
|
context 'when destroyed' do
|
114
182
|
before { subject.destroy }
|
115
|
-
its(:paranoid_deleted?) {
|
183
|
+
its(:paranoid_deleted?) { is_expected.to be_truthy }
|
116
184
|
end
|
117
185
|
end
|
118
186
|
|
119
187
|
context 'when Mongoid::Paranoia not included' do
|
120
188
|
subject { non_paranoid_doc }
|
121
|
-
its(:paranoid_deleted?) {
|
189
|
+
its(:paranoid_deleted?) { is_expected.to be_falsey }
|
122
190
|
end
|
123
191
|
end
|
124
192
|
|
data/spec/mongoid/slug_spec.rb
CHANGED
@@ -7,8 +7,8 @@ module Mongoid
|
|
7
7
|
Book.create(title: 'A Thousand Plateaus')
|
8
8
|
end
|
9
9
|
|
10
|
-
context '
|
11
|
-
it 'slugs
|
10
|
+
context 'special cases' do
|
11
|
+
it 'slugs are not be generated from invalid documents' do
|
12
12
|
# this will fail now
|
13
13
|
x = IncorrectSlugPersistence.create!(name: 'test')
|
14
14
|
expect(x.slug).to eq('test')
|
@@ -23,9 +23,33 @@ module Mongoid
|
|
23
23
|
x.save!
|
24
24
|
end
|
25
25
|
|
26
|
-
it
|
26
|
+
it 'has no slugs for blank strings' do
|
27
27
|
book = Book.create!(title: '')
|
28
|
-
expect(book.reload.slugs).to
|
28
|
+
expect(book.reload.slugs).to be nil
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'has no slugs for dashes' do
|
32
|
+
book = Book.create!(title: '-')
|
33
|
+
expect(book.reload.slugs).to be nil
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'has no slugs for underscores' do
|
37
|
+
book = Book.create!(title: '_')
|
38
|
+
expect(book.reload.slugs).to be nil
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'has no slugs for nil strings' do
|
42
|
+
book = Book.create!
|
43
|
+
expect(book.reload.slugs).to be nil
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'works for multiple nils' do
|
47
|
+
expect do
|
48
|
+
2.times do
|
49
|
+
Book.create!
|
50
|
+
end
|
51
|
+
end.to_not raise_error # Mongo::Error::OperationFailure
|
52
|
+
expect(Book.all.map(&:slug)).to eq Book.all.map(&:id).map(&:to_s)
|
29
53
|
end
|
30
54
|
end
|
31
55
|
|
@@ -271,7 +295,8 @@ module Mongoid
|
|
271
295
|
let!(:author) do
|
272
296
|
Author.create(
|
273
297
|
first_name: 'Gilles',
|
274
|
-
last_name: 'Deleuze'
|
298
|
+
last_name: 'Deleuze'
|
299
|
+
)
|
275
300
|
end
|
276
301
|
|
277
302
|
it 'generates a slug' do
|
@@ -288,12 +313,14 @@ module Mongoid
|
|
288
313
|
it 'generates a unique slug by appending a counter to duplicate text' do
|
289
314
|
dup = Author.create(
|
290
315
|
first_name: author.first_name,
|
291
|
-
last_name: author.last_name
|
316
|
+
last_name: author.last_name
|
317
|
+
)
|
292
318
|
expect(dup.to_param).to eql 'gilles-deleuze-1'
|
293
319
|
|
294
320
|
dup2 = Author.create(
|
295
321
|
first_name: author.first_name,
|
296
|
-
last_name: author.last_name
|
322
|
+
last_name: author.last_name
|
323
|
+
)
|
297
324
|
|
298
325
|
dup.save
|
299
326
|
expect(dup2.to_param).to eql 'gilles-deleuze-2'
|
@@ -412,7 +439,8 @@ module Mongoid
|
|
412
439
|
it 'generates a unique slug by appending a counter to duplicate text' do
|
413
440
|
dup = book.authors.create(
|
414
441
|
first_name: author.first_name,
|
415
|
-
last_name: author.last_name
|
442
|
+
last_name: author.last_name
|
443
|
+
)
|
416
444
|
expect(dup.to_param).to eql 'gilles-deleuze-1'
|
417
445
|
end
|
418
446
|
|
@@ -488,6 +516,54 @@ module Mongoid
|
|
488
516
|
end
|
489
517
|
end
|
490
518
|
|
519
|
+
context 'when block is configured globally' do
|
520
|
+
before do
|
521
|
+
Mongoid::Slug.configure do |c|
|
522
|
+
c.slug do |cur_obj|
|
523
|
+
slug = cur_obj.slug_builder
|
524
|
+
"#{slug}-#{cur_obj.id}".to_url
|
525
|
+
end
|
526
|
+
end
|
527
|
+
end
|
528
|
+
|
529
|
+
after do
|
530
|
+
# Remove global configuration to avoid affect on
|
531
|
+
# other specs run after this spec
|
532
|
+
Mongoid::Slug.default_slug = nil
|
533
|
+
expect(Mongoid::Slug.default_slug).to be_nil
|
534
|
+
end
|
535
|
+
|
536
|
+
it 'generates a slug' do
|
537
|
+
expect(Mongoid::Slug.default_slug).to be_present
|
538
|
+
class Person
|
539
|
+
include Mongoid::Document
|
540
|
+
include Mongoid::Slug
|
541
|
+
|
542
|
+
field :name
|
543
|
+
slug :name
|
544
|
+
end
|
545
|
+
|
546
|
+
person = Person.create(name: 'John')
|
547
|
+
expect(person.to_param).to eql "john-#{person.id}"
|
548
|
+
end
|
549
|
+
|
550
|
+
it 'can be overridden at model level' do
|
551
|
+
expect(Mongoid::Slug.default_slug).to be_present
|
552
|
+
class Person
|
553
|
+
include Mongoid::Document
|
554
|
+
include Mongoid::Slug
|
555
|
+
|
556
|
+
field :name
|
557
|
+
slug :name do |cur_object|
|
558
|
+
cur_object.slug_builder.to_url
|
559
|
+
end
|
560
|
+
end
|
561
|
+
|
562
|
+
person = Person.create(name: 'John')
|
563
|
+
expect(person.to_param).to eql 'john'
|
564
|
+
end
|
565
|
+
end
|
566
|
+
|
491
567
|
context 'when slugged field contains non-ASCII characters' do
|
492
568
|
it 'slugs Cyrillic characters' do
|
493
569
|
book.title = 'Капитал'
|
@@ -514,126 +590,108 @@ module Mongoid
|
|
514
590
|
end
|
515
591
|
end
|
516
592
|
|
517
|
-
context 'when
|
518
|
-
|
519
|
-
|
520
|
-
|
593
|
+
context 'when slug is not scoped by a reference association' do
|
594
|
+
subject { Book }
|
595
|
+
it_should_behave_like 'has an index', { _slugs: 1 }, unique: true, sparse: true
|
596
|
+
end
|
521
597
|
|
522
|
-
|
523
|
-
|
598
|
+
context 'with a value exceeding mongodb max index key' do
|
599
|
+
if Mongoid::Compatibility::Version.mongoid5? || Mongoid::Compatibility::Version.mongoid6?
|
600
|
+
it 'errors with a model without a max length' do
|
601
|
+
expect do
|
602
|
+
Book.create!(title: 't' * 1025)
|
603
|
+
end.to raise_error Mongo::Error::OperationFailure, /key too large to index/
|
604
|
+
end
|
605
|
+
elsif Mongoid::Compatibility::Version.mongoid4?
|
606
|
+
it 'errors with a model without a max length' do
|
607
|
+
expect do
|
608
|
+
Book.create!(title: 't' * 1025)
|
609
|
+
end.to raise_error Moped::Errors::OperationFailure, /key too large to index/
|
610
|
+
end
|
524
611
|
end
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
AuthorPolymorphic.remove_indexes
|
531
|
-
BookPolymorphic.remove_indexes
|
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
|
532
617
|
end
|
618
|
+
end
|
533
619
|
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
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
|
538
624
|
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
Book.create!(title: 't' * 1025)
|
544
|
-
end.to raise_error Mongo::Error::OperationFailure, /key too large to index/
|
545
|
-
end
|
546
|
-
elsif Mongoid::Compatibility::Version.mongoid4?
|
547
|
-
it 'errors with a model without a max length' do
|
548
|
-
expect do
|
549
|
-
Book.create!(title: 't' * 1025)
|
550
|
-
end.to raise_error Moped::Errors::OperationFailure, /key too large to index/
|
551
|
-
end
|
552
|
-
end
|
553
|
-
it 'succeeds with a model with a max length' do
|
554
|
-
expect do
|
555
|
-
author = Author.create!(last_name: 't' * 1025)
|
556
|
-
expect(author.slug.length).to eq 256
|
557
|
-
end.to_not raise_error
|
558
|
-
end
|
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
|
559
629
|
end
|
560
630
|
|
561
631
|
context 'when slug is scoped by a reference association' do
|
562
|
-
subject {
|
563
|
-
it_should_behave_like 'does not have an index', _slugs: 1
|
632
|
+
subject { AuthorPolymorphic }
|
633
|
+
it_should_behave_like 'does not have an index', _type: 1, _slugs: 1
|
564
634
|
end
|
565
635
|
|
566
|
-
context '
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
end
|
571
|
-
|
572
|
-
context 'when slug is scoped by a reference association' do
|
573
|
-
subject { AuthorPolymorphic }
|
574
|
-
it_should_behave_like 'does not have an index', _type: 1, _slugs: 1
|
575
|
-
end
|
576
|
-
|
577
|
-
context 'when the object has STI' do
|
578
|
-
it 'scopes by the subclass' do
|
579
|
-
b = BookPolymorphic.create!(title: 'Book')
|
580
|
-
expect(b.slug).to eq('book')
|
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')
|
581
640
|
|
582
|
-
|
583
|
-
|
641
|
+
b2 = BookPolymorphic.create!(title: 'Book')
|
642
|
+
expect(b2.slug).to eq('book-1')
|
584
643
|
|
585
|
-
|
586
|
-
|
644
|
+
c = ComicBookPolymorphic.create!(title: 'Book')
|
645
|
+
expect(c.slug).to eq('book')
|
587
646
|
|
588
|
-
|
589
|
-
|
647
|
+
c2 = ComicBookPolymorphic.create!(title: 'Book')
|
648
|
+
expect(c2.slug).to eq('book-1')
|
590
649
|
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
-
end
|
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)
|
596
654
|
end
|
597
655
|
end
|
598
656
|
end
|
657
|
+
end
|
599
658
|
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
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')
|
606
665
|
|
607
|
-
|
608
|
-
|
609
|
-
|
666
|
+
friend2 = Friend.create(name: 'bar')
|
667
|
+
expect(friend2.slugs).not_to include('bar')
|
668
|
+
expect(friend2.slugs).to include('bar-1')
|
610
669
|
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
670
|
+
friend3 = Friend.create(name: 'en')
|
671
|
+
expect(friend3.slugs).not_to include('en')
|
672
|
+
expect(friend3.slugs).to include('en-1')
|
673
|
+
end
|
615
674
|
|
616
|
-
|
617
|
-
|
618
|
-
|
619
|
-
|
620
|
-
|
621
|
-
|
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
|
622
681
|
|
623
|
-
|
624
|
-
|
625
|
-
|
626
|
-
|
627
|
-
end
|
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
|
628
686
|
end
|
629
687
|
end
|
630
|
-
|
631
|
-
|
632
|
-
|
633
|
-
|
634
|
-
|
635
|
-
|
636
|
-
|
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")
|
637
695
|
end
|
638
696
|
end
|
639
697
|
end
|
@@ -667,7 +725,7 @@ module Mongoid
|
|
667
725
|
let(:book) { Book.new }
|
668
726
|
|
669
727
|
it 'should return nil' do
|
670
|
-
expect(book.to_param).to
|
728
|
+
expect(book.to_param).to eq book._id.to_s
|
671
729
|
end
|
672
730
|
|
673
731
|
it 'should not persist the record' do
|
@@ -677,24 +735,22 @@ module Mongoid
|
|
677
735
|
end
|
678
736
|
|
679
737
|
context 'when called on an existing record with no slug' do
|
680
|
-
let!(:
|
681
|
-
|
682
|
-
before do
|
683
|
-
if Mongoid::Compatibility::Version.mongoid5?
|
738
|
+
let!(:book_no_slug) do
|
739
|
+
if Mongoid::Compatibility::Version.mongoid5? || Mongoid::Compatibility::Version.mongoid6?
|
684
740
|
Book.collection.insert_one(title: 'Proust and Signs')
|
685
741
|
else
|
686
742
|
Book.collection.insert(title: 'Proust and Signs')
|
687
743
|
end
|
744
|
+
Book.where(title: 'Proust and Signs').first
|
688
745
|
end
|
689
746
|
|
690
747
|
it 'should return the id if there is no slug' do
|
691
|
-
|
692
|
-
expect(
|
693
|
-
expect(book.reload.slugs).to be_empty
|
748
|
+
expect(book_no_slug.to_param).to eq(book_no_slug.id.to_s)
|
749
|
+
expect(book_no_slug.slugs).to be_nil
|
694
750
|
end
|
695
751
|
|
696
752
|
it 'should not persist the record' do
|
697
|
-
expect(
|
753
|
+
expect(book_no_slug.to_param).to eq(book_no_slug._id.to_s)
|
698
754
|
end
|
699
755
|
end
|
700
756
|
end
|
@@ -738,9 +794,17 @@ module Mongoid
|
|
738
794
|
end
|
739
795
|
|
740
796
|
it 'ensures uniqueness' do
|
741
|
-
Book.create(title: 'A Thousand Plateaus', slugs: ['not-what-you-expected'])
|
742
|
-
|
743
|
-
|
797
|
+
book1 = Book.create(title: 'A Thousand Plateaus', slugs: ['not-what-you-expected'])
|
798
|
+
expect(book1.to_param).to eql 'not-what-you-expected'
|
799
|
+
if Mongoid::Compatibility::Version.mongoid5? || Mongoid::Compatibility::Version.mongoid6?
|
800
|
+
expect do
|
801
|
+
Book.create(title: 'A Thousand Plateaus', slugs: ['not-what-you-expected'])
|
802
|
+
end.to raise_error Mongo::Error::OperationFailure, /duplicate/
|
803
|
+
elsif Mongoid::Compatibility::Version.mongoid4?
|
804
|
+
expect do
|
805
|
+
Book.create(title: 'A Thousand Plateaus', slugs: ['not-what-you-expected'])
|
806
|
+
end.to raise_error Moped::Errors::OperationFailure, /duplicate/
|
807
|
+
end
|
744
808
|
end
|
745
809
|
|
746
810
|
it 'updates the slug when a new one is passed in' do
|
@@ -761,8 +825,15 @@ module Mongoid
|
|
761
825
|
Book.create(title: 'Sleepyhead')
|
762
826
|
book2 = Book.create(title: 'A Thousand Plateaus')
|
763
827
|
book2.slugs.push 'sleepyhead'
|
764
|
-
|
765
|
-
|
828
|
+
if Mongoid::Compatibility::Version.mongoid5? || Mongoid::Compatibility::Version.mongoid6?
|
829
|
+
expect do
|
830
|
+
book2.save
|
831
|
+
end.to raise_error Mongo::Error::OperationFailure, /duplicate/
|
832
|
+
elsif Mongoid::Compatibility::Version.mongoid4?
|
833
|
+
expect do
|
834
|
+
book2.save
|
835
|
+
end.to raise_error Moped::Errors::OperationFailure, /duplicate/
|
836
|
+
end
|
766
837
|
end
|
767
838
|
end
|
768
839
|
|
@@ -1087,5 +1158,13 @@ module Mongoid
|
|
1087
1158
|
expect(author3.slug.ends_with?('tt-2')).to be true
|
1088
1159
|
end
|
1089
1160
|
end
|
1161
|
+
|
1162
|
+
context 'has_many / belongs_to' do
|
1163
|
+
let(:book) { Book.create!(title: 'War and Peace') }
|
1164
|
+
it 'allows for duplicates with different slugs' do
|
1165
|
+
Author.create!(first_name: 'Leo', last_name: 'Tostoy')
|
1166
|
+
expect { book.authors.create!(first_name: 'Leo', last_name: 'Tostoy') }.to_not raise_error
|
1167
|
+
end
|
1168
|
+
end
|
1090
1169
|
end
|
1091
1170
|
end
|