gojee-sunspot 2.0.3 → 2.0.4

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 (178) hide show
  1. data/.gitignore +12 -0
  2. data/Gemfile +5 -0
  3. data/History.txt +252 -0
  4. data/LICENSE +18 -0
  5. data/Rakefile +13 -0
  6. data/TODO +13 -0
  7. data/lib/light_config.rb +40 -0
  8. data/lib/sunspot/adapters.rb +265 -0
  9. data/lib/sunspot/batcher.rb +62 -0
  10. data/lib/sunspot/class_set.rb +23 -0
  11. data/lib/sunspot/composite_setup.rb +202 -0
  12. data/lib/sunspot/configuration.rb +53 -0
  13. data/lib/sunspot/data_extractor.rb +50 -0
  14. data/lib/sunspot/dsl/adjustable.rb +47 -0
  15. data/lib/sunspot/dsl/field_group.rb +57 -0
  16. data/lib/sunspot/dsl/field_query.rb +327 -0
  17. data/lib/sunspot/dsl/fields.rb +103 -0
  18. data/lib/sunspot/dsl/fulltext.rb +243 -0
  19. data/lib/sunspot/dsl/function.rb +27 -0
  20. data/lib/sunspot/dsl/functional.rb +44 -0
  21. data/lib/sunspot/dsl/more_like_this_query.rb +56 -0
  22. data/lib/sunspot/dsl/paginatable.rb +32 -0
  23. data/lib/sunspot/dsl/query_facet.rb +36 -0
  24. data/lib/sunspot/dsl/restriction.rb +25 -0
  25. data/lib/sunspot/dsl/restriction_with_near.rb +160 -0
  26. data/lib/sunspot/dsl/scope.rb +217 -0
  27. data/lib/sunspot/dsl/search.rb +30 -0
  28. data/lib/sunspot/dsl/standard_query.rb +123 -0
  29. data/lib/sunspot/dsl.rb +5 -0
  30. data/lib/sunspot/field.rb +193 -0
  31. data/lib/sunspot/field_factory.rb +129 -0
  32. data/lib/sunspot/indexer.rb +136 -0
  33. data/lib/sunspot/query/abstract_field_facet.rb +52 -0
  34. data/lib/sunspot/query/bbox.rb +15 -0
  35. data/lib/sunspot/query/boost_query.rb +24 -0
  36. data/lib/sunspot/query/common_query.rb +96 -0
  37. data/lib/sunspot/query/composite_fulltext.rb +36 -0
  38. data/lib/sunspot/query/connective.rb +206 -0
  39. data/lib/sunspot/query/date_field_facet.rb +14 -0
  40. data/lib/sunspot/query/dismax.rb +132 -0
  41. data/lib/sunspot/query/field_facet.rb +41 -0
  42. data/lib/sunspot/query/field_group.rb +36 -0
  43. data/lib/sunspot/query/filter.rb +38 -0
  44. data/lib/sunspot/query/function_query.rb +52 -0
  45. data/lib/sunspot/query/geo.rb +53 -0
  46. data/lib/sunspot/query/geofilt.rb +16 -0
  47. data/lib/sunspot/query/highlighting.rb +62 -0
  48. data/lib/sunspot/query/more_like_this.rb +61 -0
  49. data/lib/sunspot/query/more_like_this_query.rb +12 -0
  50. data/lib/sunspot/query/pagination.rb +42 -0
  51. data/lib/sunspot/query/query_facet.rb +16 -0
  52. data/lib/sunspot/query/restriction.rb +262 -0
  53. data/lib/sunspot/query/scope.rb +9 -0
  54. data/lib/sunspot/query/sort.rb +109 -0
  55. data/lib/sunspot/query/sort_composite.rb +34 -0
  56. data/lib/sunspot/query/standard_query.rb +16 -0
  57. data/lib/sunspot/query/text_field_boost.rb +17 -0
  58. data/lib/sunspot/query.rb +11 -0
  59. data/lib/sunspot/schema.rb +151 -0
  60. data/lib/sunspot/search/abstract_search.rb +281 -0
  61. data/lib/sunspot/search/date_facet.rb +35 -0
  62. data/lib/sunspot/search/facet_row.rb +27 -0
  63. data/lib/sunspot/search/field_facet.rb +88 -0
  64. data/lib/sunspot/search/field_group.rb +32 -0
  65. data/lib/sunspot/search/group.rb +50 -0
  66. data/lib/sunspot/search/highlight.rb +38 -0
  67. data/lib/sunspot/search/hit.rb +150 -0
  68. data/lib/sunspot/search/hit_enumerable.rb +72 -0
  69. data/lib/sunspot/search/more_like_this_search.rb +31 -0
  70. data/lib/sunspot/search/paginated_collection.rb +57 -0
  71. data/lib/sunspot/search/query_facet.rb +67 -0
  72. data/lib/sunspot/search/standard_search.rb +21 -0
  73. data/lib/sunspot/search.rb +9 -0
  74. data/lib/sunspot/session.rb +262 -0
  75. data/lib/sunspot/session_proxy/abstract_session_proxy.rb +29 -0
  76. data/lib/sunspot/session_proxy/class_sharding_session_proxy.rb +66 -0
  77. data/lib/sunspot/session_proxy/id_sharding_session_proxy.rb +89 -0
  78. data/lib/sunspot/session_proxy/master_slave_session_proxy.rb +43 -0
  79. data/lib/sunspot/session_proxy/multicore_session_proxy.rb +67 -0
  80. data/lib/sunspot/session_proxy/sharding_session_proxy.rb +222 -0
  81. data/lib/sunspot/session_proxy/silent_fail_session_proxy.rb +42 -0
  82. data/lib/sunspot/session_proxy/thread_local_session_proxy.rb +37 -0
  83. data/lib/sunspot/session_proxy.rb +95 -0
  84. data/lib/sunspot/setup.rb +350 -0
  85. data/lib/sunspot/text_field_setup.rb +29 -0
  86. data/lib/sunspot/type.rb +393 -0
  87. data/lib/sunspot/util.rb +252 -0
  88. data/lib/sunspot/version.rb +3 -0
  89. data/lib/sunspot.rb +579 -0
  90. data/log/.gitignore +1 -0
  91. data/pkg/.gitignore +1 -0
  92. data/script/console +10 -0
  93. data/spec/api/adapters_spec.rb +33 -0
  94. data/spec/api/batcher_spec.rb +112 -0
  95. data/spec/api/binding_spec.rb +50 -0
  96. data/spec/api/class_set_spec.rb +24 -0
  97. data/spec/api/hit_enumerable_spec.rb +47 -0
  98. data/spec/api/indexer/attributes_spec.rb +149 -0
  99. data/spec/api/indexer/batch_spec.rb +72 -0
  100. data/spec/api/indexer/dynamic_fields_spec.rb +42 -0
  101. data/spec/api/indexer/fixed_fields_spec.rb +57 -0
  102. data/spec/api/indexer/fulltext_spec.rb +43 -0
  103. data/spec/api/indexer/removal_spec.rb +53 -0
  104. data/spec/api/indexer/spec_helper.rb +1 -0
  105. data/spec/api/indexer_spec.rb +14 -0
  106. data/spec/api/query/advanced_manipulation_examples.rb +35 -0
  107. data/spec/api/query/connectives_examples.rb +189 -0
  108. data/spec/api/query/dsl_spec.rb +18 -0
  109. data/spec/api/query/dynamic_fields_examples.rb +165 -0
  110. data/spec/api/query/faceting_examples.rb +397 -0
  111. data/spec/api/query/fulltext_examples.rb +313 -0
  112. data/spec/api/query/function_spec.rb +79 -0
  113. data/spec/api/query/geo_examples.rb +68 -0
  114. data/spec/api/query/group_spec.rb +32 -0
  115. data/spec/api/query/highlighting_examples.rb +245 -0
  116. data/spec/api/query/more_like_this_spec.rb +140 -0
  117. data/spec/api/query/ordering_pagination_examples.rb +116 -0
  118. data/spec/api/query/scope_examples.rb +275 -0
  119. data/spec/api/query/spatial_examples.rb +27 -0
  120. data/spec/api/query/spec_helper.rb +1 -0
  121. data/spec/api/query/standard_spec.rb +29 -0
  122. data/spec/api/query/text_field_scoping_examples.rb +30 -0
  123. data/spec/api/query/types_spec.rb +20 -0
  124. data/spec/api/search/dynamic_fields_spec.rb +33 -0
  125. data/spec/api/search/faceting_spec.rb +360 -0
  126. data/spec/api/search/highlighting_spec.rb +69 -0
  127. data/spec/api/search/hits_spec.rb +131 -0
  128. data/spec/api/search/paginated_collection_spec.rb +36 -0
  129. data/spec/api/search/results_spec.rb +72 -0
  130. data/spec/api/search/search_spec.rb +23 -0
  131. data/spec/api/search/spec_helper.rb +1 -0
  132. data/spec/api/session_proxy/class_sharding_session_proxy_spec.rb +85 -0
  133. data/spec/api/session_proxy/id_sharding_session_proxy_spec.rb +30 -0
  134. data/spec/api/session_proxy/master_slave_session_proxy_spec.rb +41 -0
  135. data/spec/api/session_proxy/sharding_session_proxy_spec.rb +77 -0
  136. data/spec/api/session_proxy/silent_fail_session_proxy_spec.rb +24 -0
  137. data/spec/api/session_proxy/spec_helper.rb +9 -0
  138. data/spec/api/session_proxy/thread_local_session_proxy_spec.rb +39 -0
  139. data/spec/api/session_spec.rb +232 -0
  140. data/spec/api/spec_helper.rb +3 -0
  141. data/spec/api/sunspot_spec.rb +29 -0
  142. data/spec/ext.rb +11 -0
  143. data/spec/helpers/indexer_helper.rb +17 -0
  144. data/spec/helpers/integration_helper.rb +8 -0
  145. data/spec/helpers/mock_session_helper.rb +13 -0
  146. data/spec/helpers/query_helper.rb +26 -0
  147. data/spec/helpers/search_helper.rb +68 -0
  148. data/spec/integration/dynamic_fields_spec.rb +57 -0
  149. data/spec/integration/faceting_spec.rb +251 -0
  150. data/spec/integration/field_grouping_spec.rb +66 -0
  151. data/spec/integration/geospatial_spec.rb +85 -0
  152. data/spec/integration/highlighting_spec.rb +44 -0
  153. data/spec/integration/indexing_spec.rb +55 -0
  154. data/spec/integration/keyword_search_spec.rb +317 -0
  155. data/spec/integration/local_search_spec.rb +64 -0
  156. data/spec/integration/more_like_this_spec.rb +43 -0
  157. data/spec/integration/scoped_search_spec.rb +354 -0
  158. data/spec/integration/stored_fields_spec.rb +12 -0
  159. data/spec/integration/test_pagination.rb +43 -0
  160. data/spec/integration/unicode_spec.rb +15 -0
  161. data/spec/mocks/adapters.rb +32 -0
  162. data/spec/mocks/blog.rb +3 -0
  163. data/spec/mocks/comment.rb +21 -0
  164. data/spec/mocks/connection.rb +126 -0
  165. data/spec/mocks/mock_adapter.rb +30 -0
  166. data/spec/mocks/mock_class_sharding_session_proxy.rb +24 -0
  167. data/spec/mocks/mock_record.rb +52 -0
  168. data/spec/mocks/mock_sharding_session_proxy.rb +15 -0
  169. data/spec/mocks/photo.rb +11 -0
  170. data/spec/mocks/post.rb +86 -0
  171. data/spec/mocks/super_class.rb +2 -0
  172. data/spec/mocks/user.rb +13 -0
  173. data/spec/spec_helper.rb +40 -0
  174. data/sunspot.gemspec +42 -0
  175. data/tasks/rdoc.rake +27 -0
  176. data/tasks/schema.rake +19 -0
  177. data/tasks/todo.rake +4 -0
  178. metadata +261 -3
