nxa-sunspot 0.10.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (142) hide show
  1. data/History.txt +153 -0
  2. data/LICENSE +18 -0
  3. data/README.rdoc +191 -0
  4. data/Rakefile +9 -0
  5. data/TODO +14 -0
  6. data/VERSION.yml +4 -0
  7. data/bin/sunspot-configure-solr +40 -0
  8. data/bin/sunspot-solr +95 -0
  9. data/lib/light_config.rb +40 -0
  10. data/lib/sunspot.rb +488 -0
  11. data/lib/sunspot/adapters.rb +265 -0
  12. data/lib/sunspot/composite_setup.rb +188 -0
  13. data/lib/sunspot/configuration.rb +56 -0
  14. data/lib/sunspot/data_extractor.rb +50 -0
  15. data/lib/sunspot/dsl.rb +4 -0
  16. data/lib/sunspot/dsl/field_query.rb +150 -0
  17. data/lib/sunspot/dsl/fields.rb +100 -0
  18. data/lib/sunspot/dsl/fulltext.rb +228 -0
  19. data/lib/sunspot/dsl/query.rb +162 -0
  20. data/lib/sunspot/dsl/query_facet.rb +36 -0
  21. data/lib/sunspot/dsl/restriction.rb +25 -0
  22. data/lib/sunspot/dsl/scope.rb +225 -0
  23. data/lib/sunspot/dsl/search.rb +30 -0
  24. data/lib/sunspot/field.rb +181 -0
  25. data/lib/sunspot/field_factory.rb +144 -0
  26. data/lib/sunspot/indexer.rb +133 -0
  27. data/lib/sunspot/query.rb +9 -0
  28. data/lib/sunspot/query/abstract_field_facet.rb +43 -0
  29. data/lib/sunspot/query/boost_query.rb +20 -0
  30. data/lib/sunspot/query/connective.rb +189 -0
  31. data/lib/sunspot/query/date_field_facet.rb +14 -0
  32. data/lib/sunspot/query/dismax.rb +88 -0
  33. data/lib/sunspot/query/field_facet.rb +9 -0
  34. data/lib/sunspot/query/highlighting.rb +55 -0
  35. data/lib/sunspot/query/local.rb +27 -0
  36. data/lib/sunspot/query/pagination.rb +38 -0
  37. data/lib/sunspot/query/query.rb +86 -0
  38. data/lib/sunspot/query/query_facet.rb +16 -0
  39. data/lib/sunspot/query/restriction.rb +254 -0
  40. data/lib/sunspot/query/scope.rb +9 -0
  41. data/lib/sunspot/query/sort.rb +105 -0
  42. data/lib/sunspot/query/sort_composite.rb +33 -0
  43. data/lib/sunspot/query/text_field_boost.rb +15 -0
  44. data/lib/sunspot/schema.rb +147 -0
  45. data/lib/sunspot/search.rb +216 -0
  46. data/lib/sunspot/search/date_facet.rb +35 -0
  47. data/lib/sunspot/search/facet_row.rb +27 -0
  48. data/lib/sunspot/search/field_facet.rb +44 -0
  49. data/lib/sunspot/search/highlight.rb +38 -0
  50. data/lib/sunspot/search/hit.rb +117 -0
  51. data/lib/sunspot/search/query_facet.rb +62 -0
  52. data/lib/sunspot/session.rb +236 -0
  53. data/lib/sunspot/setup.rb +323 -0
  54. data/lib/sunspot/text_field_setup.rb +29 -0
  55. data/lib/sunspot/type.rb +204 -0
  56. data/lib/sunspot/util.rb +210 -0
  57. data/solr/etc/jetty.xml +212 -0
  58. data/solr/etc/webdefault.xml +379 -0
  59. data/solr/lib/jetty-6.1.3.jar +0 -0
  60. data/solr/lib/jetty-util-6.1.3.jar +0 -0
  61. data/solr/lib/jsp-2.1/ant-1.6.5.jar +0 -0
  62. data/solr/lib/jsp-2.1/core-3.1.1.jar +0 -0
  63. data/solr/lib/jsp-2.1/jsp-2.1.jar +0 -0
  64. data/solr/lib/jsp-2.1/jsp-api-2.1.jar +0 -0
  65. data/solr/lib/servlet-api-2.5-6.1.3.jar +0 -0
  66. data/solr/solr/conf/elevate.xml +36 -0
  67. data/solr/solr/conf/protwords.txt +21 -0
  68. data/solr/solr/conf/schema.xml +64 -0
  69. data/solr/solr/conf/solrconfig.xml +725 -0
  70. data/solr/solr/conf/stopwords.txt +57 -0
  71. data/solr/solr/conf/synonyms.txt +31 -0
  72. data/solr/solr/lib/geoapi-nogenerics-2.1-M2.jar +0 -0
  73. data/solr/solr/lib/gt2-referencing-2.3.1.jar +0 -0
  74. data/solr/solr/lib/jsr108-0.01.jar +0 -0
  75. data/solr/solr/lib/locallucene.jar +0 -0
  76. data/solr/solr/lib/localsolr.jar +0 -0
  77. data/solr/start.jar +0 -0
  78. data/solr/webapps/solr.war +0 -0
  79. data/spec/api/adapters_spec.rb +33 -0
  80. data/spec/api/indexer/attributes_spec.rb +110 -0
  81. data/spec/api/indexer/batch_spec.rb +46 -0
  82. data/spec/api/indexer/dynamic_fields_spec.rb +33 -0
  83. data/spec/api/indexer/fixed_fields_spec.rb +57 -0
  84. data/spec/api/indexer/fulltext_spec.rb +43 -0
  85. data/spec/api/indexer/removal_spec.rb +46 -0
  86. data/spec/api/indexer/spec_helper.rb +1 -0
  87. data/spec/api/indexer_spec.rb +4 -0
  88. data/spec/api/query/adjust_params_spec.rb +37 -0
  89. data/spec/api/query/connectives_spec.rb +176 -0
  90. data/spec/api/query/dsl_spec.rb +12 -0
  91. data/spec/api/query/dynamic_fields_spec.rb +149 -0
  92. data/spec/api/query/faceting_spec.rb +296 -0
  93. data/spec/api/query/fulltext_spec.rb +281 -0
  94. data/spec/api/query/highlighting_spec.rb +225 -0
  95. data/spec/api/query/local_spec.rb +62 -0
  96. data/spec/api/query/ordering_pagination_spec.rb +95 -0
  97. data/spec/api/query/scope_spec.rb +266 -0
  98. data/spec/api/query/spec_helper.rb +1 -0
  99. data/spec/api/query/text_field_scoping_spec.rb +30 -0
  100. data/spec/api/query/types_spec.rb +20 -0
  101. data/spec/api/search/dynamic_fields_spec.rb +27 -0
  102. data/spec/api/search/faceting_spec.rb +242 -0
  103. data/spec/api/search/highlighting_spec.rb +65 -0
  104. data/spec/api/search/hits_spec.rb +67 -0
  105. data/spec/api/search/results_spec.rb +52 -0
  106. data/spec/api/search/search_spec.rb +23 -0
  107. data/spec/api/search/spec_helper.rb +1 -0
  108. data/spec/api/session_spec.rb +198 -0
  109. data/spec/api/spec_helper.rb +1 -0
  110. data/spec/api/sunspot_spec.rb +18 -0
  111. data/spec/helpers/indexer_helper.rb +29 -0
  112. data/spec/helpers/query_helper.rb +13 -0
  113. data/spec/helpers/search_helper.rb +78 -0
  114. data/spec/integration/dynamic_fields_spec.rb +55 -0
  115. data/spec/integration/faceting_spec.rb +188 -0
  116. data/spec/integration/highlighting_spec.rb +22 -0
  117. data/spec/integration/indexing_spec.rb +7 -0
  118. data/spec/integration/keyword_search_spec.rb +245 -0
  119. data/spec/integration/local_search_spec.rb +56 -0
  120. data/spec/integration/scoped_search_spec.rb +303 -0
  121. data/spec/integration/spec_helper.rb +7 -0
  122. data/spec/integration/stored_fields_spec.rb +10 -0
  123. data/spec/integration/test_pagination.rb +32 -0
  124. data/spec/mocks/adapters.rb +32 -0
  125. data/spec/mocks/blog.rb +3 -0
  126. data/spec/mocks/comment.rb +19 -0
  127. data/spec/mocks/connection.rb +106 -0
  128. data/spec/mocks/mock_adapter.rb +30 -0
  129. data/spec/mocks/mock_record.rb +48 -0
  130. data/spec/mocks/photo.rb +11 -0
  131. data/spec/mocks/post.rb +75 -0
  132. data/spec/mocks/super_class.rb +2 -0
  133. data/spec/mocks/user.rb +8 -0
  134. data/spec/spec_helper.rb +68 -0
  135. data/tasks/gemspec.rake +42 -0
  136. data/tasks/rcov.rake +28 -0
  137. data/tasks/rdoc.rake +22 -0
  138. data/tasks/schema.rake +19 -0
  139. data/tasks/spec.rake +24 -0
  140. data/tasks/todo.rake +4 -0
  141. data/templates/schema.xml.erb +36 -0
  142. metadata +319 -0
