sunspot 1.1.0 → 1.2.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 (66) hide show
  1. data/Gemfile +10 -0
  2. data/Gemfile.lock +32 -0
  3. data/History.txt +24 -0
  4. data/README.rdoc +18 -5
  5. data/lib/sunspot.rb +40 -0
  6. data/lib/sunspot/dsl.rb +2 -2
  7. data/lib/sunspot/dsl/field_query.rb +2 -2
  8. data/lib/sunspot/dsl/fields.rb +0 -10
  9. data/lib/sunspot/dsl/restriction.rb +4 -4
  10. data/lib/sunspot/dsl/restriction_with_near.rb +121 -0
  11. data/lib/sunspot/dsl/scope.rb +55 -67
  12. data/lib/sunspot/dsl/standard_query.rb +11 -15
  13. data/lib/sunspot/field.rb +30 -29
  14. data/lib/sunspot/field_factory.rb +0 -18
  15. data/lib/sunspot/installer/solrconfig_updater.rb +0 -30
  16. data/lib/sunspot/query.rb +4 -3
  17. data/lib/sunspot/query/common_query.rb +2 -2
  18. data/lib/sunspot/query/composite_fulltext.rb +7 -2
  19. data/lib/sunspot/query/connective.rb +21 -6
  20. data/lib/sunspot/query/dismax.rb +1 -0
  21. data/lib/sunspot/query/geo.rb +53 -0
  22. data/lib/sunspot/query/more_like_this.rb +1 -0
  23. data/lib/sunspot/query/restriction.rb +5 -5
  24. data/lib/sunspot/query/standard_query.rb +0 -4
  25. data/lib/sunspot/search/abstract_search.rb +1 -7
  26. data/lib/sunspot/search/hit.rb +10 -10
  27. data/lib/sunspot/search/query_facet.rb +8 -3
  28. data/lib/sunspot/session.rb +10 -2
  29. data/lib/sunspot/session_proxy.rb +16 -0
  30. data/lib/sunspot/session_proxy/master_slave_session_proxy.rb +1 -1
  31. data/lib/sunspot/session_proxy/sharding_session_proxy.rb +7 -0
  32. data/lib/sunspot/session_proxy/silent_fail_session_proxy.rb +42 -0
  33. data/lib/sunspot/session_proxy/thread_local_session_proxy.rb +1 -1
  34. data/lib/sunspot/setup.rb +1 -17
  35. data/lib/sunspot/type.rb +38 -6
  36. data/lib/sunspot/util.rb +21 -31
  37. data/lib/sunspot/version.rb +1 -1
  38. data/solr/solr/conf/solrconfig.xml +0 -4
  39. data/spec/api/binding_spec.rb +12 -0
  40. data/spec/api/indexer/attributes_spec.rb +22 -22
  41. data/spec/api/query/connectives_examples.rb +14 -1
  42. data/spec/api/query/fulltext_examples.rb +3 -3
  43. data/spec/api/query/geo_examples.rb +69 -0
  44. data/spec/api/query/scope_examples.rb +32 -13
  45. data/spec/api/query/standard_spec.rb +1 -1
  46. data/spec/api/search/faceting_spec.rb +5 -1
  47. data/spec/api/search/hits_spec.rb +14 -12
  48. data/spec/api/session_proxy/class_sharding_session_proxy_spec.rb +1 -1
  49. data/spec/api/session_proxy/sharding_session_proxy_spec.rb +1 -1
  50. data/spec/api/session_proxy/silent_fail_session_proxy_spec.rb +24 -0
  51. data/spec/api/session_spec.rb +22 -0
  52. data/spec/integration/local_search_spec.rb +42 -69
  53. data/spec/integration/scoped_search_spec.rb +30 -0
  54. data/spec/mocks/connection.rb +6 -2
  55. data/spec/mocks/photo.rb +0 -1
  56. data/spec/mocks/post.rb +11 -2
  57. data/spec/mocks/user.rb +6 -1
  58. data/spec/spec_helper.rb +2 -12
  59. metadata +209 -177
  60. data/lib/sunspot/query/local.rb +0 -26
  61. data/solr/solr/lib/lucene-spatial-2.9.1.jar +0 -0
  62. data/solr/solr/lib/solr-spatial-light-0.0.6.jar +0 -0
  63. data/spec/api/query/local_examples.rb +0 -38
  64. data/tasks/gemspec.rake +0 -33
  65. data/tasks/rcov.rake +0 -28
  66. data/tasks/spec.rake +0 -24
