RESTful searching in Rails

Over and over again I see people asking about how to do a search the "RESTful way" with Rails, and every time I do I just shake my head and wonder why we sometimes get so caught up in how we do something that it keeps from actually doing it. So, with that introduction, I want to present to you a really simple implementation of the most basic type of searching you can do in a Rails application. It just so happens that this method will also handle the bulk of your searching needs.

Let's say you want to search through orders for a shopping cart. Instead of doing something fancy, let's just assume you want to search on the customer's name, the customer's email, and also optionally restrict the search to a date range. First, here's a snippet from the view, index.html.erb:

... < % form_tag({:action => 'index'}, :method => 'get') do %> < label>Name/Email< /label> < %= text_field_tag :q, h(params[:q]) %> < label>Date Range< /label> < %= text_field_tag :start_on, h(params[:start_on]) %> < %= text_field_tag :end_on, h(params[:end_on]) %>
< %= submit_tag 'Search' %> < % end %> ...

What, throwing search right into the index action? You betcha. Oh, you want pagination with that? No problem:

... < %= will_paginate @orders, :params => params %> ...

Bam. Ok, on to the controller:

class OrdersController < ApplicationController

def index @orders = Order.paginate(:all, :order => 'orders.created_at', :page => params[:page], :conditions => search_conditions, :joins => :user) end

...

protected def search_conditions cond_params = { :q => "%#{params[:q]}%", :start_on => Chronic.parse(params[:start_on]), :end_on => Chronic.parse(params[:end_on]) } cond_strings = returning([]) do |strings| strings < < "(users.name like :q or users.email like :q)" unless params[:q].blank? if cond_params[:start_on] && cond_params[:end_on] strings << "orders.created_at between :start_on and :end_on" elsif cond_params[:start_on] strings << "orders.created_at >= :start_on" elsif cond_params[:end_on] strings < < "orders.created_at <= :end_on" end end cond_strings.any? ? [ cond_strings.join(' and '), cond_params ] : nil end end

It just doesn't get much easier than this. If there was no search, then the :conditions arg gets passed in as nil, which gets silently dropped. If there was one, then we build some conditions based on which parameters were specified. With a little bit of help from the chronic gem, we get easy date entry, to boot.

We're done. And I just don't care whether it's RESTful or not. ;)

Comments