friendly_id 2.1.5 → 2.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +10 -6
- data/README.rdoc +74 -49
- data/Rakefile +18 -36
- data/friendly_id.gemspec +4 -4
- data/lib/friendly_id.rb +14 -2
- data/lib/friendly_id/non_sluggable_class_methods.rb +3 -10
- data/lib/friendly_id/slug.rb +5 -7
- data/lib/friendly_id/sluggable_class_methods.rb +11 -17
- data/lib/friendly_id/sluggable_instance_methods.rb +22 -7
- data/lib/friendly_id/version.rb +2 -2
- data/test/cached_slug_test.rb +96 -0
- data/test/schema.rb +10 -0
- data/test/slug_test.rb +24 -24
- data/test/slugged_model_test.rb +15 -0
- data/test/sti_test.rb +2 -2
- data/test/test_helper.rb +12 -5
- metadata +32 -37
data/History.txt
CHANGED
@@ -1,14 +1,18 @@
|
|
1
|
-
== 2.
|
1
|
+
== 2.2.0 2009-10-19
|
2
2
|
|
3
|
-
* 1 major
|
4
|
-
*
|
3
|
+
* 1 major enhancement:
|
4
|
+
* Added slug caching, offers huge performance boost (Bruno Michel)
|
5
|
+
|
6
|
+
* 2 minor enhancements:
|
7
|
+
* Handle Unicode string length correctly (Mikhail Shirkov)
|
8
|
+
* Remove alias_method_chain in favor of super (Diego Carrion)
|
5
9
|
|
6
10
|
== 2.1.4 2009-09-01
|
7
11
|
|
8
|
-
*
|
12
|
+
* 3 minor enhancements:
|
9
13
|
* Fixed upgrade generator not installing rake tasks (Harry Love)
|
10
14
|
* Fixed handling of very large id's (Nathan Phelps)
|
11
|
-
* Fixed long index name on migration (
|
15
|
+
* Fixed long index name on migration (Rob Ingram)
|
12
16
|
|
13
17
|
== 2.1.3 2009-06-03
|
14
18
|
|
@@ -109,7 +113,7 @@ an error being thrown. (Steve Luscher)
|
|
109
113
|
|
110
114
|
== 2008-07-14
|
111
115
|
|
112
|
-
* Improved slug generation for friendly id's with apostrophes. (
|
116
|
+
* Improved slug generation for friendly id's with apostrophes. (Alistair Holt)
|
113
117
|
* Added support for namespaced models in Rakefile. (David Ramalho)
|
114
118
|
|
115
119
|
== 2008-06-23
|
data/README.rdoc
CHANGED
@@ -143,6 +143,50 @@ As of FriendlyId version 2.0.2, "new" and "index" are reseved by default. When
|
|
143
143
|
you attempt to store a reserved value, FriendlyId raises a
|
144
144
|
FriendlyId::SlugGenerationError.
|
145
145
|
|
146
|
+
=== Cached slugs
|
147
|
+
|
148
|
+
Checking the slugs table all the time has an impact on performance, so as of
|
149
|
+
2.3.0, FriendlyId offers slug caching.
|
150
|
+
|
151
|
+
This feature can improve the performance of some views by about 25%, and reduce
|
152
|
+
memory consumption by about 40% as compared to the same view without cached
|
153
|
+
slugs. The biggest improvement will be for "index" type views with many links
|
154
|
+
that depend on slugs to generate the URL.
|
155
|
+
|
156
|
+
==== Automagic setup:
|
157
|
+
|
158
|
+
To enable slug caching, simply add a column named "cached_slug" to your model.
|
159
|
+
FriendlyId will automagically use this column if it detects it:
|
160
|
+
|
161
|
+
class AddCachedSlugToUsers < ActiveRecord::Migration
|
162
|
+
def self.up
|
163
|
+
add_column :users, :cached_slug, :string
|
164
|
+
end
|
165
|
+
|
166
|
+
def self.down
|
167
|
+
remove_column :users, :cached_slug
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
Then, redo the slugs:
|
172
|
+
|
173
|
+
rake friendly_id:redo_slugs MODEL=User
|
174
|
+
|
175
|
+
*DO NOT* forget to redo the slugs, or else this feature will not work!
|
176
|
+
|
177
|
+
Also note that this feature exists largely to improve the performance of URL
|
178
|
+
generation, the part of Rails where FriendlyId has the biggest performance
|
179
|
+
impact. FriendlyId never queries against this column, so it's not necessary to
|
180
|
+
add an index on it unless your application does.
|
181
|
+
|
182
|
+
==== Using a custom column name:
|
183
|
+
|
184
|
+
You can also use a different name for the column if you choose, via the
|
185
|
+
:cache_column config option:
|
186
|
+
|
187
|
+
class User < ActiveRecord::Base
|
188
|
+
has_friendly_id :name, :use_slug => true, :cache_column => 'my_cached_slug'
|
189
|
+
end
|
146
190
|
|
147
191
|
=== Scoped Slugs
|
148
192
|
|
@@ -212,7 +256,7 @@ that uses a non-Roman writing system, your feedback would be most welcome.
|
|
212
256
|
@post.friendly_id # "友好编号在中国"
|
213
257
|
@post2 = Post.create(:title => "友好编号在中国")
|
214
258
|
@post2.friendly_id # "友好编号在中国--2"
|
215
|
-
|
259
|
+
|
216
260
|
=== Custom Slug Generation
|
217
261
|
|
218
262
|
While FriendlyId's slug generation options work for most people, you may need
|
@@ -224,7 +268,7 @@ generation block:
|
|
224
268
|
MySlugGeneratorClass::my_slug_method(text)
|
225
269
|
end
|
226
270
|
end
|
227
|
-
|
271
|
+
|
228
272
|
FriendlyId will still respect your settings for max length and reserved words,
|
229
273
|
but will use your block rather than the baked-in methods to normalize the
|
230
274
|
friendly_id text.
|
@@ -253,14 +297,10 @@ an older version of FriendlyId. Here's how to set it up.
|
|
253
297
|
script/generate friendly_id
|
254
298
|
rake db:migrate
|
255
299
|
|
256
|
-
2)
|
300
|
+
2) Add the gem to config/environment.rb:
|
257
301
|
|
258
|
-
# Rails 2.1 and higher; add this to the gem section of environment.rb:
|
259
302
|
config.gem "friendly_id"
|
260
303
|
|
261
|
-
# Rails 2.0; this goes at the bottom of environment.rb
|
262
|
-
require 'friendly_id'
|
263
|
-
|
264
304
|
3) Add some code to your models:
|
265
305
|
|
266
306
|
class Post < ActiveRecord::Base
|
@@ -283,46 +323,8 @@ rake friendly_id:remove_old_slugs MODEL=MyModelName DAYS=60
|
|
283
323
|
|
284
324
|
== Installing an older version
|
285
325
|
|
286
|
-
|
287
|
-
|
288
|
-
gem install friendly_id --version 2.0.4
|
289
|
-
|
290
|
-
Note that this version depends on the Unicode gem, which doesn't compile on
|
291
|
-
Windows and is incompatible with Ruby 1.9. It also can't be installed on
|
292
|
-
Heroku. If these are problems, you'll need to update your application to Rails
|
293
|
-
2.2 or higher and use the current release of FriendlyId.
|
294
|
-
|
295
|
-
== Upgrading from an older version
|
296
|
-
|
297
|
-
If you installed an older version of FriendlyId and want to upgrade to 2.0.x,
|
298
|
-
follow these steps:
|
299
|
-
|
300
|
-
==== Install the friendly_id Gem:
|
301
|
-
|
302
|
-
sudo gem install friendly_id
|
303
|
-
|
304
|
-
==== Add FriendlyId to environment.rb:
|
305
|
-
|
306
|
-
===== For Rails 2.1 and higher:
|
307
|
-
|
308
|
-
config.gem "friendly_id"
|
309
|
-
|
310
|
-
===== For Rails 2.0:
|
311
|
-
|
312
|
-
Add this to the bottom of environment.rb:
|
313
|
-
|
314
|
-
require 'friendly_id'
|
315
|
-
|
316
|
-
==== Remove the older version of FriendlyId:
|
317
|
-
|
318
|
-
git rm -rf vendor/plugins/friendly_id
|
319
|
-
svn delete vendor/plugins/friendly_id
|
320
|
-
# or whatever
|
321
|
-
|
322
|
-
==== Generate the upgrade migration and run it
|
323
|
-
|
324
|
-
./script/generate friendly_id_20_upgrade
|
325
|
-
rake db:migrate
|
326
|
+
You can download older versions of FriendlyId from Github that work with Rails <
|
327
|
+
2.2. These versions are, however, no longer maintained or supported.
|
326
328
|
|
327
329
|
== Hacking FriendlyId:
|
328
330
|
|
@@ -331,13 +333,36 @@ we love pull requests. :-)
|
|
331
333
|
|
332
334
|
== Bugs:
|
333
335
|
|
334
|
-
Please report them on
|
335
|
-
|
336
|
+
Please report them on the {Github issue tracker}[http://github.com/norman/friendly_id/issues]
|
337
|
+
for this project.
|
336
338
|
|
337
339
|
== Credits:
|
338
340
|
|
339
341
|
FriendlyId was created by {Norman Clarke}[mailto:norman@randomba.org],
|
340
342
|
{Adrian Mugnolo}[mailto:adrian@randomba.org], and {Emilio Tagua}[mailto:miloops@gmail.com].
|
341
343
|
|
344
|
+
We are grateful for many contributions from the Ruby and Rails community, in
|
345
|
+
particular from the following people:
|
346
|
+
|
347
|
+
* Adam Cigánek
|
348
|
+
* Alistair Holt
|
349
|
+
* Andrew Loe III
|
350
|
+
* Bence Nagy
|
351
|
+
* Bruno Michel
|
352
|
+
* Chris Nolan
|
353
|
+
* David Ramalho
|
354
|
+
* Diego Carrion
|
355
|
+
* Florian Aßmann
|
356
|
+
* Harry Love
|
357
|
+
* Jesse Crouch
|
358
|
+
* Joe Van Dyk
|
359
|
+
* Josh Nichols
|
360
|
+
* Mikhail Shirkov
|
361
|
+
* Nathan Phelps
|
362
|
+
* Rob Ingram
|
363
|
+
* Sean Abrahams
|
364
|
+
* Steve Luscher
|
365
|
+
* Tim Kadom
|
366
|
+
|
342
367
|
Copyright (c) 2008 Norman Clarke, Adrian Mugnolo and Emilio Tagua, released
|
343
368
|
under the MIT license.
|
data/Rakefile
CHANGED
@@ -1,49 +1,31 @@
|
|
1
1
|
require 'newgem'
|
2
|
+
require 'hoe'
|
2
3
|
require 'lib/friendly_id/version'
|
4
|
+
require 'hoe'
|
3
5
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
6
|
+
Hoe.spec "friendly_id" do
|
7
|
+
self.version = FriendlyId::Version::STRING
|
8
|
+
self.rubyforge_name = "friendly-id"
|
9
|
+
self.author = ['Norman Clarke', 'Adrian Mugnolo', 'Emilio Tagua']
|
10
|
+
self.email = ['norman@njclarke.com', 'adrian@mugnolo.com', 'miloops@gmail.com']
|
11
|
+
self.summary = "A comprehensive slugging and pretty-URL plugin for ActiveRecord."
|
12
|
+
self.description = 'A comprehensive slugging and pretty-URL plugin for ActiveRecord.'
|
13
|
+
self.url = 'http://friendly-id.rubyforge.org/'
|
14
|
+
self.test_globs = ['test/**/*_test.rb']
|
15
|
+
self.extra_deps << ['activerecord', '>= 2.2.3']
|
16
|
+
self.extra_deps << ['activesupport', '>= 2.2.3']
|
17
|
+
self.extra_dev_deps << ['newgem', ">= #{::Newgem::VERSION}"]
|
18
|
+
self.extra_dev_deps << ['sqlite3-ruby']
|
19
|
+
self.remote_rdoc_dir = ""
|
20
|
+
self.extra_rdoc_files = ["README.rdoc"]
|
17
21
|
end
|
18
22
|
|
19
23
|
require 'newgem/tasks'
|
20
24
|
|
21
|
-
desc "Run RCov"
|
22
|
-
task :rcov do
|
23
|
-
run_coverage Dir["test/**/*_test.rb"]
|
24
|
-
end
|
25
|
-
|
26
|
-
def run_coverage(files)
|
27
|
-
rm_f "coverage"
|
28
|
-
rm_f "coverage.data"
|
29
|
-
if files.length == 0
|
30
|
-
puts "No files were specified for testing"
|
31
|
-
return
|
32
|
-
end
|
33
|
-
files = files.join(" ")
|
34
|
-
# if RUBY_PLATFORM =~ /darwin/
|
35
|
-
# exclude = '--exclude "gems/"'
|
36
|
-
# else
|
37
|
-
# exclude = '--exclude "rubygems"'
|
38
|
-
# end
|
39
|
-
rcov = ENV["RCOV"] ? ENV["RCOV"] : "rcov"
|
40
|
-
sh "#{rcov} -Ilib:test --sort coverage --text-report #{files}"
|
41
|
-
end
|
42
|
-
|
43
25
|
desc 'Publish RDoc to RubyForge.'
|
44
26
|
task :publish_docs => [:clean, :docs] do
|
45
27
|
host = "compay@rubyforge.org"
|
46
28
|
remote_dir = "/var/www/gforge-projects/friendly-id"
|
47
29
|
local_dir = 'doc'
|
48
30
|
sh %{rsync -av --delete #{local_dir}/ #{host}:#{remote_dir}}
|
49
|
-
end
|
31
|
+
end
|
data/friendly_id.gemspec
CHANGED
@@ -2,16 +2,16 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = %q{friendly_id}
|
5
|
-
s.version = "2.
|
5
|
+
s.version = "2.2.0"
|
6
6
|
|
7
7
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
8
8
|
s.authors = ["Norman Clarke", "Adrian Mugnolo", "Emilio Tagua"]
|
9
|
-
s.date = %q{
|
9
|
+
s.date = %q{2009-09-21}
|
10
10
|
s.description = %q{A comprehensive slugging and pretty-URL plugin for ActiveRecord.}
|
11
|
-
s.email = ["norman@
|
11
|
+
s.email = ["norman@rubysouth.com", "adrian@rubysouth.com", "miloops@gmail.com"]
|
12
12
|
s.extra_rdoc_files = ["History.txt", "Manifest.txt", "README.rdoc"]
|
13
13
|
s.files = ["History.txt", "MIT-LICENSE", "Manifest.txt", "README.rdoc", "Rakefile", "config/website.yml", "friendly_id.gemspec", "generators/friendly_id/friendly_id_generator.rb", "generators/friendly_id/templates/create_slugs.rb", "generators/friendly_id_20_upgrade/friendly_id_20_upgrade_generator.rb", "generators/friendly_id_20_upgrade/templates/upgrade_friendly_id_to_20.rb", "init.rb", "lib/friendly_id.rb", "lib/friendly_id/helpers.rb", "lib/friendly_id/non_sluggable_class_methods.rb", "lib/friendly_id/non_sluggable_instance_methods.rb", "lib/friendly_id/slug.rb", "lib/friendly_id/sluggable_class_methods.rb", "lib/friendly_id/sluggable_instance_methods.rb", "lib/friendly_id/version.rb", "lib/tasks/friendly_id.rake", "lib/tasks/friendly_id.rb", "test/contest.rb", "test/custom_slug_normalizer_test.rb", "test/models/book.rb", "test/models/country.rb", "test/models/event.rb", "test/models/novel.rb", "test/models/person.rb", "test/models/post.rb", "test/models/thing.rb", "test/models/user.rb", "test/non_slugged_test.rb", "test/schema.rb", "test/scoped_model_test.rb", "test/slug_test.rb", "test/slugged_model_test.rb", "test/sti_test.rb", "test/test_helper.rb"]
|
14
|
-
s.homepage = %q{http://
|
14
|
+
s.homepage = %q{http://friendly-id.rubyforge.org/}
|
15
15
|
s.rdoc_options = ["--main", "README.rdoc"]
|
16
16
|
s.require_paths = ["lib"]
|
17
17
|
s.rubyforge_project = %q{friendly-id}
|
data/lib/friendly_id.rb
CHANGED
@@ -12,6 +12,7 @@ module FriendlyId
|
|
12
12
|
:method => nil,
|
13
13
|
:reserved => ["new", "index"],
|
14
14
|
:reserved_message => 'can not be "%s"',
|
15
|
+
:cache_column => nil,
|
15
16
|
:scope => nil,
|
16
17
|
:strip_diacritics => false,
|
17
18
|
:strip_non_ascii => false,
|
@@ -22,6 +23,7 @@ module FriendlyId
|
|
22
23
|
:max_length,
|
23
24
|
:reserved,
|
24
25
|
:reserved_message,
|
26
|
+
:cache_column,
|
25
27
|
:scope,
|
26
28
|
:strip_diacritics,
|
27
29
|
:strip_non_ascii,
|
@@ -40,9 +42,10 @@ module FriendlyId
|
|
40
42
|
# Options:
|
41
43
|
# * <tt>:use_slug</tt> - Defaults to false. Use slugs when you want to use a non-unique text field for friendly ids.
|
42
44
|
# * <tt>:max_length</tt> - Defaults to 255. The maximum allowed length for a slug.
|
45
|
+
# * <tt>:cache_column</tt> - Defaults to nil. Use this column as a cache for generating to_param (experimental).
|
43
46
|
# * <tt>:strip_diacritics</tt> - Defaults to false. If true, it will remove accents, umlauts, etc. from western characters.
|
44
47
|
# * <tt>:strip_non_ascii</tt> - Defaults to false. If true, it will all non-ascii ([^a-z0-9]) characters.
|
45
|
-
# * <tt>:reserved</tt> - Array of words that are reserved and can't be used as friendly_id's. For sluggable models, if such a word is used, it will
|
48
|
+
# * <tt>:reserved</tt> - Array of words that are reserved and can't be used as friendly_id's. For sluggable models, if such a word is used, it will raise a FriendlyId::SlugGenerationError. Defaults to ["new", "index"].
|
46
49
|
# * <tt>:reserved_message</tt> - The validation message that will be shown when a reserved word is used as a frindly_id. Defaults to '"%s" is reserved'.
|
47
50
|
#
|
48
51
|
# You can also optionally pass a block if you want to use your own custom
|
@@ -55,9 +58,15 @@ module FriendlyId
|
|
55
58
|
# # Use stringex to generate the friendly_id rather than the baked-in methods
|
56
59
|
# text.to_url
|
57
60
|
# end
|
58
|
-
# end
|
61
|
+
# end
|
59
62
|
def has_friendly_id(column, options = {}, &block)
|
60
63
|
options.assert_valid_keys VALID_FRIENDLY_ID_KEYS
|
64
|
+
unless options.has_key?(:cache_column)
|
65
|
+
if columns.any? { |c| c.name == 'cached_slug' }
|
66
|
+
options[:use_slug] = true
|
67
|
+
options[:cache_column] = :cached_slug
|
68
|
+
end
|
69
|
+
end
|
61
70
|
options = DEFAULT_FRIENDLY_ID_OPTIONS.merge(options).merge(:column => column)
|
62
71
|
write_inheritable_attribute :friendly_id_options, options
|
63
72
|
class_inheritable_accessor :friendly_id_options
|
@@ -73,6 +82,9 @@ module FriendlyId
|
|
73
82
|
if block_given?
|
74
83
|
write_inheritable_attribute :slug_normalizer_block, block
|
75
84
|
end
|
85
|
+
if options[:cache_column]
|
86
|
+
attr_protected options[:cache_column].to_sym
|
87
|
+
end
|
76
88
|
else
|
77
89
|
require 'friendly_id/non_sluggable_class_methods'
|
78
90
|
require 'friendly_id/non_sluggable_instance_methods'
|
@@ -4,25 +4,18 @@ module FriendlyId::NonSluggableClassMethods
|
|
4
4
|
|
5
5
|
include FriendlyId::Helpers
|
6
6
|
|
7
|
-
def self.extended(base) #:nodoc:#
|
8
|
-
class << base
|
9
|
-
alias_method_chain :find_one, :friendly
|
10
|
-
alias_method_chain :find_some, :friendly
|
11
|
-
end
|
12
|
-
end
|
13
|
-
|
14
7
|
protected
|
15
8
|
|
16
|
-
def
|
9
|
+
def find_one(id, options) #:nodoc:#
|
17
10
|
if id.is_a?(String) && result = send("find_by_#{ friendly_id_options[:column] }", id, options)
|
18
11
|
result.send(:found_using_friendly_id=, true)
|
19
12
|
else
|
20
|
-
result =
|
13
|
+
result = super id, options
|
21
14
|
end
|
22
15
|
result
|
23
16
|
end
|
24
17
|
|
25
|
-
def
|
18
|
+
def find_some(ids_and_names, options) #:nodoc:#
|
26
19
|
|
27
20
|
results = with_scope :find => options do
|
28
21
|
find :all, :conditions => ["#{quoted_table_name}.#{primary_key} IN (?) OR #{friendly_id_options[:column].to_s} IN (?)",
|
data/lib/friendly_id/slug.rb
CHANGED
@@ -28,13 +28,10 @@ class Slug < ActiveRecord::Base
|
|
28
28
|
# slug.normalize('This... is an example!') # => "this-is-an-example"
|
29
29
|
#
|
30
30
|
# Note that the Unicode handling in ActiveSupport may fail to process some
|
31
|
-
# characters from Polish, Icelandic and other languages.
|
32
|
-
# application uses these languages, check {out this
|
33
|
-
# article}[http://link-coming-soon.com] for information on how to get
|
34
|
-
# better urls in your application.
|
31
|
+
# characters from Polish, Icelandic and other languages.
|
35
32
|
def normalize(slug_text)
|
36
33
|
return "" if slug_text.nil? || slug_text == ""
|
37
|
-
ActiveSupport::Multibyte
|
34
|
+
ActiveSupport::Multibyte.proxy_class.new(slug_text.to_s).normalize(:kc).
|
38
35
|
gsub(/[\W]/u, ' ').
|
39
36
|
strip.
|
40
37
|
gsub(/\s+/u, '-').
|
@@ -52,7 +49,8 @@ class Slug < ActiveRecord::Base
|
|
52
49
|
# Remove diacritics (accents, umlauts, etc.) from the string. Borrowed
|
53
50
|
# from "The Ruby Way."
|
54
51
|
def strip_diacritics(string)
|
55
|
-
ActiveSupport::Multibyte
|
52
|
+
a = ActiveSupport::Multibyte.proxy_class.new(string).normalize(:kd) || ""
|
53
|
+
a.unpack('U*').inject([]) { |a, u|
|
56
54
|
if ASCII_APPROXIMATIONS[u]
|
57
55
|
a += ASCII_APPROXIMATIONS[u].unpack('U*')
|
58
56
|
elsif (u < 0x300 || u > 0x036F)
|
@@ -99,4 +97,4 @@ class Slug < ActiveRecord::Base
|
|
99
97
|
self.sequence = last.sequence + 1 if last
|
100
98
|
end
|
101
99
|
|
102
|
-
end
|
100
|
+
end
|
@@ -4,21 +4,11 @@ module FriendlyId::SluggableClassMethods
|
|
4
4
|
|
5
5
|
include FriendlyId::Helpers
|
6
6
|
|
7
|
-
def self.extended(base) #:nodoc:#
|
8
|
-
|
9
|
-
class << base
|
10
|
-
alias_method_chain :find_one, :friendly
|
11
|
-
alias_method_chain :find_some, :friendly
|
12
|
-
alias_method_chain :validate_find_options, :friendly
|
13
|
-
end
|
14
|
-
|
15
|
-
end
|
16
|
-
|
17
7
|
# Finds a single record using the friendly id, or the record's id.
|
18
|
-
def
|
8
|
+
def find_one(id_or_name, options) #:nodoc:#
|
19
9
|
|
20
10
|
scope = options.delete(:scope)
|
21
|
-
return
|
11
|
+
return super(id_or_name, options) if id_or_name.is_a?(Integer)
|
22
12
|
|
23
13
|
find_options = {:select => "#{self.table_name}.*"}
|
24
14
|
find_options[:joins] = :slugs unless options[:include] && [*options[:include]].flatten.include?(:slugs)
|
@@ -35,8 +25,10 @@ module FriendlyId::SluggableClassMethods
|
|
35
25
|
|
36
26
|
if result
|
37
27
|
result.finder_slug_name = id_or_name
|
28
|
+
elsif id_or_name.to_i.to_s != id_or_name
|
29
|
+
raise ActiveRecord::RecordNotFound
|
38
30
|
else
|
39
|
-
result =
|
31
|
+
result = super id_or_name, options
|
40
32
|
end
|
41
33
|
|
42
34
|
result
|
@@ -44,9 +36,11 @@ module FriendlyId::SluggableClassMethods
|
|
44
36
|
|
45
37
|
if friendly_id_options[:scope]
|
46
38
|
if !scope
|
47
|
-
|
39
|
+
raise ActiveRecord::RecordNotFound.new("%s; expected scope but got none" % e.message)
|
40
|
+
# e.message << "; expected scope but got none"
|
48
41
|
else
|
49
|
-
|
42
|
+
raise ActiveRecord::RecordNotFound.new("%s and scope=#{scope}" % e.message)
|
43
|
+
# e.message << " and scope=#{scope}"
|
50
44
|
end
|
51
45
|
end
|
52
46
|
|
@@ -55,7 +49,7 @@ module FriendlyId::SluggableClassMethods
|
|
55
49
|
end
|
56
50
|
|
57
51
|
# Finds multiple records using the friendly ids, or the records' ids.
|
58
|
-
def
|
52
|
+
def find_some(ids_and_names, options) #:nodoc:#
|
59
53
|
|
60
54
|
slugs, ids = get_slugs_and_ids(ids_and_names, options)
|
61
55
|
results = []
|
@@ -77,7 +71,7 @@ module FriendlyId::SluggableClassMethods
|
|
77
71
|
results
|
78
72
|
end
|
79
73
|
|
80
|
-
def
|
74
|
+
def validate_find_options(options) #:nodoc:#
|
81
75
|
options.assert_valid_keys([:conditions, :include, :joins, :limit, :offset,
|
82
76
|
:order, :select, :readonly, :group, :from, :lock, :having, :scope])
|
83
77
|
end
|
@@ -13,7 +13,7 @@ module FriendlyId::SluggableInstanceMethods
|
|
13
13
|
|
14
14
|
# Was the record found using one of its friendly ids?
|
15
15
|
def found_using_friendly_id?
|
16
|
-
|
16
|
+
!!@finder_slug_name
|
17
17
|
end
|
18
18
|
|
19
19
|
# Was the record found using its numeric id?
|
@@ -23,12 +23,20 @@ module FriendlyId::SluggableInstanceMethods
|
|
23
23
|
|
24
24
|
# Was the record found using an old friendly id?
|
25
25
|
def found_using_outdated_friendly_id?
|
26
|
+
if cache = friendly_id_options[:cache_column]
|
27
|
+
return false if send(cache) == @finder_slug_name
|
28
|
+
end
|
26
29
|
finder_slug.id != slug.id
|
27
30
|
end
|
28
31
|
|
29
32
|
# Was the record found using an old friendly id, or its numeric id?
|
30
33
|
def has_better_id?
|
31
|
-
|
34
|
+
has_a_slug? and found_using_numeric_id? || found_using_outdated_friendly_id?
|
35
|
+
end
|
36
|
+
|
37
|
+
# Does the record have (at least) one slug?
|
38
|
+
def has_a_slug?
|
39
|
+
@finder_slug_name || slug
|
32
40
|
end
|
33
41
|
|
34
42
|
# Returns the friendly id.
|
@@ -47,11 +55,14 @@ module FriendlyId::SluggableInstanceMethods
|
|
47
55
|
# id.
|
48
56
|
def slug(reload = false)
|
49
57
|
@most_recent_slug = nil if reload
|
50
|
-
@most_recent_slug ||= slugs.first
|
58
|
+
@most_recent_slug ||= slugs.first(:order => "id DESC")
|
51
59
|
end
|
52
60
|
|
53
61
|
# Returns the friendly id, or if none is available, the numeric id.
|
54
62
|
def to_param
|
63
|
+
if cache = friendly_id_options[:cache_column]
|
64
|
+
return read_attribute(cache) || id.to_s
|
65
|
+
end
|
55
66
|
slug ? slug.to_friendly_id : id.to_s
|
56
67
|
end
|
57
68
|
|
@@ -69,9 +80,9 @@ module FriendlyId::SluggableInstanceMethods
|
|
69
80
|
end
|
70
81
|
base = Slug::normalize(base)
|
71
82
|
end
|
72
|
-
|
73
|
-
if base.length > friendly_id_options[:max_length]
|
74
|
-
base = base[0...friendly_id_options[:max_length]]
|
83
|
+
|
84
|
+
if base.mb_chars.length > friendly_id_options[:max_length]
|
85
|
+
base = base.mb_chars[0...friendly_id_options[:max_length]]
|
75
86
|
end
|
76
87
|
if friendly_id_options[:reserved].include?(base)
|
77
88
|
raise FriendlyId::SlugGenerationError.new("The slug text is a reserved value")
|
@@ -79,7 +90,7 @@ module FriendlyId::SluggableInstanceMethods
|
|
79
90
|
return base
|
80
91
|
end
|
81
92
|
|
82
|
-
|
93
|
+
private
|
83
94
|
|
84
95
|
def finder_slug=(finder_slug)
|
85
96
|
@finder_slug_name = finder_slug.name
|
@@ -108,6 +119,10 @@ module FriendlyId::SluggableInstanceMethods
|
|
108
119
|
# slug so that we can recycle the name without having to use a sequence.
|
109
120
|
slugs.find(:all, :conditions => {:name => slug_text, :scope => scope}).each { |s| s.destroy }
|
110
121
|
slug = slugs.build slug_attributes
|
122
|
+
if cache = friendly_id_options[:cache_column]
|
123
|
+
new_slug = slug.to_friendly_id
|
124
|
+
send("#{cache}=", new_slug) unless send(cache) == new_slug
|
125
|
+
end
|
111
126
|
slug
|
112
127
|
end
|
113
128
|
end
|
data/lib/friendly_id/version.rb
CHANGED
@@ -0,0 +1,96 @@
|
|
1
|
+
# encoding: utf-8!
|
2
|
+
# @paris.reload
|
3
|
+
|
4
|
+
require File.dirname(__FILE__) + '/test_helper'
|
5
|
+
|
6
|
+
class CachedSlugModelTest < Test::Unit::TestCase
|
7
|
+
|
8
|
+
context "A slugged model with a cached_slugs column" do
|
9
|
+
|
10
|
+
setup do
|
11
|
+
City.delete_all
|
12
|
+
Slug.delete_all
|
13
|
+
@paris = City.new(:name => "Paris")
|
14
|
+
@paris.save!
|
15
|
+
end
|
16
|
+
|
17
|
+
should "have a slug" do
|
18
|
+
assert_not_nil @paris.slug
|
19
|
+
end
|
20
|
+
|
21
|
+
should "have a cached slug" do
|
22
|
+
assert_not_nil @paris.my_slug
|
23
|
+
end
|
24
|
+
|
25
|
+
should "have a to_param method that returns the cached slug" do
|
26
|
+
assert_equal "paris", @paris.to_param
|
27
|
+
end
|
28
|
+
|
29
|
+
should "protect the cached slug value" do
|
30
|
+
@paris.update_attributes(:my_slug => "Madrid")
|
31
|
+
@paris.reload
|
32
|
+
assert_equal("paris", @paris.my_slug)
|
33
|
+
end
|
34
|
+
|
35
|
+
context "found by its friendly id" do
|
36
|
+
|
37
|
+
setup do
|
38
|
+
@paris = City.find(@paris.friendly_id)
|
39
|
+
end
|
40
|
+
|
41
|
+
should "not indicate that it has a better id" do
|
42
|
+
assert !@paris.has_better_id?
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
context "found by its numeric id" do
|
48
|
+
|
49
|
+
setup do
|
50
|
+
@paris = City.find(@paris.id)
|
51
|
+
end
|
52
|
+
|
53
|
+
should "indicate that it has a better id" do
|
54
|
+
assert @paris.has_better_id?
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
context "with a new slug" do
|
61
|
+
|
62
|
+
setup do
|
63
|
+
@paris.name = "Paris, France"
|
64
|
+
@paris.save!
|
65
|
+
@paris.reload
|
66
|
+
end
|
67
|
+
|
68
|
+
should "have its cached slug updated" do
|
69
|
+
assert_equal "paris-france", @paris.my_slug
|
70
|
+
end
|
71
|
+
|
72
|
+
should "have its cached slug synchronized with its friendly_id" do
|
73
|
+
assert_equal @paris.my_slug, @paris.friendly_id
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
context "with a cached_slug column" do
|
80
|
+
|
81
|
+
setup do
|
82
|
+
District.delete_all
|
83
|
+
@district = District.new(:name => "Latin Quarter")
|
84
|
+
@district.save!
|
85
|
+
end
|
86
|
+
|
87
|
+
should "have its cached_slug filled automatically" do
|
88
|
+
assert_equal @district.cached_slug, "latin-quarter"
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
|
data/test/schema.rb
CHANGED
@@ -40,6 +40,16 @@ ActiveRecord::Schema.define(:version => 1) do
|
|
40
40
|
t.column "event_date", "datetime"
|
41
41
|
end
|
42
42
|
|
43
|
+
create_table "cities", :force => true do |t|
|
44
|
+
t.column "name", "string"
|
45
|
+
t.column "my_slug", "string"
|
46
|
+
end
|
47
|
+
|
48
|
+
create_table "districts", :force => true do |t|
|
49
|
+
t.column "name", "string"
|
50
|
+
t.column "cached_slug", "string"
|
51
|
+
end
|
52
|
+
|
43
53
|
create_table "slugs", :force => true do |t|
|
44
54
|
t.column "name", "string"
|
45
55
|
t.column "sluggable_id", "integer"
|
data/test/slug_test.rb
CHANGED
@@ -5,7 +5,7 @@ require File.dirname(__FILE__) + '/test_helper'
|
|
5
5
|
class SlugTest < Test::Unit::TestCase
|
6
6
|
|
7
7
|
context "a slug" do
|
8
|
-
|
8
|
+
|
9
9
|
setup do
|
10
10
|
Slug.delete_all
|
11
11
|
Post.delete_all
|
@@ -20,13 +20,13 @@ class SlugTest < Test::Unit::TestCase
|
|
20
20
|
end
|
21
21
|
|
22
22
|
end
|
23
|
-
|
23
|
+
|
24
24
|
context "the Slug class" do
|
25
|
-
|
25
|
+
|
26
26
|
should "parse the slug name and sequence" do
|
27
27
|
assert_equal ["test", "2"], Slug::parse("test--2")
|
28
28
|
end
|
29
|
-
|
29
|
+
|
30
30
|
should "parse with a default sequence of 1" do
|
31
31
|
assert_equal ["test", "1"], Slug::parse("test")
|
32
32
|
end
|
@@ -34,7 +34,7 @@ class SlugTest < Test::Unit::TestCase
|
|
34
34
|
should "should strip diacritics" do
|
35
35
|
assert_equal "acai", Slug::strip_diacritics("açaí")
|
36
36
|
end
|
37
|
-
|
37
|
+
|
38
38
|
should "strip diacritics correctly " do
|
39
39
|
input = "ÀÁÂÃÄÅÆÇÈÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿ"
|
40
40
|
output = Slug::strip_diacritics(input).split(//)
|
@@ -43,64 +43,64 @@ class SlugTest < Test::Unit::TestCase
|
|
43
43
|
assert_equal expected[i], output[i]
|
44
44
|
end
|
45
45
|
end
|
46
|
-
|
46
|
+
|
47
47
|
end
|
48
|
-
|
48
|
+
|
49
49
|
context "the Slug class's to_friendly_id method" do
|
50
|
-
|
50
|
+
|
51
51
|
should "include the sequence if the sequence is greater than 1" do
|
52
52
|
slug = Slug.new(:name => "test", :sequence => 2)
|
53
53
|
assert_equal "test--2", slug.to_friendly_id
|
54
54
|
end
|
55
|
-
|
55
|
+
|
56
56
|
should "not include the sequence if the sequence is 1" do
|
57
57
|
slug = Slug.new(:name => "test", :sequence => 1)
|
58
58
|
assert_equal "test", slug.to_friendly_id
|
59
59
|
end
|
60
|
-
|
60
|
+
|
61
61
|
end
|
62
|
-
|
63
|
-
context "the Slug class's normalize method" do
|
64
|
-
|
62
|
+
|
63
|
+
context "the Slug class's normalize method" do
|
64
|
+
|
65
65
|
should "should lowercase strings" do
|
66
66
|
assert_match /abc/, Slug::normalize("ABC")
|
67
67
|
end
|
68
|
-
|
68
|
+
|
69
69
|
should "should replace whitespace with dashes" do
|
70
70
|
assert_match /a-b/, Slug::normalize("a b")
|
71
71
|
end
|
72
|
-
|
72
|
+
|
73
73
|
should "should replace 2spaces with 1dash" do
|
74
74
|
assert_match /a-b/, Slug::normalize("a b")
|
75
75
|
end
|
76
|
-
|
76
|
+
|
77
77
|
should "should remove punctuation" do
|
78
78
|
assert_match /abc/, Slug::normalize('abc!@#$%^&*•¶§∞¢££¡¿()><?"":;][]\.,/')
|
79
79
|
end
|
80
|
-
|
80
|
+
|
81
81
|
should "should strip trailing space" do
|
82
82
|
assert_match /ab/, Slug::normalize("ab ")
|
83
83
|
end
|
84
|
-
|
84
|
+
|
85
85
|
should "should strip leading space" do
|
86
86
|
assert_match /ab/, Slug::normalize(" ab")
|
87
87
|
end
|
88
|
-
|
88
|
+
|
89
89
|
should "should strip trailing slashes" do
|
90
90
|
assert_match /ab/, Slug::normalize("ab-")
|
91
91
|
end
|
92
|
-
|
92
|
+
|
93
93
|
should "should strip leading slashes" do
|
94
94
|
assert_match /ab/, Slug::normalize("-ab")
|
95
95
|
end
|
96
|
-
|
96
|
+
|
97
97
|
should "should not modify valid name strings" do
|
98
98
|
assert_match /a-b-c-d/, Slug::normalize("a-b-c-d")
|
99
99
|
end
|
100
|
-
|
100
|
+
|
101
101
|
should "work with non roman chars" do
|
102
102
|
assert_equal "検-索", Slug::normalize("検 索")
|
103
103
|
end
|
104
|
-
|
104
|
+
|
105
105
|
end
|
106
|
-
end
|
106
|
+
end
|
data/test/slugged_model_test.rb
CHANGED
@@ -32,6 +32,16 @@ class SluggedModelTest < Test::Unit::TestCase
|
|
32
32
|
assert Post.find(@post.id)
|
33
33
|
end
|
34
34
|
|
35
|
+
should "be findable by its regular id as a string" do
|
36
|
+
assert Post.find(@post.id.to_s)
|
37
|
+
end
|
38
|
+
|
39
|
+
should "not be findable by its id if looking for something else" do
|
40
|
+
assert_raises ActiveRecord::RecordNotFound do
|
41
|
+
Post.find("#{@post.id}-i-dont-exists")
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
35
45
|
should "generate slug text" do
|
36
46
|
post = Post.new :title => "Test post", :content => "Test content"
|
37
47
|
assert_not_nil @post.slug_text
|
@@ -111,6 +121,11 @@ class SluggedModelTest < Test::Unit::TestCase
|
|
111
121
|
assert_equal @post.slug_text.length, Post.friendly_id_options[:max_length]
|
112
122
|
end
|
113
123
|
|
124
|
+
should "truncate slug in 'right way' when slug is unicode" do
|
125
|
+
@post = Post.new(:title => "ё" * 100 + 'ю' *(Post.friendly_id_options[:max_length] - 100 + 1))
|
126
|
+
assert_equal @post.slug_text.mb_chars[-1], 'ю'
|
127
|
+
end
|
128
|
+
|
114
129
|
should "be able to reuse an old friendly_id without incrementing the sequence" do
|
115
130
|
old_title = @post.title
|
116
131
|
old_friendly_id = @post.friendly_id
|
data/test/sti_test.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
require File.dirname(__FILE__) + '/test_helper'
|
4
4
|
|
5
|
-
class
|
5
|
+
class STIModelTest < Test::Unit::TestCase
|
6
6
|
|
7
7
|
context "A slugged model using single table inheritance" do
|
8
8
|
|
@@ -45,4 +45,4 @@ class SluggedModelTest < Test::Unit::TestCase
|
|
45
45
|
|
46
46
|
end
|
47
47
|
|
48
|
-
end
|
48
|
+
end
|
data/test/test_helper.rb
CHANGED
@@ -14,6 +14,16 @@ if ENV["AR_VERSION"]
|
|
14
14
|
end
|
15
15
|
require 'active_record'
|
16
16
|
require 'active_support'
|
17
|
+
|
18
|
+
ActiveRecord::Base.establish_connection :adapter => "sqlite3", :database => ":memory:"
|
19
|
+
silence_stream(STDOUT) do
|
20
|
+
load(File.dirname(__FILE__) + "/schema.rb")
|
21
|
+
end
|
22
|
+
|
23
|
+
class ActiveRecord::Base
|
24
|
+
def log_protected_attribute_removal(*args) end
|
25
|
+
end
|
26
|
+
|
17
27
|
require 'friendly_id'
|
18
28
|
require 'models/post'
|
19
29
|
require 'models/person'
|
@@ -23,8 +33,5 @@ require 'models/book'
|
|
23
33
|
require 'models/novel'
|
24
34
|
require 'models/thing'
|
25
35
|
require 'models/event'
|
26
|
-
|
27
|
-
|
28
|
-
silence_stream(STDOUT) do
|
29
|
-
load(File.dirname(__FILE__) + "/schema.rb")
|
30
|
-
end
|
36
|
+
require 'models/city'
|
37
|
+
require 'models/district'
|
metadata
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: friendly_id
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
5
|
-
version: 2.1.5
|
4
|
+
version: 2.2.0
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Norman Clarke
|
@@ -12,64 +11,59 @@ autorequire:
|
|
12
11
|
bindir: bin
|
13
12
|
cert_chain: []
|
14
13
|
|
15
|
-
date:
|
14
|
+
date: 2009-10-19 00:00:00 -03:00
|
16
15
|
default_executable:
|
17
16
|
dependencies:
|
18
17
|
- !ruby/object:Gem::Dependency
|
19
18
|
name: activerecord
|
20
|
-
|
21
|
-
|
22
|
-
|
19
|
+
type: :runtime
|
20
|
+
version_requirement:
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
22
|
requirements:
|
24
23
|
- - ">="
|
25
24
|
- !ruby/object:Gem::Version
|
26
|
-
version: 2.
|
27
|
-
|
28
|
-
version_requirements: *id001
|
25
|
+
version: 2.2.3
|
26
|
+
version:
|
29
27
|
- !ruby/object:Gem::Dependency
|
30
28
|
name: activesupport
|
31
|
-
|
32
|
-
|
33
|
-
|
29
|
+
type: :runtime
|
30
|
+
version_requirement:
|
31
|
+
version_requirements: !ruby/object:Gem::Requirement
|
34
32
|
requirements:
|
35
33
|
- - ">="
|
36
34
|
- !ruby/object:Gem::Version
|
37
|
-
version: 2.
|
38
|
-
|
39
|
-
version_requirements: *id002
|
35
|
+
version: 2.2.3
|
36
|
+
version:
|
40
37
|
- !ruby/object:Gem::Dependency
|
41
38
|
name: newgem
|
42
|
-
|
43
|
-
|
44
|
-
|
39
|
+
type: :development
|
40
|
+
version_requirement:
|
41
|
+
version_requirements: !ruby/object:Gem::Requirement
|
45
42
|
requirements:
|
46
43
|
- - ">="
|
47
44
|
- !ruby/object:Gem::Version
|
48
|
-
version: 1.
|
49
|
-
|
50
|
-
version_requirements: *id003
|
45
|
+
version: 1.5.2
|
46
|
+
version:
|
51
47
|
- !ruby/object:Gem::Dependency
|
52
48
|
name: sqlite3-ruby
|
53
|
-
|
54
|
-
|
55
|
-
|
49
|
+
type: :development
|
50
|
+
version_requirement:
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
56
52
|
requirements:
|
57
53
|
- - ">="
|
58
54
|
- !ruby/object:Gem::Version
|
59
55
|
version: "0"
|
60
|
-
|
61
|
-
version_requirements: *id004
|
56
|
+
version:
|
62
57
|
- !ruby/object:Gem::Dependency
|
63
58
|
name: hoe
|
64
|
-
|
65
|
-
|
66
|
-
|
59
|
+
type: :development
|
60
|
+
version_requirement:
|
61
|
+
version_requirements: !ruby/object:Gem::Requirement
|
67
62
|
requirements:
|
68
63
|
- - ">="
|
69
64
|
- !ruby/object:Gem::Version
|
70
|
-
version:
|
71
|
-
|
72
|
-
version_requirements: *id005
|
65
|
+
version: 2.3.3
|
66
|
+
version:
|
73
67
|
description: A comprehensive slugging and pretty-URL plugin for ActiveRecord.
|
74
68
|
email:
|
75
69
|
- norman@njclarke.com
|
@@ -124,35 +118,36 @@ files:
|
|
124
118
|
- test/sti_test.rb
|
125
119
|
- test/test_helper.rb
|
126
120
|
has_rdoc: true
|
127
|
-
homepage: http://
|
121
|
+
homepage: http://friendly-id.rubyforge.org/
|
128
122
|
licenses: []
|
129
123
|
|
130
124
|
post_install_message:
|
131
125
|
rdoc_options:
|
132
126
|
- --main
|
133
|
-
- README.
|
127
|
+
- README.txt
|
134
128
|
require_paths:
|
135
129
|
- lib
|
136
130
|
required_ruby_version: !ruby/object:Gem::Requirement
|
137
|
-
none: false
|
138
131
|
requirements:
|
139
132
|
- - ">="
|
140
133
|
- !ruby/object:Gem::Version
|
141
134
|
version: "0"
|
135
|
+
version:
|
142
136
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
143
|
-
none: false
|
144
137
|
requirements:
|
145
138
|
- - ">="
|
146
139
|
- !ruby/object:Gem::Version
|
147
140
|
version: "0"
|
141
|
+
version:
|
148
142
|
requirements: []
|
149
143
|
|
150
144
|
rubyforge_project: friendly-id
|
151
|
-
rubygems_version: 1.5
|
145
|
+
rubygems_version: 1.3.5
|
152
146
|
signing_key:
|
153
147
|
specification_version: 3
|
154
148
|
summary: A comprehensive slugging and pretty-URL plugin for ActiveRecord.
|
155
149
|
test_files:
|
150
|
+
- test/cached_slug_test.rb
|
156
151
|
- test/custom_slug_normalizer_test.rb
|
157
152
|
- test/non_slugged_test.rb
|
158
153
|
- test/scoped_model_test.rb
|