friendly_id 3.3.3.0 → 4.0.0.beta7

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