@@ -1,4 +1,5 @@
1
1
  require File.join(File.dirname(__FILE__), 'spec_helper')
2
+ require 'bigdecimal'
2
3
 
3
4
  describe 'indexing attribute fields', :type => :indexer do
4
5
  it 'should correctly index a stored string attribute field' do
@@ -72,41 +73,29 @@ describe 'indexing attribute fields', :type => :indexer do
72
73
 
73
74
  it 'should correctly index a boolean field' do
74
75
  session.index(post(:featured => true))
75
- connection.should have_add_with(:featured_b => 'true')
76
+ connection.should have_add_with(:featured_bs => 'true')
76
77
  end
77
78
 
78
79
  it 'should correctly index a false boolean field' do
79
80
  session.index(post(:featured => false))
80
- connection.should have_add_with(:featured_b => 'false')
81
+ connection.should have_add_with(:featured_bs => 'false')
81
82
  end
82
83
 
83
84
  it 'should not index a nil boolean field' do
84
85
  session.index(post)
85
- connection.should_not have_add_with(:featured_b)
86
+ connection.should_not have_add_with(:featured_bs)
86
87
  end
87
88
 
88
89
  it 'should index latitude and longitude as a pair' do
89
- session.index(post(:coordinates => [40.7, -73.5]))
90
- connection.should have_add_with(:lat => 40.7, :lng => -73.5)
90
+ session.index(post(:coordinates => Sunspot::Util::Coordinates.new(40.7, -73.5)))
91
+ connection.should have_add_with(:coordinates_s => 'dr5xx3nytvgs')
91
92
  end
92
93
 
93
- [
94
- [:lat, :lng],
95
- [:lat, :lon],
96
- [:lat, :long],
97
- [:latitude, :longitude]
98
- ].each do |lat_attr, lng_attr|
99
- it "should index latitude and longitude from #{lat_attr.inspect}, #{lng_attr.inspect}" do
100
- session.index(post(
101
- :coordinates => OpenStruct.new(lat_attr => 40.7, lng_attr => -73.5)
102
- ))
103
- connection.should have_add_with(:lat => 40.7, :lng => -73.5)
104
- end
105
- end
106
-
107
- it 'should index latitude and longitude from a block' do
108
- session.index(Photo.new(:lat => 30, :lng => -60))
109
- connection.should have_add_with(:lat => 30.0, :lng => -60.0)
94
+ it 'should index latitude and longitude passed as non-Floats' do
95
+ coordinates = Sunspot::Util::Coordinates.new(
96
+ BigDecimal.new('40.7'), BigDecimal.new('-73.5'))
97
+ session.index(post(:coordinates => coordinates))
98
+ connection.should have_add_with(:coordinates_s => 'dr5xx3nytvgs')
110
99
  end
111
100
 
112
101
  it 'should correctly index an attribute field with block access' do
@@ -146,4 +135,15 @@ describe 'indexing attribute fields', :type => :indexer do
146
135
  Sunspot.setup(Post) { integer :popularity, :more_like_this => true }
147
136
  end.should raise_error(ArgumentError)
148
137
  end
138
+
139
+ it 'should use a specified field name when the :as option is set' do
140
+ session.index(post(:title => 'A Title'))
141
+ connection.should have_add_with(:legacy_field_s => 'legacy A Title')
142
+ end
143
+
144
+ it 'should use a specified field name when the :as option is set for array values' do
145
+ session.index(post(:title => 'Another Title'))
146
+ connection.should have_add_with(:legacy_array_field_sm => ['first string', 'second string'])
147
+ end
149
148
  end
