friendly_id 5.2.2 → 5.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +2 -0
  4. data/.github/stale.yml +17 -0
  5. data/.github/workflows/test.yml +60 -0
  6. data/Changelog.md +39 -1
  7. data/Gemfile +3 -0
  8. data/README.md +54 -164
  9. data/Rakefile +2 -2
  10. data/UPGRADING.md +115 -0
  11. data/certs/parndt.pem +25 -0
  12. data/friendly_id.gemspec +9 -5
  13. data/gemfiles/Gemfile.rails-5.0.rb +2 -2
  14. data/gemfiles/{Gemfile.rails-4.2.rb → Gemfile.rails-5.1.rb} +4 -5
  15. data/gemfiles/{Gemfile.rails-4.1.rb → Gemfile.rails-5.2.rb} +5 -7
  16. data/gemfiles/{Gemfile.rails-4.0.rb → Gemfile.rails-6.0.rb} +5 -8
  17. data/lib/friendly_id/base.rb +4 -8
  18. data/lib/friendly_id/candidates.rb +0 -2
  19. data/lib/friendly_id/configuration.rb +3 -2
  20. data/lib/friendly_id/finder_methods.rb +18 -7
  21. data/lib/friendly_id/finders.rb +1 -1
  22. data/lib/friendly_id/history.rb +21 -12
  23. data/lib/friendly_id/initializer.rb +11 -0
  24. data/lib/friendly_id/migration.rb +9 -3
  25. data/lib/friendly_id/object_utils.rb +9 -2
  26. data/lib/friendly_id/reserved.rb +1 -0
  27. data/lib/friendly_id/scoped.rb +9 -2
  28. data/lib/friendly_id/sequentially_slugged.rb +12 -2
  29. data/lib/friendly_id/slug.rb +4 -0
  30. data/lib/friendly_id/slug_generator.rb +6 -1
  31. data/lib/friendly_id/slugged.rb +3 -3
  32. data/lib/friendly_id/version.rb +1 -1
  33. data/test/databases.yml +6 -4
  34. data/test/finders_test.rb +24 -0
  35. data/test/helper.rb +13 -3
  36. data/test/history_test.rb +86 -7
  37. data/test/numeric_slug_test.rb +31 -0
  38. data/test/object_utils_test.rb +5 -3
  39. data/test/reserved_test.rb +10 -0
  40. data/test/schema.rb +19 -2
  41. data/test/scoped_test.rb +13 -0
  42. data/test/sequentially_slugged_test.rb +59 -0
  43. data/test/shared.rb +4 -4
  44. data/test/simple_i18n_test.rb +2 -2
  45. data/test/slugged_test.rb +168 -4
  46. metadata +48 -19
  47. metadata.gz.sig +0 -0
  48. data/.travis.yml +0 -40
@@ -0,0 +1,31 @@
1
+ require 'helper'
2
+
3
+ class NumericSlugTest < TestCaseClass
4
+ include FriendlyId::Test
5
+ include FriendlyId::Test::Shared::Core
6
+
7
+ def model_class
8
+ Article
9
+ end
10
+
11
+ test "should generate numeric slugs" do
12
+ transaction do
13
+ record = model_class.create! :name => "123"
14
+ assert_equal "123", record.slug
15
+ end
16
+ end
17
+
18
+ test "should find by numeric slug" do
19
+ transaction do
20
+ record = model_class.create! :name => "123"
21
+ assert_equal model_class.friendly.find("123").id, record.id
22
+ end
23
+ end
24
+
25
+ test "should exist? by numeric slug" do
26
+ transaction do
27
+ record = model_class.create! :name => "123"
28
+ assert model_class.friendly.exists?("123")
29
+ end
30
+ end
31
+ end
@@ -14,14 +14,16 @@ class ObjectUtilsTest < TestCaseClass
14
14
  end
15
15
 
16
16
  test "numeric strings are neither friendly nor unfriendly" do
17
- assert_equal nil, "1".friendly_id?
18
- assert_equal nil, "1".unfriendly_id?
17
+ assert_nil "1".friendly_id?
18
+ assert_nil "1".unfriendly_id?
19
19
  end
