outoftime-sunspot 0.8.9 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. data/README.rdoc +13 -21
  2. data/Rakefile +0 -2
  3. data/TODO +2 -15
  4. data/VERSION.yml +2 -2
  5. data/bin/sunspot-configure-solr +46 -0
  6. data/bin/sunspot-solr +15 -7
  7. data/lib/sunspot/adapters.rb +5 -1
  8. data/lib/sunspot/composite_setup.rb +186 -0
  9. data/lib/sunspot/configuration.rb +7 -1
  10. data/lib/sunspot/data_extractor.rb +10 -0
  11. data/lib/sunspot/date_facet.rb +36 -0
  12. data/lib/sunspot/date_facet_row.rb +17 -0
  13. data/lib/sunspot/dsl/field_query.rb +72 -0
  14. data/lib/sunspot/dsl/fields.rb +30 -3
  15. data/lib/sunspot/dsl/query.rb +16 -35
  16. data/lib/sunspot/dsl/query_facet.rb +31 -0
  17. data/lib/sunspot/dsl/scope.rb +76 -20
  18. data/lib/sunspot/dsl/search.rb +30 -0
  19. data/lib/sunspot/dsl.rb +1 -1
  20. data/lib/sunspot/facet.rb +17 -3
  21. data/lib/sunspot/facet_row.rb +4 -4
  22. data/lib/sunspot/field.rb +130 -207
  23. data/lib/sunspot/field_factory.rb +126 -0
  24. data/lib/sunspot/indexer.rb +61 -14
  25. data/lib/sunspot/instantiated_facet.rb +38 -0
  26. data/lib/sunspot/instantiated_facet_row.rb +12 -0
  27. data/lib/sunspot/query/base_query.rb +90 -0
  28. data/lib/sunspot/query/connective.rb +77 -0
  29. data/lib/sunspot/query/dynamic_query.rb +39 -56
  30. data/lib/sunspot/query/field_facet.rb +132 -4
  31. data/lib/sunspot/query/field_query.rb +57 -0
  32. data/lib/sunspot/query/pagination.rb +1 -1
  33. data/lib/sunspot/query/query_facet.rb +72 -0
  34. data/lib/sunspot/query/query_facet_row.rb +19 -0
  35. data/lib/sunspot/query/restriction.rb +9 -7
  36. data/lib/sunspot/query/scope.rb +165 -0
  37. data/lib/sunspot/query/sort.rb +17 -14
  38. data/lib/sunspot/query/sort_composite.rb +33 -0
  39. data/lib/sunspot/query.rb +162 -351
  40. data/lib/sunspot/query_facet.rb +33 -0
  41. data/lib/sunspot/query_facet_row.rb +21 -0
  42. data/lib/sunspot/schema.rb +165 -0
  43. data/lib/sunspot/search/hit.rb +62 -0
  44. data/lib/sunspot/search.rb +104 -41
  45. data/lib/sunspot/session.rb +64 -32
  46. data/lib/sunspot/setup.rb +119 -48
  47. data/lib/sunspot/type.rb +48 -2
  48. data/lib/sunspot.rb +74 -8
  49. data/solr/solr/conf/schema.xml +44 -225
  50. data/spec/api/build_search_spec.rb +557 -63
  51. data/spec/api/indexer_spec.rb +156 -74
  52. data/spec/api/query_spec.rb +55 -31
  53. data/spec/api/search_retrieval_spec.rb +210 -33
  54. data/spec/api/session_spec.rb +81 -26
  55. data/spec/api/sunspot_spec.rb +5 -7
  56. data/spec/integration/faceting_spec.rb +130 -0
  57. data/spec/integration/keyword_search_spec.rb +72 -31
  58. data/spec/integration/scoped_search_spec.rb +13 -0
  59. data/spec/integration/stored_fields_spec.rb +10 -0
  60. data/spec/mocks/blog.rb +3 -0
  61. data/spec/mocks/comment.rb +12 -23
  62. data/spec/mocks/connection.rb +84 -0
  63. data/spec/mocks/mock_adapter.rb +11 -3
  64. data/spec/mocks/mock_record.rb +41 -0
  65. data/spec/mocks/photo.rb +8 -0
  66. data/spec/mocks/post.rb +18 -23
  67. data/spec/spec_helper.rb +29 -14
  68. data/tasks/gemspec.rake +4 -3
  69. data/tasks/rdoc.rake +2 -2
  70. data/tasks/schema.rake +19 -0
  71. data/templates/schema.xml.haml +24 -0
  72. metadata +48 -7
  73. data/spec/mocks/base_class.rb +0 -2
@@ -1,215 +1,377 @@
1
1
  require File.join(File.dirname(__FILE__), 'spec_helper')
2
2
 
3
3
  describe 'Search' do
4
- it 'should search by keywords' do
5
- connection.should_receive(:query).with('(keyword search) AND (type:Post)', hash_including).twice
4
+ it 'should search by keywords from DSL' do
5
+ session.search Post do
6
+ keywords 'keyword search'
7
+ end
8
+ connection.should have_last_search_with(:q => 'keyword search')
9
+ end
10
+
11
+ it 'should search by keywords from options' do
6
12
  session.search Post, :keywords => 'keyword search'
13
+ connection.should have_last_search_with(:q => 'keyword search')
14
+ end
15
+
16
+ it 'should set default query parser to dismax when keywords used' do
7
17
  session.search Post do
8
18
  keywords 'keyword search'
9
19
  end
