friendly_id 2.2.7 → 2.3.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.
Files changed (73) hide show
  1. data/Changelog.md +225 -0
  2. data/Contributors.md +28 -0
  3. data/Guide.md +509 -0
  4. data/LICENSE +1 -1
  5. data/README.md +76 -0
  6. data/Rakefile +48 -15
  7. data/extras/bench.rb +59 -0
  8. data/extras/extras.rb +31 -0
  9. data/extras/prof.rb +14 -0
  10. data/extras/template-gem.rb +1 -1
  11. data/extras/template-plugin.rb +1 -1
  12. data/generators/friendly_id/friendly_id_generator.rb +1 -1
  13. data/generators/friendly_id/templates/create_slugs.rb +2 -2
  14. data/lib/friendly_id.rb +54 -63
  15. data/lib/friendly_id/active_record2.rb +47 -0
  16. data/lib/friendly_id/active_record2/configuration.rb +66 -0
  17. data/lib/friendly_id/active_record2/finders.rb +140 -0
  18. data/lib/friendly_id/active_record2/simple_model.rb +162 -0
  19. data/lib/friendly_id/active_record2/slug.rb +111 -0
  20. data/lib/friendly_id/active_record2/slugged_model.rb +317 -0
  21. data/lib/friendly_id/active_record2/tasks.rb +66 -0
  22. data/lib/friendly_id/active_record2/tasks/friendly_id.rake +19 -0
  23. data/lib/friendly_id/configuration.rb +132 -0
  24. data/lib/friendly_id/finders.rb +106 -0
  25. data/lib/friendly_id/slug_string.rb +292 -0
  26. data/lib/friendly_id/slugged.rb +91 -0
  27. data/lib/friendly_id/status.rb +35 -0
  28. data/lib/friendly_id/test.rb +168 -0
  29. data/lib/friendly_id/version.rb +5 -5
  30. data/rails/init.rb +2 -0
  31. data/test/active_record2/basic_slugged_model_test.rb +14 -0
  32. data/test/active_record2/cached_slug_test.rb +61 -0
  33. data/test/active_record2/core.rb +93 -0
  34. data/test/active_record2/custom_normalizer_test.rb +20 -0
  35. data/test/active_record2/custom_table_name_test.rb +22 -0
  36. data/test/active_record2/scoped_model_test.rb +111 -0
  37. data/test/active_record2/simple_test.rb +59 -0
  38. data/test/active_record2/slug_test.rb +34 -0
  39. data/test/active_record2/slugged.rb +30 -0
  40. data/test/active_record2/slugged_status_test.rb +61 -0
  41. data/test/active_record2/sti_test.rb +22 -0
  42. data/test/active_record2/support/database.mysql.yml +4 -0
  43. data/test/{support/database.yml.postgres → active_record2/support/database.postgres.yml} +0 -0
  44. data/test/{support/database.yml.sqlite3 → active_record2/support/database.sqlite3.yml} +0 -0
  45. data/test/{support → active_record2/support}/models.rb +28 -0
  46. data/test/active_record2/tasks_test.rb +82 -0
  47. data/test/active_record2/test_helper.rb +107 -0
  48. data/test/friendly_id_test.rb +23 -0
  49. data/test/slug_string_test.rb +74 -0
  50. data/test/test_helper.rb +7 -102
  51. metadata +64 -56
  52. data/History.txt +0 -194
  53. data/README.rdoc +0 -385
  54. data/generators/friendly_id_20_upgrade/friendly_id_20_upgrade_generator.rb +0 -12
  55. data/generators/friendly_id_20_upgrade/templates/upgrade_friendly_id_to_20.rb +0 -19
  56. data/init.rb +0 -1
  57. data/lib/friendly_id/helpers.rb +0 -12
  58. data/lib/friendly_id/non_sluggable_class_methods.rb +0 -34
  59. data/lib/friendly_id/non_sluggable_instance_methods.rb +0 -45
  60. data/lib/friendly_id/slug.rb +0 -98
  61. data/lib/friendly_id/sluggable_class_methods.rb +0 -110
  62. data/lib/friendly_id/sluggable_instance_methods.rb +0 -161
  63. data/lib/friendly_id/tasks.rb +0 -56
  64. data/lib/tasks/friendly_id.rake +0 -25
  65. data/lib/tasks/friendly_id.rb +0 -1
  66. data/test/cached_slug_test.rb +0 -109
  67. data/test/custom_slug_normalizer_test.rb +0 -36
  68. data/test/non_slugged_test.rb +0 -99
  69. data/test/scoped_model_test.rb +0 -64
  70. data/test/slug_test.rb +0 -105
  71. data/test/slugged_model_test.rb +0 -348
  72. data/test/sti_test.rb +0 -49
  73. data/test/tasks_test.rb +0 -105
