public
Rubygem
Fork of freelancing-god/thinking-sphinx
Description: Sphinx plugin for Rails and Merb -- added distance variables that are included into the returned records and the ability to return the raw Sphinx results
Homepage: http://ts.freelancing-gods.com
Clone URL: git://github.com/DrMark/thinking-sphinx.git
adding the code that appends a distance variable to each record in the 
search results when :distance_name => "some_name" is passed to the 
search.
DrMark (author)
Tue Jul 01 19:05:27 -0700 2008
commit  986ddbfbfadb3323565ae3c0d9da53b2320505d7
tree    0a243806a344014cf696fff5af68cc42b7924ca5
parent  68135b9d4bc0533e6826d8dc659d0a568cb619d7
...
5
6
7
8
 
9
10
11
...
14
15
16
17
 
18
19
20
 
21
22
23
...
35
36
37
38
 
39
40
41
42
 
43
44
45
...
47
48
49
50
 
51
52
53
54
 
55
56
57
 
58
59
60
...
74
75
76
77
 
78
79
 
80
81
 
82
83
84
...
87
88
89
90
 
91
92
93
...
112
113
114
115
 
116
117
118
119
120
121
 
122
123
124
...
128
129
130
131
 
132
133
134
135
 
136
137
138
...
141
142
143
144
 
145
146
147
148
 
149
150
151
152
153
154
 
155
156
157
...
161
162
163
164
 
165
166
167
...
186
187
188
189
 
190
191
192
193
194
195
196
 
197
198
 
199
200
 
201
202
203
204
 
205
206
207
208
209
210
211
 
212
213
214
215
216
217
218
 
219
220
 
221
222
223
224
 
225
226
227
228
 
229
230
231
232
233
234
235
 
236
237
 
238
239
240
...
246
247
248
249
 
250
251
252
 
 
 
 
 
 
 
 
 
 
 
 
253
254
255
...
261
262
263
264
 
 
 
 
 
265
266
267
 
268
269
270
 
271
272
273
274
275
276
 
277
278
 
279
280
281
282
 
283
284
285
286
287
288
 
289
290
291
 
292
293
294
 
295
296
297
298
299
300
 
301
302
303
...
309
310
311
312
 
313
314
 
315
316
317
...
319
320
321
322
 
323
324
325
326
327
 
328
329
330
331
332
 
333
334
335
 
336
337
338
...
343
344
345
346
 
347
348
349
 
350
351
352
353
354
 
355
356
357
 
358
359
360
...
365
366
367
368
 
369
370
371
372
 
373
374
375
 
376
377
378
379
 
380
381
382
383
384
 
385
386
387
388
 
389
390
391
392
 
393
394
395
396
 
397
398
399
400
401
 
402
403
404
 
405
406
407
408
409
 
410
411
412
...
414
415
416
417
 
418
419
420
 
421
422
423
424
425
426
 
427
428
429
...
438
439
440
441
 
442
443
444
445
 
446
447
448
 
449
450
451
452
453
454
455
 
456
457
458
459
460
 
461
...
5
6
7
 
8
9
10
11
...
14
15
16
 
17
18
19
 
20
21
22
23
...
35
36
37
 
38
39
40
41
 
42
43
44
45
...
47
48
49
 
50
51
52
53
 
54
55
56
 
57
58
59
60
...
74
75
76
 
77
78
 
79
80
 
81
82
83
84
...
87
88
89
 
90
91
92
93
...
112
113
114
 
115
116
117
118
119
120
 
121
122
123
124
...
128
129
130
 
131
132
133
134
 
135
136
137
138
...
141
142
143
 
144
145
146
147
 
148
149
150
151
152
153
 
154
155
156
157
...
161
162
163
 
164
165
166
167
...
186
187
188
 
189
190
191
192
193
194
195
 
196
197
 
198
199
 
200
201
202
203
 
204
205
206
207
208
209
210
 
211
212
213
214
215
216
217
 
218
219
 
220
221
222
223
 
224
225
226
227
 
228
229
230
231
232
233
234
 
