dougcole-friendly_id 2.0.5 → 2.0.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/History.txt +20 -0
  2. data/Manifest.txt +10 -18
  3. data/README.rdoc +34 -5
  4. data/Rakefile +8 -0
  5. data/VERSION.yml +1 -1
  6. data/lib/friendly_id.rb +33 -13
  7. data/lib/friendly_id/non_sluggable_class_methods.rb +3 -3
  8. data/lib/friendly_id/non_sluggable_instance_methods.rb +9 -1
  9. data/lib/friendly_id/slug.rb +14 -12
  10. data/lib/friendly_id/sluggable_class_methods.rb +14 -3
  11. data/lib/friendly_id/sluggable_instance_methods.rb +11 -4
  12. data/lib/friendly_id/version.rb +1 -1
  13. data/test/custom_slug_normalizer_test.rb +35 -0
  14. data/test/models/book.rb +2 -0
  15. data/test/{fixtures → models}/country.rb +0 -0
  16. data/test/models/novel.rb +3 -0
  17. data/test/{fixtures → models}/person.rb +0 -0
  18. data/test/models/post.rb +3 -0
  19. data/test/models/thing.rb +6 -0
  20. data/test/{fixtures → models}/user.rb +0 -0
  21. data/test/non_slugged_test.rb +71 -60
  22. data/test/schema.rb +29 -20
  23. data/test/scoped_model_test.rb +43 -13
  24. data/test/slug_test.rb +93 -74
  25. data/test/slugged_model_test.rb +263 -0
  26. data/test/sti_test.rb +48 -0
  27. data/test/test_helper.rb +30 -29
  28. metadata +15 -20
  29. data/lib/friendly_id/shoulda_macros.rb +0 -36
  30. data/test/database.yml +0 -3
  31. data/test/fixtures/countries.yml +0 -4
  32. data/test/fixtures/people.yml +0 -7
  33. data/test/fixtures/post.rb +0 -3
  34. data/test/fixtures/posts.yml +0 -23
  35. data/test/fixtures/slugs.yml +0 -53
  36. data/test/fixtures/users.yml +0 -7
  37. data/test/rails/2.x/app/controllers/application.rb +0 -0
  38. data/test/rails/2.x/config/boot.rb +0 -109
  39. data/test/rails/2.x/config/database.yml +0 -3
  40. data/test/rails/2.x/config/environment.rb +0 -7
  41. data/test/rails/2.x/config/environments/test.rb +0 -6
  42. data/test/rails/2.x/config/routes.rb +0 -0
  43. data/test/sluggable_test.rb +0 -185
data/History.txt CHANGED
@@ -1,3 +1,23 @@
1
+ == 2.0.4 2009-02-12
2
+
3
+ * 1 major enhancment:
4
+ * You can now pass in your own custom slug generation blocks while setting up friendly_id.
5
+
6
+ == 2.0.3 2009-02-11
7
+
8
+ * 1 minor enhancment:
9
+ * Fixed to_param returning an empty string for non-slugged models with a null friendly_id.
10
+
11
+ == 2.0.2 2009-02-09
12
+
13
+ * 2 major enhancements:
14
+ * Made FriendlyId depend only on ActiveRecord. It should now be possible to
15
+ use FriendlyId with Camping or any other codebase that uses AR.
16
+ * Overhauled creaky testing setup and switched to Shoulda.
17
+
18
+ * 1 minor enhancment:
19
+ * Made reserved words work for non-slugged models.
20
+
1
21
  == 2.0.1 2009-01-19
2
22
 
3
23
  * 1 minor enhancements:
data/Manifest.txt CHANGED
@@ -14,32 +14,24 @@ lib/friendly_id.rb
14
14
  lib/friendly_id/helpers.rb
15
15
  lib/friendly_id/non_sluggable_class_methods.rb
16
16
  lib/friendly_id/non_sluggable_instance_methods.rb
17
- lib/friendly_id/shoulda_macros.rb
18
17
  lib/friendly_id/slug.rb
19
18
  lib/friendly_id/sluggable_class_methods.rb
