DrMark-thinking-sphinx 0.9.7 → 0.9.8

Sign up to get free protection for your applications and to get access to all the features.
data/README CHANGED
@@ -4,6 +4,8 @@
4
4
 
5
5
  First, if you haven't done so already, check out the main usage[http://ts.freelancing-gods.com/usage.html] page. Once you've done that, the next place to look for information is the specific method docs - ThinkingSphinx::Search and ThinkingSphinx::Index::Builder in particular.
6
6
 
7
+ Keep in mind that while Thinking Sphinx works for ActiveRecord with Merb, it doesn't yet support DataMapper (although that is planned).
8
+
7
9
  == Contributing
8
10
 
9
11
  Fork on GitHub and after you've committed tested patches, send a pull request.
@@ -49,4 +51,7 @@ Since I first released this library, there's been quite a few people who have su
49
51
  - Chris Heald
50
52
  - Peter Vandenberk
51
53
  - Josh French
52
- - Andrew Bennett
54
+ - Andrew Bennett
55
+ - Jordan Fowler
56
+ - Seth Walker
57
+ - Joe Noon
@@ -18,7 +18,7 @@ module Riddle
18
18
  def query_message
19
19
  message = Message.new
20
20
 
21
- message.append_string self.attribute
21
+ message.append_string self.attribute.to_s
22
22
  case self.values
23
23
  when Range
24
24
  if self.values.first.is_a?(Float) && self.values.last.is_a?(Float)
data/lib/riddle/client.rb CHANGED
@@ -100,7 +100,7 @@ module Riddle
100
100
  :match_mode, :sort_mode, :sort_by, :weights, :id_range, :filters,
101
101
  :group_by, :group_function, :group_clause, :group_distinct, :cut_off,
102
102
  :retry_count, :retry_delay, :anchor, :index_weights, :rank_mode,
103
- :max_query_time, :field_weights
103
+ :max_query_time, :field_weights, :timeout
104
104
  attr_reader :queue
105
105
 
106
106
  # Can instantiate with a specific server and port - otherwise it assumes
@@ -134,10 +134,40 @@ module Riddle
134
134
  @max_query_time = 0
135
135
  # string keys are field names, integer values are weightings
136
136
  @field_weights = {}
137
+ @timeout = 0
137
138
 
138
139
  @queue = []
139
140
  end
140
141
 
142
+ # Reset attributes and settings to defaults.
143
+ def reset
144
+ # defaults
145
+ @offset = 0
146
+ @limit = 20
147
+ @max_matches = 1000
148
+ @match_mode = :all
149
+ @sort_mode = :relevance
150
+ @sort_by = ''
151
+ @weights = []
152
+ @id_range = 0..0
153
+ @filters = []
154
+ @group_by = ''
155
+ @group_function = :day
156
+ @group_clause = '@group desc'
157
+ @group_distinct = ''
158
+ @cut_off = 0
159
+ @retry_count = 0
160
+ @retry_delay = 0
161
+ @anchor = {}
162
+ # string keys are index names, integer values are weightings
163
+ @index_weights = {}
164
+ @rank_mode = :proximity_bm25
165
+ @max_query_time = 0
166
+ # string keys are field names, integer values are weightings
167
+ @field_weights = {}
168
+ @timeout = 0
169
+ end
170
+
141
171
  # Set the geo-anchor point - with the names of the attributes that contain
142
172
  # the latitude and longitude (in radians), and the reference position.
143
173
  # Note that for geocoding to work properly, you must also set
@@ -149,12 +179,12 @@ module Riddle
149
179
  # Example:
150
180
  # client.set_anchor('lat', -0.6591741, 'long', 2.530770)
151
181
  #
152
- def set_anchor(lat_attr, lat, long_attr, long)
182
+ def set_anchor(lat_attr, lat, long_attr, long, units='')
153
183
  @anchor = {
154
184
  :latitude_attribute => lat_attr,
155
- :latitude => lat,
185
+ :latitude => (units == 'degrees' ? lat * Math::PI / 180 : lat),
156
186
  :longitude_attribute => long_attr,
157
- :longitude => long
187
+ :longitude => (units == 'degrees' ? long * Math::PI / 180 : long)
158
188
  }
159
189
  end
160
190
 
@@ -384,6 +414,26 @@ module Riddle
384
414
  # Connects to the Sphinx daemon, and yields a socket to use. The socket is
385
415
  # closed at the end of the block.
386
416
  def connect(&block)
417
+ socket = nil
418
+ if @timeout == 0
419
+ socket = initialise_connection
420
+ else
421
+ begin
422
+ Timeout.timeout(@timeout) { socket = initialise_connection }
423
+ rescue Timeout::Error
424
+ raise Riddle::ConnectionError,
425
+ "Connection to #{@server} on #{@port} timed out after #{@timeout} seconds"
426
+ end
427
+ end
428
+
429
+ begin
430
+ yield socket
431
+ ensure
432
+ socket.close
433
+ end
434
+ end
435
+
436
+ def initialise_connection
387
437
  socket = TCPSocket.new @server, @port
388
438
 
389
439
  # Checking version
@@ -396,11 +446,7 @@ module Riddle
396
446
  # Send version
397
447
  socket.send [1].pack('N'), 0
398
448
 
399
- begin
400
- yield socket
401
- ensure
402
- socket.close
403
- end
449
+ socket
404
450
  end
