pixeltrix-thinking-sphinx 1.1.5 → 1.2.1

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 (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