friendly_id 2.2.7 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. data/Changelog.md +225 -0
  2. data/Contributors.md +28 -0
  3. data/Guide.md +509 -0
  4. data/LICENSE +1 -1
  5. data/README.md +76 -0
  6. data/Rakefile +48 -15
  7. data/extras/bench.rb +59 -0
  8. data/extras/extras.rb +31 -0
  9. data/extras/prof.rb +14 -0
  10. data/extras/template-gem.rb +1 -1
  11. data/extras/template-plugin.rb +1 -1
  12. data/generators/friendly_id/friendly_id_generator.rb +1 -1
  13. data/generators/friendly_id/templates/create_slugs.rb +2 -2
  14. data/lib/friendly_id.rb +54 -63
  15. data/lib/friendly_id/active_record2.rb +47 -0
  16. data/lib/friendly_id/active_record2/configuration.rb +66 -0
  17. data/lib/friendly_id/active_record2/finders.rb +140 -0
  18. data/lib/friendly_id/active_record2/simple_model.rb +162 -0
  19. data/lib/friendly_id/active_record2/slug.rb +111 -0
  20. data/lib/friendly_id/active_record2/slugged_model.rb +317 -0
  21. data/lib/friendly_id/active_record2/tasks.rb +66 -0
  22. data/lib/friendly_id/active_record2/tasks/friendly_id.rake +19 -0
  23. data/lib/friendly_id/configuration.rb +132 -0
  24. data/lib/friendly_id/finders.rb +106 -0
  25. data/lib/friendly_id/slug_string.rb +292 -0
  26. data/lib/friendly_id/slugged.rb +91 -0
  27. data/lib/friendly_id/status.rb +35 -0
  28. data/lib/friendly_id/test.rb +168 -0
  29. data/lib/friendly_id/version.rb +5 -5
  30. data/rails/init.rb +2 -0
  31. data/test/active_record2/basic_slugged_model_test.rb +14 -0
  32. data/test/active_record2/cached_slug_test.rb +61 -0
  33. data/test/active_record2/core.rb +93 -0
  34. data/test/active_record2/custom_normalizer_test.rb +20 -0
  35. data/test/active_record2/custom_table_name_test.rb +22 -0
  36. data/test/active_record2/scoped_model_test.rb +111 -0
  37. data/test/active_record2/simple_test.rb +59 -0
  38. data/test/active_record2/slug_test.rb +34 -0
  39. data/test/active_record2/slugged.rb +30 -0
  40. data/test/active_record2/slugged_status_test.rb +61 -0
  41. data/test/active_record2/sti_test.rb +22 -0
  42. data/test/active_record2/support/database.mysql.yml +4 -0
  43. data/test/{support/database.yml.postgres → active_record2/support/database.postgres.yml} +0 -0
  44. data/test/{support/database.yml.sqlite3 → active_record2/support/database.sqlite3.yml} +0 -0
  45. data/test/{support → active_record2/support}/models.rb +28 -0
  46. data/test/active_record2/tasks_test.rb +82 -0
  47. data/test/active_record2/test_helper.rb +107 -0
  48. data/test/friendly_id_test.rb +23 -0
  49. data/test/slug_string_test.rb +74 -0
  50. data/test/test_helper.rb +7 -102
  51. metadata +64 -56
  52. data/History.txt +0 -194
  53. data/README.rdoc +0 -385
  54. data/generators/friendly_id_20_upgrade/friendly_id_20_upgrade_generator.rb +0 -12
  55. data/generators/friendly_id_20_upgrade/templates/upgrade_friendly_id_to_20.rb +0 -19
  56. data/init.rb +0 -1
  57. data/lib/friendly_id/helpers.rb +0 -12
  58. data/lib/friendly_id/non_sluggable_class_methods.rb +0 -34
  59. data/lib/friendly_id/non_sluggable_instance_methods.rb +0 -45
  60. data/lib/friendly_id/slug.rb +0 -98
  61. data/lib/friendly_id/sluggable_class_methods.rb +0 -110
  62. data/lib/friendly_id/sluggable_instance_methods.rb +0 -161
  63. data/lib/friendly_id/tasks.rb +0 -56
  64. data/lib/tasks/friendly_id.rake +0 -25
  65. data/lib/tasks/friendly_id.rb +0 -1
  66. data/test/cached_slug_test.rb +0 -109
  67. data/test/custom_slug_normalizer_test.rb +0 -36
  68. data/test/non_slugged_test.rb +0 -99
  69. data/test/scoped_model_test.rb +0 -64
  70. data/test/slug_test.rb +0 -105
  71. data/test/slugged_model_test.rb +0 -348
  72. data/test/sti_test.rb +0 -49
  73. data/test/tasks_test.rb +0 -105