405
451
 
406
452
  # Send a collection of messages, for a command type (eg, search, excerpts,
@@ -507,7 +553,7 @@ module Riddle
507
553
  # Per Index Weights
508
554
  message.append_int @index_weights.length
509
555
  @index_weights.each do |key,val|
510
- message.append_string key
556
+ message.append_string key.to_s
511
557
  message.append_int val
512
558
  end
513
559
 
@@ -517,7 +563,7 @@ module Riddle
517
563
  # Per Field Weights
518
564
  message.append_int @field_weights.length
519
565
  @field_weights.each do |key,val|
520
- message.append_string key
566
+ message.append_string key.to_s
521
567
  message.append_int val
522
568
  end
523
569
 
data/lib/riddle.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require 'socket'
2
+ require 'timeout'
2
3
  require 'riddle/client'
3
4
  require 'riddle/client/filter'
4
5
  require 'riddle/client/message'
@@ -9,14 +10,17 @@ module Riddle #:nodoc:
9
10
  end
10
11
 
11
12
  module Version #:nodoc:
12
- Major = 0
13
- Minor = 9
14
- Tiny = 8
13
+ Major = 0
14
+ Minor = 9
15
+ Tiny = 8
15
16
  # Revision number for RubyForge's sake, taken from what Sphinx
16
17
  # outputs to the command line.
17
- Rev = 1198
18
+ Rev = 1371
19
+ # Release number to mark my own fixes, beyond feature parity with
20
+ # Sphinx itself.
21
+ Release = 0
18
22
 
19
- String = [Major, Minor, Tiny].join('.') + "rc1"
20
- GemVersion = [Major, Minor, Tiny, Rev].join('.')
23
+ String = [Major, Minor, Tiny].join('.')
24
+ GemVersion = [Major, Minor, Tiny, Rev, Release].join('.')
21
25
  end
22
26
  end
@@ -73,10 +73,7 @@ module ThinkingSphinx
73
73
  # if running in the test environment.
74
74
  #
75
75
  def index_delta
76
- if ThinkingSphinx::Configuration.environment == "test" ||
77
- !ThinkingSphinx.deltas_enabled?
78
- return true
79
- end
76
+ return true unless ThinkingSphinx.deltas_enabled?
80
77
 
81
78
  configuration = ThinkingSphinx::Configuration.new
82
79
  system "indexer --config #{configuration.config_file} --rotate #{self.class.indexes.first.name}_delta"
@@ -10,9 +10,8 @@ module ThinkingSphinx
10
10
  module ActiveRecord
11
11
  def self.included(base)
12
12
  base.class_eval do
13
+ class_inheritable_array :indexes
13
14
  class << self
14
- attr_accessor :indexes
15
-
16
15
  # Allows creation of indexes for Sphinx. If you don't do this, there
17
16
  # isn't much point trying to search (or using this plugin at all,
18
17
  # really).
@@ -65,10 +64,10 @@ module ThinkingSphinx
65
64
  def define_index(&block)
66
65
  return unless ThinkingSphinx.define_indexes?
67
66
 
68
- @indexes ||= []
67
+ self.indexes ||= []
69
68
  index = Index.new(self, &block)
70
69
 
71
- @indexes << index
70
+ self.indexes << index
72
71
  unless ThinkingSphinx.indexed_models.include?(self.name)
73
72
  ThinkingSphinx.indexed_models << self.name
74
73
  end
@@ -132,7 +131,9 @@ module ThinkingSphinx
132
131
  "#{self.class.indexes.first.name}_delta",
133
132
  ['sphinx_deleted'],
134
133
  {self.id => 1}
135
- ) if self.class.indexes.any? { |index| index.delta? } && self.delta?
134
+ ) if ThinkingSphinx.deltas_enabled? &&
135
+ self.class.indexes.any? { |index| index.delta? } &&
136
+ self.delta?
136
137
  end
137
138
  end
138
139
  end
@@ -11,7 +11,7 @@ module ThinkingSphinx
11
11
  # query log file:: log/searchd.query.log
12
12
  # pid file:: log/searchd.#{environment}.pid
13
13
  # searchd files:: db/sphinx/#{environment}/
14
- # address:: 0.0.0.0 (all)
14
+ # address:: 127.0.0.1
15
15
  # port:: 3312
16
16
  # allow star:: false
17
17
  # min prefix length:: 1
@@ -63,7 +63,7 @@ module ThinkingSphinx
63
63
  self.query_log_file = "#{self.app_root}/log/searchd.query.log"
64
64
  self.pid_file = "#{self.app_root}/log/searchd.#{environment}.pid"
65
65
  self.searchd_file_path = "#{self.app_root}/db/sphinx/#{environment}"
66
- self.address = "0.0.0.0"
66
+ self.address = "127.0.0.1"
67
67
  self.port = 3312
68
68
  self.allow_star = false
69
69
  self.min_prefix_len = 1
@@ -165,7 +165,7 @@ searchd
165
165
  }
166
166
 
167
167
  begin
168
- model_name.classify.constantize
168
+ model_name.camelize.constantize
169
169
  rescue LoadError
170
170
  model_name.gsub!(/.*[\/\\]/, '')
171
171
  retry
@@ -41,6 +41,11 @@ module ThinkingSphinx
41
41
  model.name.underscore.tr(':/\\', '_')
