friendly_id 2.2.7 → 2.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Changelog.md +225 -0
- data/Contributors.md +28 -0
- data/Guide.md +509 -0
- data/LICENSE +1 -1
- data/README.md +76 -0
- data/Rakefile +48 -15
- data/extras/bench.rb +59 -0
- data/extras/extras.rb +31 -0
- data/extras/prof.rb +14 -0
- data/extras/template-gem.rb +1 -1
- data/extras/template-plugin.rb +1 -1
- data/generators/friendly_id/friendly_id_generator.rb +1 -1
- data/generators/friendly_id/templates/create_slugs.rb +2 -2
- data/lib/friendly_id.rb +54 -63
- data/lib/friendly_id/active_record2.rb +47 -0
- data/lib/friendly_id/active_record2/configuration.rb +66 -0
- data/lib/friendly_id/active_record2/finders.rb +140 -0
- data/lib/friendly_id/active_record2/simple_model.rb +162 -0
- data/lib/friendly_id/active_record2/slug.rb +111 -0
- data/lib/friendly_id/active_record2/slugged_model.rb +317 -0
- data/lib/friendly_id/active_record2/tasks.rb +66 -0
- data/lib/friendly_id/active_record2/tasks/friendly_id.rake +19 -0
- data/lib/friendly_id/configuration.rb +132 -0
- data/lib/friendly_id/finders.rb +106 -0
- data/lib/friendly_id/slug_string.rb +292 -0
- data/lib/friendly_id/slugged.rb +91 -0
- data/lib/friendly_id/status.rb +35 -0
- data/lib/friendly_id/test.rb +168 -0
- data/lib/friendly_id/version.rb +5 -5
- data/rails/init.rb +2 -0
- data/test/active_record2/basic_slugged_model_test.rb +14 -0
- data/test/active_record2/cached_slug_test.rb +61 -0
- data/test/active_record2/core.rb +93 -0
- data/test/active_record2/custom_normalizer_test.rb +20 -0
- data/test/active_record2/custom_table_name_test.rb +22 -0
- data/test/active_record2/scoped_model_test.rb +111 -0
- data/test/active_record2/simple_test.rb +59 -0
- data/test/active_record2/slug_test.rb +34 -0
- data/test/active_record2/slugged.rb +30 -0
- data/test/active_record2/slugged_status_test.rb +61 -0
- data/test/active_record2/sti_test.rb +22 -0
- data/test/active_record2/support/database.mysql.yml +4 -0
- data/test/{support/database.yml.postgres → active_record2/support/database.postgres.yml} +0 -0
- data/test/{support/database.yml.sqlite3 → active_record2/support/database.sqlite3.yml} +0 -0
- data/test/{support → active_record2/support}/models.rb +28 -0
- data/test/active_record2/tasks_test.rb +82 -0
- data/test/active_record2/test_helper.rb +107 -0
- data/test/friendly_id_test.rb +23 -0
- data/test/slug_string_test.rb +74 -0
- data/test/test_helper.rb +7 -102
- metadata +64 -56
- data/History.txt +0 -194
- data/README.rdoc +0 -385
- data/generators/friendly_id_20_upgrade/friendly_id_20_upgrade_generator.rb +0 -12
- data/generators/friendly_id_20_upgrade/templates/upgrade_friendly_id_to_20.rb +0 -19
- data/init.rb +0 -1
- data/lib/friendly_id/helpers.rb +0 -12
- data/lib/friendly_id/non_sluggable_class_methods.rb +0 -34
- data/lib/friendly_id/non_sluggable_instance_methods.rb +0 -45
- data/lib/friendly_id/slug.rb +0 -98
- data/lib/friendly_id/sluggable_class_methods.rb +0 -110
- data/lib/friendly_id/sluggable_instance_methods.rb +0 -161
- data/lib/friendly_id/tasks.rb +0 -56
- data/lib/tasks/friendly_id.rake +0 -25
- data/lib/tasks/friendly_id.rb +0 -1
- data/test/cached_slug_test.rb +0 -109
- data/test/custom_slug_normalizer_test.rb +0 -36
- data/test/non_slugged_test.rb +0 -99
- data/test/scoped_model_test.rb +0 -64
- data/test/slug_test.rb +0 -105
- data/test/slugged_model_test.rb +0 -348
- data/test/sti_test.rb +0 -49
- data/test/tasks_test.rb +0 -105
data/lib/tasks/friendly_id.rake
DELETED
@@ -1,25 +0,0 @@
|
|
1
|
-
namespace :friendly_id do
|
2
|
-
desc "Make slugs for a model."
|
3
|
-
task :make_slugs => :environment do
|
4
|
-
validate_model_given
|
5
|
-
FriendlyId::Tasks.make_slugs(ENV["MODEL"]) do |r|
|
6
|
-
puts "%s(%d) friendly_id set to '%s'" % [r.class.to_s, r.id, r.slug.name]
|
7
|
-
end
|
8
|
-
end
|
9
|
-
|
10
|
-
desc "Regenereate slugs for a model."
|
11
|
-
task :redo_slugs => :environment do
|
12
|
-
validate_model_given
|
13
|
-
FriendlyId::Tasks.delete_slugs_for(ENV["MODEL"])
|
14
|
-
Rake::Task["friendly_id:make_slugs"].invoke
|
15
|
-
end
|
16
|
-
|
17
|
-
desc "Kill obsolete slugs older than DAYS=45 days."
|
18
|
-
task :remove_old_slugs => :environment do
|
19
|
-
FriendlyId::Tasks.delete_old_slugs(ENV["DAYS"], ENV["MODEL"])
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
def validate_model_given
|
24
|
-
raise 'USAGE: rake friendly_id:make_slugs MODEL=MyModelName' if ENV["MODEL"].nil?
|
25
|
-
end
|
data/lib/tasks/friendly_id.rb
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
load 'tasks/friendly_id.rake'
|
data/test/cached_slug_test.rb
DELETED
@@ -1,109 +0,0 @@
|
|
1
|
-
require File.dirname(__FILE__) + '/test_helper'
|
2
|
-
|
3
|
-
class CachedSlugModelTest < Test::Unit::TestCase
|
4
|
-
|
5
|
-
context "A slugged model with a cached_slugs column" do
|
6
|
-
|
7
|
-
setup do
|
8
|
-
@paris = City.new(:name => "Paris")
|
9
|
-
@paris.save!
|
10
|
-
end
|
11
|
-
|
12
|
-
teardown do
|
13
|
-
City.delete_all
|
14
|
-
Slug.delete_all
|
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
|
-
should "cache the incremented sequence for duplicate slug names" do
|
36
|
-
paris2 = City.create!(:name => "Paris")
|
37
|
-
assert_equal 2, paris2.slug.sequence
|
38
|
-
assert_equal "paris--2", paris2.my_slug
|
39
|
-
end
|
40
|
-
|
41
|
-
should "not update the cached slug column if it has not changed" do
|
42
|
-
@paris.population = 10_000_000
|
43
|
-
@paris.expects(:my_slug=).never
|
44
|
-
@paris.save
|
45
|
-
end
|
46
|
-
|
47
|
-
|
48
|
-
context "found by its friendly id" do
|
49
|
-
|
50
|
-
setup do
|
51
|
-
@paris = City.find(@paris.friendly_id)
|
52
|
-
end
|
53
|
-
|
54
|
-
should "not indicate that it has a better id" do
|
55
|
-
assert !@paris.has_better_id?
|
56
|
-
end
|
57
|
-
|
58
|
-
end
|
59
|
-
|
60
|
-
context "found by its numeric id" do
|
61
|
-
|
62
|
-
setup do
|
63
|
-
@paris = City.find(@paris.id)
|
64
|
-
end
|
65
|
-
|
66
|
-
should "indicate that it has a better id" do
|
67
|
-
assert @paris.has_better_id?
|
68
|
-
end
|
69
|
-
|
70
|
-
end
|
71
|
-
|
72
|
-
|
73
|
-
context "with a new slug" do
|
74
|
-
|
75
|
-
setup do
|
76
|
-
@paris.name = "Paris, France"
|
77
|
-
@paris.save!
|
78
|
-
@paris.reload
|
79
|
-
end
|
80
|
-
|
81
|
-
should "have its cached slug updated" do
|
82
|
-
assert_equal "paris-france", @paris.my_slug
|
83
|
-
end
|
84
|
-
|
85
|
-
should "have its cached slug synchronized with its friendly_id" do
|
86
|
-
assert_equal @paris.my_slug, @paris.friendly_id
|
87
|
-
end
|
88
|
-
|
89
|
-
end
|
90
|
-
|
91
|
-
|
92
|
-
context "with a cached_slug column" do
|
93
|
-
|
94
|
-
setup do
|
95
|
-
District.delete_all
|
96
|
-
@district = District.new(:name => "Latin Quarter")
|
97
|
-
@district.save!
|
98
|
-
end
|
99
|
-
|
100
|
-
should "have its cached_slug filled automatically" do
|
101
|
-
assert_equal @district.cached_slug, "latin-quarter"
|
102
|
-
end
|
103
|
-
|
104
|
-
end
|
105
|
-
|
106
|
-
end
|
107
|
-
|
108
|
-
end
|
109
|
-
|
@@ -1,36 +0,0 @@
|
|
1
|
-
require File.dirname(__FILE__) + '/test_helper'
|
2
|
-
|
3
|
-
class CustomSlugNormalizerTest < Test::Unit::TestCase
|
4
|
-
|
5
|
-
context "A slugged model using a custom slug generator" do
|
6
|
-
|
7
|
-
setup do
|
8
|
-
Person.friendly_id_options = FriendlyId::DEFAULT_OPTIONS.merge(:method => :name, :use_slug => true)
|
9
|
-
end
|
10
|
-
|
11
|
-
teardown do
|
12
|
-
Person.delete_all
|
13
|
-
Slug.delete_all
|
14
|
-
end
|
15
|
-
|
16
|
-
should "invoke the block code" do
|
17
|
-
@person = Person.create!(:name => "Joe Schmoe")
|
18
|
-
assert_equal "JOE SCHMOE", @person.friendly_id
|
19
|
-
end
|
20
|
-
|
21
|
-
should "respect the max_length option" do
|
22
|
-
Person.friendly_id_options = Person.friendly_id_options.merge(:max_length => 3)
|
23
|
-
@person = Person.create!(:name => "Joe Schmoe")
|
24
|
-
assert_equal "JOE", @person.friendly_id
|
25
|
-
end
|
26
|
-
|
27
|
-
should "respect the reserved option" do
|
28
|
-
Person.friendly_id_options = Person.friendly_id_options.merge(:reserved => ["JOE"])
|
29
|
-
assert_raises FriendlyId::SlugGenerationError do
|
30
|
-
Person.create!(:name => "Joe")
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
end
|
35
|
-
|
36
|
-
end
|
data/test/non_slugged_test.rb
DELETED
@@ -1,99 +0,0 @@
|
|
1
|
-
require File.dirname(__FILE__) + '/test_helper'
|
2
|
-
|
3
|
-
class NonSluggedTest < Test::Unit::TestCase
|
4
|
-
|
5
|
-
context "A non-slugged model with default FriendlyId options" do
|
6
|
-
|
7
|
-
setup do
|
8
|
-
@user = User.create!(:name => "joe")
|
9
|
-
end
|
10
|
-
|
11
|
-
teardown do
|
12
|
-
User.delete_all
|
13
|
-
end
|
14
|
-
|
15
|
-
should "have friendly_id options" do
|
16
|
-
assert_not_nil User.friendly_id_options
|
17
|
-
end
|
18
|
-
|
19
|
-
should "not have a slug" do
|
20
|
-
assert !@user.respond_to?(:slug)
|
21
|
-
end
|
22
|
-
|
23
|
-
should "be findable by its friendly_id" do
|
24
|
-
assert User.find(@user.friendly_id)
|
25
|
-
end
|
26
|
-
|
27
|
-
should "be findable by its regular id" do
|
28
|
-
assert User.find(@user.id)
|
29
|
-
end
|
30
|
-
|
31
|
-
should "respect finder conditions" do
|
32
|
-
assert_raises ActiveRecord::RecordNotFound do
|
33
|
-
User.find(@user.friendly_id, :conditions => "1 = 2")
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
should "indicate if it was found by its friendly id" do
|
38
|
-
user = User.find(@user.friendly_id)
|
39
|
-
assert user.found_using_friendly_id?
|
40
|
-
end
|
41
|
-
|
42
|
-
should "indicate if it was found by its numeric id" do
|
43
|
-
user = User.find(@user.id)
|
44
|
-
assert user.found_using_numeric_id?
|
45
|
-
end
|
46
|
-
|
47
|
-
should "indicate if it has a better id" do
|
48
|
-
user = User.find(@user.id)
|
49
|
-
assert user.has_better_id?
|
50
|
-
end
|
51
|
-
|
52
|
-
should "not validate if the friendly_id text is reserved" do
|
53
|
-
user = User.new(:name => "new")
|
54
|
-
assert !user.valid?
|
55
|
-
end
|
56
|
-
|
57
|
-
should "have always string for a friendly_id" do
|
58
|
-
assert_equal String, @user.to_param.class
|
59
|
-
end
|
60
|
-
|
61
|
-
should "return its id if the friendly_id is null" do
|
62
|
-
@user.name = nil
|
63
|
-
assert_equal @user.id.to_s, @user.to_param
|
64
|
-
end
|
65
|
-
|
66
|
-
|
67
|
-
context "when using an array as the find argument" do
|
68
|
-
|
69
|
-
setup do
|
70
|
-
@user2 = User.create(:name => "jane")
|
71
|
-
end
|
72
|
-
|
73
|
-
should "return results" do
|
74
|
-
assert_equal 2, User.find([@user.friendly_id, @user2.friendly_id]).size
|
75
|
-
end
|
76
|
-
|
77
|
-
should "not allow mixed friendly and non-friendly ids for the same record" do
|
78
|
-
assert_raises ActiveRecord::RecordNotFound do
|
79
|
-
User.find([@user.id, @user.friendly_id]).size
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
should "raise an error when all records are not found" do
|
84
|
-
assert_raises ActiveRecord::RecordNotFound do
|
85
|
-
User.find(['bad', 'bad2'])
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
|
-
should "indicate if the results were found using a friendly_id" do
|
90
|
-
users = User.find([@user.id, @user2.friendly_id], :order => "name ASC")
|
91
|
-
assert users[0].found_using_friendly_id?
|
92
|
-
assert users[1].found_using_numeric_id?
|
93
|
-
end
|
94
|
-
|
95
|
-
end
|
96
|
-
|
97
|
-
end
|
98
|
-
|
99
|
-
end
|
data/test/scoped_model_test.rb
DELETED
@@ -1,64 +0,0 @@
|
|
1
|
-
require File.dirname(__FILE__) + '/test_helper'
|
2
|
-
|
3
|
-
|
4
|
-
class ScopedModelTest < Test::Unit::TestCase
|
5
|
-
|
6
|
-
context "A slugged model that uses a scope" do
|
7
|
-
|
8
|
-
setup do
|
9
|
-
@usa = Country.create!(:name => "USA")
|
10
|
-
@canada = Country.create!(:name => "Canada")
|
11
|
-
@resident = Resident.create!(:name => "John Smith", :country => @usa)
|
12
|
-
@resident2 = Resident.create!(:name => "John Smith", :country => @canada)
|
13
|
-
end
|
14
|
-
|
15
|
-
teardown do
|
16
|
-
Resident.delete_all
|
17
|
-
Country.delete_all
|
18
|
-
Slug.delete_all
|
19
|
-
end
|
20
|
-
|
21
|
-
should "should not show the scope in the friendly_id" do
|
22
|
-
assert_equal "john-smith", @resident.friendly_id
|
23
|
-
assert_equal "john-smith", @resident2.friendly_id
|
24
|
-
end
|
25
|
-
|
26
|
-
should "find all scoped records without scope" do
|
27
|
-
assert_equal 2, Resident.find(:all, @resident.friendly_id).size
|
28
|
-
end
|
29
|
-
|
30
|
-
should "find a single scoped records with a scope as a string" do
|
31
|
-
assert Resident.find(@resident.friendly_id, :scope => @resident.country.to_param)
|
32
|
-
end
|
33
|
-
|
34
|
-
should "find a single scoped records with a scope" do
|
35
|
-
assert Resident.find(@resident.friendly_id, :scope => @resident.country)
|
36
|
-
end
|
37
|
-
|
38
|
-
should "raise an error when finding a single scoped record with no scope" do
|
39
|
-
assert_raises ActiveRecord::RecordNotFound do
|
40
|
-
Resident.find(@resident.friendly_id)
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
should "append scope error info when missing scope causes a find to fail" do
|
45
|
-
begin
|
46
|
-
Resident.find(@resident.friendly_id)
|
47
|
-
fail "The find should not have succeeded"
|
48
|
-
rescue ActiveRecord::RecordNotFound => e
|
49
|
-
assert_match /expected scope/, e.message
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
should "append scope error info when the scope value causes a find to fail" do
|
54
|
-
begin
|
55
|
-
Resident.find(@resident.friendly_id, :scope => "badscope")
|
56
|
-
fail "The find should not have succeeded"
|
57
|
-
rescue ActiveRecord::RecordNotFound => e
|
58
|
-
assert_match /scope=badscope/, e.message
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
end
|
63
|
-
|
64
|
-
end
|
data/test/slug_test.rb
DELETED
@@ -1,105 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
require File.dirname(__FILE__) + '/test_helper'
|
3
|
-
|
4
|
-
class SlugTest < Test::Unit::TestCase
|
5
|
-
|
6
|
-
context "a slug" do
|
7
|
-
|
8
|
-
teardown do
|
9
|
-
Slug.delete_all
|
10
|
-
Post.delete_all
|
11
|
-
end
|
12
|
-
|
13
|
-
should "indicate if it is the most recent slug" do
|
14
|
-
post = Post.create!(:name => "test title")
|
15
|
-
post.name = "a new title"
|
16
|
-
post.save!
|
17
|
-
assert post.slugs.last.is_most_recent?
|
18
|
-
assert !post.slugs.first.is_most_recent?
|
19
|
-
end
|
20
|
-
|
21
|
-
end
|
22
|
-
|
23
|
-
context "the Slug class" do
|
24
|
-
|
25
|
-
should "parse the slug name and sequence" do
|
26
|
-
assert_equal ["test", "2"], Slug::parse("test--2")
|
27
|
-
end
|
28
|
-
|
29
|
-
should "parse with a default sequence of 1" do
|
30
|
-
assert_equal ["test", "1"], Slug::parse("test")
|
31
|
-
end
|
32
|
-
|
33
|
-
should "should strip diacritics" do
|
34
|
-
assert_equal "acai", Slug::strip_diacritics("açaí")
|
35
|
-
end
|
36
|
-
|
37
|
-
should "strip diacritics correctly " do
|
38
|
-
input = "ÀÁÂÃÄÅÆÇÈÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿ"
|
39
|
-
output = Slug::strip_diacritics(input).split(//)
|
40
|
-
expected = "AAAAAAAECEEEIIIIDNOOOOOOUUUUYThssaaaaaaaeceeeeiiiidnoooooouuuuythy".split(//)
|
41
|
-
output.each_index do |i|
|
42
|
-
assert_equal expected[i], output[i]
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
end
|
47
|
-
|
48
|
-
context "the Slug class's to_friendly_id method" do
|
49
|
-
|
50
|
-
should "include the sequence if the sequence is greater than 1" do
|
51
|
-
slug = Slug.new(:name => "test", :sequence => 2)
|
52
|
-
assert_equal "test--2", slug.to_friendly_id
|
53
|
-
end
|
54
|
-
|
55
|
-
should "not include the sequence if the sequence is 1" do
|
56
|
-
slug = Slug.new(:name => "test", :sequence => 1)
|
57
|
-
assert_equal "test", slug.to_friendly_id
|
58
|
-
end
|
59
|
-
|
60
|
-
end
|
61
|
-
|
62
|
-
context "the Slug class's normalize method" do
|
63
|
-
|
64
|
-
should "should lowercase strings" do
|
65
|
-
assert_match /abc/, Slug::normalize("ABC")
|
66
|
-
end
|
67
|
-
|
68
|
-
should "should replace whitespace with dashes" do
|
69
|
-
assert_match /a-b/, Slug::normalize("a b")
|
70
|
-
end
|
71
|
-
|
72
|
-
should "should replace 2spaces with 1dash" do
|
73
|
-
assert_match /a-b/, Slug::normalize("a b")
|
74
|
-
end
|
75
|
-
|
76
|
-
should "should remove punctuation" do
|
77
|
-
assert_match /abc/, Slug::normalize('abc!@#$%^&*•¶§∞¢££¡¿()><?"":;][]\.,/')
|
78
|
-
end
|
79
|
-
|
80
|
-
should "should strip trailing space" do
|
81
|
-
assert_match /ab/, Slug::normalize("ab ")
|
82
|
-
end
|
83
|
-
|
84
|
-
should "should strip leading space" do
|
85
|
-
assert_match /ab/, Slug::normalize(" ab")
|
86
|
-
end
|
87
|
-
|
88
|
-
should "should strip trailing slashes" do
|
89
|
-
assert_match /ab/, Slug::normalize("ab-")
|
90
|
-
end
|
91
|
-
|
92
|
-
should "should strip leading slashes" do
|
93
|
-
assert_match /ab/, Slug::normalize("-ab")
|
94
|
-
end
|
95
|
-
|
96
|
-
should "should not modify valid name strings" do
|
97
|
-
assert_match /a-b-c-d/, Slug::normalize("a-b-c-d")
|
98
|
-
end
|
99
|
-
|
100
|
-
should "work with non roman chars" do
|
101
|
-
assert_equal "検-索", Slug::normalize("検 索")
|
102
|
-
end
|
103
|
-
|
104
|
-
end
|
105
|
-
end
|