149
+
@@ -137,7 +137,7 @@ shared_examples_for "query with connective scope" do
137
137
  end
138
138
  end
139
139
  connection.should have_last_search_including(
140
- :fq, "-(id:Post\\ #{post.id} AND -category_ids_im:1)"
140
+ :fq, "-(id:(Post\\ #{post.id}) AND -category_ids_im:1)"
141
141
  )
142
142
  end
143
143
 
@@ -153,6 +153,19 @@ shared_examples_for "query with connective scope" do
153
153
  )
154
154
  end
155
155
 
156
+ it 'creates a disjunction with instance inclusion' do
157
+ post = Post.new
158
+ search do
159
+ any_of do
160
+ with(post)
161
+ with(:average_rating).greater_than(3.0)
162
+ end
163
+ end
164
+ connection.should have_last_search_including(
165
+ :fq, "(id:(Post\\ #{post.id}) OR average_rating_ft:[3\\.0 TO *])"
166
+ )
167
+ end
168
+
156
169
  it 'creates a disjunction with some text field components' do
157
170
  search do
158
171
  any_of do
@@ -70,8 +70,8 @@ shared_examples_for 'fulltext query' do
70
70
  subqueries(:q).last[:qf].split(' ').sort.should == %w(backwards_title_text body_textsv tags_textv title_text)
71
71
  end
72
72
 
73
- it 'puts automatic dismax parameters in subquery' do
74
- subqueries(:q).each { |subquery| subquery[:fl].should == '* score' }
73
+ it 'puts field list in main query' do
74
+ connection.should have_last_search_with(:fl => '* score')
75
75
  end
76
76
  end
77
77
 
@@ -243,7 +243,7 @@ shared_examples_for 'fulltext query' do
243
243
  connection.should have_last_search_with(
244
244
  :bq => [
245
245
  'average_rating_ft:[2\.0 TO *]^2.0',
246
- 'featured_b:true^1.5'
246
+ 'featured_bs:true^1.5'
247
247
  ]
248
248
  )
249
249
  end
