search plugin for rails

# 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


Leave a Reply

Formatting: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <code> <em> <i> <strike> <strong>