zipme-thinking-sphinx 1.3.14

Sign up to get free protection for your applications and to get access to all the features.
Files changed (158) hide show
  1. data/LICENCE +20 -0
  2. data/README.textile +168 -0
  3. data/VERSION +1 -0
  4. data/features/abstract_inheritance.feature +10 -0
  5. data/features/alternate_primary_key.feature +27 -0
  6. data/features/attribute_transformation.feature +22 -0
  7. data/features/attribute_updates.feature +51 -0
  8. data/features/deleting_instances.feature +67 -0
  9. data/features/direct_attributes.feature +11 -0
  10. data/features/excerpts.feature +13 -0
  11. data/features/extensible_delta_indexing.feature +9 -0
  12. data/features/facets.feature +82 -0
  13. data/features/facets_across_model.feature +29 -0
  14. data/features/handling_edits.feature +92 -0
  15. data/features/retry_stale_indexes.feature +24 -0
  16. data/features/searching_across_models.feature +20 -0
  17. data/features/searching_by_index.feature +40 -0
  18. data/features/searching_by_model.feature +175 -0
  19. data/features/searching_with_find_arguments.feature +56 -0
  20. data/features/sphinx_detection.feature +25 -0
  21. data/features/sphinx_scopes.feature +42 -0
  22. data/features/step_definitions/alpha_steps.rb +16 -0
  23. data/features/step_definitions/beta_steps.rb +7 -0
  24. data/features/step_definitions/common_steps.rb +188 -0
  25. data/features/step_definitions/extensible_delta_indexing_steps.rb +7 -0
  26. data/features/step_definitions/facet_steps.rb +96 -0
  27. data/features/step_definitions/find_arguments_steps.rb +36 -0
  28. data/features/step_definitions/gamma_steps.rb +15 -0
  29. data/features/step_definitions/scope_steps.rb +15 -0
  30. data/features/step_definitions/search_steps.rb +89 -0
  31. data/features/step_definitions/sphinx_steps.rb +35 -0
  32. data/features/sti_searching.feature +19 -0
  33. data/features/support/database.example.yml +3 -0
  34. data/features/support/db/fixtures/alphas.rb +10 -0
  35. data/features/support/db/fixtures/authors.rb +1 -0
  36. data/features/support/db/fixtures/betas.rb +10 -0
  37. data/features/support/db/fixtures/boxes.rb +9 -0
  38. data/features/support/db/fixtures/categories.rb +1 -0
  39. data/features/support/db/fixtures/cats.rb +3 -0
  40. data/features/support/db/fixtures/comments.rb +24 -0
  41. data/features/support/db/fixtures/developers.rb +29 -0
  42. data/features/support/db/fixtures/dogs.rb +3 -0
  43. data/features/support/db/fixtures/extensible_betas.rb +10 -0
  44. data/features/support/db/fixtures/foxes.rb +3 -0
  45. data/features/support/db/fixtures/gammas.rb +10 -0
  46. data/features/support/db/fixtures/music.rb +4 -0
  47. data/features/support/db/fixtures/people.rb +1001 -0
  48. data/features/support/db/fixtures/posts.rb +6 -0
  49. data/features/support/db/fixtures/robots.rb +14 -0
  50. data/features/support/db/fixtures/tags.rb +27 -0
  51. data/features/support/db/migrations/create_alphas.rb +8 -0
  52. data/features/support/db/migrations/create_animals.rb +5 -0
  53. data/features/support/db/migrations/create_authors.rb +3 -0
  54. data/features/support/db/migrations/create_authors_posts.rb +6 -0
  55. data/features/support/db/migrations/create_betas.rb +5 -0
  56. data/features/support/db/migrations/create_boxes.rb +5 -0
  57. data/features/support/db/migrations/create_categories.rb +3 -0
  58. data/features/support/db/migrations/create_comments.rb +10 -0
  59. data/features/support/db/migrations/create_developers.rb +9 -0
  60. data/features/support/db/migrations/create_extensible_betas.rb +5 -0
  61. data/features/support/db/migrations/create_gammas.rb +3 -0
  62. data/features/support/db/migrations/create_genres.rb +3 -0
  63. data/features/support/db/migrations/create_music.rb +6 -0
  64. data/features/support/db/migrations/create_people.rb +13 -0
  65. data/features/support/db/migrations/create_posts.rb +5 -0
  66. data/features/support/db/migrations/create_robots.rb +4 -0
  67. data/features/support/db/migrations/create_taggings.rb +5 -0
  68. data/features/support/db/migrations/create_tags.rb +4 -0
  69. data/features/support/env.rb +21 -0
  70. data/features/support/lib/generic_delta_handler.rb +8 -0
  71. data/features/support/models/alpha.rb +22 -0
  72. data/features/support/models/animal.rb +5 -0
  73. data/features/support/models/author.rb +3 -0
  74. data/features/support/models/beta.rb +8 -0
  75. data/features/support/models/box.rb +8 -0
  76. data/features/support/models/cat.rb +3 -0
  77. data/features/support/models/category.rb +4 -0
  78. data/features/support/models/comment.rb +10 -0
  79. data/features/support/models/developer.rb +16 -0
  80. data/features/support/models/dog.rb +3 -0
  81. data/features/support/models/extensible_beta.rb +9 -0
  82. data/features/support/models/fox.rb +5 -0
  83. data/features/support/models/gamma.rb +5 -0
  84. data/features/support/models/genre.rb +3 -0
  85. data/features/support/models/medium.rb +5 -0
  86. data/features/support/models/music.rb +8 -0
  87. data/features/support/models/person.rb +23 -0
  88. data/features/support/models/post.rb +21 -0
  89. data/features/support/models/robot.rb +12 -0
  90. data/features/support/models/tag.rb +3 -0
  91. data/features/support/models/tagging.rb +4 -0
  92. data/lib/cucumber/thinking_sphinx/external_world.rb +8 -0
  93. data/lib/cucumber/thinking_sphinx/internal_world.rb +126 -0
  94. data/lib/cucumber/thinking_sphinx/sql_logger.rb +20 -0
  95. data/lib/thinking_sphinx.rb +231 -0
  96. data/lib/thinking_sphinx/active_record.rb +373 -0
  97. data/lib/thinking_sphinx/active_record/attribute_updates.rb +46 -0
  98. data/lib/thinking_sphinx/active_record/delta.rb +61 -0
  99. data/lib/thinking_sphinx/active_record/has_many_association.rb +29 -0
  100. data/lib/thinking_sphinx/active_record/scopes.rb +75 -0
  101. data/lib/thinking_sphinx/adapters/abstract_adapter.rb +42 -0
  102. data/lib/thinking_sphinx/adapters/mysql_adapter.rb +54 -0
  103. data/lib/thinking_sphinx/adapters/postgresql_adapter.rb +143 -0
  104. data/lib/thinking_sphinx/association.rb +164 -0
  105. data/lib/thinking_sphinx/attribute.rb +362 -0
  106. data/lib/thinking_sphinx/auto_version.rb +22 -0
  107. data/lib/thinking_sphinx/class_facet.rb +15 -0
  108. data/lib/thinking_sphinx/configuration.rb +283 -0
  109. data/lib/thinking_sphinx/context.rb +68 -0
  110. data/lib/thinking_sphinx/core/array.rb +7 -0
  111. data/lib/thinking_sphinx/core/string.rb +15 -0
  112. data/lib/thinking_sphinx/deltas.rb +28 -0
  113. data/lib/thinking_sphinx/deltas/default_delta.rb +62 -0
  114. data/lib/thinking_sphinx/deploy/capistrano.rb +100 -0
  115. data/lib/thinking_sphinx/excerpter.rb +22 -0
  116. data/lib/thinking_sphinx/facet.rb +125 -0
  117. data/lib/thinking_sphinx/facet_search.rb +136 -0
  118. data/lib/thinking_sphinx/field.rb +82 -0
  119. data/lib/thinking_sphinx/index.rb +157 -0
  120. data/lib/thinking_sphinx/index/builder.rb +296 -0
  121. data/lib/thinking_sphinx/index/faux_column.rb +110 -0
  122. data/lib/thinking_sphinx/property.rb +162 -0
  123. data/lib/thinking_sphinx/rails_additions.rb +150 -0
  124. data/lib/thinking_sphinx/search.rb +775 -0
  125. data/lib/thinking_sphinx/search_methods.rb +439 -0
  126. data/lib/thinking_sphinx/source.rb +153 -0
  127. data/lib/thinking_sphinx/source/internal_properties.rb +46 -0
  128. data/lib/thinking_sphinx/source/sql.rb +130 -0
  129. data/lib/thinking_sphinx/tasks.rb +121 -0
  130. data/lib/thinking_sphinx/test.rb +52 -0
  131. data/rails/init.rb +16 -0
  132. data/spec/thinking_sphinx/active_record/delta_spec.rb +128 -0
  133. data/spec/thinking_sphinx/active_record/has_many_association_spec.rb +55 -0
  134. data/spec/thinking_sphinx/active_record/scopes_spec.rb +177 -0
  135. data/spec/thinking_sphinx/active_record_spec.rb +622 -0
  136. data/spec/thinking_sphinx/association_spec.rb +239 -0
  137. data/spec/thinking_sphinx/attribute_spec.rb +570 -0
  138. data/spec/thinking_sphinx/auto_version_spec.rb +39 -0
  139. data/spec/thinking_sphinx/configuration_spec.rb +234 -0
  140. data/spec/thinking_sphinx/context_spec.rb +119 -0
  141. data/spec/thinking_sphinx/core/array_spec.rb +9 -0
  142. data/spec/thinking_sphinx/core/string_spec.rb +9 -0
  143. data/spec/thinking_sphinx/excerpter_spec.rb +57 -0
  144. data/spec/thinking_sphinx/facet_search_spec.rb +176 -0
  145. data/spec/thinking_sphinx/facet_spec.rb +333 -0
  146. data/spec/thinking_sphinx/field_spec.rb +154 -0
  147. data/spec/thinking_sphinx/index/builder_spec.rb +479 -0
  148. data/spec/thinking_sphinx/index/faux_column_spec.rb +30 -0
  149. data/spec/thinking_sphinx/index_spec.rb +183 -0
  150. data/spec/thinking_sphinx/rails_additions_spec.rb +203 -0
  151. data/spec/thinking_sphinx/search_methods_spec.rb +152 -0
  152. data/spec/thinking_sphinx/search_spec.rb +1181 -0
  153. data/spec/thinking_sphinx/source_spec.rb +235 -0
  154. data/spec/thinking_sphinx_spec.rb +204 -0
  155. data/tasks/distribution.rb +39 -0
  156. data/tasks/rails.rake +1 -0
  157. data/tasks/testing.rb +72 -0
  158. metadata +242 -0
