class ThinkingSphinx::Search

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.

Constants

ArrayOptions
CoreMethods
DefaultStarToken
HashOptions
SafeMethods

Attributes

args[R]
options[R]

Public Class Methods

bundle_searches(enum = nil) { |bundle| ... } click to toggle source
# File lib/thinking_sphinx/search.rb, line 63
def self.bundle_searches(enum = nil)
  bundle = ThinkingSphinx::BundledSearch.new

  if enum.nil?
    yield bundle
  else
    enum.each { |item| yield bundle, item }
  end

  bundle.searches
end
count(*args) click to toggle source

Deprecated. Use ThinkingSphinx.count

# File lib/thinking_sphinx/search.rb, line 48
def self.count(*args)
  warn 'ThinkingSphinx::Search.count is deprecated. Please use ThinkingSphinx.count instead.'
  ThinkingSphinx.count(*args)
end
facets(*args) click to toggle source

Deprecated. Use ThinkingSphinx.facets

# File lib/thinking_sphinx/search.rb, line 54
def self.facets(*args)
  warn 'ThinkingSphinx::Search.facets is deprecated. Please use ThinkingSphinx.facets instead.'
  ThinkingSphinx.facets(*args)
end
matching_fields(fields, bitmask) click to toggle source
# File lib/thinking_sphinx/search.rb, line 75
def self.matching_fields(fields, bitmask)
  matches   = []
  bitstring = bitmask.to_s(2).rjust(32, '0').reverse

  fields.each_with_index do |field, index|
    matches << field if bitstring[index, 1] == '1'
  end
  matches
end
new(*args) click to toggle source
# File lib/thinking_sphinx/search.rb, line 85
def initialize(*args)
  ThinkingSphinx.context.define_indexes

  @array    = []
  @options  = args.extract_options!
  @args     = args

  add_default_scope unless options[:ignore_default]

  populate if @options[:populate]
end
search_for_id(*args) click to toggle source

Deprecated. Use ThinkingSphinx.search_for_ids

# File lib/thinking_sphinx/search.rb, line 42
def self.search_for_id(*args)
  warn 'ThinkingSphinx::Search.search_for_id is deprecated. Please use ThinkingSphinx.search_for_id instead.'
  ThinkingSphinx.search_for_id(*args)
end
search_for_ids(*args) click to toggle source

Deprecated. Use ThinkingSphinx.search_for_ids

# File lib/thinking_sphinx/search.rb, line 36
def self.search_for_ids(*args)
  warn 'ThinkingSphinx::Search.search_for_ids is deprecated. Please use ThinkingSphinx.search_for_ids instead.'
  ThinkingSphinx.search_for_ids(*args)
end
warn(message) click to toggle source
# File lib/thinking_sphinx/search.rb, line 59
def self.warn(message)
  ::ActiveSupport::Deprecation.warn message
end

Private Class Methods

log(message) { || ... } click to toggle source
# File lib/thinking_sphinx/search.rb, line 548
def self.log(message, &block)
  if ThinkingSphinx::ActiveRecord::LogSubscriber.logger.nil?
    yield if block_given?
    return
  end

  if block_given?
    ::ActiveSupport::Notifications.
      instrument('query.thinking_sphinx', :query => message, &block)
  else
    ::ActiveSupport::Notifications.
      instrument('message.thinking_sphinx', :message => message)
  end
end

Public Instance Methods

==(object) click to toggle source
Calls superclass method
# File lib/thinking_sphinx/search.rb, line 97
def ==(object)
  populate
  super
end
all() click to toggle source

Populates the search result set

# File lib/thinking_sphinx/search.rb, line 108
def all
  populate
  self
end
append_to(client) click to toggle source
# File lib/thinking_sphinx/search.rb, line 399
def append_to(client)
  prepare client
  client.append_query query, indexes, comment
  client.reset
end
as_json(*args) click to toggle source
# File lib/thinking_sphinx/search.rb, line 119
def as_json(*args)
  populate
  @array.as_json(*args)
