The blog has moved to http://jessehouse.com/ ... Many google searches point here so I am leaving it operational, but there will be no new posts.

Tuesday, March 23, 2010

sorting rails view with will_paginate plugin

This solution for sorting data on rails index actions does NOT use ajax. Sometimes sorting with ajax is nice but often it is not actually as user friendly; breaking the browser back button, etc...

Disclaimer 1: this code is probably using all kinds of ruby anti-patterns :) - but it is a very simple implementation and so I just went with it...

Disclaimer 2: the title of the post might be a little miss leading as you can use this with or without the will_paginate plugin. In my case I am using it with the will_paginate plugin - and I highly recommend it!

put this file in your rails lib directory /lib/sort_index.rb
# see http://gist.github.com/341290 (sort_index_controller_usage.rb)
# see http://gist.github.com/341295 (sort_index_view_usage.html.erb)
# The classes in this module help to enable sorting on index pages
# building sql order clauses and rendering html table header links
module SortIndex
SORT_KEY_ASC = 'asc'
SORT_KEY_DESC = 'desc'
SORT_DIRECTION_MAP = {
SORT_KEY_DESC => 'DESC',
SORT_KEY_ASC => 'ASC'
}
# The +SortIndex::Config+ class specifies all possible sort columns
# for a given controller action including the default column and the default order
class Config
attr_accessor :columns
attr_accessor :default_direction
def default
return @default
end
# The +initialize+ method;
# both the default and columns parameters contain key value pairs
# where the key is passed in the query string to the action and the
# value contains the sql order by value
# === Parameters
# * _default_ = Hash; must contain only one pair; automatically gets added to the columns member
# * _columns_ = Hash; one pair per sortable column
# * _default_direction_ = String; optional, if not specified order will be DESC
def initialize(default, columns, default_direction = nil)
@columns = columns
@default_direction = default_direction || SORT_KEY_DESC
raise "default only supports 1 pair" if default.length != 1
default.each_pair { |key, value|
@default = value
@columns[key] = value
}
end
end
# The +SortIndex::Sortable+ class enable sorting on index pages
# avoids sql injection by only using values from the SortIndex::Config#columns
# Hash and not the values passed in the query string
class Sortable
# The +initialize+ method;
# === Parameters
# * _params_ = the controllers params Hash
# * _config_ = SortIndex::Config
# * _index_url_ = String; optional, if not specified will be the name of the controller
# ** Examples
# *** not specified /employees (the index action)
# *** specified /employees/special_action
def initialize(params, config, index_url = nil)
@config = config
@params = params
@index_url = index_url || params[:controller]
# sets up for building the sql order by
@sort_direction = SORT_DIRECTION_MAP[@params[:sort_direction]] || @config.default_direction
@sort_by = @config.columns[@params[:sort_by]] || @config.default
end
# The +order+ method returns the sql order criteria
# use with your find calls or via paginate from will_paginate plugin
def order
specified_sort_by || "#{@sort_by} #{@sort_direction}"
end
# The +header_link+ method returns a string of html containing the table header and a tags
# Example: <th><a href="/employess?sort_by=first_name&amp;sort_direction=desc">First Name</a></th>
# If the column is the currently sorted column then a css class of current-sort-asc or current-sort-describe
# is applied; this allows you to use css to add visual indicators such as up and down arrows
# this method is called from the view; once per column
# === Parameters
# * _sort_key_ = String; must be one of the key values from SortIndex::Config
# * _display_ = The display text
# * _sortable_ = Boolean; default is true;
# ** passing false will not render an anchor tag; instead the display will be wrapped in a span
def header_link(sort_key, display, sortable = true)
if @config.columns[sort_key].nil? and sortable then
raise "Sort key of '#{sort_key}' not found. Check your controllers SortIndex::Config variable"
end
class_attr = ""
if @config.columns[sort_key] == @sort_by then
class_attr = " class='current-sort-#{@sort_direction.downcase}'"
end
a_href = "<a href=\"#{@index_url}?sort_by=#{sort_key}&amp;sort_direction=#{next_direction}\" title=\"Sort by #{display}\">#{display}</a>"
if sortable == false then
a_href = "<span>#{display}</span>"
end
return "<th#{class_attr}>#{a_href}</th>"
end
# The +next_direction+ method is called by header_link and specifies which way the rendered
# links should sort. Returns the opposite of the current sort
def next_direction
sort_direction = SORT_KEY_ASC
if (@params[:sort_direction].nil?) then
sort_direction = (@sort_direction == SORT_KEY_ASC) ? SORT_KEY_DESC : SORT_KEY_ASC
elsif (@params[:sort_direction] == SORT_KEY_ASC) then
sort_direction = SORT_KEY_DESC
end
return sort_direction
end
# The +specified_sort_by+ method is called by order returns the sql order by criteria
# This can be more than one sql column; when it is multiple columns we apply the same direction to all
# For Example if you had one header column for Employee#full_name which mapped to two
# database columns first_name and last_name of the employees table the result would look like
# first_name DESC, last_name DESC or last_name DESC, first_name DESC depending on your configuration
def specified_sort_by
sort = @config.columns[@params[:sort_by]]
return nil if sort.nil?
return sort.split(',').map {|order_criteria| "#{order_criteria} #{@sort_direction}"}.join(',')
end
end
end
view raw sort_index.rb hosted with ❤ by GitHub