20
20
 
21
21
  test "ActiveRecord::Base instances should be unfriendly_ids" do
22
+ FriendlyId.mark_as_unfriendly(ActiveRecord::Base)
23
+
22
24
  model_class = Class.new(ActiveRecord::Base) do
23
25
  self.table_name = "authors"
24
26
  end
25
27
  assert model_class.new.unfriendly_id?
26
28
  end
27
- end
29
+ end
@@ -62,4 +62,14 @@ class ReservedTest < TestCaseClass
62
62
  end
63
63
  end
64
64
 
65
+ test "should optionally treat reserved words as conflict" do
66
+ klass = Class.new(model_class) do
67
+ friendly_id :slug_candidates, :use => [:slugged, :reserved], :reserved_words => %w(new edit), :treat_reserved_as_conflict => true
68
+ end
69
+
70
+ with_instance_of(klass, name: 'new') do |record|
71
+ assert_match(/new-([0-9a-z]+\-){4}[0-9a-z]+\z/, record.slug)
72
+ end
73
+ end
74
+
65
75
  end
@@ -2,7 +2,14 @@ require "friendly_id/migration"
2
2
 
3
3
  module FriendlyId
4
4
  module Test
5
- class Schema < ActiveRecord::Migration
5
+ migration_class =
6
+ if ActiveRecord::VERSION::MAJOR >= 5
7
+ ActiveRecord::Migration[4.2]
8
+ else
9
+ ActiveRecord::Migration
10
+ end
11
+
12
+ class Schema < migration_class
6
13
  class << self
7
14
  def down
8
15
  CreateFriendlyIdSlugs.down
@@ -41,6 +48,12 @@ module FriendlyId
41
48
  add_column table_name, :slug, :string
42
49
  end
43
50
 
51
+ paranoid_tables.each do |table_name|
52
+ add_column table_name, :slug, :string
53
+ add_column table_name, :deleted_at, :datetime
54
+ add_index table_name, :deleted_at
55
+ end
56
+
44
57
  # This will be used to test scopes
45
58
  add_column :novels, :novelist_id, :integer
46
59
  add_column :novels, :publisher_id, :integer
@@ -78,6 +91,10 @@ module FriendlyId
78
91
  %w[journalists articles novelists novels manuals cities]
79
92
  end
80
93
 
94
+ def paranoid_tables
95
+ ["paranoid_records"]
96
+ end
97
+
81
98
  def tables_with_uuid_primary_key
82
99
  ["menu_items"]
83
100
  end
@@ -91,7 +108,7 @@ module FriendlyId
91
108
  end
92
109
 
93
110
  def tables
94
- simple_tables + slugged_tables + scoped_tables
111
+ simple_tables + slugged_tables + scoped_tables + paranoid_tables
95
112
  end
96
113
  end
97
114
  end
@@ -81,4 +81,17 @@ class ScopedTest < TestCaseClass
81
81
  end
82
82
  end
83
83
 
84
+ test "should generate new slug when scope changes" do
85
+ transaction do
86
+ novelist = Novelist.create! :name => "a"
87
+ publisher = Publisher.create! :name => "b"
88
+ novel1 = Novel.create! :name => "c", :novelist => novelist, :publisher => publisher
89
+ novel2 = Novel.create! :name => "c", :novelist => novelist, :publisher => Publisher.create(:name => "d")
90
+ assert_equal novel1.friendly_id, novel2.friendly_id
91
+ novel2.publisher = publisher
92
+ novel2.save!
93
+ assert novel2.friendly_id != novel1.friendly_id
94
+ end
95
+ end
96
+
84
97
  end
@@ -137,4 +137,63 @@ class SequentiallySluggedTestWithHistory < TestCaseClass
137
137
  assert_equal 'test-name-2', record2.slug
138
138
  end
139
139
  end
