skalee-thinking-sphinx 1.3.14.1

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