20
+ connection.should have_last_search_with(:defType => 'dismax')
10
21
  end
11
22
 
12
- it 'should scope by exact match with a string' do
13
- connection.should_receive(:query).with('(type:Post)', hash_including(:filter_queries => ['title_s:My\ Pet\ Post'])).twice
14
- session.search Post, :conditions => { :title => 'My Pet Post' }
23
+ it 'should search types in filter query if keywords used' do
24
+ session.search Post do
25
+ keywords 'keyword search'
26
+ end
27
+ connection.should have_last_search_with(:fq => 'type:Post')
28
+ end
29
+
30
+ it 'should search all text fields for searched class' do
31
+ session.search Post do
32
+ keywords 'keyword search'
33
+ end
34
+ connection.searches.last[:qf].split(' ').sort.should == %w(backwards_title_text body_text title_text)
35
+ end
36
+
37
+ it 'should search only specified text fields when specified' do
38
+ session.search Post do
39
+ keywords 'keyword search', :fields => [:title, :body]
40
+ end
41
+ connection.searches.last[:qf].split(' ').sort.should == %w(body_text title_text)
42
+ end
43
+
44
+ it 'should request score when keywords used' do
45
+ session.search Post, :keywords => 'keyword search'
46
+ connection.should have_last_search_with(:fl => '* score')
47
+ end
48
+
49
+ it 'should not request score when keywords not used' do
50
+ session.search Post
51
+ connection.should_not have_last_search_with(:fl)
52
+ end
53
+
54
+ it 'should scope by exact match with a string from DSL' do
15
55
  session.search Post do
16
56
  with :title, 'My Pet Post'
17
57
  end
58
+ connection.should have_last_search_with(:fq => ['title_ss:My\ Pet\ Post'])
59
+ end
60
+
61
+ it 'should scope by exact match with a string from options' do
62
+ session.search Post, :conditions => { :title => 'My Pet Post' }
63
+ connection.should have_last_search_with(:fq => ['title_ss:My\ Pet\ Post'])
18
64
  end
19
65
 
20
66
  it 'should ignore nonexistant fields in hash scope' do
21
- connection.should_receive(:query).with('(type:Post)', hash_not_including(:filter_queries))
22
67
  session.search Post, :conditions => { :bogus => 'Field' }
68
+ connection.should_not have_last_search_with(:fq)
23
69
  end
24
70
 
25
71
  it 'should scope by exact match with time' do
26
- connection.should_receive(:query).with('(type:Post)', hash_including(:filter_queries => ['published_at_d:1983\-07\-08T09\:00\:00Z'])).twice
27
72
  time = Time.parse('1983-07-08 05:00:00 -0400')
28
- session.search Post, :conditions => { :published_at => time }
29
73
  session.search Post do
30
74
  with :published_at, time
31
75
  end
76
+ connection.should have_last_search_with(
77
+ :fq => ['published_at_d:1983\-07\-08T09\:00\:00Z']
78
+ )
79
+ end
80
+
81
+ it 'should scope by exact match with date' do
82
+ date = Date.new(1983, 7, 8)
83
+ session.search Post do
84
+ with :expire_date, date
85
+ end
86
+ connection.should have_last_search_with(
87
+ :fq => ['expire_date_d:1983\-07\-08T00\:00\:00Z']
88
+ )
32
89
  end
33
90
 
34
91
  it 'should scope by exact match with boolean' do
35
- connection.should_receive(:query).with('(type:Post)', hash_including(:filter_queries => ['featured_b:false'])).twice
36
- session.search Post, :conditions => { :featured => false }
37
92
  session.search Post do
38
93
  with :featured, false
39
94
  end
95
+ connection.should have_last_search_with(:fq => ['featured_b:false'])
40
96
  end
41
97
 
42
98
  it 'should scope by less than match with float' do
43
- connection.should_receive(:query).with('(type:Post)', hash_including(:filter_queries => ['average_rating_f:[* TO 3\.0]']))
44
99
  session.search Post do
45
100
  with(:average_rating).less_than 3.0
46
101
  end
102
+ connection.should have_last_search_with(:fq => ['average_rating_f:[* TO 3\.0]'])
47
103
  end
48
104
 
49
105
  it 'should scope by greater than match with float' do
50
- connection.should_receive(:query).with('(type:Post)', hash_including(:filter_queries => ['average_rating_f:[3\.0 TO *]']))
51
106
  session.search Post do
52
107
  with(:average_rating).greater_than 3.0
53
108
  end
109
+ connection.should have_last_search_with(:fq => ['average_rating_f:[3\.0 TO *]'])
110
+ end
111
+
112
+ it 'should scope by short-form between match with integers' do
113
+ session.search Post do
114
+ with :blog_id, 2..4
115
+ end
116
+ connection.should have_last_search_with(:fq => ['blog_id_i:[2 TO 4]'])
54
117
  end
55
118
 
56
119
  it 'should scope by between match with float' do
57
- connection.should_receive(:query).with('(type:Post)', hash_including(:filter_queries => ['average_rating_f:[2\.0 TO 4\.0]'])).twice
58
- session.search Post, :conditions => { :average_rating => 2.0..4.0 }
59
120
  session.search Post do
60
121
  with(:average_rating).between 2.0..4.0
61
122
  end
123
+ connection.should have_last_search_with(:fq => ['average_rating_f:[2\.0 TO 4\.0]'])
62
124
  end
63
125
 
