DrMark-thinking-sphinx 1.1.15 → 1.2.5

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.
Files changed (63) hide show
  1. data/README.textile +22 -0
  2. data/VERSION.yml +4 -0
  3. data/lib/thinking_sphinx/active_record/scopes.rb +39 -0
  4. data/lib/thinking_sphinx/active_record.rb +27 -7
  5. data/lib/thinking_sphinx/adapters/postgresql_adapter.rb +9 -3
  6. data/lib/thinking_sphinx/association.rb +4 -1
  7. data/lib/thinking_sphinx/attribute.rb +91 -30
  8. data/lib/thinking_sphinx/configuration.rb +51 -12
  9. data/lib/thinking_sphinx/deltas/datetime_delta.rb +2 -2
  10. data/lib/thinking_sphinx/deltas/default_delta.rb +1 -1
  11. data/lib/thinking_sphinx/deltas/delayed_delta/delta_job.rb +1 -1
  12. data/lib/thinking_sphinx/deltas/delayed_delta.rb +3 -0
  13. data/lib/thinking_sphinx/deploy/capistrano.rb +25 -8
  14. data/lib/thinking_sphinx/excerpter.rb +22 -0
  15. data/lib/thinking_sphinx/facet.rb +1 -1
  16. data/lib/thinking_sphinx/facet_search.rb +134 -0
  17. data/lib/thinking_sphinx/index.rb +2 -1
  18. data/lib/thinking_sphinx/rails_additions.rb +14 -0
  19. data/lib/thinking_sphinx/search.rb +599 -658
  20. data/lib/thinking_sphinx/search_methods.rb +421 -0
  21. data/lib/thinking_sphinx/source/internal_properties.rb +1 -1
  22. data/lib/thinking_sphinx/source/sql.rb +17 -13
  23. data/lib/thinking_sphinx/source.rb +6 -6
  24. data/lib/thinking_sphinx/tasks.rb +42 -8
  25. data/lib/thinking_sphinx.rb +82 -54
  26. data/rails/init.rb +14 -0
  27. data/spec/{unit → lib}/thinking_sphinx/active_record/delta_spec.rb +5 -5
  28. data/spec/{unit → lib}/thinking_sphinx/active_record/has_many_association_spec.rb +0 -0
  29. data/spec/lib/thinking_sphinx/active_record/scopes_spec.rb +96 -0
  30. data/spec/{unit → lib}/thinking_sphinx/active_record_spec.rb +51 -31
  31. data/spec/{unit → lib}/thinking_sphinx/association_spec.rb +4 -5
  32. data/spec/lib/thinking_sphinx/attribute_spec.rb +465 -0
  33. data/spec/{unit → lib}/thinking_sphinx/configuration_spec.rb +161 -29
  34. data/spec/{unit → lib}/thinking_sphinx/core/string_spec.rb +0 -0
  35. data/spec/lib/thinking_sphinx/excerpter_spec.rb +49 -0
  36. data/spec/lib/thinking_sphinx/facet_search_spec.rb +176 -0
  37. data/spec/{unit → lib}/thinking_sphinx/facet_spec.rb +24 -0
  38. data/spec/{unit → lib}/thinking_sphinx/field_spec.rb +8 -8
  39. data/spec/{unit → lib}/thinking_sphinx/index/builder_spec.rb +6 -2
  40. data/spec/{unit → lib}/thinking_sphinx/index/faux_column_spec.rb +0 -0
  41. data/spec/lib/thinking_sphinx/index_spec.rb +45 -0
  42. data/spec/{unit → lib}/thinking_sphinx/rails_additions_spec.rb +25 -5
  43. data/spec/lib/thinking_sphinx/search_methods_spec.rb +152 -0
  44. data/spec/lib/thinking_sphinx/search_spec.rb +960 -0
  45. data/spec/{unit → lib}/thinking_sphinx/source_spec.rb +63 -2
  46. data/spec/{unit → lib}/thinking_sphinx_spec.rb +32 -4
  47. data/tasks/distribution.rb +36 -35
  48. data/vendor/riddle/lib/riddle/client/message.rb +4 -3
  49. data/vendor/riddle/lib/riddle/client.rb +3 -0
  50. data/vendor/riddle/lib/riddle/configuration/section.rb +8 -2
  51. data/vendor/riddle/lib/riddle/controller.rb +17 -7
  52. data/vendor/riddle/lib/riddle.rb +1 -1
  53. metadata +79 -83
  54. data/lib/thinking_sphinx/active_record/search.rb +0 -57
  55. data/lib/thinking_sphinx/collection.rb +0 -148
  56. data/lib/thinking_sphinx/facet_collection.rb +0 -59
  57. data/lib/thinking_sphinx/search/facets.rb +0 -98
  58. data/spec/unit/thinking_sphinx/active_record/search_spec.rb +0 -107
  59. data/spec/unit/thinking_sphinx/attribute_spec.rb +0 -232
  60. data/spec/unit/thinking_sphinx/collection_spec.rb +0 -14
  61. data/spec/unit/thinking_sphinx/facet_collection_spec.rb +0 -64
  62. data/spec/unit/thinking_sphinx/index_spec.rb +0 -139
  63. data/spec/unit/thinking_sphinx/search_spec.rb +0 -130
