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.
@@ -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