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/README.rdoc DELETED
@@ -1,385 +0,0 @@
1
- = FriendlyId
2
-
3
- FriendlyId is the "Swiss Army bulldozer" of slugging and permalink plugins for
4
- Ruby on Rails. It allows you to create pretty URL's and work with
5
- human-friendly strings as if they were numeric ids for ActiveRecord models.
6
-
7
- Using FriendlyId, it's easy to make your application use URL's like:
8
-
9
- http://example.com/states/washington
10
-
11
- instead of:
12
-
13
- http://example.com/states/4323454
14
-
15
- You can always read the latest version of this document at the {friendly_id page on
16
- rdoc.info}[http://rdoc.info/projects/norman/friendly_id].
17
-
18
- === Why?
19
-
20
- * Text-based id's look better
21
- * They make URL's easier to remember.
22
- * They give no hint about the number of records in your database.
23
- * They are better for search engine optimization.
24
-
25
- === But...
26
-
27
- * They can change, breaking your URL's and your SEO.
28
- * It can be tricky to ensure they're always unique.
29
- * They can become a pain to manage in large Rails applications.
30
- * They can conflict with your application's namespace.
31
-
32
- FriendlyId tries to offer you the all the advantages, and avoid or soften the
33
- potential impact of the disadvantages.
34
-
35
- == Typical Uses
36
-
37
- === User names ("non-slugged" models)
38
-
39
- Usually users have unique user names stored in a column with a unique
40
- constraint or index. In this case, all you need to do is add this to your
41
- model:
42
-
43
- has_friendly_id :login
44
-
45
- and you can then write code like this:
46
-
47
- @member = Member.find("joe") # the old Member.find(1) still works, too.
48
- @member.to_param # returns "joe"
49
- redirect_to @member # The URL would be /members/joe
50
-
51
- === Blog posts ("slugged" models)
52
-
53
- Blog posts generally have titles which are distinctive but not necessarily
54
- unique. In this and similar cases, FriendlyId provides a Slug model separate
55
- from your Post model. The Slug model handles duplicate friendly_ids, as well
56
- as versioning.
57
-
58
- Your model code would look something like this:
59
-
60
- has_friendly_id :title, :use_slug => true
61
-
62
- and you can then write code like this:
63
-
64
- @post = Post.find("new-version-released") # Post.find(1) still works, too
65
- @post.to_param # returns "new-version-released"
66
- redirect_to @post # The URL would be /posts/new-version-released
67
-
68
- Now in your controllers, if you want to prevent people from accessing your
69
- models by numeric id, you can detect whether they were found by the
70
- friendly_id:
71
-
72
- @post = Post.find(params[:id])
73
- raise "some error" if !@post.found_using_friendly_id?
74
-
75
- or, you can 301 redirect if the model was found by the numeric id if you don't
76
- care about numeric access, but want the SEO value of the friendly_id:
77
-
78
- @post = Post.find(params[:id])
79
- redirect_to @post, :status => 301 if @post.has_better_id?
80
-
81
- The "has_better_id?" method returns true if the model was found with the
82
- numeric id, or with an outdated slug.
83
-
84
- == Extra Features
85
-
86
- === Slug Versioning
87
-
88
- FriendlyId will record changes to slugs so that you can tell when the model is
89
- found with an older slug, or by the numeric id. This can be useful if you want
90
- to do a 301 redirect to your updated URL.
91
-
92
- class PostsController < ApplicationController
93
-
94
- before_filter ensure_current_post_url, :only => :show
95
-
96
- ...
97
-
98
- def ensure_current_post_url
99
- redirect_to @post, :status => :moved_permanently if @post.has_better_id?
100
- end
101
-
102
- end
103
-
104
- This is particularly useful when implementing FrindlyId on an existing
105
- website that already has many URL's with the old numeric id listed on search
106
- engines. When the search engine spiders crawl your site, they will
107
- eventually pick up the new, more SEO-friendly URL's.
108
-
109
- === Non-unique Slug Names
110
-
111
- FriendlyId will append a arbitrary number to the end of the id to keep it
112
- unique if necessary:
113
-
114
- /posts/new-version-released
115
- /posts/new-version-released--2
116
- /posts/new-version-released--3
117
- ...
118
- etc.
119
-
120
- Note that the number is preceeded by two dashes to distinguish it from the
121
- rest of the slug. This is important to enable having slugs like:
122
-
123
- /cars/peugeot-206
124
- /cars/peugeot-206--2
125
-
126
- === Reserved Names
127
-
128
- You can mark off some strings as reserved so that, for example, you don't end
129
- up with this problem:
130
-
131
- /users/joe-schmoe # A user chose "joe schmoe" as his user name - no worries.
132
- /users/new # A user chose "new" as his user name, and now no one can sign up.
133
-
134
- Here's how to do it:
135
-
136
- class Restaurant < ActiveRecord::Base
137
- belongs_to :city
138
- has_friendly_id :name, :use_slug => true, :reserved => ["my", "values"]
139
- end
140
-
141
- As of FriendlyId version 2.0.2, "new" and "index" are reseved by default. When
142
- you attempt to store a reserved value, FriendlyId raises a
143
- FriendlyId::SlugGenerationError.
144
-
145
- === Cached slugs
146
-
147
- Checking the slugs table all the time has an impact on performance, so as of
148
- 2.3.0, FriendlyId offers slug caching.
149
-
150
- This feature can improve the performance of some views by about 25%, and reduce
151
- memory consumption by about 40% as compared to the same view without cached
152
- slugs. The biggest improvement will be for "index" type views with many links
153
- that depend on slugs to generate the URL.
154
-
155
- ==== Automagic setup:
156
-
157
- To enable slug caching, simply add a column named "cached_slug" to your model.
158
- FriendlyId will automagically use this column if it detects it:
159
-
160
- class AddCachedSlugToUsers < ActiveRecord::Migration
161
- def self.up
162
- add_column :users, :cached_slug, :string
163
- end
164
-
165
- def self.down
166
- remove_column :users, :cached_slug
167
- end
168
- end
169
-
170
- Then, redo the slugs:
171
-
172
- rake friendly_id:redo_slugs MODEL=User
173
-
174
- This feature exists largely to improve the performance of URL
175
- generation, the part of Rails where FriendlyId has the biggest
176
- performance impact. FriendlyId never queries against this column, so
177
- it's not necessary to add an index on it unless your application does.
178
-
179
- Two warnings when using this feature:
180
-
181
- *DO NOT* forget to redo the slugs, or else this feature will not work!
182
-
183
- Also, this feature uses +attr_protected+ to protect the cached_slug
184
- column, unless you have already invoked +attr_accessible+. So if you
185
- wish to use +attr_accessible+, you must invoke it BEFORE you invoke
186
- +has_friendly_id+ in your class.
187
-
188
- ==== Using a custom column name:
189
-
190
- You can also use a different name for the column if you choose, via the
191
- :cache_column config option:
192
-
193
- class User < ActiveRecord::Base
194
- has_friendly_id :name, :use_slug => true, :cache_column => 'my_cached_slug'
195
- end
196
-
197
- === Scoped Slugs
198
-
199
- FriendlyId can generate unique slugs within a given scope. For example:
200
-
201
- class Restaurant < ActiveRecord::Base
202
- belongs_to :city
203
- has_friendly_id :name, :use_slug => true, :scope => :city
204
- end
205
-
206
- class City < ActiveRecord::Base
207
- has_many :restaurants
208
- has_friendly_id :name, :use_slug => true
209
- end
210
-
211
- http://example.org/cities/seattle/restaurants/joes-diner
212
- http://example.org/cities/chicago/restaurants/joes-diner
213
-
214
- Restaurant.find("joes-diner", :scope => "seattle") # returns 1 record
215
- Restaurant.find("joes-diner", :scope => "chicago") # returns 1 record
216
- Restaurant.find("joes-diner") # returns both records
217
-
218
-
219
- The value for the :scope key in your model can be a custom method you define,
220
- or the name of a relation. If it's the name of a relation, then the scope's
221
- text value will be the result of calling <code>to_param</code> on the related
222
- model record. In the example above, the city model also uses FriendlyId and so
223
- its <code>to_param</code> method returns its friendly_id: chicago or seattle.
224
-
225
- This feature is new in FriendlyId 2 and should be considered of experimental
226
- quality. Please don't use this for code that needs to run on the Space
227
- Shuttle.
228
-
229
- === Text Normalization
230
-
231
- FriendlyId's slugging can strip diacritics from Western European characters,
232
- so that you can have ASCII-only URL's; for example, conveting "ñøîéçü" to
233
- "noiecu."
234
-
235
- has_friendly_id :title, :use_slug => true, :strip_diacritics => true
236
-
237
- If you are not using slugs, you'll have to do this manually for whatever value
238
- you're using as the friendly_id.
239
-
240
- === Diacritic-sensitive normalization
241
-
242
- FriendlyId can also normalize slug text while preserving accented characters, if
243
- you prefer to leave them in your URL's:
244
-
245
- has_friendly_id :title, :use_slug => true
246
- ...
247
- @post = Post.create(:title => "¡Feliz Año!")
248
- @post.friendly_id # "feliz-año"
249
-
250
- === Unicode URL's
251
-
252
- FriendlyId can generate slugs in any language that can be written with
253
- Unicode. It does its best to strip away punctuation regardless of the language
254
- being used. Since the authors only speak English, Spanish, Portuguese and
255
- German, this has not been extensively tested with anything like Chinese,
256
- Russian, Greek, etc, but it "should work." If you're a speaker of a language
257
- that uses a non-Roman writing system, your feedback would be most welcome.
258
-
259
- has_friendly_id :title, :use_slug => true
260
- ...
261
- @post = Post.create(:title => "友好编号在中国")
262
- @post.friendly_id # "友好编号在中国"
263
- @post2 = Post.create(:title => "友好编号在中国")
264
- @post2.friendly_id # "友好编号在中国--2"
265
-
266
- === Custom Slug Generation
267
-
268
- While FriendlyId's slug generation options work for most people, you may need
269
- something else. As of version 2.0.4 you can pass in your own custom slug
270
- generation block:
271
-
272
- class Post < ActiveRecord::Base
273
- has_friendly_id :title, :use_slug => true do |text|
274
- MySlugGeneratorClass::my_slug_method(text)
275
- end
276
- end
277
-
278
- FriendlyId will still respect your settings for max length and reserved words,
279
- but will use your block rather than the baked-in methods to normalize the
280
- friendly_id text.
281
-
282
- == Getting it
283
-
284
- FriendlyId is best installed as a Ruby Gem:
285
-
286
- gem install friendly_id
287
-
288
- Alternatively, you can install it as a Rails plugin, though this is
289
- discouraged:
290
-
291
- ./script/plugin install git://github.com/norman/friendly_id.git
292
-
293
- == Setting it up
294
-
295
- The current release works with Rails 2.2 and above, and is compatible with
296
- Ruby 1.8 and 1.9.
297
-
298
- 1) Install the Gem:
299
-
300
- sudo gem install friendly_id
301
- cd my_app
302
- script/generate friendly_id
303
- rake db:migrate
304
-
305
- 2) Add the gem to config/environment.rb:
306
-
307
- config.gem "friendly_id"
308
-
309
- 3) Add some code to your models:
310
-
311
- class Post < ActiveRecord::Base
312
- has_friendly_id :title, :use_slug => true
313
- end
314
-
315
- 4) If you are using slugs, you can use a Rake task to generate slugs for your
316
- existing records:
317
-
318
- rake friendly_id:make_slugs MODEL=MyModelName
319
-
320
- If you eventually want to expire old slugs every so often, or perhaps every
321
- day via cron, you can do:
322
-
323
- rake friendly_id:remove_old_slugs
324
-
325
- The default is to remove dead slugs older than 45 days, but is configurable:
326
-
327
- rake friendly_id:remove_old_slugs MODEL=MyModelName DAYS=60
328
-
329
- == Installing an older version
330
-
331
- You can download older versions of FriendlyId from Github that work with Rails <
332
- 2.2. These versions are, however, no longer maintained or supported.
333
-
334
- == Hacking FriendlyId:
335
-
336
- FriendlyId is {hosted on Github}[git://github.com/norman/friendly_id.git], and
337
- we love pull requests. :-)
338
-
339
- == Bugs:
340
-
341
- Please report them on the {Github issue tracker}[http://github.com/norman/friendly_id/issues]
342
- for this project.
343
-
344
- If you have a bug to report, please include the following information:
345
-
346
- * Stack trace and error message.
347
- * Version information for FriendlyId, Rails and Ruby.
348
- * Any snippets of relevant model, view or controller code that shows how your are using FriendlyId.
349
-
350
- If you are able to, it helps even more if you can fork FriendlyId on Github, and
351
- add a test that reproduces the error you are experiencing.
352
-
353
- == Credits:
354
-
355
- FriendlyId was created by {Norman Clarke}[mailto:norman@njclarke.com],
356
- {Adrian Mugnolo}[mailto:adrian@mugnolo.com], and {Emilio Tagua}[mailto:miloops@gmail.com].
357
-
358
- We are grateful for many contributions from the Ruby and Rails community, in
359
- particular from the following people:
360
-
361
- * Adam Cigánek
362
- * Alistair Holt
363
- * Andrew Loe III
364
- * Ben Woosley
365
- * Bence Nagy
366
- * Bruno Michel
367
- * Chris Nolan
368
- * David Ramalho
369
- * Diego Carrion
370
- * Diego R. V.
371
- * Florian Aßmann
372
- * Harry Love
373
- * Jesse Crouch
374
- * Joe Van Dyk
375
- * Josh Nichols
376
- * Mikhail Shirkov
377
- * Nathan Phelps
378
- * Rob Ingram
379
- * Sean Abrahams
380
- * Steve Luscher
381
- * Steven Noble
382
- * Tim Kadom
383
-
384
- Copyright (c) 2008 Norman Clarke, Adrian Mugnolo and Emilio Tagua, released
385
- under the MIT license.
@@ -1,12 +0,0 @@
1
- class FriendlyId20UpgradeGenerator < Rails::Generator::Base
2
- def manifest
3
- record do |m|
4
- unless options[:skip_migration]
5
- m.migration_template(
6
- 'upgrade_friendly_id_to_20.rb', 'db/migrate', :migration_file_name => 'upgrade_friendly_id_to_20'
7
- )
8
- m.file "/../../../lib/tasks/friendly_id.rake", "lib/tasks/friendly_id.rake"
9
- end
10
- end
11
- end
12
- end
@@ -1,19 +0,0 @@
1
- class UpgradeFriendlyIdTo20 < ActiveRecord::Migration
2
-
3
- def self.up
4
- remove_column :slugs, :updated_at
5
- remove_index :slugs, :column => [:name, :sluggable_type]
6
- add_column :slugs, :sequence, :integer, :null => false, :default => 1
7
- add_column :slugs, :scope, :string, :limit => 40
8
- add_index :slugs, [:name, :sluggable_type, :scope, :sequence], :unique => true, :name => "index_slugs_on_n_s_s_and_s"
9
- end
10
-
11
- def self.down
12
- remove_index :slugs, :name => "index_slugs_on_n_s_s_and_s"
13
- remove_column :slugs, :scope
14
- remove_column :slugs, :sequence
15
- add_column :slugs, :updated_at, :datetime
16
- add_index :slugs, [:name, :sluggable_type], :unique => true
17
- end
18
-
19
- end
data/init.rb DELETED
@@ -1 +0,0 @@
1
- require "friendly_id"
@@ -1,12 +0,0 @@
1
- module FriendlyId
2
-
3
- module Helpers
4
- # Calculate expected result size for find_some_with_friendly (taken from
5
- # active_record/base.rb)
6
- def expected_size(ids_and_names, options) #:nodoc:#
7
- size = options[:offset] ? ids_and_names.size - options[:offset] : ids_and_names.size
8
- size = options[:limit] if options[:limit] && size > options[:limit]
9
- size
10
- end
11
- end
12
- end
@@ -1,34 +0,0 @@
1
- module FriendlyId::NonSluggableClassMethods
2
-
3
- include FriendlyId::Helpers
4
-
5
- protected
6
-
7
- def find_one(id, options) #:nodoc:#
8
- if id.respond_to?(:to_str) && result = send("find_by_#{ friendly_id_options[:method] }", id.to_str, options)
9
- result.send(:found_using_friendly_id=, true)
10
- else
11
- result = super id, options
12
- end
13
- result
14
- end
15
-
16
- def find_some(ids_and_names, options) #:nodoc:#
17
-
18
- names, ids = ids_and_names.partition {|id_or_name| id_or_name.respond_to?(:to_str) && id_or_name.to_str }
19
- results = with_scope :find => options do
20
- find :all, :conditions => ["#{quoted_table_name}.#{primary_key} IN (?) OR #{friendly_id_options[:method]} IN (?)",
21
- ids, names]
22
- end
23
-
24
- expected = expected_size(ids_and_names, options)
25
- if results.size != expected
26
- raise ActiveRecord::RecordNotFound, "Couldn't find all #{ name.pluralize } with IDs (#{ ids_and_names * ', ' }) AND #{ sanitize_sql options[:conditions] } (found #{ results.size } results, but was looking for #{ expected })"
27
- end
28
-
29
- results.each {|r| r.send(:found_using_friendly_id=, true) if names.include?(r.friendly_id)}
30
-
31
- results
32
-
33
- end
34
- end