gojee-sunspot 2.0.3 → 2.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +12 -0
- data/Gemfile +5 -0
- data/History.txt +252 -0
- data/LICENSE +18 -0
- data/Rakefile +13 -0
- data/TODO +13 -0
- data/lib/light_config.rb +40 -0
- data/lib/sunspot/adapters.rb +265 -0
- data/lib/sunspot/batcher.rb +62 -0
- data/lib/sunspot/class_set.rb +23 -0
- data/lib/sunspot/composite_setup.rb +202 -0
- data/lib/sunspot/configuration.rb +53 -0
- data/lib/sunspot/data_extractor.rb +50 -0
- data/lib/sunspot/dsl/adjustable.rb +47 -0
- data/lib/sunspot/dsl/field_group.rb +57 -0
- data/lib/sunspot/dsl/field_query.rb +327 -0
- data/lib/sunspot/dsl/fields.rb +103 -0
- data/lib/sunspot/dsl/fulltext.rb +243 -0
- data/lib/sunspot/dsl/function.rb +27 -0
- data/lib/sunspot/dsl/functional.rb +44 -0
- data/lib/sunspot/dsl/more_like_this_query.rb +56 -0
- data/lib/sunspot/dsl/paginatable.rb +32 -0
- data/lib/sunspot/dsl/query_facet.rb +36 -0
- data/lib/sunspot/dsl/restriction.rb +25 -0
- data/lib/sunspot/dsl/restriction_with_near.rb +160 -0
- data/lib/sunspot/dsl/scope.rb +217 -0
- data/lib/sunspot/dsl/search.rb +30 -0
- data/lib/sunspot/dsl/standard_query.rb +123 -0
- data/lib/sunspot/dsl.rb +5 -0
- data/lib/sunspot/field.rb +193 -0
- data/lib/sunspot/field_factory.rb +129 -0
- data/lib/sunspot/indexer.rb +136 -0
- data/lib/sunspot/query/abstract_field_facet.rb +52 -0
- data/lib/sunspot/query/bbox.rb +15 -0
- data/lib/sunspot/query/boost_query.rb +24 -0
- data/lib/sunspot/query/common_query.rb +96 -0
- data/lib/sunspot/query/composite_fulltext.rb +36 -0
- data/lib/sunspot/query/connective.rb +206 -0
- data/lib/sunspot/query/date_field_facet.rb +14 -0
- data/lib/sunspot/query/dismax.rb +132 -0
- data/lib/sunspot/query/field_facet.rb +41 -0
- data/lib/sunspot/query/field_group.rb +36 -0
- data/lib/sunspot/query/filter.rb +38 -0
- data/lib/sunspot/query/function_query.rb +52 -0
- data/lib/sunspot/query/geo.rb +53 -0
- data/lib/sunspot/query/geofilt.rb +16 -0
- data/lib/sunspot/query/highlighting.rb +62 -0
- data/lib/sunspot/query/more_like_this.rb +61 -0
- data/lib/sunspot/query/more_like_this_query.rb +12 -0
- data/lib/sunspot/query/pagination.rb +42 -0
- data/lib/sunspot/query/query_facet.rb +16 -0
- data/lib/sunspot/query/restriction.rb +262 -0
- data/lib/sunspot/query/scope.rb +9 -0
- data/lib/sunspot/query/sort.rb +109 -0
- data/lib/sunspot/query/sort_composite.rb +34 -0
- data/lib/sunspot/query/standard_query.rb +16 -0
- data/lib/sunspot/query/text_field_boost.rb +17 -0
- data/lib/sunspot/query.rb +11 -0
- data/lib/sunspot/schema.rb +151 -0
- data/lib/sunspot/search/abstract_search.rb +281 -0
- data/lib/sunspot/search/date_facet.rb +35 -0
- data/lib/sunspot/search/facet_row.rb +27 -0
- data/lib/sunspot/search/field_facet.rb +88 -0
- data/lib/sunspot/search/field_group.rb +32 -0
- data/lib/sunspot/search/group.rb +50 -0
- data/lib/sunspot/search/highlight.rb +38 -0
- data/lib/sunspot/search/hit.rb +150 -0
- data/lib/sunspot/search/hit_enumerable.rb +72 -0
- data/lib/sunspot/search/more_like_this_search.rb +31 -0
- data/lib/sunspot/search/paginated_collection.rb +57 -0
- data/lib/sunspot/search/query_facet.rb +67 -0
- data/lib/sunspot/search/standard_search.rb +21 -0
- data/lib/sunspot/search.rb +9 -0
- data/lib/sunspot/session.rb +262 -0
- data/lib/sunspot/session_proxy/abstract_session_proxy.rb +29 -0
- data/lib/sunspot/session_proxy/class_sharding_session_proxy.rb +66 -0
- data/lib/sunspot/session_proxy/id_sharding_session_proxy.rb +89 -0
- data/lib/sunspot/session_proxy/master_slave_session_proxy.rb +43 -0
- data/lib/sunspot/session_proxy/multicore_session_proxy.rb +67 -0
- data/lib/sunspot/session_proxy/sharding_session_proxy.rb +222 -0
- data/lib/sunspot/session_proxy/silent_fail_session_proxy.rb +42 -0
- data/lib/sunspot/session_proxy/thread_local_session_proxy.rb +37 -0
- data/lib/sunspot/session_proxy.rb +95 -0
- data/lib/sunspot/setup.rb +350 -0
- data/lib/sunspot/text_field_setup.rb +29 -0
- data/lib/sunspot/type.rb +393 -0
- data/lib/sunspot/util.rb +252 -0
- data/lib/sunspot/version.rb +3 -0
- data/lib/sunspot.rb +579 -0
- data/log/.gitignore +1 -0
- data/pkg/.gitignore +1 -0
- data/script/console +10 -0
- data/spec/api/adapters_spec.rb +33 -0
- data/spec/api/batcher_spec.rb +112 -0
- data/spec/api/binding_spec.rb +50 -0
- data/spec/api/class_set_spec.rb +24 -0
- data/spec/api/hit_enumerable_spec.rb +47 -0
- data/spec/api/indexer/attributes_spec.rb +149 -0
- data/spec/api/indexer/batch_spec.rb +72 -0
- data/spec/api/indexer/dynamic_fields_spec.rb +42 -0
- data/spec/api/indexer/fixed_fields_spec.rb +57 -0
- data/spec/api/indexer/fulltext_spec.rb +43 -0
- data/spec/api/indexer/removal_spec.rb +53 -0
- data/spec/api/indexer/spec_helper.rb +1 -0
- data/spec/api/indexer_spec.rb +14 -0
- data/spec/api/query/advanced_manipulation_examples.rb +35 -0
- data/spec/api/query/connectives_examples.rb +189 -0
- data/spec/api/query/dsl_spec.rb +18 -0
- data/spec/api/query/dynamic_fields_examples.rb +165 -0
- data/spec/api/query/faceting_examples.rb +397 -0
- data/spec/api/query/fulltext_examples.rb +313 -0
- data/spec/api/query/function_spec.rb +79 -0
- data/spec/api/query/geo_examples.rb +68 -0
- data/spec/api/query/group_spec.rb +32 -0
- data/spec/api/query/highlighting_examples.rb +245 -0
- data/spec/api/query/more_like_this_spec.rb +140 -0
- data/spec/api/query/ordering_pagination_examples.rb +116 -0
- data/spec/api/query/scope_examples.rb +275 -0
- data/spec/api/query/spatial_examples.rb +27 -0
- data/spec/api/query/spec_helper.rb +1 -0
- data/spec/api/query/standard_spec.rb +29 -0
- data/spec/api/query/text_field_scoping_examples.rb +30 -0
- data/spec/api/query/types_spec.rb +20 -0
- data/spec/api/search/dynamic_fields_spec.rb +33 -0
- data/spec/api/search/faceting_spec.rb +360 -0
- data/spec/api/search/highlighting_spec.rb +69 -0
- data/spec/api/search/hits_spec.rb +131 -0
- data/spec/api/search/paginated_collection_spec.rb +36 -0
- data/spec/api/search/results_spec.rb +72 -0
- data/spec/api/search/search_spec.rb +23 -0
- data/spec/api/search/spec_helper.rb +1 -0
- data/spec/api/session_proxy/class_sharding_session_proxy_spec.rb +85 -0
- data/spec/api/session_proxy/id_sharding_session_proxy_spec.rb +30 -0
- data/spec/api/session_proxy/master_slave_session_proxy_spec.rb +41 -0
- data/spec/api/session_proxy/sharding_session_proxy_spec.rb +77 -0
- data/spec/api/session_proxy/silent_fail_session_proxy_spec.rb +24 -0
- data/spec/api/session_proxy/spec_helper.rb +9 -0
- data/spec/api/session_proxy/thread_local_session_proxy_spec.rb +39 -0
- data/spec/api/session_spec.rb +232 -0
- data/spec/api/spec_helper.rb +3 -0
- data/spec/api/sunspot_spec.rb +29 -0
- data/spec/ext.rb +11 -0
- data/spec/helpers/indexer_helper.rb +17 -0
- data/spec/helpers/integration_helper.rb +8 -0
- data/spec/helpers/mock_session_helper.rb +13 -0
- data/spec/helpers/query_helper.rb +26 -0
- data/spec/helpers/search_helper.rb +68 -0
- data/spec/integration/dynamic_fields_spec.rb +57 -0
- data/spec/integration/faceting_spec.rb +251 -0
- data/spec/integration/field_grouping_spec.rb +66 -0
- data/spec/integration/geospatial_spec.rb +85 -0
- data/spec/integration/highlighting_spec.rb +44 -0
- data/spec/integration/indexing_spec.rb +55 -0
- data/spec/integration/keyword_search_spec.rb +317 -0
- data/spec/integration/local_search_spec.rb +64 -0
- data/spec/integration/more_like_this_spec.rb +43 -0
- data/spec/integration/scoped_search_spec.rb +354 -0
- data/spec/integration/stored_fields_spec.rb +12 -0
- data/spec/integration/test_pagination.rb +43 -0
- data/spec/integration/unicode_spec.rb +15 -0
- data/spec/mocks/adapters.rb +32 -0
- data/spec/mocks/blog.rb +3 -0
- data/spec/mocks/comment.rb +21 -0
- data/spec/mocks/connection.rb +126 -0
- data/spec/mocks/mock_adapter.rb +30 -0
- data/spec/mocks/mock_class_sharding_session_proxy.rb +24 -0
- data/spec/mocks/mock_record.rb +52 -0
- data/spec/mocks/mock_sharding_session_proxy.rb +15 -0
- data/spec/mocks/photo.rb +11 -0
- data/spec/mocks/post.rb +86 -0
- data/spec/mocks/super_class.rb +2 -0
- data/spec/mocks/user.rb +13 -0
- data/spec/spec_helper.rb +40 -0
- data/sunspot.gemspec +42 -0
- data/tasks/rdoc.rake +27 -0
- data/tasks/schema.rake +19 -0
- data/tasks/todo.rake +4 -0
- metadata +261 -3
@@ -0,0 +1,360 @@
|
|
1
|
+
require File.expand_path('spec_helper', File.dirname(__FILE__))
|
2
|
+
|
3
|
+
describe 'faceting', :type => :search do
|
4
|
+
it 'returns field name for facet' do
|
5
|
+
stub_facet(:title_ss, {})
|
6
|
+
result = session.search Post do
|
7
|
+
facet :title
|
8
|
+
end
|
9
|
+
result.facet(:title).field_name.should == :title
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'returns facet specified by string' do
|
13
|
+
stub_facet(:title_ss, {})
|
14
|
+
result = session.search Post do
|
15
|
+
facet :title
|
16
|
+
end
|
17
|
+
result.facet('title').field_name.should == :title
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'returns all facets specified by search' do
|
21
|
+
stub_facet(:title_ss, { 'Author 1' => 1 })
|
22
|
+
stub_facet(:blog_id_i, { '1' => 3 })
|
23
|
+
result = session.search(Post) do
|
24
|
+
facet :title
|
25
|
+
facet :blog_id
|
26
|
+
end
|
27
|
+
result.facets.first.field_name.should == :title
|
28
|
+
result.facets.last.field_name.should == :blog_id
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'returns string facet' do
|
32
|
+
stub_facet(:title_ss, 'Author 1' => 2, 'Author 2' => 1)
|
33
|
+
result = session.search Post do
|
34
|
+
facet :title
|
35
|
+
end
|
36
|
+
facet_values(result, :title).should == ['Author 1', 'Author 2']
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'returns counts for facet' do
|
40
|
+
stub_facet(:title_ss, 'Author 1' => 2, 'Author 2' => 1)
|
41
|
+
result = session.search Post do
|
42
|
+
facet :title
|
43
|
+
end
|
44
|
+
facet_counts(result, :title).should == [2, 1]
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'returns integer facet' do
|
48
|
+
stub_facet(:blog_id_i, '3' => 2, '1' => 1)
|
49
|
+
result = session.search Post do
|
50
|
+
facet :blog_id
|
51
|
+
end
|
52
|
+
facet_values(result, :blog_id).should == [3, 1]
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'returns float facet' do
|
56
|
+
stub_facet(:average_rating_ft, '9.3' => 2, '1.1' => 1)
|
57
|
+
result = session.search Post do
|
58
|
+
facet :average_rating
|
59
|
+
end
|
60
|
+
facet_values(result, :average_rating).should == [9.3, 1.1]
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'returns time facet' do
|
64
|
+
stub_facet(
|
65
|
+
:published_at_dt,
|
66
|
+
'2009-04-07T20:25:23Z' => 3,
|
67
|
+
'2009-04-07T20:26:19Z' => 2,
|
68
|
+
'2050-04-07T20:27:15Z' => 1
|
69
|
+
)
|
70
|
+
result = session.search Post do
|
71
|
+
facet :published_at
|
72
|
+
end
|
73
|
+
# In JRuby, Time doesn't have 32-bit range constraint, apparently.
|
74
|
+
future_time =
|
75
|
+
begin
|
76
|
+
Time.gm(2050, 4, 7, 20, 27, 15)
|
77
|
+
rescue ArgumentError
|
78
|
+
DateTime.civil(2050, 4, 7, 20, 27, 15)
|
79
|
+
end
|
80
|
+
facet_values(result, :published_at).should ==
|
81
|
+
[Time.gm(2009, 4, 7, 20, 25, 23),
|
82
|
+
Time.gm(2009, 4, 7, 20, 26, 19),
|
83
|
+
future_time]
|
84
|
+
end
|
85
|
+
|
86
|
+
it 'returns date facet' do
|
87
|
+
stub_facet(
|
88
|
+
:expire_date_d,
|
89
|
+
'2009-07-13T00:00:00Z' => 3,
|
90
|
+
'2009-04-01T00:00:00Z' => 1
|
91
|
+
)
|
92
|
+
result = session.search(Post) do
|
93
|
+
facet :expire_date
|
94
|
+
end
|
95
|
+
facet_values(result, :expire_date).should ==
|
96
|
+
[Date.new(2009, 07, 13),
|
97
|
+
Date.new(2009, 04, 01)]
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'returns trie integer facet' do
|
101
|
+
stub_facet(:size_it, '3' => 2, '1' => 1)
|
102
|
+
result = session.search Photo do
|
103
|
+
facet :size
|
104
|
+
end
|
105
|
+
facet_values(result, :size).should == [3, 1]
|
106
|
+
end
|
107
|
+
|
108
|
+
it 'returns float facet' do
|
109
|
+
stub_facet(:average_rating_ft, '9.3' => 2, '1.1' => 1)
|
110
|
+
result = session.search Photo do
|
111
|
+
facet :average_rating
|
112
|
+
end
|
113
|
+
facet_values(result, :average_rating).should == [9.3, 1.1]
|
114
|
+
end
|
115
|
+
|
116
|
+
it 'returns time facet' do
|
117
|
+
stub_facet(
|
118
|
+
:created_at_dt,
|
119
|
+
'2009-04-07T20:25:23Z' => 3,
|
120
|
+
'2009-04-07T20:26:19Z' => 1
|
121
|
+
)
|
122
|
+
result = session.search Photo do
|
123
|
+
facet :created_at
|
124
|
+
end
|
125
|
+
facet_values(result, :created_at).should ==
|
126
|
+
[Time.gm(2009, 04, 07, 20, 25, 23),
|
127
|
+
Time.gm(2009, 04, 07, 20, 26, 19)]
|
128
|
+
end
|
129
|
+
|
130
|
+
it 'returns boolean facet' do
|
131
|
+
stub_facet(:featured_bs, 'true' => 3, 'false' => 1)
|
132
|
+
result = session.search(Post) { facet(:featured) }
|
133
|
+
facet_values(result, :featured).should == [true, false]
|
134
|
+
end
|
135
|
+
|
136
|
+
|
137
|
+
{ 'string' => 'blog', 'symbol' => :blog }.each_pair do |type, name|
|
138
|
+
it "returns field facet with #{type} custom name" do
|
139
|
+
stub_facet(:blog, '2' => 1, '1' => 4)
|
140
|
+
result = session.search(Post) { facet(:blog_id, :name => name) }
|
141
|
+
facet_values(result, :blog).should == [1, 2]
|
142
|
+
end
|
143
|
+
|
144
|
+
it "assigns #{type} custom name to field facet" do
|
145
|
+
stub_facet(:blog, '2' => 1)
|
146
|
+
result = session.search(Post) { facet(:blog_id, :name => name) }
|
147
|
+
result.facet(:blog).name.should == :blog
|
148
|
+
end
|
149
|
+
|
150
|
+
it "retains field name for #{type} custom-named field facet" do
|
151
|
+
stub_facet(:blog, '2' => 1)
|
152
|
+
result = session.search(Post) { facet(:blog_id, :name => name) }
|
153
|
+
result.facet(:blog).field_name.should == :blog_id
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
it 'returns class facet' do
|
158
|
+
stub_facet(:class_name, 'Post' => 3, 'Namespaced::Comment' => 1)
|
159
|
+
result = session.search(Post) { facet(:class) }
|
160
|
+
facet_values(result, :class).should == [Post, Namespaced::Comment]
|
161
|
+
end
|
162
|
+
|
163
|
+
it 'returns special :any facet' do
|
164
|
+
stub_query_facet(
|
165
|
+
'category_ids_im:[* TO *]' => 3
|
166
|
+
)
|
167
|
+
search = session.search(Post) { facet(:category_ids, :extra => :any) }
|
168
|
+
row = search.facet(:category_ids).rows.first
|
169
|
+
row.value.should == :any
|
170
|
+
row.count.should == 3
|
171
|
+
end
|
172
|
+
|
173
|
+
it 'returns special :none facet' do
|
174
|
+
stub_query_facet(
|
175
|
+
'-category_ids_im:[* TO *]' => 3
|
176
|
+
)
|
177
|
+
search = session.search(Post) { facet(:category_ids, :extra => :none) }
|
178
|
+
row = search.facet(:category_ids).rows.first
|
179
|
+
row.value.should == :none
|
180
|
+
row.count.should == 3
|
181
|
+
end
|
182
|
+
|
183
|
+
it 'returns date range facet' do
|
184
|
+
stub_date_facet(:published_at_dt, 60*60*24, '2009-07-08T04:00:00Z' => 2, '2009-07-07T04:00:00Z' => 1)
|
185
|
+
start_time = Time.utc(2009, 7, 7, 4)
|
186
|
+
end_time = start_time + 2*24*60*60
|
187
|
+
result = session.search(Post) { facet(:published_at, :time_range => start_time..end_time) }
|
188
|
+
facet = result.facet(:published_at)
|
189
|
+
facet.rows.first.value.should == (start_time..(start_time+24*60*60))
|
190
|
+
facet.rows.last.value.should == ((start_time+24*60*60)..end_time)
|
191
|
+
end
|
192
|
+
|
193
|
+
it 'returns date range facet sorted by count' do
|
194
|
+
stub_date_facet(:published_at_dt, 60*60*24, '2009-07-08T04:00:00Z' => 2, '2009-07-07T04:00:00Z' => 1)
|
195
|
+
start_time = Time.utc(2009, 7, 7, 4)
|
196
|
+
end_time = start_time + 2*24*60*60
|
197
|
+
result = session.search(Post) { facet(:published_at, :time_range => start_time..end_time, :sort => :count) }
|
198
|
+
facet = result.facet(:published_at)
|
199
|
+
facet.rows.first.value.should == ((start_time+24*60*60)..end_time)
|
200
|
+
facet.rows.last.value.should == (start_time..(start_time+24*60*60))
|
201
|
+
end
|
202
|
+
|
203
|
+
it 'returns query facet' do
|
204
|
+
stub_query_facet(
|
205
|
+
'average_rating_ft:[3\.0 TO 5\.0]' => 3,
|
206
|
+
'average_rating_ft:[1\.0 TO 3\.0]' => 1
|
207
|
+
)
|
208
|
+
search = session.search(Post) do
|
209
|
+
facet :average_rating do
|
210
|
+
row 3.0..5.0 do
|
211
|
+
with :average_rating, 3.0..5.0
|
212
|
+
end
|
213
|
+
row 1.0..3.0 do
|
214
|
+
with :average_rating, 1.0..3.0
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
facet = search.facet(:average_rating)
|
219
|
+
facet.rows.first.value.should == (3.0..5.0)
|
220
|
+
facet.rows.first.count.should == 3
|
221
|
+
facet.rows.last.value.should == (1.0..3.0)
|
222
|
+
facet.rows.last.count.should == 1
|
223
|
+
end
|
224
|
+
|
225
|
+
describe 'query facet option handling' do
|
226
|
+
def facet_values_from_options(options = {})
|
227
|
+
session.search(Post) do
|
228
|
+
facet :average_rating, options do
|
229
|
+
row(1) { with(:average_rating, 1.0..2.0) }
|
230
|
+
row(3) { with(:average_rating, 3.0..4.0) }
|
231
|
+
row(2) { with(:average_rating, 2.0..3.0) }
|
232
|
+
row(4) { with(:average_rating, 4.0..5.0) }
|
233
|
+
end
|
234
|
+
end.facet(:average_rating).rows.map { |row| row.value }
|
235
|
+
end
|
236
|
+
|
237
|
+
before :each do
|
238
|
+
stub_query_facet(
|
239
|
+
'average_rating_ft:[1\.0 TO 2\.0]' => 2,
|
240
|
+
'average_rating_ft:[2\.0 TO 3\.0]' => 3,
|
241
|
+
'average_rating_ft:[3\.0 TO 4\.0]' => 1,
|
242
|
+
'average_rating_ft:[4\.0 TO 5\.0]' => 0
|
243
|
+
)
|
244
|
+
end
|
245
|
+
|
246
|
+
it 'sorts in order of specification if no limit is given' do
|
247
|
+
facet_values_from_options.should == [1, 3, 2]
|
248
|
+
end
|
249
|
+
|
250
|
+
it 'sorts lexically if lexical option is specified' do
|
251
|
+
facet_values_from_options(:sort => :index).should == [1, 2, 3]
|
252
|
+
end
|
253
|
+
|
254
|
+
it 'sorts by count by default if limit is given' do
|
255
|
+
facet_values_from_options(:limit => 2).should == [2, 1]
|
256
|
+
end
|
257
|
+
|
258
|
+
it 'sorts by count if count option is specified' do
|
259
|
+
facet_values_from_options(:sort => :count).should == [2, 1, 3]
|
260
|
+
end
|
261
|
+
|
262
|
+
it 'sorts lexically if lexical option is specified even if limit is given' do
|
263
|
+
facet_values_from_options(:sort => :index, :limit => 2).should == [1, 2]
|
264
|
+
end
|
265
|
+
|
266
|
+
it 'limits facets if limit option is given' do
|
267
|
+
facet_values_from_options(:limit => 1).should == [2]
|
268
|
+
end
|
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
|
+
|
274
|
+
it 'returns all facets if limit greater than number of facets' do
|
275
|
+
facet_values_from_options(:limit => 10).should == [2, 1, 3]
|
276
|
+
end
|
277
|
+
|
278
|
+
it 'allows zero count if specified' do
|
279
|
+
facet_values_from_options(:zeros => true).should == [1, 3, 2, 4]
|
280
|
+
end
|
281
|
+
|
282
|
+
it 'sets minimum count' do
|
283
|
+
facet_values_from_options(:minimum_count => 2).should == [1, 2]
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
it 'returns limited field facet' do
|
288
|
+
stub_query_facet(
|
289
|
+
'category_ids_im:1' => 3,
|
290
|
+
'category_ids_im:3' => 1
|
291
|
+
)
|
292
|
+
search = session.search(Post) do
|
293
|
+
facet :category_ids, :only => [1, 3, 5]
|
294
|
+
end
|
295
|
+
facet = search.facet(:category_ids)
|
296
|
+
facet.rows.first.value.should == 1
|
297
|
+
facet.rows.first.count.should == 3
|
298
|
+
facet.rows.last.value.should == 3
|
299
|
+
facet.rows.last.count.should == 1
|
300
|
+
end
|
301
|
+
|
302
|
+
it 'returns instantiated facet values' do
|
303
|
+
blogs = Array.new(2) { Blog.new }
|
304
|
+
stub_facet(:blog_id_i, blogs[0].id.to_s => 2, blogs[1].id.to_s => 1)
|
305
|
+
search = session.search(Post) { facet(:blog_id) }
|
306
|
+
search.facet(:blog_id).rows.map { |row| row.instance }.should == blogs
|
307
|
+
end
|
308
|
+
|
309
|
+
it 'returns all instantiated facet rows, whether or not the instances exist' do
|
310
|
+
blogs = Array.new(2) { Blog.new }
|
311
|
+
blogs.last.destroy
|
312
|
+
stub_facet(:blog_id_i, blogs[0].id.to_s => 2, blogs[1].id.to_s => 1)
|
313
|
+
search = session.search(Post) { facet(:blog_id) }
|
314
|
+
search.facet(:blog_id).rows.map { |row| row.instance }.should == [blogs.first, nil]
|
315
|
+
end
|
316
|
+
|
317
|
+
it 'returns only rows with available instances if specified' do
|
318
|
+
blogs = Array.new(2) { Blog.new }
|
319
|
+
blogs.last.destroy
|
320
|
+
stub_facet(:blog_id_i, blogs[0].id.to_s => 2, blogs[1].id.to_s => 1)
|
321
|
+
search = session.search(Post) { facet(:blog_id) }
|
322
|
+
search.facet(:blog_id).rows(:verify => true).map { |row| row.instance }.should == blogs[0..0]
|
323
|
+
end
|
324
|
+
|
325
|
+
it 'returns both verified and unverified rows from the same facet' do
|
326
|
+
blogs = Array.new(2) { Blog.new }
|
327
|
+
blogs.last.destroy
|
328
|
+
stub_facet(:blog_id_i, blogs[0].id.to_s => 2, blogs[1].id.to_s => 1)
|
329
|
+
search = session.search(Post) { facet(:blog_id) }
|
330
|
+
search.facet(:blog_id).rows(:verify => true).map { |row| row.instance }.should == blogs[0..0]
|
331
|
+
search.facet(:blog_id).rows.map { |row| row.instance }.should == [blogs.first, nil]
|
332
|
+
end
|
333
|
+
|
334
|
+
it 'ignores :verify option if facet not a reference facet' do
|
335
|
+
stub_facet(:category_ids_im, '1' => 2, '2' => 1)
|
336
|
+
search = session.search(Post) { facet(:category_ids) }
|
337
|
+
search.facet(:category_ids).should have(2).rows(:verify => true)
|
338
|
+
end
|
339
|
+
|
340
|
+
it 'returns instantiated facet values for limited field facet' do
|
341
|
+
blogs = Array.new(2) { Blog.new }
|
342
|
+
stub_query_facet(
|
343
|
+
"blog_id_i:#{blogs[0].id}" => 3,
|
344
|
+
"blog_id_i:#{blogs[1].id}" => 1
|
345
|
+
)
|
346
|
+
search = session.search(Post) do
|
347
|
+
facet(:blog_id, :only => blogs.map { |blog| blog.id })
|
348
|
+
end
|
349
|
+
search.facet(:blog_id).rows.map { |row| row.instance }.should == blogs
|
350
|
+
end
|
351
|
+
|
352
|
+
it 'only queries the persistent store once for an instantiated facet' do
|
353
|
+
query_count = Blog.query_count
|
354
|
+
blogs = Array.new(2) { Blog.new }
|
355
|
+
stub_facet(:blog_id_i, blogs[0].id.to_s => 2, blogs[1].id.to_s => 1)
|
356
|
+
result = session.search(Post) { facet(:blog_id) }
|
357
|
+
result.facet(:blog_id).rows.each { |row| row.instance }
|
358
|
+
(Blog.query_count - query_count).should == 1
|
359
|
+
end
|
360
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require File.expand_path('spec_helper', File.dirname(__FILE__))
|
2
|
+
|
3
|
+
describe 'search with highlighting results', :type => :search do
|
4
|
+
before :each do
|
5
|
+
@posts = Array.new(2) { Post.new }
|
6
|
+
stub_results_with_highlighting(
|
7
|
+
@posts[0],
|
8
|
+
{ 'title_text' => ['one @@@hl@@@two@@@endhl@@@ three'] },
|
9
|
+
@posts[1],
|
10
|
+
{ 'title_text' => ['three four @@@hl@@@five@@@endhl@@@'],
|
11
|
+
'body_text' => ['@@@hl@@@five@@@ six seven', '@@@hl@@@eight@@@endhl@@@ nine @@@hl@@@ten@@@endhl@@@'] }
|
12
|
+
)
|
13
|
+
@search = session.search(Post)
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'returns all highlights' do
|
17
|
+
@search.hits.last.should have(3).highlights
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'returns all highlights for a specified field' do
|
21
|
+
@search.hits.last.should have(2).highlights(:body)
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'returns first highlight for a specified field' do
|
25
|
+
@search.hits.first.highlight(:title).format.should == 'one <em>two</em> three'
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'returns an empty array if a given field does not have a highlight' do
|
29
|
+
@search.hits.first.highlights(:body).should == []
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'formats hits with <em> by default' do
|
33
|
+
highlight = @search.hits.first.highlights(:title).first.formatted
|
34
|
+
highlight.should == 'one <em>two</em> three'
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'formats hits with provided block' do
|
38
|
+
highlight = @search.hits.first.highlights(:title).first.format do |word|
|
39
|
+
"<i>#{word}</i>"
|
40
|
+
end
|
41
|
+
highlight.should == 'one <i>two</i> three'
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'handles multiple highlighted words' do
|
45
|
+
highlight = @search.hits.last.highlights(:body).last.format do |word|
|
46
|
+
"<b>#{word}</b>"
|
47
|
+
end
|
48
|
+
highlight.should == '<b>eight</b> nine <b>ten</b>'
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def stub_results_with_highlighting(*instances_and_highlights)
|
54
|
+
docs, highlights = [], []
|
55
|
+
instances_and_highlights.each_slice(2) do |doc, highlight|
|
56
|
+
docs << doc
|
57
|
+
highlights << highlight
|
58
|
+
end
|
59
|
+
response = stub_full_results(*docs.map { |doc| { 'instance' => doc }})
|
60
|
+
highlighting = response['highlighting'] = {}
|
61
|
+
highlights.each_with_index do |highlight, i|
|
62
|
+
if highlight
|
63
|
+
instance = docs[i]
|
64
|
+
highlighting["#{instance.class.name} #{instance.id}"] = highlight
|
65
|
+
end
|
66
|
+
end
|
67
|
+
response
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
require File.expand_path('spec_helper', File.dirname(__FILE__))
|
2
|
+
|
3
|
+
describe 'hits', :type => :search do
|
4
|
+
it 'should return hits without loading instances' do
|
5
|
+
post_1, post_2 = Array.new(2) { Post.new }
|
6
|
+
stub_results(post_1, post_2)
|
7
|
+
%w(load load_all).each do |message|
|
8
|
+
MockAdapter::DataAccessor.should_not_receive(message)
|
9
|
+
end
|
10
|
+
session.search(Post).hits.map do |hit|
|
11
|
+
[hit.class_name, hit.primary_key]
|
12
|
+
end.should == [['Post', post_1.id.to_s], ['Post', post_2.id.to_s]]
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'returns search total as attribute of hits' do
|
16
|
+
stub_results(Post.new, 4)
|
17
|
+
session.search(Post) do
|
18
|
+
paginate(:page => 1)
|
19
|
+
end.hits.total_entries.should == 4
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'returns search total as attribute of verified hits' do
|
23
|
+
stub_results(Post.new, 4)
|
24
|
+
session.search(Post) do
|
25
|
+
paginate(:page => 1)
|
26
|
+
end.hits(:verify => true).total_entries.should == 4
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'should return instance from hit' do
|
30
|
+
posts = Array.new(2) { Post.new }
|
31
|
+
stub_results(*posts)
|
32
|
+
session.search(Post).hits.first.instance.should == posts.first
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'should return the instance primary key when you use it as a param' do
|
36
|
+
posts = Array.new(2) { Post.new }
|
37
|
+
stub_results(*posts)
|
38
|
+
session.search(Post).hits.first.to_param.should == posts.first.id.to_s
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'should provide iterator over hits with instances' do
|
42
|
+
posts = Array.new(2) { Post.new }
|
43
|
+
stub_results(*posts)
|
44
|
+
search = session.search(Post)
|
45
|
+
hits, results = [], []
|
46
|
+
search.each_hit_with_result do |hit, result|
|
47
|
+
hits << hit
|
48
|
+
results << result
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'should hydrate all hits when an instance is requested from a hit' do
|
53
|
+
posts = Array.new(2) { Post.new }
|
54
|
+
stub_results(*posts)
|
55
|
+
search = session.search(Post)
|
56
|
+
search.hits.first.instance
|
57
|
+
%w(load load_all).each do |message|
|
58
|
+
MockAdapter::DataAccessor.should_not_receive(message)
|
59
|
+
end
|
60
|
+
search.hits.last.instance.should == posts.last
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'should return only hits whose referenced object exists in the data store if :verify option passed' do
|
64
|
+
posts = Array.new(2) { Post.new }
|
65
|
+
posts.last.destroy
|
66
|
+
stub_results(*posts)
|
67
|
+
search = session.search(Post)
|
68
|
+
search.hits(:verify => true).map { |hit| hit.instance }.should == posts[0..0]
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'should return verified and unverified hits from the same search' do
|
72
|
+
posts = Array.new(2) { Post.new }
|
73
|
+
posts.last.destroy
|
74
|
+
stub_results(*posts)
|
75
|
+
search = session.search(Post)
|
76
|
+
search.hits(:verify => true).map { |hit| hit.instance }.should == posts[0..0]
|
77
|
+
search.hits.map { |hit| hit.instance }.should == [posts.first, nil]
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'should attach score to hits' do
|
81
|
+
stub_full_results('instance' => Post.new, 'score' => 1.23)
|
82
|
+
session.search(Post).hits.first.score.should == 1.23
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'should return stored field values in hits' do
|
86
|
+
stub_full_results('instance' => Post.new, 'title_ss' => 'Title')
|
87
|
+
session.search(Post).hits.first.stored(:title).should == 'Title'
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'should return stored field values for searches against multiple types' do
|
91
|
+
stub_full_results('instance' => Post.new, 'title_ss' => 'Title')
|
92
|
+
session.search(Post, Namespaced::Comment).hits.first.stored(:title).should == 'Title'
|
93
|
+
end
|
94
|
+
|
95
|
+
it 'should return stored field values for searches against base type when subtype matches' do
|
96
|
+
class SubclassedPost < Post; end;
|
97
|
+
stub_full_results('instance' => SubclassedPost.new, 'title_ss' => 'Title')
|
98
|
+
session.search(Post).hits.first.stored(:title).should == 'Title'
|
99
|
+
end
|
100
|
+
|
101
|
+
it 'should return stored text fields' do
|
102
|
+
stub_full_results('instance' => Post.new, 'body_textsv' => 'Body')
|
103
|
+
session.search(Post, Namespaced::Comment).hits.first.stored(:body).should == 'Body'
|
104
|
+
end
|
105
|
+
|
106
|
+
it 'should return stored boolean fields' do
|
107
|
+
stub_full_results('instance' => Post.new, 'featured_bs' => true)
|
108
|
+
session.search(Post, Namespaced::Comment).hits.first.stored(:featured).should be_true
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'should return stored boolean fields that evaluate to false' do
|
112
|
+
stub_full_results('instance' => Post.new, 'featured_bs' => false)
|
113
|
+
session.search(Post, Namespaced::Comment).hits.first.stored(:featured).should == false
|
114
|
+
end
|
115
|
+
|
116
|
+
it 'should return stored dynamic fields' do
|
117
|
+
stub_full_results('instance' => Post.new, 'custom_string:test_ss' => 'Custom')
|
118
|
+
session.search(Post, Namespaced::Comment).hits.first.stored(:custom_string, :test).should == 'Custom'
|
119
|
+
end
|
120
|
+
|
121
|
+
it 'should typecast stored field values in hits' do
|
122
|
+
time = Time.utc(2008, 7, 8, 2, 45)
|
123
|
+
stub_full_results('instance' => Post.new, 'last_indexed_at_ds' => time.xmlschema)
|
124
|
+
session.search(Post).hits.first.stored(:last_indexed_at).should == time
|
125
|
+
end
|
126
|
+
|
127
|
+
it 'should return stored values for multi-valued fields' do
|
128
|
+
stub_full_results('instance' => User.new, 'role_ids_ims' => %w(1 4 5))
|
129
|
+
session.search(User).hits.first.stored(:role_ids).should == [1, 4, 5]
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require File.expand_path('spec_helper', File.dirname(__FILE__))
|
2
|
+
|
3
|
+
describe "PaginatedCollection" do
|
4
|
+
subject { Sunspot::Search::PaginatedCollection.new [], 1, 10, 20 }
|
5
|
+
|
6
|
+
it { subject.should be_an(Array) }
|
7
|
+
|
8
|
+
context "behaves like a WillPaginate::Collection" do
|
9
|
+
it { subject.total_entries.should eql(20) }
|
10
|
+
it { subject.total_pages.should eql(2) }
|
11
|
+
it { subject.current_page.should eql(1) }
|
12
|
+
it { subject.per_page.should eql(10) }
|
13
|
+
it { subject.previous_page.should be_nil }
|
14
|
+
it { subject.next_page.should eql(2) }
|
15
|
+
it { subject.out_of_bounds?.should_not be_true }
|
16
|
+
it { subject.offset.should eql(0) }
|
17
|
+
|
18
|
+
it 'should allow setting total_count' do
|
19
|
+
subject.total_count = 1
|
20
|
+
subject.total_count.should eql(1)
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'should allow setting total_entries' do
|
24
|
+
subject.total_entries = 1
|
25
|
+
subject.total_entries.should eql(1)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context "behaves like Kaminari" do
|
30
|
+
it { subject.total_count.should eql(20) }
|
31
|
+
it { subject.num_pages.should eql(2) }
|
32
|
+
it { subject.limit_value.should eql(10) }
|
33
|
+
it { subject.first_page?.should be_true }
|
34
|
+
it { subject.last_page?.should_not be_true }
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require File.expand_path('spec_helper', File.dirname(__FILE__))
|
2
|
+
|
3
|
+
describe 'search results', :type => :search do
|
4
|
+
it 'loads single result' do
|
5
|
+
post = Post.new
|
6
|
+
stub_results(post)
|
7
|
+
session.search(Post).results.should == [post]
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'loads multiple results in order' do
|
11
|
+
post_1, post_2 = Post.new, Post.new
|
12
|
+
stub_results(post_1, post_2)
|
13
|
+
session.search(Post).results.should == [post_1, post_2]
|
14
|
+
stub_results(post_2, post_1)
|
15
|
+
session.search(Post).results.should == [post_2, post_1]
|
16
|
+
end
|
17
|
+
|
18
|
+
# This is a reduction of a crazy bug I found in production where some hits
|
19
|
+
# were inexplicably not being populated.
|
20
|
+
it 'properly loads results of multiple classes that have the same primary key' do
|
21
|
+
Post.reset!
|
22
|
+
Namespaced::Comment.reset!
|
23
|
+
results = [Post.new, Namespaced::Comment.new]
|
24
|
+
stub_results(*results)
|
25
|
+
session.search(Post, Namespaced::Comment).results.should == results
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'gracefully returns empty results when response is nil' do
|
29
|
+
stub_nil_results
|
30
|
+
session.search(Post).results.should == []
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'returns search total as attribute of results' do
|
34
|
+
stub_results(Post.new, 4)
|
35
|
+
session.search(Post) do
|
36
|
+
paginate(:page => 1)
|
37
|
+
end.results.total_entries.should == 4
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'returns total' do
|
41
|
+
stub_results(Post.new, Post.new, 4)
|
42
|
+
session.search(Post) { paginate(:page => 1) }.total.should == 4
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'returns query time' do
|
46
|
+
stub_nil_results
|
47
|
+
connection.response['responseHeader'] = { 'QTime' => 42 }
|
48
|
+
session.search(Post) { paginate(:page => 1) }.query_time.should == 42
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'returns total for nil search' do
|
52
|
+
stub_nil_results
|
53
|
+
session.search(Post).total.should == 0
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'returns available results if some results are not available from data store' do
|
57
|
+
posts = [Post.new, Post.new]
|
58
|
+
posts.last.destroy
|
59
|
+
stub_results(*posts)
|
60
|
+
session.search(Post).results.should == posts[0..0]
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'does not attempt to query the data store more than once when results are unavailable' do
|
64
|
+
posts = [Post.new, Post.new]
|
65
|
+
posts.each { |post| post.destroy }
|
66
|
+
stub_results(*posts)
|
67
|
+
search = session.search(Post) do
|
68
|
+
data_accessor_for(Post).should_receive(:load_all).once.and_return([])
|
69
|
+
end
|
70
|
+
search.results.should == []
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require File.expand_path('spec_helper', File.dirname(__FILE__))
|
2
|
+
|
3
|
+
describe Sunspot::Search do
|
4
|
+
it 'should allow access to the data accessor' do
|
5
|
+
stub_results(posts = Post.new)
|
6
|
+
search = session.search Post do
|
7
|
+
data_accessor_for(Post).custom_title = 'custom title'
|
8
|
+
end
|
9
|
+
search.results.first.title.should == 'custom title'
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'should re-execute search' do
|
13
|
+
post_1, post_2 = Post.new, Post.new
|
14
|
+
|
15
|
+
stub_results(post_1)
|
16
|
+
search = session.search Post
|
17
|
+
search.results.should == [post_1]
|
18
|
+
|
19
|
+
stub_results(post_2)
|
20
|
+
search.execute!
|
21
|
+
search.results.should == [post_2]
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require File.expand_path('spec_helper', File.join(File.dirname(__FILE__), '..'))
|