42
42
  end
43
43
 
44
+ def empty?(part = :core)
45
+ config = ThinkingSphinx::Configuration.new
46
+ File.size?("#{config.searchd_file_path}/#{self.name}_#{part}.spa").nil?
47
+ end
48
+
44
49
  def to_config(index, database_conf, charset_type)
45
50
  # Set up associations and joins
46
51
  link!
@@ -252,7 +257,7 @@ GROUP BY #{ (
252
257
 
253
258
  unless @model.descends_from_active_record?
254
259
  stored_class = @model.store_full_sti_class ? @model.name : @model.name.demodulize
255
- builder.where("#{@model.inheritance_column} = '#{stored_class}'")
260
+ builder.where("#{@model.quoted_table_name}.#{quote_column(@model.inheritance_column)} = '#{stored_class}'")
256
261
  end
257
262
 
258
263
  @fields = builder.fields
@@ -5,7 +5,7 @@ module ThinkingSphinx
5
5
  # Most times, you will just want a specific model's results - to search and
6
6
  # search_for_ids methods will do the job in exactly the same manner when
7
7
  # called from a model.
8
- #
8
+ #
9
9
  class Search
10
10
  class << self
11
11
  # Searches for results that match the parameters provided. Will only
@@ -14,16 +14,23 @@ module ThinkingSphinx
14
14
  #
15
15
  def search_for_ids(*args)
16
16
  results, client = search_results(*args.clone)
17
-
17
+
18
18
  options = args.extract_options!
19
19
  page = options[:page] ? options[:page].to_i : 1
20
-
20
+
21
21
  begin
22
- pager = WillPaginate::Collection.new(page,
23
- client.limit, results[:total] || 0)
24
- pager.replace results[:matches].collect { |match| match[:doc] }
22
+ pager = WillPaginate::Collection.create(page,
23
+ client.limit, results[:total_found] || 0) do |collection|
24
+ collection.replace results[:matches].collect { |match| match[:doc] }
25
+ collection.instance_variable_set :@total_entries, results[:total_found]
26
+ end
27
+ return (options[:include_raw] ? [pager, results] : pager)
25
28
  rescue
26
- results[:matches].collect { |match| match[:doc] }
29
+ if options[:include_raw]
30
+ return results[:matches].collect { |match| match[:doc] }, results
31
+ else
32
+ return results[:matches].collect { |match| match[:doc] }
33
+ end
27
34
  end
28
35
  end
29
36
 
@@ -35,11 +42,11 @@ module ThinkingSphinx
35
42
  # just like paginate. The same parameters - :page and :per_page - work as
36
43
  # expected, and the returned result set can be used by the will_paginate
37
44
  # helper.
38
- #
45
+ #
39
46
  # == Basic Searching
40
47
  #
41
48
  # The simplest way of searching is straight text.
42
- #
49
+ #
43
50
  # ThinkingSphinx::Search.search "pat"
44
51
  # ThinkingSphinx::Search.search "google"
45
52
  # User.search "pat", :page => (params[:page] || 1)
@@ -47,14 +54,32 @@ module ThinkingSphinx
47
54
  #
48
55
  # If you specify :include, like in an #find call, this will be respected
49
56
  # when loading the relevant models from the search results.
50
- #
57
+ #
51
58
  # User.search "pat", :include => :posts
52
59
  #
60
+ # == Advanced Searching
61
+ #
62
+ # Sphinx supports 5 different matching modes. By default Thinking Sphinx
63
+ # uses :all, which unsurprisingly requires all the supplied search terms
64
+ # to match a result.
65
+ #
66
+ # Alternative modes include:
67
+ #
68
+ # User.search "pat allan", :match_mode => :any
69
+ # User.search "pat allan", :match_mode => :phrase
70
+ # User.search "pat | allan", :match_mode => :boolean
71
+ # User.search "@name pat | @username pat", :match_mode => :extended
72
+ #
73
+ # Any will find results with any of the search terms. Phrase treats the search
74
+ # terms a single phrase instead of individual words. Boolean and extended allow
75
+ # for more complex query syntax, refer to the sphinx documentation for further
76
+ # details.
77
+ #
53
78
  # == Searching by Fields
54
- #
79
+ #
55
80
  # If you want to step it up a level, you can limit your search terms to
56
81
  # specific fields:
57
- #
82
+ #
58
83
  # User.search :conditions => {:name => "pat"}
59
84
  #
60
85
  # This uses Sphinx's extended match mode, unless you specify a different
@@ -74,11 +99,11 @@ module ThinkingSphinx
74
99
  # (not multi-model searching). With a single model, Thinking Sphinx
75
100
  # can figure out what attributes and fields are available, so you can
76
101
  # put it all in the :conditions hash, and it will sort it out.
77
- #
102
+ #
78
103
  # Node.search :conditions => {:parent_id => 10}
79
- #
104
+ #
80
105
  # Filters can be single values, arrays of values, or ranges.
81
- #
106
+ #
82
107
  # Article.search "East Timor", :conditions => {:rating => 3..5}
83
108
  #
84
109
  # == Excluding by Attributes
@@ -87,7 +112,7 @@ module ThinkingSphinx
87
112
  # attribute values to exclude. This is done with the :without option:
88
113
  #
89
114
  # User.search :without => {:role_id => 1}