64
- it 'should scope by any match with integer' do
65
- connection.should_receive(:query).with('(type:Post)', hash_including(:filter_queries => ['category_ids_im:(2 OR 7 OR 12)'])).twice
66
- session.search Post, :conditions => { :category_ids => [2, 7, 12] }
126
+ it 'should scope by any match with integer using DSL' do
67
127
  session.search Post do
68
128
  with(:category_ids).any_of [2, 7, 12]
69
129
  end
130
+ connection.should have_last_search_with(:fq => ['category_ids_im:(2 OR 7 OR 12)'])
131
+ end
132
+
133
+ it 'should scope by any match with integer using options' do
134
+ session.search Post, :conditions => { :category_ids => [2, 7, 12] }
135
+ connection.should have_last_search_with(:fq => ['category_ids_im:(2 OR 7 OR 12)'])
136
+ end
137
+
138
+ it 'should scope by short-form any-of match with integers' do
139
+ session.search Post do
140
+ with :category_ids, [2, 7, 12]
141
+ end
142
+ connection.should have_last_search_with(:fq => ['category_ids_im:(2 OR 7 OR 12)'])
70
143
  end
71
144
 
72
145
  it 'should scope by all match with integer' do
73
- connection.should_receive(:query).with('(type:Post)', hash_including(:filter_queries => ['category_ids_im:(2 AND 7 AND 12)']))
74
146
  session.search Post do
75
147
  with(:category_ids).all_of [2, 7, 12]
76
148
  end
149
+ connection.should have_last_search_with(:fq => ['category_ids_im:(2 AND 7 AND 12)'])
77
150
  end
78
151
 
79
152
  it 'should scope by not equal match with string' do
80
- connection.should_receive(:query).with('(type:Post)', hash_including(:filter_queries => ['-title_s:Bad\ Post']))
81
153
  session.search Post do
82
154
  without :title, 'Bad Post'
83
155
  end
156
+ connection.should have_last_search_with(:fq => ['-title_ss:Bad\ Post'])
84
157
  end
85
158
 
86
159
  it 'should scope by not less than match with float' do
87
- connection.should_receive(:query).with('(type:Post)', hash_including(:filter_queries => ['-average_rating_f:[* TO 3\.0]']))
88
160
  session.search Post do
89
161
  without(:average_rating).less_than 3.0
90
162
  end
163
+ connection.should have_last_search_with(:fq => ['-average_rating_f:[* TO 3\.0]'])
91
164
  end
92
165
 
93
166
  it 'should scope by not greater than match with float' do
94
- connection.should_receive(:query).with('(type:Post)', hash_including(:filter_queries => ['-average_rating_f:[3\.0 TO *]']))
95
167
  session.search Post do
96
168
  without(:average_rating).greater_than 3.0
97
169
  end
170
+ connection.should have_last_search_with(:fq => ['-average_rating_f:[3\.0 TO *]'])
171
+ end
172
+
173
+ it 'should scope by not between match with shorthand' do
174
+ session.search Post do
175
+ without(:blog_id, 2..4)
176
+ end
177
+ connection.should have_last_search_with(:fq => ['-blog_id_i:[2 TO 4]'])
98
178
  end
99
179
 
100
180
  it 'should scope by not between match with float' do
101
- connection.should_receive(:query).with('(type:Post)', hash_including(:filter_queries => ['-average_rating_f:[2\.0 TO 4\.0]']))
102
181
  session.search Post do
103
182
  without(:average_rating).between 2.0..4.0
104
183
  end
184
+ connection.should have_last_search_with(:fq => ['-average_rating_f:[2\.0 TO 4\.0]'])
105
185
  end
106
186
 
107
187
  it 'should scope by not any match with integer' do
108
- connection.should_receive(:query).with('(type:Post)', hash_including(:filter_queries => ['-category_ids_im:(2 OR 7 OR 12)']))
109
188
  session.search Post do
110
189
  without(:category_ids).any_of [2, 7, 12]
111
190
  end
191
+ connection.should have_last_search_with(:fq => ['-category_ids_im:(2 OR 7 OR 12)'])
112
192
  end
113
193
 
114
194
 
115
195
  it 'should scope by not all match with integer' do
116
- connection.should_receive(:query).with('(type:Post)', hash_including(:filter_queries => ['-category_ids_im:(2 AND 7 AND 12)']))
117
196
  session.search Post do
118
197
  without(:category_ids).all_of [2, 7, 12]
119
198
  end
199
+ connection.should have_last_search_with(:fq => ['-category_ids_im:(2 AND 7 AND 12)'])
120
200
  end
121
201
 
122
202
  it 'should scope by empty field' do
123
- connection.should_receive(:query).with('(type:Post)', hash_including(:filter_queries => ['-average_rating_f:[* TO *]']))
124
203
  session.search Post do
125
204
  with :average_rating, nil
126
205
  end
206
+ connection.should have_last_search_with(:fq => ['-average_rating_f:[* TO *]'])
127
207
  end
128
208
 
129
209
  it 'should scope by non-empty field' do
130
- connection.should_receive(:query).with('(type:Post)', hash_including(:filter_queries => ['average_rating_f:[* TO *]']))
131
210
  session.search Post do
132
211
  without :average_rating, nil
133
212
  end
213
+ connection.should have_last_search_with(:fq => ['average_rating_f:[* TO *]'])
134
214
  end
135
215
 
136
216
  it 'should exclude by object identity' do
137
217
  post = Post.new
138
- connection.should_receive(:query).with('(type:Post)', hash_including(:filter_queries => ["-id:Post\\ #{post.id}"]))
139
218
  session.search Post do