@@ -0,0 +1,59 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ module FriendlyId
4
+ module Test
5
+ module ActiveRecord2
6
+ module Simple
7
+
8
+ module SimpleTest
9
+ def klass
10
+ @klass ||= User
11
+ end
12
+
13
+ def instance
14
+ @instance ||= User.create! :name => "hello world"
15
+ end
16
+
17
+ def other_class
18
+ Author
19
+ end
20
+ end
21
+
22
+ class StatusTest < ::Test::Unit::TestCase
23
+
24
+ include SimpleTest
25
+
26
+ test "should default to not friendly" do
27
+ assert !status.friendly?
28
+ end
29
+
30
+ test "should default to numeric" do
31
+ assert status.numeric?
32
+ end
33
+
34
+ test "should be friendly if name is set" do
35
+ status.name = "name"
36
+ assert status.friendly?
37
+ end
38
+
39
+ test "should be best if it is numeric, but record has no friendly_id" do
40
+ instance.send("#{klass.friendly_id_config.column}=", nil)
41
+ assert status.best?
42
+ end
43
+
44
+ def status
45
+ @status ||= instance.friendly_id_status
46
+ end
47
+
48
+ end
49
+
50
+ class BasicTest < ::Test::Unit::TestCase
51
+ include Core
52
+ include SimpleTest
53
+ end
54
+
55
+ end
56
+ end
57
+ end
58
+ end
59
+
@@ -0,0 +1,34 @@
1
+ # encoding: utf-8
2
+ require File.dirname(__FILE__) + '/test_helper'
3
+
4
+ module FriendlyId
5
+ module Test
6
+
7
+ class SlugTest < ::Test::Unit::TestCase
8
+
9
+ def teardown
10
+ Slug.delete_all
11
+ Post.delete_all
12
+ end
13
+
14
+ test "should indicate if it is the most recent slug" do
15
+ post = Post.create!(:name => "test title")
16
+ post.name = "a new title"
17
+ post.save!
18
+ assert post.slugs.first.current?
19
+ assert !post.slugs.last.current?
20
+ end
21
+
22
+ test "should include the sequence if the sequence is greater than 1" do
23
+ slug = Slug.new(:name => "test", :sluggable => Post.new, :sequence => 2)
24
+ assert_equal "test--2", slug.to_friendly_id
25
+ end
26
+
27
+ test "should not include the sequence if the sequence is 1" do
28
+ slug = Slug.new(:name => "test", :sluggable => Post.new, :sequence => 1)
29
+ assert_equal "test", slug.to_friendly_id
30
+ end
31
+
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,30 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ module FriendlyId
4
+ module Test
5
+ module ActiveRecord2
6
+ module Slugged
7
+
8
+ test "should allow eager loading of slugs" do
9
+ assert_nothing_raised do
10
+ klass.find(instance.friendly_id, :include => :slugs)
11
+ end
12
+ end
13
+
14
+ def klass
15
+ Post
16
+ end
17
+
18
+ def other_class
19
+ District
20
+ end
21
+
22
+ def instance
23
+ @instance ||= klass.create! :name => "hello world"
24
+ end
25
+
26
+ end
27
+ end
28
+ end
29
+ end
30
+
@@ -0,0 +1,61 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+
4
+ module FriendlyId
5
+ module Test
6
+ module ActiveRecord2
7
+
8
+ class StatusTest < ::Test::Unit::TestCase
9
+
10
+ test "should default to not friendly" do
11
+ assert !status.friendly?
12
+ end
13
+
14
+ test "should default to numeric" do
15
+ assert status.numeric?
16
+ end
17
+
18
+ test "should be friendly if slug is set" do
19
+ status.slug = Slug.new
20
+ assert status.friendly?
21
+ end
22
+
23
+ test "should be friendly if name is set" do
24
+ status.name = "name"
25
+ assert status.friendly?
26
+ end
27
+
28
+ test "should be current if current slug is set" do
29
+ status.slug = instance.slug
30
+ assert status.current?
31
+ end
32
+
33
+ test "should not be current if non-current slug is set" do
34
+ status.slug = Slug.new(:sluggable => instance)
35
+ assert !status.current?
36
+ end
37
+
38
+ test "should be best if it is current" do
39
+ status.slug = instance.slug
40
+ assert status.best?
41
+ end
42
+
43
+ test "should be best if it is numeric, but record has not slug" do
44
+ instance.slugs = []
45
+ instance.slug = nil
46
+ assert status.best?
47
+ end
48
+
49
+ def instance
50
+ @instance ||= Post.create! :name => "hello world"
51
+ end
52
+
53
+ def status
54
+ @status ||= instance.friendly_id_status
55
+ end
56
+
57
+ end
58
+ end
59
+ end
60
+ end
61
+
@@ -0,0 +1,22 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ module FriendlyId
4
+ module Test
5
+ module ActiveRecord2
6
+
7
+ class StiTest < ::Test::Unit::TestCase
8
+
9
+ include FriendlyId::Test::Generic
10
+ include FriendlyId::Test::Slugged
11
+ include FriendlyId::Test::ActiveRecord2::Slugged
12
+ include FriendlyId::Test::ActiveRecord2::Core
13
+
14
+ def klass
15
+ Novel
16
+ end
17
+
18
+ end
19
+
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,4 @@
1
+ adapter: mysql
2
+ database: friendly_id_test
3
+ username: root
4
+ hostname: 127.0.0.1
@@ -1,42 +1,70 @@
1
1
  class CreateSupportModels < ActiveRecord::Migration