@@ -0,0 +1,397 @@
1
+ shared_examples_for "facetable query" do
2
+ describe 'on fields' do
3
+ it 'does not turn faceting on if no facet requested' do
4
+ search
5
+ connection.should_not have_last_search_with('facet')
6
+ end
7
+
8
+ it 'turns faceting on if facet is requested' do
9
+ search do
10
+ facet :category_ids
11
+ end
12
+ connection.should have_last_search_with(:facet => 'true')
13
+ end
14
+
15
+ it 'requests single field facet' do
16
+ search do
17
+ facet :category_ids
18
+ end
19
+ connection.should have_last_search_with(:"facet.field" => %w(category_ids_im))
20
+ end
21
+
22
+ it 'requests multiple field facets' do
23
+ search do
24
+ facet :category_ids, :blog_id
25
+ end
26
+ connection.should have_last_search_with(:"facet.field" => %w(category_ids_im blog_id_i))
27
+ end
28
+
29
+ it 'sets facet sort by count' do
30
+ search do
31
+ facet :category_ids, :sort => :count
32
+ end
33
+ connection.should have_last_search_with(:"f.category_ids_im.facet.sort" => 'true')
34
+ end
35
+
36
+ it 'sets facet sort by index' do
37
+ search do
38
+ facet :category_ids, :sort => :index
39
+ end
40
+ connection.should have_last_search_with(:"f.category_ids_im.facet.sort" => 'false')
41
+ end
42
+
43
+ it 'raises ArgumentError if bogus facet sort provided' do
44
+ lambda do
45
+ search do
46
+ facet :category_ids, :sort => :sideways
47
+ end
48
+ end.should raise_error(ArgumentError)
49
+ end
50
+
51
+ it 'sets the facet limit' do
52
+ search do
53
+ facet :category_ids, :limit => 10
54
+ end
55
+ connection.should have_last_search_with(:"f.category_ids_im.facet.limit" => 10)
56
+ end
57
+
58
+ it 'sets the facet minimum count' do
59
+ search do
60
+ facet :category_ids, :minimum_count => 5
61
+ end
62
+ connection.should have_last_search_with(:"f.category_ids_im.facet.mincount" => 5)
63
+ end
64
+
65
+ it 'sets the facet minimum count to zero if zeros are allowed' do
66
+ search do
67
+ facet :category_ids, :zeros => true
68
+ end
69
+ connection.should have_last_search_with(:"f.category_ids_im.facet.mincount" => 0)
70
+ end
71
+
72
+ it 'sets the facet minimum count to one by default' do
73
+ search do
74
+ facet :category_ids
75
+ end
76
+ connection.should have_last_search_with(:"f.category_ids_im.facet.mincount" => 1)
77
+ end
78
+
79
+ it 'sets the facet prefix' do
80
+ search do
81
+ facet :title, :prefix => 'Test'
82
+ end
83
+ connection.should have_last_search_with(:"f.title_ss.facet.prefix" => 'Test')
84
+ end
85
+
86
+ it 'escapes the facet prefix' do
87
+ search do
88
+ facet :title, :prefix => 'Test Title'
89
+ end
90
+ connection.should have_last_search_with(:"f.title_ss.facet.prefix" => 'Test\ Title')
91
+ end
92
+
93
+ it 'sends a query facet for :any extra' do
94
+ search do
95
+ facet :category_ids, :extra => :any
96
+ end
97
+ connection.should have_last_search_with(:"facet.query" => "category_ids_im:[* TO *]")
98
+ end
99
+
100
+ it 'sends a query facet for :none extra' do
101
+ search do
102
+ facet :category_ids, :extra => :none
103
+ end
104
+ connection.should have_last_search_with(:"facet.query" => "-category_ids_im:[* TO *]")
105
+ end
106
+
107
+ it 'raises an ArgumentError if bogus extra is passed' do
108
+ lambda do
109
+ search do
110
+ facet :category_ids, :extra => :bogus
111
+ end
112
+ end.should raise_error(ArgumentError)
113
+ end
114
+
115
+ it 'tags and excludes a scope filter in a field facet' do
116
+ search do
117
+ blog_filter = with(:blog_id, 1)
118
+ facet(:blog_id, :exclude => blog_filter)
119
+ end
120
+ filter_tag = get_filter_tag('blog_id_i:1')
121
+ connection.should have_last_search_with(
122
+ :"facet.field" => %W({!ex=#{filter_tag}}blog_id_i)
123
+ )
124
+ end
125
+
126
+ it 'tags and excludes a disjunction filter in a field facet' do
127
+ search do
128
+ blog_filter = any_of do
129
+ with(:blog_id, 1)
130
+ with(:blog_id, 2)
131
+ end
132
+ facet(:blog_id, :exclude => blog_filter)
133
+ end
134
+ filter_tag = get_filter_tag('(blog_id_i:1 OR blog_id_i:2)')
135
+ connection.should have_last_search_with(
136
+ :"facet.field" => %W({!ex=#{filter_tag}}blog_id_i)
137
+ )
138
+ end
139
+
140
+ it 'tags and excludes multiple filters in a field facet' do
141
+ search do
142
+ blog_filter = with(:blog_id, 1)
143
+ category_filter = with(:category_ids, 2)
144
+ facet(:blog_id, :exclude => [blog_filter, category_filter])
145
+ end
146
+ filter_tags = %w(blog_id_i:1 category_ids_im:2).map do |phrase|
147
+ get_filter_tag(phrase)
148
+ end.join(',')
149
+ connection.should have_last_search_with(
150
+ :"facet.field" => %W({!ex=#{filter_tags}}blog_id_i)
151
+ )
152
+ end
153
+
154
+ it 'does not tag a filter if it is not excluded' do
155
+ search do
156
+ with(:blog_id, 1)
157
+ end
158
+ connection.should have_last_search_including(:fq, "blog_id_i:1")
159
+ end
160
+
161
+ it 'names a field facet' do
162
+ search do
163
+ facet(:blog_id, :name => :blog)
164
+ end
165
+ connection.should have_last_search_including(:"facet.field", "{!key=blog}blog_id_i")
166
+ end
167
+
168
+ it 'uses the custom field facet name in facet option parameters' do
169
+ search do
170
+ facet(:blog_id, :name => :blog, :sort => :count)
171
+ end
172
+ connection.should have_last_search_with(:"f.blog.facet.sort" => 'true')
173
+ end
174
+
175
+ it 'raises an ArgumentError if exclusion attempted on a query facet' do
176
+ lambda do
177
+ search do
178
+ blog_filter = with(:blog_id, 1)
179
+ facet(:bad, :exclude => blog_filter) do
180
+ row(:bogus) { with(:blog_id, 1) }
181
+ end
182
+ end
183
+ end.should raise_error(ArgumentError)
184
+ end
185
+
186
+ it 'raises an ArgumentError if exclusion attempted on a restricted field facet' do
187
+ lambda do
188
+ search do
189
+ blog_filter = with(:blog_id, 1)
190
+ facet(:blog_id, :only => 1, :exclude => blog_filter)
191
+ end
192
+ end.should raise_error(ArgumentError)
193
+ end
194
+
195
+ it 'raises an ArgumentError if exclusion attempted on a facet with :extra' do
196
+ lambda do
197
+ search do
198
+ blog_filter = with(:blog_id, 1)
199
+ facet(:blog_id, :extra => :all, :exclude => blog_filter)
200
+ end
201
+ end.should raise_error(ArgumentError)
202
+ end
203
+ end
204
+
205
+ describe 'on time ranges' do
206
+ before :each do
207
+ @time_range = (Time.parse('2009-06-01 00:00:00 -0400')..
208
+ Time.parse('2009-07-01 00:00:00 -0400'))
209
+ end
210
+
211
+ it 'does not send date facet parameters if time range is not specified' do
212
+ search do |query|
213
+ query.facet :published_at
214
+ end
215
+ connection.should_not have_last_search_with(:"facet.date")
216
+ end
217
+
218
+ it 'sets the facet to a date facet if time range is specified' do
219
+ search do |query|
220
+ query.facet :published_at, :time_range => @time_range
221
+ end
222
+ connection.should have_last_search_with(:"facet.date" => ['published_at_dt'])
223
+ end
224
+
225
+ it 'sets the facet start and end' do
226
+ search do |query|
227
+ query.facet :published_at, :time_range => @time_range
228
+ end
229
+ connection.should have_last_search_with(
230
+ :"f.published_at_dt.facet.date.start" => '2009-06-01T04:00:00Z',
231
+ :"f.published_at_dt.facet.date.end" => '2009-07-01T04:00:00Z'
232
+ )
233
+ end
234
+
235
+ it 'defaults the time interval to 1 day' do
236
+ search do |query|
237
+ query.facet :published_at, :time_range => @time_range
238
+ end
239
+ connection.should have_last_search_with(:"f.published_at_dt.facet.date.gap" => "+86400SECONDS")
240
+ end
241
+
242
+ it 'uses custom time interval' do
243
+ search do |query|
244
+ query.facet :published_at, :time_range => @time_range, :time_interval => 3600
245
+ end
246
+ connection.should have_last_search_with(:"f.published_at_dt.facet.date.gap" => "+3600SECONDS")
247
+ end
248
+
249
+ it 'does not allow date faceting on a non-date field' do
250
+ lambda do
251
+ search do |query|
252
+ query.facet :blog_id, :time_range => @time_range
253
+ end
254
+ end.should raise_error(ArgumentError)
255
+ end
256
+ end
257
+
258
+ describe 'using queries' do
259
+ it 'turns faceting on' do
260
+ search do
261
+ facet :foo do
262
+ row :bar do
263
+ with(:average_rating).between(4.0..5.0)
264
+ end
265
+ end
266
+ end
267
+ connection.should have_last_search_with(:facet => 'true')
268
+ end
269
+
270
+ it 'facets by query' do
271
+ search do
272
+ facet :foo do
273
+ row :bar do
274
+ with(:average_rating).between(4.0..5.0)
275
+ end
276
+ end
277
+ end
278
+ connection.should have_last_search_with(:"facet.query" => 'average_rating_ft:[4\.0 TO 5\.0]')
279
+ end
280
+
281
+ it 'requests multiple query facets' do
282
+ search do
283
+ facet :foo do
284
+ row :bar do
285
+ with(:average_rating).between(3.0..4.0)
286
+ end
287
+ row :baz do
288
+ with(:average_rating).between(4.0..5.0)
289
+ end
290
+ end
291
+ end
292
+ connection.should have_last_search_with(
293
+ :"facet.query" => [
294
+ 'average_rating_ft:[3\.0 TO 4\.0]',
295
+ 'average_rating_ft:[4\.0 TO 5\.0]'
296
+ ]
297
+ )
298
+ end
299
+
300
+ it 'requests query facet with multiple conditions' do
301
+ search do
302
+ facet :foo do
303
+ row :bar do
304
+ with(:category_ids, 1)
305
+ with(:blog_id, 2)
306
+ end
307
+ end
308
+ end
309
+ connection.should have_last_search_with(
310
+ :"facet.query" => '(category_ids_im:1 AND blog_id_i:2)'
311
+ )
312
+ end
313
+
314
+ it 'requests query facet with disjunction' do
315
+ search do
316
+ facet :foo do
317
+ row :bar do
318
+ any_of do
319
+ with(:category_ids, 1)
320
+ with(:blog_id, 2)
321
+ end
322
+ end
323
+ end
324
+ end
325
+ connection.should have_last_search_with(
326
+ :"facet.query" => '(category_ids_im:1 OR blog_id_i:2)'
327
+ )
328
+ end
329
+
330
+ it 'builds query facets when passed :only argument to field facet declaration' do
331
+ search do
332
+ facet :category_ids, :only => [1, 3]
333
+ end
334
+ connection.should have_last_search_with(
335
+ :"facet.query" => ['category_ids_im:1', 'category_ids_im:3']
336
+ )
337
+ end
338
+
339
+ it 'converts limited query facet values to the correct type' do
340
+ search do
341
+ facet :published_at, :only => [Time.utc(2009, 8, 28, 15, 33), Time.utc(2008,8, 28, 15, 33)]
342
+ end
343
+ connection.should have_last_search_with(
344
+ :"facet.query" => [
345
+ 'published_at_dt:2009\-08\-28T15\:33\:00Z',
346
+ 'published_at_dt:2008\-08\-28T15\:33\:00Z'
347
+ ]
348
+ )
349
+ end
350
+
351
+ it 'ignores facet query with no rows' do
352
+ search do
353
+ facet(:foo) {}
354
+ end
355
+ connection.should_not have_last_search_with(:"facet.query")
356
+ end
357
+
358
+ it 'ignores facet query row with no restrictions' do
359
+ search do
360
+ facet :foo do
361
+ row(:bar) do
362
+ with(:blog_id, 1)
363
+ end
364
+ row(:baz) {}
365
+ end
366
+ end
367
+ connection.searches.last[:"facet.query"].should be_a(String)
368
+ end
369
+
370
+ it 'ignores facet query with only empty rows' do
371
+ search do
372
+ facet :foo do
373
+ row(:bar) {}
374
+ end
375
+ end
376
+ connection.should_not have_last_search_with(:"facet.query")
377
+ end
378
+
379
+ it 'does not allow 0 arguments to facet method with block' do
380
+ lambda do
381
+ search do
382
+ facet do
383
+ end
384
+ end
385
+ end.should raise_error(ArgumentError)
386
+ end
387
+
388
+ it 'does not allow more than 1 argument to facet method with block' do
389
+ lambda do
390
+ search do
391
+ facet :foo, :bar do
392
+ end
393
+ end
394
+ end.should raise_error(ArgumentError)
395
+ end
396
+ end
397
+ end
@@ -0,0 +1,313 @@
1
+ shared_examples_for 'fulltext query' do
2
+ it 'searches by keywords' do
3
+ search do
4
+ keywords 'keyword search'
5
+ end
6
+ connection.should have_last_search_with(:q => 'keyword search')
7
+ end
8
+
9
+ it 'ignores keywords if empty' do
10
+ search do
11
+ keywords ''
12
+ end
13
+ connection.should_not have_last_search_with(:defType => 'dismax')
14
+ end
15
+
16
+ it 'ignores keywords if nil' do
17
+ search do
18
+ keywords nil
19
+ end
20
+ connection.should_not have_last_search_with(:defType => 'dismax')
21
+ end
22
+
23
+ it 'ignores keywords with only whitespace' do
24
+ search do
25
+ keywords " \t"
26
+ end
27
+ connection.should_not have_last_search_with(:defType => 'dismax')
28
+ end
29
+
30
+ it 'gracefully ignores keywords block if keywords ignored' do
31
+ search do
32
+ keywords(nil) { fields(:title) }
33
+ end
34
+ end
35
+
36
+ it 'sets default query parser to dismax when keywords used' do
37
+ search do
38
+ keywords 'keyword search'
39
+ end
40
+ connection.should have_last_search_with(:defType => 'dismax')
41
+ end
42
+
43
+ it 'searches types in filter query if keywords used' do
44
+ search do
45
+ keywords 'keyword search'
46
+ end
47
+ connection.should have_last_search_with(:fq => ['type:Post'])
48
+ end
49
+
50
+ describe 'with multiple keyword components' do
51
+ before :each do
52
+ session.search Post do
53
+ keywords 'first search', :fields => :title
54
+ keywords 'second search'
55
+ end
56
+ end
57
+
58
+ it 'puts specified keywords in subquery' do
59
+ subqueries(:q).map { |subquery| subquery[:v] }.should ==
60
+ ['first search', 'second search']
61
+ end
62
+
63
+ it 'puts specified dismax parameters in subquery' do
64
+ subqueries(:q).first[:qf].should == 'title_text'
65
+ end
66
+
67
+ it 'puts default dismax parameters in subquery' do
68
+ subqueries(:q).last[:qf].split(' ').sort.should == %w(backwards_title_text body_textsv tags_textv title_text)
69
+ end
70
+
71
+ it 'puts field list in main query' do
72
+ connection.should have_last_search_with(:fl => '* score')
73
+ end
74
+ end
75
+
76
+ it 'searches all text fields for searched class' do
77
+ search = search do
78
+ keywords 'keyword search'
79
+ end
80
+ connection.searches.last[:qf].split(' ').sort.should == %w(backwards_title_text body_textsv tags_textv title_text)
81
+ end
82
+
83
+ it 'searches both stored and unstored text fields' do
84
+ search Post, Namespaced::Comment do
85
+ keywords 'keyword search'
86
+ end
87
+ connection.searches.last[:qf].split(' ').sort.should == %w(author_name_text backwards_title_text body_text body_textsv tags_textv title_text)
88
+ end
89
+
90
+ it 'searches only specified text fields when specified' do
91
+ search do
92
+ keywords 'keyword search', :fields => [:title, :body]
93
+ end
94
+ connection.searches.last[:qf].split(' ').sort.should == %w(body_textsv title_text)
95
+ end
96
+
97
+ it 'excludes text fields when instructed' do
98
+ search do
99
+ keywords 'keyword search' do
100
+ exclude_fields :backwards_title, :body_mlt
101
+ end
102
+ end
103
+ connection.searches.last[:qf].split(' ').sort.should == %w(body_textsv tags_textv title_text)
104
+ end
105
+
106
+ it 'assigns boost to fields when specified' do
107
+ search do
108
+ keywords 'keyword search' do
109
+ fields :title => 2.0, :body => 0.75
110
+ end
111
+ end
112
+ connection.searches.last[:qf].split(' ').sort.should == %w(body_textsv^0.75 title_text^2.0)
113
+ end
114
+
115
+ it 'allows assignment of boosted and unboosted fields' do
116
+ search do
117
+ keywords 'keyword search' do
118
+ fields :body, :title => 2.0
119
+ end
120
+ end
121
+ end
122
+
123
+ it 'searches both unstored and stored text field with same name when specified' do
124
+ search Post, Namespaced::Comment do
125
+ keywords 'keyword search', :fields => [:body]
126
+ end
127
+ connection.searches.last[:qf].split(' ').sort.should == %w(body_text body_textsv)
128
+ end
129
+
130
+ it 'requests score when keywords used' do
131
+ search do
132
+ keywords 'keyword search'
133
+ end
134
+ connection.should have_last_search_with(:fl => '* score')
135
+ end
136
+
137
+ it 'does not request score when keywords not used' do
138
+ search Post
139
+ connection.should_not have_last_search_with(:fl)
140
+ end
141
+
142
+ it 'sets phrase fields' do
143
+ search do
144
+ keywords 'great pizza' do
145
+ phrase_fields :title => 2.0
146
+ end
147
+ end
148
+ connection.should have_last_search_with(:pf => 'title_text^2.0')
149
+ end
150
+
151
+ it 'sets phrase fields with boost' do
152
+ search do
153
+ keywords 'great pizza' do
154
+ phrase_fields :title => 1.5
155
+ end
156
+ end
157
+ connection.should have_last_search_with(:pf => 'title_text^1.5')
158
+ end
159
+
160
+ it 'sets phrase slop from DSL' do
161
+ search do
162
+ keywords 'great pizza' do
163
+ phrase_slop 2
164
+ end
165
+ end
166
+ connection.should have_last_search_with(:ps => 2)
167
+ end
168
+
169
+ it 'sets boost for certain fields without restricting fields' do
170
+ search do
171
+ keywords 'great pizza' do
172
+ boost_fields :title => 1.5
173
+ end
174
+ end
175
+ connection.searches.last[:qf].split(' ').sort.should == %w(backwards_title_text body_textsv tags_textv title_text^1.5)
176
+ end
177
+
178
+ it 'ignores boost fields that do not apply' do
179
+ search do
180
+ keywords 'great pizza' do
181
+ boost_fields :bogus => 1.2, :title => 1.5
182
+ end
183
+ end
184
+ connection.searches.last[:qf].split(' ').sort.should == %w(backwards_title_text body_textsv tags_textv title_text^1.5)
185
+ end
186
+
187
+ it 'sets default boost with default fields' do
188
+ search Photo do
189
+ keywords 'great pizza'
190
+ end
191
+ connection.should have_last_search_with(:qf => 'caption_text^1.5')
192
+ end
193
+
194
+ it 'sets default boost with fields specified in options' do
195
+ search Photo do
196
+ keywords 'great pizza', :fields => [:caption]
197
+ end
198
+ connection.should have_last_search_with(:qf => 'caption_text^1.5')
199
+ end
200
+
201
+ it 'sets default boost with fields specified in DSL' do
202
+ search Photo do
203
+ keywords 'great pizza' do
204
+ fields :caption
205
+ end
206
+ end
207
+ connection.should have_last_search_with(:qf => 'caption_text^1.5')
208
+ end
209
+
210
+ it 'overrides default boost when specified in DSL' do
211
+ search Photo do
212
+ keywords 'great pizza' do
213
+ fields :caption => 2.0
214
+ end
215
+ end
216
+ connection.should have_last_search_with(:qf => 'caption_text^2.0')
217
+ end
218
+
219
+ it 'creates boost query' do
220
+ search do
221
+ keywords 'great pizza' do
222
+ boost 2.0 do
223
+ with(:average_rating).greater_than(2.0)
224
+ end
225
+ end
226
+ end
227
+ connection.should have_last_search_with(:bq => ['average_rating_ft:[2\.0 TO *]^2.0'])
228
+ end
229
+
230
+ it 'creates multiple boost queries' do
231
+ search do
232
+ keywords 'great pizza' do
233
+ boost(2.0) do
234
+ with(:average_rating).greater_than(2.0)
235
+ end
236
+ boost(1.5) do
237
+ with(:featured, true)
238
+ end
239
+ end
240
+ end
241
+ connection.should have_last_search_with(
242
+ :bq => [
243
+ 'average_rating_ft:[2\.0 TO *]^2.0',
244
+ 'featured_bs:true^1.5'
245
+ ]
246
+ )
247
+ end
248
+
249
+ it 'sends minimum match parameter from options' do
250
+ search do
251
+ keywords 'great pizza', :minimum_match => 2
252
+ end
253
+ connection.should have_last_search_with(:mm => 2)
254
+ end
255
+
256
+ it 'sends minimum match parameter from DSL' do
257
+ search do
258
+ keywords('great pizza') { minimum_match(2) }
259
+ end
260
+ connection.should have_last_search_with(:mm => 2)
261
+ end
262
+
263
+ it 'sends tiebreaker parameter from options' do
264
+ search do
265
+ keywords 'great pizza', :tie => 0.1
266
+ end
267
+ connection.should have_last_search_with(:tie => 0.1)
268
+ end
269
+
270
+ it 'sends tiebreaker parameter from DSL' do
271
+ search do
272
+ keywords('great pizza') { tie(0.1) }
273
+ end
274
+ connection.should have_last_search_with(:tie => 0.1)
275
+ end
276
+
277
+ it 'sends query phrase slop from options' do
278
+ search do
279
+ keywords 'great pizza', :query_phrase_slop => 2
280
+ end
281
+ connection.should have_last_search_with(:qs => 2)
282
+ end
283
+
284
+ it 'sends query phrase slop from DSL' do
285
+ search do
286
+ keywords('great pizza') { query_phrase_slop(2) }
287
+ end
288
+ connection.should have_last_search_with(:qs => 2)
289
+ end
290
+
291
+ it 'allows specification of a text field that only exists in one type' do
292
+ search Post, Namespaced::Comment do
293
+ keywords 'keywords', :fields => :author_name
294
+ end
295
+ connection.searches.last[:qf].should == 'author_name_text'
296
+ end
297
+
298
+ it 'raises Sunspot::UnrecognizedFieldError for nonexistant fields in keywords' do
299
+ lambda do
300
+ search do
301
+ keywords :text, :fields => :bogus
302
+ end
303
+ end.should raise_error(Sunspot::UnrecognizedFieldError)
304
+ end
305
+
306
+ it 'raises Sunspot::UnrecognizedFieldError if a text field that does not exist for any type is specified' do
307
+ lambda do
308
+ search Post, Namespaced::Comment do
309
+ keywords 'fulltext', :fields => :bogus
310
+ end
311
+ end.should raise_error(Sunspot::UnrecognizedFieldError)
312
+ end
313
+ end