DrMark-thinking-sphinx 1.1.15 → 1.2.5

Sign up to get free protection for your applications and to get access to all the features.
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