sunspot 2.3.0 → 2.6.0

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 (56) hide show
  1. checksums.yaml +4 -4
  2. data/Appraisals +4 -4
  3. data/lib/sunspot/adapters.rb +15 -1
  4. data/lib/sunspot/data_extractor.rb +36 -6
  5. data/lib/sunspot/dsl/fields.rb +16 -0
  6. data/lib/sunspot/dsl/fulltext.rb +1 -1
  7. data/lib/sunspot/dsl/group.rb +10 -0
  8. data/lib/sunspot/dsl/scope.rb +17 -17
  9. data/lib/sunspot/dsl/standard_query.rb +29 -1
  10. data/lib/sunspot/dsl.rb +2 -2
  11. data/lib/sunspot/field.rb +15 -4
  12. data/lib/sunspot/indexer.rb +37 -8
  13. data/lib/sunspot/query/abstract_fulltext.rb +7 -3
  14. data/lib/sunspot/query/abstract_json_field_facet.rb +3 -0
  15. data/lib/sunspot/query/composite_fulltext.rb +21 -2
  16. data/lib/sunspot/query/date_field_json_facet.rb +2 -16
  17. data/lib/sunspot/query/dismax.rb +10 -4
  18. data/lib/sunspot/query/function_query.rb +25 -1
  19. data/lib/sunspot/query/group.rb +4 -5
  20. data/lib/sunspot/query/join.rb +3 -5
  21. data/lib/sunspot/query/range_json_facet.rb +5 -2
  22. data/lib/sunspot/query/restriction.rb +18 -13
  23. data/lib/sunspot/query/standard_query.rb +12 -0
  24. data/lib/sunspot/search/abstract_search.rb +1 -1
  25. data/lib/sunspot/search/field_json_facet.rb +14 -3
  26. data/lib/sunspot/search/hit.rb +6 -1
  27. data/lib/sunspot/session.rb +9 -1
  28. data/lib/sunspot/setup.rb +69 -0
  29. data/lib/sunspot/util.rb +4 -11
  30. data/lib/sunspot/version.rb +1 -1
  31. data/lib/sunspot.rb +9 -1
  32. data/spec/api/adapters_spec.rb +13 -0
  33. data/spec/api/data_extractor_spec.rb +39 -0
  34. data/spec/api/indexer/removal_spec.rb +87 -0
  35. data/spec/api/query/connective_boost_examples.rb +85 -0
  36. data/spec/api/query/fulltext_examples.rb +6 -12
  37. data/spec/api/query/join_spec.rb +2 -2
  38. data/spec/api/query/standard_spec.rb +10 -0
  39. data/spec/api/search/hits_spec.rb +14 -0
  40. data/spec/api/setup_spec.rb +99 -0
  41. data/spec/api/sunspot_spec.rb +3 -0
  42. data/spec/helpers/indexer_helper.rb +22 -0
  43. data/spec/integration/atomic_updates_spec.rb +169 -5
  44. data/spec/integration/faceting_spec.rb +68 -34
  45. data/spec/integration/field_grouping_spec.rb +19 -0
  46. data/spec/integration/field_lists_spec.rb +16 -0
  47. data/spec/integration/geospatial_spec.rb +15 -0
  48. data/spec/integration/join_spec.rb +64 -0
  49. data/spec/integration/scoped_search_spec.rb +78 -0
  50. data/spec/mocks/adapters.rb +33 -0
  51. data/spec/mocks/connection.rb +6 -0
  52. data/spec/mocks/photo.rb +19 -5
  53. data/spec/mocks/post.rb +35 -1
  54. data/sunspot.gemspec +0 -2
  55. metadata +14 -8
  56. data/gemfiles/.gitkeep +0 -0
@@ -12,6 +12,20 @@ describe 'hits', :type => :search do
12
12
  end).to eq([['Post', post_1.id.to_s], ['Post', post_2.id.to_s]])
13
13
  end
14
14
 
