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.