235
236
 
237
238
239
240
...
246
247
248
 
249
250
251
 
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
...
272
273
274
 
275
276
277
278
279
280
281
 
282
283
284
 
285
286
287
288
289
290
 
291
292
 
293
294
295
296
 
297
298
299
300
301
302
 
303
304
305
 
306
307
308
 
309
310
311
312
313
314
 
315
316
317
318
...
324
325
326
 
327
328
 
329
330
331
332
...
334
335
336
 
337
338
339
340
341
 
342
343
344
345
346
 
347
348
349
 
350
351
352
353
...
358
359
360
 
361
362
363
 
364
365
366
367
368
 
369
370
371
 
372
373
374
375
...
380
381
382
 
383
384
385
386
 
387
388
389
 
390
391
392
393
 
394
395
396
397
398
 
399
400
401
402
 
403
404
405
406
 
407
408
409
410
 
411
412
413
414
415
 
416
417
418
 
419
420
421
422
423
 
424
425
426
427
...
429
430
431
 
432
433
434
 
435
436
437
438
439
440
 
441
442
443
444
...
453
454
455
 
456
457
458
459
 
460
461
462
 
463
464
465
466
467
468
469
 
470
471
472
473
474
 
475
476
0
@@ -5,7 +5,7 @@ module ThinkingSphinx
0
   # Most times, you will just want a specific model's results - to search and
0
   # search_for_ids methods will do the job in exactly the same manner when
0
   # called from a model.
0
- #
0
+ #
0
   class Search
0
     class << self
0
       # Searches for results that match the parameters provided. Will only
0
@@ -14,10 +14,10 @@ module ThinkingSphinx
0
       #
0
       def search_for_ids(*args)
0
         results, client = search_results(*args.clone)
0
-
0
+
0
         options = args.extract_options!
0
         page = options[:page] ? options[:page].to_i : 1
0
-
0
+
0
         begin
0
           pager = WillPaginate::Collection.new(page,
0
             client.limit, results[:total] || 0)
0
@@ -35,11 +35,11 @@ module ThinkingSphinx
0
       # just like paginate. The same parameters - :page and :per_page - work as
0
       # expected, and the returned result set can be used by the will_paginate
0
       # helper.
0
- #
0
+ #
0
       # == Basic Searching
0
       #
0
       # The simplest way of searching is straight text.
0
- #
0
+ #
0
       # ThinkingSphinx::Search.search "pat"
0
       # ThinkingSphinx::Search.search "google"
0
       # User.search "pat", :page => (params[:page] || 1)
0
@@ -47,14 +47,14 @@ module ThinkingSphinx
0
       #
0
       # If you specify :include, like in an #find call, this will be respected
0
       # when loading the relevant models from the search results.
0
- #
0
+ #
0
       # User.search "pat", :include => :posts
0
       #
0
       # == Searching by Fields
0
- #
0
+ #
0
       # If you want to step it up a level, you can limit your search terms to
0
       # specific fields:
0
- #
0
+ #
0
       # User.search :conditions => {:name => "pat"}
0
       #
0
       # This uses Sphinx's extended match mode, unless you specify a different
0
@@ -74,11 +74,11 @@ module ThinkingSphinx
0
       # (not multi-model searching). With a single model, Thinking Sphinx
0
       # can figure out what attributes and fields are available, so you can
0
       # put it all in the :conditions hash, and it will sort it out.
0
- #
0
+ #
0
       # Node.search :conditions => {:parent_id => 10}
0
- #
0
+ #
0
       # Filters can be single values, arrays of values, or ranges.
0
- #
0
+ #
0
       # Article.search "East Timor", :conditions => {:rating => 3..5}
0
       #
0
       # == Excluding by Attributes
0
@@ -87,7 +87,7 @@ module ThinkingSphinx
0
       # attribute values to exclude. This is done with the :without option:
0
       #
0
       # User.search :without => {:role_id => 1}
0
- #
0
+ #
0
       # == Sorting
0
       #
0
       # Sphinx can only sort by attributes, so generally you will need to avoid
