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.
@@ -1,14 +1,18 @@
1
- == 2.1.5 2011-02-22
1
+ == 2.2.0 2009-10-19
2
2
 
3
- * 1 major bugfix
4
- * Fixed compatibility with Rails 2.1 multibyte
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
- * 1 minor enhancement:
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 (Ron Ingram)
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. (alistairholt)
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
@@ -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) Load FriendlyId in your app:
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
- If you are still on Rails 2.1 or lower, please install version 2.0.4:
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 Lighthouse[http://randomba.lighthouseapp.com/projects/14675-friendly_id].
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
- $hoe = Hoe.new("friendly_id", FriendlyId::Version::STRING) do |p|
5
- p.rubyforge_name = "friendly-id"
6
- p.author = ['Norman Clarke', 'Adrian Mugnolo', 'Emilio Tagua']
7
- p.email = ['norman@rubysouth.com', 'adrian@rubysouth.com', 'miloops@gmail.com']
8
- p.summary = "A comprehensive slugging and pretty-URL plugin for ActiveRecord."
9
- p.description = 'A comprehensive slugging and pretty-URL plugin for ActiveRecord.'
10
- p.url = 'http://friendly-id.rubyforge.org/'
11
- p.test_globs = ['test/**/*_test.rb']
12
- p.extra_deps << ['activerecord', '>= 2.0.0']
13
- p.extra_deps << ['activesupport', '>= 2.0.0']
14
- p.extra_dev_deps << ['newgem', ">= #{::Newgem::VERSION}"]
15
- p.extra_dev_deps << ['sqlite3-ruby']
16
- p.remote_rdoc_dir = ""
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
@@ -2,16 +2,16 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = %q{friendly_id}
5
- s.version = "2.1.5"
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{2011-02-22}
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@njclarke.com", "adrian@mugnolo.com", "miloops@gmail.com"]
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://norman.github.com/friendly_id/}
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}
@@ -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 be treated the same as if that slug was already taken (numeric extension will be appended). Defaults to ["new", "index"].
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 find_one_with_friendly(id, options) #:nodoc:#
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 = find_one_without_friendly id, options
13
+ result = super id, options
21
14
  end
22
15
  result
23
16
  end
24
17
 
25
- def find_some_with_friendly(ids_and_names, options) #:nodoc:#
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 (?)",
@@ -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. If your
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::Chars.new(slug_text.to_s).normalize(:kc).
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::Chars.new(string).normalize(:kd).unpack('U*').inject([]) { |a, u|
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 find_one_with_friendly(id_or_name, options) #:nodoc:#
8
+ def find_one(id_or_name, options) #:nodoc:#
19
9
 
20
10
  scope = options.delete(:scope)
21
- return find_one_without_friendly(id_or_name, options) if id_or_name.is_a?(Integer)
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 = find_one_without_friendly id_or_name, options
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
- e.message << "; expected scope but got none"
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
- e.message << " and scope=#{scope}"
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 find_some_with_friendly(ids_and_names, options) #:nodoc:#
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 validate_find_options_with_friendly(options) #:nodoc:#
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
- finder_slug
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
- slug and found_using_numeric_id? || found_using_outdated_friendly_id?
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
- private
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
@@ -3,8 +3,8 @@
3
3
  module FriendlyId #:nodoc:
4
4
  module Version #:nodoc:
5
5
  MAJOR = 2
6
- MINOR = 1
7
- TINY = 4
6
+ MINOR = 2
7
+ TINY = 0
8
8
  STRING = [MAJOR, MINOR, TINY].join('.')
9
9
  end
10
10
  end
@@ -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
+
@@ -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"
@@ -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
@@ -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
@@ -2,7 +2,7 @@
2
2
 
3
3
  require File.dirname(__FILE__) + '/test_helper'
4
4
 
5
- class SluggedModelTest < Test::Unit::TestCase
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
@@ -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
- ActiveRecord::Base.establish_connection :adapter => "sqlite3", :database => ":memory:"
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
- prerelease:
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: 2011-02-22 00:00:00 -03:00
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
- prerelease: false
21
- requirement: &id001 !ruby/object:Gem::Requirement
22
- none: false
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.0.0
27
- type: :runtime
28
- version_requirements: *id001
25
+ version: 2.2.3
26
+ version:
29
27
  - !ruby/object:Gem::Dependency
30
28
  name: activesupport
31
- prerelease: false
32
- requirement: &id002 !ruby/object:Gem::Requirement
33
- none: false
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.0.0
38
- type: :runtime
39
- version_requirements: *id002
35
+ version: 2.2.3
36
+ version:
40
37
  - !ruby/object:Gem::Dependency
41
38
  name: newgem
42
- prerelease: false
43
- requirement: &id003 !ruby/object:Gem::Requirement
44
- none: false
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.4.1
49
- type: :development
50
- version_requirements: *id003
45
+ version: 1.5.2
46
+ version:
51
47
  - !ruby/object:Gem::Dependency
52
48
  name: sqlite3-ruby
53
- prerelease: false
54
- requirement: &id004 !ruby/object:Gem::Requirement
55
- none: false
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
- type: :development
61
- version_requirements: *id004
56
+ version:
62
57
  - !ruby/object:Gem::Dependency
63
58
  name: hoe
64
- prerelease: false
65
- requirement: &id005 !ruby/object:Gem::Requirement
66
- none: false
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: 1.8.0
71
- type: :development
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://norman.github.com/friendly_id/
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.rdoc
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.0
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