Class: SimpleSearch::Search

Inherits:
Object
  • Object
show all
Defined in:
lib/simple_search/search.rb

Overview

How it works

Okay, so form_for is a really handy helper, right? Yes. But it expects that whatever object you are building a form… er, for, responds to methods that match the name of the first parameter. So, for instance …

f.text_field :headly_von_headdington

… expects that the object you passed to form_for responds to the headly_von_headdington method. SimpleSearch uses this to its advantage by auto-creating methods that will help it build a set of conditions to search on.

So, when you instantiate an object of class SimpleSearch::Search, it takes a look at the model you want it to search, and goes about creating a bunch of methods you will find handy to have available in your form_for block, based on the persistent attributes of your model – that is, the columns in the database table.

The methods vary by the type of data to be searched. For example purposes, let’s assume the attribute is question is named “gipe”, and see what kinds of conditions SimpleSearch will create depending on its data type.

Strings

  • gipe - Matches if the searched attribute contains this string. In SQL terms, this is a fragment that looks like gipe LIKE '%value%'

  • gipe_starts_with - Matches if the attribute starts with this string. SQL: gipe LIKE 'value%'

  • gipe_exact - Exact match. In SQL terms: gipe = 'value'

  • gipe_not, gipe_not_starts_with, gipe_not_exact - Negation of the previous examples. Matches if the attribute doesn’t contain, start with, or equal the value, respectively.

Numbers

  • gipe - Matches if the searched attribute equals the supplied numeric value.

  • gipe_not - Matches if the searched attribute doesn’t equal the value.

  • gipe_min - Matches if the searched attribute is greater than or equal to the value.

  • gipe_max - Matches if the searched attribute is less than or equal to the value.

Dates/DateTimes

  • gipe - Supplied value can be a String, Date or DateTime. This method will always search on a range against a datetime column.

    If a string, it will be parsed to a Date or DateTime, depending on the type of column it is being compared against, and converted to a range from beginning_of_day to end_of_day in the current time zone, if a range is needed. If it contains the text “ to ” it will be treated as a specific date range. So “12/25/2009 to 12/31/2009” will return results from midnight on Christmas day to just before the ball drops at Times Square.

  • gipe_from, gipe_to - Sets the start or endpoint of a date/time range explicitly, much as min/max do on numeric values. Keep in mind that range specifications are still locked to a day boundary, so gipe_from will be beginning_of_day and gipe_to will be end_of_day when searching against datetime columns.

Associations

When you pass a list of associations to SimpleSearch::Search#new (in the :search_associations option) SimpleSearch will add methods for that association as well. These work as you might expect, so a :search_association => [:manufacturer, :owner, :users] on a model that belongs_to a manufacturer and owner, and has_many users will gain methods like users_first_name_starts_with and manufacturer_years_in_operation_min, depending on the attributes of the associated models.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(model, options = {}) ⇒ Search

SimpleSearch always needs a model parameter to know which ActiveRecord model it will be wrapping around and squeezing juicy attribute goodness from. options are, oddly enough, optional. That being said, without at least a few search criteria specified, SimpleSearch isn’t going to be doing much besides providing a poorly-optimized alternative to Model.all in its search method.

One special option is the :search_associations option. This is a list of one or more associations on the model (as referenced by has_many, belongs_to, etc) that you would like to autogenerate search methods for, as well.

Wait, what? Scroll up and read “How it works” for more details.



76
77
78
79
80
81
82
83
84
85
86
# File 'lib/simple_search/search.rb', line 76

def initialize(model, options = {})
  @model = model
  @associations = [*options.delete(:search_associations)].compact
  @association_objects = @associations.map do |a|
    @model.reflect_on_association(a)
  end
  initialize_option_methods
  # Need to make sure to keep multipart date stuff around
  dates = dates_from_hash(Hash[*options.select {|k, v| k =~ /\(.*\)$/}.flatten])
  @options = options.reject{|k, v| !respond_to?(k)}.merge dates
end

Instance Attribute Details

#associationsObject (readonly)

Returns the value of attribute associations.



63
64
65
# File 'lib/simple_search/search.rb', line 63

def associations
  @associations
end

#optionsObject (readonly)

Returns the value of attribute options.



63
64
65
# File 'lib/simple_search/search.rb', line 63

def options
  @options
end

Instance Method Details

#conditionsObject

Returns the array of conditions that is passed to ActiveRecord’s find or paginate method by this search. Useful for debugging or to do your own custom searches without using the search or count methods.



91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/simple_search/search.rb', line 91

def conditions
  conditions = []
  parameters = []

  return [] if options.empty?

  @options.keys.select{|k| self.respond_to?("#{k}_conditions".to_sym)}.each do |opt|
    next if self.send(opt.to_sym).blank?
    condition = self.send("#{opt}_conditions".to_sym)
    parameter = self.send("#{opt}_parameters".to_sym)
    if condition.is_a?(Array)
      concat = respond_to?("#{opt}_concat".to_sym) ? send("#{opt}_concat".to_sym) : 'OR'
      condition = '(' + condition.join(" #{concat} ") + ')'
    end
    conditions << condition
    parameters.concat [*parameter]
  end

  conditions.blank? ? [] : [conditions.join(" AND "), *parameters]
end

#count(args = {}) ⇒ Object

Get the count of records matched by the current search. All arguments are passed to ActiveRecord’s count method.



130
131
132
133
134
135
136
# File 'lib/simple_search/search.rb', line 130

def count(args = {})
  args.merge!(
    :conditions => self.conditions,
    :include => (self.associations + [*args[:include]]).compact.uniq
  )
  @model.count(args)
end

#search(args = {}) ⇒ Object

Runs the current search against the database, returning all results, or paginated results, depending on whether a :page parameter has been received (and, of course, whether your model responds to paginate). All other options are passed through to the paginate/find call.



116
117
118
119
120
121
122
123
124
125
126
# File 'lib/simple_search/search.rb', line 116

def search(args = {})
  args.merge!(
    :conditions => self.conditions,
    :include => (self.associations + [*args[:include]]).compact.uniq
  )
  if @model.respond_to?(:paginate) && args.has_key?(:page)
    @model.paginate(:all, args)
  else
    @model.find(:all, args)
  end
end