140
219
  without post
141
220
  end
221
+ connection.should have_last_search_with(:fq => ["-id:Post\\ #{post.id}"])
142
222
  end
143
223
 
144
224
  it 'should exclude multiple objects passed as varargs by object identity' do
145
225
  post1, post2 = Post.new, Post.new
146
- connection.should_receive(:query).with('(type:Post)', hash_including(:filter_queries => ["-id:Post\\ #{post1.id}", "-id:Post\\ #{post2.id}"]))
147
226
  session.search Post do
148
227
  without post1, post2
149
228
  end
229
+ connection.should have_last_search_with(
230
+ :fq => ["-id:Post\\ #{post1.id}", "-id:Post\\ #{post2.id}"]
231
+ )
150
232
  end
151
233
 
152
234
  it 'should exclude multiple objects passed as array by object identity' do
153
235
  posts = [Post.new, Post.new]
154
- connection.should_receive(:query).with('(type:Post)', hash_including(:filter_queries => ["-id:Post\\ #{posts.first.id}", "-id:Post\\ #{posts.last.id}"]))
155
236
  session.search Post do
156
237
  without posts
157
238
  end
239
+ connection.should have_last_search_with(
240
+ :fq => ["-id:Post\\ #{posts.first.id}", "-id:Post\\ #{posts.last.id}"]
241
+ )
242
+ end
243
+
244
+ it 'should create a disjunction between two restrictions' do
245
+ session.search Post do
246
+ any_of do
247
+ with :category_ids, 1
248
+ with :blog_id, 2
249
+ end
250
+ end
251
+ connection.should have_last_search_with(
252
+ :fq => '(category_ids_im:1 OR blog_id_i:2)'
253
+ )
254
+ end
255
+
256
+ it 'should create a conjunction inside of a disjunction' do
257
+ session.search Post do
258
+ any_of do
259
+ with :blog_id, 2
260
+ all_of do
261
+ with :category_ids, 1
262
+ with(:average_rating).greater_than(3.0)
263
+ end
264
+ end
265
+ end
266
+ connection.should have_last_search_with(
267
+ :fq => '(blog_id_i:2 OR (category_ids_im:1 AND average_rating_f:[3\.0 TO *]))'
268
+ )
269
+ end
270
+
271
+ it 'should do nothing special if #all_of called from the top level' do
272
+ session.search Post do
273
+ all_of do
274
+ with :blog_id, 2
275
+ with :category_ids, 1
276
+ end
277
+ end
278
+ connection.should have_last_search_with(
279
+ :fq => ['blog_id_i:2', 'category_ids_im:1']
280
+ )
281
+ end
282
+
283
+ it 'should create a disjunction with negated restrictions' do
284
+ session.search Post do
285
+ any_of do
286
+ with :category_ids, 1
287
+ without(:average_rating).greater_than(3.0)
288
+ end
289
+ end
290
+ connection.should have_last_search_with(
291
+ :fq => '(category_ids_im:1 OR -average_rating_f:[3\.0 TO *])'
292
+ )
293
+ end
294
+
295
+ it 'should create a disjunction with instance exclusion' do
296
+ post = Post.new
297
+ session.search Post do
298
+ any_of do
299
+ without(post)
300
+ with(:category_ids, 1)
301
+ end
302
+ end
303
+ connection.should have_last_search_with(
304
+ :fq => "(-id:Post\\ #{post.id} OR category_ids_im:1)"
305
+ )
158
306
  end
159
307
 
160
308
  it 'should restrict by dynamic string field with equality restriction' do
161
- connection.should_receive(:query).with('(type:Post)', hash_including(:filter_queries => ['custom_string\:test_s:string']))
162
309
  session.search Post do
163
310
  dynamic :custom_string do
164
311
  with :test, 'string'
165
312
  end
166
313
  end
314
+ connection.should have_last_search_with(:fq => ['custom_string\:test_s:string'])
167
315
  end
168
316
 
169
317
  it 'should restrict by dynamic integer field with less than restriction' do
170
- connection.should_receive(:query).with(anything, hash_including(:filter_queries => ['custom_integer\:test_i:[* TO 1]']))
171
318
  session.search Post do
172
319
  dynamic :custom_integer do
173
320
  with(:test).less_than(1)
174
321
  end
175
322
  end
323
+ connection.should have_last_search_with(:fq => ['custom_integer\:test_i:[* TO 1]'])
176
324
  end
177
325
 
178
326
  it 'should restrict by dynamic float field with between restriction' do
179
- connection.should_receive(:query).with(anything, hash_including(:filter_queries => ['custom_float\:test_fm:[2\.2 TO 3\.3]']))
180
327
  session.search Post do
181
328
  dynamic :custom_float do
182
329
  with(:test).between(2.2..3.3)
183
330
  end
184
331
  end
332
+ connection.should have_last_search_with(:fq => ['custom_float\:test_fm:[2\.2 TO 3\.3]'])
185
333
  end
186
334
 
187
335
  it 'should restrict by dynamic time field with any of restriction' do
188
- connection.should_receive(:query).with(anything, hash_including(:filter_queries => ['custom_time\:test_d:(2009\-02\-10T14\:00\:00Z OR 2009\-02\-13T18\:00\:00Z)']))
189
336
  session.search Post do
190
337
  dynamic :custom_time do
191
338
  with(:test).any_of([Time.parse('2009-02-10 14:00:00 UTC'),
192
339
  Time.parse('2009-02-13 18:00:00 UTC')])
