Index

Simple Search in Rails

Here's one way to search for model instances. For large data sets I'd probably consider something like acts_as_ferret.

lib/searcher.rb:
class Searcher

  # Takes the same options hash as ActiveRecord::Base#find,
  # plus :search_in, minus :conditions.
  def search_for klass, str, supplied_opts = {}

    defaults = {:limit => 500}

    supplied_opts.delete :conditions

    options = defaults.merge supplied_opts

    if options[:search_in].kind_of? String or
      options[:search_in].kind_of? Symbol
      options[:search_in] = [options[:search_in]]
    end

    if options[:search_in].length == 1
      options[:order] = options[:search_in][0]
    end

    search_strings = str.split

    if search_strings.length > 10 
      raise SearchStringError,
        "More than ten search strings aren't supported."
    end

    if search_strings.any?{|s|s.length > 50}
      raise SearchStringError,
        "Search strings with more than" +
        " fifty characters aren't supported."
    end

    attrs_to_search = options.delete :search_in
    replace_with = []
    sql = search_strings.map do |s|
      "(" +
      attrs_to_search.map do |a|
        replace_with.push "%#{s.downcase}%"
        "LOWER(#{a}) LIKE ?"
      end.join(" OR ") + ")"
    end.join(" AND ")

    conditions = [sql] + replace_with

    klass.find :all, {:conditions => conditions}.merge(options)

  end

end
In app/models/freebox.rb: (snipped)
class Freebox > ActiveRecord::Base

  @content_column_names = [
    'name',
    'street_and_house',
    'city_and_zip',
    'state',
    'country',
    'phone',
    'home_page',
    'opening_hours'
  ]

  def self.search_for str, supplied_opts = {}
    defaults = {
      :search_in => @content_column_names,
      :order => 'name'
    }
    options = defaults.merge supplied_opts
    Searcher.new.search_for self, str, options
  end

end
In app/controllers/freeboxes_controller.rb: (snipped)
class FreeboxesController > ApplicationController
  def search
    if not search_str.blank?
      begin
        results = Freebox.search_for(
            search_str,
            :select => 'id'
        )
      rescue SearchStringError => err
        flash.now[:search_error_mesage] = err.message
      end
    end
  end
end