140
+
141
+ test "should work with regeneration with history when 2 slugs already exists and the second is changed" do
142
+ transaction do
143
+ record1 = model_class.create! :name => "Test name"
144
+ record2 = model_class.create! :name => "Test name"
145
+ record3 = model_class.create! :name => "Another test name"
146
+ assert_equal 'test-name', record1.slug
147
+ assert_equal 'test-name-2', record2.slug
148
+ assert_equal 'another-test-name', record3.slug
149
+
150
+ record2.name = "One more test name"
151
+ record2.slug = nil
152
+ record2.save!
153
+ assert_equal 'one-more-test-name', record2.slug
154
+
155
+ record3.name = "Test name"
156
+ record3.slug = nil
157
+ record3.save!
158
+ assert_equal 'test-name-3', record3.slug
159
+ end
160
+ end
161
+ end
162
+
163
+ class City < ActiveRecord::Base
164
+ has_many :restaurants
165
+ end
166
+
167
+ class Restaurant < ActiveRecord::Base
168
+ extend FriendlyId
169
+ belongs_to :city
170
+ friendly_id :name, :use => [:sequentially_slugged, :scoped, :history], :scope => :city
171
+ end
172
+
173
+ class SequentiallySluggedTestWithScopedHistory < TestCaseClass
174
+ include FriendlyId::Test
175
+ include FriendlyId::Test::Shared::Core
176
+
177
+ def model_class
178
+ Restaurant
179
+ end
180
+
181
+ test "should work with regeneration with scoped history" do
182
+ transaction do
183
+ city1 = City.create!
184
+ city2 = City.create!
185
+ record1 = model_class.create! :name => "Test name", :city => city1
186
+ record2 = model_class.create! :name => "Test name", :city => city1
187
+
188
+ assert_equal 'test-name', record1.slug
189
+ assert_equal 'test-name-2', record2.slug
190
+
191
+ record2.name = 'Another test name'
192
+ record2.slug = nil
193
+ record2.save!
194
+
195
+ record3 = model_class.create! :name => "Test name", :city => city1
196
+ assert_equal 'test-name-3', record3.slug
197
+ end
198
+ end
140
199
  end
@@ -62,9 +62,9 @@ module FriendlyId
62
62
  my_model_class = Class.new(model_class)
63
63
  self.class.const_set("Foo", my_model_class)
64
64
  with_instance_of my_model_class do |record|
65
- record.update_attributes my_model_class.friendly_id_config.slug_column => nil
65
+ record.update my_model_class.friendly_id_config.slug_column => nil
66
66
  record = my_model_class.friendly.find(record.id)
67
- record.class.validate Proc.new {errors[:name] = "FAIL"}
67
+ record.class.validate Proc.new {errors.add(:name, "FAIL")}
68
68
  record.save
69
69
  assert_equal record.to_param, record.friendly_id
70
70
  end
@@ -132,7 +132,7 @@ module FriendlyId
132
132
  test "updating record's other values should not change the friendly_id" do
133
133
  with_instance_of model_class do |record|
134
134
  old = record.friendly_id
135
- record.update_attributes! :active => false
135
+ record.update! active: false
136
136
  assert model_class.friendly.find old
137
137
  end
138
138
  end
@@ -174,7 +174,7 @@ module FriendlyId
174
174
  end
175
175
 
176
176
  test "should return nil for to_param with a new record" do
177
- assert_equal nil, model_class.new.to_param
177
+ assert_nil model_class.new.to_param
178
178
  end
179
179
  end
180
180
  end
@@ -91,14 +91,14 @@ class SimpleI18nTest < TestCaseClass
91
91
  class RegressionTest < TestCaseClass
92
92
  include FriendlyId::Test
93
93
 
94
- test "should not overwrite other locale's slugs on update_attributes" do
94
+ test "should not overwrite other locale's slugs on update" do
95
95
  transaction do
96
96
  journalist = Journalist.create!(:name => "John Smith")
97
97
  journalist.set_friendly_id("Juan Fulano", :es)
98
98
  journalist.save!
99
99
  assert_equal "john-smith", journalist.to_param
100
100
  journalist.slug = nil
101
- journalist.update_attributes :name => "Johnny Smith"
101
+ journalist.update :name => "Johnny Smith"
102
102
  assert_equal "johnny-smith", journalist.to_param
