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
data/README.md
CHANGED
@@ -1,3 +1,6 @@
|
|
1
|
+
*IMPORTANT:* If you are upgrading to Mongoid Slug 0.20.0 please migrate in accordance with the instructions in https://github.com/digitalplaywright/mongoid-slug/wiki/How-to-upgrade-to-0.20.0.
|
2
|
+
Mongoid Slug 0.20.0 stores the slugs in a single field _slugs of array type, and all previous slugs must be migrated.
|
3
|
+
|
1
4
|
Mongoid Slug
|
2
5
|
============
|
3
6
|
|
@@ -5,7 +8,7 @@ Mongoid Slug generates a URL slug or permalink based on one or more fields in a
|
|
5
8
|
Mongoid model. It sits idly on top of [stringex] [1], supporting non-Latin
|
6
9
|
characters.
|
7
10
|
|
8
|
-
[](http://travis-ci.org/digitalplaywright/mongoid-slug)
|
9
12
|
|
10
13
|
Installation
|
11
14
|
------------
|
@@ -31,14 +34,52 @@ class Book
|
|
31
34
|
end
|
32
35
|
```
|
33
36
|
|
34
|
-
Find a
|
37
|
+
Find a document by its slug:
|
35
38
|
|
36
39
|
```ruby
|
37
40
|
# GET /books/a-thousand-plateaus
|
38
|
-
book = Book.
|
41
|
+
book = Book.find params[:book_id]
|
42
|
+
```
|
43
|
+
|
44
|
+
Mongoid Slug will attempt to determine whether you want to find using the `slugs` field or the `_id` field by inspecting the supplied parameters.
|
45
|
+
|
46
|
+
* If your document uses `BSON::ObjectId` identifiers, and all arguments passed to `find` are `String` and look like valid `BSON::ObjectId`, then Mongoid Slug will perform a find based on `_id`.
|
47
|
+
* If your document uses any other type of identifiers, and all arguments passed to `find` are of the same type, then Mongoid Slug will perform a find based on `_id`.
|
48
|
+
* Otherwise, if all arguments passed to `find` are of the type `String`, then Mongoid Slug will perform a find based on `slugs`.
|
49
|
+
|
50
|
+
To override this behaviour you may supply a hash of options as the final argument to `find` with the key `force_slugs` set to `true` or `false` as required. For example:
|
51
|
+
|
52
|
+
```ruby
|
53
|
+
Book.fields['_id'].type
|
54
|
+
=> String
|
55
|
+
book = Book.find 'a-thousand-plateaus' # Finds by _id
|
56
|
+
=> ...
|
57
|
+
book = Book.find 'a-thousand-plateaus', { force_slugs: true } # Finds by slugs
|
58
|
+
=> ...
|
39
59
|
```
|
40
60
|
|
41
|
-
|
61
|
+
|
62
|
+
[Read here] [4] for all available options.
|
63
|
+
|
64
|
+
Custom Slug Generation
|
65
|
+
-------
|
66
|
+
|
67
|
+
By default Mongoid Slug generates slugs with stringex. If this is not desired you can
|
68
|
+
define your own slug generator like this:
|
69
|
+
|
70
|
+
```
|
71
|
+
class Caption
|
72
|
+
include Mongoid::Document
|
73
|
+
include Mongoid::Slug
|
74
|
+
|
75
|
+
#create a block that takes the current object as an argument
|
76
|
+
#and returns the slug.
|
77
|
+
slug do |cur_object|
|
78
|
+
cur_object.slug_builder.to_url
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
```
|
42
83
|
|
43
84
|
Scoping
|
44
85
|
-------
|
@@ -109,7 +150,7 @@ page = Page.new title: "Home"
|
|
109
150
|
page.save
|
110
151
|
page.update_attributes title: "Welcome"
|
111
152
|
|
112
|
-
Page.
|
153
|
+
Page.find("welcome") == Page.find("home") #=> true
|
113
154
|
```
|
114
155
|
|
115
156
|
Reserved Slugs
|
@@ -126,7 +167,7 @@ class Friend
|
|
126
167
|
end
|
127
168
|
|
128
169
|
friend = Friend.create name: 'admin'
|
129
|
-
Friend.
|
170
|
+
Friend.find('admin') # => nil
|
130
171
|
friend.slug # => 'admin-1'
|
131
172
|
```
|
132
173
|
|
data/lib/mongoid/slug.rb
CHANGED
@@ -1,41 +1,34 @@
|
|
1
|
-
require 'mongoid'
|
2
|
-
require 'stringex'
|
3
|
-
|
4
1
|
module Mongoid
|
5
|
-
#
|
6
|
-
# more fields in a Mongoid model.
|
2
|
+
# Slugs your Mongoid model.
|
7
3
|
module Slug
|
8
4
|
extend ActiveSupport::Concern
|
9
5
|
|
10
6
|
included do
|
11
|
-
cattr_accessor :
|
12
|
-
:slug_name,
|
13
|
-
:slug_history_name,
|
7
|
+
cattr_accessor :reserved_words,
|
14
8
|
:slug_scope,
|
15
|
-
:
|
16
|
-
:
|
9
|
+
:slugged_attributes,
|
10
|
+
:url_builder,
|
11
|
+
:history
|
12
|
+
|
13
|
+
field :_slugs, type: Array, default: []
|
14
|
+
alias_attribute :slugs, :_slugs
|
17
15
|
end
|
18
16
|
|
19
17
|
module ClassMethods
|
18
|
+
|
19
|
+
|
20
20
|
# @overload slug(*fields)
|
21
21
|
# Sets one ore more fields as source of slug.
|
22
22
|
# @param [Array] fields One or more fields the slug should be based on.
|
23
23
|
# @yield If given, the block is used to build a custom slug.
|
24
24
|
#
|
25
|
-
# @overload slug(*fields, options)
|
25
|
+
# @overload slug(*fields, options)
|
26
26
|
# Sets one ore more fields as source of slug.
|
27
27
|
# @param [Array] fields One or more fields the slug should be based on.
|
28
28
|
# @param [Hash] options
|
29
|
-
# @param options [String] :as The name of the field that stores the
|
30
|
-
# slug. Defaults to `slug`.
|
31
29
|
# @param options [Boolean] :history Whether a history of changes to
|
32
30
|
# the slug should be retained. When searched by slug, the document now
|
33
31
|
# matches both past and present slugs.
|
34
|
-
# @param options [Boolean] :index Whether an index should be defined
|
35
|
-
# on the slug field. Defaults to `false` and has no effect if the
|
36
|
-
# document is embedded.
|
37
|
-
# Make sure you have a unique index on the slugs of root documents to
|
38
|
-
# avoid race conditions.
|
39
32
|
# @param options [Boolean] :permanent Whether the slug should be
|
40
33
|
# immutable. Defaults to `false`.
|
41
34
|
# @param options [Array] :reserve` A list of reserved slugs
|
@@ -57,176 +50,145 @@ module Mongoid
|
|
57
50
|
#
|
58
51
|
def slug(*fields, &block)
|
59
52
|
options = fields.extract_options!
|
60
|
-
options[:history] = false if options[:permanent]
|
61
|
-
|
62
|
-
self.slug_scope = options[:scope]
|
63
|
-
self.reserved_words_in_slug = options[:reserve] || []
|
64
|
-
self.slug_name = options[:as] || :slug
|
65
|
-
self.slugged_attributes = fields.map(&:to_s)
|
66
|
-
if options[:history] && !options[:permanent]
|
67
|
-
self.slug_history_name = "#{self.slug_name}_history".to_sym
|
68
|
-
end
|
69
|
-
|
70
|
-
default_builder = lambda do |doc|
|
71
|
-
slugged_attributes.map { |f| doc.send f }.join ' '
|
72
|
-
end
|
73
|
-
self.slug_builder = block_given? ? block : default_builder
|
74
53
|
|
75
|
-
|
54
|
+
self.slug_scope = options[:scope]
|
55
|
+
self.reserved_words = options[:reserve] || Set.new([:new, :edit])
|
56
|
+
self.slugged_attributes = fields.map &:to_s
|
57
|
+
self.history = options[:history]
|
76
58
|
|
77
|
-
if
|
78
|
-
|
59
|
+
if slug_scope
|
60
|
+
index({slug_scope: 1, _slugs: 1}, {unique: true})
|
61
|
+
else
|
62
|
+
index({_slugs: 1}, {unique: true})
|
79
63
|
end
|
80
64
|
|
81
|
-
|
82
|
-
|
83
|
-
|
65
|
+
#-- Why is it necessary to customize the slug builder?
|
66
|
+
default_url_builder = lambda do |cur_object|
|
67
|
+
cur_object.slug_builder.to_url
|
84
68
|
end
|
85
69
|
|
86
|
-
|
87
|
-
|
70
|
+
self.url_builder = block_given? ? block : default_url_builder
|
71
|
+
|
72
|
+
#-- always create slug on create
|
73
|
+
#-- do not create new slug on update if the slug is permanent
|
74
|
+
if options[:permanent]
|
75
|
+
set_callback :create, :before, :build_slug
|
76
|
+
else
|
77
|
+
set_callback :save, :before, :build_slug, :if => :slug_should_be_rebuilt?
|
88
78
|
end
|
89
79
|
|
90
|
-
# Build a finder for slug.
|
91
|
-
#
|
92
|
-
# Defaults to `find_by_slug`.
|
93
|
-
instance_eval <<-CODE
|
94
|
-
def self.find_by_#{slug_name}(slug)
|
95
|
-
if slug_history_name
|
96
|
-
any_of({ slug_name => slug }, { slug_history_name => slug })
|
97
|
-
else
|
98
|
-
where(slug_name => slug)
|
99
|
-
end.first
|
100
|
-
end
|
101
80
|
|
102
|
-
|
103
|
-
self.find_by_#{slug_name}(slug) ||
|
104
|
-
raise(Mongoid::Errors::DocumentNotFound.new self, slug)
|
105
|
-
end
|
106
|
-
CODE
|
81
|
+
end
|
107
82
|
|
108
|
-
|
109
|
-
|
110
|
-
# Defaults to `by_slug`.
|
111
|
-
scope "by_#{slug_name}".to_sym, lambda { |slug|
|
112
|
-
if slug_history_name
|
113
|
-
any_of({ slug_name => slug }, { slug_history_name => slug })
|
114
|
-
else
|
115
|
-
where(slug_name => slug)
|
116
|
-
end
|
117
|
-
}
|
83
|
+
def look_like_slugs?(*args)
|
84
|
+
with_default_scope.look_like_slugs?(*args)
|
118
85
|
end
|
119
|
-
|
120
|
-
|
86
|
+
|
87
|
+
|
88
|
+
|
89
|
+
# Find documents by slugs.
|
121
90
|
#
|
122
|
-
#
|
123
|
-
# duplicates.
|
91
|
+
# A document matches if any of its slugs match one of the supplied params.
|
124
92
|
#
|
125
|
-
#
|
126
|
-
#
|
127
|
-
#
|
128
|
-
#
|
129
|
-
#
|
130
|
-
#
|
131
|
-
#
|
132
|
-
#
|
133
|
-
#
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
end
|
142
|
-
|
143
|
-
excluded_id = options[:model]._id if options[:model]
|
144
|
-
|
145
|
-
slug = desired_slug.to_url
|
146
|
-
|
147
|
-
# Regular expression that matches slug, slug-1, ... slug-n
|
148
|
-
# If slug_name field was indexed, MongoDB will utilize that
|
149
|
-
# index to match /^.../ pattern.
|
150
|
-
pattern = /^#{Regexp.escape(slug)}(?:-(\d+))?$/
|
151
|
-
|
152
|
-
if slug_scope &&
|
153
|
-
self.reflect_on_association(slug_scope).nil?
|
154
|
-
# scope is not an association, so it's scoped to a local field
|
155
|
-
# (e.g. an association id in a denormalized db design)
|
156
|
-
|
157
|
-
where_hash = {}
|
158
|
-
where_hash[slug_name] = pattern
|
159
|
-
where_hash[:_id.ne] = excluded_id if excluded_id
|
160
|
-
where_hash[slug_scope] = scope_attribute
|
161
|
-
|
162
|
-
existing_slugs =
|
163
|
-
deepest_document_superclass.
|
164
|
-
only(slug_name).
|
165
|
-
where(where_hash)
|
166
|
-
else
|
167
|
-
where_hash = {}
|
168
|
-
where_hash[slug_name] = pattern
|
169
|
-
where_hash[:_id.ne] = excluded_id if excluded_id
|
170
|
-
|
171
|
-
existing_slugs =
|
172
|
-
scope_object.
|
173
|
-
only(slug_name).
|
174
|
-
where(where_hash)
|
175
|
-
end
|
176
|
-
|
177
|
-
existing_slugs = existing_slugs.map do |doc|
|
178
|
-
doc.slug
|
179
|
-
end
|
93
|
+
# A document matching multiple supplied params will be returned only once.
|
94
|
+
#
|
95
|
+
# If any supplied param does not match a document a Mongoid::Errors::DocumentNotFound will be raised.
|
96
|
+
#
|
97
|
+
# @example Find by a slug.
|
98
|
+
# Model.find_by_slug!('some-slug')
|
99
|
+
#
|
100
|
+
# @example Find by multiple slugs.
|
101
|
+
# Model.find_by_slug!('some-slug', 'some-other-slug')
|
102
|
+
#
|
103
|
+
# @param [ Array<Object> ] args The slugs to search for.
|
104
|
+
#
|
105
|
+
# @return [ Array<Document>, Document ] The matching document(s).
|
106
|
+
def find_by_slug!(*args)
|
107
|
+
with_default_scope.find_by_slug!(*args)
|
108
|
+
end
|
180
109
|
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
# scope is not an association, so it's scoped to a local field
|
185
|
-
# (e.g. an association id in a denormalized db design)
|
110
|
+
def queryable
|
111
|
+
scope_stack.last || Criteria.new(self) # Use Mongoid::Slug::Criteria for slugged documents.
|
112
|
+
end
|
186
113
|
|
187
|
-
|
188
|
-
where_hash[slug_history_name.all] = [pattern]
|
189
|
-
where_hash[:_id.ne] = excluded_id if excluded_id
|
190
|
-
where_hash[slug_scope] = scope_attribute
|
114
|
+
end
|
191
115
|
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
116
|
+
# Builds a new slug.
|
117
|
+
#
|
118
|
+
# @return [true]
|
119
|
+
def build_slug
|
120
|
+
_new_slug = find_unique_slug
|
121
|
+
self._slugs.delete(_new_slug)
|
122
|
+
if self.history == true
|
123
|
+
self._slugs << _new_slug
|
124
|
+
else
|
125
|
+
self._slugs = [_new_slug]
|
126
|
+
end
|
127
|
+
true
|
128
|
+
end
|
129
|
+
|
130
|
+
|
131
|
+
# Finds a unique slug, were specified string used to generate a slug.
|
132
|
+
#
|
133
|
+
# Returned slug will the same as the specified string when there are no
|
134
|
+
# duplicates.
|
135
|
+
#
|
136
|
+
# @param [String] Desired slug
|
137
|
+
# @return [String] A unique slug
|
138
|
+
def find_unique_slug
|
139
|
+
|
140
|
+
_slug = self.url_builder.call(self)
|
199
141
|
|
200
|
-
|
201
|
-
|
142
|
+
# Regular expression that matches slug, slug-1, ... slug-n
|
143
|
+
# If slug_name field was indexed, MongoDB will utilize that
|
144
|
+
# index to match /^.../ pattern.
|
145
|
+
pattern = /^#{Regexp.escape(_slug)}(?:-(\d+))?$/
|
146
|
+
|
147
|
+
where_hash = {}
|
148
|
+
where_hash[:_slugs.all] = [pattern]
|
149
|
+
where_hash[:_id.ne] = self._id
|
150
|
+
|
151
|
+
if slug_scope && self.reflect_on_association(slug_scope).nil?
|
152
|
+
# scope is not an association, so it's scoped to a local field
|
153
|
+
# (e.g. an association id in a denormalized db design)
|
154
|
+
where_hash[slug_scope] = self.try(:read_attribute, slug_scope)
|
155
|
+
|
156
|
+
end
|
157
|
+
|
158
|
+
history_slugged_documents =
|
159
|
+
uniqueness_scope.
|
202
160
|
where(where_hash)
|
203
|
-
end
|
204
161
|
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
162
|
+
existing_slugs = []
|
163
|
+
existing_history_slugs = []
|
164
|
+
last_entered_slug = []
|
165
|
+
history_slugged_documents.each do |doc|
|
166
|
+
history_slugs = doc._slugs
|
167
|
+
next if history_slugs.nil?
|
168
|
+
existing_slugs.push(*history_slugs.find_all { |cur_slug| cur_slug =~ pattern })
|
169
|
+
last_entered_slug.push(*history_slugs.last) if history_slugs.last =~ pattern
|
170
|
+
existing_history_slugs.push(*history_slugs.first(history_slugs.length() -1).find_all { |cur_slug| cur_slug =~ pattern })
|
171
|
+
end
|
211
172
|
|
212
|
-
|
213
|
-
|
214
|
-
if slug_scope && existing_slugs.count == 0 && existing_history_slugs.count > 0
|
215
|
-
history_slugged_documents.each do |doc|
|
216
|
-
doc_history_slugs = doc.read_attribute(slug_history_name)
|
217
|
-
next if doc_history_slugs.nil?
|
218
|
-
doc_history_slugs -= existing_history_slugs
|
219
|
-
doc.write_attribute(slug_history_name, doc_history_slugs)
|
220
|
-
doc.save
|
221
|
-
end
|
222
|
-
existing_history_slugs = []
|
223
|
-
end
|
173
|
+
#do not allow a slug that can be interpreted as the current document id
|
174
|
+
existing_slugs << _slug unless self.class.look_like_slugs?([_slug])
|
224
175
|
|
225
|
-
|
226
|
-
|
176
|
+
#make sure that the slug is not equal to a reserved word
|
177
|
+
if reserved_words.any? { |word| word === _slug }
|
178
|
+
existing_slugs << _slug
|
179
|
+
end
|
227
180
|
|
228
|
-
|
229
|
-
|
181
|
+
#only look for a new unique slug if the existing slugs contains the current slug
|
182
|
+
# - e.g if the slug 'foo-2' is taken, but 'foo' is available, the user can use 'foo'.
|
183
|
+
if existing_slugs.include? _slug
|
184
|
+
# If the only conflict is in the history of a document in the same scope,
|
185
|
+
# transfer the slug
|
186
|
+
if slug_scope && last_entered_slug.count == 0 && existing_history_slugs.count > 0
|
187
|
+
history_slugged_documents.each do |doc|
|
188
|
+
doc._slugs -= existing_history_slugs
|
189
|
+
doc.save
|
190
|
+
end
|
191
|
+
existing_slugs = []
|
230
192
|
end
|
231
193
|
|
232
194
|
if existing_slugs.count > 0
|
@@ -235,119 +197,90 @@ module Mongoid
|
|
235
197
|
# slug, slug-1, slug-2, ..., slug-n
|
236
198
|
existing_slugs.sort! do |a, b|
|
237
199
|
(pattern.match(a)[1] || -1).to_i <=>
|
238
|
-
|
200
|
+
(pattern.match(b)[1] || -1).to_i
|
239
201
|
end
|
240
202
|
max = existing_slugs.last.match(/-(\d+)$/).try(:[], 1).to_i
|
241
203
|
|
242
|
-
|
243
|
-
end
|
244
|
-
|
245
|
-
slug
|
246
|
-
end
|
247
|
-
|
248
|
-
private
|
249
|
-
|
250
|
-
def uniqueness_scope(model = nil)
|
251
|
-
if model
|
252
|
-
if slug_scope && (metadata = self.reflect_on_association(slug_scope))
|
253
|
-
parent = model.send(metadata.name)
|
254
|
-
|
255
|
-
# Make sure doc is actually associated with something, and that
|
256
|
-
# some referenced docs have been persisted to the parent
|
257
|
-
#
|
258
|
-
# TODO: we need better reflection for reference associations,
|
259
|
-
# like association_name instead of forcing collection_name here
|
260
|
-
# -- maybe in the forthcoming Mongoid refactorings?
|
261
|
-
inverse = metadata.inverse_of || collection_name
|
262
|
-
return parent.respond_to?(inverse) ? parent.send(inverse) : self
|
263
|
-
end
|
264
|
-
if embedded?
|
265
|
-
parent_metadata = reflect_on_all_associations(:embedded_in)[0]
|
266
|
-
return model._parent.send(parent_metadata.inverse_of || model.metadata.name)
|
267
|
-
end
|
204
|
+
_slug += "-#{max + 1}"
|
268
205
|
end
|
269
|
-
deepest_document_superclass
|
270
|
-
end
|
271
|
-
|
272
|
-
def deepest_document_superclass
|
273
|
-
appropriate_class = self
|
274
|
-
while appropriate_class.superclass.include?(Mongoid::Document)
|
275
|
-
appropriate_class = appropriate_class.superclass
|
276
|
-
end
|
277
|
-
appropriate_class
|
278
|
-
end
|
279
|
-
end
|
280
206
|
|
281
|
-
# Builds a new slug.
|
282
|
-
#
|
283
|
-
# @return [true]
|
284
|
-
def build_slug
|
285
|
-
old = slug
|
286
|
-
write_attribute slug_name, find_unique_slug
|
287
|
-
|
288
|
-
# @note I find it odd that we can't use `slug_was`, `slug_changed?`, or
|
289
|
-
# `read_attribute (slug_history_name)` here.
|
290
|
-
|
291
|
-
if slug_history_name && old && old != slug
|
292
|
-
self.send(slug_history_name).<<(old).uniq!
|
293
207
|
end
|
294
208
|
|
295
|
-
|
209
|
+
_slug
|
296
210
|
end
|
297
211
|
|
298
|
-
# Finds a unique slug, were specified string used to generate a slug.
|
299
|
-
#
|
300
|
-
# Returned slug will the same as the specified string when there are no
|
301
|
-
# duplicates.
|
302
|
-
#
|
303
|
-
# @param [String] Desired slug
|
304
|
-
# @return [String] A unique slug
|
305
|
-
def find_unique_slug_for(desired_slug)
|
306
|
-
self.class.find_unique_slug_for desired_slug, :model => self
|
307
|
-
end
|
308
212
|
|
309
213
|
# @return [Boolean] Whether the slug requires to be rebuilt
|
310
214
|
def slug_should_be_rebuilt?
|
311
|
-
new_record? or
|
312
|
-
end
|
313
|
-
|
314
|
-
unless self.respond_to? :slug
|
315
|
-
def slug
|
316
|
-
read_attribute slug_name
|
317
|
-
end
|
318
|
-
|
319
|
-
def slug_changed?
|
320
|
-
attribute_changed? slug_name
|
321
|
-
end
|
322
|
-
|
323
|
-
def slug_was
|
324
|
-
attribute_was slug_name
|
325
|
-
end
|
215
|
+
new_record? or _slugs_changed? or slugged_attributes_changed?
|
326
216
|
end
|
327
217
|
|
328
218
|
def slugged_attributes_changed?
|
329
|
-
slugged_attributes.any? { |f| attribute_changed? f }
|
219
|
+
slugged_attributes.any? { |f| attribute_changed? f.to_s }
|
330
220
|
end
|
331
221
|
|
332
222
|
# @return [String] A string which Action Pack uses for constructing an URL
|
333
223
|
# to this record.
|
334
224
|
def to_param
|
335
|
-
unless
|
225
|
+
unless _slugs.last
|
336
226
|
build_slug
|
337
227
|
save
|
338
228
|
end
|
339
229
|
|
340
|
-
|
230
|
+
_slugs.last
|
341
231
|
end
|
342
|
-
|
343
|
-
private
|
232
|
+
alias_method :slug, :to_param
|
344
233
|
|
345
|
-
def
|
346
|
-
|
234
|
+
def slug_builder
|
235
|
+
|
236
|
+
_cur_slug = nil
|
237
|
+
if (new_record? and _slugs.present?) or (persisted? and _slugs_changed?)
|
238
|
+
#user defined slug
|
239
|
+
_cur_slug = _slugs.last
|
240
|
+
end
|
241
|
+
|
242
|
+
#generate slug if the slug is not user defined or does not exist
|
243
|
+
unless _cur_slug
|
244
|
+
self.slugged_attributes.map { |f| self.send f }.join ' '
|
245
|
+
else
|
246
|
+
_cur_slug
|
247
|
+
end
|
347
248
|
end
|
348
249
|
|
349
|
-
|
350
|
-
|
250
|
+
private
|
251
|
+
|
252
|
+
def uniqueness_scope
|
253
|
+
|
254
|
+
if slug_scope &&
|
255
|
+
metadata = self.reflect_on_association(slug_scope)
|
256
|
+
|
257
|
+
parent = self.send(metadata.name)
|
258
|
+
|
259
|
+
# Make sure doc is actually associated with something, and that
|
260
|
+
# some referenced docs have been persisted to the parent
|
261
|
+
#
|
262
|
+
# TODO: we need better reflection for reference associations,
|
263
|
+
# like association_name instead of forcing collection_name here
|
264
|
+
# -- maybe in the forthcoming Mongoid refactorings?
|
265
|
+
inverse = metadata.inverse_of || collection_name
|
266
|
+
return parent.respond_to?(inverse) ? parent.send(inverse) : self.class
|
267
|
+
|
268
|
+
end
|
269
|
+
|
270
|
+
if self.embedded?
|
271
|
+
parent_metadata = reflect_on_all_associations(:embedded_in)[0]
|
272
|
+
return self._parent.send(parent_metadata.inverse_of || self.metadata.name)
|
273
|
+
end
|
274
|
+
|
275
|
+
#unless embedded or slug scope, return the deepest document superclass
|
276
|
+
appropriate_class = self.class
|
277
|
+
while appropriate_class.superclass.include?(Mongoid::Document)
|
278
|
+
appropriate_class = appropriate_class.superclass
|
279
|
+
end
|
280
|
+
appropriate_class
|
281
|
+
|
351
282
|
end
|
283
|
+
|
352
284
|
end
|
353
285
|
end
|
286
|
+
|