pixeltrix-thinking-sphinx 1.1.5 → 1.2.1

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