103
103
  I18n.with_locale(:es) do
104
104
  assert_equal "juan-fulano", journalist.to_param
@@ -92,7 +92,7 @@ class SluggedTest < TestCaseClass
92
92
  end
93
93
  end
94
94
 
95
- test "should not set slug on create if unrelated validations fail" do
95
+ test "should set slug on create if unrelated validations fail" do
96
96
  klass = Class.new model_class do
97
97
  validates_presence_of :active
98
98
  friendly_id :name, :use => :slugged
@@ -106,11 +106,30 @@ class SluggedTest < TestCaseClass
106
106
  instance = klass.new :name => 'foo'
107
107
  refute instance.save
108
108
  refute instance.valid?
109
+ assert_equal 'foo', instance.slug
110
+ end
111
+ end
112
+
113
+ test "should not set slug on create if slug validation fails" do
114
+ klass = Class.new model_class do
115
+ validates_presence_of :active
116
+ validates_length_of :slug, :minimum => 2
117
+ friendly_id :name, :use => :slugged
118
+
119
+ def self.name
120
+ "Journalist"
121
+ end
122
+ end
123
+
124
+ transaction do
125
+ instance = klass.new :name => 'x'
126
+ refute instance.save
127
+ refute instance.valid?
109
128
  assert_nil instance.slug
110
129
  end
111
130
  end
112
131
 
113
- test "should not set slug on create if unrelated validations fail with custom slug_column" do
132
+ test "should set slug on create if unrelated validations fail with custom slug_column" do
114
133
  klass = Class.new(ActiveRecord::Base) do
115
134
  self.table_name = 'authors'
116
135
  extend FriendlyId
@@ -126,11 +145,31 @@ class SluggedTest < TestCaseClass
126
145
  instance = klass.new :name => 'foo'
127
146
  refute instance.save
128
147
  refute instance.valid?
148
+ assert_equal 'foo', instance.subdomain
149
+ end
150
+ end
151
+
152
+ test "should not set slug on create if custom slug column validations fail" do
153
+ klass = Class.new(ActiveRecord::Base) do
154
+ self.table_name = 'authors'
155
+ extend FriendlyId
156
+ validates_length_of :subdomain, :minimum => 2
157
+ friendly_id :name, :use => :slugged, :slug_column => :subdomain
158
+
159
+ def self.name
160
+ "Author"
161
+ end
162
+ end
163
+
164
+ transaction do
165
+ instance = klass.new :name => 'x'
166
+ refute instance.save
167
+ refute instance.valid?
129
168
  assert_nil instance.subdomain
130
169
  end
131
170
  end
132
171
 
133
- test "should not update slug on save if unrelated validations fail" do
172
+ test "should keep new slug on save if unrelated validations fail" do
134
173
  klass = Class.new model_class do
135
174
  validates_presence_of :active
136
175
  friendly_id :name, :use => :slugged
@@ -149,10 +188,31 @@ class SluggedTest < TestCaseClass
149
188
  instance.active = nil
150
189
  refute instance.save
151
190
  refute instance.valid?
152
- assert_equal 'foo', instance.slug
191
+ assert_equal 'foobar', instance.slug
153
192
  end
154
193
  end
155
194
 
195
+ test "should not update slug on save if slug validations fail" do
196
+ klass = Class.new model_class do
197
+ validates_length_of :slug, :minimum => 2
198
+ friendly_id :name, :use => :slugged
199
+
200
+ def self.name
201
+ "Journalist"
202
+ end
203
+ end
204
+
205
+ transaction do
206
+ instance = klass.new :name => 'foo', :active => true
207
+ assert instance.save
208
+ assert instance.valid?
209
+ instance.name = 'x'
210
+ instance.slug = nil
211
+ instance.active = nil
212
+ refute instance.save
213
+ assert_equal 'foo', instance.slug
214
+ end
215
+ end
156
216
  end
157
217
 
158
218
  class SlugGeneratorTest < TestCaseClass
@@ -419,6 +479,110 @@ class FailedValidationAfterUpdateRegressionTest < TestCaseClass
419
479
 
