search plugin for rails
July 10th, 2008 • Uncategorized
# USAGE EXAMPLE
# -----------------------------------------------------------
# class PostsController < ApplicationController
# restful_search do |s|
# s.model = :foo
# s.search_param_name = "search" # defaults to 'match'
# s.columns = [ 'foo.name', 'bar.state', 'baz.comment' ],
# s.joins = [ :foo => { :bar => :baz } ],
# end
#
# def index
# @posts = restful_search_results
# end
# end
# -----------------------------------------------------------
module RestfulSearch
def self.included(base)
base.extend ClassMethods
base.send :include, InstanceMethods
end
class MissingSpecException < Exception; end
class InvalidSpecException < Exception; end
class RestfulSearchSpec
attr_accessor :model
attr_reader :columns
attr_reader :joins
attr_writer :search_param_name
def columns=(args)
@columns = args.is_a?(Array) ? args.map(&:to_sym) : [ args.to_sym ]
end
alias :column :columns
def joins=(args)
@joins = args.is_a?(Array) ? args : [ args ]
end
def model_class
model.to_s.singularize.camelize.constantize
end
def search_param_name
@search_param_name ? @search_param_name.to_sym : :match
end
def conditions(search_param_value)
condition_string = columns.map { |c| "#{c} LIKE ?" }.join(' OR ')
if search_param_value && !search_param_value.blank?
[ condition_string, *bind_variables("%#{search_param_value}%") ]
else
nil
end
end
def valid?
true
end
private
def bind_variables(search_param_value)
returning [] do |v|
columns.size.times { v << search_param_value }
end
end
end
module ClassMethods
# USAGE EXAMPLE
# -----------------------------------------------------------
# class PostsController < ApplicationController
# restful_search do |s|
# s.model = :foo
# s.search_param_name = "search" # defaults to 'match'
# s.columns = [ 'foo.name', 'bar.state', 'baz.comment' ],
# s.joins = [ :foo => { :bar => :baz } ],
# end
#
# def index
# @posts = restful_search_results
# end
# end
# -----------------------------------------------------------
def restful_search
if block_given?
spec = RestfulSearchSpec.new
yield spec
unless spec.valid?
msg = "restful_search spec is not valid"
raise InvalidSpecException, msg
end
else
msg = "restful_search must be called with a block"
raise MissingSpecException, msg
end
# make spec available
define_method :restful_search_spec do
spec
end
include InstanceMethods
rescue Exception => e
logger.error "You have an error in your restful_search configuration"
raise e
end
end
module InstanceMethods
protected
def restful_search_results(conditions = {})
options = if has_search_param_value?
restful_search_options
else
restful_search_core_options
end
if options[:conditions]
options[:conditions].merge!(conditions) unless conditions.empty?
else
options[:conditions] = conditions unless conditions.empty?
end
options.merge! :page => params[:page]
restful_search_spec.model_class.paginate(:all, options)
end
def search_param_value
params[restful_search_spec.search_param_name]
end
def has_search_param_value?
!search_param_value.nil? && !search_param_value.blank?
end
def restful_search_options
restful_search_core_options.merge(restful_search_conditions)
end
def restful_search_core_options
{
:joins => restful_search_spec.joins,
:order => 'created_at DESC'
}
end
def restful_search_conditions
{
:conditions => restful_search_spec.conditions(search_param_value)
}
end
end
end