Today we'll go over how to add pagination to your Rails app using URL parameters and no external gems.
Creating our files
To get started we need to create our controller and set up our root route, for convenience I will use rails generator and execute rails g controller Home index
in my terminal. Feel free to use another name for your controller, instead of Home. Rails will generate a few files for you and provide a detailed summary of where they can be found
create app/controllers/home_controller.rb
route get 'home/index'
invoke erb
create app/views/home
create app/views/home/index.html.erb
invoke test_unit
create test/controllers/home_controller_test.rb
invoke helper
create app/helpers/home_helper.rb
invoke test_unit
invoke assets
invoke scss
create app/assets/stylesheets/home.scss
Optional: I will set my home#index as my root path by going to config > routes.rb
and replacing get 'home/index'
with root 'home#index'
Setting up our Controller
For demonstration purposes, I am using a class method #database instead of using an actual database.
class HomeController < ApplicationController
# number of results to be shown per page
@@results_per_page = 2
def index
# params will be used in views for input and links
@params = params.permit(:query, :page)
# current page will be 1 by deault unless
# user clicks on another page
@current_page = params[:page].try(:to_i) || 1
# create a fetch request to database and retrieve the results
@results, @pages_count = database(@params, @current_page)
end
# sample database
def database(params, current_page)
# dummy data
data = [ 'apple', 'cake', 'candy apple', 'car',
'friend', 'run', 'hunt', 'ship', 'tip'
]
# filter data using provided parameters. You can filter the data
# with as many parameters as you'd like
results = data.filter{|i| i.include? params[:query].to_s}
# total number of pages that should be displayed
pages_count = (results.length/@@results_per_page.to_f).ceil
# results to display based on current page and result limit
start = @@results_per_page * (current_page - 1)
results = results[start, @@results_per_page]
[results, pages_count]
end
end
Here is what your code could look like if you were using Active Record. Please note this will only return the results for the page selected. You may need to create a separate call to your database which will return the total number of results, which is needed to calculate the number of page items that need to be created.
SampleModel.where("name LIKE ?", "%#{query}%")
.limit(results_per_page)
.offset(results_per_page * (current_page-1))
Our Views
We'll break down the views in three steps. Our search field, pagination items, and result items.
1. Search field
At the top we will have a form to allow users to search for item by name. The form will be submitted to our root_path
. For our search field I've added JavaScript to our onkeydown
attribute. If the search field is empty, it will not have a name assigned. This will prevent our URL from having a query
parameter, and make things a bit cleaner.
*I've added inline javascript for demonstration purposes. But adding javascript to your javascript
folder is better. Or you could add an event listener to iterate through the form inputs before submitting.
<%= form_with url: root_path, method: :get do %>
<input onkeydown="if(this.value == '') {this.name = 'query'} else {this.name = 'query'}" value=<%="#{@params[:query]}"%>>
<%= submit_tag "Search", name:nil %>
<%end%>
2. Pagination Items
The pagination items will be wrapped in an unordered list. When making each item, we will update the params page value to match the corresponding item. And the rest of the parameters will be passed along as well. A .active
class is used to add styling to pagination item if it matches the @current_page
<ul>
<%[*1..@pages_count].each do |page|%>
<%@params[:page] = page%>
<li class=<%="#{@current_page == page ? 'active' : ''}"%>>
<%= link_to page, root_path(@params)%>
</li>
<%end%>
</ul>
3. Result items
We will iterate through all the results and display on the page.
<%@results.each do |result|%>
<div class='results'>
<%=result%>
</div>
<%end%>
This is how your final result should look like. I've wrapped all my code in a div
element.
<div class='demo'>
<%= form_with url: root_path, method: :get do %>
<input onkeydown="if(this.value == '') {this.name = 'query'} else {this.name = 'query'}" value=<%="#{@params[:query]}"%>>
<%= submit_tag "Search", name:nil %>
<%end%>
<ul>
<%[*1..@pages_count].each do |page|%>
<%@params[:page] = page%>
<li class=<%="#{@current_page == page ? 'active' : ''}"%>>
<%= link_to page, root_path(@params)%>
</li>
<%end%>
</ul>
<%@results.each do |result|%>
<div class='results'>
<%=result%>
</div>
<%end%>
</div>
Here is the styling used in this demo, incase you want to follow along.
.demo {
margin: 80px auto 0;
width: 400px;
display: flex;
flex-direction: column;
input:first-of-type {
width: 200px;
padding: 5px 10px;
border-radius: 10px;
outline: none;
border: 1px solid black;
}
input:last-of-type {
margin-left: 5px;
}
ul {
display: flex;
margin-bottom: 20px;
margin-top: 30px;
// width: 100%;
li {
display: flex;
justify-content: center;
border: 1px solid rgb(194, 194, 194);
font-size: 15px;
border-radius: 8px;
padding: 5px 10px;
margin-right: 20px;
&.active {
background: cornflowerblue;
a {
color: white;
font-weight: bold;
}
}
}
}
}
.results {
border-radius: 8px;
box-shadow: 0 0px 2.5px rgba(0, 0, 0, 0.05), 0 0px 20px rgba(0, 0, 0, 0.1);
font-size: 20px;
padding: 5px;
margin-bottom: 5px;
padding-left: 20px;
}
Top comments (0)