end
client() click to toggle source
# File lib/thinking_sphinx/search.rb, line 393
def client
  client = options[:client] || config.client

  prepare client
end
current_page() click to toggle source

The current page number of the result set. Defaults to 1 if no page was explicitly requested.

@return [Integer]

# File lib/thinking_sphinx/search.rb, line 211
def current_page
  @options[:page].blank? ? 1 : @options[:page].to_i
end
each_with_group_and_count(&block)
each_with_groupby_and_count() { |self, match["groupby"], match["count"]| ... } click to toggle source
# File lib/thinking_sphinx/search.rb, line 329
def each_with_groupby_and_count(&block)
  populate
  results[:matches].each_with_index do |match, index|
    yield self[index],
      match[:attributes]["@groupby"],
      match[:attributes]["@count"]
  end
end
Also aliased as: each_with_group_and_count
each_with_match() { |self, match| ... } click to toggle source
# File lib/thinking_sphinx/search.rb, line 346
def each_with_match(&block)
  populate
  results[:matches].each_with_index do |match, index|
    yield self[index], match
  end
end
each_with_weighting() { |self, match| ... } click to toggle source
# File lib/thinking_sphinx/search.rb, line 339
def each_with_weighting(&block)
  populate
  results[:matches].each_with_index do |match, index|
    yield self[index], match[:weight]
  end
end
error() click to toggle source

The Sphinx-reported error, if any.

@return [String, nil]

# File lib/thinking_sphinx/search.rb, line 145
def error
  populate
  @results[:error]
end
error?() click to toggle source

Indication of whether the request resulted in an error from Sphinx.

@return [Boolean] true if Sphinx reports query error

# File lib/thinking_sphinx/search.rb, line 137
def error?
  !!error
end
excerpt_for(string, model = nil) click to toggle source
# File lib/thinking_sphinx/search.rb, line 353
def excerpt_for(string, model = nil)
  if model.nil? && one_class
    model ||= one_class
  end

  populate

  index = options[:index] || "#{model.core_index_names.first}"
  client.excerpts(
    {
      :docs   => [string.to_s],
      :words  => results[:words].keys.join(' '),
      :index  => index.split(',').first.strip
    }.merge(options[:excerpt_options] || {})
  ).first
end
facets(*args) click to toggle source
# File lib/thinking_sphinx/search.rb, line 385
def facets(*args)
  options = args.extract_options!
  merge_search self, args, options
  args << options

  ThinkingSphinx::FacetSearch.new(*args)
end
first_page?() click to toggle source
# File lib/thinking_sphinx/search.rb, line 215
def first_page?
  current_page == 1
end
freeze() click to toggle source
# File lib/thinking_sphinx/search.rb, line 113
def freeze
  populate
  @array.freeze
  self
end
indexes() click to toggle source
# File lib/thinking_sphinx/search.rb, line 320
def indexes
  return options[:index] if options[:index]
  return '*' if classes.empty?

  classes.collect { |klass|
    klass.sphinx_index_names
  }.flatten.uniq.join(',')
end
last_page?() click to toggle source
# File lib/thinking_sphinx/search.rb, line 238
def last_page?
  next_page.nil?
end
limit_value()

Kaminari support

Alias for: per_page
method_missing(method, *args, &block) click to toggle source
Calls superclass method
# File lib/thinking_sphinx/search.rb, line 176
def method_missing(method, *args, &block)
  if is_scope?(method)
    add_scope(method, *args, &block)
    return self
  elsif method == :search_count
    merge_search one_class.search(*args), self.args, options
    return scoped_count
  elsif method.to_s[/^each_with_.*/].nil? && !@array.respond_to?(method)
    super
  elsif !SafeMethods.include?(method.to_s)
    populate
  end

  if method.to_s[/^each_with_.*/] && !@array.respond_to?(method)
    each_with_attribute method.to_s.gsub(/^each_with_/, ''), &block
  else
    @array.send(method, *args, &block)
  end
end
next_page() click to toggle source

The next page number of the result set. If there are no more pages available, nil is returned.

@return [Integer, nil]

