DrMark-thinking-sphinx 1.1.15 → 1.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.textile +22 -0
- data/VERSION.yml +4 -0
- data/lib/thinking_sphinx/active_record/scopes.rb +39 -0
- data/lib/thinking_sphinx/active_record.rb +27 -7
- data/lib/thinking_sphinx/adapters/postgresql_adapter.rb +9 -3
- data/lib/thinking_sphinx/association.rb +4 -1
- data/lib/thinking_sphinx/attribute.rb +91 -30
- data/lib/thinking_sphinx/configuration.rb +51 -12
- data/lib/thinking_sphinx/deltas/datetime_delta.rb +2 -2
- data/lib/thinking_sphinx/deltas/default_delta.rb +1 -1
- data/lib/thinking_sphinx/deltas/delayed_delta/delta_job.rb +1 -1
- data/lib/thinking_sphinx/deltas/delayed_delta.rb +3 -0
- data/lib/thinking_sphinx/deploy/capistrano.rb +25 -8
- data/lib/thinking_sphinx/excerpter.rb +22 -0
- data/lib/thinking_sphinx/facet.rb +1 -1
- data/lib/thinking_sphinx/facet_search.rb +134 -0
- data/lib/thinking_sphinx/index.rb +2 -1
- data/lib/thinking_sphinx/rails_additions.rb +14 -0
- data/lib/thinking_sphinx/search.rb +599 -658
- data/lib/thinking_sphinx/search_methods.rb +421 -0
- data/lib/thinking_sphinx/source/internal_properties.rb +1 -1
- data/lib/thinking_sphinx/source/sql.rb +17 -13
- data/lib/thinking_sphinx/source.rb +6 -6
- data/lib/thinking_sphinx/tasks.rb +42 -8
- data/lib/thinking_sphinx.rb +82 -54
- data/rails/init.rb +14 -0
- data/spec/{unit → lib}/thinking_sphinx/active_record/delta_spec.rb +5 -5
- data/spec/{unit → lib}/thinking_sphinx/active_record/has_many_association_spec.rb +0 -0
- data/spec/lib/thinking_sphinx/active_record/scopes_spec.rb +96 -0
- data/spec/{unit → lib}/thinking_sphinx/active_record_spec.rb +51 -31
- data/spec/{unit → lib}/thinking_sphinx/association_spec.rb +4 -5
- data/spec/lib/thinking_sphinx/attribute_spec.rb +465 -0
- data/spec/{unit → lib}/thinking_sphinx/configuration_spec.rb +161 -29
- data/spec/{unit → lib}/thinking_sphinx/core/string_spec.rb +0 -0
- data/spec/lib/thinking_sphinx/excerpter_spec.rb +49 -0
- data/spec/lib/thinking_sphinx/facet_search_spec.rb +176 -0
- data/spec/{unit → lib}/thinking_sphinx/facet_spec.rb +24 -0
- data/spec/{unit → lib}/thinking_sphinx/field_spec.rb +8 -8
- data/spec/{unit → lib}/thinking_sphinx/index/builder_spec.rb +6 -2
- data/spec/{unit → lib}/thinking_sphinx/index/faux_column_spec.rb +0 -0
- data/spec/lib/thinking_sphinx/index_spec.rb +45 -0
- data/spec/{unit → lib}/thinking_sphinx/rails_additions_spec.rb +25 -5
- data/spec/lib/thinking_sphinx/search_methods_spec.rb +152 -0
- data/spec/lib/thinking_sphinx/search_spec.rb +960 -0
- data/spec/{unit → lib}/thinking_sphinx/source_spec.rb +63 -2
- data/spec/{unit → lib}/thinking_sphinx_spec.rb +32 -4
- data/tasks/distribution.rb +36 -35
- data/vendor/riddle/lib/riddle/client/message.rb +4 -3
- data/vendor/riddle/lib/riddle/client.rb +3 -0
- data/vendor/riddle/lib/riddle/configuration/section.rb +8 -2
- data/vendor/riddle/lib/riddle/controller.rb +17 -7
- data/vendor/riddle/lib/riddle.rb +1 -1
- metadata +79 -83
- data/lib/thinking_sphinx/active_record/search.rb +0 -57
- data/lib/thinking_sphinx/collection.rb +0 -148
- data/lib/thinking_sphinx/facet_collection.rb +0 -59
- data/lib/thinking_sphinx/search/facets.rb +0 -98
- data/spec/unit/thinking_sphinx/active_record/search_spec.rb +0 -107
- data/spec/unit/thinking_sphinx/attribute_spec.rb +0 -232
- data/spec/unit/thinking_sphinx/collection_spec.rb +0 -14
- data/spec/unit/thinking_sphinx/facet_collection_spec.rb +0 -64
- data/spec/unit/thinking_sphinx/index_spec.rb +0 -139
- 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
|