angelf-thinking-sphinx 1.3.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. data/LICENCE +20 -0
  2. data/README.textile +170 -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 +90 -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 +127 -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 +47 -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 +380 -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 +146 -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 +164 -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 +55 -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. data/tasks/testing.rb +76 -0
  159. metadata +342 -0
@@ -0,0 +1,1206 @@
1
+ require '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