@@ -0,0 +1,22 @@
1
+ describe 'keyword highlighting' do
2
+ before :all do
3
+ @posts = []
4
+ @posts << Post.new(:body => 'And the fox laughed')
5
+ @posts << Post.new(:body => 'Lorem ipsum dolor sit amet, consectetur adipiscing elit', :blog_id => 1)
6
+ Sunspot.index!(*@posts)
7
+ @search_result = Sunspot.search(Post) { keywords 'fox', :highlight => true }
8
+ end
9
+
10
+ it 'should include highlights in the results' do
11
+ @search_result.hits.first.highlights.length.should == 1
12
+ end
13
+
14
+ it 'should return formatted highlight fragments' do
15
+ @search_result.hits.first.highlights(:body).first.format.should == 'And the <em>fox</em> laughed'
16
+ end
17
+
18
+ it 'should be empty for non-keyword searches' do
19
+ search_result = Sunspot.search(Post){ with :blog_id, 1 }
20
+ search_result.hits.first.highlights.should be_empty
21
+ end
22
+ end
@@ -0,0 +1,7 @@
1
+ describe 'indexing' do
2
+ it 'should index non-multivalued field with newlines' do
3
+ lambda do
4
+ Sunspot.index!(Post.new(:title => "A\nTitle"))
5
+ end.should_not raise_error(RSolr::RequestError)
6
+ end
7
+ end
@@ -0,0 +1,245 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe 'keyword search' do
4
+ describe 'generally' do
5
+ before :all do
6
+ Sunspot.remove_all
7
+ @posts = []
8
+ @posts << Post.new(:title => 'The toast elects the insufficient spirit',
9
+ :body => 'Does the wind write?')
10
+ @posts << Post.new(:title => 'A nail abbreviates the recovering insight outside the moron',
11
+ :body => 'The interpreted strain scans the buffer around the upper temper')
12
+ @posts << Post.new(:title => 'The toast abbreviates the recovering spirit',
13
+ :body => 'Does the wind interpret the buffer, moron?')
14
+ Sunspot.index!(*@posts)
15
+ @comment = Namespaced::Comment.new(:body => 'Hey there where ya goin, not exactly knowin, who says you have to call just one place toast.')
16
+ Sunspot.index!(@comment)
17
+ end
18
+
19
+ it 'matches a single keyword out of a single field' do
20
+ results = Sunspot.search(Post) { keywords 'toast' }.results
21
+ [0, 2].each { |i| results.should include(@posts[i]) }
22
+ [1].each { |i| results.should_not include(@posts[i]) }
23
+ end
24
+
25
+ it 'matches multiple words out of a single field' do
26
+ results = Sunspot.search(Post) { keywords 'elects toast' }.results
27
+ results.should == [@posts[0]]
28
+ end
29
+
30
+ it 'matches multiple words in multiple fields' do
31
+ results = Sunspot.search(Post) { keywords 'toast wind' }.results
32
+ [0, 2].each { |i| results.should include(@posts[i]) }
33
+ [1].each { |i| results.should_not include(@posts[i]) }
34
+ end
35
+
36
+ it 'matches multiple types' do
37
+ results = Sunspot.search(Post, Namespaced::Comment) do
38
+ keywords 'toast'
39
+ end.results
40
+ [@posts[0], @posts[2], @comment].each { |obj| results.should include(obj) }
41
+ results.should_not include(@posts[1])
42
+ end
43
+
44
+ it 'matches keywords from only the fields specified' do
45
+ results = Sunspot.search(Post) do
46
+ keywords 'moron', :fields => [:title]
47
+ end.results
48
+ results.should == [@posts[1]]
49
+ end
50
+ end
51
+
52
+ describe 'with field boost' do
53
+ before :all do
54
+ Sunspot.remove_all
55
+ @posts = [:title, :body].map { |field| Post.new(field => 'rhinoceros') }
56
+ Sunspot.index!(*@posts)
57
+ end
58
+
59
+ it 'should assign a higher score to the result matching the higher-boosted field' do
60
+ search = Sunspot.search(Post) { keywords 'rhinoceros' }
61
+ search.hits.map { |hit| hit.primary_key }.should ==
62
+ @posts.map { |post| post.id.to_s }
63
+ search.hits.first.score.should > search.hits.last.score
64
+ end
65
+ end
66
+
67
+ describe 'with document boost' do
68
+ before :all do
69
+ Sunspot.remove_all
70
+ @posts = [4.0, 2.0].map do |rating|
71
+ Post.new(:title => 'Test', :ratings_average => rating)
72
+ end
73
+ Sunspot.index!(*@posts)
74
+ end
75
+
76
+ it 'should assign a higher score to the higher-boosted document' do
77
+ search = Sunspot.search(Post) { keywords 'test' }
78
+ search.hits.map { |hit| hit.primary_key }.should ==
79
+ @posts.map { |post| post.id.to_s }
80
+ search.hits.first.score.should > search.hits.last.score
81
+ end
82
+ end
83
+
84
+ describe 'with search-time boost' do
85
+ before :each do
86
+ Sunspot.remove_all
87
+ @comments = [
88
+ Namespaced::Comment.new(:body => 'test text'),
89
+ Namespaced::Comment.new(:author_name => 'test text')
90
+ ]
91
+ Sunspot.index!(@comments)
92
+ end
93
+
94
+ it 'assigns a higher score to documents in which all words appear in the phrase field' do
95
+ hits = Sunspot.search(Namespaced::Comment) do
96
+ keywords 'test text' do
97
+ phrase_fields :body => 2.0
98
+ end
99
+ end.hits
100
+ hits.first.instance.should == @comments.first
101
+ hits.first.score.should > hits.last.score
102
+ end
103
+
104
+ it 'assigns a higher score to documents in which the search terms appear in a boosted field' do
105
+ hits = Sunspot.search(Namespaced::Comment) do
106
+ keywords 'test' do
107
+ fields :body => 2.0, :author_name => 0.75
108
+ end
109
+ end.hits
110
+ hits.first.instance.should == @comments.first
111
+ hits.first.score.should > hits.last.score
112
+ end
113
+
114
+ it 'assigns a higher score to documents in which the search terms appear in a higher boosted phrase field' do
115
+ hits = Sunspot.search(Namespaced::Comment) do
116
+ keywords 'test text' do
117
+ phrase_fields :body => 2.0, :author_name => 0.75
118
+ end
119
+ end.hits
120
+ hits.first.instance.should == @comments.first
121
+ hits.first.score.should > hits.last.score
122
+ end
123
+ end
124
+
125
+ describe 'boost query' do
126
+ before :all do
127
+ Sunspot.remove_all
128
+ Sunspot.index!(
129
+ @posts = [
130
+ Post.new(:title => 'Rhino', :featured => true),
131
+ Post.new(:title => 'Rhino', :ratings_average => 3.3),
132
+ Post.new(:title => 'Rhino')
133
+ ]
134
+ )
135
+ end
136
+
137
+ it 'should assign a higher score to the document matching the boost query' do
138
+ search = Sunspot.search(Post) do |query|
139
+ query.keywords('rhino') do
140
+ boost(2.0) do
141
+ with(:featured, true)
142
+ end
143
+ end
144
+ query.without(@posts[1])
145
+ end
146
+ search.results.should == [@posts[0], @posts[2]]
147
+ search.hits[0].score.should > search.hits[1].score
148
+ end
149
+
150
+ it 'should assign scores in order of multiple boost query match' do
151
+ search = Sunspot.search(Post) do
152
+ keywords 'rhino' do
153
+ boost(2.0) { with(:featured, true) }
154
+ boost(1.5) { with(:average_rating).greater_than(3.0) }
155
+ end
156
+ end
157
+ search.results.should == @posts
158
+ search.hits[0].score.should > search.hits[1].score
159
+ search.hits[1].score.should > search.hits[2].score
160
+ end
161
+ end
162
+
163
+ describe 'minimum match' do
164
+ before do
165
+ Sunspot.remove_all
166
+ @posts = [
167
+ Post.new(:title => 'Pepperoni Sausage Anchovies'),
168
+ Post.new(:title => 'Pepperoni Tomatoes Mushrooms')
169
+ ]
170
+ Sunspot.index!(@posts)
171
+ @search = Sunspot.search(Post) do
172
+ keywords 'pepperoni sausage extra cheese', :minimum_match => 2
173
+ end
174
+ end
175
+
176
+ it 'should match documents that contain the minimum_match number of search terms' do
177
+ @search.results.should include(@posts[0])
178
+ end
179
+
180
+ it 'should not match documents that do not contain the minimum_match number of search terms' do
181
+ @search.results.should_not include(@posts[1])
182
+ end
183
+ end
184
+
185
+ describe 'query phrase slop' do
186
+ before do
187
+ Sunspot.remove_all
188
+ @posts = [
189
+ Post.new(:title => 'One four'),
190
+ Post.new(:title => 'One three four'),
191
+ Post.new(:title => 'One two three four')
192
+ ]
193
+ Sunspot.index!(@posts)
194
+ @search = Sunspot.search(Post) do
195
+ keywords '"one four"', :query_phrase_slop => 1
196
+ end
197
+ end
198
+
199
+ it 'should match exact phrase' do
200
+ @search.results.should include(@posts[0])
201
+ end
202
+
203
+ it 'should match phrase divided by query phrase slop terms' do
204
+ @search.results.should include(@posts[1])
205
+ end
206
+
207
+ it 'should not match phrase divided by more than query phrase slop terms' do
208
+ @search.results.should_not include(@posts[2])
209
+ end
210
+ end
211
+
212
+ describe 'phrase field slop' do
213
+ before do
214
+ Sunspot.remove_all
215
+ @comments = [
216
+ Namespaced::Comment.new(:author_name => 'one four'),
217
+ Namespaced::Comment.new(:body => 'one four'),
218
+ Namespaced::Comment.new(:author_name => 'one three four'),
219
+ Namespaced::Comment.new(:body => 'one three four'),
220
+ Namespaced::Comment.new(:author_name => 'one two three four'),
221
+ Namespaced::Comment.new(:body => 'one two three four')
222
+ ]
223
+ Sunspot.index!(@comments)
224
+ @search = Sunspot.search(Namespaced::Comment) do
225
+ keywords 'one four' do
226
+ phrase_fields :author_name => 3.0
227
+ phrase_slop 1
228
+ end
229
+ end
230
+ @sorted_hits = @search.hits.sort_by { |hit| @comments.index(hit.instance) }
231
+ end
232
+
233
+ it 'should give phrase field boost to exact match' do
234
+ @sorted_hits[0].score.should > @sorted_hits[1].score
235
+ end
236
+
237
+ it 'should give phrase field boost to match within slop' do
238
+ @sorted_hits[2].score.should > @sorted_hits[3].score
239
+ end
240
+
241
+ it 'should not give phrase field boost to match beyond slop' do
242
+ @sorted_hits[4].score.should == @sorted_hits[5].score
243
+ end
244
+ end
245
+ end
@@ -0,0 +1,56 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe 'local search' do
4
+ ORIGIN = [40.6749113, -73.9648859]
5
+ before :each do
6
+ Sunspot.remove_all
7
+ @posts = [
8
+ Post.new(:coordinates => ORIGIN),
9
+ Post.new(:coordinates => [40.725304, -73.997211], :title => 'teacup'),
10
+ Post.new(:coordinates => [40.800069, -73.962283]),
11
+ Post.new(:coordinates => [43.706488, -72.292233]),
12
+ Post.new(:coordinates => [38.920303, -77.110934], :title => 'teacup'),
13
+ Post.new(:coordinates => [47.661557, -122.349938])
14
+ ]
15
+ @posts.each_with_index { |post, i| post.blog_id = @posts.length - i }
16
+ Sunspot.index!(@posts)
17
+ end
18
+
19
+ it 'should find all the posts within a given radius' do
20
+ search = Sunspot.search(Post) { |query| query.near(ORIGIN, 20) }
21
+ search.results.to_set.should == @posts[0..2].to_set
22
+ end
23
+
24
+ it 'should perform a radial search with fulltext matching' do
25
+ pending 'fix for LocalSolr facet query or subquery support'
26
+ search = Sunspot.search(Post) do |query|
27
+ query.keywords 'teacup'
28
+ query.near(ORIGIN, 20)
29
+ end
30
+ search.results.should == [@posts[1]]
31
+ end
32
+
33
+ it 'should perform a radial search with attribute scoping' do
34
+ search = Sunspot.search(Post) do |query|
35
+ query.near(ORIGIN,20)
36
+ query.with(:title, 'teacup')
37
+ end
38
+ search.results.should == [@posts[1]]
39
+ end
40
+
41
+ it 'should order by arbitrary field' do
42
+ search = Sunspot.search(Post) do |query|
43
+ query.near(ORIGIN, 20)
44
+ query.order_by(:blog_id)
45
+ end
46
+ search.results.should == @posts[0..2].reverse
47
+ end
48
+
49
+ it 'should order by geo distance' do
50
+ search = Sunspot.search(Post) do |query|
51
+ query.near(ORIGIN, 20)
52
+ query.order_by(:distance)
53
+ end
54
+ search.results.should == @posts[0..2]
55
+ end
56
+ end
@@ -0,0 +1,303 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe 'scoped_search' do
4
+ def self.test_field_type(name, attribute, field, *values)
5
+ raise(ArgumentError, 'Please supply five values') unless values.length == 5
6
+
7
+ context "with field of type #{name}" do
8
+ before :all do
9
+ Sunspot.remove_all
10
+ @posts = values.map do |value|
11
+ post = Post.new(attribute => value)
12
+ Sunspot.index(post)
13
+ post
14
+ end
15
+ Sunspot.commit
16
+ end
17
+
18
+ it 'should filter by exact match' do
19
+ Sunspot.search(Post) { with(field, values[2]) }.results.should == [@posts[2]]
20
+ end
21
+
22
+ it 'should reject by inexact match' do
23
+ results = Sunspot.search(Post) { without(field, values[2]) }.results
24
+ [0, 1, 3, 4].each { |i| results.should include(@posts[i]) }
25
+ results.should_not include(@posts[2])
26
+ end
27
+
28
+ it 'should filter by less than' do
29
+ results = Sunspot.search(Post) { with(field).less_than values[2] }.results
30
+ (0..2).each { |i| results.should include(@posts[i]) }
31
+ (3..4).each { |i| results.should_not include(@posts[i]) }
32
+ end
33
+
34
+ it 'should reject by less than' do
35
+ results = Sunspot.search(Post) { without(field).less_than values[2] }.results
36
+ (0..2).each { |i| results.should_not include(@posts[i]) }
37
+ (3..4).each { |i| results.should include(@posts[i]) }
38
+ end
39
+
40
+ it 'should filter by greater than' do
41
+ results = Sunspot.search(Post) { with(field).greater_than values[2] }.results
42
+ (2..4).each { |i| results.should include(@posts[i]) }
43
+ (0..1).each { |i| results.should_not include(@posts[i]) }
44
+ end
45
+
46
+ it 'should reject by greater than' do
47
+ results = Sunspot.search(Post) { without(field).greater_than values[2] }.results
48
+ (2..4).each { |i| results.should_not include(@posts[i]) }
49
+ (0..1).each { |i| results.should include(@posts[i]) }
50
+ end
51
+
52
+ it 'should filter by between' do
53
+ results = Sunspot.search(Post) { with(field).between(values[1]..values[3]) }.results
54
+ (1..3).each { |i| results.should include(@posts[i]) }
55
+ [0, 4].each { |i| results.should_not include(@posts[i]) }
56
+ end
57
+
58
+ it 'should reject by between' do
59
+ results = Sunspot.search(Post) { without(field).between(values[1]..values[3]) }.results
60
+ (1..3).each { |i| results.should_not include(@posts[i]) }
61
+ [0, 4].each { |i| results.should include(@posts[i]) }
62
+ end
63
+
64
+ it 'should filter by any of' do
65
+ results = Sunspot.search(Post) { with(field).any_of(values.values_at(1, 3)) }.results
66
+ [1, 3].each { |i| results.should include(@posts[i]) }
67
+ [0, 2, 4].each { |i| results.should_not include(@posts[i]) }
68
+ end
69
+
70
+ it 'should reject by any of' do
71
+ results = Sunspot.search(Post) { without(field).any_of(values.values_at(1, 3)) }.results
72
+ [1, 3].each { |i| results.should_not include(@posts[i]) }
73
+ [0, 2, 4].each { |i| results.should include(@posts[i]) }
74
+ end
75
+
76
+ it 'should order by field ascending' do
77
+ results = Sunspot.search(Post) { order_by field, :asc }.results
78
+ results.should == @posts
79
+ end
80
+
81
+ it 'should order by field descending' do
82
+ results = Sunspot.search(Post) { order_by field, :desc }.results
83
+ results.should == @posts.reverse
84
+ end
85
+ end
86
+ end
87
+
88
+ test_field_type 'String', :title, :title, 'apple pie', 'banana split', 'cherry tart', 'date pastry', 'eggplant a la mode'
89
+ test_field_type 'Integer', :blog_id, :blog_id, -2, 0, 3, 12, 20
90
+ test_field_type 'Float', :ratings_average, :average_rating, -2.5, 0.0, 3.2, 3.5, 16.0
91
+ test_field_type 'Time', :published_at, :published_at, *(['1970-01-01 00:00:00 UTC', '1983-07-08 04:00:00 UTC', '1983-07-08 02:00:00 -0500',
92
+ '2005-11-05 10:00:00 UTC', Time.now.to_s].map { |t| Time.parse(t) })
93
+
94
+ describe 'Boolean field type' do
95
+ before :all do
96
+ Sunspot.remove_all
97
+ @posts = [Post.new(:featured => true), Post.new(:featured => false), Post.new]
98
+ Sunspot.index!(@posts)
99
+ end
100
+
101
+ it 'should filter by exact match for true' do
102
+ Sunspot.search(Post) { with(:featured, true) }.results.should == [@posts[0]]
103
+ end
104
+
105
+ it 'should filter for exact match for false' do
106
+ Sunspot.search(Post) { with(:featured, false) }.results.should == [@posts[1]]
107
+ end
108
+ end
109
+
110
+ describe 'passing nil value to equal' do
111
+ before :all do
112
+ Sunspot.remove_all
113
+ @posts = [Post.new(:title => 'apple'), Post.new]
114
+ Sunspot.index!(@posts)
115
+ end
116
+
117
+ it 'should filter results without value for field' do
118
+ Sunspot.search(Post) { with(:title, nil) }.results.should == [@posts[1]]
119
+ end
120
+
121
+ it 'should exclude results without value for field' do
122
+ Sunspot.search(Post) { without(:title, nil) }.results.should == [@posts[0]]
123
+ end
124
+ end
125
+
126
+ describe 'prefix searching' do
127
+ before :each do
128
+ Sunspot.remove_all
129
+ @posts = ['test', 'test post', 'some test', 'bogus'].map do |title|
130
+ Post.new(:title => title)
131
+ end
132
+ Sunspot.index!(@posts)
133
+ end
134
+
135
+ it 'should return results whose prefix matches' do
136
+ Sunspot.search(Post) { with(:title).starting_with('test') }.results.should == @posts[0..1]
137
+ end
138
+ end
139
+
140
+ describe 'exclusion by identity' do
141
+ before do
142
+ @posts = (1..5).map do |i|
143
+ post = Post.new
144
+ Sunspot.index(post)
145
+ post
146
+ end
147
+ Sunspot.commit
148
+ end
149
+
150
+ it 'should not return excluded object' do
151
+ excluded_post = @posts.shift
152
+ Sunspot.search(Post) { without(excluded_post) }.results.should_not include(excluded_post)
153
+ end
154
+
155
+ it 'should return objects not excluded' do
156
+ excluded_post = @posts.shift
157
+ for included_post in @posts
158
+ Sunspot.search(Post) { without(excluded_post) }.results.should include(included_post)
159
+ end
160
+ end
161
+
162
+ it 'should not return excluded objects' do
163
+ excluded_posts = [@posts.shift, @posts.shift]
164
+ for excluded_post in excluded_posts
165
+ Sunspot.search(Post) { without(excluded_posts) }.results.should_not include(excluded_post)
166
+ end
167
+ end
168
+ end
169
+
170
+ describe 'connectives' do
171
+ before :each do
172
+ Sunspot.remove_all
173
+ end
174
+
175
+ it 'should return results that match any restriction in a disjunction' do
176
+ posts = (1..3).map { |i| Post.new(:blog_id => i)}
177
+ Sunspot.index!(posts)
178
+ Sunspot.search(Post) do
179
+ any_of do
180
+ with(:blog_id, 1)
181
+ with(:blog_id, 2)
182
+ end
183
+ end.results.should == posts[0..1]
184
+ end
185
+
186
+ it 'should return results that match a nested conjunction in a disjunction' do
187
+ posts = [
188
+ Post.new(:title => 'No', :blog_id => 1),
189
+ Post.new(:title => 'Yes', :blog_id => 2),
190
+ Post.new(:title => 'Yes', :blog_id => 3),
191
+ Post.new(:title => 'No', :blog_id => 2)
192
+ ]
193
+ Sunspot.index!(posts)
194
+ Sunspot.search(Post) do
195
+ any_of do
196
+ with(:blog_id, 1)
197
+ all_of do
198
+ with(:blog_id, 2)
199
+ with(:title, 'Yes')
200
+ end
201
+ end
202
+ end.results.should == posts[0..1]
203
+ end
204
+
205
+ it 'should return results that match a conjunction with a negated restriction' do
206
+ posts = [
207
+ Post.new(:title => 'No', :blog_id => 1),
208
+ Post.new(:title => 'Yes', :blog_id => 2),
209
+ Post.new(:title => 'No', :blog_id => 2)
210
+ ]
211
+ Sunspot.index!(posts)
212
+ search = Sunspot.search(Post) do
213
+ any_of do
214
+ with(:blog_id, 1)
215
+ without(:title, 'No')
216
+ end
217
+ end
218
+ search.results.should == posts[0..1]
219
+ end
220
+
221
+ it 'should return results that match a conjunction with a disjunction with a conjunction with a negated restriction' do
222
+ posts = [
223
+ Post.new(:title => 'Yes', :ratings_average => 2.0),
224
+ Post.new(:blog_id => 1, :category_ids => [4], :ratings_average => 2.0),
225
+ Post.new(:blog_id => 1),
226
+ Post.new(:blog_id => 2),
227
+ Post.new(:blog_id => 1, :ratings_average => 2.0)
228
+ ]
229
+ Sunspot.index!(posts)
230
+ search = Sunspot.search(Post) do
231
+ any_of do
232
+ with(:title, 'Yes')
233
+ all_of do
234
+ with(:blog_id, 1)
235
+ any_of do
236
+ with(:category_ids, 4)
237
+ without(:average_rating, 2.0)
238
+ end
239
+ end
240
+ end
241
+ end
242
+ search.results.should == posts[0..2]
243
+ end
244
+
245
+ it 'should return results that match a disjunction with a negated restriction and a nested disjunction in a conjunction with a negated restriction' do
246
+ posts = [
247
+ Post.new,
248
+ Post.new(:title => 'Yes', :blog_id => 1, :category_ids => [4], :ratings_average => 2.0),
249
+ Post.new(:title => 'Yes', :blog_id => 1),
250
+ Post.new(:title => 'Yes'),
251
+ Post.new(:title => 'Yes', :category_ids => [4], :ratings_average => 2.0),
252
+ Post.new(:title => 'Yes', :blog_id => 1, :ratings_average => 2.0)
253
+ ]
254
+ Sunspot.index!(posts)
255
+ search = Sunspot.search(Post) do
256
+ any_of do
257
+ without(:title, 'Yes')
258
+ all_of do
259
+ with(:blog_id, 1)
260
+ any_of do
261
+ with(:category_ids, 4)
262
+ without(:average_rating, 2.0)
263
+ end
264
+ end
265
+ end
266
+ end
267
+ search.results.should == posts[0..2]
268
+ end
269
+ end
270
+
271
+ describe 'multiple column ordering' do
272
+ before do
273
+ Sunspot.remove_all
274
+ @posts = [
275
+ Post.new(:ratings_average => 2, :title => 'banana'),
276
+ Post.new(:ratings_average => 2, :title => 'eggplant'),
277
+ Post.new(:ratings_average => 1, :title => 'apple')
278
+ ].each { |post| Sunspot.index(post) }
279
+ Sunspot.commit
280
+ end
281
+
282
+ it 'should order with precedence given' do
283
+ search = Sunspot.search(Post) do
284
+ order_by :average_rating, :desc
285
+ order_by :sort_title, :asc
286
+ end
287
+ search.results.should == @posts
288
+ end
289
+ end
290
+
291
+ describe 'ordering by random' do
292
+ it 'should order randomly (run this test again if it fails)' do
293
+ Sunspot.remove_all
294
+ Sunspot.index!(Array.new(100) { Post.new })
295
+ result_sets = Array.new(2) do
296
+ Sunspot.search(Post) { order_by_random }.results.map do |result|
297
+ result.id
298
+ end
299
+ end
300
+ result_sets[0].should_not == result_sets[1]
301
+ end
302
+ end
303
+ end