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