193
340
  end
194
341
  end
342
+ connection.should have_last_search_with(:fq => ['custom_time\:test_d:(2009\-02\-10T14\:00\:00Z OR 2009\-02\-13T18\:00\:00Z)'])
195
343
  end
196
344
 
197
345
  it 'should restrict by dynamic boolean field with equality restriction' do
198
- connection.should_receive(:query).with(anything, hash_including(:filter_queries => ['custom_boolean\:test_b:false']))
199
346
  session.search Post do
200
347
  dynamic :custom_boolean do
201
348
  with :test, false
202
349
  end
203
350
  end
351
+ connection.should have_last_search_with(:fq => ['custom_boolean\:test_b:false'])
204
352
  end
205
353
 
206
354
  it 'should negate a dynamic field restriction' do
207
- connection.should_receive(:query).with(anything, hash_including(:filter_queries => ['-custom_string\:test_s:foo']))
208
355
  session.search Post do
209
356
  dynamic :custom_string do
210
357
  without :test, 'foo'
211
358
  end
212
359
  end
360
+ connection.should have_last_search_with(:fq => ['-custom_string\:test_s:foo'])
361
+ end
362
+
363
+ it 'should search by a dynamic field inside a disjunction' do
364
+ session.search Post do
365
+ any_of do
366
+ dynamic :custom_string do
367
+ with :test, 'foo'
368
+ end
369
+ with :title, 'bar'
370
+ end
371
+ end
372
+ connection.should have_last_search_with(
373
+ :fq => '(custom_string\:test_s:foo OR title_ss:bar)'
374
+ )
213
375
  end
214
376
 
215
377
  it 'should throw an UnrecognizedFieldError if an unknown dynamic field is searched by' do
@@ -231,62 +393,83 @@ describe 'Search' do
231
393
  end
232
394
 
233
395
  it 'should paginate using default per_page when page not provided' do
234
- connection.should_receive(:query).with('(type:Post)', hash_including(:rows => 30))
235
396
  session.search Post
397
+ connection.should have_last_search_with(:rows => 30)
236
398
  end
237
399
 
238
- it 'should paginate using default per_page when page provided' do
239
- connection.should_receive(:query).with('(type:Post)', hash_including(:rows => 30, :start => 30)).twice
240
- session.search Post, :page => 2
400
+ it 'should paginate using default per_page when page provided in DSL' do
241
401
  session.search Post do
242
402
  paginate :page => 2
243
403
  end
404
+ connection.should have_last_search_with(:rows => 30, :start => 30)
244
405
  end
245
406
 
246
- it 'should paginate using provided per_page' do
247
- connection.should_receive(:query).with('(type:Post)', hash_including(:rows => 15, :start => 45)).twice
248
- session.search Post, :page => 4, :per_page => 15
407
+ it 'should paginate using default per_page when page provided in options' do
408
+ session.search Post, :page => 2
409
+ connection.should have_last_search_with(:rows => 30, :start => 30)
410
+ end
411
+
412
+ it 'should paginate using provided per_page in DSL' do
249
413
  session.search Post do
250
414
  paginate :page => 4, :per_page => 15
251
415
  end
416
+ connection.should have_last_search_with(:rows => 15, :start => 45)
252
417
  end
253
418
 
254
- it 'should order' do
255
- connection.should_receive(:query).with('(type:Post)', hash_including(:sort => [{ :average_rating_f => :descending }])).twice
256
- session.search Post, :order => 'average_rating desc'
419
+ it 'should paginate using provided per_page in options' do
420
+ session.search Post, :page => 4, :per_page => 15
421
+ connection.should have_last_search_with(:rows => 15, :start => 45)
422
+ end
423
+
424
+ it 'should order in DSL' do
257
425
  session.search Post do
258
426
  order_by :average_rating, :desc
259
427
  end
428
+ connection.should have_last_search_with(:sort => 'average_rating_f desc')
260
429
  end
261
430
 
262
- it 'should order by multiple fields' do
263
- connection.should_receive(:query).with('(type:Post)', hash_including(:sort => [{ :average_rating_f => :descending },
264
- { :sort_title_s => :ascending }])).twice
265
- session.search Post, :order => ['average_rating desc', 'sort_title asc']
431
+ it 'should order in keywords' do
432
+ session.search Post, :order => 'average_rating desc'
433
+ connection.should have_last_search_with(:sort => 'average_rating_f desc')
434
+ end
435
+
436
+ it 'should order by multiple fields in DSL' do
266
437
  session.search Post do
267
438
  order_by :average_rating, :desc
268
439
  order_by :sort_title, :asc
269
440
  end
441
+ connection.should have_last_search_with(:sort => 'average_rating_f desc, sort_title_s asc')
442
+ end
443
+
444
+ it 'should order by multiple fields in options' do
445
+ session.search Post, :order => ['average_rating desc', 'sort_title asc']
446
+ connection.should have_last_search_with(:sort => 'average_rating_f desc, sort_title_s asc')
270
447
  end
271
448
 
272
449
  it 'should order by a dynamic field' do
273
- connection.should_receive(:query).with(anything, hash_including(:sort => [{ :"custom_integer:test_i" => :descending }]))
274
450
  session.search Post do
275
451
  dynamic :custom_integer do
276
452
  order_by :test, :desc
277
453
  end
278
454
  end
455
+ connection.should have_last_search_with(:sort => 'custom_integer:test_i desc')
279
456
  end
280
457
 
