Implementing Friendly URLs with UUID-Based Models

Ahmed NadarAhmed Nadar
4 min read

I want to share a practical solution to a common challenge in Rails development I face it with almost every application I work with: combining UUID-based models with user-friendly URLs. If you've ever wondered how to maintain the security benefits of UUIDs while having clean, SEO-friendly URLs, you're in the right place!

The Challenge

Imagine you have a Rails app using UUIDs for your models (great choice for security and scalability!), but your URLs look like this:

https://yourapp.com/products/123e4567-e89b-12d3-a456-426614174000

Wouldn't it be nicer to have URLs like this?

https://yourapp.com/products/awesome-product-name

Let's make it happen! I'll walk you through the process step by step.

Prerequisites

Before we dive in, make sure you have:

  • A Rails application (I'm using Rails 7, but this works with Rails 5+ too)

  • PostgreSQL database (recommended for UUID support)

  • Models using UUID as primary keys

**

Step 1: Setting up your environment
**

First, let's add the friendly_id gem. It's a mature, well-maintained solution that makes our lives much easier, thanks Norman!

  • Add this to your Gemfile:
gem "friendly_id", "~> 5.5.0"  # Latest stable version as of 2024
  • Run these commands in your terminal:
bundle install
rails generate friendly_id  # Creates the friendly_id migration
rails db:migrate            # Sets up the friendly_id_slugs table
  • Now, let's add a slug column to your model. For example, if you have a Product model:
rails generate migration AddSlugToProducts slug:string:uniq
rails db:migrate

**

Step 2: Configuring our model
**

Here's where the magic happens. Lets see how to set up your model with different slug strategies:

class Product < ApplicationRecord
  # Step 1: Enable friendly_id
  extend FriendlyId

  # Step 2: Configure slug generation
  friendly_id :slug_candidates, use: [:slugged, :finders]

  private

  # Step 3: Define your slug candidates
  def slug_candidates
    [
      :name,                          # First try: just the name
      [:name, :category],             # If that's taken: name-category
      [:name, :category, :created_at] # Last resort: name-category-timestamp
    ]
  end

  # Step 4: Control when slugs should be regenerated
  def should_generate_new_friendly_id?
    name_changed? || category_changed? || super
  end
end

Let's break down what's happening here:

  1. extend FriendlyId adds the friendly_id functionality to your model

  2. :slugged enables slug generation

  3. :finders allows you to use find(params[:id]) with both slugs and UUIDs

  4. slug_candidates provides fallback options if your first choice is taken

**

Step 3: The magic rake task**

For a quick slug generation for existing records I used use rails console. Quick but, it is not the Rails way.
Here's a super helpful rake task I've created to manage your slugs. Create lib/tasks/friendly_id.rake:

namespace :friendly_id do
  desc 'Generate slugs for all your models'
  task generate_slugs: :environment do
    # Let's be informative about what we're doing
    puts "๐Ÿš€ Starting slug generation..."

    # Replace Product with your model name
    Product.find_each do |record|
      print "Processing #{record.name}... "

      # Clear existing slug to force regeneration
      record.slug = nil
      if record.save(validate: false)
        puts "โœ… Created slug: #{record.slug}"
      else
        puts "โŒ Failed"
      end
    end

    puts "\nโœจ All done! Your URLs are now user-friendly!"
  end

  desc 'Check for any records missing slugs'
  task check_slugs: :environment do
    puts "๐Ÿ” Checking for records without slugs..."

    records_without_slugs = Product.where(slug: nil)
    if records_without_slugs.any?
      puts "Found #{records_without_slugs.count} records needing slugs:"
      records_without_slugs.each do |record|
        puts "- #{record.name} (ID: #{record.id})"
      end
    else
      puts "๐Ÿ‘ All records have slugs! You're good to go!"
    end
  end
end

Run these tasks in your terminal:

rake friendly_id:generate_slugs
rake friendly_id:check_slugs

Step 4: Implementation in your controllers

Update your controller to use friendly_id:

class ProductsController < ApplicationController
  def show
    # This will work with both slugs and UUIDs!
    @product = Product.friendly.find(params[:id])
  end
end

๐ŸŒŸ Keep in mind

  1. UUID compatibility

    • Your UUIDs are still there, working behind the scenes

    • Database relations still use UUIDs

    • URLs just look nicer now!

  2. URL generation

  3.  # In your views, nothing changes!
     <%= link_to product.name, product_path(product) %>
    
  4. Handling changes

    • Slugs automatically update when the source fields change

    • Old slugs can be preserved using the :history module

    • You can customize when slugs regenerate

  5. Troubleshooting
    Still seeing UUIDs in your URLs? Try these steps:

    1. Clear your browser cache

    2. Restart your Rails server

    3. Run rails friendly_id:generate_slugs

    4. Check your controller uses friendly.find

Common gotchas I stumbled upon and solutions

  1. Duplicate slugs

  2.  # Add a sequence for duplicates
     friendly_id :name, use: [:slugged, :sequence]
    
  3. Special characters

    • friendly_id handles most special characters well

    • You can customize with your own normalizer

  4. Performance

    • Slug lookups are indexed

    • UUID benefits remain for relationships

    • Best of both worlds! ๐ŸŽ‰

**

Wrapping up**

You now have user-friendly URLs without sacrificing the benefits of UUIDs! Your URLs are:

  • SEO-friendly โœ…

  • Human-readable โœ…

  • Secure (UUIDs still used internally) โœ…

  • Easy to maintain โœ…

Have questions or run into issues? Drop a comment below! I'd love to help you implement this in your Rails app.

Happy coding! ๐Ÿš€ ๐Ÿ’ป

0
Subscribe to my newsletter

Read articles from Ahmed Nadar directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Ahmed Nadar
Ahmed Nadar

Developer and Product Design. RapidRails UI components creator Run RapidRails Agency. https://rapidrails.cc