SEO and user friendly URLs for Rails with FriendlyID

Rails by default uses a numeric id as a parameter for its URL. Sometimes when building a website such as this blog, instead of emmanuelcorrales.com/blog/4 we want the url to be more SEO and user friendly like emmanuelcorrales.com/blog/seo-and-user-friendly-urls-for-rails-with-friendlyid. We can achieve this by using the FriendlyID gem. In this tutorial I'll show you how to use FriendlyID.

Blog app

I'll demonstrate how to use FriendlyID gem by creating a simple blog app. You can download the source code here.

Create a new rails project by executing the command below.

rails new blog

Add this line to the Gemfile.

# Gemfile
gem 'friendly_id', '~> 5.1.0'

Install friendly_id by executing the commands below.

bundle install

Run the command below to generate a migration that will create a table for the slugs.

rails generate friendly_id

Generate a scaffold for our post with attributes "title", "description" and "slug".

rails generate scaffold post title:string description:text slug:string:uniq
rake db:migrate

Modify your Post model to look like the code below.

# app/models/post.rb
class Post < ApplicationRecord
  extend FriendlyId
  friendly_id :title, use: :slugged
end

On your PostController.rb instead of accessing the post object like the code below.

# app/controllers/post_controller.rb
def set_post
  @post = Post.find(params[:id])
end

Access it using the friendly method like the code below.

# app/controllers/post_controller.rb
def set_post
  @post = Post.friendly.find(params[:id])
end

The app is finished. Run the app.

rails serve

Go to http://localhost:3000/posts You can now set your custom slug whenever you edit your post.


Automatically update the slug based on the title

Personally I want the slug to be automatically updated based on the title so whenever I edit the title, I don't have to manually edit the slug. The app already uses the title to generate the slug when creating a new post but updating it doesn't change the slug.

We need to omit the block of code from the partial form where the slug is edited because its sets the value of the slug. The slug must be nil for it to be regenerated.

<%-# Remove this block of code from app/views/posts/_form.html.erb -%>
<div class="field">
   <%= f.label :slug %>
   <%= f.text_field :slug %>
</div>

The partial form with the omitted code should be similar to the code below.

<%-# app/views/posts/_form.html.erb -%>
<%= form_for(post) do |f| %>
  <% if post.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(post.errors.count, "error") %> prohibited this post from being saved:</h2>

      <ul>
      <% post.errors.full_messages.each do |message| %>
        <li><%= message %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= f.label :title %>
    <%= f.text_field :title %>
  </div>

  <div class="field">
    <%= f.label :content %>
    <%= f.text_area :content %>
  </div>

  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>

Set the post's slug as nil before it is updated to generate the new slug on update.

def update
    @post.slug = nil
    @post.update(post_params)
end

Now whenever you edit the title of the post the slug will change.