@@ -0,0 +1,69 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+ require 'bigdecimal'
3
+
4
+ shared_examples_for 'geohash query' do
5
+ it 'searches for nearby points with defaults' do
6
+ search do
7
+ with(:coordinates).near(40.7, -73.5)
8
+ end
9
+ connection.should have_last_search_including(:q, build_geo_query)
10
+ end
11
+
12
+ it 'searches for nearby points with non-Float arguments' do
13
+ search do
14
+ with(:coordinates).near(BigDecimal.new('40.7'), BigDecimal.new('-73.5'))
15
+ end
16
+ connection.should have_last_search_including(:q, build_geo_query)
17
+ end
18
+
19
+ it 'searches for nearby points with given precision' do
20
+ search do
21
+ with(:coordinates).near(40.7, -73.5, :precision => 10)
22
+ end
23
+ connection.should have_last_search_including(:q, build_geo_query(:precision => 10))
24
+ end
25
+
26
+ it 'searches for nearby points with given precision factor' do
27
+ search do
28
+ with(:coordinates).near(40.7, -73.5, :precision_factor => 1.5)
29
+ end
30
+ connection.should have_last_search_including(:q, build_geo_query(:precision_factor => 1.5))
31
+ end
32
+
33
+ it 'searches for nearby points with given boost' do
34
+ search do
35
+ with(:coordinates).near(40.7, -73.5, :boost => 2.0)
36
+ end
37
+ connection.should have_last_search_including(:q, build_geo_query(:boost => 2.0))
38
+ end
39
+
40
+ it 'performs both dismax search and location search' do
41
+ search do
42
+ fulltext 'pizza', :fields => :title
43
+ with(:coordinates).near(40.7, -73.5)
44
+ end
45
+ expected =
46
+ "{!dismax fl='* score' qf='title_text'}pizza (#{build_geo_query})"
47
+ connection.should have_last_search_including(
48
+ :q,
49
+ %Q(_query_:"{!dismax qf='title_text'}pizza" (#{build_geo_query}))
50
+ )
51
+ end
52
+
53
+ private
54
+
55
+ def build_geo_query(options = {})
56
+ precision = options[:precision] || 7
57
+ precision_factor = options[:precision_factor] || 16.0
58
+ boost = options[:boost] || 1.0
59
+ hash = 'dr5xx3nytvgs'
60
+ (precision..12).map do |i|
61
+ phrase =
62
+ if i == 12 then hash
63
+ else "#{hash[0, i]}*"
64
+ end
65
+ precision_boost = Sunspot::Util.format_float(boost*precision_factor**(i-12.0), 3)
66
+ "coordinates_s:#{phrase}^#{precision_boost}"
67
+ end.reverse.join(' OR ')
68
+ end
69
+ end
@@ -39,7 +39,7 @@ shared_examples_for "scoped query" do
39
39
  search do
40
40
  with :featured, false
41
41
  end
42
- connection.should have_last_search_including(:fq, 'featured_b:false')
42
+ connection.should have_last_search_including(:fq, 'featured_bs:false')
43
43
  end
44
44
 
45
45
  it 'scopes by less than match with float' do
@@ -77,13 +77,6 @@ shared_examples_for "scoped query" do
77
77
  connection.should have_last_search_including(:fq, 'average_rating_ft:[2\.0 TO 4\.0]')
78
78
  end
79
79
 
80
- it 'automatically sorts ranges in between matches' do
81
- search do
82
- with(:blog_id).between(4..2)
83
- end
84
- connection.should have_last_search_including(:fq, 'blog_id_i:[2 TO 4]')
85
- end
86
-
87
80
  it 'scopes by any match with integer' do
88
81
  search do
89
82
  with(:category_ids).any_of [2, 7, 12]
@@ -175,12 +168,40 @@ shared_examples_for "scoped query" do
175
168
  connection.should have_last_search_including(:fq, 'average_rating_ft:[* TO *]')
176
169
  end
177
170
 
171
+ it 'includes by object identity' do
172
+ post = Post.new
173
+ search do
174
+ with post
175
+ end
176
+ connection.should have_last_search_including(:fq, "id:(Post\\ #{post.id})")
177
+ end
178
+
179
+ it 'includes multiple objects passed as varargs by object identity' do
180
+ post1, post2 = Post.new, Post.new
181
+ search do
182
+ with post1, post2
183
+ end
184
+ connection.should have_last_search_including(
185
+ :fq, "id:(Post\\ #{post1.id} OR Post\\ #{post2.id})"
186
+ )
187
+ end
188
+
189
+ it 'includes multiple objects passed as array by object identity' do
190
+ posts = [Post.new, Post.new]
191
+ search do
192
+ with posts
193
+ end
194
+ connection.should have_last_search_including(
195
+ :fq, "id:(Post\\ #{posts.first.id} OR Post\\ #{posts.last.id})"
196
+ )
197
+ end
198
+
178
199
  it 'excludes by object identity' do
179
200
  post = Post.new
180
201
  search do
181
202
  without post
182
203
  end
183
- connection.should have_last_search_including(:fq, "-id:Post\\ #{post.id}")
204
+ connection.should have_last_search_including(:fq, "-id:(Post\\ #{post.id})")
184
205
  end
185
206
 
186
207
  it 'excludes multiple objects passed as varargs by object identity' do
@@ -190,8 +211,7 @@ shared_examples_for "scoped query" do
190
211
  end
191
212
  connection.should have_last_search_including(
192
213
  :fq,
193
- "-id:Post\\ #{post1.id}",
194
- "-id:Post\\ #{post2.id}"
214
+ "-id:(Post\\ #{post1.id} OR Post\\ #{post2.id})"
195
215
  )
196
216
  end
197
217
 
@@ -202,8 +222,7 @@ shared_examples_for "scoped query" do
202
222
  end
203
223
  connection.should have_last_search_including(
204
224
  :fq,
205
- "-id:Post\\ #{posts.first.id}",
206
- "-id:Post\\ #{posts.last.id}"
225
+ "-id:(Post\\ #{posts.first.id} OR Post\\ #{posts.last.id})"
207
226
  )
208
227
  end
209
228
 
@@ -9,8 +9,8 @@ describe 'standard query', :type => :query do
9
9
  it_should_behave_like "fulltext query"
10
10
  it_should_behave_like "query with highlighting support"
11
11
  it_should_behave_like "sortable query"
12
- it_should_behave_like "spatial query"
13
12
  it_should_behave_like "query with text field scoping"
13
+ it_should_behave_like "geohash query"
14
14
 
15
15
  it 'adds a no-op query to :q parameter when no :q provided' do
16
16
  session.search Post do
@@ -128,7 +128,7 @@ describe 'faceting', :type => :search do
128
128
  end
129
129
 
130
130
  it 'returns boolean facet' do
131
- stub_facet(:featured_b, 'true' => 3, 'false' => 1)
131
+ stub_facet(:featured_bs, 'true' => 3, 'false' => 1)
132
132
  result = session.search(Post) { facet(:featured) }
133
133
  facet_values(result, :featured).should == [true, false]
134
134
  end
@@ -267,6 +267,10 @@ describe 'faceting', :type => :search do
267
267
  facet_values_from_options(:limit => 1).should == [2]
268
268
  end
269
269
 
270
+ it 'does not limit facets if limit option is negative' do
271
+ facet_values_from_options(:limit => -2).should == [1, 3, 2]
272
+ end
273
+
270
274
  it 'returns all facets if limit greater than number of facets' do
271
275
  facet_values_from_options(:limit => 10).should == [2, 1, 3]
272
276
  end
@@ -106,11 +106,22 @@ describe 'hits', :type => :search do
106
106
  session.search(Post, Namespaced::Comment).hits.first.stored(:title).should == 'Title'
107
107
  end
108
108
 
109
+ it 'should return stored field values for searches against base type when subtype matches' do
110
+ class SubclassedPost < Post; end;
111
+ stub_full_results('instance' => SubclassedPost.new, 'title_ss' => 'Title')
112
+ session.search(Post).hits.first.stored(:title).should == 'Title'
113
+ end
114
+
109
115
  it 'should return stored text fields' do
110
116
  stub_full_results('instance' => Post.new, 'body_textsv' => 'Body')
111
117
  session.search(Post, Namespaced::Comment).hits.first.stored(:body).should == 'Body'
112
118
  end
113
119
 
120
+ it 'should return stored boolean fields' do
121
+ stub_full_results('instance' => Post.new, 'featured_bs' => true)
122
+ session.search(Post, Namespaced::Comment).hits.first.stored(:featured).should be_true
123
+ end
124
+
114
125
  it 'should return stored dynamic fields' do
115
126
  stub_full_results('instance' => Post.new, 'custom_string:test_ss' => 'Custom')
116
127
  session.search(Post, Namespaced::Comment).hits.first.stored(:custom_string, :test).should == 'Custom'
@@ -122,17 +133,8 @@ describe 'hits', :type => :search do
122
133
  session.search(Post).hits.first.stored(:last_indexed_at).should == time
123
134
  end
124
135
 
125
- it 'should return geo distance' do
126
- post = Post.new
127
- stub_results(post)
128
- connection.response['distances'] = {
129
- "Post #{post.id}" => 1.23
130
- }
131
- session.search(Post).hits.first.distance.should == 1.23
132
- end
133
-
134
- it 'should return nil if no geo distance' do
135
- stub_results(Post.new)
136
- session.search(Post).hits.first.distance.should be_nil
136
+ it 'should return stored values for multi-valued fields' do
137
+ stub_full_results('instance' => User.new, 'role_ids_ims' => %w(1 4 5))
138
+ session.search(User).hits.first.stored(:role_ids).should == [1, 4, 5]
137
139
  end
138
140
  end
@@ -40,7 +40,7 @@ describe Sunspot::SessionProxy::ClassShardingSessionProxy do
40
40
  end
41
41
  end
42
42
 
43
- [:commit, :commit_if_dirty, :commit_if_delete_dirty].each do |method|
43
+ [:commit, :commit_if_dirty, :commit_if_delete_dirty, :optimize].each do |method|
44
44
  it "should delegate #{method} to all sessions" do
45
45
  [@proxy.post_session, @proxy.photo_session].each do |session|
46
46
  session.should_receive(method)
@@ -32,7 +32,7 @@ describe Sunspot::SessionProxy::ShardingSessionProxy do
32
32
  end
33
33
  end
34
34
 
35
- [:commit, :commit_if_dirty, :commit_if_delete_dirty].each do |method|
35
+ [:commit, :commit_if_dirty, :commit_if_delete_dirty, :optimize].each do |method|
36
36
  it "should delegate #{method} to all sessions" do
37
37
  @proxy.sessions.each do |session|
38
38
  session.should_receive(method)
@@ -0,0 +1,24 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe Sunspot::SessionProxy::ShardingSessionProxy do
4
+
5
+ FakeException = Class.new(StandardError)
6
+ SUPPORTED_METHODS = Sunspot::SessionProxy::SilentFailSessionProxy::SUPPORTED_METHODS
7
+
8
+ before do
9
+ @search_session = mock(Sunspot::Session.new)
10
+ @proxy = Sunspot::SessionProxy::SilentFailSessionProxy.new(@search_session)
11
+ end
12
+
13
+ it "should call rescued_exception when an exception is caught" do
14
+ SUPPORTED_METHODS.each do |method|
15
+ e = FakeException.new(method)
16
+ @search_session.stub!(method).and_raise(e)
17
+ @proxy.should_receive(:rescued_exception).with(method, e)
18
+ @proxy.send(method)
19
+ end
20
+ end
21
+
22
+ it_should_behave_like 'session proxy'
23
+
24
+ end
@@ -35,6 +35,16 @@ shared_examples_for 'all sessions' do
35
35
  end
36
36
  end
37
37
 
38
+ context '#optimize()' do
39
+ before :each do
40
+ @session.optimize
41
+ end
42
+
43
+ it 'should optimize' do
44
+ connection.should have(1).optims
45
+ end
46
+ end
47
+
38
48
  context '#search()' do
39
49
  before :each do
40
50
  @session.search(Post)
@@ -152,12 +162,24 @@ describe 'Session' do
152
162
  @session.dirty?.should be_false
153
163
  end
154
164
 
165
+ it 'should not be dirty after an optimize' do
166
+ @session.index(Post.new)
167
+ @session.optimize
168
+ @session.dirty?.should be_false
169
+ end
170
+
155
171
  it 'should not be delete_dirty after a commit' do
156
172
  @session.remove(Post.new)
157
173
  @session.commit
158
174
  @session.delete_dirty?.should be_false
159
175
  end
160
176
 
177
+ it 'should not be delete_dirty after an optimize' do
178
+ @session.remove(Post.new)
179
+ @session.optimize
180
+ @session.delete_dirty?.should be_false
181
+ end
182
+
161
183
  it 'should not commit when commit_if_dirty called on clean session' do
162
184
  @session.commit_if_dirty
163
185
  connection.should have(0).commits
@@ -1,91 +1,64 @@
1
1
  require File.join(File.dirname(__FILE__), 'spec_helper')
2
2
 
3
3
  describe 'local search' do
4
- ORIGIN = [40.6749113, -73.9648859]
4
+ ORIGIN = [40.7246062, -73.9969018]
5
+ LOCATIONS = [
6
+ [40.7246062, -73.9969018], # dr5rsjtn50yf
7
+ [40.724606, -73.996902], # dr5rsjtn50y9
8
+ [40.724606, -73.996901], # dr5rsjtn50z3
9
+ [40.72461, -73.996906] # dr5rsjtn51ec
10
+ ].map { |lat, lng| Sunspot::Util::Coordinates.new(lat, lng) }
11
+
5
12
  before :each do
6
13
  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, :distance => 20) }
