FriendlyId and Kaminari in Rails application

Build Blog with Ruby on Rails (Part 7)
Luan Nguyen
Luan Nguyen
Oct 09, 2020 · 5 min read

What'll we learn:

  • Introduction to FriendlyId and Kaminari gem
  • How to use FriendlyId to make URL pretty and improve SEO (Search Engine Optimization)
  • How to use Kaminari to paginate Posts
  • Conclusion

@jenellehayes in Unsplash
______
This is Part 7 in Project: Build Blog with Ruby on Rails

______

Introduction

FriendlyId is a gem that allows us to replace id by a readable string in URLs:
# without FriendlyId 
http://localhost:3000/posts/75 

# with FriendlyId
http://localhost:3000/posts/pretty-urls-with-friendlyid

Slugs Concept
  • The concept of slugs is at the heart of FriendlyId.
  • A slug is the part of a URL which identifies a page using human-readable keywords, rather than a numeric ids. It makes your application more friendly both for users and search engine optimization (SEO).
  • FriendlyId lets you treat slugs like normal IDs, that mean you can find with slugs as you find with numeric ids:
Post.find(75)
Post.friendly.find("pretty-urls-with-friendlyid")


Installation

Step 1: Add gem to Gemfile
gem 'friendly_id', '~> 5.4.0'

Execute:
bundle install

Step 2: Add slug column to Post model
rails generate migration AddSlugToPosts slug:uniq

The migration file look like:
class AddSlugToPosts < ActiveRecord::Migration[6.0]
  def change
    add_column :posts, :slug, :string
    add_index :posts, :slug, unique: true
  end
end

Step 3: Generate the friendly configuration file and a new migration
rails generate friendly_id

Log in console:
Running via Spring preloader in process 7733
      create  db/migrate/20201004224108_create_friendly_id_slugs.rb
      create  config/initializers/friendly_id.rb

  • db/migrate/20201004224108_create_friendly_id_slugs.rb:  This migration is to create FriendlyIdSlugs table
class CreateFriendlyIdSlugs < MIGRATION_CLASS
  def change
    create_table :friendly_id_slugs do |t|
      t.string   :slug,           :null => false
      t.integer  :sluggable_id,   :null => false
      t.string   :sluggable_type, :limit => 50
      t.string   :scope
      t.datetime :created_at
    end
    add_index :friendly_id_slugs, [:sluggable_type, :sluggable_id]
    add_index :friendly_id_slugs, [:slug, :sluggable_type], length: { slug: 140, sluggable_type: 50 }
    add_index :friendly_id_slugs, [:slug, :sluggable_type, :scope], length: { slug: 70, sluggable_type: 50, scope: 70 }, unique: true
  end
end

  • config/initializers/friendly_id.rb: The configuration file and we keep all default.

Step 4: Run migration:
rails db:migrate


Usage

Add FriendlyId to Post model:
class Post < ActiveRecord::Base
  extend FriendlyId 
  friendly_id :title, use: [:slugged] 
end

Option is :use, which tells FriendlyId which addons it should use.  Currently, we use the slugged module. 
Beside, FriendlyId provides some addons as :history, :reserved, :scoped, and :simple_i18n.

Verify in console by create a new Post:
post = Post.create! title: "Pretty URLs With Friendlyid"
post.slug #=> "pretty-urls-with-friendlyid"

How it work?
  • FriendlyId auto-generate the slug corresponding with the title of Post
  • FriendlyId uses Active Support's paramaterize method to create slugs. This method will replaces special characters in a string so that it may be used as part of a 'pretty' URL.
Now, we can use friendly_id by replacing Post.find by Post.friendly.find
@post = Post.friendly.find("pretty-urls-with-friendlyid")
redirect_to @post # the URL will be /posts/pretty-urls-with-friendlyid

When to generate New Slugs

In FriendlyId 5.0, slugs are only generated when the slug field is nil. If you want a slug to be regenerated, set the slug field to nil:
post = Post.friendly.find("pretty-urls-with-friendlyid")
post.title = "New Title"
post.save!
post.slug #=> "pretty-urls-with-friendlyid"

post.slug = nil
post.save!
post.slug = "new-title"

Beside, we can control exactly when new friendly ids are set by overriding the method #should_generate_new_friendly_id? 
To re-generate new slug when we change the title of Post, we do as below:
class Post < ActiveRecord::Base
  extend FriendlyId
  friendly_id :title, use: [:slugged]

  def should_generate_new_friendly_id?
    title_changed? || super
  end
end


______

Pagination for Posts

  • We can easily implement pagination in Rails with the kaminari gem.
  • Kaminari is a scope & engine based, clean, powerful, customizable and sophisticated paginator for modern web app frameworks and ORMs.
  • Kaminari gem easy to use: we just bundle the gem, then your models are ready to be paginated. No configuration required. Don’t have to define anything in your models or helpers.

Installation

To install kaminari to our application, put this line in your Gemfile:
gem 'kaminari'

Then run bundle:
bundle install

Usage

In Home controller:
Add pagination for list Posts like this:
@posts = Post.order(created_at: :desc).page(params[:page]).per(6)

  • The page scope: To fetch the (n)th page of Post.
Post.page(2) 
=> It fetch the 2nd page of users

Note: Pagination starts at page 1, not at page 0 , page(0) will return the same results as page(1).

  • The per scope: The number of posts per each page, default per_page is 25.
Post.page(2).per(6)
=> It return page 2nd with 6 posts.

In Views:
Call the paginate helper:
<%= paginate @users %>

This will render several ?page=N pagination links surrounded by an HTML5 <nav> tag. This would output several pagination links such as:

« First ‹ Prev ... 2 3 4 5 6 7 8 9 10 ... Next › Last »

Add paginate helper to view:
# app/views/home/index.html.erb
<div class="container">
  <div class="row">
    <% @posts.each do |post| %>
      <section class="col-md-4"> ... </section>
    <% end %>
  </div>
  <%= paginate @posts %>
</div>

Go to the Home page and reload the page, see what happen (don’t forget to add more than 6 posts, because we config per_page is 6).

Scroll to the bottom of the page, we’ll see the pagination links like this:
Pagination in Homepage


You can see the pagination links: 1 2 Next ›Last »
The UI of pagination links is not pretty, so we add some CSS to make it better:
Pagination link
.pagination{
    span{
      padding-right: 10px;
    }
  }
}

Yey, done!!

Conclusion

In this article, I already introduce to you 2 useful gems are FriendlyId and Kaminari. And guide you step by step to use FriendlyId gem to make URL pretty and Kaminari to paginate Post in Homepage.