20
19
  lib/friendly_id/sluggable_instance_methods.rb
21
20
  lib/friendly_id/version.rb
22
21
  lib/tasks/friendly_id.rake
23
22
  lib/tasks/friendly_id.rb
24
- test/database.yml
25
- test/fixtures/countries.yml
26
- test/fixtures/country.rb
27
- test/fixtures/people.yml
28
- test/fixtures/person.rb
29
- test/fixtures/post.rb
30
- test/fixtures/posts.yml
31
- test/fixtures/slugs.yml
32
- test/fixtures/user.rb
33
- test/fixtures/users.yml
23
+ test/custom_slug_normalizer_test.rb
24
+ test/models/book.rb
25
+ test/models/country.rb
26
+ test/models/novel.rb
27
+ test/models/person.rb
28
+ test/models/post.rb
29
+ test/models/thing.rb
30
+ test/models/user.rb
34
31
  test/non_slugged_test.rb
35
- test/rails/2.x/app/controllers/application.rb
36
- test/rails/2.x/config/boot.rb
37
- test/rails/2.x/config/database.yml
38
- test/rails/2.x/config/environment.rb
39
- test/rails/2.x/config/environments/test.rb
40
- test/rails/2.x/config/routes.rb
41
32
  test/schema.rb
42
33
  test/scoped_model_test.rb
43
34
  test/slug_test.rb
44
- test/sluggable_test.rb
35
+ test/slugged_model_test.rb
36
+ test/sti_test.rb
45
37
  test/test_helper.rb
data/README.rdoc CHANGED
@@ -136,9 +136,13 @@ Here's how to do it:
136
136
 
137
137
  class Restaurant < ActiveRecord::Base
138
138
  belongs_to :city
139
- has_friendly_id :name, :use_slug => true, :reserved => ["new", "index"]
139
+ has_friendly_id :name, :use_slug => true, :reserved => ["my", "values"]
140
140
  end
141
141
 
142
+ As of FriendlyId version 2.0.2, "new" and "index" are reseved by default. When
143
+ you attempt to store a reserved value, FriendlyId raises a
144
+ FriendlyId::SlugGenerationError.
145
+
142
146
 
143
147
  === Scoped Slugs
144
148
 
@@ -208,6 +212,33 @@ that uses a non-Roman writing system, your feedback would be most welcome.
208
212
  @post.friendly_id # "友好编号在中国"
209
213
  @post2 = Post.create(:title => "友好编号在中国")
210
214
  @post2.friendly_id # "友好编号在中国--2"
215
+
216
+ === Custom Slug Generation
217
+
218
+ While FriendlyId's slug generation options work for most people, you may need
219
+ something else. As of version 2.0.4 you can pass in your own custom slug
220
+ generation block:
221
+
222
+ require 'stringex'
223
+ class Post < ActiveRecord::Base
224
+ has_friendly_id :title, :use_slug => true do |text|
225
+ # User stringex to generate the friendly_id rather than the baked-in methods
226
+ text.to_url
227
+ end
228
+ end
229
+
230
+ ...
231
+
232
+ @post = Post.create(:title => "tell your readers 你好")
233
+ @post.friendly_id # "tell-your-readers-ni-hao"
234
+
235
+ FriendlyId will still respect your settings for max length and reserved words,
236
+ but will use your block rather than the baked-in methods to normalize the
237
+ friendly_id text.
238
+
239
+ (As an aside, the stringex[http://github.com/rsl/stringex/tree/master] library
240
+ provides some very cool slugging functionality and is a great option for
241
+ apps using FriendlyId in either English or Chinese. Definitely check it out.)
211
242
 
212
243
  == Getting it
213
244
 
@@ -228,7 +259,7 @@ which FriendlyId depends on:
228
259
 
229
260
  == Setting it up
230
261
 
231
- FriendlyId currently works with Rails 2.0.0 and higher. Here's how to set it up.
262
+ FriendlyId currently works with Rails 2.0.0 - 2.3.0. Here's how to set it up.
232
263
 
233
264
  1) Install the Gem:
234
265
 
