friendly_id 2.1.5 → 2.2.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.
- 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
|