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