mongoid-slug 4.0.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 +7 -0
- data/LICENSE +20 -0
- data/README.md +333 -0
- data/lib/mongoid/slug.rb +333 -0
- data/lib/mongoid/slug/criteria.rb +110 -0
- data/lib/mongoid/slug/index.rb +27 -0
- data/lib/mongoid/slug/paranoia.rb +22 -0
- data/lib/mongoid/slug/slug_id_strategy.rb +3 -0
- data/lib/mongoid/slug/unique_slug.rb +153 -0
- data/lib/mongoid/slug/version.rb +5 -0
- data/lib/mongoid_slug.rb +2 -0
- data/spec/models/alias.rb +6 -0
- data/spec/models/article.rb +9 -0
- data/spec/models/author.rb +11 -0
- data/spec/models/author_polymorphic.rb +11 -0
- data/spec/models/book.rb +12 -0
- data/spec/models/book_polymorphic.rb +12 -0
- data/spec/models/caption.rb +17 -0
- data/spec/models/entity.rb +12 -0
- data/spec/models/friend.rb +7 -0
- data/spec/models/incorrect_slug_persistence.rb +9 -0
- data/spec/models/integer_id.rb +9 -0
- data/spec/models/magazine.rb +7 -0
- data/spec/models/page.rb +9 -0
- data/spec/models/page_localize.rb +9 -0
- data/spec/models/page_slug_localized.rb +9 -0
- data/spec/models/page_slug_localized_custom.rb +11 -0
- data/spec/models/page_slug_localized_history.rb +9 -0
- data/spec/models/paranoid_document.rb +8 -0
- data/spec/models/paranoid_permanent.rb +8 -0
- data/spec/models/partner.rb +7 -0
- data/spec/models/person.rb +8 -0
- data/spec/models/relationship.rb +8 -0
- data/spec/models/string_id.rb +9 -0
- data/spec/models/subject.rb +7 -0
- data/spec/models/without_slug.rb +5 -0
- data/spec/mongoid/criteria_spec.rb +190 -0
- data/spec/mongoid/index_spec.rb +34 -0
- data/spec/mongoid/paranoia_spec.rb +169 -0
- data/spec/mongoid/slug_spec.rb +1022 -0
- data/spec/mongoid/slug_spec.rb.b00 +1101 -0
- data/spec/shared/indexes.rb +27 -0
- data/spec/spec_helper.rb +47 -0
- metadata +245 -0
data/spec/models/page.rb
ADDED
@@ -0,0 +1,190 @@
|
|
1
|
+
#encoding: utf-8
|
2
|
+
require "spec_helper"
|
3
|
+
|
4
|
+
describe Mongoid::Slug::Criteria do
|
5
|
+
describe ".find" do
|
6
|
+
let!(:book) { Book.create(:title => "A Working Title").tap { |d| d.update_attribute(:title, "A Thousand Plateaus") } }
|
7
|
+
let!(:book2) { Book.create(:title => "Difference and Repetition") }
|
8
|
+
let!(:friend) { Friend.create(:name => "Jim Bob") }
|
9
|
+
let!(:friend2) { Friend.create(:name => friend.id.to_s) }
|
10
|
+
let!(:integer_id) { IntegerId.new(:name => "I have integer ids").tap { |d| d.id = 123; d.save } }
|
11
|
+
let!(:integer_id2) { IntegerId.new(:name => integer_id.id.to_s).tap { |d| d.id = 456; d.save } }
|
12
|
+
let!(:string_id) { StringId.new(:name => "I have string ids").tap { |d| d.id = 'abc'; d.save } }
|
13
|
+
let!(:string_id2) { StringId.new(:name => string_id.id.to_s).tap { |d| d.id = 'def'; d.save } }
|
14
|
+
let!(:subject) { Subject.create(:name => "A Subject", :book => book) }
|
15
|
+
let!(:subject2) { Subject.create(:name => "A Subject", :book => book2) }
|
16
|
+
let!(:without_slug) { WithoutSlug.new().tap { |d| d.id = 456; d.save } }
|
17
|
+
|
18
|
+
context "when the model does not use mongoid slugs" do
|
19
|
+
it "should not use mongoid slug's custom find methods" do
|
20
|
+
Mongoid::Slug::Criteria.any_instance.should_not_receive(:find)
|
21
|
+
WithoutSlug.find(without_slug.id.to_s).should == without_slug
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
context "using slugs" do
|
26
|
+
context "(single)" do
|
27
|
+
context "and a document is found" do
|
28
|
+
it "returns the document as an object" do
|
29
|
+
Book.find(book.slugs.first).should == book
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context "but no document is found" do
|
34
|
+
it "raises a Mongoid::Errors::DocumentNotFound error" do
|
35
|
+
lambda {
|
36
|
+
Book.find("Anti Oedipus")
|
37
|
+
}.should raise_error(Mongoid::Errors::DocumentNotFound)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
context "(multiple)" do
|
43
|
+
context "and all documents are found" do
|
44
|
+
it "returns the documents as an array without duplication" do
|
45
|
+
Book.find(book.slugs + book2.slugs).should =~ [book, book2]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
context "but not all documents are found" do
|
50
|
+
it "raises a Mongoid::Errors::DocumentNotFound error" do
|
51
|
+
lambda {
|
52
|
+
Book.find(book.slugs + ['something-nonexistent'])
|
53
|
+
}.should raise_error(Mongoid::Errors::DocumentNotFound)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
context "when no documents match" do
|
59
|
+
it "raises a Mongoid::Errors::DocumentNotFound error" do
|
60
|
+
lambda {
|
61
|
+
Book.find("Anti Oedipus")
|
62
|
+
}.should raise_error(Mongoid::Errors::DocumentNotFound)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
context "when ids are BSON::ObjectIds and the supplied argument looks like a BSON::ObjectId" do
|
67
|
+
it "it should find based on ids not slugs" do # i.e. it should type cast the argument
|
68
|
+
Friend.find(friend.id.to_s).should == friend
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
context "when ids are Strings" do
|
73
|
+
it "it should find based on ids not slugs" do # i.e. string ids should take precedence over string slugs
|
74
|
+
StringId.find(string_id.id.to_s).should == string_id
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
context "when ids are Integers and the supplied arguments looks like an Integer" do
|
79
|
+
it "it should find based on slugs not ids" do # i.e. it should not type cast the argument
|
80
|
+
IntegerId.find(integer_id.id.to_s).should == integer_id2
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
context "models that does not use slugs, should find using the original find" do
|
85
|
+
it "it should find based on ids" do # i.e. it should not type cast the argument
|
86
|
+
WithoutSlug.find(without_slug.id.to_s).should == without_slug
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
context "when scoped" do
|
91
|
+
context "and a document is found" do
|
92
|
+
it "returns the document as an object" do
|
93
|
+
book.subjects.find(subject.slugs.first).should == subject
|
94
|
+
book2.subjects.find(subject.slugs.first).should == subject2
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
context "but no document is found" do
|
99
|
+
it "raises a Mongoid::Errors::DocumentNotFound error" do
|
100
|
+
lambda {
|
101
|
+
book.subjects.find('Another Subject')
|
102
|
+
}.should raise_error(Mongoid::Errors::DocumentNotFound)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
context "using ids" do
|
109
|
+
it "raises a Mongoid::Errors::DocumentNotFound error if no document is found" do
|
110
|
+
lambda {
|
111
|
+
Book.find(friend.id)
|
112
|
+
}.should raise_error(Mongoid::Errors::DocumentNotFound)
|
113
|
+
end
|
114
|
+
|
115
|
+
context "given a single document" do
|
116
|
+
it "returns the document" do
|
117
|
+
Friend.find(friend.id).should == friend
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
context "given multiple documents" do
|
122
|
+
it "returns the documents" do
|
123
|
+
Book.find([book.id, book2.id]).should =~ [book, book2]
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
describe ".find_by_slug!" do
|
130
|
+
let!(:book) { Book.create(:title => "A Working Title").tap { |d| d.update_attribute(:title, "A Thousand Plateaus") } }
|
131
|
+
let!(:book2) { Book.create(:title => "Difference and Repetition") }
|
132
|
+
let!(:friend) { Friend.create(:name => "Jim Bob") }
|
133
|
+
let!(:friend2) { Friend.create(:name => friend.id.to_s) }
|
134
|
+
let!(:integer_id) { IntegerId.new(:name => "I have integer ids").tap { |d| d.id = 123; d.save } }
|
135
|
+
let!(:integer_id2) { IntegerId.new(:name => integer_id.id.to_s).tap { |d| d.id = 456; d.save } }
|
136
|
+
let!(:string_id) { StringId.new(:name => "I have string ids").tap { |d| d.id = 'abc'; d.save } }
|
137
|
+
let!(:string_id2) { StringId.new(:name => string_id.id.to_s).tap { |d| d.id = 'def'; d.save } }
|
138
|
+
let!(:subject) { Subject.create(:name => "A Subject", :book => book) }
|
139
|
+
let!(:subject2) { Subject.create(:name => "A Subject", :book => book2) }
|
140
|
+
|
141
|
+
context "(single)" do
|
142
|
+
context "and a document is found" do
|
143
|
+
it "returns the document as an object" do
|
144
|
+
Book.find_by_slug!(book.slugs.first).should == book
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
context "but no document is found" do
|
149
|
+
it "raises a Mongoid::Errors::DocumentNotFound error" do
|
150
|
+
lambda {
|
151
|
+
Book.find_by_slug!("Anti Oedipus")
|
152
|
+
}.should raise_error(Mongoid::Errors::DocumentNotFound)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
context "(multiple)" do
|
158
|
+
context "and all documents are found" do
|
159
|
+
it "returns the documents as an array without duplication" do
|
160
|
+
Book.find_by_slug!(book.slugs + book2.slugs).should =~ [book, book2]
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
context "but not all documents are found" do
|
165
|
+
it "raises a Mongoid::Errors::DocumentNotFound error" do
|
166
|
+
lambda {
|
167
|
+
Book.find_by_slug!(book.slugs + ['something-nonexistent'])
|
168
|
+
}.should raise_error(Mongoid::Errors::DocumentNotFound)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
context "when scoped" do
|
174
|
+
context "and a document is found" do
|
175
|
+
it "returns the document as an object" do
|
176
|
+
book.subjects.find_by_slug!(subject.slugs.first).should == subject
|
177
|
+
book2.subjects.find_by_slug!(subject.slugs.first).should == subject2
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
context "but no document is found" do
|
182
|
+
it "raises a Mongoid::Errors::DocumentNotFound error" do
|
183
|
+
lambda {
|
184
|
+
book.subjects.find_by_slug!('Another Subject')
|
185
|
+
}.should raise_error(Mongoid::Errors::DocumentNotFound)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
#encoding: utf-8
|
2
|
+
require "spec_helper"
|
3
|
+
|
4
|
+
describe Mongoid::Slug::Index do
|
5
|
+
|
6
|
+
let(:scope_key) { nil }
|
7
|
+
let(:by_model_type) { false }
|
8
|
+
subject { Mongoid::Slug::Index.build_index(scope_key, by_model_type) }
|
9
|
+
|
10
|
+
context "when scope_key is set" do
|
11
|
+
let(:scope_key) { :foo }
|
12
|
+
|
13
|
+
context "when by_model_type is true" do
|
14
|
+
let(:by_model_type) { true }
|
15
|
+
it { should eq [{:_slugs=>1, :foo=>1, :_type=>1}, {}] }
|
16
|
+
end
|
17
|
+
|
18
|
+
context "when by_model_type is false" do
|
19
|
+
it { should eq [{:_slugs=>1, :foo=>1}, {:unique=>true, :sparse=>true}] }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
context "when scope_key is not set" do
|
24
|
+
|
25
|
+
context "when by_model_type is true" do
|
26
|
+
let(:by_model_type) { true }
|
27
|
+
it { should eq [{:_slugs=>1, :_type=>1}, {}] }
|
28
|
+
end
|
29
|
+
|
30
|
+
context "when by_model_type is false" do
|
31
|
+
it { should eq [{:_slugs=>1}, {:unique=>true, :sparse=>true}] }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,169 @@
|
|
1
|
+
#encoding: utf-8
|
2
|
+
require "spec_helper"
|
3
|
+
|
4
|
+
describe "Mongoid::Paranoia with Mongoid::Slug" do
|
5
|
+
|
6
|
+
let(:paranoid_doc) { ParanoidDocument.create!(:title => "slug") }
|
7
|
+
let(:paranoid_doc_2) { ParanoidDocument.create!(:title => "slug") }
|
8
|
+
let(:paranoid_perm) { ParanoidPermanent.create!(:title => "slug") }
|
9
|
+
let(:paranoid_perm_2) { ParanoidPermanent.create!(:title => "slug") }
|
10
|
+
let(:non_paranoid_doc){ Article.create!(:title => "slug") }
|
11
|
+
subject{ paranoid_doc }
|
12
|
+
|
13
|
+
describe ".paranoid?" do
|
14
|
+
|
15
|
+
context "when Mongoid::Paranoia is included" do
|
16
|
+
subject { paranoid_doc.class }
|
17
|
+
its(:is_paranoid_doc?){ should be_truthy }
|
18
|
+
end
|
19
|
+
|
20
|
+
context "when Mongoid::Paranoia not included" do
|
21
|
+
subject { non_paranoid_doc.class }
|
22
|
+
its(:is_paranoid_doc?){ should be_falsey }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "#paranoid_deleted?" do
|
27
|
+
|
28
|
+
context "when Mongoid::Paranoia is included" do
|
29
|
+
|
30
|
+
context "when not destroyed" do
|
31
|
+
its(:paranoid_deleted?){ should be_falsey }
|
32
|
+
end
|
33
|
+
|
34
|
+
context "when destroyed" do
|
35
|
+
before { subject.destroy }
|
36
|
+
its(:paranoid_deleted?){ should be_truthy }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
context "when Mongoid::Paranoia not included" do
|
41
|
+
subject { non_paranoid_doc }
|
42
|
+
its(:paranoid_deleted?){ should be_falsey }
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe "restore callbacks" do
|
47
|
+
|
48
|
+
context "when Mongoid::Paranoia is included" do
|
49
|
+
subject { paranoid_doc.class }
|
50
|
+
it { should respond_to(:before_restore) }
|
51
|
+
it { should respond_to(:after_restore) }
|
52
|
+
end
|
53
|
+
|
54
|
+
context "when Mongoid::Paranoia not included" do
|
55
|
+
it { should_not respond_to(:before_restore) }
|
56
|
+
it { should_not respond_to(:after_restore) }
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe "index" do
|
61
|
+
before { ParanoidDocument.create_indexes }
|
62
|
+
after { ParanoidDocument.remove_indexes }
|
63
|
+
subject { ParanoidDocument }
|
64
|
+
|
65
|
+
it_should_behave_like "has an index", { _slugs: 1 }, { unique: true, sparse: true }
|
66
|
+
end
|
67
|
+
|
68
|
+
shared_examples_for "paranoid slugs" do
|
69
|
+
|
70
|
+
context "querying" do
|
71
|
+
|
72
|
+
it "returns paranoid_doc for correct slug" do
|
73
|
+
subject.class.find(subject.slug).should eq(subject)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
context "delete (callbacks not fired)" do
|
78
|
+
|
79
|
+
before { subject.delete }
|
80
|
+
|
81
|
+
it "retains slug value" do
|
82
|
+
subject.slug.should eq "slug"
|
83
|
+
subject.class.unscoped.find("slug").should eq subject
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
context "destroy" do
|
88
|
+
|
89
|
+
before { subject.destroy }
|
90
|
+
|
91
|
+
it "unsets slug value when destroyed" do
|
92
|
+
subject._slugs.should eq []
|
93
|
+
subject.slug.should be_nil
|
94
|
+
end
|
95
|
+
|
96
|
+
it "persists the removed slug" do
|
97
|
+
subject.reload._slugs.should eq []
|
98
|
+
subject.reload.slug.should be_nil
|
99
|
+
end
|
100
|
+
|
101
|
+
it "persists the removed slug in the database" do
|
102
|
+
subject.class.unscoped.exists(_slugs: false).first.should eq subject
|
103
|
+
expect{subject.class.unscoped.find("slug")}.to raise_error(Mongoid::Errors::DocumentNotFound)
|
104
|
+
end
|
105
|
+
|
106
|
+
context "when saving the doc again" do
|
107
|
+
|
108
|
+
before { subject.save }
|
109
|
+
|
110
|
+
it "should have the default slug value" do
|
111
|
+
subject._slugs.should eq []
|
112
|
+
subject.slug.should be_nil
|
113
|
+
end
|
114
|
+
|
115
|
+
it "the slug remains unset in the database" do
|
116
|
+
subject.class.unscoped.exists(_slugs: false).first.should eq subject
|
117
|
+
expect{subject.class.unscoped.find("slug")}.to raise_error(Mongoid::Errors::DocumentNotFound)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
context "restore" do
|
123
|
+
|
124
|
+
before do
|
125
|
+
subject.destroy
|
126
|
+
subject.restore
|
127
|
+
end
|
128
|
+
|
129
|
+
it "resets slug value when restored" do
|
130
|
+
subject.slug.should eq "slug"
|
131
|
+
subject.reload.slug.should eq "slug"
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
context "multiple documents" do
|
136
|
+
|
137
|
+
it "new documents should be able to use the slug of destroyed documents" do
|
138
|
+
subject.slug.should eq "slug"
|
139
|
+
subject.destroy
|
140
|
+
subject.reload.slug.should be_nil
|
141
|
+
other_doc.slug.should eq "slug"
|
142
|
+
subject.restore
|
143
|
+
subject.slug.should eq "slug-1"
|
144
|
+
subject.reload.slug.should eq "slug-1"
|
145
|
+
end
|
146
|
+
|
147
|
+
it "should allow multiple documents to be destroyed without index conflict" do
|
148
|
+
subject.slug.should eq "slug"
|
149
|
+
subject.destroy
|
150
|
+
subject.reload.slug.should be_nil
|
151
|
+
other_doc.slug.should eq "slug"
|
152
|
+
other_doc.destroy
|
153
|
+
other_doc.reload.slug.should be_nil
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
context "non-permanent slug" do
|
159
|
+
subject { paranoid_doc }
|
160
|
+
let(:other_doc) { paranoid_doc_2 }
|
161
|
+
it_behaves_like "paranoid slugs"
|
162
|
+
end
|
163
|
+
|
164
|
+
context "permanent slug" do
|
165
|
+
subject { paranoid_perm }
|
166
|
+
let(:other_doc) { paranoid_perm_2 }
|
167
|
+
it_behaves_like "paranoid slugs"
|
168
|
+
end
|
169
|
+
end
|