90
- #
115
+ #
91
116
  # == Sorting
92
117
  #
93
118
  # Sphinx can only sort by attributes, so generally you will need to avoid
@@ -112,13 +137,13 @@ module ThinkingSphinx
112
137
  # detail though.
113
138
  #
114
139
  # == Grouping
115
- #
140
+ #
116
141
  # For this you can use the group_by, group_clause and group_function
117
142
  # options - which are all directly linked to Sphinx's expectations. No
118
143
  # magic from Thinking Sphinx. It can get a little tricky, so make sure
119
144
  # you read all the relevant
120
145
  # documentation[http://sphinxsearch.com/doc.html#clustering] first.
121
- #
146
+ #
122
147
  # Yes this section will be expanded, but this is a start.
123
148
  #
124
149
  # == Geo/Location Searching
@@ -128,11 +153,11 @@ module ThinkingSphinx
128
153
  # take advantage of this, you will need to have both of those values in
129
154
  # attributes. To search with that point, you can then use one of the
130
155
  # following syntax examples:
131
- #
156
+ #
132
157
  # Address.search "Melbourne", :geo => [1.4, -2.217]
133
158
  # Address.search "Australia", :geo => [-0.55, 3.108],
134
159
  # :latitude_attr => "latit", :longitude_attr => "longit"
135
- #
160
+ #
136
161
  # The first example applies when your latitude and longitude attributes
137
162
  # are named any of lat, latitude, lon, long or longitude. If that's not
138
163
  # the case, you will need to explicitly state them in your search, _or_
@@ -141,17 +166,17 @@ module ThinkingSphinx
141
166
  # define_index do
142
167
  # has :latit # Float column, stored in radians
143
168
  # has :longit # Float column, stored in radians
144
- #
169
+ #
145
170
  # set_property :latitude_attr => "latit"
146
171
  # set_property :longitude_attr => "longit"
147
172
  # end
148
- #
173
+ #
149
174
  # Now, geo-location searching really only has an affect if you have a
150
175
  # filter, sort or grouping clause related to it - otherwise it's just a
151
176
  # normal search. To make use of the positioning difference, use the
152
177
  # special attribute "@geodist" in any of your filters or sorting or grouping
153
178
  # clauses.
154
- #
179
+ #
155
180
  # And don't forget - both the latitude and longitude you use in your
156
181
  # search, and the values in your indexes, need to be stored as a float in radians,
157
182
  # _not_ degrees. Keep in mind that if you do this conversion in SQL
@@ -161,75 +186,82 @@ module ThinkingSphinx
161
186
  # has 'RADIANS(lat)', :as => :lat, :type => :float
162
187
  # # ...
163
188
  # end
164
- #
189
+ #
165
190
  def search(*args)
166
191
  results, client = search_results(*args.clone)
167
-
192
+
168
193
  ::ActiveRecord::Base.logger.error(
169
194
  "Sphinx Error: #{results[:error]}"
170
195
  ) if results[:error]
171
-
196
+
172
197
  options = args.extract_options!
173
198
  klass = options[:class]
174
199
  page = options[:page] ? options[:page].to_i : 1
175
-
200
+
176
201
  begin
177
- pager = WillPaginate::Collection.new(page,
178
- client.limit, results[:total] || 0)
179
- pager.replace instances_from_results(results[:matches], options, klass)
202
+ pager = WillPaginate::Collection.create(page,
203
+ client.limit, results[:total] || 0) do |collection|
204
+ collection.replace instances_from_results(results[:matches], options, klass)
205
+ collection.instance_variable_set :@total_entries, results[:total_found]
206
+ end
207
+ return (options[:include_raw] ? [pager, results] : pager)
180
208
  rescue StandardError => err
181
- instances_from_results(results[:matches], options, klass)
209
+ if options[:include_raw]
210
+ return instances_from_results(results[:matches], options, klass), results
211
+ else
212
+ return instances_from_results(results[:matches], options, klass)
213
+ end
182
214
  end
183
215
  end
184
-
216
+
185
217
  # Checks if a document with the given id exists within a specific index.
186
218
  # Expected parameters:
187
219
  #
188
220
  # - ID of the document
189
221
  # - Index to check within
190
222
  # - Options hash (defaults to {})
191
- #
223
+ #
192
224
  # Example:
193
- #
225
+ #
194
226
  # ThinkingSphinx::Search.search_for_id(10, "user_core", :class => User)
195
- #
227
+ #
196
228
  def search_for_id(*args)
197
229
  options = args.extract_options!
198
230
  client = client_from_options options
199
-
231
+
200
232
  query, filters = search_conditions(
201
233
  options[:class], options[:conditions] || {}
202
234
  )
203
235
  client.filters += filters
204
236
  client.match_mode = :extended unless query.empty?
205
237
  client.id_range = args.first..args.first
206
-
238
+
207
239
  begin
208
240
  return client.query(query, args[1])[:matches].length > 0
209
241
  rescue Errno::ECONNREFUSED => err
210
242
  raise ThinkingSphinx::ConnectionError, "Connection to Sphinx Daemon (searchd) failed."
211
243
  end
212
244
  end
213
-
245
+
214
246
  private
215
-
247
+
216
248
  # This method handles the common search functionality, and returns both
217
249
  # the result hash and the client. Not super elegant, but it'll do for
