alancse-friendly_id 2.0.1

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.
Files changed (45) hide show
  1. data/History.txt +82 -0
  2. data/MIT-LICENSE +19 -0
  3. data/Manifest.txt +44 -0
  4. data/README.rdoc +318 -0
  5. data/Rakefile +45 -0
  6. data/friendly_id.gemspec +38 -0
  7. data/generators/friendly_id/friendly_id_generator.rb +12 -0
  8. data/generators/friendly_id/templates/create_slugs.rb +18 -0
  9. data/generators/friendly_id_20_upgrade/friendly_id_20_upgrade_generator.rb +11 -0
  10. data/generators/friendly_id_20_upgrade/templates/upgrade_friendly_id_to_20.rb +19 -0
  11. data/init.rb +1 -0
  12. data/lib/friendly_id.rb +68 -0
  13. data/lib/friendly_id/helpers.rb +13 -0
  14. data/lib/friendly_id/non_sluggable_class_methods.rb +40 -0
  15. data/lib/friendly_id/non_sluggable_instance_methods.rb +33 -0
  16. data/lib/friendly_id/shoulda_macros.rb +36 -0
  17. data/lib/friendly_id/slug.rb +89 -0
  18. data/lib/friendly_id/sluggable_class_methods.rb +103 -0
  19. data/lib/friendly_id/sluggable_instance_methods.rb +105 -0
  20. data/lib/friendly_id/version.rb +8 -0
  21. data/lib/tasks/friendly_id.rake +48 -0
  22. data/lib/tasks/friendly_id.rb +1 -0
  23. data/test/database.yml +3 -0
  24. data/test/fixtures/countries.yml +4 -0
  25. data/test/fixtures/country.rb +4 -0
  26. data/test/fixtures/people.yml +7 -0
  27. data/test/fixtures/person.rb +6 -0
  28. data/test/fixtures/post.rb +3 -0
  29. data/test/fixtures/posts.yml +23 -0
  30. data/test/fixtures/slugs.yml +53 -0
  31. data/test/fixtures/user.rb +3 -0
  32. data/test/fixtures/users.yml +7 -0
  33. data/test/non_slugged_test.rb +85 -0
  34. data/test/rails/2.x/app/controllers/application.rb +0 -0
  35. data/test/rails/2.x/config/boot.rb +109 -0
  36. data/test/rails/2.x/config/database.yml +3 -0
  37. data/test/rails/2.x/config/environment.rb +7 -0
  38. data/test/rails/2.x/config/environments/test.rb +6 -0
  39. data/test/rails/2.x/config/routes.rb +0 -0
  40. data/test/schema.rb +38 -0
  41. data/test/scoped_model_test.rb +21 -0
  42. data/test/slug_test.rb +87 -0
  43. data/test/sluggable_test.rb +185 -0
  44. data/test/test_helper.rb +35 -0
  45. metadata +127 -0
