How to model a custom search form in Rails

Often you need to create a search form to filter the rows in a table that corresponds to a specific model. "SearchLogic":http://github.com/binarylogic/searchlogic can be a valid solution but maybe you want to bet on a more customizable alternative. The solution I propose is to create a Search.rb class that is able to collect the search parameters and to create the "where conditions" to be applied on our find query. Suppose you want to filter events records defined by an Event.rb model with the following attributes: * name, string * address, string * start_at, datetime * end_at, datetime The search mask will propose two text fields for _name_ and _address_ and possibly two DatePicker for start_at and end_at. You may decide to put AND conditions rather than OR conditions or to define intervals based on the presence of one or both date fields. Let's now see how to shape our support class Search.rb (eg create app/models/)

class Search
attr_reader :options
def initialize(model, options)
@model = model
@options = options || {}
end

def name
options[:name]
end

def address
options[:address]
end

def event_date_after
date_from_options(:event_date_after)
end

def event_date_before
date_from_options(:event_date_before)
end

def has_name?
name.present?
end

def has_address?
address.present?
end

def conditions
conditions = []
parameters = []

return nil if options.empty?

if has_name?
conditions << "#{@model.table_name}.name LIKE ?"
parameters << "%#{name}%"
end

if has_address?
conditions << "#{@model.table_name}.address LIKE ?"
parameters << "%#{address}%"
end

if event_date_after
conditions << "#{@model.table_name}.start_at >= ?"
parameters << event_date_after.to_time
end

if event_date_before
conditions << "#{@model.table_name}.end_at <= ?"
parameters << event_date_before.to_time.end_of_day
end

unless conditions.empty?
[conditions.join(" AND "), *parameters]
else
nil
end
end

private

def date_from_options(which)
part = Proc.new { |n| options["#{which}(#{n}i)"] }
y, m, d = part[1], part[2], part[3]
y = Date.today.year if y.blank?
Date.new(y.to_i, m.to_i, d.to_i)
rescue ArgumentError => e
return nil
end

end
Now we can see how to write the controller that will apply the search. Specifically, I would make a search that responds to the action _index_ of _EventsController_. At this point, an URL without parameters (such as http://localhost:3000/events) will call an Event.find(:all), a request with parameters (such http://localhost:3000/events?name=rock+contest) will apply the search. This requires that our search form responds to the GET method. We will see this aspect in detail later. The controller code looks like this:

class EventsController < ApplicationController
def index
@events = []
@search = Search.new(Event, params[:search])
if is_search?
@events = Event.search(@search, :page => params[:page])
else
@events = Event.paginate(:page => params[:page])
end
end

private

def is_search?
@search.conditions
end

end
As you can see, in the case of search, the class method _search_ will be called. Let's see how it was defined in the model Event.rb

class Event < ActiveRecord::Base
def self.search(search, args = {})
self.build_search_hash search, args
self.paginate(:all, @search_hash)
end

private

 def self.build_search_hash(search, args = {})
@search_hash = {:conditions => search.conditions,
:page => args[:page],
:per_page => args[:per_page],
:order => 'events.created_at'}
end
end
At this ponit we can code the search form in this way (using "formtastic":http://github.com/justinfrench/formtastic).

<% semantic_form_for :search, @search, :html => { :method => :get } do |form| %>
<% form.inputs do %>
<% form.inputs do %>
<%= form.input :name, :label => t('search_form.name') %>
<%= form.input :address, :label => t('search_form.address') %>
<%= form.input :event_date_after,
:as => :date,
:label => t('search_form.event_date_after') %>
<%= form.input :event_date_before,
:as => :date,
:label => t('search_form.event_date_before') %>
<% end %>
<% end %>
 
 <% form.buttons do %>
<%= pretty_positive_button t('search') %>
<% end %>
<% end %>
The search method is GET, so it will append search parameters to the url and will invoke the _index_ method of _EventsController_. Make your website design much easier by using these 20 useful cheatsheets for designers.