Twitter RSS

Simple Pagination with Rails

Tuesday, May 5th, 2009

While I was developing Motionspire, I needed to implement a pagination system for the video content.  I looked through the existing plugins, but found that I really desired something extremely simple and custom to work within my search controller.  With only a few lines of code, I created a very basic system for managing pagination that can be ported to other sites with ease.

The Search Controller

The Motionspire search controller uses one function and view for returning any content, whether it’s filtered or not, so the pagination only required one copy of the code to be implemented.  I keep a session variable for maintaining the user’s preference for items per page, and if that parameter has been sent to the search function I set the session variable :


if params[:itemsPerPage] then session[:itemsPerPage] = params[:itemsPerPage] end

When I run though the search function, I see if a page parameter has been passed:


if params[:page] then @page = params[:page].to_i else @page = 1 end

After I retrieve the articles pertaining to the search parameters (in this case the results are stored into an array called @videos), I check the :itemsPerPage preference and find the last possible page:


if session[:itemsPerPage] then @per = session[:itemsPerPage].to_i else @per = 24 end

@lastpage = @videos.length/(@per) + 1

if @videos.length%(@per) == 0

    @lastpage = @lastpage - 1

end

I have a default itemsPerPage value of 24 hardcoded, and you can change this for your search.

From here, we simply return the search results for that page, using the current page and the items per page preference:


@videos = @videos[((@page-1)*@per),@per]

For search queries within a large data set, I would recommend using these parameters with the sql query with the ‘ORDER BY’ and ‘LIMIT’ in order to avoid returning extremely large results unnecessarily.

The Helper

First, I created a short helper function that finds the min and max pages, ensuring we don’t show a negative page or a page that doesn’t exist:


def minmaxpage(current,total,option)
    min = current - 2
    if min < 1
        min = 1
        max = 1 + 4
        if max > total then max = total end
    end
    max = min + 4
    if max > total
        max = total
        min = max - 4
        while min < 1
            min = min + 1
        end
    end
    if option == 'min' then return min else return max end
end

The View

For the content from the view, we use the @videos array to present the content for the page.  The only custom coding necessary is in the links for the pages.  For this site, I provide a first and last page link, along with 2 pages on either side of the current page.  You can manipulate this code how you want, but the basic idea will remain the same.

First we grab the min and max page from the helper:


<% min = minmaxpage(@page, @lastpage, 'min') %>

<% max = minmaxpage(@page, @lastpage, 'max') %>

For the code for the page links, we simply check to see if the min page is within two of the current:


<% if min < @page %>

    <%= link_to( "Prev",

        :action => "search",

        :page => (@page - 1) ) %>

<% end %>

<% if min > 1 %>

    <%= link_to( "1",

        :action => "search",

        :page => 1 ) %>

<% end %>

<% x = min %>

Then we loop through the remaining pages, ensuring we don’t go over the max, and make the current page simple text instead of a link:


<% while x <= max %>

    <% if x.to_i == @page.to_i %>

        <span class="current"><%= x %></span>

    <% else %>

        <%= link_to( x.to_s,

            :action => "search",

            :page => x ) %>

    <% end %>

    <% x = x+1 %>

<% end %>

<% if max < @lastpage %>

    <%= link_to( @lastpage.to_s,

        :action => "search",

        :page => @lastpage ) %>

<% end %>

<% if max > @page %>

    <%= link_to( "Next",

        :action => "search",

        :page => (@page + 1) ) %>

<% end %>

The code may look a little long, but the implementation took very little time.  I know exactly what the code is doing and it is very easy to expand this for different situations and code.

There are a lot of solutions for pagination of content, but I found that this simple implementation helped me avoid using plugins and save time.  Let me know if you guys have any questions or comparable solutions.


Writing your own code vs. Plugins

Tuesday, September 30th, 2008

Recently I have been discussing with some fellow coders the benefits of plugins, and when you should write your own code.  I basically wanted to throw out a couple of ideas and hope for some discussion on the topic.  Let me know what you guys think and I’ll do a follow up soon.

Some Background

I, like many of you out there, started programming back in the day by messing around with computers, writing little calculator programs, making websites, etc.  But I really didn’t get into it much more until I started college, where I immediately started programming with C++.  

Most of my classes started out with projects where we had to write everything (no STL!), and all of my code was from scratch.  Once I started doing more of the “development” in “web design and development”, I still practiced the idea that the only code I know is good is the code I write.

