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.
- data/Gemfile +10 -0
- data/Gemfile.lock +32 -0
- data/History.txt +24 -0
- data/README.rdoc +18 -5
- data/lib/sunspot.rb +40 -0
- data/lib/sunspot/dsl.rb +2 -2
- data/lib/sunspot/dsl/field_query.rb +2 -2
- data/lib/sunspot/dsl/fields.rb +0 -10
- data/lib/sunspot/dsl/restriction.rb +4 -4
- data/lib/sunspot/dsl/restriction_with_near.rb +121 -0
- data/lib/sunspot/dsl/scope.rb +55 -67
- data/lib/sunspot/dsl/standard_query.rb +11 -15
- data/lib/sunspot/field.rb +30 -29
- data/lib/sunspot/field_factory.rb +0 -18
- data/lib/sunspot/installer/solrconfig_updater.rb +0 -30
- data/lib/sunspot/query.rb +4 -3
- data/lib/sunspot/query/common_query.rb +2 -2
- data/lib/sunspot/query/composite_fulltext.rb +7 -2
- data/lib/sunspot/query/connective.rb +21 -6
- data/lib/sunspot/query/dismax.rb +1 -0
- data/lib/sunspot/query/geo.rb +53 -0
- data/lib/sunspot/query/more_like_this.rb +1 -0
- data/lib/sunspot/query/restriction.rb +5 -5
- data/lib/sunspot/query/standard_query.rb +0 -4
- data/lib/sunspot/search/abstract_search.rb +1 -7
- data/lib/sunspot/search/hit.rb +10 -10
- data/lib/sunspot/search/query_facet.rb +8 -3
- data/lib/sunspot/session.rb +10 -2
- data/lib/sunspot/session_proxy.rb +16 -0
- data/lib/sunspot/session_proxy/master_slave_session_proxy.rb +1 -1
- data/lib/sunspot/session_proxy/sharding_session_proxy.rb +7 -0
- data/lib/sunspot/session_proxy/silent_fail_session_proxy.rb +42 -0
- data/lib/sunspot/session_proxy/thread_local_session_proxy.rb +1 -1
- data/lib/sunspot/setup.rb +1 -17
- data/lib/sunspot/type.rb +38 -6
- data/lib/sunspot/util.rb +21 -31
- data/lib/sunspot/version.rb +1 -1
- data/solr/solr/conf/solrconfig.xml +0 -4
- data/spec/api/binding_spec.rb +12 -0
- data/spec/api/indexer/attributes_spec.rb +22 -22
- data/spec/api/query/connectives_examples.rb +14 -1
- data/spec/api/query/fulltext_examples.rb +3 -3
- data/spec/api/query/geo_examples.rb +69 -0
- data/spec/api/query/scope_examples.rb +32 -13
- data/spec/api/query/standard_spec.rb +1 -1
- data/spec/api/search/faceting_spec.rb +5 -1
- data/spec/api/search/hits_spec.rb +14 -12
- data/spec/api/session_proxy/class_sharding_session_proxy_spec.rb +1 -1
- data/spec/api/session_proxy/sharding_session_proxy_spec.rb +1 -1
- data/spec/api/session_proxy/silent_fail_session_proxy_spec.rb +24 -0
- data/spec/api/session_spec.rb +22 -0
- data/spec/integration/local_search_spec.rb +42 -69
- data/spec/integration/scoped_search_spec.rb +30 -0
- data/spec/mocks/connection.rb +6 -2
- data/spec/mocks/photo.rb +0 -1
- data/spec/mocks/post.rb +11 -2
- data/spec/mocks/user.rb +6 -1
- data/spec/spec_helper.rb +2 -12
- metadata +209 -177
- data/lib/sunspot/query/local.rb +0 -26
- data/solr/solr/lib/lucene-spatial-2.9.1.jar +0 -0
- data/solr/solr/lib/solr-spatial-light-0.0.6.jar +0 -0
- data/spec/api/query/local_examples.rb +0 -38
- data/tasks/gemspec.rake +0 -33
- data/tasks/rcov.rake +0 -28
- 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(:
|
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(:
|
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(:
|
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 =>
|
90
|
-
connection.should have_add_with(:
|
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
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
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
|
74
|
-
|
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
|
-
'
|
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, '
|
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(:
|
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
|
126
|
-
|
127
|
-
|
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
|
data/spec/api/session_spec.rb
CHANGED
@@ -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.
|
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
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
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
|
-
|
40
|
-
|
41
|
-
|
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
|
-
|
44
|
-
|
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
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
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
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
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
|
-
|
68
|
-
|
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
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
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
|