Introduction

When dealing with tables of data, sometimes you might want to make the columns sortable for easier viewing. Luckily we can easily do this in our Ruby on Rails application. Let's get started.

Rails Application Setup

For this tutorial, we will create a simple Product model, which will contain 3 fields, id, name, and price. To create this model, run the command listed below.

Terminal Commands:

rails g model Product name price:decimal{12,3}

Now run a rake db:migrate to create the table.

Terminal Commands:

rake db:migrate

Now let's add some seed data that will give us something to work with. Open up your db/seeds.rb file and modify it so it looks like the code listed below.

db/seeds.rb:

Product.delete_all
Product.create!(id: 1, name: "Banana", price: "1.00")
Product.create!(id: 2, name: "Apple", price: "1.50")
Product.create!(id: 3, name: "Grape", price: "0.10")
Product.create!(id: 4, name: "Strawberry", price: "0.30")
Product.create!(id: 5, name: "Blueberry", price: "0.50")
Product.create!(id: 6, name: "Raspberry", price: "0.33")

Now run a rake db:seed to install the seed data.

Terminal Commands:

rake db:seed

Great, now let's create a products controller with an index view that will contain our list of products. Run the command below to create this now.

Terminal Commands:

rails g controller Products index

Now open up your routes file and modify it so that it looks like the code listed below.

config/routes.rb:

Rails.application.routes.draw do
  resources :products, only: [:index]
  root to: "products#index"
end

Next, we need to add Bootstrap to our project to pretty things up. Doing this gives us access to Bootstrap's icon library, which gives us icons to indicate sort direction. To add Bootstrap, open up your application layout and modify it so that it looks like the code listed below.

app/views/layouts/application.html.erb:

<!DOCTYPE html>
<html>
<head>
  <title>SortableTableColumns</title>
  <%= stylesheet_link_tag    '//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css', media: 'all', 'data-turbolinks-track' => true %>
  <%= javascript_include_tag '//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js', 'data-turbolinks-track' => true %>
  <%= stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track' => true %>
  <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
  <%= csrf_meta_tags %>
</head>
<body>

<%= yield %>

</body>
</html>

Now that we have a basic foundation for our application we can work on displaying and sorting products. To do this we will need to add a helper method as well as some controller code. Let's work on our controller first. Open up your products controller and modify it so that it looks like the code listed below.

app/controllers/products_controller.rb:

class ProductsController < ApplicationController
  helper_method :sort_column, :sort_direction

  def index
    @products = Product.order("#{sort_column} #{sort_direction}")
  end
private
  def sortable_columns
    ["name", "price"]
  end

  def sort_column
    sortable_columns.include?(params[:column]) ? params[:column] : "name"
  end

  def sort_direction
    %w[asc desc].include?(params[:direction]) ? params[:direction] : "asc"
  end
end

So what's going on here? First, we expose the sort_column and sort_direction methods to our helpers. This allows us to utilize them outside the controller. Next, the index method orders by our current sort column (specified by the sort_column method) and direction (specified by the sort_direction method). If you take a look at the sort_column method you'll see that it checks the column names against a white list of column names. This prevents the user from sorting by columns we may not wish to have them sort by, id for instance. The sort direction method checks to make sure the direction is either asc or desc. Both methods return sensible defaults otherwise. This helps keep the user from doing unexpected things with your data.

Now let's create a helper method that will be used to generate the actual sort links. Open up your products helper and modify it so it looks like the code listed below.

app/helpers/products_helper.rb:

module ProductsHelper
  def sort_link(column, title = nil)
    title ||= column.titleize
    direction = column == sort_column && sort_direction == "asc" ? "desc" : "asc"
    icon = sort_direction == "asc" ? "glyphicon glyphicon-chevron-up" : "glyphicon glyphicon-chevron-down"
    icon = column == sort_column ? icon : ""
    link_to "#{title} <span class='#{icon}'></span>".html_safe, {column: column, direction: direction}
  end
end

The sort_link method takes 2 arguments. The first, column, is the name of the column you wish to sort by. The next, title is the text based title of the column. This can be anything. If title is nil, the method will use the actual column name in Title Case. The rest of the code generates the css classes and the hyperlink. This gets returned to our view, which injects it on the page.

Now let's create our view. Open up the index view for your products controller and modify it so that it looks like the code listed below.

app/views/products/index.html.erb:

<h1>Products</h1>
<table class="table table-bordered table-striped">
  <thead>
    <tr>
      <th><%= sort_link "name" %></th>
      <th><%= sort_link "price" %></th>
      <th><%= sort_link "created_at", "Added On" %></th>
    </tr>
  </thead>
  <tbody>
    <% @products.each do |product| %>
      <tr>
        <td><%= product.name %></td>
        <td><%= number_to_currency product.price %></td>
        <td><%= product.created_at.strftime "%B %e, %Y" %></td>    
      </tr>
    <% end %>
  </tbody>
</table>

You'll notice that we call the sort_link method here to generate the link to embed in the page. From then on, we just list the data like normal. If you start a rails server at this point and open up a page to your rails app, you'll see that all of the columns are sortable in ascending or descending order. That's it, thanks for reading!