281
458
  it 'should order by a dynamic field and static field, with given precedence' do
282
- connection.should_receive(:query).with(anything, hash_including(:sort => [{ :"custom_integer:test_i" => :descending },
283
- { :sort_title_s => :ascending}]))
284
459
  session.search Post do
285
460
  dynamic :custom_integer do
286
461
  order_by :test, :desc
287
462
  end
288
463
  order_by :sort_title, :asc
289
464
  end
465
+ connection.should have_last_search_with(:sort => 'custom_integer:test_i desc, sort_title_s asc')
466
+ end
467
+
468
+ it 'should order by random' do
469
+ session.search Post do
470
+ order_by_random
471
+ end
472
+ connection.searches.last[:sort].should =~ /^random_\d+ asc$/
290
473
  end
291
474
 
292
475
  it 'should throw an ArgumentError if a bogus order direction is given' do
@@ -297,46 +480,341 @@ describe 'Search' do
297
480
  end.should raise_error(ArgumentError)
298
481
  end
299
482
 
483
+ it 'should not allow ordering by multiple-value fields' do
484
+ lambda do
485
+ session.search Post do
486
+ order_by :category_ids
487
+ end
488
+ end.should raise_error(ArgumentError)
489
+ end
490
+
491
+ it 'should not turn faceting on if no facet requested' do
492
+ session.search(Post)
493
+ connection.should_not have_last_search_with('facet')
494
+ end
495
+
496
+ it 'should turn faceting on if facet is requested' do
497
+ session.search Post do
498
+ facet :category_ids
499
+ end
500
+ connection.should have_last_search_with(:facet => 'true')
501
+ end
502
+
300
503
  it 'should request single field facet' do
301
- connection.should_receive(:query).with('(type:Post)', hash_including(:facets => { :fields => %w(category_ids_im) }))
302
504
  session.search Post do
303
505
  facet :category_ids
304
506
  end
507
+ connection.should have_last_search_with(:"facet.field" => %w(category_ids_im))
305
508
  end
306
509
 
307
510
  it 'should request multiple field facets' do
308
- connection.should_receive(:query).with('(type:Post)', hash_including(:facets => { :fields => %w(category_ids_im blog_id_i) }))
309
511
  session.search Post do
310
512
  facet :category_ids, :blog_id
311
513
  end
