-
Notifications
You must be signed in to change notification settings - Fork 0
22 Pagination
Now that we have posts presented in a predictable order, it's time to think about pagination. We solved the problem of presenting more categories than will fit on a page. It's time to do the same for posts.
There are a couple of popular gems for Active Record pagination. Let's use will_paginate
, because it's absurdly easy to get up and running. Go ahead and add it to your Gemfile
, and rerun Bundler.
Gemfile
gem 'will_paginate'
$ bundle
Don't forget to restart your server after updating the bundle.
To paginate the posts on pages/index is a piece of cake. We just chain page
onto our Active Record query and pass it params[:page]
.
app/controllers/posts_controller.rb
def index
@posts = Post.page(params[:page])
end
Then in our view (partial, in this case), just add call the helper method will_paginate
and pass it our collection of posts. We'll add it to the very end of the partial.
_app/views/posts/list.html.erb
<%= will_paginate posts %>
That's all there is to it. If you aren't seeing your posts broken up into pages, you can add a line like this to the Post
model temporarily in order to see the effect.
app/models/post.rb
self.per_page = 4
If you look at a specific category, you'll see that it does not yet work. We need to edit our CategoriesController
first. We'll store posts in their own instance variable so we can chain the page
method. Because @category.posts
is already loaded, this shouldn't cost us any extra database queries.
app/controllers/categories_controller.rb
def show
@category = Category.includes(:posts).find(params[:id])
@posts = @category.posts.page(params[:page])
end
That's not as effecient as it could be, however. Since we're paginating only after the posts have been retrieved from the database, we're loading way more records than we need. We could be loading a million posts (this app is destined for greatness, after all), only to display the first 20.
Let's remove the includes
when loading categories. It is worth the extra query to avoid potentially loading so much extra data.
def show
@category = Category.find(params[:id])
@posts = @category.posts.page(params[:page])
end
In categories/show, we need to pass @posts
to the posts/list partial, rather than passing @category.posts
, because the latter is not paginated.
app/views/categories/show.html.erb
<%= render 'posts/list', posts: @posts %>
Let's make those pagination links a little prettier. We'll use the Digg-style pagination, converted to SCSS from the styles at http://mislav.uniqpath.com/will_paginate/.
app/assets/stylesheets/style.scss
.pagination {
background: white;
cursor: default;
a, span, em {
padding: 0.2em 0.5em;
display: block;
float: left;
margin-right: 1px;
}
.disabled {
color: #999999;
border: 1px solid #dddddd;
}
.current {
font-style: normal;
font-weight: bold;
background: #2e6ab1;
color: white;
border: 1px solid #2e6ab1;
}
a {
text-decoration: none;
color: #105cb6;
border: 1px solid #9aafe5;
&:hover, &:focus {
color: #000033;
border-color: #000033;
}
}
.page_info {
background: #2e6ab1;
color: white;
padding: 0.4em 0.6em;
width: 22em;
margin-bottom: 0.3em;
text-align: center;
b {
color: #000033;
background: #2e6ab1 + 60;
padding: 0.1em 0.25em;
}
}
/* self-clearing method: */
&:after {
content: ".";
display: block;
height: 0;
clear: both;
visibility: hidden;
}
* html & {
height: 1%;
}
*:first-child+html & {
overflow: hidden;
}
}
Does everything check out? Let's commit.
$ git add .
$ git commit -m "Paginate posts."