Rails 3: An improved agnostic search model using Arel
2010-07-05
class Search
attr_accessor :attributes
def initialize(model, attrs = {})
@attributes = {}
@model = model
@arel = model.arel_table
model.columns.each do |field|
pattr = field.name
attrs = Hash[attrs.select {|k,v| !v.empty? }]
searchable = !attrs[pattr].nil?
@attributes[pattr.to_sym] = attrs[pattr] if searchable
self.class.send(:define_method,field.name) do |cond, *value|
attr = field.name.to_sym
value = value.first || @attributes[attr] || nil
@model = @model.where(@arel[attr].send(cond,value)) if searchable
end
end
if block_given?
yield self
end
end
def result
@model
end
end
The constructor takes the model you want to search in, and an hash of params to use, they’ll be the request params when used from controller. Note that if some param value is an empty string, it will be ignored: this lets the visitor to leave blank some fields in the search form and make it conditional.
To save typings, when specifying conditions, you can omit the value, because it’s taken from the params hash. By the way, if you need some more control, you can specify another value. Finally, you can pass a block to make it easier to read. Here’s a real usage example from a controller:
class RealtyRequestController < ApplicationController
#... other actions ...
def search
# value to search defaults to params[:search][:contract_id]
@s = Search.new(RealtyRequest,params[:search]) do |s|
s.contract_id :eq
# same here
s.price_max :lteq
s.price_min :gteq
# here I look for '%param%'
s.zone :matches, "%#{params[:search][:zone]}%"
s.province :matches, "%#{params[:search][:province]}%"
s.municipality :matches, "%#{params[:search][:municipality]}%"
s.rooms :eq
s.mq :eq
end
# assign result
@realty_requests = @s.result
# add order option
@realty_requests.order(:created_at)
# ... other rendering stuff ...
end
end
When Search#result
is called a model instance is returned, it can still be ordered. According to Arel's README, there’s no support for OR conditions yet, I hope it will be included soon.