514
+ connection.should have_last_search_with(:"facet.field" => %w(category_ids_im blog_id_i))
515
+ end
516
+
517
+ it 'should set facet sort by count' do
518
+ session.search Post do
519
+ facet :category_ids, :sort => :count
520
+ end
521
+ connection.should have_last_search_with(:"f.category_ids_im.facet.sort" => 'true')
522
+ end
523
+
524
+ it 'should set facet sort by index' do
525
+ session.search Post do
526
+ facet :category_ids, :sort => :index
527
+ end
528
+ connection.should have_last_search_with(:"f.category_ids_im.facet.sort" => 'false')
529
+ end
530
+
531
+ it 'should throw ArgumentError if bogus facet sort provided' do
532
+ lambda do
533
+ session.search Post do
534
+ facet :category_ids, :sort => :sideways
535
+ end
536
+ end.should raise_error(ArgumentError)
537
+ end
538
+
539
+ it 'should set the facet limit' do
540
+ session.search Post do
541
+ facet :category_ids, :limit => 10
542
+ end
543
+ connection.should have_last_search_with(:"f.category_ids_im.facet.limit" => 10)
544
+ end
545
+
546
+ it 'should set the facet minimum count' do
547
+ session.search Post do
548
+ facet :category_ids, :minimum_count => 5
549
+ end
550
+ connection.should have_last_search_with(:"f.category_ids_im.facet.mincount" => 5)
551
+ end
552
+
553
+ it 'should set the facet minimum count to zero if zeros are allowed' do
554
+ session.search Post do
555
+ facet :category_ids, :zeros => true
556
+ end
557
+ connection.should have_last_search_with(:"f.category_ids_im.facet.mincount" => 0)
558
+ end
559
+
560
+ it 'should set the facet minimum count to one by default' do
561
+ session.search Post do
562
+ facet :category_ids
563
+ end
564
+ connection.should have_last_search_with(:"f.category_ids_im.facet.mincount" => 1)
565
+ end
566
+
567
+ describe 'with date faceting' do
568
+ before :each do
569
+ @time_range = (Time.parse('2009-06-01 00:00:00 -0400')..
570
+ Time.parse('2009-07-01 00:00:00 -0400'))
571
+ end
572
+
573
+ it 'should not send date facet parameters if time range is not specified' do
574
+ session.search Post do |query|
575
+ query.facet :published_at
576
+ end
577
+ connection.should_not have_last_search_with(:"facet.date")
578
+ end
579
+
580
+ it 'should set the facet to a date facet' do
581
+ session.search Post do |query|
582
+ query.facet :published_at, :time_range => @time_range
583
+ end
584
+ connection.should have_last_search_with(:"facet.date" => ['published_at_d'])
585
+ end
586
+
587
+ it 'should set the facet start and end' do
588
+ session.search Post do |query|
589
+ query.facet :published_at, :time_range => @time_range
590
+ end
591
+ connection.should have_last_search_with(
592
+ :"f.published_at_d.facet.date.start" => '2009-06-01T04:00:00Z',
593
+ :"f.published_at_d.facet.date.end" => '2009-07-01T04:00:00Z'
594
+ )
595
+ end
596
+
597
+ it 'should default the time interval to 1 day' do
598
+ session.search Post do |query|
599
+ query.facet :published_at, :time_range => @time_range
600
+ end
601
+ connection.should have_last_search_with(:"f.published_at_d.facet.date.gap" => "+86400SECONDS")
602
+ end
603
+
604
+ it 'should use custom time interval' do
605
+ session.search Post do |query|
606
+ query.facet :published_at, :time_range => @time_range, :time_interval => 3600
607
+ end
608
+ connection.should have_last_search_with(:"f.published_at_d.facet.date.gap" => "+3600SECONDS")
609
+ end
610
+
611
+ it 'should allow computation of one other time' do
612
+ session.search Post do |query|
613
+ query.facet :published_at, :time_range => @time_range, :time_other => :before
614
+ end
615
+ connection.should have_last_search_with(:"f.published_at_d.facet.date.other" => %w(before))
616
+ end
617
+
618
+ it 'should allow computation of two other times' do
619
+ session.search Post do |query|
620
+ query.facet :published_at, :time_range => @time_range, :time_other => [:before, :after]
621
+ end
622
+ connection.should have_last_search_with(:"f.published_at_d.facet.date.other" => %w(before after))
623
+ end
624
+
625
+ it 'should not allow computation of bogus other time' do
626
+ lambda do
627
+ session.search Post do |query|
628
+ query.facet :published_at, :time_range => @time_range, :time_other => :bogus
629
+ end
630
+ end.should raise_error(ArgumentError)
631
+ end
632
+
633
+ it 'should not allow date faceting on a non-date field' do
634
+ lambda do
635
+ session.search Post do |query|
636
+ query.facet :blog_id, :time_range => @time_range
637
+ end
638
+ end.should raise_error(ArgumentError)
639
+ end
640
+ end
641
+
642
+ describe 'with query faceting' do
643
+ it 'should turn faceting on' do
644
+ session.search Post do
645
+ facet :foo do
646
+ row :bar do
647
+ with(:average_rating).between(4.0..5.0)
648
+ end
649
+ end
650
+ end
651
+ connection.should have_last_search_with(:facet => 'true')
652
+ end
653
+
654
+ it 'should facet by query' do
655
+ session.search Post do
656
+ facet :foo do
657
+ row :bar do
658
+ with(:average_rating).between(4.0..5.0)
659
+ end
660
+ end
661
+ end
662
+ connection.should have_last_search_with(:"facet.query" => 'average_rating_f:[4\.0 TO 5\.0]')
663
+ end
664
+
665
+ it 'should request multiple query facets' do
666
+ session.search Post do
667
+ facet :foo do
668
+ row :bar do
669
+ with(:average_rating).between(3.0..4.0)
670
+ end
671
+ row :baz do
672
+ with(:average_rating).between(4.0..5.0)
673
+ end
674
+ end
675
+ end
676
+ connection.should have_last_search_with(
677
+ :"facet.query" => [
678
+ 'average_rating_f:[3\.0 TO 4\.0]',
679
+ 'average_rating_f:[4\.0 TO 5\.0]'
680
+ ]
681
+ )
682
+ end
683
+
684
+ it 'should request query facet with multiple conditions' do
685
+ session.search Post do
686
+ facet :foo do
687
+ row :bar do
688
+ with(:category_ids, 1)
689
+ with(:blog_id, 2)
690
+ end
691
+ end
692
+ end
693
+ connection.should have_last_search_with(
694
+ :"facet.query" => '(category_ids_im:1 AND blog_id_i:2)'
695
+ )
696
+ end
697
+
698
+ it 'should request query facet with disjunction' do
699
+ session.search Post do
700
+ facet :foo do
701
+ row :bar do
702
+ any_of do
703
+ with(:category_ids, 1)
704
+ with(:blog_id, 2)
705
+ end
706
+ end
707
+ end
708
+ end
709
+ connection.should have_last_search_with(
710
+ :"facet.query" => '(category_ids_im:1 OR blog_id_i:2)'
711
+ )
712
+ end
713
+
714
+ it 'should request query facet with internal dynamic field' do
715
+ session.search Post do
716
+ facet :test do
717
+ row 'foo' do
718
+ dynamic :custom_string do
719
+ with :test, 'foo'
720
+ end
721
+ end
722
+ end
723
+ end
724
+ connection.should have_last_search_with(
725
+ :"facet.query" => 'custom_string\:test_s:foo'
726
+ )
727
+ end
728
+
729
+ it 'should request query facet with external dynamic field' do
730
+ session.search Post do
731
+ dynamic :custom_string do
732
+ facet :test do
733
+ row 'foo' do
734
+ with :test, 'foo'
735
+ end
736
+ end
737
+ end
738
+ end
739
+ connection.should have_last_search_with(
740
+ :"facet.query" => 'custom_string\:test_s:foo'
741
+ )
742
+ end
743
+
744
+ it 'should not allow 0 arguments to facet method with block' do
745
+ lambda do
746
+ session.search Post do
747
+ facet do
748
+ end
749
+ end
750
+ end.should raise_error(ArgumentError)
751
+ end
752
+
753
+ it 'should not allow more than 1 argument to facet method with block' do
754
+ lambda do
755
+ session.search Post do
756
+ facet :foo, :bar do
757
+ end
758
+ end
759
+ end.should raise_error(ArgumentError)
760
+ end
312
761
  end
313
762
 
314
763
  it 'should allow faceting by dynamic string field' do
315
- connection.should_receive(:query).with(anything, hash_including(:facets => { :fields => %w(custom_string:test_s) }))
316
764
  session.search Post do