420
480
  end
421
481
 
482
+ class ToParamTest < TestCaseClass
483
+
484
+ include FriendlyId::Test
485
+
486
+ class Journalist < ActiveRecord::Base
487
+ extend FriendlyId
488
+ validates_presence_of :active
489
+ validates_length_of :slug, :minimum => 2
490
+ friendly_id :name, :use => :slugged
491
+
492
+ attr_accessor :to_param_in_callback
493
+
494
+ after_save do
495
+ self.to_param_in_callback = to_param
496
+ end
497
+ end
498
+
499
+ test "to_param should return nil if record is unpersisted" do
500
+ assert_nil Journalist.new.to_param
501
+ end
502
+
503
+ test "to_param should return original slug if record failed validation" do
504
+ journalist = Journalist.new :name => 'Clark Kent', :active => nil
505
+ refute journalist.save
506
+ assert_equal 'clark-kent', journalist.to_param
507
+ end
508
+
509
+ test "to_param should clear slug attributes if slug attribute fails validation" do
510
+ journalist = Journalist.new :name => 'x', :active => true
511
+ refute journalist.save
512
+ assert_nil journalist.to_param
513
+ end
514
+
515
+ test "to_param should clear slug attribute if slug attribute fails validation and unrelated validation fails" do
516
+ journalist = Journalist.new :name => 'x', :active => nil
517
+ refute journalist.save
518
+ assert_nil journalist.to_param
519
+ end
520
+
521
+ test "to_param should use slugged attribute if record saved successfully" do
522
+ transaction do
523
+ journalist = Journalist.new :name => 'Clark Kent', :active => true
524
+ assert journalist.save
525
+ assert_equal 'clark-kent', journalist.to_param
526
+ end
527
+ end
528
+
529
+ test "to_param should use new slug if existing record changes but fails to save" do
530
+ transaction do
531
+ journalist = Journalist.new :name => 'Clark Kent', :active => true
532
+ assert journalist.save
533
+ journalist.name = 'Superman'
534
+ journalist.slug = nil
535
+ journalist.active = nil
536
+ refute journalist.save
537
+ assert_equal 'superman', journalist.to_param
538
+ end
539
+ end
540
+
541
+ test "to_param should use original slug if new slug attribute is not valid" do
542
+ transaction do
543
+ journalist = Journalist.new :name => 'Clark Kent', :active => true
544
+ assert journalist.save
545
+ journalist.name = 'x'
546
+ journalist.slug = nil
547
+ journalist.active = nil
548
+ refute journalist.save
549
+ assert_equal 'clark-kent', journalist.to_param
550
+ end
551
+ end
552
+
553
+ test "to_param should use new slug if existing record changes successfully" do
554
+ transaction do
555
+ journalist = Journalist.new :name => 'Clark Kent', :active => true
556
+ assert journalist.save
557
+ journalist.name = 'Superman'
558
+ journalist.slug = nil
559
+ assert journalist.save
560
+ assert_equal 'superman', journalist.to_param
561
+ end
562
+ end
563
+
564
+ test "to_param should use new slug within callbacks if new record is saved successfully" do
565
+ transaction do
566
+ journalist = Journalist.new :name => 'Clark Kent', :active => true
567
+ assert journalist.save
568
+ assert_equal 'clark-kent', journalist.to_param_in_callback, "value of to_param in callback should use the new slug value"
569
+ end
570
+ end
571
+
572
+ test "to_param should use new slug within callbacks if existing record changes successfully" do
573
+ transaction do
574
+ journalist = Journalist.new :name => 'Clark Kent', :active => true
575
+ assert journalist.save
576
+ assert journalist.valid?
577
+ journalist.name = 'Superman'
578
+ journalist.slug = nil
579
+ assert journalist.save, "save should be successful"
580
+ assert_equal 'superman', journalist.to_param_in_callback, "value of to_param in callback should use the new slug value"
581
+ end
582
+ end
583
+
584
+ end
585
+
422
586
  class ConfigurableRoutesTest < TestCaseClass
423
587
  include FriendlyId::Test
424
588