Railsmagazine60x60 Previous and Next Buttons

by James Schorr

Issue: Vol 2, Issue 1 - All Stuff, No Fluff

published in June 2010

James schorrx3

James Schorr has been in IT for over 13 years and been developing software for over 10 years. He is the owner of an IT consulting company, Tech Rescue, LLC (http://www.techrescue.us/) , which he started along with his lovely wife, Tara, in 2002. They live in Concord, NC with their three children - Jacob, Theresa and Elizabeth.

James spends a lot of time writing code in quite a few languages and has a passion for Ruby on Rails.

He loves to play chess online at FICS (his handle is kerudzo) and take his family on nature hikes.

His professional profile can be found on Linked In and you can read more of his writings on his blog (techrescue.wordpress.com).

Navigating through records can be a little tricky, especially when a customer desires Previous and Next buttons. In our example here, we have a membership application, in which the customer desires to navigate from member to member with a Previous and Next button. However, this code can be used for any kind of list in which easy navigation is desired. At first glance, the issue may seem simple to resolve:

Controller Code

(app/controllers/users_controller.rb, show action):

def show

  @user = User.find(params[:id])

  @all_users = User.find(:all, :select => “id”)

  @previous_user = @all_users.select{|m| m.id =

    @current_user.id + 1 }

  @next_user = @all_users.select{|m| m.id =

    @current_user.id -1 }

end

View Code

(app/views/users/show.html.erb):

<%= link_to("Previous &larr;", @previous_user %>

<%= link_to("Next &rarr;", @next_user %>

However, there are a couple of problems with this approach! As users are deleted and added, their ID will not be sequential. In this list, no records have been deleted yet, so it appears that all will work fine:

Issue #1

id first_name last_name
1  Joe Smith
2 Betty Thompson
3 Anil Narayan
4 Jeff Davis
5 Violet Alexander

Now, if Jeff’s record is deleted, Anil’s Next link and Violet’s Previous link will be invalid, and lead to the dreaded 404 “Page Not Found” error. Let’s take a look at what happens when a new record is added after deleting Jeff’s; notice how Billy Bob’s record is assigned the next sequential ID, rather than filling in the “hole” left by Jeff’s deleted record:

id first_name last_name
1 Joe Smith
2  Betty  Thompson
3
 Anil  Narayan
5
 Violet  Alexander
6  Billy Bob  Bake

Issue #2

Also, what about Joe Smith’s Previous and Billy Bob’s Next buttons? You could find the maximum and minimum IDs and prevent those links from showing, but this is messy. Wouldn’t it be nice to have Joe’s Previous button and Billy Bob’s Next button point to each other’s records, essentially “wrapping-around” the list?  

Solution

Here is the simple solution to both issues:

Controller Code

(app/controllers/users_controller.rb):

def show

  @user = User.find(params[:id])

  # collecting users to provide Previous & Next

  # links, logic is in place to work in situations

  # where users have the same last and/or

  # first names; login is unique

  @all_users_array = User.find(:all,

    :select => "id,last_name,first_name,login",

    :order =>

      "last_name, first_name, login").collect(&:id)

  @curr_users_index =

    @all_users_array.index(user.id)

  # this defines our starting point

  # from which to base the Previous and Next links

  @previous_user =

    @all_users_array[@curr_users_index - 1]

  @next_user =

    @all_users_array[@curr_users_index + 1]

  @first_users_id = @all_users_array.first

  @last_users_id = @all_users_array.last

end

View Code

(app/views/users/show.html.erb):

Previous Link:

<% @user.id == @first_users_id ?

  @previous = @last_users_id.to_s :

  @previous = @previous_user.to_s %>

<%= link_to("&larr Previous", previous) %>

Next Link:

<% @user.id == @last_users_id ?

  @next = @first_users_id.to_s :

  @next = @next_user.to_s %>

<%= link_to("Next &rarr;", next) %>

In the view-side code, we are checking to see if the user is the first or last user in the array and, if so, causing his link to point to the other "side" of the array. This wrap-around resolves Issue #2.

Remaining Issue

Due to the nature of web applications, the remaining weakness is that an administrator may delete a record after a visitor has loaded a page. Since the array is generated upon load, this could result in a broken link. Thus, we need to modify our "show" action slightly:

def show

  @user = User.find(params[:id])

  if @user == nil

    redirect_to :back

    # this will force the array

    # to reload, "refreshing" the links

  end

  # collecting users to provide Previous & Next

  # links, logic is in place to work in situations

  # where users have the same last and/or

  # first names; login is unique

  @all_users_array = User.find(:all,

    :select => "id,last_name,first_name,login",

    :order => "last_name, first_name, login").

    collect(&:id)

  @curr_users_index =

    @all_users_array.index(user.id)

  # this defines our starting point

  # from which to base the Previous and Next links

  @previous_user =

    @all_users_array[@curr_users_index - 1]

  @next_user =

    @all_users_array[@curr_users_index + 1]

  @first_users_id = @all_users_array.first

  @last_users_id = @all_users_array.last

end

I hope that you found this article to be helpful.