317
765
  dynamic :custom_string do
318
766
  facet :test
319
767
  end
320
768
  end
769
+ connection.should have_last_search_with(:"facet.field" => %w(custom_string:test_s))
321
770
  end
322
771
 
323
772
  it 'should properly escape namespaced type names' do
324
- connection.should_receive(:query).with('(type:Namespaced\:\:Comment)', hash_including)
325
773
  session.search(Namespaced::Comment)
774
+ connection.should have_last_search_with(:q => 'type:Namespaced\:\:Comment')
326
775
  end
327
776
 
328
777
  it 'should build search for multiple types' do
329
- connection.should_receive(:query).with('(type:(Post OR Namespaced\:\:Comment))', hash_including)
330
778
  session.search(Post, Namespaced::Comment)
779
+ connection.should have_last_search_with(:q => 'type:(Post OR Namespaced\:\:Comment)')
331
780
  end
332
781
 
333
- it 'should allow search on fields common to all types' do
334
- connection.should_receive(:query).with('(type:(Post OR Namespaced\:\:Comment))', hash_including(:filter_queries => ['published_at_d:1983\-07\-08T09\:00\:00Z'])).twice
782
+ it 'should allow search on fields common to all types with DSL' do
335
783
  time = Time.parse('1983-07-08 05:00:00 -0400')
336
- session.search Post, Namespaced::Comment, :conditions => { :published_at => time }
337
784
  session.search Post, Namespaced::Comment do
338
785
  with :published_at, time
339
786
  end
787
+ connection.should have_last_search_with(:fq => ['published_at_d:1983\-07\-08T09\:00\:00Z'])
788
+ end
789
+
790
+ it 'should allow search on fields common to all types with conditions' do
791
+ time = Time.parse('1983-07-08 05:00:00 -0400')
792
+ session.search Post, Namespaced::Comment, :conditions => { :published_at => time }
793
+ connection.should have_last_search_with(:fq => ['published_at_d:1983\-07\-08T09\:00\:00Z'])
794
+ end
795
+
796
+ it 'should allow search on dynamic fields common to all types' do
797
+ session.search Post, Namespaced::Comment do
798
+ dynamic :custom_string do
799
+ with(:test, 'test')
800
+ end
801
+ end
802
+ connection.should have_last_search_with(:fq => ['custom_string\\:test_s:test'])
803
+ end
804
+
805
+ it 'should combine all text fields' do
806
+ session.search Post, Namespaced::Comment do
807
+ keywords 'keywords'
808
+ end
809
+ connection.searches.last[:qf].split(' ').sort.should ==
810
+ %w(author_name_text backwards_title_text body_text title_text)
811
+ end
812
+
813
+ it 'should allow specification of a text field that only exists in one type' do
814
+ session.search Post, Namespaced::Comment do
815
+ keywords 'keywords', :fields => :author_name
816
+ end
817
+ connection.searches.last[:qf].should == 'author_name_text'
340
818
  end
341
819
 
342
820
  it 'should raise Sunspot::UnrecognizedFieldError if search scoped to field not common to all types' do
@@ -355,17 +833,25 @@ describe 'Search' do
355
833
  end.should raise_error(Sunspot::UnrecognizedFieldError)
356
834
  end
357
835
 
836
+ it 'should raise Sunspot::UnrecognizedFieldError if a text field that does not exist for any type is specified' do
837
+ lambda do
838
+ session.search Post, Namespaced::Comment do
839
+ keywords 'fulltext', :fields => :bogus
840
+ end
841
+ end.should raise_error(Sunspot::UnrecognizedFieldError)
842
+ end
843
+
358
844
  it 'should ignore condition if field is not common to all types' do
359
- connection.should_receive(:query).with('(type:(Post OR Namespaced\:\:Comment))', hash_not_including(:filter_queries))
360
845
  session.search Post, Namespaced::Comment, :conditions => { :blog_id => 1 }
846
+ connection.should_not have_last_search_with(:fq)
361
847
  end
362
848
 
363
849
  it 'should allow building search using block argument rather than instance_eval' do
364
- connection.should_receive(:query).with('(type:Post)', hash_including(:filter_queries => ['blog_id_i:1']))
365
850
  @blog_id = 1
366
851
  session.search Post do |query|
367
852
  query.with(:blog_id, @blog_id)
368
853
  end
854
+ connection.should have_last_search_with(:fq => ['blog_id_i:1'])
369
855
  end
370
856
 
371
857
  it 'should raise Sunspot::UnrecognizedFieldError for nonexistant fields in block scope' do
@@ -376,6 +862,14 @@ describe 'Search' do
376
862
  end.should raise_error(Sunspot::UnrecognizedFieldError)
377
863
  end
378
864
 
865
+ it 'should raise Sunspot::UnrecognizedFieldError for nonexistant fields in keywords' do
866
+ lambda do
867
+ session.search Post do
868
+ keywords 'text', :fields => :bogus
869
+ end
870
+ end.should raise_error(Sunspot::UnrecognizedFieldError)
871
+ end
872
+
379
873
  it 'should raise NoMethodError if bogus operator referenced' do
380
874
  lambda do
381
875
  session.search Post do
@@ -415,7 +909,7 @@ describe 'Search' do
415
909
  end
416
910
 
417
911
  def connection
418
- @connection ||= mock('connection')
912
+ @connection ||= Mock::Connection.new
419
913
  end
420
914
 
421
915
  def session