@@ -0,0 +1,1181 @@
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, :total => 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 '#populated?' do
36
+ before :each do
37
+ @search = ThinkingSphinx::Search.new
38
+ end
39
+
40
+ it "should be false if the client request has not been made" do
41
+ @search.populated?.should be_false
42
+ end
43
+
44
+ it "should be true once the client request has been made" do
45
+ @search.first
46
+ @search.populated?.should be_true
47
+ end
48
+ end
49
+
50
+ describe '#results' do
51
+ it "should populate search results before returning" do
52
+ @search = ThinkingSphinx::Search.new
53
+ @search.populated?.should be_false
54
+
55
+ @search.results
56
+ @search.populated?.should be_true
57
+ end
58
+ end
59
+
60
+ describe '#method_missing' do
61
+ before :each do
62
+ Alpha.sphinx_scope(:by_name) { |name|
63
+ {:conditions => {:name => name}}
64
+ }
65
+ Alpha.sphinx_scope(:ids_only) { {:ids_only => true} }
66
+ end
67
+
68
+ after :each do
69
+ Alpha.remove_sphinx_scopes
70
+ end
71
+
72
+ it "should handle Array methods" do
73
+ ThinkingSphinx::Search.new.private_methods.should be_an(Array)
74
+ end
75
+
76
+ it "should raise a NoMethodError exception if unknown method" do
77
+ lambda {
78
+ ThinkingSphinx::Search.new.foo
79
+ }.should raise_error(NoMethodError)
80
+ end
81
+
82
+ it "should not request results from client if method does not exist" do
83
+ @client.should_not_receive(:query)
84
+
85
+ lambda {
86
+ ThinkingSphinx::Search.new.foo
87
+ }.should raise_error(NoMethodError)
88
+ end
89
+
90
+ it "should accept sphinx scopes" do
91
+ search = ThinkingSphinx::Search.new(:classes => [Alpha])
92
+
93
+ lambda {
94
+ search.by_name('Pat')
95
+ }.should_not raise_error(NoMethodError)
96
+ end
97
+
98
+ it "should return itself when using a sphinx scope" do
99
+ search = ThinkingSphinx::Search.new(:classes => [Alpha])
100
+ search.by_name('Pat').object_id.should == search.object_id
101
+ end
102
+
103
+ it "should keep the same search object when chaining multiple scopes" do
104
+ search = ThinkingSphinx::Search.new(:classes => [Alpha])
105
+ search.by_name('Pat').ids_only.object_id.should == search.object_id
106
+ end
107
+ end
108
+
109
+ describe '.search' do
110
+ it "return the output of ThinkingSphinx.search" do
111
+ @results = [] # to confirm same object
112
+ ThinkingSphinx.stub!(:search => @results)
113
+
114
+ ThinkingSphinx::Search.search.object_id.should == @results.object_id
115
+ end
116
+ end
117
+
118
+ describe '.search_for_ids' do
119
+ it "return the output of ThinkingSphinx.search_for_ids" do
120
+ @results = [] # to confirm same object
121
+ ThinkingSphinx.stub!(:search_for_ids => @results)
122
+
123
+ ThinkingSphinx::Search.search_for_ids.object_id.
124
+ should == @results.object_id
125
+ end
126
+ end
127
+
128
+ describe '.search_for_id' do
129
+ it "return the output of ThinkingSphinx.search_for_ids" do
130
+ @results = [] # to confirm same object
131
+ ThinkingSphinx.stub!(:search_for_id => @results)
132
+
133
+ ThinkingSphinx::Search.search_for_id.object_id.
134
+ should == @results.object_id
135
+ end
136
+ end
137
+
138
+ describe '.count' do
139
+ it "return the output of ThinkingSphinx.search" do
140
+ @results = [] # to confirm same object
141
+ ThinkingSphinx.stub!(:count => @results)
142
+
143
+ ThinkingSphinx::Search.count.object_id.should == @results.object_id
144
+ end
145
+ end
146
+
147
+ describe '.facets' do
148
+ it "return the output of ThinkingSphinx.facets" do
149
+ @results = [] # to confirm same object
150
+ ThinkingSphinx.stub!(:facets => @results)
151
+
152
+ ThinkingSphinx::Search.facets.object_id.should == @results.object_id
153
+ end
154
+ end
155
+
156
+ describe '.matching_fields' do
157
+ it "should return objects with indexes matching 1's in the bitmask" do
158
+ fields = ['alpha', 'beta', 'gamma', 'delta', 'epsilon', 'zeta', 'eta']
159
+ ThinkingSphinx::Search.matching_fields(fields, 85).
160
+ should == ['alpha', 'gamma', 'epsilon', 'eta']
161
+
162
+ ThinkingSphinx::Search.matching_fields(fields, 42).
163
+ should == ['beta', 'delta', 'zeta']
164
+ end
165
+ end
166
+
167
+ describe '#populate' do
168
+ before :each do
169
+ @alpha_a, @alpha_b = Alpha.new, Alpha.new
170
+ @beta_a, @beta_b = Beta.new, Beta.new
171
+
172
+ @alpha_a.stub! :id => 1, :read_attribute => 1
173
+ @alpha_b.stub! :id => 2, :read_attribute => 2
174
+ @beta_a.stub! :id => 1, :read_attribute => 1
175
+ @beta_b.stub! :id => 2, :read_attribute => 2
176
+
177
+ @client.stub! :query => {
178
+ :matches => minimal_result_hashes(@alpha_a, @beta_b, @alpha_b, @beta_a),
179
+ :fields => ["one", "two", "three", "four", "five"]
180
+ }
181
+ Alpha.stub! :find => [@alpha_a, @alpha_b]
182
+ Beta.stub! :find => [@beta_a, @beta_b]
183
+ end
184
+
185
+ it "should issue only one select per model" do
186
+ Alpha.should_receive(:find).once.and_return([@alpha_a, @alpha_b])
187
+ Beta.should_receive(:find).once.and_return([@beta_a, @beta_b])
188
+
189
+ ThinkingSphinx::Search.new.first
190
+ end
191
+
192
+ it "should mix the results from different models" do
193
+ search = ThinkingSphinx::Search.new
194
+ search[0].should be_a(Alpha)
195
+ search[1].should be_a(Beta)
196
+ search[2].should be_a(Alpha)
197
+ search[3].should be_a(Beta)
198
+ end
199
+
200
+ it "should maintain the Xoopit ordering for results" do
201
+ search = ThinkingSphinx::Search.new
202
+ search[0].id.should == 1
203
+ search[1].id.should == 2
204
+ search[2].id.should == 2
205
+ search[3].id.should == 1
206
+ end
207
+
208
+ it "should use the requested classes to generate the index argument" do
209
+ @client.should_receive(:query) do |query, index, comment|
210
+ index.should == 'alpha_core,beta_core,beta_delta'
211
+ end
212
+
213
+ ThinkingSphinx::Search.new(:classes => [Alpha, Beta]).first
214
+ end
215
+
216
+ describe 'query' do
217
+ it "should concatenate arguments with spaces" do
218
+ @client.should_receive(:query) do |query, index, comment|
219
+ query.should == 'two words'
220
+ end
221
+
222
+ ThinkingSphinx::Search.new('two', 'words').first
223
+ end
224
+
225
+ it "should append conditions to the query" do
226
+ @client.should_receive(:query) do |query, index, comment|
227
+ query.should == 'general @focused specific'
228
+ end
229
+
230
+ ThinkingSphinx::Search.new('general', :conditions => {
231
+ :focused => 'specific'
232
+ }).first
233
+ end
234
+
235
+ it "append multiple conditions together" do
236
+ @client.should_receive(:query) do |query, index, comment|
237
+ query.should match(/general.+@foo word/)
238
+ query.should match(/general.+@bar word/)
239
+ end
240
+
241
+ ThinkingSphinx::Search.new('general', :conditions => {
242
+ :foo => 'word', :bar => 'word'
243
+ }).first
244
+ end
245
+
246
+ it "should apply stars if requested, and handle full extended syntax" do
247
+ 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)}
248
+ 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*)}
249
+
250
+ @client.should_receive(:query) do |query, index, comment|
251
+ query.should == expected
252
+ end
253
+
254
+ ThinkingSphinx::Search.new(input, :star => true).first
255
+ end
256
+
257
+ it "should default to /\w+/ as token for auto-starring" do
258
+ @client.should_receive(:query) do |query, index, comment|
259
+ query.should == '*foo*@*bar*.*com*'
260
+ end
261
+
262
+ ThinkingSphinx::Search.new('foo@bar.com', :star => true).first
263
+ end
264
+
265
+ it "should honour custom star tokens" do
266
+ @client.should_receive(:query) do |query, index, comment|
267
+ query.should == '*foo@bar.com* -*foo-bar*'
268
+ end
269
+
270
+ ThinkingSphinx::Search.new(
271
+ 'foo@bar.com -foo-bar', :star => /[\w@.-]+/u
272
+ ).first
273
+ end
274
+ end
275
+
276
+ describe 'comment' do
277
+ it "should add comment if explicitly provided" do
278
+ @client.should_receive(:query) do |query, index, comment|
279
+ comment.should == 'custom log'
280
+ end
281
+
282
+ ThinkingSphinx::Search.new(:comment => 'custom log').first
283
+ end
284
+
285
+ it "should default to a blank comment" do
286
+ @client.should_receive(:query) do |query, index, comment|
287
+ comment.should == ''
288
+ end
289
+
290
+ ThinkingSphinx::Search.new.first
291
+ end
292
+ end
293
+
294
+ describe 'match mode' do
295
+ it "should default to :all" do
296
+ ThinkingSphinx::Search.new.first
297
+
298
+ @client.match_mode.should == :all
299
+ end
300
+
301
+ it "should default to :extended if conditions are supplied" do
302
+ ThinkingSphinx::Search.new('general', :conditions => {
303
+ :foo => 'word', :bar => 'word'
304
+ }).first
305
+
306
+ @client.match_mode.should == :extended
307
+ end
308
+
309
+ it "should use explicit match modes" do
310
+ ThinkingSphinx::Search.new('general', :conditions => {
311
+ :foo => 'word', :bar => 'word'
312
+ }, :match_mode => :extended2).first
313
+
314
+ @client.match_mode.should == :extended2
315
+ end
316
+ end
317
+
318
+ describe 'sphinx_select' do
319
+ it "should default to *" do
320
+ ThinkingSphinx::Search.new.first
321
+
322
+ @client.select.should == "*"
323
+ end
324
+
325
+ it "should get set on the client if specified" do
326
+ ThinkingSphinx::Search.new('general',
327
+ :sphinx_select => "*, foo as bar"
328
+ ).first
329
+
330
+ @client.select.should == "*, foo as bar"
331
+ end
332
+
333
+ end
334
+
335
+ describe 'pagination' do
336
+ it "should set the limit using per_page" do
337
+ ThinkingSphinx::Search.new(:per_page => 30).first
338
+ @client.limit.should == 30
339
+ end
340
+
341
+ it "should set the offset if pagination is requested" do
342
+ ThinkingSphinx::Search.new(:page => 3).first
343
+ @client.offset.should == 40
344
+ end
345
+
346
+ it "should set the offset by the per_page value" do
347
+ ThinkingSphinx::Search.new(:page => 3, :per_page => 30).first
348
+ @client.offset.should == 60
349
+ end
350
+ end
351
+
352
+ describe 'filters' do
353
+ it "should filter out deleted values by default" do
354
+ ThinkingSphinx::Search.new.first
355
+
356
+ filter = @client.filters.last
357
+ filter.values.should == [0]
358
+ filter.attribute.should == 'sphinx_deleted'
359
+ filter.exclude?.should be_false
360
+ end
361
+
362
+ it "should add class filters for explicit classes" do
363
+ ThinkingSphinx::Search.new(:classes => [Alpha, Beta]).first
364
+
365
+ filter = @client.filters.last
366
+ filter.values.should == [Alpha.to_crc32, Beta.to_crc32]
367
+ filter.attribute.should == 'class_crc'
368
+ filter.exclude?.should be_false
369
+ end
370
+
371
+ it "should add class filters for subclasses of requested classes" do
372
+ ThinkingSphinx::Search.new(:classes => [Person]).first
373
+
374
+ filter = @client.filters.last
375
+ filter.values.should == [
376
+ Parent.to_crc32, Admin::Person.to_crc32,
377
+ Child.to_crc32, Person.to_crc32
378
+ ]
379
+ filter.attribute.should == 'class_crc'
380
+ filter.exclude?.should be_false
381
+ end
382
+
383
+ it "should append inclusive filters of integers" do
384
+ ThinkingSphinx::Search.new(:with => {:int => 1}).first
385
+
386
+ filter = @client.filters.last
387
+ filter.values.should == [1]
388
+ filter.attribute.should == 'int'
389
+ filter.exclude?.should be_false
390
+ end
391
+
392
+ it "should append inclusive filters of floats" do
393
+ ThinkingSphinx::Search.new(:with => {:float => 1.5}).first
394
+
395
+ filter = @client.filters.last
396
+ filter.values.should == [1.5]
397
+ filter.attribute.should == 'float'
398
+ filter.exclude?.should be_false
399
+ end
400
+
401
+ it "should append inclusive filters of booleans" do
402
+ ThinkingSphinx::Search.new(:with => {:boolean => true}).first
403
+
404
+ filter = @client.filters.last
405
+ filter.values.should == [true]
406
+ filter.attribute.should == 'boolean'
407
+ filter.exclude?.should be_false
408
+ end
409
+
410
+ it "should append inclusive filters of arrays" do
411
+ ThinkingSphinx::Search.new(:with => {:ints => [1, 2, 3]}).first
412
+
413
+ filter = @client.filters.last
414
+ filter.values.should == [1, 2, 3]
415
+ filter.attribute.should == 'ints'
416
+ filter.exclude?.should be_false
417
+ end
418
+
419
+ it "should treat nils in arrays as 0" do
420
+ ThinkingSphinx::Search.new(:with => {:ints => [nil, 1, 2, 3]}).first
421
+
422
+ filter = @client.filters.last
423
+ filter.values.should == [0, 1, 2, 3]
424
+ end
425
+
426
+ it "should append inclusive filters of time ranges" do
427
+ first, last = 1.week.ago, Time.now
428
+ ThinkingSphinx::Search.new(:with => {
429
+ :time => first..last
430
+ }).first
431
+
432
+ filter = @client.filters.last
433
+ filter.values.should == (first.to_i..last.to_i)
434
+ filter.attribute.should == 'time'
435
+ filter.exclude?.should be_false
436
+ end
437
+
438
+ it "should append exclusive filters of integers" do
439
+ ThinkingSphinx::Search.new(:without => {:int => 1}).first
440
+
441
+ filter = @client.filters.last
442
+ filter.values.should == [1]
443
+ filter.attribute.should == 'int'
444
+ filter.exclude?.should be_true
445
+ end
446
+
447
+ it "should append exclusive filters of floats" do
448
+ ThinkingSphinx::Search.new(:without => {:float => 1.5}).first
449
+
450
+ filter = @client.filters.last
451
+ filter.values.should == [1.5]
452
+ filter.attribute.should == 'float'
453
+ filter.exclude?.should be_true
454
+ end
455
+
456
+ it "should append exclusive filters of booleans" do
457
+ ThinkingSphinx::Search.new(:without => {:boolean => true}).first
458
+
459
+ filter = @client.filters.last
460
+ filter.values.should == [true]
461
+ filter.attribute.should == 'boolean'
462
+ filter.exclude?.should be_true
463
+ end
464
+
465
+ it "should append exclusive filters of arrays" do
466
+ ThinkingSphinx::Search.new(:without => {:ints => [1, 2, 3]}).first
467
+
468
+ filter = @client.filters.last
469
+ filter.values.should == [1, 2, 3]
470
+ filter.attribute.should == 'ints'
471
+ filter.exclude?.should be_true
472
+ end
473
+
474
+ it "should append exclusive filters of time ranges" do
475
+ first, last = 1.week.ago, Time.now
476
+ ThinkingSphinx::Search.new(:without => {
477
+ :time => first..last
478
+ }).first
479
+
480
+ filter = @client.filters.last
481
+ filter.values.should == (first.to_i..last.to_i)
482
+ filter.attribute.should == 'time'
483
+ filter.exclude?.should be_true
484
+ end
485
+
486
+ it "should add separate filters for each item in a with_all value" do
487
+ ThinkingSphinx::Search.new(:with_all => {:ints => [1, 2, 3]}).first
488
+
489
+ filters = @client.filters[-3, 3]
490
+ filters.each do |filter|
491
+ filter.attribute.should == 'ints'
492
+ filter.exclude?.should be_false
493
+ end
494
+
495
+ filters[0].values.should == [1]
496
+ filters[1].values.should == [2]
497
+ filters[2].values.should == [3]
498
+ end
499
+
500
+ it "should filter out specific ids using :without_ids" do
501
+ ThinkingSphinx::Search.new(:without_ids => [4, 5, 6]).first
502
+
503
+ filter = @client.filters.last
504
+ filter.values.should == [4, 5, 6]
505
+ filter.attribute.should == 'sphinx_internal_id'
506
+ filter.exclude?.should be_true
507
+ end
508
+
509
+ describe 'in :conditions' do
510
+ it "should add as filters for known attributes in :conditions option" do
511
+ ThinkingSphinx::Search.new('general',
512
+ :conditions => {:word => 'specific', :lat => 1.5},
513
+ :classes => [Alpha]
514
+ ).first
515
+
516
+ filter = @client.filters.last
517
+ filter.values.should == [1.5]
518
+ filter.attribute.should == 'lat'
519
+ filter.exclude?.should be_false
520
+ end
521
+
522
+ it "should not add the filter to the query string" do
523
+ @client.should_receive(:query) do |query, index, comment|
524
+ query.should == 'general @word specific'
525
+ end
526
+
527
+ ThinkingSphinx::Search.new('general',
528
+ :conditions => {:word => 'specific', :lat => 1.5},
529
+ :classes => [Alpha]
530
+ ).first
531
+ end
532
+ end
533
+ end
534
+
535
+ describe 'sort mode' do
536
+ it "should use :relevance as a default" do
537
+ ThinkingSphinx::Search.new.first
538
+ @client.sort_mode.should == :relevance
539
+ end
540
+
541
+ it "should use :attr_asc if a symbol is supplied to :order" do
542
+ ThinkingSphinx::Search.new(:order => :created_at).first
543
+ @client.sort_mode.should == :attr_asc
544
+ end
545
+
546
+ it "should use :attr_desc if :desc is the mode" do
547
+ ThinkingSphinx::Search.new(
548
+ :order => :created_at, :sort_mode => :desc
549
+ ).first
550
+ @client.sort_mode.should == :attr_desc
551
+ end
552
+
553
+ it "should use :extended if a string is supplied to :order" do
554
+ ThinkingSphinx::Search.new(:order => "created_at ASC").first
555
+ @client.sort_mode.should == :extended
556
+ end
557
+
558
+ it "should use :expr if explicitly requested" do
559
+ ThinkingSphinx::Search.new(
560
+ :order => "created_at ASC", :sort_mode => :expr
561
+ ).first
562
+ @client.sort_mode.should == :expr
563
+ end
564
+
565
+ it "should use :attr_desc if explicitly requested" do
566
+ ThinkingSphinx::Search.new(
567
+ :order => "created_at", :sort_mode => :desc
568
+ ).first
569
+ @client.sort_mode.should == :attr_desc
570
+ end
571
+ end
572
+
573
+ describe 'sort by' do
574
+ it "should presume order symbols are attributes" do
575
+ ThinkingSphinx::Search.new(:order => :created_at).first
576
+ @client.sort_by.should == 'created_at'
577
+ end
578
+
579
+ it "replace field names with their sortable attributes" do
580
+ ThinkingSphinx::Search.new(:order => :name, :classes => [Alpha]).first
581
+ @client.sort_by.should == 'name_sort'
582
+ end
583
+
584
+ it "should replace field names in strings" do
585
+ ThinkingSphinx::Search.new(
586
+ :order => "created_at ASC, name DESC", :classes => [Alpha]
587
+ ).first
588
+ @client.sort_by.should == 'created_at ASC, name_sort DESC'
589
+ end
590
+ end
591
+
592
+ describe 'max matches' do
593
+ it "should use the global setting by default" do
594
+ ThinkingSphinx::Search.new.first
595
+ @client.max_matches.should == 1000
596
+ end
597
+
598
+ it "should use explicit setting" do
599
+ ThinkingSphinx::Search.new(:max_matches => 2000).first
600
+ @client.max_matches.should == 2000
601
+ end
602
+ end
603
+
604
+ describe 'field weights' do
605
+ it "should set field weights as provided" do
606
+ ThinkingSphinx::Search.new(
607
+ :field_weights => {'foo' => 10, 'bar' => 5}
608
+ ).first
609
+
610
+ @client.field_weights.should == {
611
+ 'foo' => 10, 'bar' => 5
612
+ }
613
+ end
614
+
615
+ it "should use field weights set in the index" do
616
+ ThinkingSphinx::Search.new(:classes => [Alpha]).first
617
+
618
+ @client.field_weights.should == {'name' => 10}
619
+ end
620
+ end
621
+
622
+ describe 'index weights' do
623
+ it "should send index weights through to the client" do
624
+ ThinkingSphinx::Search.new(:index_weights => {'foo' => 100}).first
625
+ @client.index_weights.should == {'foo' => 100}
626
+ end
627
+
628
+ it "should convert classes to their core and delta index names" do
629
+ ThinkingSphinx::Search.new(:index_weights => {Alpha => 100}).first
630
+ @client.index_weights.should == {
631
+ 'alpha_core' => 100,
632
+ 'alpha_delta' => 100
633
+ }
634
+ end
635
+ end
636
+
637
+ describe 'grouping' do
638
+ it "should convert group into group_by and group_function" do
639
+ ThinkingSphinx::Search.new(:group => :edition).first
640
+
641
+ @client.group_function.should == :attr
642
+ @client.group_by.should == "edition"
643
+ end
644
+
645
+ it "should pass on explicit grouping arguments" do
646
+ ThinkingSphinx::Search.new(
647
+ :group_by => 'created_at',
648
+ :group_function => :attr,
649
+ :group_clause => 'clause',
650
+ :group_distinct => 'distinct'
651
+ ).first
652
+
653
+ @client.group_by.should == 'created_at'
654
+ @client.group_function.should == :attr
655
+ @client.group_clause.should == 'clause'
656
+ @client.group_distinct.should == 'distinct'
657
+ end
658
+ end
659
+
660
+ describe 'anchor' do
661
+ it "should detect lat and lng attributes on the given model" do
662
+ ThinkingSphinx::Search.new(
663
+ :geo => [1.0, -1.0],
664
+ :classes => [Alpha]
665
+ ).first
666
+
667
+ @client.anchor[:latitude_attribute].should == 'lat'
668
+ @client.anchor[:longitude_attribute].should == 'lng'
669
+ end
670
+
671
+ it "should detect lat and lon attributes on the given model" do
672
+ ThinkingSphinx::Search.new(
673
+ :geo => [1.0, -1.0],
674
+ :classes => [Beta]
675
+ ).first
676
+
677
+ @client.anchor[:latitude_attribute].should == 'lat'
678
+ @client.anchor[:longitude_attribute].should == 'lon'
679
+ end
680
+
681
+ it "should detect latitude and longitude attributes on the given model" do
682
+ ThinkingSphinx::Search.new(
683
+ :geo => [1.0, -1.0],
684
+ :classes => [Person]
685
+ ).first
686
+
687
+ @client.anchor[:latitude_attribute].should == 'latitude'
688
+ @client.anchor[:longitude_attribute].should == 'longitude'
689
+ end
690
+
691
+ it "should accept manually defined latitude and longitude attributes" do
692
+ ThinkingSphinx::Search.new(
693
+ :geo => [1.0, -1.0],
694
+ :classes => [Alpha],
695
+ :latitude_attr => :updown,
696
+ :longitude_attr => :leftright
697
+ ).first
698
+
699
+ @client.anchor[:latitude_attribute].should == 'updown'
700
+ @client.anchor[:longitude_attribute].should == 'leftright'
701
+ end
702
+
703
+ it "should accept manually defined latitude and longitude attributes in the given model" do
704
+ ThinkingSphinx::Search.new(
705
+ :geo => [1.0, -1.0],
706
+ :classes => [Friendship]
707
+ ).first
708
+
709
+ @client.anchor[:latitude_attribute].should == 'person_id'
710
+ @client.anchor[:longitude_attribute].should == 'person_id'
711
+ end
712
+
713
+ it "should accept geo array for geo-position values" do
714
+ ThinkingSphinx::Search.new(
715
+ :geo => [1.0, -1.0],
716
+ :classes => [Alpha]
717
+ ).first
718
+
719
+ @client.anchor[:latitude].should == 1.0
720
+ @client.anchor[:longitude].should == -1.0
721
+ end
722
+
723
+ it "should accept lat and lng options for geo-position values" do
724
+ ThinkingSphinx::Search.new(
725
+ :lat => 1.0,
726
+ :lng => -1.0,
727
+ :classes => [Alpha]
728
+ ).first
729
+
730
+ @client.anchor[:latitude].should == 1.0
731
+ @client.anchor[:longitude].should == -1.0
732
+ end
733
+ end
734
+
735
+ describe 'sql ordering' do
736
+ before :each do
737
+ @client.stub! :query => {
738
+ :matches => minimal_result_hashes(@alpha_b, @alpha_a)
739
+ }
740
+ Alpha.stub! :find => [@alpha_a, @alpha_b]
741
+ end
742
+
743
+ it "shouldn't re-sort SQL results based on Sphinx information" do
744
+ search = ThinkingSphinx::Search.new(
745
+ :classes => [Alpha],
746
+ :sql_order => 'id'
747
+ )
748
+ search.first.should == @alpha_a
749
+ search.last.should == @alpha_b
750
+ end
751
+
752
+ it "should use the option for the ActiveRecord::Base#find calls" do
753
+ Alpha.should_receive(:find) do |mode, options|
754
+ options[:order].should == 'id'
755
+ end
756
+
757
+ ThinkingSphinx::Search.new(
758
+ :classes => [Alpha],
759
+ :sql_order => 'id'
760
+ ).first
761
+ end
762
+ end
763
+
764
+ context 'result objects' do
765
+ describe '#excerpts' do
766
+ before :each do
767
+ @search = ThinkingSphinx::Search.new
768
+ end
769
+
770
+ it "should add excerpts method if objects don't already have one" do
771
+ @search.first.should respond_to(:excerpts)
772
+ end
773
+
774
+ it "should return an instance of ThinkingSphinx::Excerpter" do
775
+ @search.first.excerpts.should be_a(ThinkingSphinx::Excerpter)
776
+ end
777
+
778
+ it "should not add excerpts method if objects already have one" do
779
+ @search.last.excerpts.should_not be_a(ThinkingSphinx::Excerpter)
780
+ end
781
+
782
+ it "should set up the excerpter with the instances and search" do
783
+ ThinkingSphinx::Excerpter.should_receive(:new).with(@search, @alpha_a)
784
+ ThinkingSphinx::Excerpter.should_receive(:new).with(@search, @alpha_b)
785
+
786
+ @search.first
787
+ end
788
+ end
789
+
790
+ describe '#sphinx_attributes' do
791
+ before :each do
792
+ @search = ThinkingSphinx::Search.new
793
+ end
794
+
795
+ it "should add sphinx_attributes method if objects don't already have one" do
796
+ @search.last.should respond_to(:sphinx_attributes)
797
+ end
798
+
799
+ it "should return a hash" do
800
+ @search.last.sphinx_attributes.should be_a(Hash)
801
+ end
802
+
803
+ it "should not add sphinx_attributes if objects have a method of that name already" do
804
+ @search.first.sphinx_attributes.should_not be_a(Hash)
805
+ end
806
+
807
+ it "should pair sphinx_attributes with the correct hash" do
808
+ hash = @search.last.sphinx_attributes
809
+ hash['sphinx_internal_id'].should == @search.last.id
810
+ hash['class_crc'].should == @search.last.class.to_crc32
811
+ end
812
+ end
813
+
814
+ describe '#matching_fields' do
815
+ it "should add matching_fields method if using fieldmask ranking mode" do
816
+ search = ThinkingSphinx::Search.new :rank_mode => :fieldmask
817
+ search.first.should respond_to(:matching_fields)
818
+ end
819
+
820
+ it "should not add matching_fields method if using a different ranking mode" do
821
+ search = ThinkingSphinx::Search.new :rank_mode => :bm25
822
+ search.first.should_not respond_to(:matching_fields)
823
+ end
824
+
825
+ it "should not add matching_fields method if object already have one" do
826
+ search = ThinkingSphinx::Search.new :rank_mode => :fieldmask
827
+ search.last.matching_fields.should_not be_an(Array)
828
+ end
829
+
830
+ it "should return an array" do
831
+ search = ThinkingSphinx::Search.new :rank_mode => :fieldmask
832
+ search.first.matching_fields.should be_an(Array)
833
+ end
834
+
835
+ it "should return the fields that the bitmask match" do
836
+ search = ThinkingSphinx::Search.new :rank_mode => :fieldmask
837
+ search.first.matching_fields.should == ['one', 'three', 'five']
838
+ end
839
+ end
840
+ end
841
+ end
842
+
843
+ describe '#current_page' do
844
+ it "should return 1 by default" do
845
+ ThinkingSphinx::Search.new.current_page.should == 1
846
+ end
847
+
848
+ it "should handle string page values" do
849
+ ThinkingSphinx::Search.new(:page => '2').current_page.should == 2
850
+ end
851
+
852
+ it "should handle empty string page values" do
853
+ ThinkingSphinx::Search.new(:page => '').current_page.should == 1
854
+ end
855
+
856
+ it "should return the requested page" do
857
+ ThinkingSphinx::Search.new(:page => 10).current_page.should == 10
858
+ end
859
+ end
860
+
861
+ describe '#per_page' do
862
+ it "should return 20 by default" do
863
+ ThinkingSphinx::Search.new.per_page.should == 20
864
+ end
865
+
866
+ it "should allow for custom values" do
867
+ ThinkingSphinx::Search.new(:per_page => 30).per_page.should == 30
868
+ end
869
+
870
+ it "should prioritise :limit over :per_page if given" do
871
+ ThinkingSphinx::Search.new(
872
+ :per_page => 30, :limit => 40
873
+ ).per_page.should == 40
874
+ end
875
+
876
+ it "should allow for string arguments" do
877
+ ThinkingSphinx::Search.new(:per_page => '10').per_page.should == 10
878
+ end
879
+ end
880
+
881
+ describe '#total_pages' do
882
+ it "should calculate the total pages depending on per_page and total_entries" do
883
+ ThinkingSphinx::Search.new.total_pages.should == 3
884
+ end
885
+
886
+ it "should allow for custom per_page values" do
887
+ ThinkingSphinx::Search.new(:per_page => 30).total_pages.should == 2
888
+ end
889
+
890
+ it "should not overstep the max_matches implied limit" do
891
+ @client.stub!(:query => {
892
+ :matches => [], :total_found => 41, :total => 40
893
+ })
894
+
895
+ ThinkingSphinx::Search.new.total_pages.should == 2
896
+ end
897
+
898
+ it "should return 0 if there is no index and therefore no results" do
899
+ @client.stub!(:query => {
900
+ :matches => [], :total_found => nil, :total => nil
901
+ })
902
+
903
+ ThinkingSphinx::Search.new.total_pages.should == 0
904
+ end
905
+ end
906
+
907
+ describe '#next_page' do
908
+ it "should return one more than the current page" do
909
+ ThinkingSphinx::Search.new.next_page.should == 2
910
+ end
911
+
912
+ it "should return nil if on the last page" do
913
+ ThinkingSphinx::Search.new(:page => 3).next_page.should be_nil
914
+ end
915
+ end
916
+
917
+ describe '#previous_page' do
918
+ it "should return one less than the current page" do
919
+ ThinkingSphinx::Search.new(:page => 2).previous_page.should == 1
920
+ end
921
+
922
+ it "should return nil if on the first page" do
923
+ ThinkingSphinx::Search.new.previous_page.should be_nil
924
+ end
925
+ end
926
+
927
+ describe '#total_entries' do
928
+ it "should return the total number of results, not just the amount on the page" do
929
+ ThinkingSphinx::Search.new.total_entries.should == 41
930
+ end
931
+
932
+ it "should return 0 if there is no index and therefore no results" do
933
+ @client.stub!(:query => {
934
+ :matches => [], :total_found => nil
935
+ })
936
+
937
+ ThinkingSphinx::Search.new.total_entries.should == 0
938
+ end
939
+ end
940
+
941
+ describe '#offset' do
942
+ it "should default to 0" do
943
+ ThinkingSphinx::Search.new.offset.should == 0
944
+ end
945
+
946
+ it "should increase by the per_page value for each page in" do
947
+ ThinkingSphinx::Search.new(:per_page => 25, :page => 2).offset.should == 25
948
+ end
949
+
950
+ it "should prioritise explicit :offset over calculated if given" do
951
+ ThinkingSphinx::Search.new(:offset => 5).offset.should == 5
952
+ end
953
+ end
954
+
955
+ describe '#indexes' do
956
+ it "should default to '*'" do
957
+ ThinkingSphinx::Search.new.indexes.should == '*'
958
+ end
959
+
960
+ it "should use given class to determine index name" do
961
+ ThinkingSphinx::Search.new(:classes => [Alpha]).indexes.
962
+ should == 'alpha_core'
963
+ end
964
+
965
+ it "should add both core and delta indexes for given classes" do
966
+ ThinkingSphinx::Search.new(:classes => [Alpha, Beta]).indexes.
967
+ should == 'alpha_core,beta_core,beta_delta'
968
+ end
969
+
970
+ it "should respect the :index option" do
971
+ ThinkingSphinx::Search.new(:classes => [Alpha], :index => '*').indexes.
972
+ should == '*'
973
+ end
974
+ end
975
+
976
+ describe '.each_with_groupby_and_count' do
977
+ before :each do
978
+ @alpha = Alpha.new
979
+ @alpha.stub!(:id => 1, :read_attribute => 1)
980
+
981
+ @client.stub! :query => {
982
+ :matches => [{
983
+ :attributes => {
984
+ 'sphinx_internal_id' => @alpha.id,
985
+ 'class_crc' => Alpha.to_crc32,
986
+ '@groupby' => 101,
987
+ '@count' => 5
988
+ }
989
+ }]
990
+ }
991
+ Alpha.stub!(:find => [@alpha])
992
+ end
993
+
994
+ it "should yield the match, group and count" do
995
+ search = ThinkingSphinx::Search.new
996
+ search.each_with_groupby_and_count do |obj, group, count|
997
+ obj.should == @alpha
998
+ group.should == 101
999
+ count.should == 5
1000
+ end
1001
+ end
1002
+
1003
+ it "should be aliased to each_with_group_and_count" do
1004
+ search = ThinkingSphinx::Search.new
1005
+ search.each_with_group_and_count do |obj, group, count|
1006
+ obj.should == @alpha
1007
+ group.should == 101
1008
+ count.should == 5
1009
+ end
1010
+ end
1011
+ end
1012
+
1013
+ describe '.each_with_weighting' do
1014
+ before :each do
1015
+ @alpha = Alpha.new
1016
+ @alpha.stub!(:id => 1, :read_attribute => 1)
1017
+
1018
+ @client.stub! :query => {
1019
+ :matches => [{
1020
+ :attributes => {
1021
+ 'sphinx_internal_id' => @alpha.id,
1022
+ 'class_crc' => Alpha.to_crc32
1023
+ }, :weight => 12
1024
+ }]
1025
+ }
1026
+ Alpha.stub!(:find => [@alpha])
1027
+ end
1028
+
1029
+ it "should yield the match and weight" do
1030
+ search = ThinkingSphinx::Search.new
1031
+ search.each_with_weighting do |obj, weight|
1032
+ obj.should == @alpha
1033
+ weight.should == 12
1034
+ end
1035
+ end
1036
+ end
1037
+
1038
+ describe '.each_with_*' do
1039
+ before :each do
1040
+ @alpha = Alpha.new
1041
+ @alpha.stub!(:id => 1, :read_attribute => 1)
1042
+
1043
+ @client.stub! :query => {
1044
+ :matches => [{
1045
+ :attributes => {
1046
+ 'sphinx_internal_id' => @alpha.id,
1047
+ 'class_crc' => Alpha.to_crc32,
1048
+ '@geodist' => 101,
1049
+ '@groupby' => 102,
1050
+ '@count' => 103
1051
+ }, :weight => 12
1052
+ }]
1053
+ }
1054
+ Alpha.stub!(:find => [@alpha])
1055
+
1056
+ @search = ThinkingSphinx::Search.new
1057
+ end
1058
+
1059
+ it "should yield geodist if requested" do
1060
+ @search.each_with_geodist do |obj, distance|
1061
+ obj.should == @alpha
1062
+ distance.should == 101
1063
+ end
1064
+ end
1065
+
1066
+ it "should yield count if requested" do
1067
+ @search.each_with_count do |obj, count|
1068
+ obj.should == @alpha
1069
+ count.should == 103
1070
+ end
1071
+ end
1072
+
1073
+ it "should yield groupby if requested" do
1074
+ @search.each_with_groupby do |obj, group|
1075
+ obj.should == @alpha
1076
+ group.should == 102
1077
+ end
1078
+ end
1079
+
1080
+ it "should still use the array's each_with_index" do
1081
+ @search.each_with_index do |obj, index|
1082
+ obj.should == @alpha
1083
+ index.should == 0
1084
+ end
1085
+ end
1086
+ end
1087
+
1088
+ describe '#excerpt_for' do
1089
+ before :each do
1090
+ @client.stub!(:excerpts => ['excerpted string'])
1091
+ @client.stub!(:query => {
1092
+ :matches => [],
1093
+ :words => {'one' => {}, 'two' => {}}
1094
+ })
1095
+ @search = ThinkingSphinx::Search.new(:classes => [Alpha])
1096
+ end
1097
+
1098
+ it "should return the Sphinx excerpt value" do
1099
+ @search.excerpt_for('string').should == 'excerpted string'
1100
+ end
1101
+
1102
+ it "should use the given model's core index" do
1103
+ @client.should_receive(:excerpts) do |options|
1104
+ options[:index].should == 'alpha_core'
1105
+ end
1106
+
1107
+ @search.excerpt_for('string')
1108
+ end
1109
+
1110
+ it "should optionally take a second argument to allow for multi-model searches" do
1111
+ @client.should_receive(:excerpts) do |options|
1112
+ options[:index].should == 'beta_core'
1113
+ end
1114
+
1115
+ @search.excerpt_for('string', Beta)
1116
+ end
1117
+
1118
+ it "should join the words together" do
1119
+ @client.should_receive(:excerpts) do |options|
1120
+ options[:words].should == @search.results[:words].keys.join(' ')
1121
+ end
1122
+
1123
+ @search.excerpt_for('string', Beta)
1124
+ end
1125
+
1126
+ it "should use the correct index in STI situations" do
1127
+ @client.should_receive(:excerpts) do |options|
1128
+ options[:index].should == 'person_core'
1129
+ end
1130
+
1131
+ @search.excerpt_for('string', Parent)
1132
+ end
1133
+ end
1134
+
1135
+ describe '#search' do
1136
+ before :each do
1137
+ @search = ThinkingSphinx::Search.new('word',
1138
+ :conditions => {:field => 'field'},
1139
+ :with => {:int => 5}
1140
+ )
1141
+ end
1142
+
1143
+ it "should return itself" do
1144
+ @search.search.object_id.should == @search.object_id
1145
+ end
1146
+
1147
+ it "should merge in arguments" do
1148
+ @client.should_receive(:query) do |query, index, comments|
1149
+ query.should == 'word more @field field'
1150
+ end
1151
+
1152
+ @search.search('more').first
1153
+ end
1154
+
1155
+ it "should merge conditions" do
1156
+ @client.should_receive(:query) do |query, index, comments|
1157
+ query.should match(/@name plato/)
1158
+ query.should match(/@field field/)
1159
+ end
1160
+
1161
+ @search.search(:conditions => {:name => 'plato'}).first
1162
+ end
1163
+
1164
+ it "should merge filters" do
1165
+ @search.search(:with => {:float => 1.5}).first
1166
+
1167
+ @client.filters.detect { |filter|
1168
+ filter.attribute == 'float'
1169
+ }.should_not be_nil
1170
+ @client.filters.detect { |filter|
1171
+ filter.attribute == 'int'
1172
+ }.should_not be_nil
1173
+ end
1174
+ end
1175
+ end
1176
+
1177
+ describe ThinkingSphinx::Search, "playing nice with Search model" do
1178
+ it "should not conflict with models called Search" do
1179
+ lambda { Search.find(:all) }.should_not raise_error
1180
+ end
1181
+ end