friendly_id4 4.0.0.pre → 4.0.0.pre3
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/.gitignore +9 -0
- data/ABOUT.md +139 -0
- data/Guide.md +405 -0
- data/README.md +65 -47
- data/Rakefile +12 -16
- data/bench.rb +71 -0
- data/friendly_id.gemspec +29 -0
- data/lib/friendly_id.rb +9 -116
- data/lib/friendly_id/base.rb +29 -0
- data/lib/friendly_id/configuration.rb +31 -0
- data/lib/friendly_id/finder_methods.rb +14 -0
- data/lib/friendly_id/history.rb +29 -0
- data/lib/friendly_id/migration.rb +17 -0
- data/lib/friendly_id/model.rb +22 -0
- data/lib/friendly_id/object_utils.rb +27 -0
- data/lib/friendly_id/scoped.rb +24 -2
- data/lib/friendly_id/slug_sequencer.rb +79 -0
- data/lib/friendly_id/slugged.rb +11 -84
- data/lib/friendly_id/version.rb +1 -1
- data/lib/generators/friendly_id_generator.rb +21 -0
- data/test/core_test.rb +23 -56
- data/test/helper.rb +41 -0
- data/test/history_test.rb +46 -0
- data/test/object_utils_test.rb +18 -0
- data/test/scoped_test.rb +30 -49
- data/test/shared.rb +47 -0
- data/test/slugged_test.rb +31 -52
- metadata +56 -22
- data/lib/friendly_id/test.rb +0 -23
- data/lib/friendly_id/test/generic.rb +0 -84
- data/test/test_helper.rb +0 -23
data/.gitignore
ADDED
data/ABOUT.md
ADDED
@@ -0,0 +1,139 @@
|
|
1
|
+
# FriendlyId 4
|
2
|
+
|
3
|
+
This is a rewrite/rethink of FriendlyId. It will probably be released some time
|
4
|
+
in August or September 2011, once I've had the chance to actually use it in a
|
5
|
+
real website for a while.
|
6
|
+
|
7
|
+
It's probably not wise to use this on a real site right now unless you're
|
8
|
+
comfortable with the source code and willing to fix bugs that will likely occur.
|
9
|
+
|
10
|
+
That said, I will soon be deploying this on a high-traffic, production site, so
|
11
|
+
I have a personal stake in making this work well. Your feedback is most welcome.
|
12
|
+
|
13
|
+
## Back to basics
|
14
|
+
|
15
|
+
This isn't the "big rewrite," it's the "small rewrite."
|
16
|
+
|
17
|
+
Adding new features with each release is not sustainable. This release *removes*
|
18
|
+
features, but makes it possible to add them back as addons. We can also remove
|
19
|
+
some complexity by relying on the better default functionality provided by newer
|
20
|
+
versions of Active Support and Active Record. Let's see how small we can make
|
21
|
+
this!
|
22
|
+
|
23
|
+
Here's what's changed:
|
24
|
+
|
25
|
+
## Active Record 3+ only
|
26
|
+
|
27
|
+
For 2.3 support, you can use FriendlyId 3, which will continue to be maintained
|
28
|
+
until people don't want it any more.
|
29
|
+
|
30
|
+
## In-table slugs
|
31
|
+
|
32
|
+
FriendlyId no longer creates a separate slugs table - it just stores the
|
33
|
+
generated slug value in the model table, which is simpler, faster and what most
|
34
|
+
people seem to want. Keeping slug history in a separate table is an optional
|
35
|
+
add-on for FriendlyId 4.
|
36
|
+
|
37
|
+
## No more multiple finds
|
38
|
+
|
39
|
+
Person.find "joe-schmoe" # Supported
|
40
|
+
Person.find ["joe-schmoe", "john-doe"] # No longer supported
|
41
|
+
|
42
|
+
If you want find by more than one friendly id, build your own query:
|
43
|
+
|
44
|
+
Person.where(:slug => ["joe-schmoe", "john-doe"])
|
45
|
+
|
46
|
+
This lets us do *far* less monkeypatching in Active Record. How much less?
|
47
|
+
FriendlyId overrides the base find with a mere 2 lines of code, and otherwise
|
48
|
+
changes nothing else. This means more stability and less breakage between Rails
|
49
|
+
updates.
|
50
|
+
|
51
|
+
## No more finder status
|
52
|
+
|
53
|
+
FriendlyId 3 offered finder statuses to help you determine when an outdated
|
54
|
+
or non-friendly id was used to find the record, so that you could decide whether
|
55
|
+
to permanently redirect to the canonical URL. However, there's a simpler way to
|
56
|
+
do that, so this feature has been removed:
|
57
|
+
|
58
|
+
if request.path != person_path(@person)
|
59
|
+
return redirect_to @person, :status => :moved_permanently
|
60
|
+
end
|
61
|
+
|
62
|
+
## No more slug history - unless you want it
|
63
|
+
|
64
|
+
Since slugs are now stored in-table, when you update them, finds for the
|
65
|
+
previous slug will no longer work. This can be a problem for permalinks, since
|
66
|
+
renaming a friendly_id will lead to 404's.
|
67
|
+
|
68
|
+
This was transparently handled by FriendlyId 3, but there were three problems:
|
69
|
+
|
70
|
+
* Not everybody wants or needs this
|
71
|
+
* Performance was negatively affected
|
72
|
+
* Determining whether a current or old id was used was expensive, clunky, and
|
73
|
+
inconsistent when finding inside relations.
|
74
|
+
|
75
|
+
Here's how to do this in FriendlyId 4:
|
76
|
+
|
77
|
+
class PostsController < ApplicationController
|
78
|
+
|
79
|
+
before_filter :find_post
|
80
|
+
|
81
|
+
...
|
82
|
+
|
83
|
+
def find_post
|
84
|
+
return unless params[:id]
|
85
|
+
@post = begin
|
86
|
+
Post.find params[:id]
|
87
|
+
rescue ActiveRecord::RecordNotFound
|
88
|
+
Post.find_by_friendly_id params[:id]
|
89
|
+
end
|
90
|
+
# If an old id or a numeric id was used to find the record, then
|
91
|
+
# the request path will not match the post_path, and we should do
|
92
|
+
# a 301 redirect that uses the current friendly_id
|
93
|
+
if request.path != post_path(@post)
|
94
|
+
return redirect_to @post, :status => :moved_permanently
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
Under FriendlyId 4 this is a little more verbose, but offers much finer-grained
|
99
|
+
controler over the finding process, performs better, and has a much simpler
|
100
|
+
implementation.
|
101
|
+
|
102
|
+
## "Reserved words" are now just a normal validation
|
103
|
+
|
104
|
+
Rather than use a custom reserved words validator, use the validations provided
|
105
|
+
by Active Record. FriendlyId still reserves "new" and "edit" by default to avoid
|
106
|
+
routing problems.
|
107
|
+
|
108
|
+
validates_exclusion_of :name, :in => ["bad", "word"]
|
109
|
+
|
110
|
+
You can configure the default words reserved by FriendlyId in
|
111
|
+
`FriendlyId::Configuration::DEFAULTS[:reserved_words]`.
|
112
|
+
|
113
|
+
## "Allow nil" is now just another validation
|
114
|
+
|
115
|
+
Previous versions of FriendlyId offered a special option to allow nil slug
|
116
|
+
values, but this is now the default. If you don't want this, then simply add a
|
117
|
+
validation to the slug column, and/or declare the column `NOT NULL` in your
|
118
|
+
database.
|
119
|
+
|
120
|
+
## Bye-bye Babosa
|
121
|
+
|
122
|
+
[Babosa](http://github.com/norman/babosa) is FriendlyId 3's slugging library.
|
123
|
+
|
124
|
+
FriendlyId 4 doesn't use it by default because the most important pieces of it
|
125
|
+
were already accepted into Active Support 3.
|
126
|
+
|
127
|
+
However, Babosa is still useful, for example, for idiomatically transliterating
|
128
|
+
Cyrillic ([or other
|
129
|
+
language](https://github.com/norman/babosa/tree/master/lib/babosa/transliterator))
|
130
|
+
strings to ASCII. It's very easy to include - just override
|
131
|
+
`#normalize_friendly_id` in your model:
|
132
|
+
|
133
|
+
class MyModel < ActiveRecord::Base
|
134
|
+
...
|
135
|
+
|
136
|
+
def normalize_friendly_id(text)
|
137
|
+
text.to_slug.normalize! :transliterate => :russian
|
138
|
+
end
|
139
|
+
end
|
data/Guide.md
ADDED
@@ -0,0 +1,405 @@
|
|
1
|
+
# FriendlyId Guide
|
2
|
+
|
3
|
+
* Table of Contents
|
4
|
+
{:toc}
|
5
|
+
|
6
|
+
## Overview
|
7
|
+
|
8
|
+
FriendlyId is an ORM-centric Ruby library that lets you work with human-friendly
|
9
|
+
strings as if they were numeric ids. Among other things, this facilitates
|
10
|
+
replacing "unfriendly" URL's like:
|
11
|
+
|
12
|
+
http://example.com/states/4323454
|
13
|
+
|
14
|
+
with "friendly" ones such as:
|
15
|
+
|
16
|
+
http://example.com/states/washington
|
17
|
+
|
18
|
+
FriendlyId is typically used with Rails and Active Record, but can also be used in
|
19
|
+
non-Rails applications.
|
20
|
+
|
21
|
+
## Simple Models
|
22
|
+
|
23
|
+
The simplest way to use FriendlyId is with a model that has a uniquely indexed
|
24
|
+
column with no spaces or special characters, and that is seldom or never
|
25
|
+
updated. The most common example of this is a user name or login column:
|
26
|
+
|
27
|
+
class User < ActiveRecord::Base
|
28
|
+
validates_format_of :login, :with => /\A[a-z0-9]+\z/i
|
29
|
+
has_friendly_id :login
|
30
|
+
end
|
31
|
+
|
32
|
+
@user = User.find "joe" # the old User.find(1) still works, too
|
33
|
+
@user.to_param # returns "joe"
|
34
|
+
redirect_to @user # the URL will be /users/joe
|
35
|
+
|
36
|
+
In this case, FriendlyId assumes you want to use the column as-is; it will never
|
37
|
+
modify the value of the column, and your application should ensure that the value
|
38
|
+
is admissible in a URL:
|
39
|
+
|
40
|
+
class City < ActiveRecord::Base
|
41
|
+
has_friendly_id :name
|
42
|
+
end
|
43
|
+
|
44
|
+
@city.find "Viña del Mar"
|
45
|
+
redirect_to @city # the URL will be /cities/Viña%20del%20Mar
|
46
|
+
|
47
|
+
For this reason, it is often more convenient to use Slugs rather than a single
|
48
|
+
column.
|
49
|
+
|
50
|
+
## Slugged Models
|
51
|
+
|
52
|
+
FriendlyId uses a separate column to store slugs for models which require some
|
53
|
+
processing of the friendly_id text. The most common example is a blog post's
|
54
|
+
title, which may have spaces, uppercase characters, or other attributes you
|
55
|
+
wish to modify to make them more suitable for use in URL's.
|
56
|
+
|
57
|
+
class Post < ActiveRecord::Base
|
58
|
+
include FriendlyId::Slugged
|
59
|
+
has_friendly_id :title
|
60
|
+
end
|
61
|
+
|
62
|
+
@post = Post.create(:title => "This is the first post!")
|
63
|
+
@post.friendly_id # returns "this-is-the-first-post"
|
64
|
+
redirect_to @post # the URL will be /posts/this-is-the-first-post
|
65
|
+
|
66
|
+
If you are unsure whether to use slugs, then your best bet is to use them,
|
67
|
+
because FriendlyId provides many useful features that only work with this
|
68
|
+
feature. These features are explained in detail {file:Guide.md#features below}.
|
69
|
+
|
70
|
+
## Installation
|
71
|
+
|
72
|
+
gem install friendly_id
|
73
|
+
|
74
|
+
After installing the gem, add an entry in the Gemfile:
|
75
|
+
|
76
|
+
gem "friendly_id", "~> 4.0.0"
|
77
|
+
|
78
|
+
### Future Compatibility
|
79
|
+
|
80
|
+
FriendlyId will always remain compatible with the current release of Rails, and
|
81
|
+
at least one stable release behind. That means that support for 3.0.x will not be
|
82
|
+
dropped until a stable release of 3.2 is out, or possibly longer.
|
83
|
+
|
84
|
+
## Configuration
|
85
|
+
|
86
|
+
FriendlyId is configured in your model using the `has_friendly_id` method. Additional
|
87
|
+
features can be activated by including various modules:
|
88
|
+
|
89
|
+
class Post < ActiveRecord::Base
|
90
|
+
# use slugs
|
91
|
+
include FriendlyId::Slugged
|
92
|
+
# record slug history
|
93
|
+
include FriendlyId::History
|
94
|
+
# use the "title" accessor as the basis of the friendly_id
|
95
|
+
has_friendly_id :title
|
96
|
+
end
|
97
|
+
|
98
|
+
Read on to learn about the various features that can be configured. For the
|
99
|
+
full list of valid configuration options, see the instance attribute summary
|
100
|
+
for {FriendlyId::Configuration}.
|
101
|
+
|
102
|
+
# Features
|
103
|
+
|
104
|
+
## FriendlyId Strings
|
105
|
+
|
106
|
+
By default, FriendlyId uses Active Support's Transliterator class to convert strings into
|
107
|
+
ASCII slugs by default. Please see the API docs for
|
108
|
+
[transliterate](http://api.rubyonrails.org/) and
|
109
|
+
[parameterize](http://api.rubyonrails.org/) to see what options are avaialable
|
110
|
+
to you.
|
111
|
+
|
112
|
+
Previous versions of FriendlyId used [Babosa](github.com/norman/babosa) for slug
|
113
|
+
string handling, but the core functionality it provides was extracted from it
|
114
|
+
and added to Rails 3. However, Babosa offers some advanced functionality not
|
115
|
+
offered by Rails and can still be a convenient option. This section shows how
|
116
|
+
you can use it with FriendlyId.
|
117
|
+
|
118
|
+
### Using a Custom Method to Generate the Slug Text
|
119
|
+
|
120
|
+
FriendlyId can use either a column or a method to generate the slug text for
|
121
|
+
your model:
|
122
|
+
|
123
|
+
class City < ActiveRecord::Base
|
124
|
+
|
125
|
+
belongs_to :country
|
126
|
+
has_friendly_id :name_and_country, :use_slug => true
|
127
|
+
|
128
|
+
def name_and_country
|
129
|
+
#{name} #{country.name}
|
130
|
+
end
|
131
|
+
|
132
|
+
end
|
133
|
+
|
134
|
+
@country = Country.create(:name => "Argentina")
|
135
|
+
@city = City.create(:name => "Buenos Aires", :country => @country)
|
136
|
+
@city.friendly_id # will be "buenos-aires-argentina"
|
137
|
+
|
138
|
+
One word of caution: in the example above, if the country's name were updated,
|
139
|
+
say, to "Argentine Republic", the city's friendly_id would not be
|
140
|
+
automatically updated. For this reason, it's a good idea to avoid using
|
141
|
+
frequently-updated relations as a part of the friendly_id.
|
142
|
+
|
143
|
+
## Using a Custom Method to Process the Slug Text
|
144
|
+
|
145
|
+
If the built-in slug text handling options don't work for your application,
|
146
|
+
you can override the `normalize_friendly_id` method in your model class in
|
147
|
+
order to fine-tune the output:
|
148
|
+
|
149
|
+
class City < ActiveRecord::Base
|
150
|
+
|
151
|
+
def normalize_friendly_id(text)
|
152
|
+
my_text_modifier_method(text)
|
153
|
+
end
|
154
|
+
|
155
|
+
end
|
156
|
+
|
157
|
+
The normalize_friendly_id method takes a single argument and receives an
|
158
|
+
instance of {FriendlyId::SlugString}, a class which wraps a regular Ruby string
|
159
|
+
with additional formatting options.
|
160
|
+
|
161
|
+
### Converting non-Latin characters to ASCII with Babosa
|
162
|
+
|
163
|
+
Babosa offers the ability to idiomatically transliterate non-ASCII characters
|
164
|
+
to ASCII:
|
165
|
+
|
166
|
+
"Jürgen".to_slug.normalize! #=> "Jurgen"
|
167
|
+
"Jürgen".to_slug.normalize! :transliterate => :german #=> "Juergen"
|
168
|
+
|
169
|
+
Using Babosa with FriendlyId is a simple matter of installing and requiring
|
170
|
+
the `babosa` gem, and overriding the `normalize_friendly_id` method in your
|
171
|
+
model:
|
172
|
+
|
173
|
+
class City < ActiveRecord::Base
|
174
|
+
def normalize_friendly_id(text)
|
175
|
+
text.slug.normalize!
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
## Redirecting to the Current Friendly URL
|
180
|
+
|
181
|
+
FriendlyId can maintain a history of your record's older slugs, so if your
|
182
|
+
record's friendly_id changes, your URL's won't break.
|
183
|
+
|
184
|
+
class Post < ActiveRecord::Base
|
185
|
+
include FriendlyId::Slugged
|
186
|
+
include FriendlyId::History
|
187
|
+
has_friendly_id :title
|
188
|
+
end
|
189
|
+
|
190
|
+
class PostsController < ApplicationController
|
191
|
+
|
192
|
+
before_filter :find_post
|
193
|
+
|
194
|
+
...
|
195
|
+
def find_post
|
196
|
+
return unless params[:id]
|
197
|
+
@post = begin
|
198
|
+
Post.find params[:id]
|
199
|
+
rescue ActiveRecord::RecordNotFound
|
200
|
+
Post.find_by_friendly_id params[:id]
|
201
|
+
end
|
202
|
+
# If an old id or a numeric id was used to find the record, then
|
203
|
+
# the request path will not match the post_path, and we should do
|
204
|
+
# a 301 redirect that uses the current friendly_id
|
205
|
+
if request.path != post_path(@post)
|
206
|
+
return redirect_to @post, :status => :moved_permanently
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
## Non-unique Slugs
|
212
|
+
|
213
|
+
FriendlyId will append a arbitrary number to the end of the id to keep it
|
214
|
+
unique if necessary:
|
215
|
+
|
216
|
+
/posts/new-version-released
|
217
|
+
/posts/new-version-released--2
|
218
|
+
/posts/new-version-released--3
|
219
|
+
...
|
220
|
+
etc.
|
221
|
+
|
222
|
+
Note that the number is preceded by "--" rather than "-" to distinguish it from
|
223
|
+
the rest of the slug. This is important to enable having slugs like:
|
224
|
+
|
225
|
+
/cars/peugeot-206
|
226
|
+
/cars/peugeot-206--2
|
227
|
+
|
228
|
+
You can configure the separator string used by your model by setting the
|
229
|
+
`:sequence_separator` option in `has_friendly_id`:
|
230
|
+
|
231
|
+
has_friendly_id :title, :use_slug => true, :sequence_separator => ":"
|
232
|
+
|
233
|
+
You can also override the default used in
|
234
|
+
{FriendlyId::Configuration::DEFAULTS} to set the value for any model using
|
235
|
+
FriendlyId. If you change this value in an existing application, be sure to
|
236
|
+
{file:Guide.md#regenerating_slugs regenerate the slugs} afterwards.
|
237
|
+
|
238
|
+
For reasons I hope are obvious, you can't change this value to "-". If you try,
|
239
|
+
FriendlyId will raise an error.
|
240
|
+
|
241
|
+
## Reserved Words
|
242
|
+
|
243
|
+
You can configure a list of strings as reserved so that, for example, you
|
244
|
+
don't end up with this problem:
|
245
|
+
|
246
|
+
/users/joe-schmoe # A user chose "joe schmoe" as his user name - no worries.
|
247
|
+
/users/new # A user chose "new" as his user name, and now no one can sign up.
|
248
|
+
|
249
|
+
Reserved words are configured using the `:reserved_words` option:
|
250
|
+
|
251
|
+
class Restaurant < ActiveRecord::Base
|
252
|
+
belongs_to :city
|
253
|
+
has_friendly_id :name, :use_slug => true, :reserved_words => ["my", "values"]
|
254
|
+
end
|
255
|
+
|
256
|
+
The reserved words can be specified as an array or (since 3.1.7) as a regular
|
257
|
+
expression.
|
258
|
+
|
259
|
+
The strings "new" and "index" are reserved by default. When you attempt to
|
260
|
+
store a reserved value, FriendlyId raises a
|
261
|
+
{FriendlyId::ReservedError}. You can also override the default
|
262
|
+
reserved words in {FriendlyId::Configuration::DEFAULTS} to set the value for any
|
263
|
+
model using FriendlyId.
|
264
|
+
|
265
|
+
If you'd like to show a validation error when a word is reserved, you can add
|
266
|
+
an callback to your model that catches the error:
|
267
|
+
|
268
|
+
class Person < ActiveRecord::Base
|
269
|
+
has_friendly_id :name, :use_slug => true
|
270
|
+
|
271
|
+
after_validation :validate_reserved
|
272
|
+
|
273
|
+
def validate_reserved
|
274
|
+
slug_text
|
275
|
+
rescue FriendlyId::ReservedError
|
276
|
+
@errors[friendly_id_config.method] = "is reserved. Please choose something else"
|
277
|
+
return false
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
## Scoped Slugs
|
282
|
+
|
283
|
+
FriendlyId can generate unique slugs within a given scope. For example, assume
|
284
|
+
you have an application that displays restaurants. Without scoped slugs, if two
|
285
|
+
restaurants are named "Joe's Diner," the second one will end up with
|
286
|
+
"joes-diner--2" as its friendly_id. Using scoped allows you to keep the slug
|
287
|
+
names unique for each city, so that the second "Joe's Diner" can also have the
|
288
|
+
slug "joes-diner", as long as it's located in a different city:
|
289
|
+
|
290
|
+
class Restaurant < ActiveRecord::Base
|
291
|
+
belongs_to :city
|
292
|
+
include FriendlyId::Slugged
|
293
|
+
include FriendlyId::Scoped
|
294
|
+
has_friendly_id :name, :scope => :city
|
295
|
+
end
|
296
|
+
|
297
|
+
class City < ActiveRecord::Base
|
298
|
+
has_many :restaurants
|
299
|
+
include FriendlyId::Slugged
|
300
|
+
has_friendly_id :name
|
301
|
+
end
|
302
|
+
|
303
|
+
City.find("seattle").restaurants.find("joes-diner")
|
304
|
+
City.find("chicago").restaurants.find("joes-diner")
|
305
|
+
|
306
|
+
|
307
|
+
The value for the `:scope` key in your model can be a column, or the name of a
|
308
|
+
relation.
|
309
|
+
|
310
|
+
### Complications with Scoped Slugs
|
311
|
+
|
312
|
+
#### Finding Records by friendly\_id
|
313
|
+
|
314
|
+
If you are using scopes your friendly ids may not be unique, so a simple find like
|
315
|
+
|
316
|
+
Restaurant.find("joes-diner")
|
317
|
+
|
318
|
+
may return the wrong record. In these cases when you want to use the friendly\_id for queries,
|
319
|
+
either query as a relation, or specify the scope in your query conditions:
|
320
|
+
|
321
|
+
# will only return restaurants named "Joe's Diner" in the given city
|
322
|
+
@city.restaurants.find("joes-diner")
|
323
|
+
|
324
|
+
# or
|
325
|
+
|
326
|
+
Restaurants.find("joes-diner").where(:city_id => @city.id)
|
327
|
+
|
328
|
+
|
329
|
+
#### Finding All Records That Match a Scoped ID
|
330
|
+
|
331
|
+
If you want to find all records with a particular friendly\_id regardless of scope,
|
332
|
+
the easiest way is to use cached slugs and query this column directly:
|
333
|
+
|
334
|
+
Restaurant.find_all_by_slug("joes-diner")
|
335
|
+
|
336
|
+
### Routes for Scoped Models
|
337
|
+
|
338
|
+
Note that FriendlyId does not set up any routes for scoped models; you must do
|
339
|
+
this yourself in your application. Here's an example of one way to set this up:
|
340
|
+
|
341
|
+
# in routes.rb
|
342
|
+
resources :cities do
|
343
|
+
resources :restaurants
|
344
|
+
end
|
345
|
+
|
346
|
+
# in views
|
347
|
+
<%= link_to 'Show', [@city, @restaurant] %>
|
348
|
+
|
349
|
+
# in controllers
|
350
|
+
@city = City.find(params[:city_id])
|
351
|
+
@restaurant = @city.restaurants.find(params[:id])
|
352
|
+
|
353
|
+
# URL's:
|
354
|
+
http://example.org/cities/seattle/restaurants/joes-diner
|
355
|
+
http://example.org/cities/chicago/restaurants/joes-diner
|
356
|
+
|
357
|
+
|
358
|
+
# Misc tips
|
359
|
+
|
360
|
+
## Allowing Users to Override/Control Slugs
|
361
|
+
|
362
|
+
Would you like to mostly use default slugs, but allow the option of a
|
363
|
+
custom user-chosen slug in your application? If so, then you're not the first to
|
364
|
+
want this. Here's a [demo
|
365
|
+
application](http://github.com/norman/friendly_id_manual_slug_demo) showing how
|
366
|
+
it can be done.
|
367
|
+
|
368
|
+
## Default Scopes
|
369
|
+
|
370
|
+
Whether you're using FriendlyId or not, a good rule of thumb for default scopes
|
371
|
+
is to always use your model's table name. Otherwise any time you do a join, you
|
372
|
+
risk having queries fail because of duplicate column names - particularly for a
|
373
|
+
default scope like this one:
|
374
|
+
|
375
|
+
default_scope :order => "created_at DESC"
|
376
|
+
|
377
|
+
Instead, do this:
|
378
|
+
|
379
|
+
default_scope :order => = "#{quoted_table_name}.created_at DESC"
|
380
|
+
|
381
|
+
Or even better, unless you're using a custom primary key:
|
382
|
+
|
383
|
+
default_scope :order => = "#{quoted_table_name}.id DESC"
|
384
|
+
|
385
|
+
because sorting by a unique integer column is faster than sorting by a date
|
386
|
+
column.
|
387
|
+
|
388
|
+
## Some Benchmarks
|
389
|
+
|
390
|
+
These benchmarks can give you an idea of FriendlyId's impact on the
|
391
|
+
performance of your application. Of course your results may vary.
|
392
|
+
|
393
|
+
activerecord (3.0.9)
|
394
|
+
ruby 1.9.2p180 (2011-02-18 revision 30909) [x86_64-darwin10.6.0]
|
395
|
+
friendly_id (4.0.0.pre3)
|
396
|
+
sqlite3 (1.3.3) gem
|
397
|
+
sqlite3 3.6.12 in-memory database
|
398
|
+
|
399
|
+
user system total real
|
400
|
+
find (without FriendlyId) 0.280000 0.000000 0.280000 ( 0.278086)
|
401
|
+
find (in-table slug) 0.320000 0.000000 0.320000 ( 0.320151)
|
402
|
+
find (external slug) 3.040000 0.010000 3.050000 ( 3.048054)
|
403
|
+
insert (without FriendlyId) 0.780000 0.000000 0.780000 ( 0.785427)
|
404
|
+
insert (in-table-slug) 1.520000 0.010000 1.530000 ( 1.532350)
|
405
|
+
insert (external slug) 3.310000 0.020000 3.330000 ( 3.335548)
|