21
- search.results.to_set.should == @posts[0..2].to_set
22
14
  end
23
15
 
24
- it 'should perform a radial search with fulltext matching' do
25
- search = Sunspot.search(Post) do |query|
26
- query.keywords 'teacup'
27
- query.near(ORIGIN, :distance => 20)
28
- end
29
- search.results.should == [@posts[1]]
30
- end
31
-
32
- it 'should use dismax for fulltext matching in local search' do
33
- lambda do
34
- search = Sunspot.new_search(Post)
35
- search.build do |query|
36
- query.keywords 'teacup['
37
- query.near(ORIGIN, :distance => 20)
16
+ describe 'without fulltext' do
17
+ before :each do
18
+ @posts = LOCATIONS.map do |location|
19
+ Post.new(:coordinates => location)
38
20
  end
39
- search.execute
40
- end.should_not raise_error
41
- end
21
+ Sunspot.index!(@posts)
22
+ @search = Sunspot.search(Post) do
23
+ with(:coordinates).near(ORIGIN[0], ORIGIN[1], :precision_factor => 4.0)
24
+ end
25
+ end
42
26
 
43
- it 'should perform a radial search with attribute scoping' do
44
- search = Sunspot.search(Post) do |query|
45
- query.near(ORIGIN, :distance => 20)
46
- query.with(:title, 'teacup')
27
+ it 'should return results in geo order' do
28
+ @search.results.should == @posts
47
29
  end