data/Changelog.md ADDED
@@ -0,0 +1,225 @@
1
+ # FriendlyId Changelog
2
+
3
+ We would like to think our many {file:Contributors contributors} for
4
+ suggestions, ideas and improvements to FriendlyId.
5
+
6
+ * Table of Contents
7
+ {:toc}
8
+
9
+
10
+ ## 2.3.0 (2010-02-04)
11
+
12
+ This is a major update à la "Snow Leopard" that adds no new major features,
13
+ but significantly improves the underlying code. Most users should be able to
14
+ upgrade with no issues other than new deprecation messages appearing in the
15
+ logs.
16
+
17
+ If, however, you have monkey-patched FriendlyId, or are maintaining your own
18
+ fork, then this upgrade may causes issues.
19
+
20
+ **Changes:**
21
+
22
+ * Sequence separator can now be configured to something other than "--".
23
+ * New option to pass arguments to {FriendlyId::SlugString#approximate_ascii!},
24
+ allowing custom approximations specific to German or Spanish.
25
+ * FriendlyId now queries against the cached_slug column, which improves performance.
26
+ * {FriendlyId::SlugString} class added, allowing finer-grained control over
27
+ Unicode friendly_id strings.
28
+ * {FriendlyId::Configuration} class added, offering more flexible/hackable
29
+ options.
30
+ * FriendlyId now raises subclasses of {FriendlyId::SlugGenerationError}
31
+ depending on the error context.
32
+ * Simple models now correctly validate friendly_id length.
33
+ * Passing block into FriendlyId deprecated in favor of overriding
34
+ the model's `normalize_friendly_id` method.
35
+ * Updating only the model's scope now also updates the slug.
36
+ * Major refactorings, cleanups and deprecations en route to the 3.0 release.
37
+
38
+ ## 2.2.7 (2009-12-16)
39
+
40
+ * Fixed typo in Rake tasks which caused delete_old_slugs to fail. (Diego R.V.)
41
+
42
+ ## 2.2.6 (2009-12-10)
43
+
44
+ * Made cached_slug automagic configuration occur outside of has_friendly_id.
45
+ This was causing problems in code where the class is loaded before
46
+ ActiveRecord has established its connection.
47
+ * Fixes for scope feature with Postgres (Ben Woosley)
48
+ * Migrated away from Hoe/Newgem for gem management.
49
+ * Made tests database-agnostic (Ben Woosley)
50
+
51
+ ## 2.2.5 (2009-11-30)
52
+
53
+ * Fixed typo in config options (Steven Noble).
54
+
55
+ ## 2.2.4 (2009-11-12)
56
+
57
+ * Fixed typo in post-install message.
58
+
59
+ ## 2.2.3 (2009-11-12)
60
+
61
+ * Fixed some issues with gem load order under 1.8.x (closes GH Issue #20)
62
+ * Made sure friendly_id generator makes a lib/tasks directory (Josh Nichols)
63
+ * Finders now accept instances of ActiveRecord::Base, matching AR's behavior
64
+ (Josh Nichols)
65
+ * SlugGenerationError now raise when a blank value is passed to
66
+ strip_diacritics
67
+
68
+ ## 2.2.2 (2009-10-26)
69
+
70
+ * Fixed Rake tasks creating duplicate slugs and not properly clearing cached
71
+ slugs (closes GH issues #14 and #15)
72
+
73
+ ## 2.2.1 (2009-10-23)
74
+
75
+ * slug cache now properly caches the slug sequence (closes GH issue #10)
76
+ * attr_protected is now only invoked on the cached_slug column if
77
+ attr_accessible has not already been invoked. (closes GH issue #11)
78
+
79
+ ## 2.2.0 (2009-10-19)
80
+
81
+ * Added slug caching, offers huge performance boost (Bruno Michel)
82
+ * Handle Unicode string length correctly (Mikhail Shirkov)
83
+ * Remove alias_method_chain in favor of super (Diego Carrion)
84
+
85
+ ## 2.1.4 (2009-09-01)
86
+
87
+ * Fixed upgrade generator not installing rake tasks (Harry Love)
88
+ * Fixed handling of very large id's (Nathan Phelps)
89
+ * Fixed long index name on migration (Rob Ingram)
90
+
91
+ ## 2.1.3 (2009-06-03)
92
+
93
+ * Always call #to_s on slug_text to allow objects such as DateTimes to be used
94
+ for the friendly_id text. (reported by Jon Ng)
95
+
96
+ ## 2.1.2 (2009-05-21)
97
+
98
+ * Non-slugged models now validate the friendly_id on save as well as create
99
+ (Joe Van Dyk).
100
+ * Replaced Shoulda with Contest.
101
+
102
+ ## 2.1.1 (2009-03-25)
103
+
104
+ * Fixed bug with find_some; if a record has old slugs, find_some will no
105
+ longer return multiple copies of that record when finding by numerical ID.
106
+ (Steve Luscher)
107
+ * Fixed bug with find_some: you can now find_some with an array of numerical
108
+ IDs without an error being thrown. (Steve Luscher)
109
+
110
+ ## 2.1.0 (2009-03-25)
111
+
112
+ * Ruby 1.9 compatibility.
113
+ * Removed dependency on ancient Unicode gem.
114
+
115
+ ## 2.0.4 (2009-02-12)
116
+
117
+ * You can now pass in your own custom slug generation blocks while setting up
118
+ friendly_id.
119
+
120
+ ## 2.0.3 (2009-02-11)
121
+
122
+ * Fixed to_param returning an empty string for non-slugged models with a null
123
+ friendly_id.
124
+
125
+ ## 2.0.2 (2009-02-09)
126
+
127
+ * Made FriendlyId depend only on ActiveRecord. It should now be possible to
128
+ use FriendlyId with Camping or any other codebase that uses AR.
129
+ * Overhauled creaky testing setup and switched to Shoulda.
130
+ * Made reserved words work for non-slugged models.
131
+
132
+ ## 2.0.1 (2009-01-19)
133
+
134
+ * Fix infinite redirect bug when using .has_better_id? in your controllers
135
+ (Sean Abrahams)
136
+
137
+
138
+ ## 2.0.0 (2009-01-03)
139
+
140
+ * Support for scoped slugs (Norman Clarke)
141
+ * Support for UTF-8 friendly_ids (Norman Clarke)
142
+ * Can now be installed via Ruby Gems, or as a Rails plugin (Norman Clarke)
143
+ * Improved handling of non-unique slugs (Norman Clarke and Adrian Mugnolo)
144
+ * Shoulda macro (Josh Nichols)
145
+ * Various small bugfixes, cleanups and refactorings
146
+
147
+ ## 1.0 (2008-12-11)
148
+
149
+ * Fixed bug that may return invalid records having similar id/names and using
150
+ MySQL. (Emilio Tagua)
151
+ * Fixed slug generation to increment only numeric extension without modifying
152
+ the name on duplicated slugs. (Emilio Tagua)
153
+
154
+ ## 2008-10-31
155
+
156
+ * Fixed compatibility with Rails 2.0.x. (Norman Clarke)
157
+ * friendly_id::make_slugs update records in chunks of 1000 to avoid running
158
+ out of memory with large datasets. (Tim Kadom)
159
+ * Fixed logic error with slug name collisions. Thanks to Tim Kadom for
160
+ reporting this bug.
161
+
162
+ ## 2008-10-22
163
+
164
+ * Reverted use of UTF8Handler - was causing errors for some people (Bence Nagy)
165
+ * Corrected find in case if a friendly_id begins with number (Bence Nagy)
166
+ * Added ability to reserve words from slugs (Adam Cigánek)
167
+
168
+ ## 2008-10-09
169
+
170
+ * Moved "require"" for iconv to init.rb (Florian Aßmann)
171
+ * Removed "require" for Unicode, use Rails' handler instead (Florian Aßmann)
172
+ * Replaced some magic numbers with constants (Florian Aßmann)
173
+ * Don't overwrite find, alias_method_chain find_one and find_some instead
174
+ (Florian Aßmann)
175
+ * Slugs behave more like ids now (Florian Aßmann)
176
+ * Can find by mixture of ids and slugs (Florian Aßmann)
177
+ * Reformatted code and comments (Florian Aßmann)
178
+ * Added support for Edge Rails' Inflector::parameterize (Norman Clarke)
179
+
180
+ ## 0.5 (2008-08-25)
181
+
182
+ * Moved strip_diacritics into Slug for easier reuse/better organization.
183
+ * Put class methods inside class << self block. (Norman Clarke)
184
+
185
+ * Small change to allow friendly_id to work better with STI. (David Ramalho)
186
+
187
+ ## 2008-07-14
188
+
189
+ * Improved slug generation for friendly id's with apostrophes. (Alistair Holt)
190
+ * Added support for namespaced models in Rakefile. (David Ramalho)
191
+
192
+ ## 2008-06-23
193
+
194
+ * Cached most recent slug to improve performance (Emilio Tagua).
195
+
196
+ ## 2008-06-10
197
+
198
+ * Added ability to find friendly_ids by array (Emilio Tagua)
199
+
200
+ ## 2008-05-15
201
+
202
+ * Made friendly_id raise an error if slug method returns a blank value.
203
+
204
+ ## 2008-05-12
205
+
206
+ * Added experimental Github gemspec.
207
+
208
+ ## 2008-04-18
209
+
210
+ * Improved slug name collision avoidance.
211
+
212
+ ## 2008-03-13
213
+
214
+ * Added :dependent => :destroy to slug relation, as suggested by Emilio Tagua.
215
+ * Fixed error when renaming a slugged item back to a previously used name.
216
+ * Incorporated documentation changes suggested by Jesse Crouch and Chris Nolan.
217
+
218
+ ## 2008-02-07
219
+
220
+ * Applied patches from blog commenter "suntzu" to fix problem with model
221
+ values were being overwritten.
222
+ * Applied patch from Dan Blue to make friendly_id no longer ignore options on
223
+ ActiveRecordBase#find.
224
+ * Added call to options.assert_valid_keys in has_friendly_id. Thanks to W.
225
+ Andrew Loe III for pointing out that this was missing.
data/Contributors.md ADDED
@@ -0,0 +1,28 @@
1
+ We are grateful for many contributions from the Ruby and Rails
2
+ community, in particular from the following people:
3
+
4
+ * Adam Cigánek
5
+ * Alexander Gräfe
6
+ * Alistair Holt
7
+ * Andrew Loe III
8
+ * Ben Woosley
9
+ * Bence Nagy
10
+ * Bruno Michel
11
+ * Chris Nolan
12
+ * David Ramalho
13
+ * Diego Carrion
14
+ * Diego R. V.
15
+ * Florian Aßmann
16
+ * Harry Love
17
+ * Ian Stewart
18
+ * Jesse Crouch
19
+ * Joe Van Dyk
20
+ * Josh Nichols
21
+ * Mikhail Shirkov
22
+ * Nathan Phelps
23
+ * Rdavila
24
+ * Rob Ingram
25
+ * Sean Abrahams
26
+ * Steve Luscher
27
+ * Steven Noble
28
+ * Tim Kadom
data/Guide.md ADDED
@@ -0,0 +1,509 @@
1
+ # FriendlyId Guide
2
+
3
+ * Table of Contents
4
+ {:toc}
5
+
6
+ ## Overview
7
+
8
+ FriendlyId is a Ruby gem which allows you to work with human-friendly strings
9
+ as if they were numeric ids for ActiveRecord models. This facilitates replacing
10
+ "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
+ ## Simple Models
19
+
20
+ The simplest way to use FriendlyId is with a model that has a uniquely indexed
21
+ column with no spaces or special characters, and that is seldom or never
22
+ updated. The most common example of this is a user name or login column:
23
+
24
+ class User < ActiveRecord::Base
25
+ :validates_format_of :login, :with => /\A[a-z0-9]\z/i
26
+ has_friendly_id :login
27
+ end
28
+
29
+ @user = User.find "joe" # the old User.find(1) still works, too
30
+ @user.to_param # returns "joe"
31
+ redirect_to @user # the URL will be /users/joe
32
+
33
+ In this case, FriendlyId assumes you want to use the column as-is; FriendlyId
34
+ will never modify the value of the column, and your application must ensure
35
+ that the value is admissible in a URL:
36
+
37
+ class City < ActiveRecord::Base
38
+ has_friendly_id :name
39
+ end
40
+
41
+ @city.find "Viña del Mar"
42
+ redirect_to @city # the URL will be /cities/Viña%20del%20Mar
43
+
44
+ For this reason, it is often more convenient to use Slugs rather than a single
45
+ column.
46
+
47
+ ## Slugged Models
48
+
49
+ FriendlyId uses a separate table to store slugs for models which require some
50
+ processing of the friendly_id text. The most common example is a blog post's
51
+ title, which may have spaces, uppercase characters, or other attributes you
52
+ wish to modify to make them more suitable for use in URL's.
53
+
54
+ class Post < ActiveRecord::Base
55
+ has_friendly_id :title, :use_slug => true
56
+ end
57
+
58
+ @post = Post.create(:title => "This is the first post!")
59
+ @post.friendly_id # returns "this-is-the-first-post"
60
+ redirect_to @post # the URL will be /posts/this-is-the-first-post
61
+
62
+ If you are unsure whether to use slugs, then your best bet is to use them,
63
+ because FriendlyId provides many useful features that only work with slugs.
64
+ These features are explained in detail {file:Guide.md#features below}.
65
+
66
+ ## Installation
67
+
68
+ FriendlyId can be installed as a gem, or as a Rails plugin. It is compatible
69
+ with Rails 2.2.x, 2.3.x. Support for Rails 3.x is in progress.
70
+
71
+ ### As a Gem
72
+
73
+ gem install friendly_id
74
+
75
+ #### Rails 2.2.x - 2.3.x
76
+
77
+ After installing the gem, add an entry in environment.rb:
78
+
79
+ config.gem "friendly_id", :version => ">= 2.3"
80
+
81
+ ### As a Plugin
82
+
83
+ Plugin installation is simple for all supported versions of Rails:
84
+
85
+ ./script/plugin install git://github.com/norman/friendly_id.git
86
+
87
+ However, installing as a gem offers simpler version control than plugin
88
+ installation. Whenever possible, install as a gem instead. Plugin support will
89
+ probably be removed in version 3.0.
90
+
91
+ ### Setup
92
+
93
+ After installing either as a gem or plugin, run:
94
+
95
+ ./script/generate friendly_id
96
+ rake db:migrate
97
+
98
+ This will install the Rake tasks and slug migration for FriendlyId. If you are
99
+ not going to use slugs, you can do:
100
+
101
+ ./script/generate friendly_id --skip-migration
102
+
103
+ FriendlyId is now set up and ready for you to use.
104
+
105
+ ## Configuration
106
+
107
+ FriendlyId is configured in your model using the `has_friendly_id` method:
108
+
109
+ has_friendly_id :a_column_or_method options_hash
110
+
111
+ class Post < ActiveRecord::Base
112
+ # use the "title" column as the basis of the friendly_id, and use slugs
113
+ has_friendly_id :title, :use_slug => true,
114
+ # remove accents and other diacritics from Western characters
115
+ :approximate_ascii => true,
116
+ # don't use slugs longer than 50 chars
117
+ :max_length => 50
118
+ end
119
+
120
+ Read on to learn about the various features that can be configured. For the
121
+ full list of valid configuration options, see the instance attribute summary
122
+ for {FriendlyId::Configuration}.
123
+
124
+ # Features
125
+
126
+ ## FriendlyId Strings
127
+
128
+ FriendlyId comes with {FriendlyId::SlugString excellent support for Unicode
129
+ strings}. When using slugs, FriendlyId will automatically modify the slug text
130
+ to make it more suitable for use in a URL:
131
+
132
+ class City < ActiveRecord::Base
133
+ has_friendly_id :name, :use_slug => true
134
+ end
135
+
136
+ @city.create :name => "Viña del Mar"
137
+ @city.friendly_id # will be "viña-del-mar"
138
+
139
+ By default, the string is {FriendlyId::SlugString#downcase! downcased} and
140
+ {FriendlyId::SlugString#clean! stripped}, {FriendlyId::SlugString#with_dashes! spaces are replaced with dashes},
141
+ and {FriendlyId::SlugString#word_chars! non-word characters are removed}.
142
+
143
+ ### Replacing Accented Characters
144
+
145
+ If your strings use Western characters, you can use the `:approximate_ascii` option to remove
146
+ accents and other diacritics:
147
+
148
+ class City < ActiveRecord::Base
149
+ has_friendly_id :name, :use_slug => true, :approximate_ascii => true
150
+ end
151
+
152
+ @city.create :name => "Łódź, Poland"
153
+ @city.friendly_id # will be "lodz-poland"
154
+
155
+ There are special options for some languages:
156
+
157
+ ### German Approximations
158
+
159
+ class Person < ActiveRecord::Base
160
+ has_friendly_id :name, :use_slug => true, :approximate_ascii => true,
161
+ :ascii_approximation_options => :german
162
+ end
163
+
164
+ @person.create :name => "Jürgen Müller"
165
+ @person.friendly_id # will be "juergen-mueller"
166
+
167
+ ### Spanish Approximations
168
+
169
+ class Post < ActiveRecord::Base
170
+ has_friendly_id :title, :use_slug => true, :approximate_ascii => true,
171
+ :ascii_approximation_options => :spanish
172
+ end
173
+
174
+ @post.create(:title => "¡Feliz año!")
175
+ @post.title # will be "feliz-anno"
176
+
177
+ ### Approximations for Other Languages
178
+
179
+ You can add custom approximations for your language by adding Hash of
180
+ approximations to {FriendlyId::SlugString::APPROXIMATIONS}. The approximations
181
+ must be listed as Unicode decimal numbers, and arrays of numbers.
182
+
183
+ ### Unicode Slugs
184
+
185
+ By default, any character outside the Unicode Western character sets will be
186
+ passed through untouched, allowing you to have slugs in Arabic, Japanese,
187
+ Greek, etc:
188
+
189
+ @post.create :title => "katakana: ゲコゴサザシジ!"
190
+ @post.friendly_id # will be: "katakana-ゲコゴサザシジ"
191
+
192
+ ### ASCII Slugs
193
+
194
+ You can also configure FriendlyId using `:strip_non_ascii` to completely delete
195
+ any non-ascii characters:
196
+
197
+ class Post < ActiveRecord::Base
198
+ has_friendly_id :title, :use_slug => true, :strip_non_ascii => true
199
+ end
200
+
201
+ @post.create :title => "katakana: ゲコゴサザシジ!"
202
+ @post.friendly_id # will be: "katakana"
203
+
204
+
205
+ ### Using a Custom Method to Generate the Slug Text
206
+
207
+ FriendlyId can use either a column or a method to generate the slug text for
208
+ your model:
209
+
210
+ class City < ActiveRecord::Base
211
+
212
+ belongs_to :country
213
+ has_friendly_id :name_and_country, :use_slug => true
214
+
215
+ def name_and_country
216
+ #{name} #{country.name}
217
+ end
218
+
219
+ end
220
+
221
+ @country = Country.create(:name => "Argentina")
222
+ @city = City.create(:name => "Buenos Aires", :country => @country)
223
+ @city.friendly_id # will be "buenos-aires-argentina"
224
+
225
+ One word of caution: in the example above, if the country's name were updated,
226
+ say, to "Argentine Republic", the city's friendly_id would not be
227
+ automatically updated. For this reason, it's a good idea to avoid using
228
+ frequently-updated relations as a part of the friendly_id.
229
+
230
+ ## Using a Custom Method to Process the Slug Text
231
+
232
+ If the built-in slug text handling options don't work for your application,
233
+ you can override the `normalize_friendly_id` method in your model class in
234
+ order to fine-tune the output:
235
+
236
+ class City < ActiveRecord::Base
237
+
238
+ def normalize_friendly_id(text)
239
+ my_text_modifier_method(text)
240
+ end
241
+
242
+ end
243
+
244
+ The normalize_friendly_id method takes a single argument and receives an
245
+ instance of {FriendlyId::SlugString}, a class which wraps a regular Ruby
246
+ string with some additional formatting options inherits Multibyte support from
247
+ ActiveSupport::Multibyte::Chars.
248
+
249
+ ### Converting non-Western characters to ASCII with Stringex
250
+
251
+ Stringex is a library which provides some interesting options for transliterating
252
+ non-Western strings to ASCII:
253
+
254
+ "你好".to_url => "ni-hao"
255
+
256
+ Using Stringex with FriendlyId is a simple matter of installing and requiring
257
+ the `stringex` gem, and overriding the `normalize_friendly_id` method in your
258
+ model:
259
+
260
+ class City < ActiveRecord::Base
261
+
262
+ def normalize_friendly_id(text)
263
+ text.to_url
264
+ end
265
+
266
+ end
267
+
268
+ ## Redirecting to the Current Friendly URL
269
+
270
+ FriendlyId maintains a history of your record's older slugs, so if your
271
+ record's friendly_id changes, your URL's won't break. It offers several
272
+ methods to determine whether the model instance was found using the most
273
+ recent friendly_id. This helps you redirect to your "unfriendly" URL's to your
274
+ new "friendly" ones when adding FriendlyId to an existing application:
275
+
276
+ class PostsController < ApplicationController
277
+
278
+ before_filter ensure_current_post_url, :only => :show
279
+
280
+ ...
281
+
282
+ def ensure_current_post_url
283
+ redirect_to @post, :status => :moved_permanently unless @post.friendly_id_status.best?
284
+ end
285
+
286
+ end
287
+
288
+ For more information, take a look at the documentation for {FriendlyId::Status}.
289
+
290
+ ## Non-unique Slugs
291
+
292
+ FriendlyId will append a arbitrary number to the end of the id to keep it
293
+ unique if necessary:
294
+
295
+ /posts/new-version-released
296
+ /posts/new-version-released--2
297
+ /posts/new-version-released--3
298
+ ...
299
+ etc.
300
+
301
+ Note that the number is preceded by "--" to distinguish it from the
302
+ rest of the slug. This is important to enable having slugs like:
303
+
304
+ /cars/peugeot-206
305
+ /cars/peugeot-206--2
306
+
307
+ You can configure the separator string used by your model by setting the
308
+ `:sequence_separator` option in `has_friendly_id`:
309
+
310
+ has_friendly_id :title, :use_slug => true, :sequence_separator => ";"
311
+
312
+ You can also override the default used in
313
+ {FriendlyId::Configuration::DEFAULTS} to set the value for any model using
314
+ FriendlyId. If you change this value in an existing application, be sure to
315
+ {file:Guide.md#regenerating_slugs regenerate the slugs} afterwards.
316
+
317
+ ## Reserved Words
318
+
319
+ You can configure a list of strings as reserved so that, for example, you
320
+ don't end up with this problem:
321
+
322
+ /users/joe-schmoe # A user chose "joe schmoe" as his user name - no worries.
323
+ /users/new # A user chose "new" as his user name, and now no one can sign up.
324
+
325
+ Reserved words are configured using the `:reserved_words` option:
326
+
327
+ class Restaurant < ActiveRecord::Base
328
+ belongs_to :city
329
+ has_friendly_id :name, :use_slug => true, :reserved_words => ["my", "values"]
330
+ end
331
+
332
+ The strings "new" and "index" are reserved by default. When you attempt to
333
+ store a reserved value, FriendlyId raises a
334
+ {FriendlyId::ReservedError}. You can also override the default
335
+ reserved words in {FriendlyId::Configuration::DEFAULTS} to set the value for any
336
+ model using FriendlyId.
337
+
338
+ ## Caching the FriendlyId Slug for Better Performance
339
+
340
+ Checking the slugs table all the time has an impact on performance, so as of
341
+ 2.2.0, FriendlyId offers slug caching.
342
+
343
+ ### Automatic setup
344
+
345
+ To enable slug caching, simply add a column named "cached_slug" to your model.
346
+ FriendlyId will automatically use this column if it detects it:
347
+
348
+ class AddCachedSlugToUsers < ActiveRecord::Migration
349
+ def self.up
350
+ add_column :users, :cached_slug, :string
351
+ end
352
+
353
+ def self.down
354
+ remove_column :users, :cached_slug
355
+ end
356
+ end
357
+
358
+ Then, redo the slugs:
359
+
360
+ rake friendly_id:redo_slugs MODEL=User
361
+
362
+ FriendlyId will also query against the cache column if it's available, which
363
+ can significantly improve the performance of many queries, particularly when
364
+ passing an array of friendly ids to #find.
365
+
366
+ A few warnings when using this feature:
367
+
368
+ * *DO NOT* forget to redo the slugs, or else this feature will not work!
369
+ * This feature uses `attr_protected` to protect the `cached_slug` column,
370
+ unless you have already invoked `attr_accessible`. If you wish to use
371
+ `attr_accessible`, you must invoke it BEFORE you invoke `has_friendly_id` in
372
+ your class.
373
+ * FriendlyId can not query against the slug cache when you pass a :scope
374
+ argument to #find. Try to avoid passing an array of friendly id's and a
375
+ scope to #find, as this will result in weak performance.
376
+
377
+ ### Using a custom column name
378
+
379
+ You can also use a different name for the column if you choose, via the
380
+ `:cache_column` config option:
381
+
382
+ class User < ActiveRecord::Base
383
+ has_friendly_id :name, :use_slug => true, :cache_column => 'my_cached_slug'
384
+ end
385
+
386
+
387
+ ## Scoped Slugs
388
+
389
+ FriendlyId can generate unique slugs within a given scope. For example, assume
390
+ you have an application that displays restaurants. Without scoped slugs, if
391
+ two restaurants are named "Joe's Diner," the second one will end up with
392
+ "joes-diner--2" as its friendly_id. Using scoped allows you to keep the
393
+ slug names unique for each city, so that the second "Joe's Diner" could have
394
+ the slug "joes-diner" if it's located in a different city:
395
+
396
+ class Restaurant < ActiveRecord::Base
397
+ belongs_to :city
398
+ has_friendly_id :name, :use_slug => true, :scope => :city
399
+ end
400
+
401
+ class City < ActiveRecord::Base
402
+ has_many :restaurants
403
+ has_friendly_id :name, :use_slug => true
404
+ end
405
+
406
+ http://example.org/cities/seattle/restaurants/joes-diner
407
+ http://example.org/cities/chicago/restaurants/joes-diner
408
+
409
+ Restaurant.find("joes-diner", :scope => "seattle") # returns 1 record
410
+ Restaurant.find("joes-diner", :scope => "chicago") # returns 1 record
411
+ Restaurant.find("joes-diner") # returns both records
412
+
413
+ The value for the `:scope` key in your model can be a custom method you
414
+ define, or the name of a relation. If it's the name of a relation, then the
415
+ scope's text value will be the result of calling `to_param` on the related
416
+ model record. In the example above, the city model also uses FriendlyId and so
417
+ its `to_param` method returns its friendly_id: "chicago" or "seattle".
418
+
419
+ ### Updating a Relation's Scoped Slugs
420
+
421
+ When using a relation as the scope, updating the relation will update the
422
+ slugs, but only if both models have specified the relationship. In the above
423
+ example, updates to City will update the slugs for Restaurant because City
424
+ specifies that it `has_many :restaurants`.
425
+
426
+ ### Routes for Scoped Models
427
+
428
+ Note that FriendlyId does not set up any routes for scoped models; you must
429
+ do this yourself in your application. Here's an example of one way to set
430
+ this up:
431
+
432
+ # in routes.rb
433
+ map.resources :restaurants
434
+ map.restaurant "/restaurants/:scope/:id", :controller => "restaurants"
435
+
436
+ # in views
437
+ link_to 'Show', restaurant_path(restaurant.city, restaurant)
438
+
439
+ # in controllers
440
+ @restaurant = Restaurant.find(params[:id], :scope => params[:scope])
441
+
442
+
443
+ ## FriendlyId Rake Tasks
444
+
445
+ FriendlyId provides several tasks to help maintain your application.
446
+
447
+ ### Generating New Slugs For the First Time
448
+
449
+ friendly_id:make_slugs MODEL=<model name>
450
+
451
+ Use this task to generate slugs after installing FriendlyId in a new
452
+ application.
453
+
454
+ ### Regenerating Slugs
455
+
456
+ friendly_id:redo_slugs MODEL=<model name>
457
+
458
+ Use this task to regenerate slugs after making any changes to your model's
459
+ FriendlyId configuration options that affect slug generation. For example,
460
+ if you introduce a `cached_slug` column or change the `:seqence_separator`.
461
+
462
+ ### Deleting Old Slugs
463
+
464
+ rake friendly_id:remove_old_slugs MODEL=<model name> DAYS=<days>
465
+
466
+ Use this task if you wish to delete expired slugs; manually or perhaps via
467
+ cron. If you don't specify the days option, the default is to remove unused
468
+ slugs older than 45 days.
469
+
470
+ # Hacking FriendlyId
471
+
472
+ A couple of notes for programmers intending to work on FriendlyId:
473
+
474
+ If you intend to send a pull request, in general it's best to make minor
475
+ changes in the master branch, and major changes in the edge branch.
476
+
477
+ Before removing any public or protected methods, FriendlyId will deprecate
478
+ them through one major release cycle. Private methods may, however, change at
479
+ any time.
480
+
481
+ ## Some Benchmarks
482
+
483
+ These benchmarks can give you an idea of FriendlyId's impact on the
484
+ performance of your application. Of course your results may vary.
485
+
486
+ Note that much of the performance difference can be attributed to finding an
487
+ SQL record by a text column. Finding a single record by numeric primary key is
488
+ always the fastest operation, and thus the best choice when possible. If you
489
+ decide not to use FriendlyId for performance reasons, keep in mind that your
490
+ own solution is unlikely to be any faster than FriendlyId with cached slugs
491
+ enabled. But if it is, then your patches would be very welcome!
492
+
493
+ ruby 1.9.1p378 (2010-01-10 revision 26273) [i386-darwin10.2.0]
494
+ friendly_id (2.3.0)
495
+ sqlite3 3.6.19 in-memory database
496
+ ------------------------------------------------------------------------------------------------
497
+ find model using id x10000 | 2.948 | 0 |
498
+ find model using array of ids x10000 | 6.020 | 0 |
499
+ find unslugged model using friendly id x10000 | 4.474 | 52% |
500
+ find unslugged model using array of friendly ids x10000 | 6.498 | 7% |
501
+ find slugged model using friendly id x10000 | 7.536 | 155% |
502
+ find slugged model using array of friendly ids x10000 | 18.020 | 200% |
503
+ find cached slugged model using friendly id x10000 | 4.791 | 63% |
504
+ find cached slugged model using array of friendly ids x10000 | 7.275 | 21% |
505
+ find model using id, then to_param x10000 | 2.974 | 0 |
506
+ find unslugged model using friendly id, then to_param x10000 | 4.608 | 55% |
507
+ find slugged model using friendly id, then to_param x10000 | 12.589 | 323% |
508
+ find cached slugged model using friendly id, then to_param x10000 | 5.037 | 69% |
509
+ ------------------------------------------------------------------------------------------------