friendly_id 5.5.1 → 5.7.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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/Changelog.md +4 -1
  3. data/README.md +3 -3
  4. data/lib/friendly_id/history.rb +2 -2
  5. data/lib/friendly_id/slug_generator.rb +15 -0
  6. data/lib/friendly_id/slugged.rb +19 -1
  7. data/lib/friendly_id/version.rb +1 -1
  8. metadata +5 -76
  9. checksums.yaml.gz.sig +0 -0
  10. data/.gemtest +0 -0
  11. data/.github/FUNDING.yml +0 -1
  12. data/.github/dependabot.yml +0 -6
  13. data/.github/stale.yml +0 -17
  14. data/.github/workflows/test.yml +0 -62
  15. data/.gitignore +0 -14
  16. data/.yardopts +0 -8
  17. data/CONTRIBUTING.md +0 -11
  18. data/Gemfile +0 -23
  19. data/Rakefile +0 -104
  20. data/UPGRADING.md +0 -115
  21. data/bench.rb +0 -84
  22. data/certs/parndt.pem +0 -27
  23. data/friendly_id.gemspec +0 -36
  24. data/gemfiles/Gemfile.rails-5.2.rb +0 -22
  25. data/gemfiles/Gemfile.rails-6.0.rb +0 -22
  26. data/gemfiles/Gemfile.rails-6.1.rb +0 -22
  27. data/gemfiles/Gemfile.rails-7.0.rb +0 -22
  28. data/guide.rb +0 -24
  29. data/test/base_test.rb +0 -69
  30. data/test/benchmarks/finders.rb +0 -90
  31. data/test/benchmarks/object_utils.rb +0 -56
  32. data/test/candidates_test.rb +0 -142
  33. data/test/configuration_test.rb +0 -60
  34. data/test/core_test.rb +0 -35
  35. data/test/databases.yml +0 -22
  36. data/test/finders_test.rb +0 -76
  37. data/test/generator_test.rb +0 -38
  38. data/test/helper.rb +0 -125
  39. data/test/history_test.rb +0 -434
  40. data/test/numeric_slug_test.rb +0 -31
  41. data/test/object_utils_test.rb +0 -27
  42. data/test/reserved_test.rb +0 -73
  43. data/test/schema.rb +0 -117
  44. data/test/scoped_test.rb +0 -95
  45. data/test/sequentially_slugged_test.rb +0 -214
  46. data/test/shared.rb +0 -181
  47. data/test/simple_i18n_test.rb +0 -144
  48. data/test/slugged_test.rb +0 -628
  49. data/test/sti_test.rb +0 -135
  50. data.tar.gz.sig +0 -0
  51. metadata.gz.sig +0 -0