But now I can see…

Then one day, the skies parted, trumpets sounded, and my designer Steve said “Hey, have you heard of Ruby on Rails?”  

I immediately started developing with nearly pure Rails, using the built-in-just-about-everything and loving the fact that anything I couldn’t code (or didn’t want to) was usually readily available in plugins ( I love file_column!).  

I will say this, RoR is good code.  And it’s constantly being improved.  For the most part, the plugins are also good code.  And many times, a plugin works perfectly into your project to save you time and add functionality.  

Good Programming

Ask any real programmer, and the efficiency of code is not only about how it performs, but how long it takes you to create.  So obviously there is a trade-off between using plugins and writing your own code.  

That being said, if you are a web designer who is using rails to help you out, or just trying something new, use the plugins: they are great and it will save you days of coding and headaches.  

If you consider yourself a developer (or hope to), you should still use plugins.  If you code everything, you’re going to waste time. However, don’t completely rely on them.  If you want to be a programmer, you need to know how things work, because some day there won’t be a plugin for you, and you’re going to need to know what to do.  

Also, plugins are meant to be helpful for a range of situations.  Therefore, there is going to be some overhead.  If you are concerned with performance, and a plugin isn’t “perfect” for what you’re trying to do; you might need to code from scratch (or at least be prepared to customize the plugin code).

Here’s the thing…

All things considered, I will say that my natural tendency is to code most everything myself.  I like to know where everything is, and exactly what the code is doing.  I also appreciate when the code is only doing exactly what I want it to do.  If it’s impractical to write something, by all means I will ( and do ) use plugins.  

If you want some good coding experience, and have the time, try writing something yourself that you wouldn’t normally do.  Even if you don’t end up using it, you’ll learn a bit more about RoR (or any other language) and you may just have some good code you can use again.

Agree? Disagree? Don’t care?

I’m just one guy.  Let me know what you guys think.


Remember Me’s with Rails

Friday, September 26th, 2008

I recently had a need for a login system that needed a ‘remember me’ function. After hours of looking through countless blogs, I came to the conclusion that either (1) people don’t use a remember me function with custom authentication systems for Rails, or (2) they don’t talk about it. In this article, I outline a simple remember me system using the cookie variable in Rails that will tack on to most custom authentication systems.

Using the cookie functions in Rails is pretty straightforward. It’s used with the ActionController and is quite simple to use. Most people use sessions for authentication, which is a good idea. Sessions, unlike cookies, automatically save your content as encrypted strings using the browsers cookies. ActionController#cookie provides a method for saving information in the browser, but you need to hash the content yourself if need be.

However, if a user selects the remember me option when logging in, we would like to have the session expiration set to be a longer period, like 30 days. Unfortunately, this is quite difficult to do if you don’t want to change the expiration of ALL sessions.

My site already uses sessions for authentication, and I’m going to leave that be. In fact, I’m not going to change anything about the session variable at all. This way, I can add this remember function to almost any authentication system I use in the future very easily.

When a user is authenticated and has selected the “remember me” option, I do two things:

- create a cookie that stores (plain text) the user’s id (you can use name, email, etc. but I prefer the id because it says nothing about the user to anyone trying to get information)

- create a second cookie with an hashed string of some other information about the user( name, email, address )

if params[:rememberMe]
userId = (@user.id).to_s
cookies[:remember_me_id] = { :value => userId, :expires => 30.days.from_now }
userCode = Digest::SHA1.hexdigest( @user.email )[4,18]
cookies[:remember_me_code] = { :value => userCode, :expires => 30.days.from_now }
end

For the hashing of the second piece of information, use a hash such as SHA1 or MD5. We can use these two cookies to authenticate a user when they return after a session has expired.

if ( cookies[:remember_me] and cookies[:remember_me] and User.find( cookies[:remember_me]) and Digest::SHA1.hexdigest( User.find( cookies[:remember_me] ).email )[4,18] == cookies[:remember_me_code]  )
@u = User.find( cookies[:remember_me_id] )
session['user'] = @u.id
end

Just work that into your :before_filter for your authentication system, and you’re all set. Make sure you delete the variables when someone logs out:

if cookies[:remember_me_id] then cookies.delete :remember_me_id end
if cookies[:remember_me_code] then cookies.delete :remember_me_code end

****Edit: make sure to have “require ‘digest/sha1′” at the top of any page where you are using the SHA1 hash.