We will use Rails to initialize the project demo-friendly-id:
# Create a new rails project
$ rails new demo-friendly-id
$ cd demo-friendly-id
# Generate controller and methods for our demonstration purposes
$ bin/rails g controller documents
$ bin/rails g model Document body:string title:string author:string
In the above commands, we also generated a basic model Document and a controller for those documents to illustrate the usage of Friendly ID.
First of all, let's demonstrate how things work without Friendly ID.
Updating routes
In order for our show route for a resource to work, we will need to enable that endpoint:
Rails.application.routes.draw do
resources :documents, only: [:show]
end
Creating some seed data
Update seeds from inside of the db/seeds.rb file to create four documents.
Document.create([
{title: "Star Wards", body: "Doing things", author: "Bill"},
{title: "Connect 4", body: "Cool article", author: "Bob"},
{title: "Star Wards", body: "Bob has an article with the same name as Bill!", author: "Bob"},
{title: "Friends From Afar", body: "Friends are forever", author: "Jane"}
])
Note that we are purposefully creating two documents titled Star Wards. This will demonstrate how we can enforce uniqueness with Friendly ID later when there are naming conflicts for our nominated ID key.
Update the controller
We now need to create our #show route handler in app/models/document.rb:
class DocumentsController < ApplicationController
def show
@document = Document.find(params[:id])
render json: @document
end
end
We will just return the resource as JSON for our demonstration.
Seeing how the app currently works
At this point, our code is ready to run a basic app.
Create and seed database, then start the Rails server:
# Database work
$ bin/rails db:create db:migrate db:seed
# Start the server
$ bin/rails s
With the server running, we can grab any of the resources with their corresponding ID. Since we are auto-incrementing, this will be 1, 2, 3 or 4.
At the moment, our URLs are not very user friendly if we were show this data on the frontend to clients. We can resolve this problem with Friendly ID.
Adding in Friendly ID
Add the following to your Gemfile:
gem 'friendly_id', '~> 5.4.0'
Run bundle in the command-line to install the gem.
Updating our model
We need to update our model to know what to reference for the slug in the model.
Update app/controllers/documents_controller.rb to the following:
class Document < ApplicationRecord
extend FriendlyId
friendly_id :slug_candidates, use: :slugged
private
# Try building a slug based on the following fields in
# increasing order of specificity.
def slug_candidates
[
:title,
%i[title author]
]
end
end
The slug_candidates method that we have written can be used to generate a slug if there is a collision. This helps us maintain control over what happens when we use something like the document name which is not unique.
Adding a slug to our document and generating the ID
Run the following to add a slug field to documents and migrate the changes.
# Required migration for field in DB
$ bin/rails g migration AddSlugToDocuments slug:uniq
$ bin/rails generate friendly_id
$ bin/rails db:migrate
Update the controller to use Friendly ID
Friendly ID adds the friendly property to our model which we can insert to handle the param we pass the slug or database ID value to.
class DocumentsController < ApplicationController
def show
@document = Document.friendly.find(params[:id])
render json: @document
end
end
At this point, let's reset and reseed the database in order to generate the slugs and store them in our database.
# Reset, migrate and re-seed to generate the slugs
$ bin/rails db:reset db:migrate db:seed
Testing our endpoint
Restart the server with bin/rails s
Now, both http GET localhost:3000/documents/1 and localhost:3000/documents/star-wards will return the first document.
$ curl http://localhost:3000/documents/1
{
"author": "Bill",
"body": "Doing things",
"created_at": "2022-03-12T00:28:48.391Z",
"id": 1,
"slug": "star-wards",
"title": "Star Wards",
"updated_at": "2022-03-12T00:28:48.391Z"
}
$ curl http://localhost:3000/documents/star-wards
{
"author": "Bill",
"body": "Doing things",
"created_at": "2022-03-12T00:28:48.391Z",
"id": 1,
"slug": "star-wards",
"title": "Star Wards",
"updated_at": "2022-03-12T00:28:48.391Z"
}
# An example of our colliding document name going to the #{title}-#{author} format
$ curl http://localhost:3000/documents/star-wards-bob
{
"author": "Bob",
"body": "Bob has an article with the same name as Bill!",
"created_at": "2022-03-12T00:28:48.403Z",
"id": 3,
"slug": "star-wards-bob",
"title": "Star Wards",
"updated_at": "2022-03-12T00:28:48.403Z"
}
Awesome!
Summary
Today's post demonstrated how to use the Friendly ID gem in order to generate friendly slugs for our URLs.