@@ -267,7 +298,7 @@ rake:friendly_id:remove_old_slugs MODEL=MyModelName DAYS=60
267
298
 
268
299
  == Upgrading from an older version
269
300
 
270
- If you installed an older version of FriendlyId and want to upgrade to 2.0,
301
+ If you installed an older version of FriendlyId and want to upgrade to 2.0.x,
271
302
  follow these steps:
272
303
 
273
304
  ==== Install the friendly_id Gem:
@@ -297,8 +328,6 @@ Add this to the bottom of environment.rb:
297
328
  ./script generate friendly_id_20_upgrade
298
329
  rake db:migrate
299
330
 
300
- That's it!
301
-
302
331
  == Hacking FriendlyId:
303
332
 
304
333
  FriendlyId is {hosted on Github}[git://github.com/norman/friendly_id.git], and
data/Rakefile CHANGED
@@ -59,3 +59,11 @@ def run_coverage(files)
59
59
  puts cmd
60
60
  sh cmd
61
61
  end
62
+
63
+ desc 'Publish RDoc to RubyForge.'
64
+ task :publish_docs => [:clean, :docs] do
65
+ host = "compay@rubyforge.org"
66
+ remote_dir = "/var/www/gforge-projects/friendly-id"
67
+ local_dir = 'doc'
68
+ sh %{rsync -av --delete #{local_dir}/ #{host}:#{remote_dir}}
69
+ end
data/VERSION.yml CHANGED
@@ -1,4 +1,4 @@
1
1
  ---
2
- :patch: 5
2
+ :patch: 6
3
3
  :major: 2
4
4
  :minor: 0
data/lib/friendly_id.rb CHANGED
@@ -1,21 +1,35 @@
1
- require 'unicode'
2
1
  require 'friendly_id/helpers'
3
2
  require 'friendly_id/slug'
4
- require 'friendly_id/shoulda_macros'
5
-
6
3
 
7
4
  # FriendlyId is a comprehensize Rails plugin/gem for slugging and permalinks.
8
5
  module FriendlyId
9
6
 
7
+ # Default options for has_friendly_id.
8
+ DEFAULT_FRIENDLY_ID_OPTIONS = {
9
+ :max_length => 255,
10
+ :method => nil,
11
+ :reserved => ["new", "index"],
12
+ :reserved_message => 'can not be "%s"',
13
+ :scope => nil,
14
+ :strip_diacritics => false,
15
+ :strip_non_ascii => false,
16
+ :use_slug => false }.freeze
17
+
18
+ # Valid keys for has_friendly_id options.
19
+ VALID_FRIENDLY_ID_KEYS = [
20
+ :max_length,
21
+ :reserved,
22
+ :reserved_message,
23
+ :scope,
24
+ :strip_diacritics,
25
+ :strip_non_ascii,
26
+ :use_slug ].freeze
27
+
10
28
  # This error is raised when it's not possible to generate a unique slug.
11
29
  class SlugGenerationError < StandardError ; end
12
30
 
13
31
  module ClassMethods
14
32
 
15
- # Default options for friendly_id.
16
- DEFAULT_FRIENDLY_ID_OPTIONS = {:method => nil, :use_slug => false, :max_length => 255, :reserved => [], :strip_diacritics => false, :scope => nil}.freeze
17
- VALID_FRIENDLY_ID_KEYS = [:use_slug, :max_length, :reserved, :strip_diacritics, :scope].freeze
18
-
19
33
  # Set up an ActiveRecord model to use a friendly_id.
20
34
  #
21
35
  # The column argument can be one of your model's columns, or a method
@@ -25,25 +39,32 @@ module FriendlyId
25
39
  # * <tt>:use_slug</tt> - Defaults to false. Use slugs when you want to use a non-unique text field for friendly ids.
26
40
  # * <tt>:max_length</tt> - Defaults to 255. The maximum allowed length for a slug.
27
41
  # * <tt>:strip_diacritics</tt> - Defaults to false. If true, it will remove accents, umlauts, etc. from western characters.
28
- # * <tt>:reseved</tt> - Array of words that are reserved and can't be used as slugs. 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 [].
29
- def has_friendly_id(column, options = {})
42
+ # * <tt>:strip_non_ascii</tt> - Defaults to false. If true, it will all non-ascii ([^a-z0-9]) characters.
43
+ # * <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"].
44
+ # * <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'.
45
+ def has_friendly_id(column, options = {}, &block)
30
46
  options.assert_valid_keys VALID_FRIENDLY_ID_KEYS
31
47
  options = DEFAULT_FRIENDLY_ID_OPTIONS.merge(options).merge(:column => column)
32
48
  write_inheritable_attribute :friendly_id_options, options
33
- class_inheritable_reader :friendly_id_options
49
+ class_inheritable_accessor :friendly_id_options
50
+ class_inheritable_reader :slug_normalizer_block
34
51
 
35
52
  if options[:use_slug]
36
- has_many :slugs, :order => 'id DESC', :as => :sluggable, :dependent => :destroy, :readonly => true
53
+ has_many :slugs, :order => 'id DESC', :as => :sluggable, :dependent => :destroy
37
54
  require 'friendly_id/sluggable_class_methods'
38
55
  require 'friendly_id/sluggable_instance_methods'
39
56
  extend SluggableClassMethods
40
57
  include SluggableInstanceMethods
41
58
  before_save :set_slug
59
+ if block_given?
60
+ write_inheritable_attribute :slug_normalizer_block, block
61
+ end
42
62
  else
43
63
  require 'friendly_id/non_sluggable_class_methods'
44
64
  require 'friendly_id/non_sluggable_instance_methods'
45
65
  extend NonSluggableClassMethods
46
66
  include NonSluggableInstanceMethods
67
+ validate_on_create :validate_friendly_id
47
68
  end
48
69
  end
49
70
 
@@ -55,7 +76,6 @@ module FriendlyId
55
76
  def enable
56
77
  return if ActiveRecord::Base.methods.include? 'has_friendly_id'
57
78
  ActiveRecord::Base.class_eval { extend FriendlyId::ClassMethods }
58
- Test::Unit::TestCase.class_eval { include FriendlyId::ShouldaMacros }
59
79
  end
60
80
 
61
81
  end
@@ -64,4 +84,4 @@ end
64
84
 
65
85
  if defined?(ActiveRecord)
66
86
  FriendlyId::enable
67
- end
87
+ end
@@ -21,17 +21,17 @@ module FriendlyId::NonSluggableClassMethods
21
21
  end
22
22
 
23
23
  def find_some_with_friendly(ids_and_names, options) #:nodoc:#
24
-
24
+
25
25
  results = with_scope :find => options do
26
26
  find :all, :conditions => ["#{ quoted_table_name }.#{ primary_key } IN (?) OR #{friendly_id_options[:column].to_s} IN (?)",
27
27
  ids_and_names.map {|id| id.to_i}, ids_and_names.map {|id| id.to_s}]
28
28
  end
29
-
29
+
30
30
  expected = expected_size(ids_and_names, options)
31
31
  if results.size != expected
32
32
  raise ActiveRecord::RecordNotFound, "Couldn't find all #{ name.pluralize } with IDs (#{ ids_and_names * ', ' }) AND #{ sanitize_sql options[:conditions] } (found #{ results.size } results, but was looking for #{ expected })"
33
33
  end
34
-
34
+
35
35
  results.each {|r| r.send(:found_using_friendly_id=, true) if ids_and_names.include?(r.friendly_id)}
36
36
 
37
37
  results
@@ -24,10 +24,18 @@ module FriendlyId::NonSluggableInstanceMethods
24
24
 
25
25
  # Returns the friendly id, or if none is available, the numeric id.
26
26
  def to_param
27
- friendly_id.to_s || id.to_s
27
+ (friendly_id || id).to_s
28
28
  end
29
29
 
30
30
  private
31
+
32
+ def validate_friendly_id
33
+ if self.class.friendly_id_options[:reserved].include? friendly_id
34
+ self.errors.add(self.class.friendly_id_options[:column],
35
+ self.class.friendly_id_options[:reserved_message] % friendly_id)
36
+ return false
37
+ end
38
+ end
31
39
 
32
40
  def found_using_friendly_id=(value) #:nodoc#
33
41
  @found_using_friendly_id = value
@@ -1,3 +1,4 @@
1
+ require 'unicode'
1
2
  # A Slug is a unique, human-friendly identifier for an ActiveRecord.
2
3
  class Slug < ActiveRecord::Base
3
4
 
@@ -23,12 +24,12 @@ class Slug < ActiveRecord::Base
23
24
  # terror in Europe unlike anything ever seen before or after. I'm not
24
25
  # taking any chances.
25
26
  def normalize(slug_text)
26
- return "" if slug_text.blank?
27
- slug_text.
27
+ return "" if slug_text.nil? || slug_text == ""
28
+ Unicode::normalize_KC(slug_text).
28
29
  send(chars_func).
29
30
  # For some reason Spanish ¡ and ¿ are not detected as non-word
30
31
  # characters. Bug in Ruby?
31
- normalize.gsub(/[\W|¡|¿]/u, ' ').
32
+ gsub(/[\W|¡|¿]/u, ' ').
32
33
  strip.
33
34
  gsub(/\s+/u, '-').
34
35
  gsub(/-\z/u, '').
@@ -42,20 +43,21 @@ class Slug < ActiveRecord::Base
42
43
  return name, sequence
43
44
  end
44
45
 
45
- # Remove diacritics (accents, umlauts, etc.) from the string.
46
+ # Remove diacritics (accents, umlauts, etc.) from the string. Borrowed
47
+ # from "The Ruby Way."
46
48
  def strip_diacritics(string)
47
- require 'unicode'
48
- Unicode::normalize_KD(string).unpack('U*').select { |cp|
49
- cp < 0x300 || cp > 0x036F
50
- }.pack('U*')
49
+ Unicode::normalize_KD(string).unpack('U*').select { |u| u < 0x300 || u > 0x036F }.pack('U*')
50
+ end
51
+
52
+ # Remove non-ascii characters from the string.
53
+ def strip_non_ascii(string)
54
+ strip_diacritics(string).gsub(/[^a-z0-9]+/i, ' ')
51
55
  end
52
56
 
53
57
  private
54
58
 
55
59
  def chars_func
56
- Rails.version =~ /2.2.[\d]*/ ? :mb_chars : :chars
57
- rescue NoMethodError
58
- :chars
60
+ "".respond_to?(:mb_chars) ? :mb_chars : :chars
59
61
  end
60
62
 
61
63
  end
@@ -73,7 +75,7 @@ class Slug < ActiveRecord::Base
73
75
 
74
76
  # Raise a FriendlyId::SlugGenerationError if the slug name is blank.
75
77
  def check_for_blank_name #:nodoc:#
76
- if name.blank?
78
+ if name == "" || name.nil?
77
79
  raise FriendlyId::SlugGenerationError.new("The slug text is blank.")
78
80
  end
79
81
  end
@@ -1,7 +1,7 @@
1
1
  module FriendlyId::SluggableClassMethods
2
-
3
- include FriendlyId::Helpers
4
2
 
3
+ include FriendlyId::Helpers
4
+
5
5
  def self.extended(base) #:nodoc:#
6
6
 
7
7
  class << base
@@ -38,6 +38,17 @@ module FriendlyId::SluggableClassMethods
38
38
  end
39
39
 
40
40
  result
41
+ rescue ActiveRecord::RecordNotFound => e
42
+
43
+ if friendly_id_options[:scope]
44
+ if !scope
45
+ e.message << "; expected scope but got none"
46
+ else
47
+ e.message << " and scope=#{scope}"
48
+ end
49
+ end
50
+
51
+ raise e
41
52
 
42
53
  end
43
54
 
@@ -87,7 +98,7 @@ module FriendlyId::SluggableClassMethods
87
98
  ids = []
88
99
  ids_and_names.each do |id_or_name|
89
100
  name, sequence = Slug.parse id_or_name
90
- slug = Slug.find(:first, :readonly => true, :conditions => {
101
+ slug = Slug.find(:first, :conditions => {
91
102
  :name => name,
92
103
  :scope => scope,
93
104
  :sequence => sequence,
@@ -6,7 +6,7 @@ module FriendlyId::SluggableInstanceMethods
6
6
  attr_accessor :finder_slug_name
7
7
 
8
8
  def finder_slug
9
- @finder_slug ||= init_finder_slug
9
+ @finder_slug ||= init_finder_slug or nil
10
10
  end
11
11
 
12
12
  # Was the record found using one of its friendly ids?
@@ -56,11 +56,18 @@ module FriendlyId::SluggableInstanceMethods
56
56
  # Get the processed string used as the basis of the friendly id.
57
57
  def slug_text
58
58
  base = send friendly_id_options[:column]
59
- if self.friendly_id_options[:strip_diacritics]
60
- base = Slug::normalize(Slug::strip_diacritics(base))
59
+ if self.slug_normalizer_block
60
+ base = self.slug_normalizer_block.call(base)
61
61
  else
62
+ if self.friendly_id_options[:strip_diacritics]
63
+ base = Slug::strip_diacritics(base)
64
+ end
65
+ if self.friendly_id_options[:strip_non_ascii]
66
+ base = Slug::strip_non_ascii(base)
67
+ end
62
68
  base = Slug::normalize(base)
63
69
  end
70
+
64
71
  if base.length > friendly_id_options[:max_length]
65
72
  base = base[0...friendly_id_options[:max_length]]
66
73
  end
@@ -82,7 +89,7 @@ module FriendlyId::SluggableInstanceMethods
82
89
  def init_finder_slug
83
90
  return false if !@finder_slug_name
84
91
  name, sequence = Slug.parse(@finder_slug_name)
85
- slug = Slug.find(:first, :conditions => {:sluggable_id => id, :name => name, :sequence => sequence, :sluggable_type => self.class.name })
92
+ slug = Slug.find(:first, :conditions => {:sluggable_id => id, :name => name, :sequence => sequence, :sluggable_type => self.class.base_class.name })
86
93
  finder_slug = slug
87
94
  end
88
95
 
@@ -2,7 +2,7 @@ module FriendlyId #:nodoc:
2
2
  module Version #:nodoc:
3
3
  MAJOR = 2
4
4
  MINOR = 0
5
- TINY = 1
5
+ TINY = 4
6
6
  STRING = [MAJOR, MINOR, TINY].join('.')
7
7
  end
8
8
  end
@@ -0,0 +1,35 @@
1
+ # encoding: utf-8
2
+
3
+ require File.dirname(__FILE__) + '/test_helper'
4
+
5
+ class CustomSlugNormalizerTest < Test::Unit::TestCase
6
+
7
+ context "A slugged model using a custom slug generator" do
8
+
9
+ setup do
10
+ Thing.friendly_id_options = FriendlyId::DEFAULT_FRIENDLY_ID_OPTIONS.merge(:column => :name, :use_slug => true)
11
+ Thing.delete_all
12
+ Slug.delete_all
13
+ end
14
+
15
+ should "invoke the block code" do
16
+ @thing = Thing.create!(:name => "test")
17
+ assert_equal "a94a8fe5ccb19ba61c4c0873d391e987982fbbd3", @thing.friendly_id
18
+ end
19
+
20
+ should "respect the max_length option" do
21
+ Thing.friendly_id_options = Thing.friendly_id_options.merge(:max_length => 10)
22
+ @thing = Thing.create!(:name => "test")
23
+ assert_equal "a94a8fe5cc", @thing.friendly_id
24
+ end
25
+
26
+ should "respect the reserved option" do
27
+ Thing.friendly_id_options = Thing.friendly_id_options.merge(:reserved => ["a94a8fe5ccb19ba61c4c0873d391e987982fbbd3"])
28
+ assert_raises FriendlyId::SlugGenerationError do
29
+ Thing.create!(:name => "test")
30
+ end
31
+ end
32
+
33
+ end
34
+
35
+ end