# File lib/thinking_sphinx/search.rb, line 230
def next_page
  current_page >= total_pages ? nil : current_page + 1
end
next_page?() click to toggle source
# File lib/thinking_sphinx/search.rb, line 234
def next_page?
  !next_page.nil?
end
num_pages()
Alias for: total_pages
offset() click to toggle source

The current page's offset, based on the number of records per page. Or explicit :offset if given.

@return [Integer]

# File lib/thinking_sphinx/search.rb, line 314
def offset
  @options[:offset] || ((current_page - 1) * per_page)
end
Also aliased as: offset_value
offset_value()
Alias for: offset
page(page_number) click to toggle source

Kaminari support

# File lib/thinking_sphinx/search.rb, line 220
def page(page_number)
  @options[:page] = page_number
  self
end
page_count()

Compatibility with kaminari and older versions of will_paginate

Alias for: total_pages
per(limit) click to toggle source

Kaminari support

# File lib/thinking_sphinx/search.rb, line 265
def per(limit)
  @options[:limit] = limit
  self
end
per_page() click to toggle source

The amount of records per set of paged results. Defaults to 20 unless a specific page size is requested.

@return [Integer]

# File lib/thinking_sphinx/search.rb, line 256
def per_page
  @options[:limit] ||= @options[:per_page]
  @options[:limit] ||= 20
  @options[:limit].to_i
end
Also aliased as: limit_value
populate_from_queue(results) click to toggle source
# File lib/thinking_sphinx/search.rb, line 405
def populate_from_queue(results)
  return if @populated
  @populated = true
  @results   = results

  compose_results
end
populated?() click to toggle source

Indication of whether the request has been made to Sphinx for the search query.

@return [Boolean] true if the results have been requested.

# File lib/thinking_sphinx/search.rb, line 129
def populated?
  !!@populated
end
previous_page() click to toggle source

The previous page number of the result set. If this is the first page, then nil is returned.

@return [Integer, nil]

# File lib/thinking_sphinx/search.rb, line 247
def previous_page
  current_page == 1 ? nil : current_page - 1
end
query_time() click to toggle source

Query time taken

@return [Integer]

# File lib/thinking_sphinx/search.rb, line 288
def query_time
  populate
  return 0 if @results[:time].nil?

  @query_time ||= @results[:time]
end
respond_to?(method, include_private = false) click to toggle source

Returns true if the Search object or the underlying Array object respond to the requested method.

@param [Symbol] method The method name @return [Boolean] true if either Search or Array responds to the method.

Calls superclass method
# File lib/thinking_sphinx/search.rb, line 202
def respond_to?(method, include_private = false)
  super || @array.respond_to?(method, include_private)
end
results() click to toggle source

The query result hash from Riddle.

@return [Hash] Raw Sphinx results

# File lib/thinking_sphinx/search.rb, line 171
def results
  populate
  @results
end
search_for_ids(*args) click to toggle source
# File lib/thinking_sphinx/search.rb, line 376
def search_for_ids(*args)
  args << args.extract_options!.merge(
    :ignore_default => true,
    :ids_only       => true
  )
  merge_search ThinkingSphinx::Search.new(*args), self.args, options
  self
end
to_a() click to toggle source
# File lib/thinking_sphinx/search.rb, line 102
def to_a
  populate
  @array
end
total_count()

Compatibility with kaminari

Alias for: total_entries
total_entries() click to toggle source

The total number of search results available.

@return [Integer]

# File lib/thinking_sphinx/search.rb, line 299
def total_entries
  populate
  return 0 if @results.nil? || @results[:total_found].nil?

  @total_entries ||= @results[:total_found]
end
Also aliased as: total_count
total_pages() click to toggle source

The total number of pages available if the results are paginated.

@return [Integer]

# File lib/thinking_sphinx/search.rb, line 274
def total_pages
  populate
  return 0 if @results.nil? || @results[:total].nil?

  @total_pages ||= (@results[:total] / per_page.to_f).ceil
end
Also aliased as: page_count, num_pages
warning() click to toggle source