48
- search.results.should == [@posts[1]]
49
- end
50
30
 
51
- it 'should perform a radial search with attribute scoping and distance sorting' do
52
- search = Sunspot.search(Post) do |query|
53
- query.near(ORIGIN, :sort => true)
54
- query.with(:title, 'teacup')
31
+ it 'should asssign higher score to closer locations' do
32
+ hits = @search.hits
33
+ hits[1..-1].each_with_index do |hit, i|
34
+ hit.score.should < hits[i].score
35
+ end
55
36
  end
56
- search.results.should == [@posts[1], @posts[4]]
57
37
  end
58
38
 
59
- it 'should order by arbitrary field' do
60
- search = Sunspot.search(Post) do |query|
61
- query.near(ORIGIN, :distance => 20)
62
- query.order_by(:blog_id)
39
+ describe 'with fulltext' do
40
+ before :each do
41
+ @posts = [
42
+ Post.new(:title => 'pizza', :coordinates => LOCATIONS[0]),
43
+ Post.new(:title => 'pizza', :coordinates => LOCATIONS[1]),
44
+ Post.new(:title => 'pasta calzone pizza antipasti', :coordinates => LOCATIONS[1])
45
+ ]
46
+ Sunspot.index!(@posts)
47
+ @search = Sunspot.search(Post) do
48
+ keywords 'pizza'
49
+ with(:coordinates).near(ORIGIN[0], ORIGIN[1])
50
+ end
63
51
  end
64
- search.results.should == @posts[0..2].reverse
65
- end
66
52
 
67
- it 'should order by geo distance' do
68
- search = Sunspot.search(Post) do |query|
69
- query.near(ORIGIN, :distance => 20, :sort => true)
53
+ it 'should take both fulltext and distance into account in ordering' do
54
+ @search.results.should == @posts
70
55
  end
71
- search.results.should == @posts[0..2]
72
- end
73
56
 
74
- it 'should order by geo distance with fulltext' do
75
- lambda do
76
- search = Sunspot.search(Post) do |query|
77
- query.fulltext('teacup')
78
- query.near(ORIGIN, :sort => true)
57
+ it 'should take both fulltext and distance into account in scoring' do
58
+ hits = @search.hits
59
+ hits[1..-1].each_with_index do |hit, i|
60
+ hit.score.should < hits[i].score
79
61
  end
80
- end.should_not raise_error
81
- end
82
-
83
- it 'should return geographical distance from origin' do
84
- search = Sunspot.search(Post) do |query|
85
- query.near(ORIGIN, :sort => true)
86
- end
87
- search.hits.each do |hit|
88
- hit.distance.should_not be_nil
89
62
  end
90
63
  end
91
64
  end