UnderpantsGnome-sunspot 0.9.1.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 (103) hide show
  1. data/History.txt +39 -0
  2. data/LICENSE +18 -0
  3. data/README.rdoc +154 -0
  4. data/Rakefile +9 -0
  5. data/TODO +4 -0
  6. data/VERSION.yml +4 -0
  7. data/bin/sunspot-configure-solr +46 -0
  8. data/bin/sunspot-solr +62 -0
  9. data/lib/light_config.rb +40 -0
  10. data/lib/sunspot.rb +470 -0
  11. data/lib/sunspot/adapters.rb +265 -0
  12. data/lib/sunspot/composite_setup.rb +186 -0
  13. data/lib/sunspot/configuration.rb +38 -0
  14. data/lib/sunspot/data_extractor.rb +47 -0
  15. data/lib/sunspot/date_facet.rb +36 -0
  16. data/lib/sunspot/date_facet_row.rb +17 -0
  17. data/lib/sunspot/dsl.rb +3 -0
  18. data/lib/sunspot/dsl/field_query.rb +72 -0
  19. data/lib/sunspot/dsl/fields.rb +86 -0
  20. data/lib/sunspot/dsl/query.rb +59 -0
  21. data/lib/sunspot/dsl/query_facet.rb +31 -0
  22. data/lib/sunspot/dsl/restriction.rb +25 -0
  23. data/lib/sunspot/dsl/scope.rb +193 -0
  24. data/lib/sunspot/dsl/search.rb +30 -0
  25. data/lib/sunspot/facet.rb +51 -0
  26. data/lib/sunspot/facet_row.rb +34 -0
  27. data/lib/sunspot/field.rb +157 -0
  28. data/lib/sunspot/field_factory.rb +126 -0
  29. data/lib/sunspot/indexer.rb +127 -0
  30. data/lib/sunspot/instantiated_facet.rb +38 -0
  31. data/lib/sunspot/instantiated_facet_row.rb +12 -0
  32. data/lib/sunspot/query.rb +190 -0
  33. data/lib/sunspot/query/base_query.rb +90 -0
  34. data/lib/sunspot/query/connective.rb +77 -0
  35. data/lib/sunspot/query/dynamic_query.rb +69 -0
  36. data/lib/sunspot/query/field_facet.rb +149 -0
  37. data/lib/sunspot/query/field_query.rb +57 -0
  38. data/lib/sunspot/query/pagination.rb +39 -0
  39. data/lib/sunspot/query/query_facet.rb +72 -0
  40. data/lib/sunspot/query/query_facet_row.rb +19 -0
  41. data/lib/sunspot/query/restriction.rb +225 -0
  42. data/lib/sunspot/query/scope.rb +165 -0
  43. data/lib/sunspot/query/sort.rb +36 -0
  44. data/lib/sunspot/query/sort_composite.rb +33 -0
  45. data/lib/sunspot/query_facet.rb +33 -0
  46. data/lib/sunspot/query_facet_row.rb +21 -0
  47. data/lib/sunspot/schema.rb +165 -0
  48. data/lib/sunspot/search.rb +222 -0
  49. data/lib/sunspot/search/hit.rb +62 -0
  50. data/lib/sunspot/session.rb +201 -0
  51. data/lib/sunspot/setup.rb +271 -0
  52. data/lib/sunspot/type.rb +200 -0
  53. data/lib/sunspot/util.rb +164 -0
  54. data/solr/etc/jetty.xml +212 -0
  55. data/solr/etc/webdefault.xml +379 -0
  56. data/solr/lib/jetty-6.1.3.jar +0 -0
  57. data/solr/lib/jetty-util-6.1.3.jar +0 -0
  58. data/solr/lib/jsp-2.1/ant-1.6.5.jar +0 -0
  59. data/solr/lib/jsp-2.1/core-3.1.1.jar +0 -0
  60. data/solr/lib/jsp-2.1/jsp-2.1.jar +0 -0
  61. data/solr/lib/jsp-2.1/jsp-api-2.1.jar +0 -0
  62. data/solr/lib/servlet-api-2.5-6.1.3.jar +0 -0
  63. data/solr/solr/conf/elevate.xml +36 -0
  64. data/solr/solr/conf/protwords.txt +21 -0
  65. data/solr/solr/conf/schema.xml +50 -0
  66. data/solr/solr/conf/solrconfig.xml +696 -0
  67. data/solr/solr/conf/stopwords.txt +57 -0
  68. data/solr/solr/conf/synonyms.txt +31 -0
  69. data/solr/start.jar +0 -0
  70. data/solr/webapps/solr.war +0 -0
  71. data/spec/api/adapters_spec.rb +33 -0
  72. data/spec/api/build_search_spec.rb +918 -0
  73. data/spec/api/indexer_spec.rb +311 -0
  74. data/spec/api/query_spec.rb +153 -0
  75. data/spec/api/search_retrieval_spec.rb +325 -0
  76. data/spec/api/session_spec.rb +157 -0
  77. data/spec/api/spec_helper.rb +1 -0
  78. data/spec/api/sunspot_spec.rb +18 -0
  79. data/spec/integration/dynamic_fields_spec.rb +55 -0
  80. data/spec/integration/faceting_spec.rb +169 -0
  81. data/spec/integration/keyword_search_spec.rb +83 -0
  82. data/spec/integration/scoped_search_spec.rb +188 -0
  83. data/spec/integration/spec_helper.rb +1 -0
  84. data/spec/integration/stored_fields_spec.rb +10 -0
  85. data/spec/integration/test_pagination.rb +32 -0
  86. data/spec/mocks/adapters.rb +32 -0
  87. data/spec/mocks/blog.rb +3 -0
  88. data/spec/mocks/comment.rb +19 -0
  89. data/spec/mocks/connection.rb +84 -0
  90. data/spec/mocks/mock_adapter.rb +30 -0
  91. data/spec/mocks/mock_record.rb +41 -0
  92. data/spec/mocks/photo.rb +8 -0
  93. data/spec/mocks/post.rb +70 -0
  94. data/spec/mocks/user.rb +8 -0
  95. data/spec/spec_helper.rb +47 -0
  96. data/tasks/gemspec.rake +25 -0
  97. data/tasks/rcov.rake +28 -0
  98. data/tasks/rdoc.rake +21 -0
  99. data/tasks/schema.rake +19 -0
  100. data/tasks/spec.rake +24 -0
  101. data/tasks/todo.rake +4 -0
  102. data/templates/schema.xml.haml +24 -0
  103. metadata +245 -0