The Sphinx-reported warning, if any.

@return [String, nil]

# File lib/thinking_sphinx/search.rb, line 162
def warning
  populate
  @results[:warning]
end
warning?() click to toggle source

Indication of whether the request resulted in a warning from Sphinx.

@return [Boolean] true if Sphinx reports query warning

# File lib/thinking_sphinx/search.rb, line 154
def warning?
  !!warning
end

Private Instance Methods

add_default_scope() click to toggle source

Adds the default_sphinx_scope if set.

# File lib/thinking_sphinx/search.rb, line 1000
def add_default_scope
  return unless one_class && one_class.has_default_sphinx_scope?
  add_scope(one_class.get_default_sphinx_scope.to_sym)
end
add_excerpter() click to toggle source
# File lib/thinking_sphinx/search.rb, line 506
def add_excerpter
  each do |object|
    next if object.nil?

    object.excerpts = ThinkingSphinx::Excerpter.new self, object
  end
end
add_matching_fields() click to toggle source
# File lib/thinking_sphinx/search.rb, line 525
def add_matching_fields
  each do |object|
    next if object.nil?

    match = match_hash object
    next if match.nil?
    object.matching_fields = ThinkingSphinx::Search.matching_fields(
      @results[:fields], match[:weight]
    )
  end
end
add_scope(method, *args, &block) click to toggle source
# File lib/thinking_sphinx/search.rb, line 1005
def add_scope(method, *args, &block)
  method = "#{method}_without_default".to_sym
  merge_search one_class.send(method, *args, &block), self.args, options
end
add_sphinx_attributes() click to toggle source
# File lib/thinking_sphinx/search.rb, line 514
def add_sphinx_attributes
  each do |object|
    next if object.nil?

    match = match_hash object
    next if match.nil?

    object.sphinx_attributes = match[:attributes]
  end
end
anchor() click to toggle source
# File lib/thinking_sphinx/search.rb, line 818
def anchor
  return {} unless options[:geo] || (options[:lat] && options[:lng])

  {
    :latitude   => options[:geo] ? options[:geo].first : options[:lat],
    :longitude  => options[:geo] ? options[:geo].last  : options[:lng],
    :latitude_attribute  => latitude_attr.to_s,
    :longitude_attribute => longitude_attr.to_s
  }
end
attribute(*keys) click to toggle source
# File lib/thinking_sphinx/search.rb, line 849
def attribute(*keys)
  return nil unless one_class

  keys.detect { |key|
    attributes.include?(key)
  }
end
attributes() click to toggle source
# File lib/thinking_sphinx/search.rb, line 857
def attributes
  return [] unless one_class

  attributes = one_class.sphinx_indexes.collect { |index|
    index.attributes.collect { |attrib| attrib.unique_name }
  }.flatten
end
class_from_crc(crc) click to toggle source
# File lib/thinking_sphinx/search.rb, line 979
def class_from_crc(crc)
  if Riddle.loaded_version.to_i < 2
    config.models_by_crc[crc].constantize
  else
    crc.constantize
  end
end
classes() click to toggle source
# File lib/thinking_sphinx/search.rb, line 626
def classes
  @classes ||= options[:classes] || []
end
comment() click to toggle source
# File lib/thinking_sphinx/search.rb, line 678
def comment
  options[:comment] || ''
end
compose_attributes_results() click to toggle source
# File lib/thinking_sphinx/search.rb, line 480
def compose_attributes_results
  replace @results[:matches].collect { |match|
    attributes = {}
    match[:attributes].each do |name, value|
      attributes[name.to_sym] = match[:attributes][name]
    end
    attributes
  }
end
compose_ids_results() click to toggle source
# File lib/thinking_sphinx/search.rb, line 474
def compose_ids_results
  replace @results[:matches].collect { |match|
    match[:attributes]['sphinx_internal_id']
  }
