DrMark-thinking-sphinx 0.9.7 → 0.9.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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