218
250
  # the moment.
219
- #
251
+ #
220
252
  def search_results(*args)
221
253
  options = args.extract_options!
222
254
  client = client_from_options options
223
-
255
+
224
256
  query, filters = search_conditions(
225
257
  options[:class], options[:conditions] || {}
226
258
  )
227
259
  client.filters += filters
228
260
  client.match_mode = :extended unless query.empty?
229
261
  query = args.join(" ") + query
230
-
262
+
231
263
  set_sort_options! client, options
232
-
264
+
233
265
  client.limit = options[:per_page].to_i if options[:per_page]
234
266
  page = options[:page] ? options[:page].to_i : 1
235
267
  client.offset = (page - 1) * client.limit
@@ -241,10 +273,21 @@ module ThinkingSphinx
241
273
  rescue Errno::ECONNREFUSED => err
242
274
  raise ThinkingSphinx::ConnectionError, "Connection to Sphinx Daemon (searchd) failed."
243
275
  end
244
-
276
+
245
277
  return results, client
246
278
  end
247
-
279
+
280
+ # This function loops over the records and appends a 'distance' variable to each one with
281
+ # the value from Sphinx
282
+ def append_distances(instances, results, distance_name)
283
+ instances.each_with_index do |record, index|
284
+ if record
285
+ distance = (results[index][:attributes]['@geodist'] or nil)
286
+ record.instance_variable_get('@attributes')["#{distance_name}"] = distance
287
+ end
288
+ end
289
+ end
290
+
248
291
  def instances_from_results(results, options = {}, klass = nil)
249
292
  if klass.nil?
250
293
  results.collect { |result| instance_from_result result, options }
@@ -256,43 +299,47 @@ module ThinkingSphinx
256
299
  :include => options[:include],
257
300
  :select => options[:select]
258
301
  )
259
- ids.collect { |obj_id| instances.detect { |obj| obj.id == obj_id } }
302
+ final_instances = ids.collect { |obj_id| instances.detect { |obj| obj.id == obj_id } }
303
+
304
+ final_instances = append_distances(final_instances, results, options[:distance_name]) if options[:distance_name] && (results.collect { |result| result[:attributes]['@geodist'] }.length > 0)
305
+
306
+ return final_instances
260
307
  end
261
308
  end
262
-
309
+
263
310
  # Either use the provided class to instantiate a result from a model, or
264
311
  # get the result's CRC value and determine the class from that.
265
- #
312
+ #
266
313
  def instance_from_result(result, options)
267
314
  class_from_crc(result[:attributes]["class_crc"]).find(
268
315
  result[:doc], :include => options[:include], :select => options[:select]
269
316
  )
270
317
  end
271
-
318
+
272
319
  # Convert a CRC value to the corresponding class.
273
- #
320
+ #
274
321
  def class_from_crc(crc)
275
322
  unless @models_by_crc
276
323
  Configuration.new.load_models
277
-
324
+
278
325
  @models_by_crc = ThinkingSphinx.indexed_models.inject({}) do |hash, model|
279
326
  hash[model.constantize.to_crc32] = model
280
327
  hash
281
328
  end
282
329
  end
283
-
330
+
284
331
  @models_by_crc[crc].constantize
285
332
  end
286
-
333
+
287
334
  # Set all the appropriate settings for the client, using the provided
288
335
  # options hash.
289
336
  #
290
- def client_from_options(options)
337
+ def client_from_options(options = {})
291
338
  config = ThinkingSphinx::Configuration.new
292
339
  client = Riddle::Client.new config.address, config.port
293
340
  klass = options[:class]
294
341
  index_options = klass ? klass.indexes.last.options : {}
295
-
342
+
296
343
  [
297
344
  :max_matches, :match_mode, :sort_mode, :sort_by, :id_range,
298
345
  :group_by, :group_function, :group_clause, :group_distinct, :cut_off,
@@ -304,9 +351,9 @@ module ThinkingSphinx
304
351
  options[key] || index_options[key] || client.send(key)
305
352
  )
306
353
  end
307
-
354
+
308
355
  client.anchor = anchor_conditions(klass, options) || {} if client.anchor.empty?
309
-
356
+
310
357
  client.filters << Riddle::Client::Filter.new(
311
358
  "sphinx_deleted", [0]
312
359
  )
@@ -314,20 +361,20 @@ module ThinkingSphinx
314
361
  client.filters << Riddle::Client::Filter.new(
315
362
  "class_crc", options[:classes].collect { |klass| klass.to_crc32 }
316
363
  ) if options[:classes]
317
-
364
+
318
365
  # normal attribute filters
319
366
  client.filters += options[:with].collect { |attr,val|
320
367
  Riddle::Client::Filter.new attr.to_s, filter_value(val)
321
368
  } if options[:with]
322
-
369
+
323
370
  # exclusive attribute filters
324
371
  client.filters += options[:without].collect { |attr,val|
325
372
  Riddle::Client::Filter.new attr.to_s, filter_value(val), true
326
373
  } if options[:without]
327
-
374
+
328
375
  client
329
376
  end
330
-
377
+
331
378
  def filter_value(value)
332
379
  case value
333
380
  when Range
@@ -338,18 +385,18 @@ module ThinkingSphinx
338
385
  Array(value)
339
386
  end
340
387
  end
341
-
388
+
342
389
  # Translate field and attribute conditions to the relevant search string
