Code Samples

All of the code samples have now been consolidated and moved to my blog at http://house9.blogspot.com/. Many google searches still point here so I am leaving this blog operational.

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

Wednesday, January 6, 2010

Ruby Http Get with Net::HTTP

require 'net/http'
require 'uri'
def get_html_content(requested_url)
url = URI.parse(requested_url)
full_path = (url.query.blank?) ? url.path : "#{url.path}?#{url.query}"
the_request = Net::HTTP::Get.new(full_path)
the_response = Net::HTTP.start(url.host, url.port) { |http|
http.request(the_request)
}
raise "Response was not 200, response was #{the_response.code}" if the_response.code != "200"
return the_response.body
end
# this will fail with ArgumentError: HTTP request path is empty
s = get_html_content("http://www.google.com")
# these should be fine
s = get_html_content("http://www.google.com/")
s = get_html_content("http://github.com/search?q=http")
# above code does not handle redirects but raises exception for non-200
s = get_html_content("http://www.yahoo.com/") # http 302

Resources



require 'net/http'
require 'uri'

def get_html_content(requested_url)
url = URI.parse(requested_url)
full_path = (url.query.blank?) ? url.path : "#{url.path}?#{url.query}"
the_request = Net::HTTP::Get.new(full_path)

the_response = Net::HTTP.start(url.host, url.port) { |http|
http.request(the_request)
}

raise "Response was not 200, response was #{the_response.code}" if the_response.code != "200"
return the_response.body
end

# this will fail with ArgumentError: HTTP request path is empty
s = get_html_content("http://www.google.com")
# these should be fine
s = get_html_content("http://www.google.com/")
s = get_html_content("http://github.com/search?q=http")
# above code does not handle redirects but raises exception for non-200
s = get_html_content("http://www.yahoo.com/") # http 302

Ruby Regex to remove script tags

# remove all script tags
html_content = html_content.gsub(/<script.*?>[\s\S]*<\/script>/i, "")

Resources
Custom Search
< ... back