end
compose_only_results() click to toggle source
# File lib/thinking_sphinx/search.rb, line 490
def compose_only_results
  replace @results[:matches].collect { |match|
    case only = options[:only]
    when String, Symbol
      match[:attributes][only.to_s]
    when Array
      only.inject({}) do |hash, attribute|
        hash[attribute.to_sym] = match[:attributes][attribute.to_s]
        hash
      end
    else
      raise "Unexpected object for :only argument. String or Array is expected, #{only.class} was received."
    end
  }
end
compose_results() click to toggle source
# File lib/thinking_sphinx/search.rb, line 459
def compose_results
  if options[:ids_only]
    compose_ids_results
  elsif options[:attributes_only]
    compose_attributes_results
  elsif options[:only]
    compose_only_results
  else
    replace instances_from_matches
    add_excerpter
    add_sphinx_attributes
    add_matching_fields if client.rank_mode == :fieldmask
  end
end
conditions_as_query() click to toggle source
# File lib/thinking_sphinx/search.rb, line 641
def conditions_as_query
  return '' if @options[:conditions].blank?

  ' ' + @options[:conditions].keys.collect { |key|
    "@#{key} #{options[:conditions][key]}"
  }.join(' ')
end
config() click to toggle source
# File lib/thinking_sphinx/search.rb, line 415
def config
  ThinkingSphinx::Configuration.instance
end
crc_attribute() click to toggle source
# File lib/thinking_sphinx/search.rb, line 1038
def crc_attribute
  Riddle.loaded_version.to_i < 2 ? 'class_crc' : 'sphinx_internal_class'
end
default_star_token() click to toggle source
# File lib/thinking_sphinx/search.rb, line 674
def default_star_token
  DefaultStarToken
