friendly_id 4.0.0.beta14 → 4.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/.yardopts +1 -2
- data/Guide.rdoc +466 -0
- data/README.md +3 -0
- data/Rakefile +24 -1
- data/WhatsNew.md +1 -1
- data/lib/friendly_id.rb +14 -50
- data/lib/friendly_id/base.rb +76 -1
- data/lib/friendly_id/history.rb +9 -5
- data/lib/friendly_id/i18n.rb +11 -7
- data/lib/friendly_id/reserved.rb +8 -4
- data/lib/friendly_id/scoped.rb +5 -3
- data/lib/friendly_id/slug_generator.rb +2 -1
- data/lib/friendly_id/slugged.rb +70 -23
- data/test/slugged_test.rb +33 -2
- metadata +25 -24
- data/lib/friendly_id/model.rb +0 -26
data/.yardopts
CHANGED
data/Guide.rdoc
ADDED
@@ -0,0 +1,466 @@
|
|
1
|
+
#encoding: utf-8
|
2
|
+
|
3
|
+
|
4
|
+
== About FriendlyId
|
5
|
+
|
6
|
+
FriendlyId is an add-on to Ruby's Active Record that allows you to replace ids
|
7
|
+
in your URLs with strings:
|
8
|
+
|
9
|
+
# without FriendlyId
|
10
|
+
http://example.com/states/4323454
|
11
|
+
|
12
|
+
# with FriendlyId
|
13
|
+
http://example.com/states/washington
|
14
|
+
|
15
|
+
It requires few changes to your application code and offers flexibility,
|
16
|
+
performance and a well-documented codebase.
|
17
|
+
|
18
|
+
=== Core Concepts
|
19
|
+
|
20
|
+
==== Slugs
|
21
|
+
|
22
|
+
The concept of "slugs[http://en.wikipedia.org/wiki/Slug_(web_publishing)]" is at
|
23
|
+
the heart of FriendlyId.
|
24
|
+
|
25
|
+
A slug is the part of a URL which identifies a page using human-readable
|
26
|
+
keywords, rather than an opaque identifier such as a numeric id. This can make
|
27
|
+
your application more friendly both for users and search engine.
|
28
|
+
|
29
|
+
==== Finders: Slugs Act Like Numeric IDs
|
30
|
+
|
31
|
+
To the extent possible, FriendlyId lets you treat text-based identifiers like
|
32
|
+
normal IDs. This means that you can perform finds with slugs just like you do
|
33
|
+
with numeric ids:
|
34
|
+
|
35
|
+
Person.find(82542335)
|
36
|
+
Person.find("joe")
|
37
|
+
|
38
|
+
|
39
|
+
== Setting Up FriendlyId in Your Model
|
40
|
+
|
41
|
+
To use FriendlyId in your ActiveRecord models, you must first extend the
|
42
|
+
FriendlyId module, then invoke the {FriendlyId::Base#friendly_id friendly_id}
|
43
|
+
method to configure your desired options:
|
44
|
+
|
45
|
+
class Foo < ActiveRecord::Base
|
46
|
+
extend FriendlyId
|
47
|
+
friendly_id bar, :use => [:slugged, :i18n]
|
48
|
+
end
|
49
|
+
|
50
|
+
The most important option is `:use`, which you use to tell FriendlyId which
|
51
|
+
addons it should use. See the documentation for this method for a list of all
|
52
|
+
available addons, or skim through the rest of the docs to get a high-level
|
53
|
+
overview.
|
54
|
+
|
55
|
+
=== The Default Setup: Simple Models
|
56
|
+
|
57
|
+
The simplest way to use FriendlyId is with a model that has a uniquely indexed
|
58
|
+
column with no spaces or special characters, and that is seldom or never
|
59
|
+
updated. The most common example of this is a user name:
|
60
|
+
|
61
|
+
class User < ActiveRecord::Base
|
62
|
+
extend FriendlyId
|
63
|
+
friendly_id :login
|
64
|
+
validates_format_of :login, :with => /\A[a-z0-9]+\z/i
|
65
|
+
end
|
66
|
+
|
67
|
+
@user = User.find "joe" # the old User.find(1) still works, too
|
68
|
+
@user.to_param # returns "joe"
|
69
|
+
redirect_to @user # the URL will be /users/joe
|
70
|
+
|
71
|
+
In this case, FriendlyId assumes you want to use the column as-is; it will never
|
72
|
+
modify the value of the column, and your application should ensure that the
|
73
|
+
value is unique and admissible in a URL:
|
74
|
+
|
75
|
+
class City < ActiveRecord::Base
|
76
|
+
extend FriendlyId
|
77
|
+
friendly_id :name
|
78
|
+
end
|
79
|
+
|
80
|
+
@city.find "Viña del Mar"
|
81
|
+
redirect_to @city # the URL will be /cities/Viña%20del%20Mar
|
82
|
+
|
83
|
+
Writing the code to process an arbitrary string into a good identifier for use
|
84
|
+
in a URL can be repetitive and surprisingly tricky, so for this reason it's
|
85
|
+
often better and easier to use {FriendlyId::Slugged slugs}.
|
86
|
+
|
87
|
+
|
88
|
+
== Slugged Models
|
89
|
+
|
90
|
+
FriendlyId can use a separate column to store slugs for models which require
|
91
|
+
some text processing.
|
92
|
+
|
93
|
+
For example, blog applications typically use a post title to provide the basis
|
94
|
+
of a search engine friendly URL. Such identifiers typically lack uppercase
|
95
|
+
characters, use ASCII to approximate UTF-8 character, and strip out other
|
96
|
+
characters which may make them aesthetically unappealing or error-prone when
|
97
|
+
used in a URL.
|
98
|
+
|
99
|
+
class Post < ActiveRecord::Base
|
100
|
+
extend FriendlyId
|
101
|
+
friendly_id :title, :use => :slugged
|
102
|
+
end
|
103
|
+
|
104
|
+
@post = Post.create(:title => "This is the first post!")
|
105
|
+
@post.friendly_id # returns "this-is-the-first-post"
|
106
|
+
redirect_to @post # the URL will be /posts/this-is-the-first-post
|
107
|
+
|
108
|
+
In general, use slugs by default unless you know for sure you don't need them.
|
109
|
+
To activate the slugging functionality, use the {FriendlyId::Slugged} module.
|
110
|
+
|
111
|
+
FriendlyId will generate slugs from a method or column that you specify, and
|
112
|
+
store them in a field in your model. By default, this field must be named
|
113
|
+
+:slug+, though you may change this using the
|
114
|
+
{FriendlyId::Slugged::Configuration#slug_column slug_column} configuration
|
115
|
+
option. You should add an index to this column, and in most cases, make it
|
116
|
+
unique. You may also wish to constrain it to NOT NULL, but this depends on your
|
117
|
+
app's behavior and requirements.
|
118
|
+
|
119
|
+
=== Example Setup
|
120
|
+
|
121
|
+
# your model
|
122
|
+
class Post < ActiveRecord::Base
|
123
|
+
extend FriendlyId
|
124
|
+
friendly_id :title, :use => :slugged
|
125
|
+
validates_presence_of :title, :slug, :body
|
126
|
+
end
|
127
|
+
|
128
|
+
# a migration
|
129
|
+
class CreatePosts < ActiveRecord::Migration
|
130
|
+
def self.up
|
131
|
+
create_table :posts do |t|
|
132
|
+
t.string :title, :null => false
|
133
|
+
t.string :slug, :null => false
|
134
|
+
t.text :body
|
135
|
+
end
|
136
|
+
|
137
|
+
add_index :posts, :slug, :unique => true
|
138
|
+
end
|
139
|
+
|
140
|
+
def self.down
|
141
|
+
drop_table :posts
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
=== Working With Slugs
|
146
|
+
|
147
|
+
==== Formatting
|
148
|
+
|
149
|
+
By default, FriendlyId uses Active Support's
|
150
|
+
paramaterize[http://api.rubyonrails.org/classes/ActiveSupport/Inflector.html#method-i-parameterize]
|
151
|
+
method to create slugs. This method will intelligently replace spaces with
|
152
|
+
dashes, and Unicode Latin characters with ASCII approximations:
|
153
|
+
|
154
|
+
movie = Movie.create! :title => "Der Preis fürs Überleben"
|
155
|
+
movie.slug #=> "der-preis-furs-uberleben"
|
156
|
+
|
157
|
+
==== Uniqueness
|
158
|
+
|
159
|
+
When you try to insert a record that would generate a duplicate friendly id,
|
160
|
+
FriendlyId will append a sequence to the generated slug to ensure uniqueness:
|
161
|
+
|
162
|
+
car = Car.create :title => "Peugot 206"
|
163
|
+
car2 = Car.create :title => "Peugot 206"
|
164
|
+
|
165
|
+
car.friendly_id #=> "peugot-206"
|
166
|
+
car2.friendly_id #=> "peugot-206--2"
|
167
|
+
|
168
|
+
==== Sequence Separator - The Two Dashes
|
169
|
+
|
170
|
+
By default, FriendlyId uses two dashes to separate the slug from a sequence.
|
171
|
+
|
172
|
+
You can change this with the {FriendlyId::Slugged::Configuration#sequence_separator
|
173
|
+
sequence_separator} configuration option.
|
174
|
+
|
175
|
+
==== Column or Method?
|
176
|
+
|
177
|
+
FriendlyId always uses a method as the basis of the slug text - not a column. It
|
178
|
+
first glance, this may sound confusing, but remember that Active Record provides
|
179
|
+
methods for each column in a model's associated table, and that's what
|
180
|
+
FriendlyId uses.
|
181
|
+
|
182
|
+
Here's an example of a class that uses a custom method to generate the slug:
|
183
|
+
|
184
|
+
class Person < ActiveRecord::Base
|
185
|
+
friendly_id :name_and_location
|
186
|
+
def name_and_location
|
187
|
+
"#{name} from #{location}"
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
bob = Person.create! :name => "Bob Smith", :location => "New York City"
|
192
|
+
bob.friendly_id #=> "bob-smith-from-new-york-city"
|
193
|
+
|
194
|
+
==== Providing Your Own Slug Processing Method
|
195
|
+
|
196
|
+
You can override {FriendlyId::Slugged#normalize_friendly_id} in your model for
|
197
|
+
total control over the slug format.
|
198
|
+
|
199
|
+
==== Deciding When to Generate New Slugs
|
200
|
+
|
201
|
+
Overriding {FriendlyId::Slugged#should_generate_new_friendly_id?} lets you
|
202
|
+
control whether new friendly ids are created when a model is updated. For
|
203
|
+
example, if you only want to generate slugs once and then treat them as
|
204
|
+
read-only:
|
205
|
+
|
206
|
+
class Post < ActiveRecord::Base
|
207
|
+
extend FriendlyId
|
208
|
+
friendly_id :title, :use => :slugged
|
209
|
+
|
210
|
+
def should_generate_new_friendly_id?
|
211
|
+
new_record?
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
post = Post.create!(:title => "Hello world!")
|
216
|
+
post.slug #=> "hello-world"
|
217
|
+
post.title = "Hello there, world!"
|
218
|
+
post.save!
|
219
|
+
post.slug #=> "hello-world"
|
220
|
+
|
221
|
+
==== Locale-specific Transliterations
|
222
|
+
|
223
|
+
Active Support's +parameterize+ uses
|
224
|
+
transliterate[http://api.rubyonrails.org/classes/ActiveSupport/Inflector.html#method-i-transliterate],
|
225
|
+
which in turn can use I18n's transliteration rules to consider the current
|
226
|
+
locale when replacing Latin characters:
|
227
|
+
|
228
|
+
# config/locales/de.yml
|
229
|
+
de:
|
230
|
+
i18n:
|
231
|
+
transliterate:
|
232
|
+
rule:
|
233
|
+
ü: "ue"
|
234
|
+
ö: "oe"
|
235
|
+
etc...
|
236
|
+
|
237
|
+
movie = Movie.create! :title => "Der Preis fürs Überleben"
|
238
|
+
movie.slug #=> "der-preis-fuers-ueberleben"
|
239
|
+
|
240
|
+
This functionality was in fact taken from earlier versions of FriendlyId.
|
241
|
+
|
242
|
+
==== Gotchas: Common Problems
|
243
|
+
|
244
|
+
FriendlyId uses a before_validation callback to generate and set the slug. This
|
245
|
+
means that if you create two model instances before saving them, it's possible
|
246
|
+
they will generate the same slug, and the second save will fail.
|
247
|
+
|
248
|
+
This can happen in two fairly normal cases: the first, when a model using nested
|
249
|
+
attributes creates more than one record for a model that uses friendly_id. The
|
250
|
+
second, in concurrent code, either in threads or multiple processes.
|
251
|
+
|
252
|
+
===== Nested Attributes
|
253
|
+
|
254
|
+
To solve the nested attributes issue, I recommend simply avoiding them when
|
255
|
+
creating more than one nested record for a model that uses FriendlyId. See {this
|
256
|
+
Github issue}[https://github.com/norman/friendly_id/issues/185] for discussion.
|
257
|
+
|
258
|
+
===== Concurrency
|
259
|
+
|
260
|
+
To solve the concurrency issue, I recommend locking the model's table against
|
261
|
+
inserts while when saving the record. See {this Github
|
262
|
+
issue}[https://github.com/norman/friendly_id/issues/180] for discussion.
|
263
|
+
|
264
|
+
|
265
|
+
== History: Avoiding 404's When Slugs Change
|
266
|
+
|
267
|
+
FriendlyId's {FriendlyId::History History} module adds the ability to store a
|
268
|
+
log of a model's slugs, so that when its friendly id changes, it's still
|
269
|
+
possible to perform finds by the old id.
|
270
|
+
|
271
|
+
The primary use case for this is avoiding broken URLs.
|
272
|
+
|
273
|
+
=== Setup
|
274
|
+
|
275
|
+
In order to use this module, you must add a table to your database schema to
|
276
|
+
store the slug records. FriendlyId provides a generator for this purpose:
|
277
|
+
|
278
|
+
rails generate friendly_id
|
279
|
+
rake db:migrate
|
280
|
+
|
281
|
+
This will add a table named +friendly_id_slugs+, used by the {FriendlyId::Slug}
|
282
|
+
model.
|
283
|
+
|
284
|
+
=== Considerations
|
285
|
+
|
286
|
+
This module is incompatible with the +:scoped+ module.
|
287
|
+
|
288
|
+
Because recording slug history requires creating additional database records,
|
289
|
+
this module has an impact on the performance of the associated model's +create+
|
290
|
+
method.
|
291
|
+
|
292
|
+
=== Example
|
293
|
+
|
294
|
+
class Post < ActiveRecord::Base
|
295
|
+
extend FriendlyId
|
296
|
+
friendly_id :title, :use => :history
|
297
|
+
end
|
298
|
+
|
299
|
+
class PostsController < ApplicationController
|
300
|
+
|
301
|
+
before_filter :find_post
|
302
|
+
|
303
|
+
...
|
304
|
+
|
305
|
+
def find_post
|
306
|
+
Post.find params[:id]
|
307
|
+
|
308
|
+
# If an old id or a numeric id was used to find the record, then
|
309
|
+
# the request path will not match the post_path, and we should do
|
310
|
+
# a 301 redirect that uses the current friendly id.
|
311
|
+
if request.path != post_path(@post)
|
312
|
+
return redirect_to @post, :status => :moved_permanently
|
313
|
+
end
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
|
318
|
+
== Unique Slugs by Scope
|
319
|
+
|
320
|
+
The {FriendlyId::Scoped} module allows FriendlyId to generate unique slugs
|
321
|
+
within a scope.
|
322
|
+
|
323
|
+
This allows, for example, two restaurants in different cities to have the slug
|
324
|
+
+joes-diner+:
|
325
|
+
|
326
|
+
class Restaurant < ActiveRecord::Base
|
327
|
+
extend FriendlyId
|
328
|
+
belongs_to :city
|
329
|
+
friendly_id :name, :use => :scoped, :scope => :city
|
330
|
+
end
|
331
|
+
|
332
|
+
class City < ActiveRecord::Base
|
333
|
+
extend FriendlyId
|
334
|
+
has_many :restaurants
|
335
|
+
friendly_id :name, :use => :slugged
|
336
|
+
end
|
337
|
+
|
338
|
+
City.find("seattle").restaurants.find("joes-diner")
|
339
|
+
City.find("chicago").restaurants.find("joes-diner")
|
340
|
+
|
341
|
+
Without :scoped in this case, one of the restaurants would have the slug
|
342
|
+
+joes-diner+ and the other would have +joes-diner--2+.
|
343
|
+
|
344
|
+
The value for the +:scope+ option can be the name of a +belongs_to+ relation, or
|
345
|
+
a column.
|
346
|
+
|
347
|
+
=== Finding Records by Friendly ID
|
348
|
+
|
349
|
+
If you are using scopes your friendly ids may not be unique, so a simple find
|
350
|
+
like
|
351
|
+
|
352
|
+
Restaurant.find("joes-diner")
|
353
|
+
|
354
|
+
may return the wrong record. In these cases it's best to query through the
|
355
|
+
relation:
|
356
|
+
|
357
|
+
@city.restaurants.find("joes-diner")
|
358
|
+
|
359
|
+
Alternatively, you could pass the scope value as a query parameter:
|
360
|
+
|
361
|
+
Restaurant.find("joes-diner").where(:city_id => @city.id)
|
362
|
+
|
363
|
+
|
364
|
+
=== Finding All Records That Match a Scoped ID
|
365
|
+
|
366
|
+
Query the slug column directly:
|
367
|
+
|
368
|
+
Restaurant.find_all_by_slug("joes-diner")
|
369
|
+
|
370
|
+
=== Routes for Scoped Models
|
371
|
+
|
372
|
+
Recall that FriendlyId is a database-centric library, and does not set up any
|
373
|
+
routes for scoped models. You must do this yourself in your application. Here's
|
374
|
+
an example of one way to set this up:
|
375
|
+
|
376
|
+
# in routes.rb
|
377
|
+
resources :cities do
|
378
|
+
resources :restaurants
|
379
|
+
end
|
380
|
+
|
381
|
+
# in views
|
382
|
+
<%= link_to 'Show', [@city, @restaurant] %>
|
383
|
+
|
384
|
+
# in controllers
|
385
|
+
@city = City.find(params[:city_id])
|
386
|
+
@restaurant = @city.restaurants.find(params[:id])
|
387
|
+
|
388
|
+
# URLs:
|
389
|
+
http://example.org/cities/seattle/restaurants/joes-diner
|
390
|
+
http://example.org/cities/chicago/restaurants/joes-diner
|
391
|
+
|
392
|
+
|
393
|
+
== Basic I18n
|
394
|
+
|
395
|
+
This {FriendlyId::I18n i18n} adds very basic i18n support to FriendlyId.
|
396
|
+
|
397
|
+
In order to use this module, your model must have a slug column for each locale.
|
398
|
+
By default FriendlyId looks for columns named, for example, "slug_en",
|
399
|
+
"slug_es", etc. The first part of the name can be configured by passing the
|
400
|
+
+:slug_column+ option if you choose. Note that as of 4.0.0.beta11, the column
|
401
|
+
for the default locale must also include the locale in its name.
|
402
|
+
|
403
|
+
=== Example migration
|
404
|
+
|
405
|
+
def self.up
|
406
|
+
create_table :posts do |t|
|
407
|
+
t.string :title
|
408
|
+
t.string :slug_en
|
409
|
+
t.string :slug_es
|
410
|
+
t.text :body
|
411
|
+
end
|
412
|
+
add_index :posts, :slug_en
|
413
|
+
add_index :posts, :slug_es
|
414
|
+
end
|
415
|
+
|
416
|
+
=== Finds
|
417
|
+
|
418
|
+
Finds will take into consideration the current locale:
|
419
|
+
|
420
|
+
I18n.locale = :es
|
421
|
+
Post.find("la-guerra-de-las-galaxas")
|
422
|
+
I18n.locale = :en
|
423
|
+
Post.find("star-wars")
|
424
|
+
|
425
|
+
To find a slug by an explicit locale, perform the find inside a block
|
426
|
+
passed to I18n's +with_locale+ method:
|
427
|
+
|
428
|
+
I18n.with_locale(:es) do
|
429
|
+
Post.find("la-guerra-de-las-galaxas")
|
430
|
+
end
|
431
|
+
|
432
|
+
=== Creating Records
|
433
|
+
|
434
|
+
When new records are created, the slug is generated for the current locale only.
|
435
|
+
|
436
|
+
=== Translating Slugs
|
437
|
+
|
438
|
+
To translate an existing record's friendly_id, use
|
439
|
+
{FriendlyId::I18n::Model#set_friendly_id}. This will ensure that the slug you
|
440
|
+
add is properly escaped, transliterated and sequenced:
|
441
|
+
|
442
|
+
post = Post.create :name => "Star Wars"
|
443
|
+
post.set_friendly_id("La guerra de las galaxas", :es)
|
444
|
+
|
445
|
+
If you don't pass in a locale argument, FriendlyId::I18n will just use the
|
446
|
+
current locale:
|
447
|
+
|
448
|
+
I18n.with_locale(:es) do
|
449
|
+
post.set_friendly_id("la-guerra-de-las-galaxas")
|
450
|
+
end
|
451
|
+
|
452
|
+
|
453
|
+
== Reserved Words
|
454
|
+
|
455
|
+
The {FriendlyId::Reserved Reserved} module adds the ability to exlude a list of
|
456
|
+
words from use as FriendlyId slugs.
|
457
|
+
|
458
|
+
By default, FriendlyId reserves the words "new" and "edit" when this module is
|
459
|
+
included. You can configure this globally by using {FriendlyId.defaults
|
460
|
+
FriendlyId.defaults}:
|
461
|
+
|
462
|
+
FriendlyId.defaults do |config|
|
463
|
+
config.use :reserved
|
464
|
+
# Reserve words for English and Spanish URLs
|
465
|
+
config.reserved_words = %w(new edit nueva nuevo editar)
|
466
|
+
end
|
data/README.md
CHANGED
@@ -34,6 +34,9 @@ new.
|
|
34
34
|
The current docs can always be found
|
35
35
|
[here](http://rubydoc.info/github/norman/friendly_id/master/frames).
|
36
36
|
|
37
|
+
The best place to start is with the
|
38
|
+
[Guide](http://rubydoc.info/github/norman/friendly_id/master/file/Guide.rdoc),
|
39
|
+
which compiles the top-level RDocs into one outlined document.
|
37
40
|
|
38
41
|
## Rails Quickstart
|
39
42
|
|
data/Rakefile
CHANGED
@@ -17,7 +17,7 @@ task :gem do
|
|
17
17
|
%x{gem build friendly_id.gemspec}
|
18
18
|
end
|
19
19
|
|
20
|
-
task :yard do
|
20
|
+
task :yard => :guide do
|
21
21
|
puts %x{bundle exec yard}
|
22
22
|
end
|
23
23
|
|
@@ -25,6 +25,29 @@ task :bench do
|
|
25
25
|
require File.expand_path("../bench", __FILE__)
|
26
26
|
end
|
27
27
|
|
28
|
+
task :guide do
|
29
|
+
def read_comments(path)
|
30
|
+
path = File.expand_path("../#{path}", __FILE__)
|
31
|
+
match = File.read(path).match(/\n=begin(.*)\n=end/m)[1].to_s
|
32
|
+
match.split("\n").reject {|x| x =~ /^@/}.join("\n")
|
33
|
+
end
|
34
|
+
|
35
|
+
buffer = []
|
36
|
+
|
37
|
+
buffer << read_comments("lib/friendly_id.rb")
|
38
|
+
buffer << read_comments("lib/friendly_id/base.rb")
|
39
|
+
buffer << read_comments("lib/friendly_id/slugged.rb")
|
40
|
+
buffer << read_comments("lib/friendly_id/history.rb")
|
41
|
+
buffer << read_comments("lib/friendly_id/scoped.rb")
|
42
|
+
buffer << read_comments("lib/friendly_id/i18n.rb")
|
43
|
+
buffer << read_comments("lib/friendly_id/reserved.rb")
|
44
|
+
|
45
|
+
File.open("Guide.rdoc", "w") do |file|
|
46
|
+
file.write("#encoding: utf-8\n")
|
47
|
+
file.write(buffer.join("\n"))
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
28
51
|
namespace :test do
|
29
52
|
|
30
53
|
desc "Run each test class in a separate process"
|
data/WhatsNew.md
CHANGED
@@ -46,7 +46,7 @@ maintained until people don't want it any more.
|
|
46
46
|
FriendlyId no longer creates a separate slugs table - it just stores the
|
47
47
|
generated slug value in the model table, which is simpler, faster and what most
|
48
48
|
want by default. Keeping slug history in a separate table is an
|
49
|
-
{FriendlyId::
|
49
|
+
{FriendlyId::History optional add-on} for FriendlyId 4.
|
50
50
|
|
51
51
|
## No more multiple finds
|
52
52
|
|
data/lib/friendly_id.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
require "thread"
|
3
3
|
require "friendly_id/base"
|
4
|
-
require "friendly_id/model"
|
5
4
|
require "friendly_id/object_utils"
|
6
5
|
require "friendly_id/configuration"
|
7
6
|
require "friendly_id/finder_methods"
|
@@ -22,66 +21,31 @@ in your URLs with strings:
|
|
22
21
|
It requires few changes to your application code and offers flexibility,
|
23
22
|
performance and a well-documented codebase.
|
24
23
|
|
25
|
-
=== Concepts
|
24
|
+
=== Core Concepts
|
26
25
|
|
27
|
-
|
28
|
-
not your routes.
|
26
|
+
==== Slugs
|
29
27
|
|
30
|
-
|
28
|
+
The concept of "slugs[http://en.wikipedia.org/wiki/Slug_(web_publishing)]" is at
|
29
|
+
the heart of FriendlyId.
|
31
30
|
|
32
|
-
|
33
|
-
|
34
|
-
|
31
|
+
A slug is the part of a URL which identifies a page using human-readable
|
32
|
+
keywords, rather than an opaque identifier such as a numeric id. This can make
|
33
|
+
your application more friendly both for users and search engine.
|
35
34
|
|
36
|
-
|
37
|
-
extend FriendlyId
|
38
|
-
friendly_id :login
|
39
|
-
validates_format_of :login, :with => /\A[a-z0-9]+\z/i
|
40
|
-
end
|
41
|
-
|
42
|
-
@user = User.find "joe" # the old User.find(1) still works, too
|
43
|
-
@user.to_param # returns "joe"
|
44
|
-
redirect_to @user # the URL will be /users/joe
|
45
|
-
|
46
|
-
In this case, FriendlyId assumes you want to use the column as-is; it will never
|
47
|
-
modify the value of the column, and your application should ensure that the
|
48
|
-
value is admissible in a URL:
|
49
|
-
|
50
|
-
class City < ActiveRecord::Base
|
51
|
-
extend FriendlyId
|
52
|
-
friendly_id :name
|
53
|
-
end
|
54
|
-
|
55
|
-
@city.find "Viña del Mar"
|
56
|
-
redirect_to @city # the URL will be /cities/Viña%20del%20Mar
|
57
|
-
|
58
|
-
For this reason, it is often more convenient to use "slugs" rather than a single
|
59
|
-
column.
|
60
|
-
|
61
|
-
=== Slugged Models
|
62
|
-
|
63
|
-
FriendlyId can uses a separate column to store slugs for models which require
|
64
|
-
some processing of the friendly_id text. The most common example is a blog
|
65
|
-
post's title, which may have spaces, uppercase characters, or other attributes
|
66
|
-
you wish to modify to make them more suitable for use in URL's.
|
67
|
-
|
68
|
-
class Post < ActiveRecord::Base
|
69
|
-
extend FriendlyId
|
70
|
-
friendly_id :title, :use => :slugged
|
71
|
-
end
|
35
|
+
==== Finders: Slugs Act Like Numeric IDs
|
72
36
|
|
73
|
-
|
74
|
-
|
75
|
-
|
37
|
+
To the extent possible, FriendlyId lets you treat text-based identifiers like
|
38
|
+
normal IDs. This means that you can perform finds with slugs just like you do
|
39
|
+
with numeric ids:
|
76
40
|
|
77
|
-
|
41
|
+
Person.find(82542335)
|
42
|
+
Person.find("joe")
|
78
43
|
|
79
|
-
@author Norman Clarke
|
80
44
|
=end
|
81
45
|
module FriendlyId
|
82
46
|
|
83
47
|
# The current version.
|
84
|
-
VERSION = "4.0.0.
|
48
|
+
VERSION = "4.0.0.rc1"
|
85
49
|
|
86
50
|
@mutex = Mutex.new
|
87
51
|
|
data/lib/friendly_id/base.rb
CHANGED
@@ -1,5 +1,55 @@
|
|
1
1
|
module FriendlyId
|
2
|
-
|
2
|
+
=begin
|
3
|
+
|
4
|
+
== Setting Up FriendlyId in Your Model
|
5
|
+
|
6
|
+
To use FriendlyId in your ActiveRecord models, you must first extend the
|
7
|
+
FriendlyId module, then invoke the {FriendlyId::Base#friendly_id friendly_id}
|
8
|
+
method to configure your desired options:
|
9
|
+
|
10
|
+
class Foo < ActiveRecord::Base
|
11
|
+
extend FriendlyId
|
12
|
+
friendly_id bar, :use => [:slugged, :i18n]
|
13
|
+
end
|
14
|
+
|
15
|
+
The most important option is `:use`, which you use to tell FriendlyId which
|
16
|
+
addons it should use. See the documentation for this method for a list of all
|
17
|
+
available addons, or skim through the rest of the docs to get a high-level
|
18
|
+
overview.
|
19
|
+
|
20
|
+
=== The Default Setup: Simple Models
|
21
|
+
|
22
|
+
The simplest way to use FriendlyId is with a model that has a uniquely indexed
|
23
|
+
column with no spaces or special characters, and that is seldom or never
|
24
|
+
updated. The most common example of this is a user name:
|
25
|
+
|
26
|
+
class User < ActiveRecord::Base
|
27
|
+
extend FriendlyId
|
28
|
+
friendly_id :login
|
29
|
+
validates_format_of :login, :with => /\A[a-z0-9]+\z/i
|
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
|
38
|
+
value is unique and admissible in a URL:
|
39
|
+
|
40
|
+
class City < ActiveRecord::Base
|
41
|
+
extend FriendlyId
|
42
|
+
friendly_id :name
|
43
|
+
end
|
44
|
+
|
45
|
+
@city.find "Viña del Mar"
|
46
|
+
redirect_to @city # the URL will be /cities/Viña%20del%20Mar
|
47
|
+
|
48
|
+
Writing the code to process an arbitrary string into a good identifier for use
|
49
|
+
in a URL can be repetitive and surprisingly tricky, so for this reason it's
|
50
|
+
often better and easier to use {FriendlyId::Slugged slugs}.
|
51
|
+
|
52
|
+
=end
|
3
53
|
module Base
|
4
54
|
|
5
55
|
# Configure FriendlyId's behavior in a model.
|
@@ -169,4 +219,29 @@ module FriendlyId
|
|
169
219
|
end
|
170
220
|
end
|
171
221
|
end
|
222
|
+
|
223
|
+
# Instance methods that will be added to all classes using FriendlyId.
|
224
|
+
module Model
|
225
|
+
|
226
|
+
attr_reader :current_friendly_id
|
227
|
+
|
228
|
+
# Convenience method for accessing the class method of the same name.
|
229
|
+
def friendly_id_config
|
230
|
+
self.class.friendly_id_config
|
231
|
+
end
|
232
|
+
|
233
|
+
# Get the instance's friendly_id.
|
234
|
+
def friendly_id
|
235
|
+
send friendly_id_config.query_field
|
236
|
+
end
|
237
|
+
|
238
|
+
# Either the friendly_id, or the numeric id cast to a string.
|
239
|
+
def to_param
|
240
|
+
if diff = changes[friendly_id_config.query_field]
|
241
|
+
diff.first
|
242
|
+
else
|
243
|
+
friendly_id.present? ? friendly_id : id.to_s
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|
172
247
|
end
|
data/lib/friendly_id/history.rb
CHANGED
@@ -3,12 +3,16 @@ require "friendly_id/slug"
|
|
3
3
|
module FriendlyId
|
4
4
|
|
5
5
|
=begin
|
6
|
-
|
7
|
-
|
6
|
+
|
7
|
+
== History: Avoiding 404's When Slugs Change
|
8
|
+
|
9
|
+
FriendlyId's {FriendlyId::History History} module adds the ability to store a
|
10
|
+
log of a model's slugs, so that when its friendly id changes, it's still
|
11
|
+
possible to perform finds by the old id.
|
8
12
|
|
9
13
|
The primary use case for this is avoiding broken URLs.
|
10
14
|
|
11
|
-
|
15
|
+
=== Setup
|
12
16
|
|
13
17
|
In order to use this module, you must add a table to your database schema to
|
14
18
|
store the slug records. FriendlyId provides a generator for this purpose:
|
@@ -19,7 +23,7 @@ store the slug records. FriendlyId provides a generator for this purpose:
|
|
19
23
|
This will add a table named +friendly_id_slugs+, used by the {FriendlyId::Slug}
|
20
24
|
model.
|
21
25
|
|
22
|
-
|
26
|
+
=== Considerations
|
23
27
|
|
24
28
|
This module is incompatible with the +:scoped+ module.
|
25
29
|
|
@@ -27,7 +31,7 @@ Because recording slug history requires creating additional database records,
|
|
27
31
|
this module has an impact on the performance of the associated model's +create+
|
28
32
|
method.
|
29
33
|
|
30
|
-
|
34
|
+
=== Example
|
31
35
|
|
32
36
|
class Post < ActiveRecord::Base
|
33
37
|
extend FriendlyId
|
data/lib/friendly_id/i18n.rb
CHANGED
@@ -3,7 +3,10 @@ require "i18n"
|
|
3
3
|
module FriendlyId
|
4
4
|
|
5
5
|
=begin
|
6
|
-
|
6
|
+
|
7
|
+
== Basic I18n
|
8
|
+
|
9
|
+
This {FriendlyId::I18n i18n} adds very basic i18n support to FriendlyId.
|
7
10
|
|
8
11
|
In order to use this module, your model must have a slug column for each locale.
|
9
12
|
By default FriendlyId looks for columns named, for example, "slug_en",
|
@@ -11,7 +14,7 @@ By default FriendlyId looks for columns named, for example, "slug_en",
|
|
11
14
|
+:slug_column+ option if you choose. Note that as of 4.0.0.beta11, the column
|
12
15
|
for the default locale must also include the locale in its name.
|
13
16
|
|
14
|
-
|
17
|
+
=== Example migration
|
15
18
|
|
16
19
|
def self.up
|
17
20
|
create_table :posts do |t|
|
@@ -24,7 +27,7 @@ for the default locale must also include the locale in its name.
|
|
24
27
|
add_index :posts, :slug_es
|
25
28
|
end
|
26
29
|
|
27
|
-
|
30
|
+
=== Finds
|
28
31
|
|
29
32
|
Finds will take into consideration the current locale:
|
30
33
|
|
@@ -40,14 +43,15 @@ passed to I18n's +with_locale+ method:
|
|
40
43
|
Post.find("la-guerra-de-las-galaxas")
|
41
44
|
end
|
42
45
|
|
43
|
-
|
46
|
+
=== Creating Records
|
44
47
|
|
45
48
|
When new records are created, the slug is generated for the current locale only.
|
46
49
|
|
47
|
-
|
50
|
+
=== Translating Slugs
|
48
51
|
|
49
|
-
To translate an existing record's friendly_id, use
|
50
|
-
ensure that the slug you
|
52
|
+
To translate an existing record's friendly_id, use
|
53
|
+
{FriendlyId::I18n::Model#set_friendly_id}. This will ensure that the slug you
|
54
|
+
add is properly escaped, transliterated and sequenced:
|
51
55
|
|
52
56
|
post = Post.create :name => "Star Wars"
|
53
57
|
post.set_friendly_id("La guerra de las galaxas", :es)
|
data/lib/friendly_id/reserved.rb
CHANGED
@@ -1,11 +1,15 @@
|
|
1
1
|
module FriendlyId
|
2
2
|
|
3
3
|
=begin
|
4
|
-
This module adds the ability to exlude a list of words from use as
|
5
|
-
FriendlyId slugs.
|
6
4
|
|
7
|
-
|
8
|
-
|
5
|
+
== Reserved Words
|
6
|
+
|
7
|
+
The {FriendlyId::Reserved Reserved} module adds the ability to exlude a list of
|
8
|
+
words from use as FriendlyId slugs.
|
9
|
+
|
10
|
+
By default, FriendlyId reserves the words "new" and "edit" when this module is
|
11
|
+
included. You can configure this globally by using {FriendlyId.defaults
|
12
|
+
FriendlyId.defaults}:
|
9
13
|
|
10
14
|
FriendlyId.defaults do |config|
|
11
15
|
config.use :reserved
|
data/lib/friendly_id/scoped.rb
CHANGED
@@ -3,7 +3,11 @@ require "friendly_id/slugged"
|
|
3
3
|
module FriendlyId
|
4
4
|
|
5
5
|
=begin
|
6
|
-
|
6
|
+
|
7
|
+
== Unique Slugs by Scope
|
8
|
+
|
9
|
+
The {FriendlyId::Scoped} module allows FriendlyId to generate unique slugs
|
10
|
+
within a scope.
|
7
11
|
|
8
12
|
This allows, for example, two restaurants in different cities to have the slug
|
9
13
|
+joes-diner+:
|
@@ -29,8 +33,6 @@ Without :scoped in this case, one of the restaurants would have the slug
|
|
29
33
|
The value for the +:scope+ option can be the name of a +belongs_to+ relation, or
|
30
34
|
a column.
|
31
35
|
|
32
|
-
== Tips For Working With Scoped Slugs
|
33
|
-
|
34
36
|
=== Finding Records by Friendly ID
|
35
37
|
|
36
38
|
If you are using scopes your friendly ids may not be unique, so a simple find
|
@@ -12,7 +12,8 @@ module FriendlyId
|
|
12
12
|
|
13
13
|
# Given a slug, get the next available slug in the sequence.
|
14
14
|
def next
|
15
|
-
|
15
|
+
# Don't assume that the separator is unique within the slug
|
16
|
+
sequence = conflict.to_param.gsub(/^#{Regexp.quote(normalized)}(#{Regexp.quote(separator)})?/, '').to_i
|
16
17
|
next_sequence = sequence == 0 ? 2 : sequence.next
|
17
18
|
"#{normalized}#{separator}#{next_sequence}"
|
18
19
|
end
|
data/lib/friendly_id/slugged.rb
CHANGED
@@ -3,21 +3,37 @@ require "friendly_id/slug_generator"
|
|
3
3
|
|
4
4
|
module FriendlyId
|
5
5
|
=begin
|
6
|
-
This module adds in-table slugs to a model.
|
7
6
|
|
8
|
-
|
9
|
-
characters that a developer considers inconvenient for use in URLs. For example,
|
10
|
-
blog applications typically use a post title to provide the basis of a search
|
11
|
-
engine friendly URL:
|
7
|
+
== Slugged Models
|
12
8
|
|
13
|
-
|
9
|
+
FriendlyId can use a separate column to store slugs for models which require
|
10
|
+
some text processing.
|
14
11
|
|
15
|
-
|
16
|
-
|
17
|
-
|
12
|
+
For example, blog applications typically use a post title to provide the basis
|
13
|
+
of a search engine friendly URL. Such identifiers typically lack uppercase
|
14
|
+
characters, use ASCII to approximate UTF-8 character, and strip out other
|
15
|
+
characters which may make them aesthetically unappealing or error-prone when
|
16
|
+
used in a URL.
|
17
|
+
|
18
|
+
class Post < ActiveRecord::Base
|
19
|
+
extend FriendlyId
|
20
|
+
friendly_id :title, :use => :slugged
|
21
|
+
end
|
22
|
+
|
23
|
+
@post = Post.create(:title => "This is the first post!")
|
24
|
+
@post.friendly_id # returns "this-is-the-first-post"
|
25
|
+
redirect_to @post # the URL will be /posts/this-is-the-first-post
|
26
|
+
|
27
|
+
In general, use slugs by default unless you know for sure you don't need them.
|
28
|
+
To activate the slugging functionality, use the {FriendlyId::Slugged} module.
|
29
|
+
|
30
|
+
FriendlyId will generate slugs from a method or column that you specify, and
|
31
|
+
store them in a field in your model. By default, this field must be named
|
32
|
+
+:slug+, though you may change this using the
|
18
33
|
{FriendlyId::Slugged::Configuration#slug_column slug_column} configuration
|
19
|
-
option. You should add an index to this
|
20
|
-
to NOT NULL, but this depends on your
|
34
|
+
option. You should add an index to this column, and in most cases, make it
|
35
|
+
unique. You may also wish to constrain it to NOT NULL, but this depends on your
|
36
|
+
app's behavior and requirements.
|
21
37
|
|
22
38
|
=== Example Setup
|
23
39
|
|
@@ -45,7 +61,9 @@ to NOT NULL, but this depends on your app's behavior and requirements.
|
|
45
61
|
end
|
46
62
|
end
|
47
63
|
|
48
|
-
===
|
64
|
+
=== Working With Slugs
|
65
|
+
|
66
|
+
==== Formatting
|
49
67
|
|
50
68
|
By default, FriendlyId uses Active Support's
|
51
69
|
paramaterize[http://api.rubyonrails.org/classes/ActiveSupport/Inflector.html#method-i-parameterize]
|
@@ -55,7 +73,7 @@ dashes, and Unicode Latin characters with ASCII approximations:
|
|
55
73
|
movie = Movie.create! :title => "Der Preis fürs Überleben"
|
56
74
|
movie.slug #=> "der-preis-furs-uberleben"
|
57
75
|
|
58
|
-
====
|
76
|
+
==== Uniqueness
|
59
77
|
|
60
78
|
When you try to insert a record that would generate a duplicate friendly id,
|
61
79
|
FriendlyId will append a sequence to the generated slug to ensure uniqueness:
|
@@ -66,9 +84,11 @@ FriendlyId will append a sequence to the generated slug to ensure uniqueness:
|
|
66
84
|
car.friendly_id #=> "peugot-206"
|
67
85
|
car2.friendly_id #=> "peugot-206--2"
|
68
86
|
|
69
|
-
====
|
87
|
+
==== Sequence Separator - The Two Dashes
|
70
88
|
|
71
|
-
|
89
|
+
By default, FriendlyId uses two dashes to separate the slug from a sequence.
|
90
|
+
|
91
|
+
You can change this with the {FriendlyId::Slugged::Configuration#sequence_separator
|
72
92
|
sequence_separator} configuration option.
|
73
93
|
|
74
94
|
==== Column or Method?
|
@@ -92,14 +112,15 @@ Here's an example of a class that uses a custom method to generate the slug:
|
|
92
112
|
|
93
113
|
==== Providing Your Own Slug Processing Method
|
94
114
|
|
95
|
-
You can override {Slugged#normalize_friendly_id} in your model for
|
96
|
-
control over the slug format.
|
115
|
+
You can override {FriendlyId::Slugged#normalize_friendly_id} in your model for
|
116
|
+
total control over the slug format.
|
97
117
|
|
98
|
-
==== Deciding
|
118
|
+
==== Deciding When to Generate New Slugs
|
99
119
|
|
100
|
-
Overriding {Slugged#should_generate_new_friendly_id?} lets you
|
101
|
-
new friendly ids are created when a model is updated. For
|
102
|
-
want to generate slugs once and then treat them as
|
120
|
+
Overriding {FriendlyId::Slugged#should_generate_new_friendly_id?} lets you
|
121
|
+
control whether new friendly ids are created when a model is updated. For
|
122
|
+
example, if you only want to generate slugs once and then treat them as
|
123
|
+
read-only:
|
103
124
|
|
104
125
|
class Post < ActiveRecord::Base
|
105
126
|
extend FriendlyId
|
@@ -136,6 +157,29 @@ locale when replacing Latin characters:
|
|
136
157
|
movie.slug #=> "der-preis-fuers-ueberleben"
|
137
158
|
|
138
159
|
This functionality was in fact taken from earlier versions of FriendlyId.
|
160
|
+
|
161
|
+
==== Gotchas: Common Problems
|
162
|
+
|
163
|
+
FriendlyId uses a before_validation callback to generate and set the slug. This
|
164
|
+
means that if you create two model instances before saving them, it's possible
|
165
|
+
they will generate the same slug, and the second save will fail.
|
166
|
+
|
167
|
+
This can happen in two fairly normal cases: the first, when a model using nested
|
168
|
+
attributes creates more than one record for a model that uses friendly_id. The
|
169
|
+
second, in concurrent code, either in threads or multiple processes.
|
170
|
+
|
171
|
+
===== Nested Attributes
|
172
|
+
|
173
|
+
To solve the nested attributes issue, I recommend simply avoiding them when
|
174
|
+
creating more than one nested record for a model that uses FriendlyId. See {this
|
175
|
+
Github issue}[https://github.com/norman/friendly_id/issues/185] for discussion.
|
176
|
+
|
177
|
+
===== Concurrency
|
178
|
+
|
179
|
+
To solve the concurrency issue, I recommend locking the model's table against
|
180
|
+
inserts while when saving the record. See {this Github
|
181
|
+
issue}[https://github.com/norman/friendly_id/issues/180] for discussion.
|
182
|
+
|
139
183
|
=end
|
140
184
|
module Slugged
|
141
185
|
|
@@ -195,10 +239,13 @@ This functionality was in fact taken from earlier versions of FriendlyId.
|
|
195
239
|
# You can override this method in your model if, for example, you only want
|
196
240
|
# slugs to be generated once, and then never updated.
|
197
241
|
def should_generate_new_friendly_id?
|
242
|
+
base = send(friendly_id_config.base)
|
243
|
+
slug_value = send(friendly_id_config.slug_column)
|
244
|
+
return false if base.nil? && slug_value.nil?
|
198
245
|
return true if new_record?
|
199
|
-
slug_base = normalize_friendly_id
|
246
|
+
slug_base = normalize_friendly_id(base)
|
200
247
|
separator = Regexp.escape friendly_id_config.sequence_separator
|
201
|
-
slug_base != current_friendly_id.try(:sub, /#{separator}[\d]*\z/, '')
|
248
|
+
slug_base != (current_friendly_id || slug_value).try(:sub, /#{separator}[\d]*\z/, '')
|
202
249
|
end
|
203
250
|
|
204
251
|
# Sets the slug.
|
data/test/slugged_test.rb
CHANGED
@@ -42,6 +42,14 @@ class SluggedTest < MiniTest::Unit::TestCase
|
|
42
42
|
refute instance.valid?
|
43
43
|
end
|
44
44
|
|
45
|
+
test "should allow nil slugs" do
|
46
|
+
transaction do
|
47
|
+
m1 = model_class.create!
|
48
|
+
model_class.create!
|
49
|
+
assert_nil m1.slug
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
45
53
|
test "should not break validates_uniqueness_of" do
|
46
54
|
model_class = Class.new(ActiveRecord::Base) do
|
47
55
|
self.table_name = "journalists"
|
@@ -59,14 +67,16 @@ class SluggedTest < MiniTest::Unit::TestCase
|
|
59
67
|
refute instance2.valid?
|
60
68
|
end
|
61
69
|
end
|
62
|
-
|
63
|
-
|
64
70
|
end
|
65
71
|
|
66
72
|
class SlugGeneratorTest < MiniTest::Unit::TestCase
|
67
73
|
|
68
74
|
include FriendlyId::Test
|
69
75
|
|
76
|
+
def model_class
|
77
|
+
Journalist
|
78
|
+
end
|
79
|
+
|
70
80
|
test "should quote column names" do
|
71
81
|
model_class = Class.new(ActiveRecord::Base)
|
72
82
|
model_class.table_name = "journalists"
|
@@ -78,6 +88,27 @@ class SlugGeneratorTest < MiniTest::Unit::TestCase
|
|
78
88
|
flunk "column name was not quoted"
|
79
89
|
end
|
80
90
|
end
|
91
|
+
|
92
|
+
test "should not resequence lower sequences on update" do
|
93
|
+
transaction do
|
94
|
+
m1 = model_class.create! :name => "a b c d"
|
95
|
+
assert_equal "a-b-c-d", m1.slug
|
96
|
+
model_class.create! :name => "a b c d"
|
97
|
+
m1 = model_class.find(m1.id)
|
98
|
+
m1.save!
|
99
|
+
assert_equal "a-b-c-d", m1.slug
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
test "should correctly sequence slugs that end with numbers" do
|
104
|
+
transaction do
|
105
|
+
record1 = model_class.create! :name => "Peugeuot 206"
|
106
|
+
assert_equal "peugeuot-206", record1.slug
|
107
|
+
record2 = model_class.create! :name => "Peugeuot 206"
|
108
|
+
assert_equal "peugeuot-206--2", record2.slug
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
81
112
|
end
|
82
113
|
|
83
114
|
class SlugSeparatorTest < MiniTest::Unit::TestCase
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: friendly_id
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.0.0.
|
4
|
+
version: 4.0.0.rc1
|
5
5
|
prerelease: 6
|
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: 2011-
|
12
|
+
date: 2011-12-19 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: railties
|
16
|
-
requirement: &
|
16
|
+
requirement: &70137630484920 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ~>
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: 3.1.0
|
22
22
|
type: :development
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *70137630484920
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: activerecord
|
27
|
-
requirement: &
|
27
|
+
requirement: &70137630470220 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ~>
|
@@ -32,10 +32,10 @@ dependencies:
|
|
32
32
|
version: 3.1.0
|
33
33
|
type: :development
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *70137630470220
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: sqlite3
|
38
|
-
requirement: &
|
38
|
+
requirement: &70137630469500 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ~>
|
@@ -43,10 +43,10 @@ dependencies:
|
|
43
43
|
version: 1.3.4
|
44
44
|
type: :development
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *70137630469500
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: minitest
|
49
|
-
requirement: &
|
49
|
+
requirement: &70137630469000 !ruby/object:Gem::Requirement
|
50
50
|
none: false
|
51
51
|
requirements:
|
52
52
|
- - ~>
|
@@ -54,10 +54,10 @@ dependencies:
|
|
54
54
|
version: 2.4.0
|
55
55
|
type: :development
|
56
56
|
prerelease: false
|
57
|
-
version_requirements: *
|
57
|
+
version_requirements: *70137630469000
|
58
58
|
- !ruby/object:Gem::Dependency
|
59
59
|
name: mocha
|
60
|
-
requirement: &
|
60
|
+
requirement: &70137630468520 !ruby/object:Gem::Requirement
|
61
61
|
none: false
|
62
62
|
requirements:
|
63
63
|
- - ~>
|
@@ -65,10 +65,10 @@ dependencies:
|
|
65
65
|
version: 0.9.12
|
66
66
|
type: :development
|
67
67
|
prerelease: false
|
68
|
-
version_requirements: *
|
68
|
+
version_requirements: *70137630468520
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: ffaker
|
71
|
-
requirement: &
|
71
|
+
requirement: &70137630468040 !ruby/object:Gem::Requirement
|
72
72
|
none: false
|
73
73
|
requirements:
|
74
74
|
- - ~>
|
@@ -76,10 +76,10 @@ dependencies:
|
|
76
76
|
version: 1.8.0
|
77
77
|
type: :development
|
78
78
|
prerelease: false
|
79
|
-
version_requirements: *
|
79
|
+
version_requirements: *70137630468040
|
80
80
|
- !ruby/object:Gem::Dependency
|
81
81
|
name: maruku
|
82
|
-
requirement: &
|
82
|
+
requirement: &70137630467540 !ruby/object:Gem::Requirement
|
83
83
|
none: false
|
84
84
|
requirements:
|
85
85
|
- - ~>
|
@@ -87,10 +87,10 @@ dependencies:
|
|
87
87
|
version: 0.6.0
|
88
88
|
type: :development
|
89
89
|
prerelease: false
|
90
|
-
version_requirements: *
|
90
|
+
version_requirements: *70137630467540
|
91
91
|
- !ruby/object:Gem::Dependency
|
92
92
|
name: yard
|
93
|
-
requirement: &
|
93
|
+
requirement: &70137630466980 !ruby/object:Gem::Requirement
|
94
94
|
none: false
|
95
95
|
requirements:
|
96
96
|
- - ~>
|
@@ -98,10 +98,10 @@ dependencies:
|
|
98
98
|
version: 0.7.2
|
99
99
|
type: :development
|
100
100
|
prerelease: false
|
101
|
-
version_requirements: *
|
101
|
+
version_requirements: *70137630466980
|
102
102
|
- !ruby/object:Gem::Dependency
|
103
103
|
name: i18n
|
104
|
-
requirement: &
|
104
|
+
requirement: &70137630466280 !ruby/object:Gem::Requirement
|
105
105
|
none: false
|
106
106
|
requirements:
|
107
107
|
- - ~>
|
@@ -109,10 +109,10 @@ dependencies:
|
|
109
109
|
version: 0.6.0
|
110
110
|
type: :development
|
111
111
|
prerelease: false
|
112
|
-
version_requirements: *
|
112
|
+
version_requirements: *70137630466280
|
113
113
|
- !ruby/object:Gem::Dependency
|
114
114
|
name: simplecov
|
115
|
-
requirement: &
|
115
|
+
requirement: &70137630465020 !ruby/object:Gem::Requirement
|
116
116
|
none: false
|
117
117
|
requirements:
|
118
118
|
- - ! '>='
|
@@ -120,7 +120,7 @@ dependencies:
|
|
120
120
|
version: '0'
|
121
121
|
type: :development
|
122
122
|
prerelease: false
|
123
|
-
version_requirements: *
|
123
|
+
version_requirements: *70137630465020
|
124
124
|
description: ! 'FriendlyId is the "Swiss Army bulldozer" of slugging and permalink
|
125
125
|
plugins for
|
126
126
|
|
@@ -141,6 +141,7 @@ files:
|
|
141
141
|
- .yardopts
|
142
142
|
- Changelog.md
|
143
143
|
- Gemfile
|
144
|
+
- Guide.rdoc
|
144
145
|
- MIT-LICENSE
|
145
146
|
- README.md
|
146
147
|
- Rakefile
|
@@ -156,7 +157,6 @@ files:
|
|
156
157
|
- lib/friendly_id/history.rb
|
157
158
|
- lib/friendly_id/i18n.rb
|
158
159
|
- lib/friendly_id/migration.rb
|
159
|
-
- lib/friendly_id/model.rb
|
160
160
|
- lib/friendly_id/object_utils.rb
|
161
161
|
- lib/friendly_id/reserved.rb
|
162
162
|
- lib/friendly_id/scoped.rb
|
@@ -210,8 +210,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
210
210
|
version: 1.3.1
|
211
211
|
requirements: []
|
212
212
|
rubyforge_project: friendly_id
|
213
|
-
rubygems_version: 1.8.
|
213
|
+
rubygems_version: 1.8.10
|
214
214
|
signing_key:
|
215
215
|
specification_version: 3
|
216
216
|
summary: A comprehensive slugging and pretty-URL plugin.
|
217
217
|
test_files: []
|
218
|
+
has_rdoc:
|
data/lib/friendly_id/model.rb
DELETED
@@ -1,26 +0,0 @@
|
|
1
|
-
module FriendlyId
|
2
|
-
# Instance methods that will be added to all classes using FriendlyId.
|
3
|
-
module Model
|
4
|
-
|
5
|
-
attr_reader :current_friendly_id
|
6
|
-
|
7
|
-
# Convenience method for accessing the class method of the same name.
|
8
|
-
def friendly_id_config
|
9
|
-
self.class.friendly_id_config
|
10
|
-
end
|
11
|
-
|
12
|
-
# Get the instance's friendly_id.
|
13
|
-
def friendly_id
|
14
|
-
send friendly_id_config.query_field
|
15
|
-
end
|
16
|
-
|
17
|
-
# Either the friendly_id, or the numeric id cast to a string.
|
18
|
-
def to_param
|
19
|
-
if diff = changes[friendly_id_config.query_field]
|
20
|
-
diff.first
|
21
|
-
else
|
22
|
-
friendly_id.present? ? friendly_id : id.to_s
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|