ruby 113 lines · 4 tabs

Auto-Generating URL Slugs in Rails with a before_validation Callback and Friendly Finder

Shared by codesnips Jul 2026
4 tabs
class AddSlugToArticles < ActiveRecord::Migration[7.1]
  def change
    add_column :articles, :slug, :string
    add_index :articles, :slug, unique: true

    reversible do |dir|
      dir.up do
        Article.reset_column_information
        Article.where(slug: nil).find_each do |article|
          article.send(:set_slug)
          article.update_column(:slug, article.slug)
        end
      end
    end
  end
end
4 files · ruby Explain with highlit

This snippet shows a self-contained approach to friendly URL slugs in Rails without pulling in the friendly_id gem, favoring a small, auditable concern instead. The pattern turns a human-readable title into a stable, URL-safe slug that is generated automatically, guaranteed unique, and usable directly in routes via to_param.

The add_slug.rb (migration) tab sets up the storage: a string column plus a unique index. The uniqueness constraint at the database level is the real safety net — application-level validation can race under concurrency, so the index is what ultimately prevents two records from sharing a slug. It is written as a partial-friendly plain unique index here for portability.

The Sluggable concern tab holds the logic. Including it wires up a before_validation callback, set_slug, that only fills the slug when it is blank or when the source attribute changed, so hand-edited slugs and existing rows are left alone. slug_candidate parameterizes the source text, and generate_unique_slug appends a short random suffix if the base slug already exists, looping until it finds a free value. This candidate-and-check loop is the same core idea friendly_id uses. The concern also overrides to_param so link_to article and article_path(article) emit the slug instead of the numeric id, and it centralizes the source column through slug_source_column so different models can slug off different fields.

The Article model tab shows how thin the model stays: it includes Sluggable, declares its source column, and validates presence and uniqueness of slug. The uniqueness validation mostly produces friendly errors; the DB index handles true races.

The ArticlesController tab demonstrates the finder side. Using find_by!(slug: params[:id]) means routes keep working with /articles/:id while resolving by slug, and find_by! raises RecordNotFound so Rails returns a proper 404. A key pitfall this design accepts is that changing a title changes the slug, which can break old links; teams that care about permalinks typically freeze the slug after first save or keep a history table. For most content this trade-off is acceptable and the implementation stays small and dependency-free.


Related snips

Share this code

Here's the card — post it anywhere.

Auto-Generating URL Slugs in Rails with a before_validation Callback and Friendly Finder — share card
Link copied