0
@@ -112,13 +112,13 @@ module ThinkingSphinx
0
       # detail though.
0
       #
0
       # == Grouping
0
- #
0
+ #
0
       # For this you can use the group_by, group_clause and group_function
0
       # options - which are all directly linked to Sphinx's expectations. No
0
       # magic from Thinking Sphinx. It can get a little tricky, so make sure
0
       # you read all the relevant
0
       # documentation[http://sphinxsearch.com/doc.html#clustering] first.
0
- #
0
+ #
0
       # Yes this section will be expanded, but this is a start.
0
       #
0
       # == Geo/Location Searching
0
@@ -128,11 +128,11 @@ module ThinkingSphinx
0
       # take advantage of this, you will need to have both of those values in
0
       # attributes. To search with that point, you can then use one of the
0
       # following syntax examples:
0
- #
0
+ #
0
       # Address.search "Melbourne", :geo => [1.4, -2.217]
0
       # Address.search "Australia", :geo => [-0.55, 3.108],
0
       # :latitude_attr => "latit", :longitude_attr => "longit"
0
- #
0
+ #
0
       # The first example applies when your latitude and longitude attributes
0
       # are named any of lat, latitude, lon, long or longitude. If that's not
0
       # the case, you will need to explicitly state them in your search, _or_
0
@@ -141,17 +141,17 @@ module ThinkingSphinx
0
       # define_index do
0
       # has :latit # Float column, stored in radians
0
       # has :longit # Float column, stored in radians
0
- #
0
+ #
0
       # set_property :latitude_attr => "latit"
0
       # set_property :longitude_attr => "longit"
0
       # end
0
- #
0
+ #
0
       # Now, geo-location searching really only has an affect if you have a
0
       # filter, sort or grouping clause related to it - otherwise it's just a
0
       # normal search. To make use of the positioning difference, use the
0
       # special attribute "@geodist" in any of your filters or sorting or grouping
0
       # clauses.
0
- #
0
+ #
0
       # And don't forget - both the latitude and longitude you use in your
0
       # search, and the values in your indexes, need to be stored as a float in radians,
0
       # _not_ degrees. Keep in mind that if you do this conversion in SQL
0
@@ -161,7 +161,7 @@ module ThinkingSphinx
0
       # has 'RADIANS(lat)', :as => :lat, :type => :float
0
       # # ...
0
       # end
0
- #
0
+ #
0
       def search(*args)
0
         results, client = search_results(*args.clone)
0
 
0
@@ -186,55 +186,55 @@ module ThinkingSphinx
0
           end
0
         end
0
       end
0
-
0
+
0
       # Checks if a document with the given id exists within a specific index.
0
       # Expected parameters:
0
       #
0
       # - ID of the document
0
       # - Index to check within
0
       # - Options hash (defaults to {})
0
- #
0
+ #
0
       # Example:
0
- #
0
+ #
0
       # ThinkingSphinx::Search.search_for_id(10, "user_core", :class => User)
0
- #
0
+ #
0
       def search_for_id(*args)
0
         options = args.extract_options!
0
         client = client_from_options options
0
-
0
+
0
         query, filters = search_conditions(
0
           options[:class], options[:conditions] || {}
0
         )
0
         client.filters += filters
0
         client.match_mode = :extended unless query.empty?
0
         client.id_range = args.first..args.first
0
-
0
+
0
         begin
0
           return client.query(query, args[1])[:matches].length > 0
0
         rescue Errno::ECONNREFUSED => err
0
           raise ThinkingSphinx::ConnectionError, "Connection to Sphinx Daemon (searchd) failed."
0
         end
0
       end
0
-
0
+
0
       private
0
-
0
+
0
       # This method handles the common search functionality, and returns both
0
       # the result hash and the client. Not super elegant, but it'll do for
0
       # the moment.
0
- #
0
+ #
0
       def search_results(*args)
0
         options = args.extract_options!
0
         client = client_from_options options
0
-
0
+
0
         query, filters = search_conditions(
0
           options[:class], options[:conditions] || {}
0
         )
