Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions lib/friendly_id/slug_generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,28 @@ def available?(slug)
return false if @config.reserved_words.include?(slug)
end

if @config.treat_numeric_as_conflict && purely_numeric_slug?(slug)
return false
end

!@scope.exists_by_friendly_id?(slug)
end

def generate(candidates)
candidates.each { |c| return c if available?(c) }
nil
end

private

def purely_numeric_slug?(slug)
return false unless slug
begin
Integer(slug, 10)
slug.to_s == Integer(slug, 10).to_s
rescue
false
end
end
end
end
2 changes: 1 addition & 1 deletion lib/friendly_id/slugged.rb
Original file line number Diff line number Diff line change
Expand Up @@ -386,7 +386,7 @@ def unset_slug_if_invalid
# {FriendlyId::Configuration FriendlyId::Configuration}.
module Configuration
attr_writer :slug_column, :slug_limit, :sequence_separator
attr_accessor :slug_generator_class
attr_accessor :slug_generator_class, :treat_numeric_as_conflict

# Makes FriendlyId use the slug column for querying.
# @return String The slug column.
Expand Down
69 changes: 69 additions & 0 deletions test/numeric_slug_test.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
require "helper"

class Article < ActiveRecord::Base
extend FriendlyId
friendly_id :name, use: :slugged
end

class ArticleWithNumericPrevention < ActiveRecord::Base
self.table_name = "articles"
extend FriendlyId
friendly_id :name, use: :slugged
friendly_id_config.treat_numeric_as_conflict = true
end

class NumericSlugTest < TestCaseClass
include FriendlyId::Test
include FriendlyId::Test::Shared::Core
Expand Down Expand Up @@ -28,4 +40,61 @@ def model_class
assert model_class.friendly.exists?("123")
end
end

test "should prevent purely numeric slugs when treat_numeric_as_conflict is enabled" do
transaction do
record = ArticleWithNumericPrevention.create! name: "123"
refute_equal "123", record.slug
assert_match(/\A123-[0-9a-f-]{36}\z/, record.slug)
end
end

test "should allow non-numeric slugs when treat_numeric_as_conflict is enabled" do
transaction do
record = ArticleWithNumericPrevention.create! name: "abc123"
assert_equal "abc123", record.slug
end
end

test "should allow alphanumeric slugs when treat_numeric_as_conflict is enabled" do
transaction do
record = ArticleWithNumericPrevention.create! name: "product-123"
assert_equal "product-123", record.slug
end
end

test "should handle zero as numeric when treat_numeric_as_conflict is enabled" do
transaction do
record = ArticleWithNumericPrevention.create! name: "0"
refute_equal "0", record.slug
assert_match(/\A0-[0-9a-f-]{36}\z/, record.slug)
end
end

test "should handle large numbers as numeric when treat_numeric_as_conflict is enabled" do
transaction do
record = ArticleWithNumericPrevention.create! name: "999999999"
refute_equal "999999999", record.slug
assert_match(/\A999999999-[0-9a-f-]{36}\z/, record.slug)
end
end

test "should find records with UUID-suffixed numeric slugs when treat_numeric_as_conflict is enabled" do
transaction do
record = ArticleWithNumericPrevention.create! name: "123"
found = ArticleWithNumericPrevention.friendly.find(record.slug)
assert_equal record.id, found.id
end
end

test "should resolve conflicts between multiple numeric slugs when treat_numeric_as_conflict is enabled" do
transaction do
record1 = ArticleWithNumericPrevention.create! name: "456"
record2 = ArticleWithNumericPrevention.create! name: "456"

refute_equal record1.slug, record2.slug
assert_match(/\A456-[0-9a-f-]{36}\z/, record1.slug)
assert_match(/\A456-[0-9a-f-]{36}\z/, record2.slug)
end
end
end