Class: ThinkingSphinx::Search

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

Overview

Once you’ve got those indexes in and built, this is the stuff that matters - how to search! This class provides a generic search interface - which you can use to search all your indexed models at once. Most times, you will just want a specific model’s results - to search and search_for_ids methods will do the job in exactly the same manner when called from a model.

Class Method Summary collapse

Class Method Details

.search(*args) ⇒ Object

Searches through the Sphinx indexes for relevant matches. There’s various ways to search, sort, group and filter - which are covered below.

Also, if you have WillPaginate installed, the search method can be used just like paginate. The same parameters - :page and :per_page - work as expected, and the returned result set can be used by the will_paginate helper.

Basic Searching

The simplest way of searching is straight text.

ThinkingSphinx::Search.search "pat"
ThinkingSphinx::Search.search "google"
User.search "pat", :page => (params[:page] || 1)
Article.search "relevant news issue of the day"

If you specify :include, like in an #find call, this will be respected when loading the relevant models from the search results.

User.search "pat", :include => :posts

Searching by Fields

If you want to step it up a level, you can limit your search terms to specific fields:

User.search :conditions => {:name => "pat"}

This uses Sphinx’s extended match mode, unless you specify a different match mode explicitly (but then this way of searching won’t work). Also note that you don’t need to put in a search string.

Searching by Attributes

Also known as filters, you can limit your searches to documents that have specific values for their attributes. There are two ways to do this. The first is one that works in all scenarios - using the :with option.

ThinkingSphinx::Search.search :with => {:parent_id => 10}

The second is only viable if you’re searching with a specific model (not multi-model searching). With a single model, Thinking Sphinx can figure out what attributes and fields are available, so you can put it all in the :conditions hash, and it will sort it out.

Node.search :conditions => {:parent_id => 10}

Filters can be single values, arrays of values, or ranges.

Article.search "East Timor", :conditions => {:rating => 3..5}

Excluding by Attributes

Sphinx also supports negative filtering - where the filters are of attribute values to exclude. This is done with the :without option:

User.search :without => {:role_id => 1}

Sorting

Sphinx can only sort by attributes, so generally you will need to avoid using field names in your :order option. However, if you’re searching on a single model, and have specified some fields as sortable, you can use those field names and Thinking Sphinx will interpret accordingly. Remember: this will only happen for single-model searches, and only through the :order option.

Location.search "Melbourne", :order => :state
User.search :conditions => {:role_id => 2}, :order => "name ASC"

Keep in mind that if you use a string, you must specify the direction (ASC or DESC) else Sphinx won’t return any results. If you use a symbol then Thinking Sphinx assumes ASC, but if you wish to state otherwise, use the :sort_mode option:

Location.search "Melbourne", :order => :state, :sort_mode => :desc

Of course, there are other sort modes - check out the Sphinx documentation for that level of detail though.

Grouping

For this you can use the group_by, group_clause and group_function options - which are all directly linked to Sphinx’s expectations. No magic from Thinking Sphinx. It can get a little tricky, so make sure you read all the relevant documentation first.

Yes this section will be expanded, but this is a start.

Geo/Location Searching

Sphinx - and therefore Thinking Sphinx - has the facility to search around a geographical point, using a given latitude and longitude. To take advantage of this, you will need to have both of those values in attributes. To search with that point, you can then use one of the following syntax examples:

Address.search "Melbourne", :geo => [1.4, -2.217]
Address.search "Australia", :geo => [-0.55, 3.108],
  :latitude_attr => "latit", :longitude_attr => "longit"

The first example applies when your latitude and longitude attributes are named any of lat, latitude, lon, long or longitude. If that’s not the case, you will need to explicitly state them in your search, or you can do so in your model:

define_index do
  has :latit  # Float column, stored in radians
  has :longit # Float column, stored in radians

  set_property :latitude_attr   => "latit"
  set_property :longitude_attr  => "longit"
end

Now, geo-location searching really only has an affect if you have a filter, sort or grouping clause related to it - otherwise it’s just a normal search. To make use of the positioning difference, use the special attribute “@geodist” in any of your filters or sorting or grouping clauses.

And don’t forget - both the latitude and longitude you use in your search, and the values in your indexes, need to be stored as a float in radians, not degrees. Keep in mind that if you do this conversion in SQL you will need to explicitly declare a column type of :float.

define_index do
  has 'RADIANS(lat)', :as => :lat,  :type => :float
  # ...
end


165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
# File 'lib/thinking_sphinx/search.rb', line 165

def search(*args)
  results, client = search_results(*args.clone)
  
  ::ActiveRecord::Base.logger.error(
    "Sphinx Error: #{results[:error]}"
  ) if results[:error]
  
  options = args.extract_options!
  klass   = options[:class]
  page    = options[:page] ? options[:page].to_i : 1
  
  begin
    pager = WillPaginate::Collection.new(page,
      client.limit, results[:total] || 0)
    pager.replace instances_from_results(results[:matches], options, klass)
  rescue StandardError => err
    instances_from_results(results[:matches], options, klass)
  end
end

.search_for_id(*args) ⇒ Object

Checks if a document with the given id exists within a specific index. Expected parameters:

  • ID of the document

  • Index to check within

  • Options hash (defaults to {})

Example:

ThinkingSphinx::Search.search_for_id(10, "user_core", :class => User)


196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
# File 'lib/thinking_sphinx/search.rb', line 196

def search_for_id(*args)
  options = args.extract_options!
  client  = client_from_options options
  
  query, filters    = search_conditions(
    options[:class], options[:conditions] || {}
  )
  client.filters   += filters
  client.match_mode = :extended unless query.empty?
  client.id_range   = args.first..args.first
  
  begin
    return client.query(query, args[1])[:matches].length > 0
  rescue Errno::ECONNREFUSED => err
    raise ThinkingSphinx::ConnectionError, "Connection to Sphinx Daemon (searchd) failed."
  end
end

.search_for_ids(*args) ⇒ Object

Searches for results that match the parameters provided. Will only return the ids for the matching objects. See #search for syntax examples.



15
16
17
18
19
20
21
22
23
24
25
26
27
28
# File 'lib/thinking_sphinx/search.rb', line 15

def search_for_ids(*args)
  results, client = search_results(*args.clone)
  
  options = args.extract_options!
  page    = options[:page] ? options[:page].to_i : 1
  
  begin
    pager = WillPaginate::Collection.new(page,
      client.limit, results[:total] || 0)
    pager.replace results[:matches].collect { |match| match[:doc] }
  rescue
    results[:matches].collect { |match| match[:doc] }
  end
end