0
         client.filters += filters
0
         client.match_mode = :extended unless query.empty?
0
         query = args.join(" ") + query
0
-
0
+
0
         set_sort_options! client, options
0
-
0
+
0
         client.limit = options[:per_page].to_i if options[:per_page]
0
         page = options[:page] ? options[:page].to_i : 1
0
         client.offset = (page - 1) * client.limit
0
@@ -246,10 +246,21 @@ module ThinkingSphinx
0
         rescue Errno::ECONNREFUSED => err
0
           raise ThinkingSphinx::ConnectionError, "Connection to Sphinx Daemon (searchd) failed."
0
         end
0
-
0
+
0
         return results, client
0
       end
0
-
0
+
0
+ # This function loops over the records and appends a 'distance' variable to each one with
0
+ # the value from Sphinx
0
+ def append_distances(instances, results, distance_name)
0
+ instances.each_with_index do |record, index|
0
+ if record
0
+ distance = (results[index][:attributes]['@geodist'] or nil)
0
+ record.instance_variable_get('@attributes')["#{distance_name}"] = distance
0
+ end
0
+ end
0
+ end
0
+
0
       def instances_from_results(results, options = {}, klass = nil)
0
         if klass.nil?
0
           results.collect { |result| instance_from_result result, options }
0
@@ -261,43 +272,47 @@ module ThinkingSphinx
0
             :include => options[:include],
0
             :select => options[:select]
0
           )
0
- ids.collect { |obj_id| instances.detect { |obj| obj.id == obj_id } }
0
+ final_instances = ids.collect { |obj_id| instances.detect { |obj| obj.id == obj_id } }
0
+
0
+ final_instances = append_distances(final_instances, results, options[:distance_name]) if options[:distance_name] && (results.collect { |result| result[:attributes]['@geodist'] }.length > 0)
0
+
0
+ return final_instances
0
         end
0
       end
0
-
0
+
0
       # Either use the provided class to instantiate a result from a model, or
0
       # get the result's CRC value and determine the class from that.
0
- #
0
+ #
0
       def instance_from_result(result, options)
0
         class_from_crc(result[:attributes]["class_crc"]).find(
0
           result[:doc], :include => options[:include], :select => options[:select]
0
         )
0
       end
0
-
0
+
0
       # Convert a CRC value to the corresponding class.
0
- #
0
+ #
0
       def class_from_crc(crc)
0
         unless @models_by_crc
0
           Configuration.new.load_models
0
-
0
+
0
           @models_by_crc = ThinkingSphinx.indexed_models.inject({}) do |hash, model|
0
             hash[model.constantize.to_crc32] = model
0
             hash
0
           end
0
         end
0
-
0
+
0
         @models_by_crc[crc].constantize
0
       end
0
-
0
+
0
       # Set all the appropriate settings for the client, using the provided
0
       # options hash.
0
- #
0
+ #
0
       def client_from_options(options)
0
         config = ThinkingSphinx::Configuration.new
0
         client = Riddle::Client.new config.address, config.port
0
         klass = options[:class]
0
         index_options = klass ? klass.indexes.last.options : {}
0
-
0
+
0
         [
0
           :max_matches, :match_mode, :sort_mode, :sort_by, :id_range,
0
           :group_by, :group_function, :group_clause, :group_distinct, :cut_off,
0
@@ -309,9 +324,9 @@ module ThinkingSphinx
0
             options[key] || index_options[key] || client.send(key)
0
           )
0
         end
0
-
0
+
0
         client.anchor = anchor_conditions(klass, options) || {} if client.anchor.empty?
0
-
0
+
0
         client.filters << Riddle::Client::Filter.new(
0
           "sphinx_deleted", [0]
0
         )
0
@@ -319,20 +334,20 @@ module ThinkingSphinx
0
         client.filters << Riddle::Client::Filter.new(
0
           "class_crc", options[:classes].collect { |klass| klass.to_crc32 }
0
         ) if options[:classes]
0
-
0
+
0
         # normal attribute filters