343
390
  # and filters.
344
- #
391
+ #
345
392
  def search_conditions(klass, conditions={})
346
393
  attributes = klass ? klass.indexes.collect { |index|
347
394
  index.attributes.collect { |attrib| attrib.unique_name }
348
395
  }.flatten : []
349
-
396
+
350
397
  search_string = ""
351
398
  filters = []
352
-
399
+
353
400
  conditions.each do |key,val|
354
401
  if attributes.include?(key.to_sym)
355
402
  filters << Riddle::Client::Filter.new(
@@ -360,48 +407,48 @@ module ThinkingSphinx
360
407
  search_string << "@#{key} #{val} "
361
408
  end
362
409
  end
363
-
410
+
364
411
  filters << Riddle::Client::Filter.new(
365
412
  "class_crc", [klass.to_crc32]
366
413
  ) if klass
367
-
414
+
368
415
  return search_string, filters
369
416
  end
370
-
417
+
371
418
  # Return the appropriate latitude and longitude values, depending on
372
419
  # whether the relevant attributes have been defined, and also whether
373
420
  # there's actually any values.
374
- #
421
+ #
375
422
  def anchor_conditions(klass, options)
376
423
  attributes = klass ? klass.indexes.collect { |index|
377
424
  index.attributes.collect { |attrib| attrib.unique_name }
378
425
  }.flatten : []
379
-
426
+
380
427
  lat_attr = klass ? klass.indexes.collect { |index|
381
428
  index.options[:latitude_attr]
382
429
  }.compact.first : nil
383
-
430
+
384
431
  lon_attr = klass ? klass.indexes.collect { |index|
385
432
  index.options[:longitude_attr]
386
433
  }.compact.first : nil
387
-
434
+
388
435
  lat_attr = options[:latitude_attr] if options[:latitude_attr]
389
436
  lat_attr ||= :lat if attributes.include?(:lat)
390
437
  lat_attr ||= :latitude if attributes.include?(:latitude)
391
-
438
+
392
439
  lon_attr = options[:longitude_attr] if options[:longitude_attr]
393
440
  lon_attr ||= :lon if attributes.include?(:lon)
394
441
  lon_attr ||= :long if attributes.include?(:long)
395
442
  lon_attr ||= :longitude if attributes.include?(:longitude)
396
-
443
+
397
444
  lat = options[:lat]
398
445
  lon = options[:lon]
399
-
446
+
400
447
  if options[:geo]
401
448
  lat = options[:geo].first
402
449
  lon = options[:geo].last
403
450
  end
404
-
451
+
405
452
  lat && lon ? {
406
453
  :latitude_attribute => lat_attr,
407
454
  :latitude => lat,
@@ -409,19 +456,19 @@ module ThinkingSphinx
409
456
  :longitude => lon
410
457
  } : nil
411
458
  end
412
-
459
+
413
460
  # Set the sort options using the :order key as well as the appropriate
414
461
  # Riddle settings.
415
- #
462
+ #
416
463
  def set_sort_options!(client, options)
417
464
  klass = options[:class]
418
465
  fields = klass ? klass.indexes.collect { |index|
419
466
  index.fields.collect { |field| field.unique_name }
420
467
  }.flatten : []
421
-
468
+
422
469
  case order = options[:order]
423
470
  when Symbol
424
- client.sort_mode ||= :attr_asc
471
+ client.sort_mode = :attr_asc if client.sort_mode == :relevance || client.sort_mode.nil?
425
472
  if fields.include?(order)
426
473
  client.sort_by = order.to_s.concat("_sort")
427
474
  else
@@ -433,23 +480,23 @@ module ThinkingSphinx
433
480
  else
434
481
  # do nothing
435
482
  end
436
-
483
+
437
484
  client.sort_mode = :attr_asc if client.sort_mode == :asc
438
485
  client.sort_mode = :attr_desc if client.sort_mode == :desc
439
486
  end
440
-
487
+
441
488
  # Search through a collection of fields and translate any appearances
442
489
  # of them in a string to their attribute equivalent for sorting.
443
- #
490
+ #
444
491
  def sorted_fields_to_attributes(string, fields)
445
492
  fields.each { |field|
446
493
  string.gsub!(/(^|\s)#{field}(,?\s|$)/) { |match|
447
494
  match.gsub field.to_s, field.to_s.concat("_sort")
448
495
  }
449
496
  }
450
-
497
+
451
498
  string
452
499
  end
453
500
  end
454
501
  end
455
- end
502
+ end
@@ -20,7 +20,7 @@ module ThinkingSphinx
20
20
  module Version #:nodoc:
21
21
  Major = 0
22
22
  Minor = 9
23
- Tiny = 7
23
+ Tiny = 8
24
24
 
25
25
  String = [Major, Minor, Tiny].join('.')
26
26
  end
@@ -52,10 +52,12 @@ module ThinkingSphinx
52
52
  @@define_indexes = value
53
53
  end
54
54
 
55
+ @@deltas_enabled = nil
56
+
55
57
  # Check if delta indexing is enabled.
56
58
  #
57
59
  def self.deltas_enabled?
58
- @@deltas_enabled = true unless defined?(@@deltas_enabled)
60
+ @@deltas_enabled = (ThinkingSphinx::Configuration.environment != 'test') if @@deltas_enabled.nil?
59
61
  @@deltas_enabled == true
60
62
  end
61
63
 
@@ -79,4 +81,4 @@ module ThinkingSphinx
79
81
  "SELECT @@global.sql_mode, @@session.sql_mode;"
80
82
  ).all? { |key,value| value.nil? || value[/ONLY_FULL_GROUP_BY/].nil? }