2
2
  def self.up
3
+
4
+ create_table :authors do |t|
5
+ t.string :name
6
+ end
7
+
3
8
  create_table :books do |t|
4
9
  t.string :name
5
10
  t.string :type
11
+ t.string :note
6
12
  end
13
+
7
14
  create_table :cities do |t|
8
15
  t.string :name
9
16
  t.string :my_slug
10
17
  t.integer :population
18
+ t.index :my_slug, :unique => true
11
19
  end
20
+
12
21
  create_table :countries do |t|
13
22
  t.string :name
14
23
  end
24
+
15
25
  create_table :districts do |t|
16
26
  t.string :name
27
+ t.string :note
17
28
  t.string :cached_slug
29
+ t.index :cached_slug, :unique => true
18
30
  end
31
+
19
32
  create_table :events do |t|
20
33
  t.string :name
21
34
  t.datetime :event_date
22
35
  end
36
+
37
+ create_table :houses do |t|
38
+ t.string :name
39
+ t.integer :user_id
40
+ end
41
+
23
42
  create_table :legacy_table do |t|
24
43
  t.string :name
44
+ t.string :note
25
45
  end
46
+
26
47
  create_table :people do |t|
27
48
  t.string :name
49
+ t.string :note
28
50
  end
51
+
29
52
  create_table :posts do |t|
30
53
  t.string :name
31
54
  t.boolean :published
55
+ t.string :note
32
56
  end
57
+
33
58
  create_table :residents do |t|
34
59
  t.string :name
