Module: Sphincter::Search

Included in:
ActiveRecord::Base
Defined in:
lib/sphincter/search.rb

Overview

Search extension for ActiveRecord::Base that automatically extends ActiveRecord::Base.

Defined Under Namespace

Classes: Results

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.indexesObject

Accessor for indexes registered with add_index.



28
29
30
# File 'lib/sphincter/search.rb', line 28

def self.indexes
  @@indexes
end

Instance Method Details

#add_index(options = {}) ⇒ Object

Adds an index with options.

add_index will automatically add Sphincter::AssociationSearcher to any has_many associations referenced by this model’s belongs_to associations. If this model belongs_to another model, and that model has_many of this model, then you will be able to other.models.search and recieve only records in the association.

Currently, only has_many associations without conditions will have AssociationSearcher appended.

Options are:

:name

Name of index. Defaults to ActiveRecord::Base::table_name.

:fields

Array of fields to index. Foreign key columns for belongs_to associations are automatically added. Fields from associations may be included by using “association.field”.

:conditions

Array of SQL conditions that will be ANDed together to predicate inclusion in the search index.

Example:

class Post < ActiveRecord::Base
  belongs_to :user
  belongs_to :blog
  has_many :comments

  add_index :fields => %w[title body user.name, comments.body],
            :conditions => ['published = 1']
end

When including fields from associations, MySQL’s GROUP_CONCAT() function is used. By default this will create a string up to 1024 characters long. A larger string can be used by changing the value of MySQL’s group_concat_max_len variable. To do this, add the following to your sphincter.RAILS_ENV.yml files:

mysql:
  sql_query_pre:
    - SET NAMES utf8
    - SET SESSION group_concat_max_len = VALUE


75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/sphincter/search.rb', line 75

def add_index(options = {})
  options[:fields] ||= []

  reflect_on_all_associations.each do |my_assoc|
    next unless my_assoc.macro == :belongs_to

    options[:fields] << my_assoc.primary_key_name.to_s

    has_many_klass = my_assoc.class_name.constantize

    has_many_klass.reflect_on_all_associations.each do |opp_assoc|
      next if opp_assoc.class_name != name or
              opp_assoc.macro != :has_many or
              opp_assoc.options[:conditions]

      extends = Array(opp_assoc.options[:extend])
      extends << Sphincter::AssociationSearcher
      opp_assoc.options[:extend] = extends
    end
  end

  options[:fields].uniq!

  Sphincter::Search.indexes[self] << options
end

#search(query, options = {}) ⇒ Object

Searches for query with options.

Allowed options are:

:between

Hash of Sphinx range filter conditions. Hash keys are sphinx group_column or date_column names. Values can be Date/Time/DateTime or Integers.

:conditions

Hash of Sphinx value filter conditions. Hash keys are sphinx group_column or date_column names. Values can be a single value or an Array of values.

:index

Name of Sphinx index to search. Defaults to ActiveRecord::Base::table_name.

:page

Page offset of records to return, for easy use with paginators.

:per_page

Size of a page. Default page size is controlled by the configuration.

Returns a Sphincter::Search::Results object.



138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
# File 'lib/sphincter/search.rb', line 138

def search(query, options = {})
  sphinx = Sphinx::Client.new

  @host ||= Sphincter::Configure.get_conf['sphincter']['host']
  @port ||= Sphincter::Configure.get_conf['sphincter']['port']

  sphinx.SetServer @host, @port

  options[:conditions] ||= {}
  options[:conditions].each do |column, values|
    values = sphincter_convert_values Array(values)
    sphinx.SetFilter column.to_s, values
  end

  options[:between] ||= {}
  options[:between].each do |column, between|
    min, max = sphincter_convert_values between

    sphinx.SetFilterRange column.to_s, min, max
  end

  @default_per_page ||= Sphincter::Configure.get_conf['sphincter']['per_page']

  per_page = options[:per_page] || @default_per_page
  page_offset = options.key?(:page) ? options[:page] - 1 : 0
  offset = page_offset * per_page

  sphinx.SetLimits offset, per_page

  index_name = options[:index] || table_name

  sphinx_result = sphinx.Query query, index_name

  matches = sphinx_result['matches'].sort_by do |id, match|
    -match['index'] # #find reverses, lame!
  end

  ids = matches.map do |id, match|
    (id - match['attrs']['sphincter_index_id']) /
      Sphincter::Configure.index_count
  end

  results = Results.new

  results.records = find ids
  results.total = sphinx_result['total_found']
  results.per_page = per_page

  results
end

#sphincter_convert_values(values) ⇒ Object

Converts values into an Array of values SetFilter can digest.

true/false becomes 1/0, Time/Date/DateTime becomes a time in epoch seconds. Everything else is passed straight through.



107
108
109
110
111
112
113
114
115
116
117
# File 'lib/sphincter/search.rb', line 107

def sphincter_convert_values(values)
  values.map do |value|
    case value
    when Date, DateTime then Time.parse(value.to_s).to_i
    when FalseClass then 0
    when Time then value.to_i
    when TrueClass then 1
    else value
    end
  end
end