Skip to content

Commit

Permalink
Fix findings
Browse files Browse the repository at this point in the history
  • Loading branch information
treagod committed Aug 21, 2024
1 parent 6800f76 commit c688ae5
Show file tree
Hide file tree
Showing 6 changed files with 106 additions and 44 deletions.
54 changes: 54 additions & 0 deletions spec/marten/core/sluggable_spec.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
require "./spec_helper"

describe Marten::Core::Sluggable do
describe ".generate_slug" do
value = "Test Title 123"
max_size = 20
slug = Marten::Core::Sluggable.generate_slug(value, max_size)

it "removes non-alphanumeric characters" do
value_with_special_chars = "Test@Title#123!"
slug = Marten::Core::Sluggable.generate_slug(value_with_special_chars, max_size)
slug.starts_with?("testtitle12-").should be_true
end

it "converts the string to lowercase" do
slug.starts_with?("testtitle12-").should be_true
end

it "replaces whitespace and hyphens with a single hyphen" do
value_with_spaces_and_hyphens = "Test - Title 123"
slug = Marten::Core::Sluggable.generate_slug(value_with_spaces_and_hyphens, max_size)
slug.starts_with?("test-title-").should be_true
end

it "strips leading and trailing hyphens and underscores" do
value_with_hyphens = "-Test Title 123-"
slug = Marten::Core::Sluggable.generate_slug(value_with_hyphens, max_size)
slug.starts_with?("test-title-").should be_true
end

it "removes non-ASCII characters" do
value_with_non_ascii = "Test Títle 123"
slug = Marten::Core::Sluggable.generate_slug(value_with_non_ascii, max_size)
slug.starts_with?("test-ttle-1-").should be_true
end

it "limits the slug length to max_size" do
slug.size.should eq(max_size)
end

it "does not exceed max_size even with long input" do
long_value = "This is a very long title that should be truncated"
slug = Marten::Core::Sluggable.generate_slug(long_value, max_size)
slug.size.should eq(max_size)
end

it "does not truncate the slug when max_size is large enough" do
long_value = "This is a very long title that should not be truncated"
slug = Marten::Core::Sluggable.generate_slug(long_value, 100)

slug.starts_with?("this-is-a-very-long-title-that-should-not-be-truncated").should be_true
end
end
end
8 changes: 0 additions & 8 deletions spec/marten/db/field/slug_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -111,14 +111,6 @@ describe Marten::DB::Field::Slug do

article.slug.not_nil!.should eq("custom-slug")
end

it "uses a custom slug generator function when provided" do
article = Marten::DB::Field::SlugSpec::ArticleWithCustomSlugGenerator.new(title: "My First Article")

article.save

article.slug.not_nil!.should eq("MY_FIRST_ARTICLE")
end
end

describe "#validate" do
Expand Down

This file was deleted.

31 changes: 31 additions & 0 deletions src/marten/core/sluggable.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
module Marten
module Core
# The Sluggable module provides functionality for generating URL-friendly slugs from strings.
module Sluggable
extend self

NON_ALPHANUMERIC_RE = /[^\w\s-]/
WHITESPACE_HYPHEN_RE = /[-\s]+/
NON_ASCII_RE = /[^\x00-\x7F]/

# Generates a slug from the given value, ensuring the resulting slug does not exceed the specified max_size.
#
# The slug is created by:
# 1. Removing non-alphanumeric characters (except whitespace and hyphens).
# 2. Converting the string to lowercase.
# 3. Replacing sequences of whitespace and hyphens with a single hyphen.
# 4. Removing non-ASCII characters.
# 5. Truncating the slug to fit within the max_size, minus the size of a randomly generated suffix.
# 6. Stripping trailing hyphens and underscores of the slug without suffix, and appending the suffix.
def generate_slug(value, max_size)
suffix = "-#{Random::Secure.hex(4)}"

slug = value.gsub(NON_ALPHANUMERIC_RE, "").downcase
slug = slug.gsub(WHITESPACE_HYPHEN_RE, "-")
slug = slug.gsub(NON_ASCII_RE, "")

slug[...(max_size - suffix.size)].strip("-_") + suffix
end
end
end
end
19 changes: 14 additions & 5 deletions src/marten/db/field/base.cr
Original file line number Diff line number Diff line change
Expand Up @@ -86,11 +86,8 @@ module Marten
def perform_validation(record : Model)
value = record.get_field_value(id)

if value.nil? && !@null
record.errors.add(id, null_error_message(record), type: :null)
elsif empty_value?(value) && !@blank
record.errors.add(id, blank_error_message(record), type: :blank)
end
validate_null(record, value)
validate_blank(record, value)

validate(record, value)
end
Expand Down Expand Up @@ -147,6 +144,18 @@ module Marten
def validate(record, value)
end

protected def validate_null(record : Model, value)
if value.nil? && !@null
record.errors.add(id, null_error_message(record), type: :null)
end
end

protected def validate_blank(record : Model, value)
if empty_value?(value) && !@blank
record.errors.add(id, blank_error_message(record), type: :blank)
end
end

# :nodoc:
macro check_definition(field_id, kwargs)
end
Expand Down
26 changes: 7 additions & 19 deletions src/marten/db/field/slug.cr
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,7 @@ module Marten
module Field
# Represents a slug field.
class Slug < String
NON_ALPHANUMERIC_RE = /[^\w\s-]/
WHITESPACE_HYPHEN_RE = /[-\s]+/

private getter slugify
private getter slugify_cb

def initialize(
@id : ::String,
Expand All @@ -21,13 +17,8 @@ module Marten
@unique = false,
@index = true,
@db_column = nil,
@slugify : Symbol? = nil,
@slugify_cb : (::String -> ::String) = ->(value : ::String) { generate_slug(value) }
@slugify : Symbol? = nil
)
if @slugify
@null = true
@blank = true
end
end

macro check_definition(field_id, kwargs)
Expand All @@ -36,9 +27,8 @@ module Marten

def validate(record, value)
if slugify?(value)
slug = slugify_cb.call(record.get_field_value(slugify.not_nil!).to_s)
slug = Core::Sluggable.generate_slug(record.get_field_value(slugify.not_nil!).to_s, max_size)
record.set_field_value(id, slug)
return
end

return if !value.is_a?(::String)
Expand All @@ -51,14 +41,12 @@ module Marten
end
end

private def generate_slug(value)
suffix = "-#{Random::Secure.hex(4)}"

slug = value.gsub(NON_ALPHANUMERIC_RE, "").downcase
slug = slug.gsub(WHITESPACE_HYPHEN_RE, "-").strip("-_")
slug = slug.gsub(/[^\x00-\x7F]/, "")
protected def validate_null(record : Model, value)
super if slugify.nil?
end

slug[...(max_size - suffix.size)] + suffix
protected def validate_blank(record : Model, value)
super if slugify.nil?
end

private def slugify?(value)
Expand Down

0 comments on commit c688ae5

Please sign in to comment.