@@ -0,0 +1,960 @@
1
+ require 'spec/spec_helper'
2
+ require 'will_paginate/collection'
3
+
4
+ describe ThinkingSphinx::Search do
5
+ before :each do
6
+ @config = ThinkingSphinx::Configuration.instance
7
+ @client = Riddle::Client.new
8
+
9
+ @config.stub!(:client => @client)
10
+ @client.stub!(:query => {:matches => [], :total_found => 41})
11
+ end
12
+
13
+ it "not request results from the client if not accessing items" do
14
+ @config.should_not_receive(:client)
15
+
16
+ ThinkingSphinx::Search.new.class
17
+ end
18
+
19
+ it "should request results if access is required" do
20
+ @config.should_receive(:client)
21
+
22
+ ThinkingSphinx::Search.new.first
23
+ end
24
+
25
+ describe '#respond_to?' do
26
+ it "should respond to Array methods" do
27
+ ThinkingSphinx::Search.new.respond_to?(:each).should be_true
28
+ end
29
+
30
+ it "should respond to Search methods" do
31
+ ThinkingSphinx::Search.new.respond_to?(:per_page).should be_true
32
+ end
33
+ end
34
+
35
+ describe '#method_missing' do
36
+ before :each do
37
+ Alpha.sphinx_scope(:by_name) { |name|
38
+ {:conditions => {:name => name}}
39
+ }
40
+ Alpha.sphinx_scope(:ids_only) { {:ids_only => true} }
41
+ end
42
+
43
+ after :each do
44
+ Alpha.remove_sphinx_scopes
45
+ end
46
+
47
+ it "should handle Array methods" do
48
+ ThinkingSphinx::Search.new.private_methods.should be_an(Array)
49
+ end
50
+
51
+ it "should raise a NoMethodError exception if unknown method" do
52
+ lambda {
53
+ ThinkingSphinx::Search.new.foo
54
+ }.should raise_error(NoMethodError)
55
+ end
56
+
57
+ it "should not request results from client if method does not exist" do
58
+ @client.should_not_receive(:query)
59
+
60
+ lambda {
61
+ ThinkingSphinx::Search.new.foo
62
+ }.should raise_error(NoMethodError)
63
+ end
64
+
65
+ it "should accept sphinx scopes" do
66
+ search = ThinkingSphinx::Search.new(:classes => [Alpha])
67
+
68
+ lambda {
69
+ search.by_name('Pat')
70
+ }.should_not raise_error(NoMethodError)
71
+ end
72
+
73
+ it "should return itself when using a sphinx scope" do
74
+ search = ThinkingSphinx::Search.new(:classes => [Alpha])
75
+ search.by_name('Pat').object_id.should == search.object_id
76
+ end
77
+
78
+ it "should keep the same search object when chaining multiple scopes" do
79
+ search = ThinkingSphinx::Search.new(:classes => [Alpha])
80
+ search.by_name('Pat').ids_only.object_id.should == search.object_id
81
+ end
82
+ end
83
+
84
+ describe '.search' do
85
+ it "return the output of ThinkingSphinx.search" do
86
+ @results = [] # to confirm same object
87
+ ThinkingSphinx.stub!(:search => @results)
88
+
89
+ ThinkingSphinx::Search.search.object_id.should == @results.object_id
90
+ end
91
+ end
92
+
93
+ describe '.search_for_ids' do
94
+ it "return the output of ThinkingSphinx.search_for_ids" do
95
+ @results = [] # to confirm same object
96
+ ThinkingSphinx.stub!(:search_for_ids => @results)
97
+
98
+ ThinkingSphinx::Search.search_for_ids.object_id.
99
+ should == @results.object_id
100
+ end
101
+ end
102
+
103
+ describe '.search_for_id' do
104
+ it "return the output of ThinkingSphinx.search_for_ids" do
105
+ @results = [] # to confirm same object
106
+ ThinkingSphinx.stub!(:search_for_id => @results)
107
+
108
+ ThinkingSphinx::Search.search_for_id.object_id.
109
+ should == @results.object_id
110
+ end
111
+ end
112
+
113
+ describe '.count' do
114
+ it "return the output of ThinkingSphinx.search" do
115
+ @results = [] # to confirm same object
116
+ ThinkingSphinx.stub!(:count => @results)
117
+
118
+ ThinkingSphinx::Search.count.object_id.should == @results.object_id
119
+ end
120
+ end
121
+
122
+ describe '.facets' do
123
+ it "return the output of ThinkingSphinx.facets" do
124
+ @results = [] # to confirm same object
125
+ ThinkingSphinx.stub!(:facets => @results)
126
+
127
+ ThinkingSphinx::Search.facets.object_id.should == @results.object_id
128
+ end
129
+ end
130
+
131
+ describe '#populate' do
132
+ before :each do
133
+ @alpha_a, @alpha_b = Alpha.new, Alpha.new
134
+ @beta_a, @beta_b = Beta.new, Beta.new
135
+
136
+ @alpha_a.stub! :id => 1, :attributes => {'id' => 1}
137
+ @alpha_b.stub! :id => 2, :attributes => {'id' => 2}
138
+ @beta_a.stub! :id => 1, :attributes => {'id' => 1}
139
+ @beta_b.stub! :id => 2, :attributes => {'id' => 2}
140
+
141
+ @client.stub! :query => {
142
+ :matches => minimal_result_hashes(@alpha_a, @beta_b, @alpha_b, @beta_a)
143
+ }
144
+ Alpha.stub! :find => [@alpha_a, @alpha_b]
145
+ Beta.stub! :find => [@beta_a, @beta_b]
146
+ end
147
+
148
+ it "should issue only one select per model" do
149
+ Alpha.should_receive(:find).once.and_return([@alpha_a, @alpha_b])
150
+ Beta.should_receive(:find).once.and_return([@beta_a, @beta_b])
151
+
152
+ ThinkingSphinx::Search.new.first
153
+ end
154
+
155
+ it "should mix the results from different models" do
156
+ search = ThinkingSphinx::Search.new
157
+ search[0].should be_a(Alpha)
158
+ search[1].should be_a(Beta)
159
+ search[2].should be_a(Alpha)
160
+ search[3].should be_a(Beta)
161
+ end
162
+
163
+ it "should maintain the Xoopit ordering for results" do
164
+ search = ThinkingSphinx::Search.new
165
+ search[0].id.should == 1
166
+ search[1].id.should == 2
167
+ search[2].id.should == 2
168
+ search[3].id.should == 1
169
+ end
170
+
171
+ describe 'query' do
172
+ it "should concatenate arguments with spaces" do
173
+ @client.should_receive(:query) do |query, index, comment|
174
+ query.should == 'two words'
175
+ end
176
+
177
+ ThinkingSphinx::Search.new('two', 'words').first
178
+ end
179
+
180
+ it "should append conditions to the query" do
181
+ @client.should_receive(:query) do |query, index, comment|
182
+ query.should == 'general @focused specific'
183
+ end
184
+
185
+ ThinkingSphinx::Search.new('general', :conditions => {
186
+ :focused => 'specific'
187
+ }).first
188
+ end
189
+
190
+ it "append multiple conditions together" do
191
+ @client.should_receive(:query) do |query, index, comment|
192
+ query.should match(/general.+@foo word/)
193
+ query.should match(/general.+@bar word/)
194
+ end
195
+
196
+ ThinkingSphinx::Search.new('general', :conditions => {
197
+ :foo => 'word', :bar => 'word'
198
+ }).first
199
+ end
200
+
201
+ it "should apply stars if requested, and handle full extended syntax" do
202
+ input = %{a b* c (d | e) 123 5&6 (f_f g) !h "i j" "k l"~10 "m n"/3 @o p -(q|r)}
203
+ expected = %{*a* b* *c* (*d* | *e*) *123* *5*&*6* (*f_f* *g*) !*h* "i j" "k l"~10 "m n"/3 @o *p* -(*q*|*r*)}
204
+
205
+ @client.should_receive(:query) do |query, index, comment|
206
+ query.should == expected
207
+ end
208
+
209
+ ThinkingSphinx::Search.new(input, :star => true).first
210
+ end
211
+
212
+ it "should default to /\w+/ as token for auto-starring" do
213
+ @client.should_receive(:query) do |query, index, comment|
214
+ query.should == '*foo*@*bar*.*com*'
215
+ end
216
+
217
+ ThinkingSphinx::Search.new('foo@bar.com', :star => true).first
218
+ end
219
+
220
+ it "should honour custom star tokens" do
221
+ @client.should_receive(:query) do |query, index, comment|
222
+ query.should == '*foo@bar.com* -*foo-bar*'
223
+ end
224
+
225
+ ThinkingSphinx::Search.new(
226
+ 'foo@bar.com -foo-bar', :star => /[\w@.-]+/u
227
+ ).first
228
+ end
229
+ end
230
+
231
+ describe 'comment' do
232
+ it "should add comment if explicitly provided" do
233
+ @client.should_receive(:query) do |query, index, comment|
234
+ comment.should == 'custom log'
235
+ end
236
+
237
+ ThinkingSphinx::Search.new(:comment => 'custom log').first
238
+ end
239
+
240
+ it "should default to a blank comment" do
241
+ @client.should_receive(:query) do |query, index, comment|
242
+ comment.should == ''
243
+ end
244
+
245
+ ThinkingSphinx::Search.new.first
246
+ end
247
+ end
248
+
249
+ describe 'match mode' do
250
+ it "should default to :all" do
251
+ ThinkingSphinx::Search.new.first
252
+
253
+ @client.match_mode.should == :all
254
+ end
255
+
256
+ it "should default to :extended if conditions are supplied" do
257
+ ThinkingSphinx::Search.new('general', :conditions => {
258
+ :foo => 'word', :bar => 'word'
259
+ }).first
260
+
261
+ @client.match_mode.should == :extended
262
+ end
263
+
264
+ it "should use explicit match modes" do
265
+ ThinkingSphinx::Search.new('general', :conditions => {
266
+ :foo => 'word', :bar => 'word'
267
+ }, :match_mode => :extended2).first
268
+
269
+ @client.match_mode.should == :extended2
270
+ end
271
+ end
272
+
273
+ describe 'pagination' do
274
+ it "should set the limit using per_page" do
275
+ ThinkingSphinx::Search.new(:per_page => 30).first
276
+ @client.limit.should == 30
277
+ end
278
+
279
+ it "should set the offset if pagination is requested" do
280
+ ThinkingSphinx::Search.new(:page => 3).first
281
+ @client.offset.should == 40
282
+ end
283
+
284
+ it "should set the offset by the per_page value" do
285
+ ThinkingSphinx::Search.new(:page => 3, :per_page => 30).first
286
+ @client.offset.should == 60
287
+ end
288
+ end
289
+
290
+ describe 'filters' do
291
+ it "should filter out deleted values by default" do
292
+ ThinkingSphinx::Search.new.first
293
+
294
+ filter = @client.filters.last
295
+ filter.values.should == [0]
296
+ filter.attribute.should == 'sphinx_deleted'
297
+ filter.exclude?.should be_false
298
+ end
299
+
300
+ it "should add class filters for explicit classes" do
301
+ ThinkingSphinx::Search.new(:classes => [Alpha, Beta]).first
302
+
303
+ filter = @client.filters.last
304
+ filter.values.should == [Alpha.to_crc32, Beta.to_crc32]
305
+ filter.attribute.should == 'class_crc'
306
+ filter.exclude?.should be_false
307
+ end
308
+
309
+ it "should add class filters for subclasses of requested classes" do
310
+ ThinkingSphinx::Search.new(:classes => [Person]).first
311
+
312
+ filter = @client.filters.last
313
+ filter.values.should == [
314
+ Parent.to_crc32, Admin::Person.to_crc32,
315
+ Child.to_crc32, Person.to_crc32
316
+ ]
317
+ filter.attribute.should == 'class_crc'
318
+ filter.exclude?.should be_false
319
+ end
320
+
321
+ it "should append inclusive filters of integers" do
322
+ ThinkingSphinx::Search.new(:with => {:int => 1}).first
323
+
324
+ filter = @client.filters.last
325
+ filter.values.should == [1]
326
+ filter.attribute.should == 'int'
327
+ filter.exclude?.should be_false
328
+ end
329
+
330
+ it "should append inclusive filters of floats" do
331
+ ThinkingSphinx::Search.new(:with => {:float => 1.5}).first
332
+
333
+ filter = @client.filters.last
334
+ filter.values.should == [1.5]
335
+ filter.attribute.should == 'float'
336
+ filter.exclude?.should be_false
337
+ end
338
+
339
+ it "should append inclusive filters of booleans" do
340
+ ThinkingSphinx::Search.new(:with => {:boolean => true}).first
341
+
342
+ filter = @client.filters.last
343
+ filter.values.should == [true]
344
+ filter.attribute.should == 'boolean'
345
+ filter.exclude?.should be_false
346
+ end
347
+
348
+ it "should append inclusive filters of arrays" do
349
+ ThinkingSphinx::Search.new(:with => {:ints => [1, 2, 3]}).first
350
+
351
+ filter = @client.filters.last
352
+ filter.values.should == [1, 2, 3]
353
+ filter.attribute.should == 'ints'
354
+ filter.exclude?.should be_false
355
+ end
356
+
357
+ it "should append inclusive filters of time ranges" do
358
+ first, last = 1.week.ago, Time.now
359
+ ThinkingSphinx::Search.new(:with => {
360
+ :time => first..last
361
+ }).first
362
+
363
+ filter = @client.filters.last
364
+ filter.values.should == (first.to_i..last.to_i)
365
+ filter.attribute.should == 'time'
366
+ filter.exclude?.should be_false
367
+ end
368
+
369
+ it "should append exclusive filters of integers" do
370
+ ThinkingSphinx::Search.new(:without => {:int => 1}).first
371
+
372
+ filter = @client.filters.last
373
+ filter.values.should == [1]
374
+ filter.attribute.should == 'int'
375
+ filter.exclude?.should be_true
376
+ end
377
+
378
+ it "should append exclusive filters of floats" do
379
+ ThinkingSphinx::Search.new(:without => {:float => 1.5}).first
380
+
381
+ filter = @client.filters.last
382
+ filter.values.should == [1.5]
383
+ filter.attribute.should == 'float'
384
+ filter.exclude?.should be_true
385
+ end
386
+
387
+ it "should append exclusive filters of booleans" do
388
+ ThinkingSphinx::Search.new(:without => {:boolean => true}).first
389
+
390
+ filter = @client.filters.last
391
+ filter.values.should == [true]
392
+ filter.attribute.should == 'boolean'
393
+ filter.exclude?.should be_true
394
+ end
395
+
396
+ it "should append exclusive filters of arrays" do
397
+ ThinkingSphinx::Search.new(:without => {:ints => [1, 2, 3]}).first
398
+
399
+ filter = @client.filters.last
400
+ filter.values.should == [1, 2, 3]
401
+ filter.attribute.should == 'ints'
402
+ filter.exclude?.should be_true
403
+ end
404
+
405
+ it "should append exclusive filters of time ranges" do
406
+ first, last = 1.week.ago, Time.now
407
+ ThinkingSphinx::Search.new(:without => {
408
+ :time => first..last
409
+ }).first
410
+
411
+ filter = @client.filters.last
412
+ filter.values.should == (first.to_i..last.to_i)
413
+ filter.attribute.should == 'time'
414
+ filter.exclude?.should be_true
415
+ end
416
+
417
+ it "should add separate filters for each item in a with_all value" do
418
+ ThinkingSphinx::Search.new(:with_all => {:ints => [1, 2, 3]}).first
419
+
420
+ filters = @client.filters[-3, 3]
421
+ filters.each do |filter|
422
+ filter.attribute.should == 'ints'
423
+ filter.exclude?.should be_false
424
+ end
425
+
426
+ filters[0].values.should == [1]
427
+ filters[1].values.should == [2]
428
+ filters[2].values.should == [3]
429
+ end
430
+
431
+ it "should filter out specific ids using :without_ids" do
432
+ ThinkingSphinx::Search.new(:without_ids => [4, 5, 6]).first
433
+
434
+ filter = @client.filters.last
435
+ filter.values.should == [4, 5, 6]
436
+ filter.attribute.should == 'sphinx_internal_id'
437
+ filter.exclude?.should be_true
438
+ end
439
+
440
+ describe 'in :conditions' do
441
+ it "should add as filters for known attributes in :conditions option" do
442
+ ThinkingSphinx::Search.new('general',
443
+ :conditions => {:word => 'specific', :lat => 1.5},
444
+ :classes => [Alpha]
445
+ ).first
446
+
447
+ filter = @client.filters.last
448
+ filter.values.should == [1.5]
449
+ filter.attribute.should == 'lat'
450
+ filter.exclude?.should be_false
451
+ end
452
+
453
+ it "should not add the filter to the query string" do
454
+ @client.should_receive(:query) do |query, index, comment|
455
+ query.should == 'general @word specific'
456
+ end
457
+
458
+ ThinkingSphinx::Search.new('general',
459
+ :conditions => {:word => 'specific', :lat => 1.5},
460
+ :classes => [Alpha]
461
+ ).first
462
+ end
463
+ end
464
+ end
465
+
466
+ describe 'sort mode' do
467
+ it "should use :relevance as a default" do
468
+ ThinkingSphinx::Search.new.first
469
+ @client.sort_mode.should == :relevance
470
+ end
471
+
472
+ it "should use :attr_asc if a symbol is supplied to :order" do
473
+ ThinkingSphinx::Search.new(:order => :created_at).first
474
+ @client.sort_mode.should == :attr_asc
475
+ end
476
+
477
+ it "should use :attr_desc if :desc is the mode" do
478
+ ThinkingSphinx::Search.new(
479
+ :order => :created_at, :sort_mode => :desc
480
+ ).first
481
+ @client.sort_mode.should == :attr_desc
482
+ end
483
+
484
+ it "should use :extended if a string is supplied to :order" do
485
+ ThinkingSphinx::Search.new(:order => "created_at ASC").first
486
+ @client.sort_mode.should == :extended
487
+ end
488
+
489
+ it "should use :expr if explicitly requested" do
490
+ ThinkingSphinx::Search.new(
491
+ :order => "created_at ASC", :sort_mode => :expr
492
+ ).first
493
+ @client.sort_mode.should == :expr
494
+ end
495
+
496
+ it "should use :attr_desc if explicitly requested" do
497
+ ThinkingSphinx::Search.new(
498
+ :order => "created_at", :sort_mode => :desc
499
+ ).first
500
+ @client.sort_mode.should == :attr_desc
501
+ end
502
+ end
503
+
504
+ describe 'sort by' do
505
+ it "should presume order symbols are attributes" do
506
+ ThinkingSphinx::Search.new(:order => :created_at).first
507
+ @client.sort_by.should == 'created_at'
508
+ end
509
+
510
+ it "replace field names with their sortable attributes" do
511
+ ThinkingSphinx::Search.new(:order => :name, :classes => [Alpha]).first
512
+ @client.sort_by.should == 'name_sort'
513
+ end
514
+
515
+ it "should replace field names in strings" do
516
+ ThinkingSphinx::Search.new(
517
+ :order => "created_at ASC, name DESC", :classes => [Alpha]
518
+ ).first
519
+ @client.sort_by.should == 'created_at ASC, name_sort DESC'
520
+ end
521
+ end
522
+
523
+ describe 'max matches' do
524
+ it "should use the global setting by default" do
525
+ ThinkingSphinx::Search.new.first
526
+ @client.max_matches.should == 1000
527
+ end
528
+
529
+ it "should use explicit setting" do
530
+ ThinkingSphinx::Search.new(:max_matches => 2000).first
531
+ @client.max_matches.should == 2000
532
+ end
533
+ end
534
+
535
+ describe 'field weights' do
536
+ it "should set field weights as provided" do
537
+ ThinkingSphinx::Search.new(
538
+ :field_weights => {'foo' => 10, 'bar' => 5}
539
+ ).first
540
+
541
+ @client.field_weights.should == {
542
+ 'foo' => 10, 'bar' => 5
543
+ }
544
+ end
545
+
546
+ it "should use field weights set in the index" do
547
+ ThinkingSphinx::Search.new(:classes => [Alpha]).first
548
+
549
+ @client.field_weights.should == {'name' => 10}
550
+ end
551
+ end
552
+
553
+ describe 'index weights' do
554
+ it "should send index weights through to the client" do
555
+ ThinkingSphinx::Search.new(:index_weights => {'foo' => 100}).first
556
+ @client.index_weights.should == {'foo' => 100}
557
+ end
558
+
559
+ it "should convert classes to their core and delta index names" do
560
+ ThinkingSphinx::Search.new(:index_weights => {Alpha => 100}).first
561
+ @client.index_weights.should == {
562
+ 'alpha_core' => 100,
563
+ 'alpha_delta' => 100
564
+ }
565
+ end
566
+ end
567
+
568
+ describe 'grouping' do
569
+ it "should convert group into group_by and group_function" do
570
+ ThinkingSphinx::Search.new(:group => :edition).first
571
+
572
+ @client.group_function.should == :attr
573
+ @client.group_by.should == "edition"
574
+ end
575
+
576
+ it "should pass on explicit grouping arguments" do
577
+ ThinkingSphinx::Search.new(
578
+ :group_by => 'created_at',
579
+ :group_function => :attr,
580
+ :group_clause => 'clause',
581
+ :group_distinct => 'distinct'
582
+ ).first
583
+
584
+ @client.group_by.should == 'created_at'
585
+ @client.group_function.should == :attr
586
+ @client.group_clause.should == 'clause'
587
+ @client.group_distinct.should == 'distinct'
588
+ end
589
+ end
590
+
591
+ describe 'anchor' do
592
+ it "should detect lat and lng attributes on the given model" do
593
+ ThinkingSphinx::Search.new(
594
+ :geo => [1.0, -1.0],
595
+ :classes => [Alpha]
596
+ ).first
597
+
598
+ @client.anchor[:latitude_attribute].should == 'lat'
599
+ @client.anchor[:longitude_attribute].should == 'lng'
600
+ end
601
+
602
+ it "should detect lat and lon attributes on the given model" do
603
+ ThinkingSphinx::Search.new(
604
+ :geo => [1.0, -1.0],
605
+ :classes => [Beta]
606
+ ).first
607
+
608
+ @client.anchor[:latitude_attribute].should == 'lat'
609
+ @client.anchor[:longitude_attribute].should == 'lon'
610
+ end
611
+
612
+ it "should detect latitude and longitude attributes on the given model" do
613
+ ThinkingSphinx::Search.new(
614
+ :geo => [1.0, -1.0],
615
+ :classes => [Person]
616
+ ).first
617
+
618
+ @client.anchor[:latitude_attribute].should == 'latitude'
619
+ @client.anchor[:longitude_attribute].should == 'longitude'
620
+ end
621
+
622
+ it "should accept manually defined latitude and longitude attributes" do
623
+ ThinkingSphinx::Search.new(
624
+ :geo => [1.0, -1.0],
625
+ :classes => [Alpha],
626
+ :latitude_attr => :updown,
627
+ :longitude_attr => :leftright
628
+ ).first
629
+
630
+ @client.anchor[:latitude_attribute].should == 'updown'
631
+ @client.anchor[:longitude_attribute].should == 'leftright'
632
+ end
633
+
634
+ it "should accept manually defined latitude and longitude attributes in the given model" do
635
+ ThinkingSphinx::Search.new(
636
+ :geo => [1.0, -1.0],
637
+ :classes => [Friendship]
638
+ ).first
639
+
640
+ @client.anchor[:latitude_attribute].should == 'person_id'
641
+ @client.anchor[:longitude_attribute].should == 'person_id'
642
+ end
643
+
644
+ it "should accept geo array for geo-position values" do
645
+ ThinkingSphinx::Search.new(
646
+ :geo => [1.0, -1.0],
647
+ :classes => [Alpha]
648
+ ).first
649
+
650
+ @client.anchor[:latitude].should == 1.0
651
+ @client.anchor[:longitude].should == -1.0
652
+ end
653
+
654
+ it "should accept lat and lng options for geo-position values" do
655
+ ThinkingSphinx::Search.new(
656
+ :lat => 1.0,
657
+ :lng => -1.0,
658
+ :classes => [Alpha]
659
+ ).first
660
+
661
+ @client.anchor[:latitude].should == 1.0
662
+ @client.anchor[:longitude].should == -1.0
663
+ end
664
+ end
665
+
666
+ describe 'excerpts' do
667
+ before :each do
668
+ @search = ThinkingSphinx::Search.new
669
+ end
670
+
671
+ it "should add excerpts method if objects don't already have one" do
672
+ @search.first.should respond_to(:excerpts)
673
+ end
674
+
675
+ it "should return an instance of ThinkingSphinx::Excerpter" do
676
+ @search.first.excerpts.should be_a(ThinkingSphinx::Excerpter)
677
+ end
678
+
679
+ it "should not add excerpts method if objects already have one" do
680
+ @search.last.excerpts.should_not be_a(ThinkingSphinx::Excerpter)
681
+ end
682
+
683
+ it "should set up the excerpter with the instances and search" do
684
+ ThinkingSphinx::Excerpter.should_receive(:new).with(@search, @alpha_a)
685
+ ThinkingSphinx::Excerpter.should_receive(:new).with(@search, @alpha_b)
686
+
687
+ @search.first
688
+ end
689
+ end
690
+ end
691
+
692
+ describe '#current_page' do
693
+ it "should return 1 by default" do
694
+ ThinkingSphinx::Search.new.current_page.should == 1
695
+ end
696
+
697
+ it "should handle string page values" do
698
+ ThinkingSphinx::Search.new(:page => '2').current_page.should == 2
699
+ end
700
+
701
+ it "should handle empty string page values" do
702
+ ThinkingSphinx::Search.new(:page => '').current_page.should == 1
703
+ end
704
+
705
+ it "should return the requested page" do
706
+ ThinkingSphinx::Search.new(:page => 10).current_page.should == 10
707
+ end
708
+ end
709
+
710
+ describe '#per_page' do
711
+ it "should return 20 by default" do
712
+ ThinkingSphinx::Search.new.per_page.should == 20
713
+ end
714
+
715
+ it "should allow for custom values" do
716
+ ThinkingSphinx::Search.new(:per_page => 30).per_page.should == 30
717
+ end
718
+
719
+ it "should prioritise :limit over :per_page if given" do
720
+ ThinkingSphinx::Search.new(
721
+ :per_page => 30, :limit => 40
722
+ ).per_page.should == 40
723
+ end
724
+ end
725
+
726
+ describe '#total_pages' do
727
+ it "should calculate the total pages depending on per_page and total_entries" do
728
+ ThinkingSphinx::Search.new.total_pages.should == 3
729
+ end
730
+
731
+ it "should allow for custom per_page values" do
732
+ ThinkingSphinx::Search.new(:per_page => 30).total_pages.should == 2
733
+ end
734
+ end
735
+
736
+ describe '#next_page' do
737
+ it "should return one more than the current page" do
738
+ ThinkingSphinx::Search.new.next_page.should == 2
739
+ end
740
+
741
+ it "should return nil if on the last page" do
742
+ ThinkingSphinx::Search.new(:page => 3).next_page.should be_nil
743
+ end
744
+ end
745
+
746
+ describe '#previous_page' do
747
+ it "should return one less than the current page" do
748
+ ThinkingSphinx::Search.new(:page => 2).previous_page.should == 1
749
+ end
750
+
751
+ it "should return nil if on the first page" do
752
+ ThinkingSphinx::Search.new.previous_page.should be_nil
753
+ end
754
+ end
755
+
756
+ describe '#total_entries' do
757
+ it "should return the total number of results, not just the amount on the page" do
758
+ ThinkingSphinx::Search.new.total_entries.should == 41
759
+ end
760
+ end
761
+
762
+ describe '#offset' do
763
+ it "should default to 0" do
764
+ ThinkingSphinx::Search.new.offset.should == 0
765
+ end
766
+
767
+ it "should increase by the per_page value for each page in" do
768
+ ThinkingSphinx::Search.new(:per_page => 25, :page => 2).offset.should == 25
769
+ end
770
+ end
771
+
772
+ describe '.each_with_groupby_and_count' do
773
+ before :each do
774
+ @alpha = Alpha.new
775
+ @alpha.stub!(:id => 1, :attributes => {'id' => 1})
776
+
777
+ @client.stub! :query => {
778
+ :matches => [{
779
+ :attributes => {
780
+ 'sphinx_internal_id' => @alpha.id,
781
+ 'class_crc' => Alpha.to_crc32,
782
+ '@groupby' => 101,
783
+ '@count' => 5
784
+ }
785
+ }]
786
+ }
787
+ Alpha.stub!(:find => [@alpha])
788
+ end
789
+
790
+ it "should yield the match, group and count" do
791
+ search = ThinkingSphinx::Search.new
792
+ search.each_with_groupby_and_count do |obj, group, count|
793
+ obj.should == @alpha
794
+ group.should == 101
795
+ count.should == 5
796
+ end
797
+ end
798
+ end
799
+
800
+ describe '.each_with_weighting' do
801
+ before :each do
802
+ @alpha = Alpha.new
803
+ @alpha.stub!(:id => 1, :attributes => {'id' => 1})
804
+
805
+ @client.stub! :query => {
806
+ :matches => [{
807
+ :attributes => {
808
+ 'sphinx_internal_id' => @alpha.id,
809
+ 'class_crc' => Alpha.to_crc32
810
+ }, :weight => 12
811
+ }]
812
+ }
813
+ Alpha.stub!(:find => [@alpha])
814
+ end
815
+
816
+ it "should yield the match and weight" do
817
+ search = ThinkingSphinx::Search.new
818
+ search.each_with_weighting do |obj, weight|
819
+ obj.should == @alpha
820
+ weight.should == 12
821
+ end
822
+ end
823
+ end
824
+
825
+ describe '.each_with_*' do
826
+ before :each do
827
+ @alpha = Alpha.new
828
+ @alpha.stub!(:id => 1, :attributes => {'id' => 1})
829
+
830
+ @client.stub! :query => {
831
+ :matches => [{
832
+ :attributes => {
833
+ 'sphinx_internal_id' => @alpha.id,
834
+ 'class_crc' => Alpha.to_crc32,
835
+ '@geodist' => 101,
836
+ '@groupby' => 102,
837
+ '@count' => 103
838
+ }, :weight => 12
839
+ }]
840
+ }
841
+ Alpha.stub!(:find => [@alpha])
842
+
843
+ @search = ThinkingSphinx::Search.new
844
+ end
845
+
846
+ it "should yield geodist if requested" do
847
+ @search.each_with_geodist do |obj, distance|
848
+ obj.should == @alpha
849
+ distance.should == 101
850
+ end
851
+ end
852
+
853
+ it "should yield count if requested" do
854
+ @search.each_with_count do |obj, count|
855
+ obj.should == @alpha
856
+ count.should == 103
857
+ end
858
+ end
859
+
860
+ it "should yield groupby if requested" do
861
+ @search.each_with_groupby do |obj, group|
862
+ obj.should == @alpha
863
+ group.should == 102
864
+ end
865
+ end
866
+
867
+ it "should still use the array's each_with_index" do
868
+ @search.each_with_index do |obj, index|
869
+ obj.should == @alpha
870
+ index.should == 0
871
+ end
872
+ end
873
+ end
874
+
875
+ describe '#excerpt_for' do
876
+ before :each do
877
+ @client.stub!(:excerpts => ['excerpted string'])
878
+ @client.stub!(:query => {
879
+ :matches => [],
880
+ :words => {'one' => {}, 'two' => {}}
881
+ })
882
+ @search = ThinkingSphinx::Search.new(:classes => [Alpha])
883
+ end
884
+
885
+ it "should return the Sphinx excerpt value" do
886
+ @search.excerpt_for('string').should == 'excerpted string'
887
+ end
888
+
889
+ it "should use the given model's core index" do
890
+ @client.should_receive(:excerpts) do |options|
891
+ options[:index].should == 'alpha_core'
892
+ end
893
+
894
+ @search.excerpt_for('string')
895
+ end
896
+
897
+ it "should optionally take a second argument to allow for multi-model searches" do
898
+ @client.should_receive(:excerpts) do |options|
899
+ options[:index].should == 'beta_core'
900
+ end
901
+
902
+ @search.excerpt_for('string', Beta)
903
+ end
904
+
905
+ it "should join the words together" do
906
+ @client.should_receive(:excerpts) do |options|
907
+ options[:words].should == @search.results[:words].keys.join(' ')
908
+ end
909
+
910
+ @search.excerpt_for('string', Beta)
911
+ end
912
+ end
913
+
914
+ describe '#search' do
915
+ before :each do
916
+ @search = ThinkingSphinx::Search.new('word',
917
+ :conditions => {:field => 'field'},
918
+ :with => {:int => 5}
919
+ )
920
+ end
921
+
922
+ it "should return itself" do
923
+ @search.search.object_id.should == @search.object_id
924
+ end
925
+
926
+ it "should merge in arguments" do
927
+ @client.should_receive(:query) do |query, index, comments|
928
+ query.should == 'word more @field field'
929
+ end
930
+
931
+ @search.search('more').first
932
+ end
933
+
934
+ it "should merge conditions" do
935
+ @client.should_receive(:query) do |query, index, comments|
936
+ query.should match(/@name plato/)
937
+ query.should match(/@field field/)
938
+ end
939
+
940
+ @search.search(:conditions => {:name => 'plato'}).first
941
+ end
942
+
943
+ it "should merge filters" do
944
+ @search.search(:with => {:float => 1.5}).first
945
+
946
+ @client.filters.detect { |filter|
947
+ filter.attribute == 'float'
948
+ }.should_not be_nil
949
+ @client.filters.detect { |filter|
950
+ filter.attribute == 'int'
951
+ }.should_not be_nil
952
+ end
953
+ end
954
+ end
955
+
956
+ describe ThinkingSphinx::Search, "playing nice with Search model" do
957
+ it "should not conflict with models called Search" do
958
+ lambda { Search.find(:all) }.should_not raise_error
959
+ end
960
+ end