35
60
  t.integer :country_id
36
61
  end
62
+
37
63
  create_table :users do |t|
38
64
  t.string :name
65
+ t.index :name, :unique => true
39
66
  end
67
+
40
68
  end
41
69
 
42
70
  def self.down
@@ -0,0 +1,82 @@
1
+ require(File.dirname(__FILE__) + '/test_helper')
2
+ require File.dirname(__FILE__) + '/../../lib/friendly_id/active_record2/tasks'
3
+
4
+ class TasksTest < Test::Unit::TestCase
5
+
6
+ def teardown
7
+ ENV["MODEL"] = nil
8
+ [City, District, Post, Slug].map(&:delete_all)
9
+ end
10
+
11
+ test "should make slugs" do
12
+ City.create! :name => "Nairobi"
13
+ City.create! :name => "Buenos Aires"
14
+ Slug.delete_all
15
+ ENV["MODEL"] = "City"
16
+ FriendlyId::TaskRunner.new.make_slugs
17
+ assert_equal 2, Slug.count
18
+ end
19
+
20
+ test "should admit lower case, plural model names" do
21
+ ENV["MODEL"] = "cities"
22
+ assert_equal City, FriendlyId::TaskRunner.new.klass
23
+ end
24
+
25
+ test "make_slugs should raise error if no model given" do
26
+ assert_raise(RuntimeError) { FriendlyId::TaskRunner.new.make_slugs }
27
+ end
28
+
29
+ test "make_slugs should raise error if class doesn't use FriendlyId" do
30
+ ENV["MODEL"] = "String"
31
+ assert_raise(RuntimeError) { FriendlyId::TaskRunner.new.make_slugs }
32
+ end
33
+
34
+ test"delete_slugs delete only slugs for the specified model" do
35
+ Post.create! :name => "Slugs Considered Harmful"
36
+ City.create! :name => "Buenos Aires"
37
+ ENV["MODEL"] = "city"
38
+ FriendlyId::TaskRunner.new.delete_slugs
39
+ assert_equal 1, Slug.count
40
+ end
41
+
42
+ test "delete_slugs should set the cached_slug column to NULL" do
43
+ ENV["MODEL"] = "district"
44
+ District.create! :name => "Garment"
45
+ FriendlyId::TaskRunner.new.delete_slugs
46
+ assert_nil District.first.cached_slug
47
+ end
48
+
49
+
50
+ test "delete_old_slugs should delete slugs older than 45 days by default" do
51
+ set_up_old_slugs
52
+ FriendlyId::TaskRunner.new.delete_old_slugs
53
+ assert_equal 2, Slug.count
54
+ end
55
+
56
+ test "delete_old_slugs should respect the days argument" do
57
+ set_up_old_slugs
58
+ ENV["DAYS"] = "100"
59
+ FriendlyId::TaskRunner.new.delete_old_slugs
60
+ assert_equal 3, Slug.count
61
+ end
62
+
63
+ test "delete_old_slugs should respect the class argument" do
64
+ set_up_old_slugs
65
+ ENV["MODEL"] = "post"
66
+ FriendlyId::TaskRunner.new.delete_old_slugs
67
+ assert_equal 3, Slug.count
68
+ end
69
+
70
+ private
71
+
72
+ def set_up_old_slugs
73
+ Post.create! :name => "Slugs Considered Harmful"
74
+ city = City.create! :name => "Buenos Aires"
75
+ City.connection.execute "UPDATE slugs SET created_at = '%s' WHERE id = %d" % [
76
+ 45.days.ago.strftime("%Y-%m-%d"), city.slug.id
77
+ ]
78
+ city.update_attributes :name => "Ciudad de Buenos Aires"
79
+ assert_equal 3, Slug.count
80
+ end
81
+
82
+ end
@@ -0,0 +1,107 @@
1
+ require File.dirname(__FILE__) + "/../test_helper"
2
+
3
+ require "active_record"
4
+ require "active_support"
5
+ require File.dirname(__FILE__) + "/../../lib/friendly_id/active_record2.rb"
6
+ require File.dirname(__FILE__) + "/../../generators/friendly_id/templates/create_slugs"
7
+ require File.dirname(__FILE__) + "/support/models"
8
+ require File.dirname(__FILE__) + '/core'
9
+ require File.dirname(__FILE__) + '/slugged'
10
+
11
+ local_db_settings = File.dirname(__FILE__) + "/support/database.yml"
12
+ default_db_settings = File.dirname(__FILE__) + "/support/database.sqlite3.yml"
13
+ db_settings = File.exists?(local_db_settings) ? local_db_settings : default_db_settings
14
+ ActiveRecord::Base.establish_connection(YAML::load(File.open(db_settings)))
15
+
16
+ class ActiveRecord::Base
17
+ def log_protected_attribute_removal(*args) end
18
+ end
19
+
20
+ ActiveRecord::Base.connection.tables.each do |table|
21
+ ActiveRecord::Base.connection.drop_table(table)
22
+ end
23
+ ActiveRecord::Migration.verbose = false
24
+ CreateSlugs.up
25
+ CreateSupportModels.up
26
+
27
+ # A model that uses the automagically configured "cached_slug" column
28
+ class District < ActiveRecord::Base
29
+ has_friendly_id :name, :use_slug => true
30
+ end
31
+
32
+ # A model that specifies a custom cached slug column
33
+ class City < ActiveRecord::Base
34
+ attr_accessible :name
35
+ has_friendly_id :name, :use_slug => true, :cache_column => "my_slug"
36
+ end
37
+
38
+ # A model with a custom slug text normalizer
39
+ class Person < ActiveRecord::Base
40
+ has_friendly_id :name, :use_slug => true
41
+
42
+ def normalize_friendly_id(string)
43
+ string.upcase
44
+ end
45
+
46
+ end
47
+
48
+ # A slugged model that uses a scope
49
+ class Resident < ActiveRecord::Base
50
+ belongs_to :country
51
+ has_friendly_id :name, :use_slug => true, :scope => :country
52
+ end
53
+
54
+ # A slugged model used as a scope
55
+ class Country < ActiveRecord::Base
56
+ has_many :people
57
+ has_many :residents
58
+ has_friendly_id :name, :use_slug => true
59
+ end
60
+
61
+ # A model that doesn"t use slugs
62
+ class User < ActiveRecord::Base
63
+ has_friendly_id :name
64
+ has_many :houses
65
+ end
66
+
67
+ # Another model that doesn"t use slugs
68
+ class Author < ActiveRecord::Base
69
+ has_friendly_id :name
70
+ end
71
+
72
+
73
+ # A model that uses a non-slugged model for its scope
74
+ class House < ActiveRecord::Base
75
+ belongs_to :user
76
+ has_friendly_id :name, :use_slug => true, :scope => :user
77
+ end
78
+
79
+ # A model that uses default slug settings and has a named scope
80
+ class Post < ActiveRecord::Base
81
+ has_friendly_id :name, :use_slug => true
82
+ named_scope :published, :conditions => { :published => true }
83
+ end
84
+
85
+ # Model that uses a custom table name
86
+ class Place < ActiveRecord::Base
87
+ self.table_name = "legacy_table"
88
+ has_friendly_id :name, :use_slug => true
89
+ end
90
+
91
+ # A model that uses a datetime field for its friendly_id
92
+ class Event < ActiveRecord::Base
93
+ has_friendly_id :event_date, :use_slug => true
94
+ end
95
+
96
+ # A base model for single table inheritence
97
+ class Book < ActiveRecord::Base ; end
98
+
99
+ # A model that uses STI
100
+ class Novel < ::Book
101
+ has_friendly_id :name, :use_slug => true
102
+ end
103
+
104
+ # A model with no table
105
+ class Question < ActiveRecord::Base
106
+ has_friendly_id :name, :use_slug => true
107
+ end