friendly_id 3.3.3.0 → 4.0.0.beta7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. data/.gitignore +11 -0
  2. data/.travis.yml +24 -0
  3. data/.yardopts +4 -0
  4. data/Changelog.md +9 -10
  5. data/README.md +39 -48
  6. data/Rakefile +56 -58
  7. data/WhatsNew.md +95 -0
  8. data/bench.rb +63 -0
  9. data/friendly_id.gemspec +40 -0
  10. data/gemfiles/Gemfile.rails-3.0.rb +18 -0
  11. data/gemfiles/Gemfile.rails-3.0.rb.lock +52 -0
  12. data/gemfiles/Gemfile.rails-3.1.rb +18 -0
  13. data/gemfiles/Gemfile.rails-3.1.rb.lock +57 -0
  14. data/lib/friendly_id.rb +126 -80
  15. data/lib/friendly_id/active_record_adapter/relation.rb +10 -2
  16. data/lib/friendly_id/active_record_adapter/slugged_model.rb +3 -9
  17. data/lib/friendly_id/base.rb +132 -0
  18. data/lib/friendly_id/configuration.rb +65 -152
  19. data/lib/friendly_id/finder_methods.rb +20 -0
  20. data/lib/friendly_id/history.rb +88 -0
  21. data/lib/friendly_id/migration.rb +18 -0
  22. data/lib/friendly_id/model.rb +22 -0
  23. data/lib/friendly_id/object_utils.rb +40 -0
  24. data/lib/friendly_id/reserved.rb +46 -0
  25. data/lib/friendly_id/scoped.rb +131 -0
  26. data/lib/friendly_id/slug.rb +9 -0
  27. data/lib/friendly_id/slug_sequencer.rb +82 -0
  28. data/lib/friendly_id/slugged.rb +191 -76
  29. data/lib/friendly_id/version.rb +2 -2
  30. data/test/base_test.rb +54 -0
  31. data/test/configuration_test.rb +27 -0
  32. data/test/core_test.rb +30 -0
  33. data/test/databases.yml +19 -0
  34. data/test/helper.rb +88 -0
  35. data/test/history_test.rb +55 -0
  36. data/test/object_utils_test.rb +26 -0
  37. data/test/reserved_test.rb +26 -0
  38. data/test/schema.rb +59 -0
  39. data/test/scoped_test.rb +57 -0
  40. data/test/shared.rb +118 -0
  41. data/test/slugged_test.rb +83 -0
  42. data/test/sti_test.rb +48 -0
  43. metadata +110 -102
  44. data/Contributors.md +0 -46
  45. data/Guide.md +0 -626
  46. data/extras/README.txt +0 -3
  47. data/extras/bench.rb +0 -40
  48. data/extras/extras.rb +0 -38
  49. data/extras/prof.rb +0 -19
  50. data/extras/template-gem.rb +0 -26
  51. data/extras/template-plugin.rb +0 -28
  52. data/generators/friendly_id/friendly_id_generator.rb +0 -30
  53. data/generators/friendly_id/templates/create_slugs.rb +0 -18
  54. data/lib/tasks/friendly_id.rake +0 -19
  55. data/rails/init.rb +0 -2
  56. data/test/active_record_adapter/ar_test_helper.rb +0 -149
  57. data/test/active_record_adapter/basic_slugged_model_test.rb +0 -14
  58. data/test/active_record_adapter/cached_slug_test.rb +0 -76
  59. data/test/active_record_adapter/core.rb +0 -138
  60. data/test/active_record_adapter/custom_normalizer_test.rb +0 -20
  61. data/test/active_record_adapter/custom_table_name_test.rb +0 -22
  62. data/test/active_record_adapter/default_scope_test.rb +0 -30
  63. data/test/active_record_adapter/optimistic_locking_test.rb +0 -18
  64. data/test/active_record_adapter/scoped_model_test.rb +0 -129
  65. data/test/active_record_adapter/simple_test.rb +0 -76
  66. data/test/active_record_adapter/slug_test.rb +0 -34
  67. data/test/active_record_adapter/slugged.rb +0 -33
  68. data/test/active_record_adapter/slugged_status_test.rb +0 -28
  69. data/test/active_record_adapter/sti_test.rb +0 -22
  70. data/test/active_record_adapter/support/database.jdbcsqlite3.yml +0 -2
  71. data/test/active_record_adapter/support/database.mysql.yml +0 -4
  72. data/test/active_record_adapter/support/database.mysql2.yml +0 -4
  73. data/test/active_record_adapter/support/database.postgres.yml +0 -6
  74. data/test/active_record_adapter/support/database.sqlite3.yml +0 -2
  75. data/test/active_record_adapter/support/models.rb +0 -104
  76. data/test/active_record_adapter/tasks_test.rb +0 -82
  77. data/test/compatibility/ancestry/Gemfile.lock +0 -34
  78. data/test/friendly_id_test.rb +0 -96
  79. data/test/test_helper.rb +0 -13