81
83
  end
82
- end
84
+ end
@@ -167,6 +167,7 @@ describe "ThinkingSphinx::ActiveRecord::Delta" do
167
167
  end
168
168
 
169
169
  it "shouldn't index if the environment is 'test'" do
170
+ ThinkingSphinx.unstub_method(:deltas_enabled?)
170
171
  ThinkingSphinx::Configuration.stub_method(:environment => "test")
171
172
 
172
173
  @person.send(:index_delta)
@@ -182,4 +183,4 @@ describe "ThinkingSphinx::ActiveRecord::Delta" do
182
183
  )
183
184
  end
184
185
  end
185
- end
186
+ end
@@ -78,4 +78,4 @@ describe "ThinkingSphinx::ActiveRecord::Search" do
78
78
  )
79
79
  end
80
80
  end
81
- end
81
+ end
@@ -125,12 +125,6 @@ describe "ThinkingSphinx::ActiveRecord" do
125
125
  @person.stub_method(:in_core_index? => true)
126
126
  end
127
127
 
128
- # after :each do
129
- # ThinkingSphinx::Configuration.unstub_method(:new)
130
- # Riddle::Client.unstub_method(:new)
131
- # Person.indexes.each { |index| index.unstub_method(:delta?) }
132
- # end
133
-
134
128
  it "should create a client using the Configuration's address and port" do
135
129
  @person.toggle_deleted
136
130
 
@@ -157,7 +151,8 @@ describe "ThinkingSphinx::ActiveRecord" do
157
151
  )
158
152
  end
159
153
 
160
- it "should update the delta index's deleted flag if delta indexing is enabled and the instance's delta is true" do
154
+ it "should update the delta index's deleted flag if delta indexes are enabled and the instance's delta is true" do
155
+ ThinkingSphinx.stub_method(:deltas_enabled? => true)
161
156
  Person.indexes.each { |index| index.stub_method(:delta? => true) }
162
157
  @person.delta = true
163
158
 
@@ -168,7 +163,8 @@ describe "ThinkingSphinx::ActiveRecord" do
168
163
  )
169
164
  end
170
165
 
171
- it "should not update the delta index's deleted flag if delta indexing is enabled and the instance's delta is false" do
166
+ it "should not update the delta index's deleted flag if delta indexes are enabled and the instance's delta is false" do
167
+ ThinkingSphinx.stub_method(:deltas_enabled? => true)
172
168
  Person.indexes.each { |index| index.stub_method(:delta? => true) }
173
169
  @person.delta = false
174
170
 
@@ -179,7 +175,8 @@ describe "ThinkingSphinx::ActiveRecord" do
179
175
  )
180
176
  end
181
177
 
182
- it "should not update the delta index's deleted flag if delta indexing is enabled and the instance's delta is equivalent to false" do
178
+ it "should not update the delta index's deleted flag if delta indexes are enabled and the instance's delta is equivalent to false" do
179
+ ThinkingSphinx.stub_method(:deltas_enabled? => true)
183
180
  Person.indexes.each { |index| index.stub_method(:delta? => true) }
184
181
  @person.delta = 0
185
182
 
@@ -190,7 +187,20 @@ describe "ThinkingSphinx::ActiveRecord" do
190
187
  )
191
188
  end
192
189
 
193
- it "shouldn't update the delta index if delta indexing is disabled" do
190
+ it "shouldn't update the delta index if delta indexes are disabled" do
191
+ ThinkingSphinx.stub_method(:deltas_enabled? => true)
192
+ @person.toggle_deleted
193
+
194
+ @client.should_not have_received(:update).with(
195
+ "person_delta", ["sphinx_deleted"], {@person.id => 1}
196
+ )
197
+ end
198
+
199
+ it "should not update the delta index if delta indexing is disabled" do
200
+ ThinkingSphinx.stub_method(:deltas_enabled? => false)
201
+ Person.indexes.each { |index| index.stub_method(:delta? => true) }
202
+ @person.delta = true
203
+
194
204
  @person.toggle_deleted
195
205
 
196
206
  @client.should_not have_received(:update).with(
@@ -198,4 +208,16 @@ describe "ThinkingSphinx::ActiveRecord" do
198
208
  )
199
209
  end
200
210
  end
201
- end
211
+
212
+ describe "indexes in the inheritance chain (STI)" do
213
+ it "should hand defined indexes on a class down to its child classes" do
214
+ Child.indexes.should include(*Person.indexes)
215
+ end
216
+
217
+ it "should allow associations to other STI models" do
218
+ Child.indexes.last.link!
219
+ sql = Child.indexes.last.to_sql.gsub('$start', '0').gsub('$end', '100')
220
+ lambda { Child.connection.execute(sql) }.should_not raise_error(ActiveRecord::StatementInvalid)
221
+ end
222
+ end
223
+ end
@@ -207,7 +207,7 @@ describe ThinkingSphinx::Index do
207
207
  end
208
208
  end
209
209
 
