joshcutler-thinking-sphinx 1.3.18

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