@@ -0,0 +1,55 @@
1
+ require File.expand_path("../helper.rb", __FILE__)
2
+
3
+ class Manual < ActiveRecord::Base
4
+ extend FriendlyId
5
+ friendly_id :name, :use => :history
6
+ end
7
+
8
+ class HistoryTest < MiniTest::Unit::TestCase
9
+
10
+ include FriendlyId::Test
11
+ include FriendlyId::Test::Shared::Core
12
+
13
+ def model_class
14
+ Manual
15
+ end
16
+
17
+ test "should insert record in slugs table on create" do
18
+ with_instance_of(model_class) {|record| assert !record.slugs.empty?}
19
+ end
20
+
21
+ test "should not create new slug record if friendly_id is not changed" do
22
+ with_instance_of(model_class) do |record|
23
+ record.active = true
24
+ record.save!
25
+ assert_equal 1, FriendlyId::Slug.count
26
+ end
27
+ end
28
+
29
+ test "should create new slug record when friendly_id changes" do
30
+ with_instance_of(model_class) do |record|
31
+ record.name = record.name + "b"
32
+ record.save!
33
+ assert_equal 2, FriendlyId::Slug.count
34
+ end
35
+ end
36
+
37
+ test "should be findable by old slugs" do
38
+ with_instance_of(model_class) do |record|
39
+ old_friendly_id = record.friendly_id
40
+ record.name = record.name + "b"
41
+ record.save!
42
+ assert found = model_class.find_by_friendly_id(old_friendly_id)
43
+ assert !found.readonly?
44
+ end
45
+ end
46
+
47
+ test "should raise error if used with scoped" do
48
+ model_class = Class.new(ActiveRecord::Base)
49
+ model_class.extend FriendlyId
50
+ assert_raises RuntimeError do
51
+ model_class.friendly_id :name, :use => [:history, :scoped]
52
+ end
53
+ end
54
+
55
+ end
@@ -0,0 +1,26 @@
1
+ require File.expand_path("../helper.rb", __FILE__)
2
+
3
+
4
+ class ObjectUtilsTest < MiniTest::Unit::TestCase
5
+
6
+ include FriendlyId::Test
7
+
8
+ test "strings with letters are friendly_ids" do
9
+ assert "a".friendly_id?
10
+ end
11
+
12
+ test "integers should be unfriendly ids" do
13
+ assert 1.unfriendly_id?
14
+ end
15
+
16
+ test "numeric strings are neither friendly nor unfriendly" do
17
+ assert_equal nil, "1".friendly_id?
18
+ assert_equal nil, "1".unfriendly_id?
19
+ end
20
+
21
+ test "ActiveRecord::Base instances should be unfriendly_ids" do
22
+ model_class = Class.new(ActiveRecord::Base)
23
+ model_class.table_name = "authors"
24
+ assert model_class.new.unfriendly_id?
25
+ end
26
+ end
@@ -0,0 +1,26 @@
1
+ require File.expand_path("../helper.rb", __FILE__)
2
+
3
+ class ReservedTest < MiniTest::Unit::TestCase
4
+
5
+ include FriendlyId::Test
6
+
7
+ class Journalist < ActiveRecord::Base
8
+ extend FriendlyId
9
+ friendly_id :name
10
+ end
11
+
12
+
13
+ def model_class
14
+ Journalist
15
+ end
16
+
17
+ test "should reserve 'new' and 'edit' by default" do
18
+ ["new", "edit"].each do |word|
19
+ transaction do
20
+ assert_raises(ActiveRecord::RecordInvalid) {model_class.create! :name => word}
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+
data/test/schema.rb ADDED
@@ -0,0 +1,59 @@
1
+ require "friendly_id/migration"
2
+
3
+ module FriendlyId
4
+ module Test
5
+ class Schema < ActiveRecord::Migration
6
+ class << self
7
+ def down
8
+ CreateFriendlyIdSlugs.down
9
+ tables.each do |name|
10
+ drop_table name
11
+ end
12
+ end
13
+
14
+ def up
15
+ # TODO: use schema version to avoid ugly hacks like this
16
+ return if @done
17
+ CreateFriendlyIdSlugs.up
18
+
19
+ tables.each do |table_name|
20
+ create_table table_name do |t|
21
+ t.string :name
22
+ t.boolean :active
23
+ end
24
+ end
25
+
26
+ slugged_tables.each do |table_name|
27
+ add_column table_name, :slug, :string
28
+ add_index table_name, :slug, :unique => true
29
+ end
30
+
31
+ # This will be used to test scopes
32
+ add_column :novels, :novelist_id, :integer
33
+ remove_index :novels, :slug
34
+
35
+ # This will be used to test column name quoting
36
+ add_column :journalists, "strange name", :string
37
+
38
+ # This will be used to test STI
39
+ add_column :journalists, "type", :string
40
+ @done = true
41
+ end
42
+
43
+ private
44
+
45
+ def slugged_tables
46
+ ["journalists", "articles", "novelists", "novels", "manuals"]
47
+ end
48
+
49
+ def simple_tables
50
+ ["authors", "books"]
51
+ end
52
+
53
+ def tables
54
+ simple_tables + slugged_tables
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,57 @@
1
+ require File.expand_path("../helper", __FILE__)
2
+
3
+ class Novelist < ActiveRecord::Base
4
+ extend FriendlyId
5
+ friendly_id :name, :use => :slugged
6
+ end
7
+
8
+ class Novel < ActiveRecord::Base
9
+ extend FriendlyId
10
+ belongs_to :novelist
11
+ friendly_id :name, :use => :scoped, :scope => :novelist
12
+ end
13
+
14
+ class ScopedTest < MiniTest::Unit::TestCase
15
+
16
+ include FriendlyId::Test
17
+ include FriendlyId::Test::Shared::Core
18
+
19
+ def model_class
20
+ Novel
21
+ end
22
+
23
+ test "should detect scope column from belongs_to relation" do
24
+ assert_equal "novelist_id", Novel.friendly_id_config.scope_column
25
+ end
26
+
27
+ test "should detect scope column from explicit column name" do
28
+ model_class = Class.new(ActiveRecord::Base)
29
+ model_class.extend FriendlyId
30
+ model_class.friendly_id :empty, :use => :scoped, :scope => :dummy
31
+ assert_equal "dummy", model_class.friendly_id_config.scope_column
32
+ end
33
+
34
+ test "should allow duplicate slugs outside scope" do
35
+ transaction do
36
+ novel1 = Novel.create! :name => "a", :novelist => Novelist.create!(:name => "a")
37
+ novel2 = Novel.create! :name => "a", :novelist => Novelist.create!(:name => "b")
38
+ assert_equal novel1.friendly_id, novel2.friendly_id
39
+ end
40
+ end
41
+
42
+ test "should not allow duplicate slugs inside scope" do
43
+ with_instance_of Novelist do |novelist|
44
+ novel1 = Novel.create! :name => "a", :novelist => novelist
45
+ novel2 = Novel.create! :name => "a", :novelist => novelist
46
+ assert novel1.friendly_id != novel2.friendly_id
47
+ end
48
+ end
49
+
50
+ test "should raise error if used with history" do
51
+ model_class = Class.new(ActiveRecord::Base)
52
+ model_class.extend FriendlyId
53
+ assert_raises RuntimeError do
54
+ model_class.friendly_id :name, :use => [:scoped, :history]
55
+ end
56
+ end
57
+ end
data/test/shared.rb ADDED
@@ -0,0 +1,118 @@
1
+ module FriendlyId
2
+ module Test
3
+ module Shared
4
+
5
+ module Slugged
6
+ test "configuration should have a sequence_separator" do
7
+ assert !model_class.friendly_id_config.sequence_separator.empty?
8
+ end
9
+
10
+ test "should make a new slug if the friendly_id method value has changed" do
11
+ with_instance_of model_class do |record|
12
+ record.name = "Changed Value"
13
+ record.save!
14
+ assert_equal "changed-value", record.slug
15
+ end
16
+ end
17
+
18
+ test "should increment the slug sequence for duplicate friendly ids" do
19
+ with_instance_of model_class do |record|
20
+ record2 = model_class.create! :name => record.name
21
+ assert record2.friendly_id.match(/2\z/)
22
+ end
23
+ end
24
+
25
+ test "should not add slug sequence on update after other conflicting slugs were added" do
26
+ with_instance_of model_class do |record|
27
+ old = record.friendly_id
28
+ record2 = model_class.create! :name => record.name
29
+ record.save!
30
+ record.reload
31
+ assert_equal old, record.to_param
32
+ end
33
+ end
34
+
35
+ test "should not increment sequence on save" do
36
+ with_instance_of model_class do |record|
37
+ record2 = model_class.create! :name => record.name
38
+ record2.active = !record2.active
39
+ record2.save!
40
+ assert record2.friendly_id.match(/2\z/)
41
+ end
42
+ end
43
+ end
44
+
45
+ module Core
46
+ test "finds should respect conditions" do
47
+ with_instance_of(model_class) do |record|
48
+ assert_raises(ActiveRecord::RecordNotFound) do
49
+ model_class.where("1 = 2").find record.friendly_id
50
+ end
51
+ end
52
+ end
53
+
54
+ test "should be findable by friendly id" do
55
+ with_instance_of(model_class) {|record| assert model_class.find record.friendly_id}
56
+ end
57
+
58
+ test "should be findable by id as integer" do
59
+ with_instance_of(model_class) {|record| assert model_class.find record.id.to_i}
60
+ end
61
+
62
+ test "should be findable by id as string" do
63
+ with_instance_of(model_class) {|record| assert model_class.find record.id.to_s}
64
+ end
65
+
66
+ test "should be findable by numeric friendly_id" do
67
+ with_instance_of(model_class, :name => "206") {|record| assert model_class.find record.friendly_id}
68
+ end
69
+
70
+ test "to_param should return the friendly_id" do
71
+ with_instance_of(model_class) {|record| assert_equal record.friendly_id, record.to_param}
72
+ end
73
+
74
+ test "should be findable by themselves" do
75
+ with_instance_of(model_class) {|record| assert_equal record, model_class.find(record)}
76
+ end
77
+
78
+ test "updating record's other values should not change the friendly_id" do
79
+ with_instance_of model_class do |record|
80
+ old = record.friendly_id
81
+ record.update_attributes! :active => false
82
+ assert model_class.find old
83
+ end
84
+ end
85
+
86
+ test "instances found by a single id should not be read-only" do
87
+ with_instance_of(model_class) {|record| assert !model_class.find(record.friendly_id).readonly?}
88
+ end
89
+
90
+ test "failing finds with unfriendly_id should raise errors normally" do
91
+ assert_raises(ActiveRecord::RecordNotFound) {model_class.find 0}
92
+ end
93
+
94
+ test "should return numeric id if the friendly_id is nil" do
95
+ with_instance_of(model_class) do |record|
96
+ record.expects(:friendly_id).returns(nil)
97
+ assert_equal record.id.to_s, record.to_param
98
+ end
99
+ end
100
+
101
+ test "should return numeric id if the friendly_id is an empty string" do
102
+ with_instance_of(model_class) do |record|
103
+ record.expects(:friendly_id).returns("")
104
+ assert_equal record.id.to_s, record.to_param
105
+ end
106
+ end
107
+
108
+ test "should return numeric id if the friendly_id is blank" do
109
+ with_instance_of(model_class) do |record|
110
+ record.expects(:friendly_id).returns(" ")
111
+ assert_equal record.id.to_s, record.to_param
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end
118
+
@@ -0,0 +1,83 @@
1
+ require File.expand_path("../helper.rb", __FILE__)
2
+
3
+ Journalist, Article = 2.times.map do
4
+ Class.new(ActiveRecord::Base) do
5
+ extend FriendlyId
6
+ friendly_id :name, :use => :slugged
7
+ end
8
+ end
9
+
10
+ class SluggedTest < MiniTest::Unit::TestCase
11
+
12
+ include FriendlyId::Test
13
+ include FriendlyId::Test::Shared::Core
14
+ include FriendlyId::Test::Shared::Slugged
15
+
16
+ def model_class
17
+ Journalist
18
+ end
19
+ end
20
+
21
+ class SlugSequencerTest < MiniTest::Unit::TestCase
22
+
23
+ include FriendlyId::Test
24
+
25
+ test "should quote column names" do
26
+ model_class = Class.new(ActiveRecord::Base)
27
+ model_class.table_name = "journalists"
28
+ model_class.extend FriendlyId
29
+ model_class.friendly_id :name, :use => :slugged, :slug_column => "strange name"
30
+ begin
31
+ with_instance_of(model_class) {|record| assert model_class.find(record.friendly_id)}
32
+ rescue ActiveRecord::StatementInvalid
33
+ flunk "column name was not quoted"
34
+ end
35
+ end
36
+ end
37
+
38
+ class SlugSeparatorTest < MiniTest::Unit::TestCase
39
+
40
+ include FriendlyId::Test
41
+
42
+ class Journalist < ActiveRecord::Base
43
+ extend FriendlyId
44
+ friendly_id :name, :use => :slugged, :sequence_separator => ":"
45
+ end
46
+
47
+ def model_class
48
+ Journalist
49
+ end
50
+
51
+ test "should increment sequence with configured sequence separator" do
52
+ with_instance_of model_class do |record|
53
+ record2 = model_class.create! :name => record.name
54
+ assert record2.friendly_id.match(/:2\z/)
55
+ end
56
+ end
57
+
58
+ test "should detect when a sequenced slug has changed" do
59
+ with_instance_of model_class do |record|
60
+ record2 = model_class.create! :name => record.name
61
+ assert !record2.slug_sequencer.slug_changed?
62
+ record2.name = "hello world"
63
+ assert record2.slug_sequencer.slug_changed?
64
+ end
65
+ end
66
+ end
67
+
68
+ class SluggedRegressionsTest < MiniTest::Unit::TestCase
69
+ include FriendlyId::Test
70
+
71
+ def model_class
72
+ Journalist
73
+ end
74
+
75
+ test "should increment the slug sequence for duplicate friendly ids beyond 10" do
76
+ with_instance_of model_class do |record|
77
+ (2..12).each do |i|
78
+ r = model_class.create! :name => record.name
79
+ assert r.friendly_id.match(/#{i}\z/)
80
+ end
81
+ end
82
+ end
83
+ end
data/test/sti_test.rb ADDED
@@ -0,0 +1,48 @@
1
+ require File.expand_path("../helper.rb", __FILE__)
2
+
3
+ class StiTest < MiniTest::Unit::TestCase
4
+
5
+ include FriendlyId::Test
6
+ include FriendlyId::Test::Shared::Core
7
+ include FriendlyId::Test::Shared::Slugged
8
+
9
+ class Journalist < ActiveRecord::Base
10
+ extend FriendlyId
11
+ friendly_id :name, :use => :slugged
12
+ end
13
+
14
+ class Editorialist < Journalist
15
+ end
16
+
17
+ def model_class
18
+ Editorialist
19
+ end
20
+
21
+ test "friendly_id should accept a base and a hash with single table inheritance" do
22
+ abstract_klass = Class.new(ActiveRecord::Base) do
23
+ extend FriendlyId
24
+ friendly_id :foo, :use => :slugged, :slug_column => :bar
25
+ end
26
+ klass = Class.new(abstract_klass)
27
+ assert klass < FriendlyId::Slugged
28
+ assert_equal :foo, klass.friendly_id_config.base
29
+ assert_equal :bar, klass.friendly_id_config.slug_column
30
+ end
31
+
32
+
33
+ test "friendly_id should accept a block with single table inheritance" do
34
+ abstract_klass = Class.new(ActiveRecord::Base) do
35
+ extend FriendlyId
36
+ friendly_id :foo do |config|
37
+ config.use :slugged
38
+ config.base = :foo
39
+ config.slug_column = :bar
40
+ end
41
+ end
42
+ klass = Class.new(abstract_klass)
43
+ assert klass < FriendlyId::Slugged
44
+ assert_equal :foo, klass.friendly_id_config.base
45
+ assert_equal :bar, klass.friendly_id_config.slug_column
46
+ end
47
+
48
+ end