friendly_id 5.2.2 → 5.4.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 (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