data/test/history_test.rb DELETED
@@ -1,434 +0,0 @@
1
- require "helper"
2
-
3
- class HistoryTest < TestCaseClass
4
- include FriendlyId::Test
5
- include FriendlyId::Test::Shared::Core
6
-
7
- class Manual < ActiveRecord::Base
8
- extend FriendlyId
9
- friendly_id :name, use: [:slugged, :history]
10
- end
11
-
12
- def model_class
13
- Manual
14
- end
15
-
16
- test "should insert record in slugs table on create" do
17
- with_instance_of(model_class) { |record| assert record.slugs.any? }
18
- end
19
-
20
- test "should not create new slug record if friendly_id is not changed" do
21
- with_instance_of(model_class) do |record|
22
- record.active = true
23
- record.save!
24
- assert_equal 1, FriendlyId::Slug.count
25
- end
26
- end
27
-
28
- test "should create new slug record when friendly_id changes" do
29
- with_instance_of(model_class) do |record|
30
- record.name = record.name + "b"
31
- record.slug = nil
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.slug = nil
42
- record.save!
43
- begin
44
- assert model_class.friendly.find(old_friendly_id)
45
- assert model_class.friendly.exists?(old_friendly_id), "should exist? by old id"
46
- rescue ActiveRecord::RecordNotFound
47
- flunk "Could not find record by old id"
48
- end
49
- end
50
- end
51
-
52
- test "should create slug records on each change" do
53
- transaction do
54
- model_class.create! name: "hello"
55
- assert_equal 1, FriendlyId::Slug.count
56
-
57
- record = model_class.friendly.find("hello")
58
- record.name = "hello again"
59
- record.slug = nil
60
- record.save!
61
- assert_equal 2, FriendlyId::Slug.count
62
- end
63
- end
64
-
65
- test "should not be read only when found by slug" do
66
- with_instance_of(model_class) do |record|
67
- refute model_class.friendly.find(record.friendly_id).readonly?
68
- assert record.update name: "foo"
69
- end
70
- end
71
-
72
- test "should not be read only when found by old slug" do
73
- with_instance_of(model_class) do |record|
74
- old_friendly_id = record.friendly_id
75
- record.name = record.name + "b"
76
- record.save!
77
- assert !model_class.friendly.find(old_friendly_id).readonly?
78
- end
79
- end
80
-
81
- test "should handle renames" do
82
- with_instance_of(model_class) do |record|
83
- record.name = "x"
84
- record.slug = nil
85
- assert record.save
86
- record.name = "y"
87
- record.slug = nil
88
- assert record.save
89
- record.name = "x"
90
- record.slug = nil
91
- assert record.save
92
- end
93
- end
94
-
95
- test "should maintain history even if current slug is not the most recent one" do
96
- with_instance_of(model_class) do |record|
97
- record.name = "current"
98
- assert record.save
99
-
100
- # this feels like a hack. only thing i can get to work with the HistoryTestWithSti
101
- # test cases. (Editorialist vs Journalist.)
102
- sluggable_type = FriendlyId::Slug.first.sluggable_type
103
- # create several slugs for record
104
- # current slug does not have max id
105
- FriendlyId::Slug.delete_all
106
- FriendlyId::Slug.create(sluggable_type: sluggable_type, sluggable_id: record.id, slug: "current")
107
- FriendlyId::Slug.create(sluggable_type: sluggable_type, sluggable_id: record.id, slug: "outdated")
108
-
109
- record.reload
110
- record.slug = nil
111
- assert record.save
112
-
113
- assert_equal 2, FriendlyId::Slug.count
114
- end
115
- end
116
-
117
- test "should not create new slugs that match old slugs" do
118
- transaction do
119
- first_record = model_class.create! name: "foo"
120
- first_record.name = "bar"
121
- first_record.save!
122
- second_record = model_class.create! name: "foo"
123
- assert second_record.slug != "foo"
124
- assert_match(/foo-.+/, second_record.slug)
125
- end
126
- end
127
-
128
- test "should not fail when updating historic slugs" do
129
- transaction do
130
- first_record = model_class.create! name: "foo"
131
- second_record = model_class.create! name: "another"
132
-
133
- second_record.update name: "foo", slug: nil
134
- assert_match(/foo-.*/, second_record.slug)
135
-
136
- first_record.update name: "another", slug: nil
137
- assert_match(/another-.*/, first_record.slug)
138
- end
139
- end
140
-
141
- test "should prefer product that used slug most recently" do
142
- transaction do
143
- first_record = model_class.create! name: "foo"
144
- second_record = model_class.create! name: "bar"
145
-
146
- first_record.update! slug: "not_foo"
147
- second_record.update! slug: "foo" # now both records have used foo; second_record most recently
148
- second_record.update! slug: "not_bar"
149
-
150
- assert_equal model_class.friendly.find("foo"), second_record
151
- end
152
- end
153
-
154
- test "should name table according to prefix and suffix" do
155
- transaction do
156
- prefix = "prefix_"
157
- without_prefix = FriendlyId::Slug.table_name
158
- ActiveRecord::Base.table_name_prefix = prefix
159
- FriendlyId::Slug.reset_table_name
160
- assert_equal prefix + without_prefix, FriendlyId::Slug.table_name
161
- ensure
162
- ActiveRecord::Base.table_name_prefix = ""
163
- FriendlyId::Slug.table_name = without_prefix
164
- end
165
- end
166
- end
167
-
168
- class HistoryTestWithAutomaticSlugRegeneration < HistoryTest
169
- class Manual < ActiveRecord::Base
170
- extend FriendlyId
171
- friendly_id :name, use: [:slugged, :history]
172
-
173
- def should_generate_new_friendly_id?
174
- slug.blank? or name_changed?
175
- end
176
- end
177
-
178
- def model_class
179
- Manual
180
- end
181
-
182
- test "should allow reversion back to a previously used slug" do
183
- with_instance_of(model_class, name: "foo") do |record|
184
- record.name = "bar"
185
- record.save!
186
- assert_equal "bar", record.friendly_id
187
- record.name = "foo"
188
- record.save!
189
- assert_equal "foo", record.friendly_id
190
- end
191
- end
192
- end
193
-
194
- class DependentDestroyTest < TestCaseClass
195
- include FriendlyId::Test
196
-
197
- class FalseManual < ActiveRecord::Base
198
- self.table_name = "manuals"
199
-
200
- extend FriendlyId
201
- friendly_id :name, use: :history, dependent: false
202
- end
203
-
204
- class DefaultManual < ActiveRecord::Base
205
- self.table_name = "manuals"
206
-
207
- extend FriendlyId
208
- friendly_id :name, use: :history
209
- end
210
-
211
- test "should allow disabling of dependent destroy" do
212
- transaction do
213
- assert FriendlyId::Slug.find_by_slug("foo").nil?
214
- l = FalseManual.create! name: "foo"
215
- assert FriendlyId::Slug.find_by_slug("foo").present?
216
- l.destroy
217
- assert FriendlyId::Slug.find_by_slug("foo").present?
218
- end
219
- end
220
-
221
- test "should dependently destroy by default" do
222
- transaction do
223
- assert FriendlyId::Slug.find_by_slug("baz").nil?
224
- l = DefaultManual.create! name: "baz"
225
- assert FriendlyId::Slug.find_by_slug("baz").present?
226
- l.destroy
227
- assert FriendlyId::Slug.find_by_slug("baz").nil?
228
- end
229
- end
230
- end
231
-
232
- if ActiveRecord::VERSION::STRING >= "5.0"
233
- class HistoryTestWithParanoidDeletes < HistoryTest
234
- class ParanoidRecord < ActiveRecord::Base
235
- extend FriendlyId
236
- friendly_id :name, use: :history, dependent: false
237
-
238
- default_scope { where(deleted_at: nil) }
239
- end
240
-
241
- def model_class
242
- ParanoidRecord
243
- end
244
-
245
- test "slug should have a sluggable even when soft deleted by a library" do
246
- transaction do
247
- assert FriendlyId::Slug.find_by_slug("paranoid").nil?
248
- record = model_class.create(name: "paranoid")
249
- assert FriendlyId::Slug.find_by_slug("paranoid").present?
250
-
251
- record.update deleted_at: Time.now
252
-
253
- orphan_slug = FriendlyId::Slug.find_by_slug("paranoid")
254
- assert orphan_slug.present?, "Orphaned slug should exist"
255
-
256
- assert orphan_slug.valid?, "Errors: #{orphan_slug.errors.full_messages}"
257
- assert orphan_slug.sluggable.present?, "Orphaned slug should still find corresponding paranoid sluggable"
258
- end
259
- end
260
- end
261
- end
262
-
263
- class HistoryTestWithSti < HistoryTest
264
- class Journalist < ActiveRecord::Base
265
- extend FriendlyId
266
- friendly_id :name, use: [:slugged, :history]
267
- end
268
-
269
- class Editorialist < Journalist
270
- end
271
-
272
- def model_class
273
- Editorialist
274
- end
275
- end
276
-
277
- class HistoryTestWithFriendlyFinders < HistoryTest
278
- class Journalist < ActiveRecord::Base
279
- extend FriendlyId
280
- friendly_id :name, use: [:slugged, :finders, :history]
281
- end
282
-
283
- class Restaurant < ActiveRecord::Base
284
- extend FriendlyId
285
- belongs_to :city
286
- friendly_id :name, use: [:slugged, :history, :finders]
287
- end
288
-
289
- test "should be findable by old slugs" do
290
- [Journalist, Restaurant].each do |model_class|
291
- with_instance_of(model_class) do |record|
292
- old_friendly_id = record.friendly_id
293
- record.name = record.name + "b"
294
- record.slug = nil
295
- record.save!
296
- begin
297
- assert model_class.find(old_friendly_id)
298
- assert model_class.exists?(old_friendly_id), "should exist? by old id for #{model_class.name}"
299
- rescue ActiveRecord::RecordNotFound
300
- flunk "Could not find record by old id for #{model_class.name}"
301
- end
302
- end
303
- end
304
- end
305
- end
306
-
307
- class HistoryTestWithFindersBeforeHistory < HistoryTest
308
- class Novelist < ActiveRecord::Base
309
- has_many :novels
310
- end
311
-
312
- class Novel < ActiveRecord::Base
313
- extend FriendlyId
314
-
315
- belongs_to :novelist
316
-
317
- friendly_id :name, use: [:finders, :history]
318
-
319
- def should_generate_new_friendly_id?
320
- slug.blank? || name_changed?
321
- end
322
- end
323
-
324
- test "should be findable by old slug through has_many association" do
325
- transaction do
326
- novelist = Novelist.create!(name: "Stephen King")
327
- novel = novelist.novels.create(name: "Rita Hayworth and Shawshank Redemption")
328
- slug = novel.slug
329
- novel.name = "Shawshank Redemption"
330
- novel.save!
331
- assert_equal novel, Novel.find(slug)
332
- assert_equal novel, novelist.novels.find(slug)
333
- end
334
- end
335
- end
336
-
337
- class City < ActiveRecord::Base
338
- has_many :restaurants
339
- end
340
-
341
- class Restaurant < ActiveRecord::Base
342
- extend FriendlyId
343
- belongs_to :city
344
- friendly_id :name, use: [:scoped, :history], scope: :city
345
- end
346
-
347
- class ScopedHistoryTest < TestCaseClass
348
- include FriendlyId::Test
349
- include FriendlyId::Test::Shared::Core
350
-
351
- def model_class
352
- Restaurant
353
- end
354
-
355
- test "should find old scoped slugs" do
356
- transaction do
357
- city = City.create!
358
- with_instance_of(Restaurant) do |record|
359
- record.city = city
360
-
361
- record.name = "x"
362
- record.slug = nil
363
- record.save!
364
-
365
- record.name = "y"
366
- record.slug = nil
367
- record.save!
368
-
369
- assert_equal city.restaurants.friendly.find("x"), city.restaurants.friendly.find("y")
370
- end
371
- end
372
- end
373
-
374
- test "should consider old scoped slugs when creating slugs" do
375
- transaction do
376
- city = City.create!
377
- with_instance_of(Restaurant) do |record|
378
- record.city = city
379
-
380
- record.name = "x"
381
- record.slug = nil
382
- record.save!
383
-
384
- record.name = "y"
385
- record.slug = nil
386
- record.save!
387
-
388
- second_record = model_class.create! city: city, name: "x"
389
- assert_match(/x-.+/, second_record.friendly_id)
390
-
391
- third_record = model_class.create! city: city, name: "y"
392
- assert_match(/y-.+/, third_record.friendly_id)
393
- end
394
- end
395
- end
396
-
397
- test "should record history when scope changes" do
398
- transaction do
399
- city1 = City.create!
400
- city2 = City.create!
401
- with_instance_of(Restaurant) do |record|
402
- record.name = "x"
403
- record.slug = nil
404
-
405
- record.city = city1
406
- record.save!
407
- assert_equal("city_id:#{city1.id}", record.slugs.reload.first.scope)
408
- assert_equal("x", record.slugs.reload.first.slug)
409
-
410
- record.city = city2
411
- record.save!
412
- assert_equal("city_id:#{city2.id}", record.slugs.reload.first.scope)
413
-
414
- record.name = "y"
415
- record.slug = nil
416
- record.city = city1
417
- record.save!
418
- assert_equal("city_id:#{city1.id}", record.slugs.reload.first.scope)
419
- assert_equal("y", record.slugs.reload.first.slug)
420
- end
421
- end
422
- end
423
-
424
- test "should allow equal slugs in different scopes" do
425
- transaction do
426
- city = City.create!
427
- second_city = City.create!
428
- record = model_class.create! city: city, name: "x"
429
- second_record = model_class.create! city: second_city, name: "x"
430
-
431
- assert_equal record.slug, second_record.slug
432
- end
433
- end
434
- end
@@ -1,31 +0,0 @@
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
- model_class.create! name: "123"
28
- assert model_class.friendly.exists?("123")
29
- end
30
- end
31
- end
@@ -1,27 +0,0 @@
1
- require "helper"
2
-
3
- class ObjectUtilsTest < TestCaseClass
4
- include FriendlyId::Test
5
-
6
- test "strings with letters are friendly_ids" do
7
- assert "a".friendly_id?
8
- end
9
-
10
- test "integers should be unfriendly ids" do
11
- assert 1.unfriendly_id?
12
- end
13
-
14
- test "numeric strings are neither friendly nor unfriendly" do
15
- assert_nil "1".friendly_id?
16
- assert_nil "1".unfriendly_id?
17
- end
18
-
19
- test "ActiveRecord::Base instances should be unfriendly_ids" do
20
- FriendlyId.mark_as_unfriendly(ActiveRecord::Base)
21
-
22
- model_class = Class.new(ActiveRecord::Base) do
23
- self.table_name = "authors"
24
- end
25
- assert model_class.new.unfriendly_id?
26
- end
27
- end
@@ -1,73 +0,0 @@
1
- require "helper"
2
-
3
- class ReservedTest < TestCaseClass
4
- include FriendlyId::Test
5
-
6
- class Journalist < ActiveRecord::Base
7
- extend FriendlyId
8
- friendly_id :slug_candidates, use: [:slugged, :reserved], reserved_words: %w[new edit]
9
-
10
- after_validation :move_friendly_id_error_to_name
11
-
12
- def move_friendly_id_error_to_name
13
- errors.add :name, *errors.delete(:friendly_id) if errors[:friendly_id].present?
14
- end
15
-
16
- def slug_candidates
17
- name
18
- end
19
- end
20
-
21
- def model_class
22
- Journalist
23
- end
24
-
25
- test "should reserve words" do
26
- %w[new edit NEW Edit].each do |word|
27
- transaction do
28
- assert_raises(ActiveRecord::RecordInvalid) { model_class.create! name: word }
29
- end
30
- end
31
- end
32
-
33
- test "should move friendly_id error to name" do
34
- with_instance_of(model_class) do |record|
35
- record.errors.add :name, "xxx"
36
- record.errors.add :friendly_id, "yyy"
37
- record.move_friendly_id_error_to_name
38
- assert record.errors[:name].present? && record.errors[:friendly_id].blank?
39
- assert_equal 2, record.errors.count
40
- end
41
- end
42
-
43
- test "should reject reserved candidates" do
44
- transaction do
45
- record = model_class.new(name: "new")
46
- def record.slug_candidates
47
- [:name, "foo"]
48
- end
49
- record.save!
50
- assert_equal "foo", record.friendly_id
51
- end
52
- end
53
-
54
- test "should be invalid if all candidates are reserved" do
55
- transaction do
56
- record = model_class.new(name: "new")
57
- def record.slug_candidates
58
- ["edit", "new"]
59
- end
60
- assert_raises(ActiveRecord::RecordInvalid) { record.save! }
61
- end
62
- end
63
-
64
- test "should optionally treat reserved words as conflict" do
65
- klass = Class.new(model_class) do
66
- friendly_id :slug_candidates, use: [:slugged, :reserved], reserved_words: %w[new edit], treat_reserved_as_conflict: true
67
- end
68
-
69
- with_instance_of(klass, name: "new") do |record|
70
- assert_match(/new-([0-9a-z]+-){4}[0-9a-z]+\z/, record.slug)
71
- end
72
- end
73
- end
data/test/schema.rb DELETED
@@ -1,117 +0,0 @@
1
- require "friendly_id/migration"
2
-
3
- module FriendlyId
4
- module Test
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
13
- class << self
14
- def down
15
- CreateFriendlyIdSlugs.down
16
- tables.each do |name|
17
- drop_table name
18
- end
19
- end
20
-
21
- def up
22
- # TODO: use schema version to avoid ugly hacks like this
23
- return if @done
24
- CreateFriendlyIdSlugs.migrate :up
25
-
26
- tables.each do |table_name|
27
- create_table table_name do |t|
28
- t.string :name
29
- t.boolean :active
30
- end
31
- end
32
-
33
- tables_with_uuid_primary_key.each do |table_name|
34
- create_table table_name, primary_key: :uuid_key, id: false do |t|
35
- t.string :name
36
- t.string :uuid_key, null: false
37
- t.string :slug
38
- end
39
- add_index table_name, :slug, unique: true
40
- end
41
-
42
- slugged_tables.each do |table_name|
43
- add_column table_name, :slug, :string
44
- add_index table_name, :slug, unique: true if table_name != "novels"
45
- end
46
-
47
- scoped_tables.each do |table_name|
48
- add_column table_name, :slug, :string
49
- end
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
-
57
- # This will be used to test scopes
58
- add_column :novels, :novelist_id, :integer
59
- add_column :novels, :publisher_id, :integer
60
- add_index :novels, [:slug, :publisher_id, :novelist_id], unique: true
61
-
62
- # This will be used to test column name quoting
63
- add_column :journalists, "strange name", :string
64
-
65
- # This will be used to test STI
66
- add_column :journalists, "type", :string
67
-
68
- # These will be used to test i18n
69
- add_column :journalists, "slug_en", :string
70
- add_column :journalists, "slug_es", :string
71
- add_column :journalists, "slug_de", :string
72
- add_column :journalists, "slug_fr_ca", :string
73
-
74
- # This will be used to test relationships
75
- add_column :books, :author_id, :integer
76
-
77
- # Used to test :scoped and :history together
78
- add_column :restaurants, :city_id, :integer
79
-
80
- # Used to test candidates
81
- add_column :cities, :code, :string, limit: 3
82
-
83
- # Used as a non-default slug_column
84
- add_column :authors, :subdomain, :string
85
-
86
- @done = true
87
- end
88
-
89
- private
90
-
91
- def slugged_tables
92
- %w[journalists articles novelists novels manuals cities]
93
- end
94
-
95
- def paranoid_tables
96
- ["paranoid_records"]
97
- end
98
-
99
- def tables_with_uuid_primary_key
100
- ["menu_items"]
101
- end
102
-
103
- def scoped_tables
104
- ["restaurants"]
105
- end
106
-
107
- def simple_tables
108
- %w[authors books publishers]
109
- end
110
-
111
- def tables
112
- simple_tables + slugged_tables + scoped_tables + paranoid_tables
113
- end
114
- end
115
- end
116
- end
117
- end