210
- describe "infix_fields" do
210
+ describe "infix_fields method" do
211
211
  before :each do
212
212
  @index = ThinkingSphinx::Index.new(Person)
213
213
 
@@ -227,4 +227,41 @@ describe ThinkingSphinx::Index do
227
227
  @index.infix_fields.should_not include(@field_b)
228
228
  end
229
229
  end
230
+
231
+ describe "empty? method" do
232
+ before :each do
233
+ @index = ThinkingSphinx::Index.new(Person)
234
+ config = ThinkingSphinx::Configuration.new
235
+
236
+ `mkdir -p #{config.searchd_file_path}`
237
+ @file_path = "#{config.searchd_file_path}/#{@index.name}_core.spa"
238
+ end
239
+
240
+ after :each do
241
+ `rm #{@file_path}` if File.exists?(@file_path)
242
+ end
243
+
244
+ it "should return true if the core index files are empty" do
245
+ `touch #{@file_path}`
246
+ @index.should be_empty
247
+ end
248
+
249
+ it "should return true if the core index files don't exist" do
250
+ @index.should be_empty
251
+ end
252
+
253
+ it "should return false if the core index files aren't empty" do
254
+ `echo 'a' > #{@file_path}`
255
+ @index.should_not be_empty
256
+ end
257
+
258
+ it "should check the delta files if specified" do
259
+ @index.should be_empty(:delta)
260
+
261
+ `echo 'a' > #{@file_path.gsub(/_core.spa$/, '_delta.spa')}`
262
+ @index.should_not be_empty(:delta)
263
+
264
+ `rm #{@file_path}` if File.exists?(@file_path.gsub(/_core.spa$/, '_delta.spa'))
265
+ end
266
+ end
230
267
  end
@@ -1,6 +1,10 @@
1
1
  require 'spec/spec_helper'
2
2
 
3
3
  describe ThinkingSphinx::Search do
4
+ describe "search method" do
5
+ it "should actually have some tests"
6
+ end
7
+
4
8
  describe "search_for_id method" do
5
9
  before :each do
6
10
  @client = Riddle::Client.stub_instance(
@@ -82,10 +86,14 @@ describe ThinkingSphinx::Search do
82
86
  @person_b = Person.stub_instance
83
87
  @person_c = Person.stub_instance
84
88
 
89
+ @person_a.stub_method(:attributes => [])
90
+ @person_b.stub_method(:attributes => [])
91
+ @person_c.stub_method(:attributes => [])
92
+
85
93
  @results = [
86
- {:doc => @person_a.id},
87
- {:doc => @person_b.id},
88
- {:doc => @person_c.id}
94
+ {:doc => @person_a.id, :attributes => {'@geodist' => 10}},
95
+ {:doc => @person_b.id, :attributes => {'@geodist' => 10}},
96
+ {:doc => @person_c.id, :attributes => {'@geodist' => 10}}
89
97
  ]
90
98
 
91
99
  Person.stub_method(
@@ -105,13 +113,13 @@ describe ThinkingSphinx::Search do
105
113
  )
106
114
 
107
115
  ThinkingSphinx::Search.should have_received(:instance_from_result).with(
108
- {:doc => @person_a.id}, {}
116
+ {:doc => @person_a.id, :attributes => {'@geodist' => 10}}, {}
109
117
  )
110
118
  ThinkingSphinx::Search.should have_received(:instance_from_result).with(
111
- {:doc => @person_b.id}, {}
119
+ {:doc => @person_b.id, :attributes => {'@geodist' => 10}}, {}
112
120
  )
113
121
  ThinkingSphinx::Search.should have_received(:instance_from_result).with(
114
- {:doc => @person_c.id}, {}
122
+ {:doc => @person_c.id, :attributes => {'@geodist' => 10}}, {}
115
123
  )
116
124
  end
117
125
 
@@ -159,5 +167,22 @@ describe ThinkingSphinx::Search do
159
167
  :instances_from_results, @results, {:select => :fields}, Person
160
168
  ).should == [@person_a, @person_b, @person_c]
161
169
  end
170
+
171
+ it "should run the append distances function when distance_name is passed" do
172
+ ThinkingSphinx::Search.stub_method(:append_distances => @results)
173
+
174
+ ThinkingSphinx::Search.send(
175
+ :instances_from_results, @results, {:distance_name => 'distance'}, Person
176
+ )
177
+
178
+ Person.should have_received(:find).with(
179
+ :all,
180
+ :conditions => {:id => [@person_a.id, @person_b.id, @person_c.id]},
181
+ :include => nil,
182
+ :select => nil
183
+ )
184
+ end
185
+
186
+ it "should have a test for the append_distances function"
162
187
  end
163
188
  end
@@ -24,6 +24,7 @@ describe ThinkingSphinx do
24
24
  end
25
25
 
26
26
  it "should index deltas by default" do
27
+ ThinkingSphinx.deltas_enabled = nil
27
28
  ThinkingSphinx.deltas_enabled?.should be_true
28
29
  end
29
30
 
@@ -104,4 +105,4 @@ describe ThinkingSphinx do
104
105
  end
105
106
  end
106
107
  end
107
- end
108
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: DrMark-thinking-sphinx
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.7
4
+ version: 0.9.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Pat Allan
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-06-25 00:00:00 -07:00
12
+ date: 2008-07-08 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies: []
15
15