0
         client.filters += options[:with].collect { |attr,val|
0
           Riddle::Client::Filter.new attr.to_s, filter_value(val)
0
         } if options[:with]
0
-
0
+
0
         # exclusive attribute filters
0
         client.filters += options[:without].collect { |attr,val|
0
           Riddle::Client::Filter.new attr.to_s, filter_value(val), true
0
         } if options[:without]
0
-
0
+
0
         client
0
       end
0
-
0
+
0
       def filter_value(value)
0
         case value
0
         when Range
0
@@ -343,18 +358,18 @@ module ThinkingSphinx
0
           Array(value)
0
         end
0
       end
0
-
0
+
0
       # Translate field and attribute conditions to the relevant search string
0
       # and filters.
0
- #
0
+ #
0
       def search_conditions(klass, conditions={})
0
         attributes = klass ? klass.indexes.collect { |index|
0
           index.attributes.collect { |attrib| attrib.unique_name }
0
         }.flatten : []
0
-
0
+
0
         search_string = ""
0
         filters = []
0
-
0
+
0
         conditions.each do |key,val|
0
           if attributes.include?(key.to_sym)
0
             filters << Riddle::Client::Filter.new(
0
@@ -365,48 +380,48 @@ module ThinkingSphinx
0
             search_string << "@#{key} #{val} "
0
           end
0
         end
0
-
0
+
0
         filters << Riddle::Client::Filter.new(
0
           "class_crc", [klass.to_crc32]
0
         ) if klass
0
-
0
+
0
         return search_string, filters
0
       end
0
-
0
+
0
       # Return the appropriate latitude and longitude values, depending on
0
       # whether the relevant attributes have been defined, and also whether
0
       # there's actually any values.
0
- #
0
+ #
0
       def anchor_conditions(klass, options)
0
         attributes = klass ? klass.indexes.collect { |index|
0
           index.attributes.collect { |attrib| attrib.unique_name }
0
         }.flatten : []
0
-
0
+
0
         lat_attr = klass ? klass.indexes.collect { |index|
0
           index.options[:latitude_attr]
0
         }.compact.first : nil
0
-
0
+
0
         lon_attr = klass ? klass.indexes.collect { |index|
0
           index.options[:longitude_attr]
0
         }.compact.first : nil
0
-
0
+
0
         lat_attr = options[:latitude_attr] if options[:latitude_attr]
0
         lat_attr ||= :lat if attributes.include?(:lat)
0
         lat_attr ||= :latitude if attributes.include?(:latitude)
0
-
0
+
0
         lon_attr = options[:longitude_attr] if options[:longitude_attr]
0
         lon_attr ||= :lon if attributes.include?(:lon)
0
         lon_attr ||= :long if attributes.include?(:long)
0
         lon_attr ||= :longitude if attributes.include?(:longitude)
0
-
0
+
0
         lat = options[:lat]
0
         lon = options[:lon]
0
-
0
+
0
         if options[:geo]
0
           lat = options[:geo].first
0
           lon = options[:geo].last
0
         end
0
-
0
+
0
         lat && lon ? {
0
           :latitude_attribute => lat_attr,
0
           :latitude => lat,
0
@@ -414,16 +429,16 @@ module ThinkingSphinx
0
           :longitude => lon
0
         } : nil
0
       end
0
-
0
+
0
       # Set the sort options using the :order key as well as the appropriate
0
       # Riddle settings.
0
- #
0
+ #
0
       def set_sort_options!(client, options)
0
         klass = options[:class]
0
         fields = klass ? klass.indexes.collect { |index|
0
           index.fields.collect { |field| field.unique_name }
0
         }.flatten : []
0
-
0
+
0
         case order = options[:order]
0
         when Symbol
0
           client.sort_mode ||= :attr_asc
0
@@ -438,23 +453,23 @@ module ThinkingSphinx
0
         else
0
           # do nothing
0
         end
0
-
0
+
0
         client.sort_mode = :attr_asc if client.sort_mode == :asc
0
         client.sort_mode = :attr_desc if client.sort_mode == :desc
0
       end
0
-
0
+
0
       # Search through a collection of fields and translate any appearances
0
       # of them in a string to their attribute equivalent for sorting.
0
- #
0
+ #
0
       def sorted_fields_to_attributes(string, fields)
0
         fields.each { |field|
0
           string.gsub!(/(^|\s)#{field}(,?\s|$)/) { |match|
0
             match.gsub field.to_s, field.to_s.concat("_sort")
0
           }
0
         }
0
-
0
+
0
         string
0
       end
0
     end
0
   end
0
-end
0
+end
0
\ No newline at end of file
...
1
2
3
 
 
 
 
4
5
6
...
82
83
84
 
 
 
 
85
86
87
88
 
 
 
89
90
91
...
105
106
107
108
 
109
110
111
 
112
113
114
 
115
116
117
...
159
160
161
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
162
163
...
1
2
3
4
5
6
7
8
9
10
...
86
87
88
89
90
91
92
93
 
 
 
94
95
96
97
98
99
...
113
114
115
 
116
117
118
 
119
120
121
 
122
123
124
125
...
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
0
@@ -1,6 +1,10 @@
0
 require 'spec/spec_helper'
0
 
0
 describe ThinkingSphinx::Search do
0
+ describe "search method" do
0
+ it "should actually have some tests"
0
+ end
0
+
0
   describe "search_for_id method" do
0
     before :each do
0
       @client = Riddle::Client.stub_instance(
0
@@ -82,10 +86,14 @@ describe ThinkingSphinx::Search do
0
       @person_b = Person.stub_instance
0
       @person_c = Person.stub_instance
0
 
0
+ @person_a.stub_method(:attributes => [])
0
+ @person_b.stub_method(:attributes => [])
0
+ @person_c.stub_method(:attributes => [])
0
+
0
       @results = [
0
- {:doc => @person_a.id},
0
- {:doc => @person_b.id},
0
- {:doc => @person_c.id}
0
+ {:doc => @person_a.id, :attributes => {'@geodist' => 10}},
0
+ {:doc => @person_b.id, :attributes => {'@geodist' => 10}},
0
+ {:doc => @person_c.id, :attributes => {'@geodist' => 10}}
0
       ]
0
       
0
       Person.stub_method(
0
@@ -105,13 +113,13 @@ describe ThinkingSphinx::Search do
0
       )
0
 
0
       ThinkingSphinx::Search.should have_received(:instance_from_result).with(
0
- {:doc => @person_a.id}, {}
0
+ {:doc => @person_a.id, :attributes => {'@geodist' => 10}}, {}
0
       )
0
       ThinkingSphinx::Search.should have_received(:instance_from_result).with(
0
- {:doc => @person_b.id}, {}
0
+ {:doc => @person_b.id, :attributes => {'@geodist' => 10}}, {}
0
       )
0
       ThinkingSphinx::Search.should have_received(:instance_from_result).with(
0
- {:doc => @person_c.id}, {}
0
+ {:doc => @person_c.id, :attributes => {'@geodist' => 10}}, {}
0
       )
0
     end
0
 
0
@@ -159,5 +167,22 @@ describe ThinkingSphinx::Search do
0
         :instances_from_results, @results, {:select => :fields}, Person
0
       ).should == [@person_a, @person_b, @person_c]
0
     end
0
+
0
+ it "should run the append distances function when distance_name is passed" do
0
+ ThinkingSphinx::Search.stub_method(:append_distances => @results)
0
+
0
+ ThinkingSphinx::Search.send(
0
+ :instances_from_results, @results, {:distance_name => 'distance'}, Person
0
+ )
0
+
0
+ Person.should have_received(:find).with(
0
+ :all,
0
+ :conditions => {:id => [@person_a.id, @person_b.id, @person_c.id]},
0
+ :include => nil,
0
+ :select => nil
0
+ )
0
+ end
0
+
0
+ it "should have a test for the append_distances function"
0
   end
0
 end

Comments

    No one has commented yet.