@@ -0,0 +1,57 @@
1
+ # Licensed to the Apache Software Foundation (ASF) under one or more
2
+ # contributor license agreements. See the NOTICE file distributed with
3
+ # this work for additional information regarding copyright ownership.
4
+ # The ASF licenses this file to You under the Apache License, Version 2.0
5
+ # (the "License"); you may not use this file except in compliance with
6
+ # the License. You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ #-----------------------------------------------------------------------
17
+ # a couple of test stopwords to test that the words are really being
18
+ # configured from this file:
19
+ stopworda
20
+ stopwordb
21
+
22
+ #Standard english stop words taken from Lucene's StopAnalyzer
23
+ an
24
+ and
25
+ are
26
+ as
27
+ at
28
+ be
29
+ but
30
+ by
31
+ for
32
+ if
33
+ in
34
+ into
35
+ is
36
+ it
37
+ no
38
+ not
39
+ of
40
+ on
41
+ or
42
+ s
43
+ such
44
+ t
45
+ that
46
+ the
47
+ their
48
+ then
49
+ there
50
+ these
51
+ they
52
+ this
53
+ to
54
+ was
55
+ will
56
+ with
57
+
@@ -0,0 +1,31 @@
1
+ # The ASF licenses this file to You under the Apache License, Version 2.0
2
+ # (the "License"); you may not use this file except in compliance with
3
+ # the License. You may obtain a copy of the License at
4
+ #
5
+ # http://www.apache.org/licenses/LICENSE-2.0
6
+ #
7
+ # Unless required by applicable law or agreed to in writing, software
8
+ # distributed under the License is distributed on an "AS IS" BASIS,
9
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+ # See the License for the specific language governing permissions and
11
+ # limitations under the License.
12
+
13
+ #-----------------------------------------------------------------------
14
+ #some test synonym mappings unlikely to appear in real input text
15
+ aaa => aaaa
16
+ bbb => bbbb1 bbbb2
17
+ ccc => cccc1,cccc2
18
+ a\=>a => b\=>b
19
+ a\,a => b\,b
20
+ fooaaa,baraaa,bazaaa
21
+
22
+ # Some synonym groups specific to this example
23
+ GB,gib,gigabyte,gigabytes
24
+ MB,mib,megabyte,megabytes
25
+ Television, Televisions, TV, TVs
26
+ #notice we use "gib" instead of "GiB" so any WordDelimiterFilter coming
27
+ #after us won't split it into two words.
28
+
29
+ # Synonym mappings can be used for spelling correction too
30
+ pixima => pixma
31
+
data/solr/start.jar ADDED
Binary file
Binary file
@@ -0,0 +1,33 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe Sunspot::Adapters::InstanceAdapter do
4
+ it "finds adapter by superclass" do
5
+ Sunspot::Adapters::InstanceAdapter::for(Model).should be(AbstractModelInstanceAdapter)
6
+ end
7
+
8
+ it "finds adapter by mixin" do
9
+ Sunspot::Adapters::InstanceAdapter::for(MixModel).should be(MixInModelInstanceAdapter)
10
+ end
11
+
12
+ it 'throws NoAdapterError if anonymous module passed in' do
13
+ lambda do
14
+ Sunspot::Adapters::InstanceAdapter::for(Module.new)
15
+ end.should raise_error(Sunspot::NoAdapterError)
16
+ end
17
+ end
18
+
19
+ describe Sunspot::Adapters::DataAccessor do
20
+ it "finds adapter by superclass" do
21
+ Sunspot::Adapters::DataAccessor::for(Model).should be(AbstractModelDataAccessor)
22
+ end
23
+
24
+ it "finds adapter by mixin" do
25
+ Sunspot::Adapters::DataAccessor::for(MixModel).should be(MixInModelDataAccessor)
26
+ end
27
+
28
+ it 'throws NoAdapterError if anonymous module passed in' do
29
+ lambda do
30
+ Sunspot::Adapters::DataAccessor::for(Module.new)
31
+ end.should raise_error(Sunspot::NoAdapterError)
32
+ end
33
+ end
@@ -0,0 +1,918 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe 'Search' do
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
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
17
+ session.search Post do
18
+ keywords 'keyword search'
19
+ end
20
+ connection.should have_last_search_with(:defType => 'dismax')
21
+ end
22
+
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
55
+ session.search Post do
56
+ with :title, 'My Pet Post'
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'])
64
+ end
65
+
66
+ it 'should ignore nonexistant fields in hash scope' do
67
+ session.search Post, :conditions => { :bogus => 'Field' }
68
+ connection.should_not have_last_search_with(:fq)
69
+ end
70
+
71
+ it 'should scope by exact match with time' do
72
+ time = Time.parse('1983-07-08 05:00:00 -0400')
73
+ session.search Post do
74
+ with :published_at, time
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
+ )
89
+ end
90
+
91
+ it 'should scope by exact match with boolean' do
92
+ session.search Post do
93
+ with :featured, false
94
+ end
95
+ connection.should have_last_search_with(:fq => ['featured_b:false'])
96
+ end
97
+
98
+ it 'should scope by less than match with float' do
99
+ session.search Post do
100
+ with(:average_rating).less_than 3.0
101
+ end
102
+ connection.should have_last_search_with(:fq => ['average_rating_f:[* TO 3\.0]'])
103
+ end
104
+
105
+ it 'should scope by greater than match with float' do
106
+ session.search Post do
107
+ with(:average_rating).greater_than 3.0
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]'])
117
+ end
118
+
119
+ it 'should scope by between match with float' do
120
+ session.search Post do
121
+ with(:average_rating).between 2.0..4.0
122
+ end
123
+ connection.should have_last_search_with(:fq => ['average_rating_f:[2\.0 TO 4\.0]'])
124
+ end
125
+
126
+ it 'should scope by any match with integer using DSL' do
127
+ session.search Post do
128
+ with(:category_ids).any_of [2, 7, 12]
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)'])
143
+ end
144
+
145
+ it 'should scope by all match with integer' do
146
+ session.search Post do
147
+ with(:category_ids).all_of [2, 7, 12]
148
+ end
149
+ connection.should have_last_search_with(:fq => ['category_ids_im:(2 AND 7 AND 12)'])
150
+ end
151
+
152
+ it 'should scope by not equal match with string' do
153
+ session.search Post do
154
+ without :title, 'Bad Post'
155
+ end
156
+ connection.should have_last_search_with(:fq => ['-title_ss:Bad\ Post'])
157
+ end
158
+
159
+ it 'should scope by not less than match with float' do
160
+ session.search Post do
161
+ without(:average_rating).less_than 3.0
162
+ end
163
+ connection.should have_last_search_with(:fq => ['-average_rating_f:[* TO 3\.0]'])
164
+ end
165
+
166
+ it 'should scope by not greater than match with float' do
167
+ session.search Post do
168
+ without(:average_rating).greater_than 3.0
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]'])
178
+ end
179
+
180
+ it 'should scope by not between match with float' do
181
+ session.search Post do
182
+ without(:average_rating).between 2.0..4.0
183
+ end
184
+ connection.should have_last_search_with(:fq => ['-average_rating_f:[2\.0 TO 4\.0]'])
185
+ end
186
+
187
+ it 'should scope by not any match with integer' do
188
+ session.search Post do
189
+ without(:category_ids).any_of [2, 7, 12]
190
+ end
191
+ connection.should have_last_search_with(:fq => ['-category_ids_im:(2 OR 7 OR 12)'])
192
+ end
193
+
194
+
195
+ it 'should scope by not all match with integer' do
196
+ session.search Post do
197
+ without(:category_ids).all_of [2, 7, 12]
198
+ end
199
+ connection.should have_last_search_with(:fq => ['-category_ids_im:(2 AND 7 AND 12)'])
200
+ end
201
+
202
+ it 'should scope by empty field' do
203
+ session.search Post do
204
+ with :average_rating, nil
205
+ end
206
+ connection.should have_last_search_with(:fq => ['-average_rating_f:[* TO *]'])
207
+ end
208
+
209
+ it 'should scope by non-empty field' do
210
+ session.search Post do
211
+ without :average_rating, nil
212
+ end
213
+ connection.should have_last_search_with(:fq => ['average_rating_f:[* TO *]'])
214
+ end
215
+
216
+ it 'should exclude by object identity' do
217
+ post = Post.new
218
+ session.search Post do
219
+ without post
220
+ end
221
+ connection.should have_last_search_with(:fq => ["-id:Post\\ #{post.id}"])
222
+ end
223
+
224
+ it 'should exclude multiple objects passed as varargs by object identity' do
225
+ post1, post2 = Post.new, Post.new
226
+ session.search Post do
227
+ without post1, post2
228
+ end
229
+ connection.should have_last_search_with(
230
+ :fq => ["-id:Post\\ #{post1.id}", "-id:Post\\ #{post2.id}"]
231
+ )
232
+ end
233
+
234
+ it 'should exclude multiple objects passed as array by object identity' do
235
+ posts = [Post.new, Post.new]
236
+ session.search Post do
237
+ without posts
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
+ )
306
+ end
307
+
308
+ it 'should restrict by dynamic string field with equality restriction' do
309
+ session.search Post do
310
+ dynamic :custom_string do
311
+ with :test, 'string'
312
+ end
313
+ end
314
+ connection.should have_last_search_with(:fq => ['custom_string\:test_s:string'])
315
+ end
316
+
317
+ it 'should restrict by dynamic integer field with less than restriction' do
318
+ session.search Post do
319
+ dynamic :custom_integer do
320
+ with(:test).less_than(1)
321
+ end
322
+ end
323
+ connection.should have_last_search_with(:fq => ['custom_integer\:test_i:[* TO 1]'])
324
+ end
325
+
326
+ it 'should restrict by dynamic float field with between restriction' do
327
+ session.search Post do
328
+ dynamic :custom_float do
329
+ with(:test).between(2.2..3.3)
330
+ end
331
+ end
332
+ connection.should have_last_search_with(:fq => ['custom_float\:test_fm:[2\.2 TO 3\.3]'])
333
+ end
334
+
335
+ it 'should restrict by dynamic time field with any of restriction' do
336
+ session.search Post do
337
+ dynamic :custom_time do
338
+ with(:test).any_of([Time.parse('2009-02-10 14:00:00 UTC'),
339
+ Time.parse('2009-02-13 18:00:00 UTC')])
340
+ end
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)'])
343
+ end
344
+
345
+ it 'should restrict by dynamic boolean field with equality restriction' do
346
+ session.search Post do
347
+ dynamic :custom_boolean do
348
+ with :test, false
349
+ end
350
+ end
351
+ connection.should have_last_search_with(:fq => ['custom_boolean\:test_b:false'])
352
+ end
353
+
354
+ it 'should negate a dynamic field restriction' do
355
+ session.search Post do
356
+ dynamic :custom_string do
357
+ without :test, 'foo'
358
+ end
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
+ )
375
+ end
376
+
377
+ it 'should throw an UnrecognizedFieldError if an unknown dynamic field is searched by' do
378
+ lambda do
379
+ session.search Post do
380
+ dynamic(:bogus) { with :some, 'value' }
381
+ end
382
+ end.should raise_error(Sunspot::UnrecognizedFieldError)
383
+ end
384
+
385
+ it 'should throw a NoMethodError if pagination is attempted in a dynamic query' do
386
+ lambda do
387
+ session.search Post do
388
+ dynamic :custom_string do
389
+ paginate 3, 10
390
+ end
391
+ end
392
+ end.should raise_error(NoMethodError)
393
+ end
394
+
395
+ it 'should paginate using default per_page when page not provided' do
396
+ session.search Post
397
+ connection.should have_last_search_with(:rows => 30)
398
+ end
399
+
400
+ it 'should paginate using default per_page when page provided in DSL' do
401
+ session.search Post do
402
+ paginate :page => 2
403
+ end
404
+ connection.should have_last_search_with(:rows => 30, :start => 30)
405
+ end
406
+
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
413
+ session.search Post do
414
+ paginate :page => 4, :per_page => 15
415
+ end
416
+ connection.should have_last_search_with(:rows => 15, :start => 45)
417
+ end
418
+
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
425
+ session.search Post do
426
+ order_by :average_rating, :desc
427
+ end
428
+ connection.should have_last_search_with(:sort => 'average_rating_f desc')
429
+ end
430
+
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
437
+ session.search Post do
438
+ order_by :average_rating, :desc
439
+ order_by :sort_title, :asc
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')
447
+ end
448
+
449
+ it 'should order by a dynamic field' do
450
+ session.search Post do
451
+ dynamic :custom_integer do
452
+ order_by :test, :desc
453
+ end
454
+ end
455
+ connection.should have_last_search_with(:sort => 'custom_integer:test_i desc')
456
+ end
457
+
458
+ it 'should order by a dynamic field and static field, with given precedence' do
459
+ session.search Post do
460
+ dynamic :custom_integer do
461
+ order_by :test, :desc
462
+ end
463
+ order_by :sort_title, :asc
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$/
473
+ end
474
+
475
+ it 'should throw an ArgumentError if a bogus order direction is given' do
476
+ lambda do
477
+ session.search Post do
478
+ order_by :sort_title, :sideways
479
+ end
480
+ end.should raise_error(ArgumentError)
481
+ end
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
+
503
+ it 'should request single field facet' do
504
+ session.search Post do
505
+ facet :category_ids
506
+ end
507
+ connection.should have_last_search_with(:"facet.field" => %w(category_ids_im))
508
+ end
509
+
510
+ it 'should request multiple field facets' do
511
+ session.search Post do
512
+ facet :category_ids, :blog_id
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
761
+ end
762
+
763
+ it 'should allow faceting by dynamic string field' do
764
+ session.search Post do
765
+ dynamic :custom_string do
766
+ facet :test
767
+ end
768
+ end
769
+ connection.should have_last_search_with(:"facet.field" => %w(custom_string:test_s))
770
+ end
771
+
772
+ it 'should properly escape namespaced type names' do
773
+ session.search(Namespaced::Comment)
774
+ connection.should have_last_search_with(:q => 'type:Namespaced\:\:Comment')
775
+ end
776
+
777
+ it 'should build search for multiple types' do
778
+ session.search(Post, Namespaced::Comment)
779
+ connection.should have_last_search_with(:q => 'type:(Post OR Namespaced\:\:Comment)')
780
+ end
781
+
782
+ it 'should allow search on fields common to all types with DSL' do
783
+ time = Time.parse('1983-07-08 05:00:00 -0400')
784
+ session.search Post, Namespaced::Comment do
785
+ with :published_at, time
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'
818
+ end
819
+
820
+ it 'should raise Sunspot::UnrecognizedFieldError if search scoped to field not common to all types' do
821
+ lambda do
822
+ session.search Post, Namespaced::Comment do
823
+ with :blog_id, 1
824
+ end
825
+ end.should raise_error(Sunspot::UnrecognizedFieldError)
826
+ end
827
+
828
+ it 'should raise Sunspot::UnrecognizedFieldError if search scoped to field configured differently between types' do
829
+ lambda do
830
+ session.search Post, Namespaced::Comment do
831
+ with :average_rating, 2.2 # this is a float in Post but an integer in Comment
832
+ end
833
+ end.should raise_error(Sunspot::UnrecognizedFieldError)
834
+ end
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
+
844
+ it 'should ignore condition if field is not common to all types' do
845
+ session.search Post, Namespaced::Comment, :conditions => { :blog_id => 1 }
846
+ connection.should_not have_last_search_with(:fq)
847
+ end
848
+
849
+ it 'should allow building search using block argument rather than instance_eval' do
850
+ @blog_id = 1
851
+ session.search Post do |query|
852
+ query.with(:blog_id, @blog_id)
853
+ end
854
+ connection.should have_last_search_with(:fq => ['blog_id_i:1'])
855
+ end
856
+
857
+ it 'should raise Sunspot::UnrecognizedFieldError for nonexistant fields in block scope' do
858
+ lambda do
859
+ session.search Post do
860
+ with :bogus, 'Field'
861
+ end
862
+ end.should raise_error(Sunspot::UnrecognizedFieldError)
863
+ end
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
+
873
+ it 'should raise NoMethodError if bogus operator referenced' do
874
+ lambda do
875
+ session.search Post do
876
+ with(:category_ids).resembling :bogus_condition
877
+ end
878
+ end.should raise_error(NoMethodError)
879
+ end
880
+
881
+ it 'should raise ArgumentError if no :page argument given to paginate' do
882
+ lambda do
883
+ session.search Post do
884
+ paginate
885
+ end
886
+ end.should raise_error(ArgumentError)
887
+ end
888
+
889
+ it 'should raise ArgumentError if bogus argument given to paginate' do
890
+ lambda do
891
+ session.search Post do
892
+ paginate :page => 4, :ugly => :puppy
893
+ end
894
+ end.should raise_error(ArgumentError)
895
+ end
896
+
897
+ it 'should raise ArgumentError if more than two arguments passed to scope method' do
898
+ lambda do
899
+ session.search Post do
900
+ with(:category_ids, 4, 5)
901
+ end
902
+ end.should raise_error(ArgumentError)
903
+ end
904
+
905
+ private
906
+
907
+ def config
908
+ @config ||= Sunspot::Configuration.build
909
+ end
910
+
911
+ def connection
912
+ @connection ||= Mock::Connection.new
913
+ end
914
+
915
+ def session
916
+ @session ||= Sunspot::Session.new(config, connection)
917
+ end
918
+ end