15
+ it "should return ID prefix when used with compositeId shard router" do
16
+ Sunspot.index!(ModelWithPrefixId.new)
17
+
18
+ expect(Sunspot.search(ModelWithPrefixId).
19
+ hits.map { |h| h.id_prefix }.uniq).to eq ["USERDATA!"]
20
+ end
21
+
22
+ it "should parse nested ID prefixes" do
23
+ Sunspot.index!(ModelWithNestedPrefixId.new)
24
+
25
+ expect(Sunspot.search(ModelWithNestedPrefixId).
26
+ hits.map { |h| h.id_prefix }.uniq).to eq ["USER!USERDATA!"]
27
+ end
28
+
15
29
  it 'returns search total as attribute of hits' do
16
30
  stub_results(Post.new, 4)
17
31
  expect(session.search(Post) do
@@ -0,0 +1,99 @@
1
+ require File.expand_path('spec_helper', File.dirname(__FILE__))
2
+
3
+ describe Sunspot::Setup do
4
+ context '#id_prefix_for_class' do
5
+ subject { Sunspot::Setup.for(clazz).id_prefix_for_class }
6
+
7
+ context 'when `id_prefix` is defined on model' do
8
+ context 'as Proc' do
9
+ let(:clazz) { PostWithProcPrefixId }
10
+
11
+ it 'returns nil' do
12
+ is_expected.to be_nil
13
+ end
14
+ end
15
+
16
+ context 'as Symbol' do
17
+ let(:clazz) { PostWithSymbolPrefixId }
18
+
19
+ it 'returns nil' do
20
+ is_expected.to be_nil
21
+ end
22
+ end
23
+
24
+ context 'as String' do
25
+ let(:clazz) { PostWithStringPrefixId }
26
+
27
+ it 'returns `id_prefix` value' do
28
+ is_expected.to eq('USERDATA!')
29
+ end
30
+ end
31
+ end
32
+
33
+ context 'when `id_prefix` is not defined on model' do
34
+ let(:clazz) { PostWithoutPrefixId }
35
+
36
+ it 'returns nil' do
37
+ is_expected.to be_nil
38
+ end
39
+ end
40
+ end
41
+
42
+ context '#id_prefix_defined?' do
43
+ subject { Sunspot::Setup.for(clazz).id_prefix_defined? }
44
+
45
+ context 'when `id_prefix` is defined on model' do
46
+ let(:clazz) { PostWithProcPrefixId }
47
+
48
+ it 'returns true' do
49
+ is_expected.to be_truthy
50
+ end
51
+ end
52
+
53
+ context 'when `id_prefix` is not defined on model' do
54
+ let(:clazz) { PostWithoutPrefixId }
55
+
56
+ it 'returns false' do
57
+ is_expected.to be_falsey
58
+ end
59
+ end
60
+ end
61
+
62
+ context '#id_prefix_requires_instance?' do
63
+ subject { Sunspot::Setup.for(clazz).id_prefix_requires_instance? }
64
+
65
+ context 'when `id_prefix` is defined on model' do
66
+ context 'as Proc' do
67
+ let(:clazz) { PostWithProcPrefixId }
68
+
69
+ it 'returns true' do
70
+ is_expected.to be_truthy
71
+ end
72
+ end
73
+
74
+ context 'as Symbol' do
75
+ let(:clazz) { PostWithSymbolPrefixId }
76
+
77
+ it 'returns true' do
78
+ is_expected.to be_truthy
79
+ end
80
+ end
81
+
82
+ context 'as String' do
83
+ let(:clazz) { PostWithStringPrefixId }
84
+
85
+ it 'returns false' do
86
+ is_expected.to be_falsey
87
+ end
88
+ end
89
+ end
90
+
91
+ context 'when `id_prefix` is not defined on model' do
92
+ let(:clazz) { PostWithoutPrefixId }
93
+
94
+ it 'returns false' do
95
+ is_expected.to be_falsey
96
+ end
97
+ end
98
+ end
99
+ end
@@ -24,6 +24,9 @@ describe Sunspot do
24
24
  config_before_reset = Sunspot.config
25
25
  Sunspot.reset!(true)
26
26
  expect(Sunspot.config).to eq(config_before_reset)
27
+
28
+ # Restore sunspot config after test
29
+ Sunspot.reset!(false)
27
30
  end
28
31
  end
29
32
  end
@@ -14,4 +14,26 @@ module IndexerHelper
14
14
  def values_in_last_document_for(field_name)
15
15
  @connection.adds.last.last.fields_by_name(field_name).map { |field| field.value }
16
16
  end
17
+
18
+ def index_post(post)
19
+ Sunspot.index!(post)
20
+ hit = find_post(post)
21
+ expect(hit).not_to be_nil
22
+ hit
23
+ end
24
+
25
+ def find_post(post)
26
+ Sunspot.search(clazz).hits.find { |h| h.primary_key.to_i == post.id && h.id_prefix == id_prefix_value(post, id_prefix) }
27
+ end
28
+
29
+ def id_prefix_value(post, id_prefix)
30
+ return unless id_prefix
31
+ return id_prefix if id_prefix.is_a?(String)
32
+
33
+ id_prefix.call(post)
34
+ end
35
+
36
+ def post_solr_id
37
+ "#{id_prefix_value(post, id_prefix)}#{clazz} #{post.id}"
38
+ end
17
39
  end
@@ -1,6 +1,79 @@
1
1
  require File.expand_path('../spec_helper', File.dirname(__FILE__))
2
2
 
3
- describe 'atomic updates' do
3
+ shared_examples 'atomic update with instance as key' do
4
+ it 'updates record' do
5
+ post = clazz.new(title: 'A Title', featured: true)
6
+ Sunspot.index!(post)
7
+
8
+ validate_hit(find_and_validate_indexed_post_with_prefix_id(post, id_prefix), title: post.title, featured: post.featured)
9
+
10
+ Sunspot.atomic_update!(clazz, post => { title: 'A New Title' })
11
+ validate_hit(find_and_validate_indexed_post_with_prefix_id(post, id_prefix), title: 'A New Title', featured: true)
12
+
13
+ Sunspot.atomic_update!(clazz, post => { featured: false })
14
+ validate_hit(find_and_validate_indexed_post_with_prefix_id(post, id_prefix), title: 'A New Title', featured: false)
15
+ end
16
+
17
+ it 'does not print warning' do
18
+ post = clazz.new(title: 'A Title', featured: true)
19
+ Sunspot.index!(post)
20
+
21
+ validate_hit(find_and_validate_indexed_post_with_prefix_id(post, id_prefix), title: post.title, featured: post.featured)
22
+
23
+ expect do
24
+ Sunspot.atomic_update!(clazz, post => { title: 'A New Title' })
25
+ end.to_not output(Sunspot::AtomicUpdateRequireInstanceForCompositeIdMessage.call(clazz)).to_stderr
26
+ end
27
+
28
+ it 'does not create duplicate document' do
29
+ post = clazz.new(title: 'A Title', featured: true)
30
+ Sunspot.index!(post)
31
+
32
+ validate_hit(find_and_validate_indexed_post_with_prefix_id(post, id_prefix), title: post.title, featured: post.featured)
33
+
34
+ Sunspot.atomic_update!(clazz, post => { title: 'A New Title' })
35
+ hit = find_indexed_post_with_prefix_id(post, nil)
36
+ expect(hit).to be_nil
37
+ end
38
+ end
39
+
40
+ shared_examples 'atomic update with id as key' do
41
+ it 'does not update record' do
42
+ post = clazz.new(title: 'A Title', featured: true)
43
+ Sunspot.index!(post)
44
+
45
+ validate_hit(find_and_validate_indexed_post_with_prefix_id(post, id_prefix), title: post.title, featured: post.featured)
46
+
47
+ Sunspot.atomic_update!(clazz, post.id => { title: 'A New Title' })
48
+ validate_hit(find_and_validate_indexed_post_with_prefix_id(post, id_prefix), title: 'A Title', featured: true)
49
+
50
+ Sunspot.atomic_update!(clazz, post.id => { featured: false })
51
+ validate_hit(find_and_validate_indexed_post_with_prefix_id(post, id_prefix), title: 'A Title', featured: true)
52
+ end
53
+
54
+ it 'prints warning' do
55
+ post = clazz.new(title: 'A Title', featured: true)
56
+ Sunspot.index!(post)
57
+
58
+ validate_hit(find_and_validate_indexed_post_with_prefix_id(post, id_prefix), title: post.title, featured: post.featured)
59
+
60
+ expect do
61
+ Sunspot.atomic_update!(clazz, post.id => { title: 'A New Title' })
62
+ end.to output(Sunspot::AtomicUpdateRequireInstanceForCompositeIdMessage.call(clazz) + "\n").to_stderr
63
+ end
64
+
65
+ it 'creates duplicate document that have only fields provided for update' do
66
+ post = clazz.new(title: 'A Title', featured: true)
67
+ Sunspot.index!(post)
68
+
69
+ validate_hit(find_and_validate_indexed_post_with_prefix_id(post, id_prefix), title: post.title, featured: post.featured)
70
+
71
+ Sunspot.atomic_update!(clazz, post.id => { title: 'A New Title' })
72
+ validate_hit(find_and_validate_indexed_post_with_prefix_id(post, nil), title: 'A New Title', featured: nil)
73
+ end
74
+ end
75
+
76
+ describe 'Atomic Update feature' do
4
77
  before :all do
5
78
  Sunspot.remove_all
6
79
  end
@@ -18,10 +91,27 @@ describe 'atomic updates' do
18
91
  hit
19
92
  end
20
93
 
21
- it 'should update single record fields one by one' do
94
+ def find_and_validate_indexed_post_with_prefix_id(post, id_prefix)
95
+ hit = find_indexed_post_with_prefix_id(post, id_prefix_value(post, id_prefix))
96
+ expect(hit).not_to be_nil
97
+ hit
98
+ end
99
+
100
+ def find_indexed_post_with_prefix_id(post, id_prefix)
101
+ Sunspot.search(post.class).hits.find { |h| h.primary_key.to_i == post.id && h.id_prefix == id_prefix }
102
+ end
103
+
104
+ def id_prefix_value(post, id_prefix)
105
+ return unless id_prefix
106
+ return id_prefix if id_prefix.is_a?(String)
107
+
108
+ id_prefix.call(post)
109
+ end
110
+
111
+ it 'updates single record fields one by one' do
22
112
  post = Post.new(title: 'A Title', featured: true)
23
113
  Sunspot.index!(post)
24
-
114
+
25
115
  validate_hit(find_indexed_post(post.id), title: post.title, featured: post.featured)
26
116
 
27
117
  Sunspot.atomic_update!(Post, post.id => {title: 'A New Title'})
@@ -31,7 +121,7 @@ describe 'atomic updates' do
31
121
  validate_hit(find_indexed_post(post.id), title: 'A New Title', featured: false)
32
122
  end
33
123
 
34
- it 'should update fields for multiple records' do
124
+ it 'updates fields for multiple records' do
35
125
  post1 = Post.new(title: 'A First Title', featured: true)
36
126
  post2 = Post.new(title: 'A Second Title', featured: false)
37
127
  Sunspot.index!(post1, post2)
@@ -44,7 +134,7 @@ describe 'atomic updates' do
44
134
  validate_hit(find_indexed_post(post2.id), title: 'A Second Title', featured: true)
45
135
  end
46
136
 
47
- it 'should clear field value properly' do
137
+ it 'clears field value properly' do
48
138
  post = Post.new(title: 'A Title', tags: %w(tag1 tag2), featured: true)
49
139
  Sunspot.index!(post)
50
140
  validate_hit(find_indexed_post(post.id), title: post.title, tag_list: post.tags, featured: true)
@@ -55,4 +145,78 @@ describe 'atomic updates' do
55
145
  Sunspot.atomic_update!(Post, post.id => {featured: nil})
56
146
  validate_hit(find_indexed_post(post.id), title: post.title, tag_list: nil, featured: nil)
57
147
  end
148
+
149
+ context 'when `id_prefix` is defined on model' do
150
+ context 'as Proc' do
151
+ let(:clazz) { PostWithProcPrefixId }
152
+ let(:id_prefix) { lambda { |post| "USERDATA-#{post.id}!" } }
153
+
154
+ context 'and instance passed as key' do
155
+ include_examples 'atomic update with instance as key'
156
+ end
157
+
158
+ context 'and id passed as key' do
159
+ include_examples 'atomic update with id as key'
160
+ end
161
+ end
162
+
163
+ context 'as Symbol' do
164
+ let(:clazz) { PostWithSymbolPrefixId }
165
+ let(:id_prefix) { lambda { |post| "#{post.title}!" } }
166
+
167
+ context 'and instance passed as key' do
168
+ include_examples 'atomic update with instance as key'
169
+ end
170
+
171
+ context 'and id passed as key' do
172
+ include_examples 'atomic update with id as key'
173
+ end
174
+ end
175
+
176
+ context 'as String' do
177
+ let(:clazz) { PostWithStringPrefixId }
178
+ let(:id_prefix) { 'USERDATA!' }
179
+
180
+ context 'and instance passed as key' do
181
+ include_examples 'atomic update with instance as key'
182
+ end
183
+
184
+ context 'and id passed as key' do
185
+ it 'updates record' do
186
+ post = clazz.new(title: 'A Title', featured: true)
187
+ Sunspot.index!(post)
188
+
189
+ validate_hit(find_and_validate_indexed_post_with_prefix_id(post, id_prefix), title: post.title, featured: post.featured)
190
+
191
+ Sunspot.atomic_update!(clazz, post.id => { title: 'A New Title' })
192
+ validate_hit(find_and_validate_indexed_post_with_prefix_id(post, id_prefix), title: 'A New Title', featured: true)
193
+
194
+ Sunspot.atomic_update!(clazz, post.id => { featured: false })
195
+ validate_hit(find_and_validate_indexed_post_with_prefix_id(post, id_prefix), title: 'A New Title', featured: false)
196
+ end
197
+
198
+ it 'does not print warning' do
199
+ post = clazz.new(title: 'A Title', featured: true)
200
+ Sunspot.index!(post)
201
+
202
+ validate_hit(find_and_validate_indexed_post_with_prefix_id(post, id_prefix), title: post.title, featured: post.featured)
203
+
204
+ expect do
205
+ Sunspot.atomic_update!(clazz, post.id => { title: 'A New Title' })
206
+ end.to_not output(Sunspot::AtomicUpdateRequireInstanceForCompositeIdMessage.call(clazz) + "\n").to_stderr
207
+ end
208
+
209
+ it 'does not create duplicate document' do
210
+ post = clazz.new(title: 'A Title', featured: true)
211
+ Sunspot.index!(post)
212
+
213
+ validate_hit(find_and_validate_indexed_post_with_prefix_id(post, id_prefix), title: post.title, featured: post.featured)
214
+
215
+ Sunspot.atomic_update!(clazz, post.id => { title: 'A New Title' })
216
+ hit = find_indexed_post_with_prefix_id(post, nil)
217
+ expect(hit).to be_nil
218
+ end
219
+ end
220
+ end
221
+ end
58
222
  end
@@ -84,7 +84,7 @@ describe 'search faceting' do
84
84
  end
85
85
  expect(search.facet(:title).rows.map { |row| row.value }).to include('zero')
86
86
  end
87
-
87
+
88
88
  it 'should return facet rows from an offset' do
89
89
  search = Sunspot.search(Post) do
90
90
  facet :title, :offset => 3
@@ -214,6 +214,15 @@ describe 'search faceting' do
214
214
  expect(search.facet(:title).rows.map { |row| row.value }).to eq(%w(four three two one))
215
215
  end
216
216
 
217
+ it 'should include allBuckets and missing' do
218
+ search = Sunspot.search(Post) do
219
+ with :blog_id, 1
220
+ json_facet :title, all_buckets: true, missing: true
221
+ end
222
+ expect(search.facet(:title).other_count('allBuckets')).to eq(10)
223
+ expect(search.facet(:title).other_count('missing')).to eq(1)
224
+ end
225
+
217
226
  it 'should limit facet values by prefix' do
218
227
  search = Sunspot.search(Post) do
219
228
  with :blog_id, 1
@@ -221,7 +230,64 @@ describe 'search faceting' do
221
230
  end
222
231
  expect(search.facet(:title).rows.map { |row| row.value }.sort).to eq(%w(three two))
223
232
  end
233
+ end
224
234
 
235
+ context 'date or time json facet' do
236
+ before :all do
237
+ Sunspot.remove_all
238
+ posts = [
239
+ Post.new(title: 'dt test', blog_id: 1, published_at: Time.new(2020, 8, 20)),
240
+ Post.new(title: 'dt test', blog_id: 1, published_at: Time.new(2020, 7, 20)),
241
+ Post.new(title: 'dt test', blog_id: 1, published_at: Time.new(2020, 6, 20)),
242
+ Post.new(title: 'dt test', blog_id: 1, published_at: Time.new(2020, 6, 15)),
243
+ Post.new(title: 'dt test', blog_id: 1, published_at: Time.new(2020, 5, 20)),
244
+ Post.new(title: 'dt test', blog_id: 1, published_at: Time.new(2020, 4, 20)),
245
+ Post.new(title: 'dt test', blog_id: 1, published_at: Time.new(2020, 3, 20))
246
+ ]
247
+ posts.each { |p| Sunspot.index(p) }
248
+ Sunspot.commit
249
+ end
250
+
251
+ it 'facets properly with the range specified as time_range' do
252
+ time_range = [Time.new(2020, 4, 1), Time.new(2020, 7, 31)]
253
+ search = Sunspot.search(Post) do
254
+ with :blog_id, 1
255
+ json_facet :published_at, time_range: time_range, gap: 1, gap_unit: 'MONTHS'
256
+ end
257
+ expected_rows = [
258
+ { count: 1, value: Time.new(2020, 4, 1).utc.iso8601 },
259
+ { count: 1, value: Time.new(2020, 5, 1).utc.iso8601 },
260
+ { count: 2, value: Time.new(2020, 6, 1).utc.iso8601 },
261
+ { count: 1, value: Time.new(2020, 7, 1).utc.iso8601 }
262
+ ]
263
+ expect(search.facet(:published_at).rows.map { |row| { count: row.count, value: row.value } }).to eq(expected_rows)
264
+ end
265
+
266
+ it 'should use custom gap parameters if provided' do
267
+ time_range = [Time.new(2020, 4, 1), Time.new(2020, 7, 31)]
268
+ search = Sunspot.search(Post) do
269
+ with :blog_id, 1
270
+ json_facet :published_at, range: time_range, gap: 1, gap_unit: 'MONTHS'
271
+ end
272
+ expected_rows = [
273
+ { count: 1, value: Time.new(2020, 4, 1).utc.iso8601 },
274
+ { count: 1, value: Time.new(2020, 5, 1).utc.iso8601 },
275
+ { count: 2, value: Time.new(2020, 6, 1).utc.iso8601 },
276
+ { count: 1, value: Time.new(2020, 7, 1).utc.iso8601 }
277
+ ]
278
+ expect(search.facet(:published_at).rows.map { |row| { count: row.count, value: row.value } }).to eq(expected_rows)
279
+ end
280
+
281
+ it 'should support computing other statistics' do
282
+ time_range = [Time.new(2020, 5, 1), Time.new(2020, 7, 1)]
283
+ search = Sunspot.search(Post) do
284
+ with :blog_id, 1
285
+ json_facet :published_at, range: time_range, gap: 1, gap_unit: 'MONTHS', other: 'all'
286
+ end
287
+ expect(search.facet(:published_at).other_count('before')).to eq(2)
288
+ expect(search.facet(:published_at).other_count('after')).to eq(2)
289
+ expect(search.facet(:published_at).other_count('between')).to eq(3)
290
+ end
225
291
  end
226
292
 
227
293
  context 'nested json facet' do
@@ -237,7 +303,7 @@ describe 'search faceting' do
237
303
  end
238
304
 
239
305
  0.upto(9) { |i| Sunspot.index(Post.new(:title => 'zero', :author_name => "another#{i}", :blog_id => 1)) }
240
-
306
+
241
307
  Sunspot.commit
242
308
  end
243
309
 
@@ -459,38 +525,6 @@ describe 'search faceting' do
459
525
  expect(search.facet(:published_at).rows.last.value).to eq((time + 60*60*24)..(time + 60*60*24*2))
460
526
  expect(search.facet(:published_at).rows.last.count).to eq(1)
461
527
  end
462
-
463
- it 'json facet should return time ranges' do
464
- days_diff = 15
465
- time_from = Time.utc(2009, 7, 8)
466
- time_to = Time.utc(2009, 7, 8 + days_diff)
467
- search = Sunspot.search(Post) do
468
- json_facet(
469
- :published_at,
470
- :time_range => time_from..time_to
471
- )
472
- end
473
-
474
- expect(search.facet(:published_at).rows.size).to eq(days_diff)
475
- expect(search.facet(:published_at).rows[0].count).to eq(2)
476
- expect(search.facet(:published_at).rows[1].count).to eq(1)
477
- end
478
-
479
- it 'json facet should return time ranges with custom gap' do
480
- days_diff = 10
481
- time_from = Time.utc(2009, 7, 8)
482
- time_to = Time.utc(2009, 7, 8 + days_diff)
483
- search = Sunspot.search(Post) do
484
- json_facet(
485
- :published_at,
486
- :time_range => time_from..time_to,
487
- gap: 60*60*24*2
488
- )
489
- end
490
- expect(search.facet(:published_at).rows.size).to eq(days_diff / 2)
491
- expect(search.facet(:published_at).rows[0].count).to eq(3)
492
- end
493
-
494
528
  end
495
529
 
496
530
  context 'class facets' do
@@ -94,6 +94,25 @@ describe "field grouping" do
94
94
  expect(search.group(:title).groups.length).to eql(1)
95
95
  expect(search.group(:title).groups.first.results).to eq([ @posts.last ])
96
96
  end
97
+
98
+ context "returns a not paginated collection" do
99
+ subject do
100
+ search = Sunspot.search(Post) do
101
+ group :title do
102
+ ngroups false
103
+ end
104
+ paginate :per_page => 1, :page => 2
105
+
106
+ end
107
+ search.group(:title).groups
108
+ end
109
+
110
+ it { expect(subject.per_page).to eql(1) }
111
+ it { expect(subject.total_pages).to eql(0) }
112
+ it { expect(subject.current_page).to eql(2) }
113
+ it { expect(subject.first_page?).to be(false) }
114
+ it { expect(subject.last_page?).to be(true) }
115
+ end
97
116
 
98
117
  context "returns a paginated collection" do
99
118
  subject do
@@ -31,6 +31,22 @@ describe 'fields lists' do
31
31
  end
32
32
  end
33
33
 
34
+ it 'does not raise Sunspot::UnrecognizedFieldError when listing existing text fields' do
35
+ expect do
36
+ Sunspot.search(Post) {
37
+ field_list(:body)
38
+ }
39
+ end.to_not raise_error
40
+ end
41
+
42
+ it 'does raise Sunspot::UnrecognizedFieldError when listing a non-existent text fields' do
43
+ expect do
44
+ Sunspot.search(Post) {
45
+ field_list(:bogus_body)
46
+ }
47
+ end.to raise_error(Sunspot::UnrecognizedFieldError)
48
+ end
49
+
34
50
  it 'does not load any stored fields' do
35
51
  hit = Sunspot.search(Post) { without_stored_fields }.hits.first
36
52
 
@@ -26,15 +26,30 @@ describe "geospatial search" do
26
26
  expect(results).not_to include(@post)
27
27
  end
28
28
 
29
+ it "filters out posts in the radius" do
30
+ results = Sunspot.search(Post) {
31
+ without(:coordinates_new).in_radius(32, -68, 1)
32
+ }.results
33
+
34
+ expect(results).not_to include(@post)
35
+ end
36
+
29
37
  it "allows conjunction queries with radius" do
38
+ post = Post.new(:title => "Howdy",
39
+ :coordinates => Sunspot::Util::Coordinates.new(35, -68))
40
+
41
+ Sunspot.index!(post)
42
+
30
43
  results = Sunspot.search(Post) {
31
44
  any_of do
32
45
  with(:coordinates_new).in_radius(32, -68, 1)
33
46
  with(:coordinates_new).in_radius(35, 68, 1)
47
+ without(:coordinates_new).in_radius(35, -68, 1)
34
48
  end
35
49
  }.results
36
50
 
37
51
  expect(results).to include(@post)
52
+ expect(results).not_to include(post)
38
53
  end
39
54
 
40
55
  it "allows conjunction queries with bounding box" do
@@ -0,0 +1,64 @@
1
+ require File.expand_path('../spec_helper', File.dirname(__FILE__))
2
+
3
+ describe "searching by joined fields" do
4
+ before :each do
5
+ Sunspot.remove_all!
6
+
7
+ @container1 = PhotoContainer.new(:id => 1)
8
+ @container2 = PhotoContainer.new(:id => 2).tap { |c| allow(c).to receive(:id).and_return(2) }
9
+ @container3 = PhotoContainer.new(:id => 3).tap { |c| allow(c).to receive(:id).and_return(3) }
10
+
11
+ @picture = Picture.new(:photo_container_id => @container1.id, :description => "one", :published => true)
12
+ @photo1 = Photo.new(:photo_container_id => @container1.id, :description => "two", :published => true)
13
+ @photo2 = Photo.new(:photo_container_id => @container2.id, :description => "three", :published => false)
14
+
15
+ Sunspot.index!(@container1, @container2, @photo1, @photo2, @picture)
16
+ end
17
+
18
+ it "matches by joined fields" do
19
+ {
20
+ "one" => [],
21
+ "two" => [@container1],
22
+ "three" => [@container2]
23
+ }.each do |key, res|
24
+ results = Sunspot.search(PhotoContainer) {
25
+ fulltext(key, :fields => [:photo_description])
26
+ }.results
27
+
28
+ expect(results).to eq res
29
+ end
30
+ end
31
+
32
+ it "doesn't match by joined fields with the same name from other collections" do
33
+ {
34
+ "one" => [@container1],
35
+ "two" => [],
36
+ "three" => []
37
+ }.each do |key, res|
38
+ results = Sunspot.search(PhotoContainer) {
39
+ fulltext(key, :fields => [:picture_description])
40
+ }.results
41
+
42
+ expect(results).to eq res
43
+ end
44
+ end
45
+
46
+ it "matches by joined fields when using filter queries" do
47
+ {
48
+ :photo_published => [
49
+ [true, [@container1]],
50
+ [false, [@container2]]
51
+ ],
52
+ :picture_published => [
53
+ [true, [@container1]],
54
+ [false, []]
55
+ ]
56
+ }.each do |key, data|
57
+ data.each do |(value, res)|
58
+ results = Sunspot.search(PhotoContainer) { with(key, value) }.results
59
+
60
+ expect(results).to eq res
61
+ end
62
+ end
63
+ end
64
+ end