DEV Community

Jonathan Yeong
Jonathan Yeong

Posted on • Originally published at jonathanyeong.com on

Making a custom kebab case method for pretty URLs

Recently, I was working on a Rails app where I wanted to make some pretty URLs for it. I have a route where you can view a Dog resource GET /dog/1. Having an ID in the URL is not what I want. People could increment the ID to potentially see other dogs. But more importantly, it's not personal! I wanted the URL to show your dog name instead, like /dog/albie or /dog/st-george-bernard-the-3rd. I needed a way to make pretty kebab case URLs.

Pretty kebab case URLs

To get a URL that's not the default id, I added the to_param method to my model. Where slug was a column on my Model:

def to_param
  slug
end

Enter fullscreen mode Exit fullscreen mode

Since I wanted the slug to be auto-generated, I had to transform the name column to kebab case (see below). Much to my surprise, there wasn't a Ruby / Rails kebabcase method:

class Dog < ApplicationRecord
  before_create ->(dog) { dog.slug = dog.name.kebabcase } # .kebabcase doesn't exist

  def to_param
    slug
  end
end

Enter fullscreen mode Exit fullscreen mode

Looking at the docs, I found .underscore and .dasherize. Unfortunately, they only work for camel case names:

"DogName".underscore.dasherize
# => "dog-name"

Enter fullscreen mode Exit fullscreen mode

But who names their dog in camel case? Maybe people do, but I'm assuming most people use spaces. Unfortunately, .underscore only works on camel case strings.

"Dog Name".underscore.dasherize
# => "dog name"

Enter fullscreen mode Exit fullscreen mode

Searching for a Gem

If Ruby and Rails didn't have a kebab case method. I'm sure there's a gem out there that does. In comes Strings::Case to the rescue:

strings.kebabcase("PostgreSQL adapter")
# => "postgre-sql-adapter"

Enter fullscreen mode Exit fullscreen mode

I could've called it a day here, but looking at the gem, it has so many other methods that I wouldn't use. Honestly, it seems like overkill for my tiny side project. It's also another library I would have to maintain and make security updates to. In the end, I decided to roll a custom kebab case method.

Rolling a custom kebab case method

Before jumping into a solution, I wanted some inspiration from Strings::Case.

Under the hood, the gem scans through a string, building words determined by whether there is a specific delimiter or if there's an uppercase. "DogName" would be two words "Dog" and "Name" since there's an upper case in the middle. And "Dog Name" would be two words, since there's a space in the middle. These words get put into an array, and after the string has been scanned, a .join method is called with a separator. In kebab case, the separator would be -. I'm oversimplifying this approach. If you're curious, about the source code, you can view it here.

For the gem, this process makes total sense. It handles many use cases as well as includes multiple string manipulation functions. Since I only wanted kebab case, I decided to go with a regex approach:

dog.name.downcase.gsub(/[^\w\d\s]/, "").gsub(/\s/u, "-")

Enter fullscreen mode Exit fullscreen mode

Let's break down what this is doing:

  1. .downcase - A built-in function that makes the string lowercase.
  2. .gsub(/[^\w\d\s]/, "") - Regex that matches anything that's not a character, digit or space, i.e. any special characters and removes it.
  3. .gsub(/\s/u, "-") - Regex that matches a space and replaces with a dash
"St. George Bernard the 3rd".downcase.gsub(/[^\w\d\s]/, "").gsub(/\s/u, "-")
# => st-george-bernard-the-3rd

Enter fullscreen mode Exit fullscreen mode

The biggest limitation with my approach is the regex. I make plenty of assumptions with what I think names should be, and it's focused primarily on English. But what if someone uses a character language like Japanese?

"餅".downcase.gsub(/[^\w\d\s]/, "").gsub(/\s/u, "-")
# => ""

Enter fullscreen mode Exit fullscreen mode

If I wanted to make this more robust, I should handle non-English characters properly. If I can, I could transliterate it. Like é becomes e. If it's in a character language, I could leave it because non-English characters can be used in URLs.

However, for a small side project, what I have is enough. And I didn't need to pull in a new gem to achieve it.


Using Ruby 3.2.0 and Rails 7.1.3.3 at time of writing

Top comments (0)