in your controller code, set up the SortIndex::Config (you can have more than one if you have multiple actions that need to support sorting
# see http://gist.github.com/341278 (sort_index.rb)
# see http://gist.github.com/341295 (sort_index_view_usage.html.erb)
# this example is an employees controller and using the will_paginate plugin
# however code should work fine with a standard ActiveRecord#find(:all, :order => ...
# put the sort_index.rb code in your rails lib directory
class EmployeesController < ApplicationController
# index sort constant config for sorting index action
# default is order by updated_at DESC
INDEX_SORT = SortIndex::Config.new(
{'updated_at' => 'updated_at'},
{
'full_name' => 'UPPER(first_name), UPPER(last_name)',
'email' => 'email'
}
#, optionally SortIndex::SORT_KEY_ASC
)
def index
@sortable = SortIndex::Sortable.new(params, INDEX_SORT)
@employees = Employee.paginate :page => params[:page], :order => @sortable.order
# render index.html.erb
end
end

then in your view code render your table headers using the sort object
<%-
# see http://gist.github.com/341278 (sort_index.rb)
# see http://gist.github.com/341290 (sort_index_controller_usage.rb)
-%>
<table style="width:100%">
<thead>
<tr>
<%= @sortable.header_link('full_name', 'Name') %>
<%= @sortable.header_link('email', 'Email') %>
<%= @sortable.header_link('updated_at', 'Updated at') %>
<%= @sortable.header_link('manage', 'Manage', false) %>
</tr>
</thead>
<tfoot>
<tr>
<td colspan="4">
&nbsp;
</td>
</tr>
</tfoot>
<tbody>
<% for employee in @employees %>
<tr class="<%= cycle('oddrow', 'evenrow') %>">
<td><%= employee.full_name %></td>
<td><%= employee.email %></td>
<td><%= employee.updated_at.to_s %></td>
<td>
<%= link_to 'Edit', edit_employee_path(employee) %>
</td>
</tr>
<% end %>
</tbody>
</table>

Does not support the following:
  • additional attributes on the anchor tags
  • additional attributes on the table header tags
  • additional query string parameters - might add this later, would be nice for search results

Monday, March 15, 2010

Digg style css for will_paginate plugin

Just started using the will_paginate plugin for my rails application. I wanted a Flickr or Digg style applied to the paging controls, did not find anything that came 'out 0f the box'. After a few google searches I came across this link which had an example - apparently from an older version of the README file.

here is that css for reference
.pagination { padding: 3px; margin: 3px; }
.pagination a { padding: 2px 5px 2px 5px; margin: 2px; border: 1px solid #aaaadd; text-decoration: none; color: #000099; }
.pagination a:hover,
.pagination a:active { border: 1px solid #000099; color: #000; }
.pagination span.current { padding: 2px 5px 2px 5px; margin: 2px; border: 1px solid #000099; font-weight: bold; background-color: #000099; color: #FFF; }
.pagination span.disabled { padding: 2px 5px 2px 5px; margin: 2px; border: 1px solid #eee; color: #ddd; }
/* From http://workingwithrails.com/railsplugin/4765-will-paginate */

.

Thursday, March 11, 2010

exclude .svn results with egrep recursive search

The exclude-dir option works nicely, I was previously was using --exclude and it would include some sub-dir .svn files
egrep --exclude-dir=\.svn -r -n {search-criteria} {directory}
i.e. find all TODO comments in the current directory and all sub directories
egrep --exclude-dir=\.svn -r -n TODO .

Found this in the comments on this post - http://antoniolorusso.com/2008/05/12/grep-excluding-svn-dirs/

.

Friday, March 5, 2010

cut back on the blog reading

I had 200+ subscriptions in my rss reader - way too many
I just cut it back to a short list. No doubt the list will start to grow again...




Thursday, March 4, 2010

"Impossible to connect to file" error opening dBase file with open office on ubuntu

Turns out the ubuntu distribution of Open Office does not include the 'Database' product by default, it tries to open the file with the spread sheet program which can not handle it and errors with "Impossible to connect to file". The fix is to install the database product
System -> Administration -> Synaptic Package Manager
install openoffice.org-base
After installing you should see the 'Open Office.org Database' under Applications menu



Found the answer from this post.