File without changes
@@ -0,0 +1,109 @@
1
+ # Don't change this file!
2
+ # Configure your app in config/environment.rb and config/environments/*.rb
3
+
4
+ RAILS_ROOT = "#{File.dirname(__FILE__)}/.." unless defined?(RAILS_ROOT)
5
+
6
+ module Rails
7
+ class << self
8
+ def boot!
9
+ unless booted?
10
+ preinitialize
11
+ pick_boot.run
12
+ end
13
+ end
14
+
15
+ def booted?
16
+ defined? Rails::Initializer
17
+ end
18
+
19
+ def pick_boot
20
+ (vendor_rails? ? VendorBoot : GemBoot).new
21
+ end
22
+
23
+ def vendor_rails?
24
+ File.exist?("#{RAILS_ROOT}/vendor/rails")
25
+ end
26
+
27
+ def preinitialize
28
+ load(preinitializer_path) if File.exist?(preinitializer_path)
29
+ end
30
+
31
+ def preinitializer_path
32
+ "#{RAILS_ROOT}/config/preinitializer.rb"
33
+ end
34
+ end
35
+
36
+ class Boot
37
+ def run
38
+ load_initializer
39
+ Rails::Initializer.run(:set_load_path)
40
+ end
41
+ end
42
+
43
+ class VendorBoot < Boot
44
+ def load_initializer
45
+ require "#{RAILS_ROOT}/vendor/rails/railties/lib/initializer"
46
+ Rails::Initializer.run(:install_gem_spec_stubs)
47
+ end
48
+ end
49
+
50
+ class GemBoot < Boot
51
+ def load_initializer
52
+ self.class.load_rubygems
53
+ load_rails_gem
54
+ require 'initializer'
55
+ end
56
+
57
+ def load_rails_gem
58
+ if version = self.class.gem_version
59
+ gem 'rails', version
60
+ else
61
+ gem 'rails'
62
+ end
63
+ rescue Gem::LoadError => load_error
64
+ $stderr.puts %(Missing the Rails #{version} gem. Please `gem install -v=#{version} rails`, update your RAILS_GEM_VERSION setting in config/environment.rb for the Rails version you do have installed, or comment out RAILS_GEM_VERSION to use the latest version installed.)
65
+ exit 1
66
+ end
67
+
68
+ class << self
69
+ def rubygems_version
70
+ Gem::RubyGemsVersion rescue nil
71
+ end
72
+
73
+ def gem_version
74
+ if defined? RAILS_GEM_VERSION
75
+ RAILS_GEM_VERSION
76
+ elsif ENV.include?('RAILS_GEM_VERSION')
77
+ ENV['RAILS_GEM_VERSION']
78
+ else
79
+ parse_gem_version(read_environment_rb)
80
+ end
81
+ end
82
+
83
+ def load_rubygems
84
+ require 'rubygems'
85
+ min_version = '1.3.1'
86
+ unless rubygems_version >= min_version
87
+ $stderr.puts %Q(Rails requires RubyGems >= #{min_version} (you have #{rubygems_version}). Please `gem update --system` and try again.)
88
+ exit 1
89
+ end
90
+
91
+ rescue LoadError
92
+ $stderr.puts %Q(Rails requires RubyGems >= #{min_version}. Please install RubyGems and try again: http://rubygems.rubyforge.org)
93
+ exit 1
94
+ end
95
+
96
+ def parse_gem_version(text)
97
+ $1 if text =~ /^[^#]*RAILS_GEM_VERSION\s*=\s*["']([!~<>=]*\s*[\d.]+)["']/
98
+ end
99
+
100
+ private
101
+ def read_environment_rb
102
+ File.read("#{RAILS_ROOT}/config/environment.rb")
103
+ end
104
+ end
105
+ end
106
+ end
107
+
108
+ # All that for this:
109
+ Rails.boot!
@@ -0,0 +1,3 @@
1
+ test:
2
+ adapter: sqlite3
3
+ database: ":memory:"
@@ -0,0 +1,7 @@
1
+ if ENV['RAILS_VERSION']
2
+ RAILS_GEM_VERSION = ENV['RAILS_VERSION']
3
+ end
4
+ require File.join(File.dirname(__FILE__), 'boot')
5
+ Rails::Initializer.run
6
+ ActiveRecord::Base.colorize_logging = false
7
+ require File.dirname(__FILE__) + '/../../../../init.rb'
@@ -0,0 +1,6 @@
1
+ config.cache_classes = true
2
+ config.whiny_nils = true
3
+ config.action_controller.consider_all_requests_local = true
4
+ config.action_controller.perform_caching = false
5
+ config.action_controller.allow_forgery_protection = false
6
+ config.action_mailer.delivery_method = :test
File without changes
data/test/schema.rb ADDED
@@ -0,0 +1,38 @@
1
+ ActiveRecord::Schema.define(:version => 3) do
2
+
3
+ create_table "posts", :force => true do |t|
4
+ t.string "name"
5
+ t.text "content"
6
+ t.datetime "created_at"
7
+ t.datetime "updated_at"
8
+ end
9
+
10
+ create_table "users", :force => true do |t|
11
+ t.string "login"
12
+ t.string "email"
13
+ t.datetime "created_at"
14
+ t.datetime "updated_at"
15
+ end
16
+
17
+ create_table "people", :force => true do |t|
18
+ t.string "name"
19
+ t.integer "country_id"
20
+ end
21
+
22
+ create_table "countries", :force => true do |t|
23
+ t.string "name"
24
+ end
25
+
26
+ create_table "slugs", :force => true do |t|
27
+ t.string "name"
28
+ t.integer "sluggable_id"
29
+ t.integer "sequence", :null => false, :default => 1
30
+ t.string "sluggable_type", :limit => 40
31
+ t.string "scope", :limit => 40
32
+ t.datetime "created_at"
33
+ end
34
+
35
+ add_index "slugs", ["sluggable_id"], :name => "index_slugs_on_sluggable_id"
36
+ add_index "slugs", ["name", "sluggable_type", "scope", "sequence"], :name => "index_slugs_on_n_s_s_and_s", :unique => true
37
+
38
+ end
@@ -0,0 +1,21 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class ScopedModelTest < Test::Unit::TestCase
4
+
5
+ fixtures :people, :countries, :slugs
6
+
7
+ def test_should_find_scoped_records_without_scope
8
+ assert_equal 2, Person.find(:all, "john-smith").size
9
+ end
10
+
11
+ def test_should_find_scoped_records_with_scope
12
+ assert_equal people(:john_smith), Person.find("john-smith", :scope => "argentina")
13
+ assert_equal people(:john_smith2), Person.find("john-smith", :scope => "usa")
14
+ end
15
+
16
+ def test_should_create_scoped_records_with_scope
17
+ person = Person.create!(:name => "Joe Schmoe", :country => countries(:usa))
18
+ assert_equal "usa", person.slug.scope
19
+ end
20
+
21
+ end
data/test/slug_test.rb ADDED
@@ -0,0 +1,87 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class SlugTest < Test::Unit::TestCase
4
+
5
+ fixtures :posts, :slugs
6
+
7
+ def test_should_indicate_if_it_is_the_most_recent
8
+ assert slugs(:two_new).is_most_recent?
9
+ assert !slugs(:two_old).is_most_recent?
10
+ end
11
+
12
+ def test_parse_should_return_slug_name_and_sequence
13
+ assert_equal ["test", "2"], Slug::parse("test--2")
14
+ end
15
+
16
+ def test_parse_should_return_a_default_sequnce_of_1
17
+ assert_equal ["test", "1"], Slug::parse("test")
18
+ end
19
+
20
+ def test_strip_diacritics_should_strip_diacritics
21
+ assert_equal "acai", Slug::strip_diacritics("açaí")
22
+ end
23
+
24
+ def test_to_friendly_id_should_include_sequence_if_its_greater_than_1
25
+ slug = Slug.new(:name => "test", :sequence => 2)
26
+ assert_equal "test--2", slug.to_friendly_id
27
+ end
28
+
29
+ def test_to_friendly_id_should_include_sequence_if_its_than_1
30
+ slug = Slug.new(:name => "test", :sequence => 1)
31
+ assert_equal "test", slug.to_friendly_id
32
+ end
33
+
34
+ def test_normalize_should_lowercase_strings
35
+ assert_match /abc/, Slug::normalize("ABC")
36
+ end
37
+
38
+ def test_normalize_should_replace_whitespace_with_dashes
39
+ assert_match /a-b/, Slug::normalize("a b")
40
+ end
41
+
42
+ def test_normalize_should_replace_2spaces_with_1dash
43
+ assert_match /a-b/, Slug::normalize("a b")
44
+ end
45
+
46
+ def test_normalize_should_remove_punctuation
47
+ assert_match /abc/, Slug::normalize('abc!@#$%^&*•¶§∞¢££¡¿()><?"":;][]\.,/')
48
+ end
49
+
50
+ def test_normalize_should_strip_trailing_space
51
+ assert_match /ab/, Slug::normalize("ab ")
52
+ end
53
+
54
+ def test_normalize_should_strip_leading_space
55
+ assert_match /ab/, Slug::normalize(" ab")
56
+ end
57
+
58
+ def test_normalize_should_strip_trailing_slashes
59
+ assert_match /ab/, Slug::normalize("ab-")
60
+ end
61
+
62
+ def test_normalize_should_strip_leading_slashes
63
+ assert_match /ab/, Slug::normalize("-ab")
64
+ end
65
+
66
+ def test_normalize_should_not_modify_valid_name_strings
67
+ assert_match /a-b-c-d/, Slug::normalize("a-b-c-d")
68
+ end
69
+
70
+ # These strings are taken from various international Google homepages. I
71
+ # would be most grateful if a fluent speaker of any language that uses a
72
+ # writing system other than the Roman alphabet could help me make some
73
+ # better tests to ensure this is working correctly.
74
+ def test_normalize_works_with_non_roman_chars
75
+ assert_equal "検-索", Slug::normalize("検 索")
76
+ end
77
+
78
+ def test_strip_diactics_correctly_strips_diacritics
79
+ input = "ÀÁÂÃÄÅÆÇÈÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿ"
80
+ output = Slug::strip_diacritics(input).split(//)
81
+ expected = "AAAAAAAECEEEIIIIDNOOOOOOUUUUYThssaaaaaaaeceeeeiiiidnoooooouuuuythy".split(//)
82
+ output.split.each_index do |i|
83
+ assert_equal output[i], expected[i]
84
+ end
85
+ end
86
+
87
+ end
@@ -0,0 +1,185 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class SluggableTest < Test::Unit::TestCase
4
+
5
+ fixtures :posts, :slugs
6
+
7
+ def setup
8
+ Post.friendly_id_options[:max_length] = FriendlyId::ClassMethods::DEFAULT_FRIENDLY_ID_OPTIONS[:max_length]
9
+ end
10
+
11
+ def test_should_allow_for_identical_slug_names_between_sluggable_types
12
+ assert !Post.find(slugs(:post_with_same_friendly_id_as_person).name).has_better_id?
13
+ end
14
+
15
+ def test_class_should_have_friendly_id_options
16
+ assert_not_nil Post.friendly_id_options
17
+ end
18
+
19
+ def test_should_generate_slug_text
20
+ @post = Post.new(:name => "Test post", :content => "Test content")
21
+ assert_equal "test-post", @post.slug_text
22
+ end
23
+
24
+ def test_should_save_slug_when_creating
25
+ @post = Post.create(:name => "Test post", :content => "Test content")
26
+ assert @post.slug
27
+ end
28
+
29
+ def test_to_param_should_always_return_a_string
30
+ assert_equal String, posts(:without_slug).to_param.class
31
+ assert_equal String, posts(:with_one_slug).to_param.class
32
+ end
33
+
34
+ def test_finder_options_are_not_ignored
35
+ assert_raises ActiveRecord::RecordNotFound do
36
+ Post.find(slugs(:one).name, :conditions => "1 = 2")
37
+ end
38
+ end
39
+
40
+ def test_should_still_be_able_to_find_record_by_id
41
+ post = Post.create!(:name => "New post")
42
+ Post.create!(:name => "#{post.id.to_s} and some text")
43
+ assert_equal post, Post.find(post.id)
44
+ end
45
+
46
+
47
+ def test_should_not_be_found_using_friendly_id_by_default
48
+ @post = Post.new
49
+ assert !@post.found_using_friendly_id?
50
+ assert @post.found_using_numeric_id?
51
+ end
52
+
53
+ def test_should_be_using_friendly_id_when_find_arg_is_an_array
54
+ @posts = Post.find([posts(:with_one_slug).friendly_id, posts(:with_two_slugs).friendly_id])
55
+ assert @posts.all? { |post| post.found_using_friendly_id? }
56
+ end
57
+
58
+ def test_raises_active_record_not_found_when_not_all_records_found
59
+ assert_raises(ActiveRecord::RecordNotFound) do
60
+ Post.find([posts(:with_one_slug).slug.name, 'non-existant-slug-record'])
61
+ end
62
+ end
63
+
64
+ def test_should_indicate_if_it_was_found_using_numeric_id
65
+ @post = Post.find(posts(:with_two_slugs).id)
66
+ assert @post.found_using_numeric_id?
67
+ end
68
+
69
+ def test_post_should_indicate_if_it_was_found_using_friendly_id
70
+ @post = Post.find(posts(:with_two_slugs).slug.name)
71
+ assert @post.found_using_friendly_id?
72
+ end
73
+
74
+ def test_post_should_indicate_if_it_was_found_using_outdated_friendly_id
75
+ @post = Post.find(posts(:with_two_slugs).slugs.last.name)
76
+ assert @post.found_using_outdated_friendly_id?
77
+ end
78
+
79
+ def test_should_indicate_there_is_a_better_id_if_found_by_numeric_id
80
+ @post = Post.find(posts(:with_one_slug).id)
81
+ assert @post.has_better_id?
82
+ end
83
+
84
+ def test_should_indicate_there_is_a_better_id_if_found_by_outdated_friendly_id
85
+ @post = Post.find(posts(:with_two_slugs).slugs.last.name)
86
+ assert @post.has_better_id?
87
+ end
88
+
89
+ def test_slug_should_always_be_the_most_recent
90
+ @post = Post.find(posts(:with_two_slugs).slug.name)
91
+ assert !@post.has_better_id?
92
+ assert slugs(:two_new).name, @post.slug.name
93
+ end
94
+
95
+ def test_should_strip_diactics_from_slug_if_configured_to_do_so
96
+ Post.friendly_id_options[:strip_diacritics] = true
97
+ @post = Post.new(:name => "¡FELIZ AÑO!")
98
+ # Happy anus to you too
99
+ assert_equal "feliz-ano", @post.slug_text
100
+ end
101
+
102
+ def test_should_not_strip_diactics_from_slug_unless_configured_to_do_so
103
+ Post.friendly_id_options[:strip_diacritics] = false
104
+ @post = Post.new(:name => "¡FELIZ AÑO!")
105
+ assert_equal "feliz-año", @post.slug_text
106
+ end
107
+
108
+ def test_should_not_make_new_slug_unless_friendly_id_method_has_changed
109
+ posts(:with_one_slug).content = "Edited content"
110
+ posts(:with_one_slug).save!
111
+ assert_equal 1, posts(:with_one_slug).slugs.size
112
+ end
113
+
114
+ def test_post_should_make_new_slug_if_friendly_id_method_is_changed
115
+ posts(:with_one_slug).name = "Edited name"
116
+ posts(:with_one_slug).save!
117
+ assert_equal 2, posts(:with_one_slug).slugs.size
118
+ end
119
+
120
+ def test_should_increment_sequence_for_duplicate_slugs
121
+ @post = Post.create!(:name => slugs(:one).name, :content => "stuff")
122
+ assert_equal 2, @post.slug.sequence
123
+ end
124
+
125
+ def test_friendly_id_should_contain_sequence_unless_its_1
126
+ @post = Post.create!(:name => slugs(:one).name, :content => "stuff")
127
+ assert_equal "#{slugs(:one).name}--2", @post.friendly_id
128
+ end
129
+
130
+ def test_should_truncate_slugs_longer_than_maxlength
131
+ Post.friendly_id_options[:max_length] = 10
132
+ @post = Post.new(:name => "x" * 11, :content => "Test content")
133
+ assert @post.slug_text.length <= Post.friendly_id_options[:max_length]
134
+ end
135
+
136
+ def test_should_ensure_truncated_slugs_that_collide_have_different_sequences
137
+ Post.friendly_id_options[:max_length] = 2
138
+ p = Post.create!(:name => "aaa")
139
+ q = Post.create!(:name => "aaab")
140
+ assert_not_equal p.friendly_id, q.friendly_id
141
+ assert_equal p.slug.name, q.slug.name
142
+ assert_not_equal p.slug.sequence, q.slug.sequence
143
+ end
144
+
145
+ def test_should_be_able_to_rename_back_to_old_friendly_id
146
+ p = Post.create!(:name => "value")
147
+ assert_equal "value", p.friendly_id
148
+ p.name = "different value"
149
+ p.save!
150
+ p.reload
151
+ assert_equal "different-value", p.friendly_id
152
+ p.name = "value"
153
+ assert p.save!
154
+ p.reload
155
+ assert_equal "value", p.friendly_id
156
+ end
157
+
158
+ def test_should_raise_error_if_friendly_id_is_blank
159
+ assert_raises(FriendlyId::SlugGenerationError) do
160
+ Post.create(:name => nil)
161
+ end
162
+ end
163
+
164
+ def test_should_raise_error_if_normalized_friendly_id_becomes_blank
165
+ assert_raises(FriendlyId::SlugGenerationError) do
166
+ post = Post.create!(:name => "-.-")
167
+ end
168
+ end
169
+
170
+ def test_should_raise_error_if_slug_text_is_reserved
171
+ assert_raises(FriendlyId::SlugGenerationError) do
172
+ Post.create(:name => "new")
173
+ end
174
+ end
175
+
176
+ def test_should_allow_eager_loading_of_slugs
177
+ assert_nothing_raised do
178
+ Post.find(slugs(:one).name, :include => :slugs)
179
+ end
180
+ assert_nothing_raised do
181
+ Post.find([slugs(:one).name, slugs(:two_new).name], :include => :slugs)
182
+ end
183
+ end
184
+
185
+ end