Permalinks must unambiguously identify a resource. However, they should also be “speaking”. This is important for SEO. In Ruby on Rails, the part of a URL that identifies a resource generally is just the ID of the database object. For SEO relevant resources, it makes sense to generate Slugs.
An obvious solution might be to include the Gem FriendlyID. It is very powerful. A little bit too extensive for many use cases. Especially since creating a proper implementation is very simple.
To guarantee the uniqueness of a Slug, it should contain the database ID of the object.
The following example is based on this approach.
The model
Blog posts are classic example for Slugs. Articles have a title and a text:
rails g model Article title:string content:text
In Ruby on Rails, the routes helper generate the URL from objects. They use the method #to_param. The implementation for ActiveRecord::Base#to_param returns the ID of the object.
For generating the Slug, this method has to be overwritten:
# app/models/article.rb
class Article < ApplicationRecord
def to_param
"#{id}-#{title.parameterize}"
end
end
For the meaningful part of the Slug, the title is used in this example. String#parameterize replaces all spaces with a hyphen -, which is quite common.
The routes
The routes:
# config/routes.rb
Rails.application.routes.draw do
resources :articles
end
The result can also be checked in the REPL (Rails console):
# Rails console
app.article_path Article.last
# => "/article/79-custom-validator-testen"
The controller
It is the advantage with this approach is that the controller looks exactly as if only the ID were passed as a parameter:
# app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
def show
@article = Article.find params[:id]
end
end
This works because Ruby String#to_i only casts all numeric characters (from the Strings beginning to the first non-numeric character):
"79-custom-validator-testen".to_i
# => 79
Therefore it is not even necessary to persist the Slug.
However if there are reasons for persisting the Slug:
rails g migration add_slug_to_articles slug:string
then only the speaking part of the Slug (without the database ID) should be stored in any case:
# app/models/article.rb
class Article < ApplicationRecord
before_create :set_slug
def to_param
"#{id}-#{slug}"
end
private
def set_slug
self.slug = title.parameterize
end
end