mongoid_slug 0.8.3 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +13 -1
- data/lib/mongoid/slug.rb +67 -42
- data/lib/mongoid/slug/version.rb +1 -1
- data/spec/models/magazine.rb +7 -0
- data/spec/mongoid/slug_spec.rb +24 -0
- metadata +13 -17
data/README.md
CHANGED
@@ -81,5 +81,17 @@ specify the `:inverse_of` option on the other side of the assocation.
|
|
81
81
|
|
82
82
|
Embedded objects are automatically scoped by their parent.
|
83
83
|
|
84
|
+
If the value of `:scope` is not an association, it should be the name of a field within the model itself:
|
85
|
+
|
86
|
+
```ruby
|
87
|
+
class Employee
|
88
|
+
include Mongoid::Document
|
89
|
+
include Mongoid::Slug
|
90
|
+
field :name
|
91
|
+
field :company_id
|
92
|
+
slug :name, :scope => :company_id
|
93
|
+
end
|
94
|
+
```
|
95
|
+
|
84
96
|
[1]: https://github.com/rsl/stringex/
|
85
|
-
[2]: https://github.com/
|
97
|
+
[2]: https://github.com/hakanensari/mongoid-slug/blob/master/lib/mongoid/slug.rb
|
data/lib/mongoid/slug.rb
CHANGED
@@ -3,8 +3,8 @@ require 'stringex'
|
|
3
3
|
|
4
4
|
module Mongoid #:nodoc:
|
5
5
|
|
6
|
-
# The slug module helps you generate a URL slug or permalink based on
|
7
|
-
# more fields in a Mongoid model.
|
6
|
+
# The slug module helps you generate a URL slug or permalink based on
|
7
|
+
# one or more fields in a Mongoid model.
|
8
8
|
#
|
9
9
|
# class Person
|
10
10
|
# include Mongoid::Document
|
@@ -18,7 +18,10 @@ module Mongoid #:nodoc:
|
|
18
18
|
extend ActiveSupport::Concern
|
19
19
|
|
20
20
|
included do
|
21
|
-
cattr_accessor :slug_builder,
|
21
|
+
cattr_accessor :slug_builder,
|
22
|
+
:slugged_fields,
|
23
|
+
:slug_name,
|
24
|
+
:slug_scope
|
22
25
|
end
|
23
26
|
|
24
27
|
module ClassMethods
|
@@ -29,26 +32,28 @@ module Mongoid #:nodoc:
|
|
29
32
|
#
|
30
33
|
# The options hash respects the following members:
|
31
34
|
#
|
32
|
-
# * `:as`, which specifies name of the field that stores the
|
33
|
-
# Defaults to `slug`.
|
35
|
+
# * `:as`, which specifies name of the field that stores the
|
36
|
+
# slug. Defaults to `slug`.
|
34
37
|
#
|
35
|
-
# * `:scope`, which specifies a reference association to scope
|
36
|
-
# by. Embedded documents are by default scoped by their
|
38
|
+
# * `:scope`, which specifies a reference association to scope
|
39
|
+
# the slug by. Embedded documents are by default scoped by their
|
40
|
+
# parent.
|
37
41
|
#
|
38
|
-
# * `:permanent`, which specifies whether the slug should be
|
39
|
-
# once created. Defaults to `false`.
|
42
|
+
# * `:permanent`, which specifies whether the slug should be
|
43
|
+
# immutable once created. Defaults to `false`.
|
40
44
|
#
|
41
|
-
# * `:index`, which specifies whether an index should be defined
|
42
|
-
# slug. Defaults to `false` and has no effect if the
|
43
|
-
#
|
44
|
-
# documents to avoid the (very unlikely) race
|
45
|
-
# if two documents with identical
|
45
|
+
# * `:index`, which specifies whether an index should be defined
|
46
|
+
# for the slug. Defaults to `false` and has no effect if the
|
47
|
+
# document is embedded. Make sure you have a unique index on the
|
48
|
+
# slug of root documents to avoid the (very unlikely) race
|
49
|
+
# condition that would ensue if two documents with identical
|
50
|
+
# slugs were to be saved simultaneously.
|
46
51
|
#
|
47
|
-
# Alternatively, this method can be given a block to build a
|
48
|
-
# out of the specified fields.
|
52
|
+
# Alternatively, this method can be given a block to build a
|
53
|
+
# custom slug out of the specified fields.
|
49
54
|
#
|
50
|
-
# The block takes a single argument, the document itself, and
|
51
|
-
# return a string that will serve as the base of the slug.
|
55
|
+
# The block takes a single argument, the document itself, and
|
56
|
+
# should return a string that will serve as the base of the slug.
|
52
57
|
#
|
53
58
|
# Here, for instance, we slug an array field.
|
54
59
|
#
|
@@ -72,7 +77,8 @@ module Mongoid #:nodoc:
|
|
72
77
|
block
|
73
78
|
else
|
74
79
|
lambda do |doc|
|
75
|
-
slugged_fields.map { |f| doc.read_attribute(f) }.
|
80
|
+
slugged_fields.map { |f| doc.read_attribute(f) }.
|
81
|
+
join(' ')
|
76
82
|
end
|
77
83
|
end
|
78
84
|
|
@@ -119,35 +125,54 @@ module Mongoid #:nodoc:
|
|
119
125
|
# TODO: An epic method which calls for refactoring.
|
120
126
|
slug = slug_builder.call(self).to_url
|
121
127
|
|
122
|
-
# Regular expression that matches slug, slug-1,
|
123
|
-
# If slug_name field was indexed, MongoDB will utilize that
|
124
|
-
# match /^.../ pattern
|
128
|
+
# Regular expression that matches slug, slug-1, ... slug-n
|
129
|
+
# If slug_name field was indexed, MongoDB will utilize that
|
130
|
+
# index to match /^.../ pattern.
|
125
131
|
pattern = /^#{Regexp.escape(slug)}(?:-(\d+))?$/
|
126
132
|
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
133
|
+
if slug_scope &&
|
134
|
+
self.class.reflect_on_association(slug_scope).nil?
|
135
|
+
# scope is not an association, so it's scoped to a local field
|
136
|
+
# (e.g. an association id in a denormalized db design)
|
137
|
+
existing_slugs =
|
138
|
+
self.class.
|
139
|
+
only(slug_name).
|
140
|
+
where(slug_name => pattern,
|
141
|
+
:_id.ne => _id,
|
142
|
+
slug_scope => self[slug_scope])
|
143
|
+
else
|
144
|
+
existing_slugs =
|
145
|
+
uniqueness_scope.
|
146
|
+
only(slug_name).
|
147
|
+
where(slug_name => pattern, :_id.ne => _id)
|
148
|
+
end
|
149
|
+
|
150
|
+
existing_slugs = existing_slugs.map do |obj|
|
151
|
+
obj.try(:read_attribute, slug_name)
|
152
|
+
end
|
132
153
|
|
133
|
-
if existing_slugs.count > 0
|
134
|
-
#
|
135
|
-
# numbers:
|
154
|
+
if existing_slugs.count > 0
|
155
|
+
# Sort the existing_slugs in increasing order by comparing the
|
156
|
+
# suffix numbers:
|
136
157
|
# slug, slug-1, slug-2, ..., slug-n
|
137
158
|
existing_slugs.sort! do |a, b|
|
138
|
-
(pattern.match(a)[1] || -1).to_i <=>
|
159
|
+
(pattern.match(a)[1] || -1).to_i <=>
|
160
|
+
(pattern.match(b)[1] || -1).to_i
|
139
161
|
end
|
140
|
-
|
162
|
+
max = existing_slugs.last.match(/-(\d+)$/).try(:[], 1).to_i
|
141
163
|
|
142
|
-
|
143
|
-
slug += "-#{max_counter + 1}"
|
164
|
+
slug += "-#{max + 1}"
|
144
165
|
end
|
145
166
|
|
146
167
|
slug
|
147
168
|
end
|
148
169
|
|
149
170
|
def generate_slug
|
150
|
-
if
|
171
|
+
# Generate a slug for new records only if the slug was not set.
|
172
|
+
# If we're not a new record generate a slug if our slugged fields
|
173
|
+
# changed on us.
|
174
|
+
if (new_record? && !read_attribute(slug_name)) ||
|
175
|
+
(!new_record? && slugged_fields_changed?)
|
151
176
|
generate_slug!
|
152
177
|
end
|
153
178
|
end
|
@@ -165,20 +190,20 @@ module Mongoid #:nodoc:
|
|
165
190
|
metadata = self.class.reflect_on_association(slug_scope)
|
166
191
|
parent = self.send(metadata.name)
|
167
192
|
|
168
|
-
# Make sure doc is actually associated with something, and that
|
169
|
-
# referenced docs have been persisted to the parent
|
193
|
+
# Make sure doc is actually associated with something, and that
|
194
|
+
# some referenced docs have been persisted to the parent
|
170
195
|
#
|
171
|
-
# TODO: we need better reflection for reference associations,
|
172
|
-
# association_name instead of forcing collection_name here
|
173
|
-
# in the forthcoming Mongoid refactorings?
|
196
|
+
# TODO: we need better reflection for reference associations,
|
197
|
+
# like association_name instead of forcing collection_name here
|
198
|
+
# -- maybe in the forthcoming Mongoid refactorings?
|
174
199
|
inverse = metadata.inverse_of || collection_name
|
175
200
|
parent.respond_to?(inverse) ? parent.send(inverse) : self.class
|
176
201
|
elsif embedded?
|
177
|
-
parent_metadata = reflect_on_all_associations(:embedded_in)
|
202
|
+
parent_metadata = reflect_on_all_associations(:embedded_in)[0]
|
178
203
|
_parent.send(parent_metadata.inverse_of || self.metadata.name)
|
179
204
|
else
|
180
205
|
appropriate_class = self.class
|
181
|
-
while
|
206
|
+
while appropriate_class.superclass.include?(Mongoid::Document)
|
182
207
|
appropriate_class = appropriate_class.superclass
|
183
208
|
end
|
184
209
|
appropriate_class
|
data/lib/mongoid/slug/version.rb
CHANGED
data/spec/mongoid/slug_spec.rb
CHANGED
@@ -236,6 +236,23 @@ module Mongoid
|
|
236
236
|
end
|
237
237
|
end
|
238
238
|
end
|
239
|
+
|
240
|
+
context "when slug is scoped by one of the class's own fields" do
|
241
|
+
let!(:magazine) do
|
242
|
+
Magazine.create(:title => "Big Weekly", :publisher_id => "abc123")
|
243
|
+
end
|
244
|
+
|
245
|
+
it "should scope by local field" do
|
246
|
+
magazine.to_param.should eql 'big-weekly'
|
247
|
+
magazine2 = Magazine.create(:title => "Big Weekly", :publisher_id => "def456")
|
248
|
+
magazine2.to_param.should eql magazine.to_param
|
249
|
+
end
|
250
|
+
|
251
|
+
it "should generate a unique slug by appending a counter to duplicate text" do
|
252
|
+
dup = Magazine.create(:title => "Big Weekly", :publisher_id => "abc123")
|
253
|
+
dup.to_param.should eql 'big-weekly-1'
|
254
|
+
end
|
255
|
+
end
|
239
256
|
|
240
257
|
context "when :slug is given a block" do
|
241
258
|
let(:caption) do
|
@@ -369,5 +386,12 @@ module Mongoid
|
|
369
386
|
book.reload.slug.should eql "proust-and-signs"
|
370
387
|
end
|
371
388
|
end
|
389
|
+
|
390
|
+
context "when the slugged field is set upon creation" do
|
391
|
+
it "respects the provided slug and does not generate a new one" do
|
392
|
+
book = Book.create(:title => "A Thousand Plateaus", :slug => 'not-what-you-expected')
|
393
|
+
book.to_param.should eql "not-what-you-expected"
|
394
|
+
end
|
395
|
+
end
|
372
396
|
end
|
373
397
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mongoid_slug
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.9.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2012-01-10 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: mongoid
|
16
|
-
requirement: &
|
16
|
+
requirement: &70354887937940 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ~>
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: '2.0'
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *70354887937940
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: stringex
|
27
|
-
requirement: &
|
27
|
+
requirement: &70354887953780 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ~>
|
@@ -32,10 +32,10 @@ dependencies:
|
|
32
32
|
version: '1.3'
|
33
33
|
type: :runtime
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *70354887953780
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: rake
|
38
|
-
requirement: &
|
38
|
+
requirement: &70354887953320 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ~>
|
@@ -43,10 +43,10 @@ dependencies:
|
|
43
43
|
version: '0.9'
|
44
44
|
type: :development
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *70354887953320
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: rspec
|
49
|
-
requirement: &
|
49
|
+
requirement: &70354887952860 !ruby/object:Gem::Requirement
|
50
50
|
none: false
|
51
51
|
requirements:
|
52
52
|
- - ~>
|
@@ -54,7 +54,7 @@ dependencies:
|
|
54
54
|
version: '2.6'
|
55
55
|
type: :development
|
56
56
|
prerelease: false
|
57
|
-
version_requirements: *
|
57
|
+
version_requirements: *70354887952860
|
58
58
|
description: Mongoid Slug generates a URL slug or permalink based on one or more fields
|
59
59
|
in a Mongoid model.
|
60
60
|
email:
|
@@ -72,6 +72,7 @@ files:
|
|
72
72
|
- spec/models/author.rb
|
73
73
|
- spec/models/book.rb
|
74
74
|
- spec/models/caption.rb
|
75
|
+
- spec/models/magazine.rb
|
75
76
|
- spec/models/page.rb
|
76
77
|
- spec/models/partner.rb
|
77
78
|
- spec/models/person.rb
|
@@ -91,21 +92,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
91
92
|
- - ! '>='
|
92
93
|
- !ruby/object:Gem::Version
|
93
94
|
version: '0'
|
94
|
-
segments:
|
95
|
-
- 0
|
96
|
-
hash: -3230490500102343809
|
97
95
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
98
96
|
none: false
|
99
97
|
requirements:
|
100
98
|
- - ! '>='
|
101
99
|
- !ruby/object:Gem::Version
|
102
100
|
version: '0'
|
103
|
-
segments:
|
104
|
-
- 0
|
105
|
-
hash: -3230490500102343809
|
106
101
|
requirements: []
|
107
102
|
rubyforge_project: mongoid_slug
|
108
|
-
rubygems_version: 1.8.
|
103
|
+
rubygems_version: 1.8.11
|
109
104
|
signing_key:
|
110
105
|
specification_version: 3
|
111
106
|
summary: Generates a URL slug
|
@@ -114,6 +109,7 @@ test_files:
|
|
114
109
|
- spec/models/author.rb
|
115
110
|
- spec/models/book.rb
|
116
111
|
- spec/models/caption.rb
|
112
|
+
- spec/models/magazine.rb
|
117
113
|
- spec/models/page.rb
|
118
114
|
- spec/models/partner.rb
|
119
115
|
- spec/models/person.rb
|