end
each_with_attribute(attribute) { |self, (match[attribute] || match["@| ... } click to toggle source
# File lib/thinking_sphinx/search.rb, line 987
def each_with_attribute(attribute, &block)
  populate
  results[:matches].each_with_index do |match, index|
    yield self[index],
      (match[:attributes][attribute] || match[:attributes]["@#{attribute}"])
  end
end
field_names() click to toggle source
# File lib/thinking_sphinx/search.rb, line 718
def field_names
  return [] unless one_class

  one_class.sphinx_indexes.collect { |index|
    index.fields.collect { |field| field.unique_name }
  }.flatten
end
filter_value(value) click to toggle source

When passed a Time instance, returns the integer timestamp.

# File lib/thinking_sphinx/search.rb, line 801
def filter_value(value)
  case value
  when Range
    filter_value(value.first).first..filter_value(value.last).first
  when Array
    value.collect { |v| filter_value(v) }.flatten
  when Time
    [value.to_i]
  when Date
    [Time.utc(value.year, value.month, value.day).to_i]
  when NilClass
    0
  else
    Array(value)
  end
end
filters() click to toggle source
# File lib/thinking_sphinx/search.rb, line 780
def filters
  internal_filters +
  (options[:with] || {}).collect { |attrib, value|
    Riddle::Client::Filter.new attrib.to_s, filter_value(value)
  } +
  (options[:without] || {}).collect { |attrib, value|
    Riddle::Client::Filter.new attrib.to_s, filter_value(value), true
  } +
  (options[:with_all] || {}).collect { |attrib, values|
    Array(values).collect { |value|
      Riddle::Client::Filter.new attrib.to_s, filter_value(value)
    }
  }.flatten +
  (options[:without_any] || {}).collect { |attrib, values|
    Array(values).collect { |value|
      Riddle::Client::Filter.new attrib.to_s, filter_value(value), true
    }
  }.flatten
end
group_by() click to toggle source
# File lib/thinking_sphinx/search.rb, line 754
def group_by
  options[:group] ? options[:group].to_s : nil
end
group_function() click to toggle source
# File lib/thinking_sphinx/search.rb, line 758
def group_function
  options[:group] ? :attr : nil
end
hard_retries() click to toggle source
# File lib/thinking_sphinx/search.rb, line 876
def hard_retries
  options[:hard_retry_count] || config.hard_retry_count
end
include_for_class(klass) click to toggle source
# File lib/thinking_sphinx/search.rb, line 880
def include_for_class(klass)
  includes = options[:include] || klass.sphinx_index_options[:include]

  case includes
  when NilClass
    nil
  when Array
    include_from_array includes, klass
  when Symbol
    klass.reflections[includes].nil? ? nil : includes
  when Hash
    include_from_hash includes, klass
  else
    includes
  end
end
include_from_array(array, klass) click to toggle source
# File lib/thinking_sphinx/search.rb, line 897
def include_from_array(array, klass)
  scoped_array = []
  array.each do |value|
    case value
    when Hash
      scoped_hash = include_from_hash(value, klass)
      scoped_array << scoped_hash unless scoped_hash.nil?
    else
      scoped_array << value unless klass.reflections[value].nil?
    end
  end
  scoped_array.empty? ? nil : scoped_array
end
include_from_hash(hash, klass) click to toggle source
# File lib/thinking_sphinx/search.rb, line 911
def include_from_hash(hash, klass)
  scoped_hash = {}
  hash.keys.each do |key|
    scoped_hash[key] = hash[key] unless klass.reflections[key].nil?
  end
  scoped_hash.empty? ? nil : scoped_hash
end
index_option(key) click to toggle source
# File lib/thinking_sphinx/search.rb, line 841
def index_option(key)
  return nil unless one_class

  one_class.sphinx_indexes.collect { |index|
    index.local_options[key]
  }.compact.first
end
index_weights() click to toggle source

Turn :index_weights => { “foo” => 2, User => 1 } into :index_weights => { “foo” => 2, “user_core” => 1, “user_delta” => 1 }

# File lib/thinking_sphinx/search.rb, line 739
def index_weights
  weights = options[:index_weights] || {}
  weights.keys.inject({}) do |hash, key|
    if key.is_a?(Class)
      name = ThinkingSphinx::Index.name_for(key)
      hash["#{name}_core"]  = weights[key]
      hash["#{name}_delta"] = weights[key]
    else
      hash[key] = weights[key]
    end

    hash
  end
end
instances_from_class(klass, matches) click to toggle source
# File lib/thinking_sphinx/search.rb, line 919
def instances_from_class(klass, matches)
  index_options = klass.sphinx_index_options

  ids = matches.collect { |match| match[:attributes]["sphinx_internal_id"] }
  instances = ids.length > 0 ? klass.unscoped.find(
    :all,
    :joins      => options[:joins],
    :conditions => {klass.primary_key_for_sphinx.to_sym => ids},
    :include    => include_for_class(klass),
    :select     => (options[:select]  || index_options[:select]),
    :order      => (options[:sql_order] || index_options[:sql_order])
  ) : []

  # Raise an exception if we find records in Sphinx but not in the DB, so
  # the search method can retry without them. See
  # ThinkingSphinx::Search.retry_search_on_stale_index.
  if options[:raise_on_stale] && instances.length < ids.length
    stale_ids = ids - instances.map { |i| i.id }
    raise StaleIdsException, stale_ids
  end

  # if the user has specified an SQL order, return the collection
  # without rearranging it into the Sphinx order
  return instances if (options[:sql_order] || index_options[:sql_order])

  ids.collect { |obj_id|
    instances.detect do |obj|
      obj.primary_key_for_sphinx == obj_id
    end
  }
end
instances_from_matches() click to toggle source

Group results by class and call find(:all) once for each group to reduce the number of find's in multi-model searches.

# File lib/thinking_sphinx/search.rb, line 954
def instances_from_matches
  return single_class_results if one_class

  groups = results[:matches].group_by { |match|
    match[:attributes][crc_attribute]
  }
  groups.each do |crc, group|
    group.replace(
      instances_from_class(class_from_crc(crc), group)
    )
  end

  results[:matches].collect do |match|
    groups.detect { |crc, group|
      crc == match[:attributes][crc_attribute]
    }[1].compact.detect { |obj|
      obj.primary_key_for_sphinx == match[:attributes]["sphinx_internal_id"]
    }
  end
end
internal_filters() click to toggle source
# File lib/thinking_sphinx/search.rb, line 762
def internal_filters
  filters = [Riddle::Client::Filter.new('sphinx_deleted', [0])]

  class_crcs = classes.collect { |klass|
    klass.to_crc32s
  }.flatten

  unless class_crcs.empty?
    filters << Riddle::Client::Filter.new('class_crc', class_crcs)
  end

  filters << Riddle::Client::Filter.new(
    'sphinx_internal_id', filter_value(options[:without_ids]), true
  ) unless options[:without_ids].nil? || options[:without_ids].empty?

  filters
end
is_scope?(method) click to toggle source
# File lib/thinking_sphinx/search.rb, line 995
def is_scope?(method)
  one_class && one_class.sphinx_scopes.include?(method)
end
latitude_attr() click to toggle source
# File lib/thinking_sphinx/search.rb, line 829
def latitude_attr
  options[:latitude_attr]      ||
  index_option(:latitude_attr) ||
  attribute(:lat, :latitude)
end
log(query, &block) click to toggle source
# File lib/thinking_sphinx/search.rb, line 563
def log(query, &block)
  self.class.log(query, &block)
end
longitude_attr() click to toggle source
# File lib/thinking_sphinx/search.rb, line 835
def longitude_attr
  options[:longitude_attr]      ||
  index_option(:longitude_attr) ||
  attribute(:lon, :lng, :longitude)
end
match_hash(object) click to toggle source
# File lib/thinking_sphinx/search.rb, line 537
def match_hash(object)
  @results[:matches].detect { |match|
    class_crc = object.class.name
    class_crc = object.class.to_crc32 if Riddle.loaded_version.to_i < 2

    match[:attributes]['sphinx_internal_id'] == object.
      primary_key_for_sphinx &&
    match[:attributes][crc_attribute] == class_crc
  }
end
match_mode() click to toggle source
# File lib/thinking_sphinx/search.rb, line 682
def match_mode
  options[:match_mode] || (options[:conditions].blank? ? :all : :extended)
end
one_class() click to toggle source
# File lib/thinking_sphinx/search.rb, line 630
def one_class
  @one_class ||= classes.length != 1 ? nil : classes.first
end
populate() click to toggle source
# File lib/thinking_sphinx/search.rb, line 419
def populate
  return if @populated
  @populated = true
  retries    = hard_retries

  begin
    retry_on_stale_index do
      begin
        log query do
          @results = client.query query, indexes, comment
        end
        total = @results[:total_found].to_i
        log "Found #{total} result#{'s' unless total == 1}"

        log "Sphinx Daemon returned warning: #{warning}" if warning?

        if error?
          log "Sphinx Daemon returned error: #{error}"
          raise SphinxError.new(error, @results) unless options[:ignore_errors]
        end
      rescue Errno::ECONNREFUSED => err
        raise ThinkingSphinx::ConnectionError,
          'Connection to Sphinx Daemon (searchd) failed.'
      end

      compose_results
    end
  rescue => e
    log 'Caught Sphinx exception: %s (%s %s left)' % [
      e.message, retries, (retries == 1 ? 'try' : 'tries')
    ]
    retries -= 1
    if retries >= 0
      retry
    else
      raise e
    end
  end
end
prepare(client) click to toggle source
# File lib/thinking_sphinx/search.rb, line 567
def prepare(client)
  index_options = {}
  if one_class && one_class.sphinx_indexes && one_class.sphinx_indexes.first
    index_options = one_class.sphinx_indexes.first.local_options
  end

  [
    :max_matches, :group_by, :group_function, :group_clause,
    :group_distinct, :id_range, :cut_off, :retry_count, :retry_delay,
    :rank_mode, :rank_expr, :max_query_time, :field_weights
  ].each do |key|
    value = options[key] || index_options[key]
    client.send("#{key}=", value) if value
  end

  # treated non-standard as :select is already used for AR queries
  client.select = options[:sphinx_select] || '*'

  client.limit      = per_page
  client.offset     = offset
  client.match_mode = match_mode
  client.filters    = filters
  client.sort_mode  = sort_mode
  client.sort_by    = sort_by
  client.group_by   = group_by if group_by
  client.group_function = group_function if group_function
  client.index_weights  = index_weights
  client.anchor     = anchor

  client
end
query() click to toggle source
# File lib/thinking_sphinx/search.rb, line 634
def query
  @query ||= begin
    q = @args.join(' ') << conditions_as_query
    (options[:star] ? star_query(q) : q).strip
  end
end
retry_on_stale_index(&block) click to toggle source
# File lib/thinking_sphinx/search.rb, line 599
def retry_on_stale_index(&block)
  stale_ids = []
  retries   = stale_retries

  begin
    options[:raise_on_stale] = retries > 0
    block.call

    # If ThinkingSphinx::Search#instances_from_matches found records in
    # Sphinx but not in the DB and the :raise_on_stale option is set, this
    # exception is raised. We retry a limited number of times, excluding the
    # stale ids from the search.
  rescue StaleIdsException => err
    retries -= 1

    # For logging
    stale_ids |= err.ids
    # ID exclusion
    options[:without_ids] = Array(options[:without_ids]) | err.ids

    log 'Stale Ids (%s %s left): %s' % [
      retries, (retries == 1 ? 'try' : 'tries'), stale_ids.join(', ')
    ]
    retry
  end
end
scoped_count() click to toggle source
# File lib/thinking_sphinx/search.rb, line 1027
def scoped_count
  return self.total_entries if(@options[:ids_only] || @options[:only])

  @options[:ids_only] = true
  results_count = self.total_entries
  @options[:ids_only] = false
  @populated = false

  results_count
end
single_class_results() click to toggle source
# File lib/thinking_sphinx/search.rb, line 975
def single_class_results
  instances_from_class one_class, results[:matches]
end
sort_by() click to toggle source
# File lib/thinking_sphinx/search.rb, line 706
def sort_by
  case @sort_by = (options[:sort_by] || options[:order])
  when String
    sorted_fields_to_attributes(@sort_by.clone)
  when Symbol
    field_names.include?(@sort_by) ?
      @sort_by.to_s.concat('_sort') : @sort_by.to_s
  else
    ''
  end
end
sort_mode() click to toggle source
# File lib/thinking_sphinx/search.rb, line 686
def sort_mode
  @sort_mode ||= case options[:sort_mode]
  when :asc
    :attr_asc
  when :desc
    :attr_desc
  when nil
    case options[:order]
    when String
      :extended
    when Symbol
      :attr_asc
    else
      :relevance
    end
  else
    options[:sort_mode]
  end
end
sorted_fields_to_attributes(order_string) click to toggle source
# File lib/thinking_sphinx/search.rb, line 726
def sorted_fields_to_attributes(order_string)
  field_names.each { |field|
    order_string.gsub!(/(^|\s)#{field}(,?\s|$)/) { |match|
      match.gsub field.to_s, field.to_s.concat("_sort")
    }
  }

  order_string
end
stale_retries() click to toggle source
# File lib/thinking_sphinx/search.rb, line 865
def stale_retries
  case options[:retry_stale]
  when TrueClass
    3
  when nil, FalseClass
    0
  else
    options[:retry_stale].to_i
  end
end
star_query(query) click to toggle source
# File lib/thinking_sphinx/search.rb, line 649
def star_query(query)
  token = options[:star].is_a?(Regexp) ? options[:star] : default_star_token

  query.gsub(/("#{token}(.*?#{token})?"|(?![!-])#{token})/) do
    pre, proper, post = $`, $&, $'
    # E.g. "@foo", "/2", "~3", but not as part of a token
    is_operator = pre.match(%r{(\W|^)[@~/]\Z}) ||
                  pre.match(%r{(\W|^)@\([^\)]*$})
    # E.g. "foo bar", with quotes
    is_quote    = proper.starts_with?('"') && proper.ends_with?('"')
    has_star    = pre.ends_with?("*") || post.starts_with?("*")
    if is_operator || is_quote || has_star
      proper
    else
      "*#{proper}*"
    end
  end
end