sunspot 1.3.3 → 2.0.0.pre.111215
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/History.txt +7 -10
- data/lib/sunspot/configuration.rb +5 -0
- data/lib/sunspot/dsl/field_group.rb +37 -0
- data/lib/sunspot/dsl/field_query.rb +48 -0
- data/lib/sunspot/dsl/restriction_with_near.rb +17 -0
- data/lib/sunspot/dsl.rb +1 -1
- data/lib/sunspot/query/common_query.rb +10 -0
- data/lib/sunspot/query/dismax.rb +5 -1
- data/lib/sunspot/query/field_group.rb +35 -0
- data/lib/sunspot/query/geofilt.rb +15 -0
- data/lib/sunspot/query/sort.rb +14 -0
- data/lib/sunspot/query/sort_composite.rb +3 -2
- data/lib/sunspot/query.rb +2 -2
- data/lib/sunspot/search/abstract_search.rb +52 -64
- data/lib/sunspot/search/field_group.rb +32 -0
- data/lib/sunspot/search/group.rb +35 -0
- data/lib/sunspot/search/hit_enumerable.rb +72 -0
- data/lib/sunspot/search/paginated_collection.rb +5 -3
- data/lib/sunspot/search.rb +1 -1
- data/lib/sunspot/session_proxy.rb +0 -8
- data/lib/sunspot/type.rb +21 -0
- data/lib/sunspot/version.rb +1 -1
- data/spec/api/class_set_spec.rb +1 -1
- data/spec/api/hit_enumerable_spec.rb +47 -0
- data/spec/api/query/group_spec.rb +32 -0
- data/spec/api/query/ordering_pagination_examples.rb +7 -0
- data/spec/api/query/spatial_examples.rb +11 -0
- data/spec/api/query/standard_spec.rb +1 -0
- data/spec/api/search/paginated_collection_spec.rb +10 -0
- data/spec/api/search/results_spec.rb +6 -0
- data/spec/integration/field_grouping_spec.rb +65 -0
- data/spec/integration/geospatial_spec.rb +59 -0
- data/spec/integration/highlighting_spec.rb +20 -0
- data/spec/mocks/post.rb +1 -0
- data/sunspot.gemspec +2 -1
- metadata +54 -34
- data/lib/sunspot/session_proxy/retry_5xx_session_proxy.rb +0 -67
- data/spec/api/session_proxy/retry_5xx_session_proxy_spec.rb +0 -73
data/History.txt
CHANGED
@@ -1,13 +1,10 @@
|
|
1
|
-
==
|
2
|
-
|
3
|
-
* Adds
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
*
|
8
|
-
* Removes deprecated `InstanceMethods` module (Anders Bengtsson)
|
9
|
-
* Adds `Retry5xxSessionProxy` to retry requests when an internal server error
|
10
|
-
occurs (Nick Zadrozny)
|
1
|
+
== 2.0.0
|
2
|
+
* Adds support for field grouping (Andy Lindeman)
|
3
|
+
* Adds support for native geospatial searches and ordering (Eric Tang, Bruno Miranda, Andy Lindeman)
|
4
|
+
* Bundled Solr installation (`sunspot_solr`) is version 3.5.0 (Chris Parker)
|
5
|
+
* Adds #query_time method to retrieve the Solr query time in
|
6
|
+
milliseconds (Jason Weathered)
|
7
|
+
* Fixes syntax of highlighting when used with nested dismax queries (Marco Crepaldi)
|
11
8
|
|
12
9
|
== 1.3.0 2011-11-26
|
13
10
|
* Requests to Solr use HTTP POST verb by default to avoid issues when the query string grows too large for GET (Johan Van Ryseghem)
|
@@ -8,6 +8,8 @@ module Sunspot
|
|
8
8
|
# Sunspot.config.pagination.default_per_page::
|
9
9
|
# Solr always paginates its results. This sets Sunspot's default result
|
10
10
|
# count per page if it is not explicitly specified in the query.
|
11
|
+
# Sunspot.config.indexing.default_batch_size::
|
12
|
+
# This sets the batch size for indexing, default is 50
|
11
13
|
#
|
12
14
|
module Configuration
|
13
15
|
class <<self
|
@@ -28,6 +30,9 @@ module Sunspot
|
|
28
30
|
pagination do
|
29
31
|
default_per_page 30
|
30
32
|
end
|
33
|
+
indexing do
|
34
|
+
default_batch_size 50
|
35
|
+
end
|
31
36
|
end
|
32
37
|
end
|
33
38
|
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Sunspot
|
2
|
+
module DSL
|
3
|
+
class FieldGroup
|
4
|
+
def initialize(query, setup, group)
|
5
|
+
@query, @setup, @group = query, setup, group
|
6
|
+
end
|
7
|
+
|
8
|
+
#
|
9
|
+
# Sets the number of results (documents) to return for each group.
|
10
|
+
# Defaults to 1.
|
11
|
+
#
|
12
|
+
def limit(num)
|
13
|
+
@group.limit = num
|
14
|
+
end
|
15
|
+
|
16
|
+
# Specify the order that results should be returned in. This method can
|
17
|
+
# be called multiple times; precedence will be in the order given.
|
18
|
+
#
|
19
|
+
# ==== Parameters
|
20
|
+
#
|
21
|
+
# field_name<Symbol>:: the field to use for ordering
|
22
|
+
# direction<Symbol>:: :asc or :desc (default :asc)
|
23
|
+
#
|
24
|
+
def order_by(field_name, direction = nil)
|
25
|
+
sort =
|
26
|
+
if special = Sunspot::Query::Sort.special(field_name)
|
27
|
+
special.new(direction)
|
28
|
+
else
|
29
|
+
Sunspot::Query::Sort::FieldSort.new(
|
30
|
+
@setup.field(field_name), direction
|
31
|
+
)
|
32
|
+
end
|
33
|
+
@group.add_sort(sort)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -31,6 +31,27 @@ module Sunspot
|
|
31
31
|
@query.add_sort(sort)
|
32
32
|
end
|
33
33
|
|
34
|
+
#
|
35
|
+
# Specify that the results should be ordered based on their
|
36
|
+
# distance from a given point.
|
37
|
+
#
|
38
|
+
# ==== Parameters
|
39
|
+
#
|
40
|
+
# field_name<Symbol>::
|
41
|
+
# the field that stores the location (declared as `latlon`)
|
42
|
+
# lat<Numeric>::
|
43
|
+
# the reference latitude
|
44
|
+
# lon<Numeric>::
|
45
|
+
# the reference longitude
|
46
|
+
# direction<Symbol>::
|
47
|
+
# :asc or :desc (default :asc)
|
48
|
+
#
|
49
|
+
def order_by_geodist(field_name, lat, lon, direction = nil)
|
50
|
+
@query.add_sort(
|
51
|
+
Sunspot::Query::Sort::GeodistSort.new(@setup.field(field_name), lat, lon, direction)
|
52
|
+
)
|
53
|
+
end
|
54
|
+
|
34
55
|
#
|
35
56
|
# DEPRECATED Use <code>order_by(:random)</code>
|
36
57
|
#
|
@@ -38,6 +59,33 @@ module Sunspot
|
|
38
59
|
order_by(:random)
|
39
60
|
end
|
40
61
|
|
62
|
+
# Specify a field for result grouping. Grouping groups documents
|
63
|
+
# with a common field value, return only the top document per
|
64
|
+
# group.
|
65
|
+
#
|
66
|
+
# More information in the Solr documentation:
|
67
|
+
# <http://wiki.apache.org/solr/FieldCollapsing>
|
68
|
+
#
|
69
|
+
# ==== Parameters
|
70
|
+
#
|
71
|
+
# field_name<Symbol>:: the field to use for grouping
|
72
|
+
def group(*field_names, &block)
|
73
|
+
options = Sunspot::Util.extract_options_from(field_names)
|
74
|
+
|
75
|
+
field_names.each do |field_name|
|
76
|
+
field = @setup.field(field_name)
|
77
|
+
group = @query.add_group(Sunspot::Query::FieldGroup.new(field))
|
78
|
+
@search.add_field_group(field)
|
79
|
+
|
80
|
+
if block
|
81
|
+
Sunspot::Util.instance_eval_or_call(
|
82
|
+
FieldGroup.new(@query, @setup, group),
|
83
|
+
&block
|
84
|
+
)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
41
89
|
#
|
42
90
|
# Request a facet on the search query. A facet is a feature of Solr that
|
43
91
|
# determines the number of documents that match the existing search *and*
|
@@ -116,6 +116,23 @@ module Sunspot
|
|
116
116
|
def near(lat, lng, options = {})
|
117
117
|
@query.fulltext.add_location(@field, lat, lng, options)
|
118
118
|
end
|
119
|
+
|
120
|
+
#
|
121
|
+
# Performs a query that is filtered by a radius around a given
|
122
|
+
# latitude and longitude.
|
123
|
+
#
|
124
|
+
# ==== Parameters
|
125
|
+
#
|
126
|
+
# :lat<Numeric>::
|
127
|
+
# Latitude (in degrees)
|
128
|
+
# :lon<Numeric>::
|
129
|
+
# Longitude (in degrees)
|
130
|
+
# :radius<Numeric>::
|
131
|
+
# Radius (in kilometers)
|
132
|
+
#
|
133
|
+
def in_radius(lat, lon, radius)
|
134
|
+
@query.add_geo(Sunspot::Query::Geofilt.new(@field, lat, lon, radius))
|
135
|
+
end
|
119
136
|
end
|
120
137
|
end
|
121
138
|
end
|
data/lib/sunspot/dsl.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
%w(fields scope paginatable adjustable field_query standard_query query_facet
|
2
2
|
functional fulltext restriction restriction_with_near search
|
3
|
-
more_like_this_query function).each do |file|
|
3
|
+
more_like_this_query function field_group).each do |file|
|
4
4
|
require File.join(File.dirname(__FILE__), 'dsl', file)
|
5
5
|
end
|
@@ -20,6 +20,11 @@ module Sunspot
|
|
20
20
|
@sort << sort
|
21
21
|
end
|
22
22
|
|
23
|
+
def add_group(group)
|
24
|
+
@components << group
|
25
|
+
group
|
26
|
+
end
|
27
|
+
|
23
28
|
def add_field_facet(facet)
|
24
29
|
@components << facet
|
25
30
|
facet
|
@@ -35,6 +40,11 @@ module Sunspot
|
|
35
40
|
function
|
36
41
|
end
|
37
42
|
|
43
|
+
def add_geo(geo)
|
44
|
+
@components << geo
|
45
|
+
geo
|
46
|
+
end
|
47
|
+
|
38
48
|
def paginate(page, per_page, offset = nil)
|
39
49
|
if @pagination
|
40
50
|
@pagination.offset = offset
|
data/lib/sunspot/query/dismax.rb
CHANGED
@@ -64,7 +64,7 @@ module Sunspot
|
|
64
64
|
params.delete :defType
|
65
65
|
params.delete :fl
|
66
66
|
keywords = params.delete(:q)
|
67
|
-
options = params.map { |key, value|
|
67
|
+
options = params.map { |key, value| escape_param(key, value) }.join(' ')
|
68
68
|
"_query_:\"{!dismax #{options}}#{escape_quotes(keywords)}\""
|
69
69
|
end
|
70
70
|
|
@@ -117,6 +117,10 @@ module Sunspot
|
|
117
117
|
|
118
118
|
|
119
119
|
private
|
120
|
+
|
121
|
+
def escape_param(key, value)
|
122
|
+
"#{key}='#{escape_quotes(Array(value).join(" "))}'"
|
123
|
+
end
|
120
124
|
|
121
125
|
def escape_quotes(value)
|
122
126
|
return value unless value.is_a? String
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Sunspot
|
2
|
+
module Query
|
3
|
+
#
|
4
|
+
# A FieldGroup groups by the unique values of a given field.
|
5
|
+
#
|
6
|
+
class FieldGroup
|
7
|
+
attr_accessor :limit
|
8
|
+
|
9
|
+
def initialize(field)
|
10
|
+
if field.multiple?
|
11
|
+
raise(ArgumentError, "#{field.name} cannot be used for grouping because it is a multiple-value field")
|
12
|
+
end
|
13
|
+
@field = field
|
14
|
+
|
15
|
+
@sort = SortComposite.new
|
16
|
+
end
|
17
|
+
|
18
|
+
def add_sort(sort)
|
19
|
+
@sort << sort
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_params
|
23
|
+
params = {
|
24
|
+
:group => "true",
|
25
|
+
:"group.field" => @field.indexed_name,
|
26
|
+
}
|
27
|
+
|
28
|
+
params.merge!(@sort.to_params("group."))
|
29
|
+
params[:"group.limit"] = @limit if @limit
|
30
|
+
|
31
|
+
params
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Sunspot
|
2
|
+
module Query
|
3
|
+
class Geofilt
|
4
|
+
def initialize(field, lat, lon, radius)
|
5
|
+
@field, @lat, @lon, @radius = field, lat, lon, radius
|
6
|
+
end
|
7
|
+
|
8
|
+
def to_params
|
9
|
+
filter = "{!geofilt sfield=#{@field.indexed_name} pt=#{@lat},#{@lon} d=#{@radius}}"
|
10
|
+
|
11
|
+
{:fq => filter}
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/lib/sunspot/query/sort.rb
CHANGED
@@ -90,6 +90,20 @@ module Sunspot
|
|
90
90
|
"score #{direction_for_solr}"
|
91
91
|
end
|
92
92
|
end
|
93
|
+
|
94
|
+
#
|
95
|
+
# A GeodistSort sorts by distance from a given point.
|
96
|
+
#
|
97
|
+
class GeodistSort < FieldSort
|
98
|
+
def initialize(field, lat, lon, direction)
|
99
|
+
@lat, @lon = lat, lon
|
100
|
+
super(field, direction)
|
101
|
+
end
|
102
|
+
|
103
|
+
def to_param
|
104
|
+
"geodist(#{@field.indexed_name.to_sym},#{@lat},#{@lon}) #{direction_for_solr}"
|
105
|
+
end
|
106
|
+
end
|
93
107
|
end
|
94
108
|
end
|
95
109
|
end
|
@@ -21,9 +21,10 @@ module Sunspot
|
|
21
21
|
#
|
22
22
|
# Combine the sorts into a single param by joining them
|
23
23
|
#
|
24
|
-
def to_params
|
24
|
+
def to_params(prefix = "")
|
25
25
|
unless @sorts.empty?
|
26
|
-
|
26
|
+
key = "#{prefix}sort".to_sym
|
27
|
+
{ key => @sorts.map { |sort| sort.to_param } * ', ' }
|
27
28
|
else
|
28
29
|
{}
|
29
30
|
end
|
data/lib/sunspot/query.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
%w(filter abstract_field_facet connective boost_query date_field_facet dismax
|
2
2
|
field_facet highlighting pagination restriction common_query
|
3
|
-
standard_query more_like_this more_like_this_query geo query_facet scope
|
3
|
+
standard_query more_like_this more_like_this_query geo geofilt query_facet scope
|
4
4
|
sort sort_composite text_field_boost function_query
|
5
|
-
composite_fulltext).each do |file|
|
5
|
+
composite_fulltext field_group).each do |file|
|
6
6
|
require(File.join(File.dirname(__FILE__), 'query', file))
|
7
7
|
end
|
8
8
|
module Sunspot
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'sunspot/search/paginated_collection'
|
2
|
+
require 'sunspot/search/hit_enumerable'
|
2
3
|
|
3
4
|
module Sunspot
|
4
5
|
module Search #:nodoc:
|
@@ -14,15 +15,21 @@ module Sunspot
|
|
14
15
|
# Retrieve all facet objects defined for this search, in order they were
|
15
16
|
# defined. To retrieve an individual facet by name, use #facet()
|
16
17
|
#
|
17
|
-
attr_reader :facets
|
18
|
+
attr_reader :facets, :groups
|
18
19
|
attr_reader :query #:nodoc:
|
19
20
|
attr_accessor :request_handler
|
21
|
+
|
22
|
+
include HitEnumerable
|
20
23
|
|
21
24
|
def initialize(connection, setup, query, configuration) #:nodoc:
|
22
25
|
@connection, @setup, @query = connection, setup, query
|
23
26
|
@query.paginate(1, configuration.pagination.default_per_page)
|
27
|
+
|
24
28
|
@facets = []
|
25
29
|
@facets_by_name = {}
|
30
|
+
|
31
|
+
@groups_by_name = {}
|
32
|
+
@groups = []
|
26
33
|
end
|
27
34
|
|
28
35
|
#
|
@@ -78,44 +85,33 @@ module Sunspot
|
|
78
85
|
#
|
79
86
|
def hits(options = {})
|
80
87
|
if options[:verify]
|
81
|
-
|
88
|
+
super
|
82
89
|
else
|
83
|
-
@hits ||=
|
84
|
-
begin
|
85
|
-
hits = if solr_response && solr_response['docs']
|
86
|
-
solr_response['docs'].map do |doc|
|
87
|
-
Hit.new(doc, highlights_for(doc), self)
|
88
|
-
end
|
89
|
-
end
|
90
|
-
paginate_collection(hits || [])
|
91
|
-
end
|
90
|
+
@hits ||= paginate_collection(super)
|
92
91
|
end
|
93
92
|
end
|
94
93
|
alias_method :raw_results, :hits
|
95
|
-
|
94
|
+
|
95
|
+
#
|
96
|
+
# The total number of documents matching the query parameters
|
96
97
|
#
|
97
|
-
#
|
98
|
-
# yielded a Sunspot::Server::Hit instance and a Sunspot::Server::Result
|
99
|
-
# instance.
|
98
|
+
# ==== Returns
|
100
99
|
#
|
101
|
-
#
|
102
|
-
# for more information).
|
100
|
+
# Integer:: Total matching documents
|
103
101
|
#
|
104
|
-
def
|
105
|
-
|
106
|
-
yield(hit, hit.result)
|
107
|
-
end
|
102
|
+
def total
|
103
|
+
@total ||= solr_response['numFound'] || 0
|
108
104
|
end
|
109
105
|
|
110
106
|
#
|
111
|
-
# The
|
107
|
+
# The time elapsed to generate the Solr response
|
112
108
|
#
|
113
109
|
# ==== Returns
|
114
110
|
#
|
115
|
-
# Integer::
|
111
|
+
# Integer:: Query runtime in milliseconds
|
116
112
|
#
|
117
|
-
def
|
118
|
-
@
|
113
|
+
def query_time
|
114
|
+
@query_time ||= solr_response_header['QTime']
|
119
115
|
end
|
120
116
|
|
121
117
|
#
|
@@ -167,6 +163,12 @@ module Sunspot
|
|
167
163
|
end
|
168
164
|
end
|
169
165
|
end
|
166
|
+
|
167
|
+
def group(name)
|
168
|
+
if name
|
169
|
+
@groups_by_name[name.to_sym]
|
170
|
+
end
|
171
|
+
end
|
170
172
|
|
171
173
|
#
|
172
174
|
# Deprecated in favor of optional second argument to #facet
|
@@ -178,19 +180,9 @@ module Sunspot
|
|
178
180
|
def facet_response #:nodoc:
|
179
181
|
@solr_result['facet_counts']
|
180
182
|
end
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
# persistent storage. Data accessors can implement any methods that may be
|
185
|
-
# useful for refining how data is loaded out of storage. When building a
|
186
|
-
# search manually (e.g., using the Sunspot#new_search method), this should
|
187
|
-
# be used before calling #execute(). Use the
|
188
|
-
# Sunspot::DSL::Search#data_accessor_for method when building searches using
|
189
|
-
# the block DSL.
|
190
|
-
#
|
191
|
-
def data_accessor_for(clazz) #:nodoc:
|
192
|
-
(@data_accessors ||= {})[clazz.name.to_sym] ||=
|
193
|
-
Adapters::DataAccessor.create(clazz)
|
183
|
+
|
184
|
+
def group_response #:nodoc:
|
185
|
+
@solr_result['grouped']
|
194
186
|
end
|
195
187
|
|
196
188
|
#
|
@@ -211,31 +203,14 @@ module Sunspot
|
|
211
203
|
self
|
212
204
|
end
|
213
205
|
|
214
|
-
#
|
215
|
-
# Populate the Hit objects with their instances. This is invoked the first
|
216
|
-
# time any hit has its instance requested, and all hits are loaded as a
|
217
|
-
# batch.
|
218
|
-
#
|
219
|
-
def populate_hits #:nodoc:
|
220
|
-
id_hit_hash = Hash.new { |h, k| h[k] = {} }
|
221
|
-
hits.each do |hit|
|
222
|
-
id_hit_hash[hit.class_name][hit.primary_key] = hit
|
223
|
-
end
|
224
|
-
id_hit_hash.each_pair do |class_name, hits|
|
225
|
-
ids = hits.map { |id, hit| hit.primary_key }
|
226
|
-
data_accessor = data_accessor_for(Util.full_const_get(class_name))
|
227
|
-
hits_for_class = id_hit_hash[class_name]
|
228
|
-
data_accessor.load_all(ids).each do |result|
|
229
|
-
hit = hits_for_class.delete(Adapters::InstanceAdapter.adapt(result).id.to_s)
|
230
|
-
hit.result = result
|
231
|
-
end
|
232
|
-
hits_for_class.values.each { |hit| hit.result = nil }
|
233
|
-
end
|
234
|
-
end
|
235
206
|
|
236
207
|
def inspect #:nodoc:
|
237
208
|
"<Sunspot::Search:#{query.to_params.inspect}>"
|
238
209
|
end
|
210
|
+
|
211
|
+
def add_field_group(field, options = {}) #:nodoc:
|
212
|
+
add_group(field.name, FieldGroup.new(field, self, options))
|
213
|
+
end
|
239
214
|
|
240
215
|
def add_field_facet(field, options = {}) #:nodoc:
|
241
216
|
name = (options[:name] || field.name)
|
@@ -250,6 +225,12 @@ module Sunspot
|
|
250
225
|
name = (options[:name] || field.name)
|
251
226
|
add_facet(name, DateFacet.new(field, self, options))
|
252
227
|
end
|
228
|
+
|
229
|
+
def highlights_for(doc) #:nodoc:
|
230
|
+
if @solr_result['highlighting']
|
231
|
+
@solr_result['highlighting'][doc['id']]
|
232
|
+
end
|
233
|
+
end
|
253
234
|
|
254
235
|
private
|
255
236
|
|
@@ -265,14 +246,16 @@ module Sunspot
|
|
265
246
|
@solr_response ||= @solr_result['response'] || {}
|
266
247
|
end
|
267
248
|
|
268
|
-
def
|
269
|
-
|
270
|
-
|
271
|
-
|
249
|
+
def solr_response_header
|
250
|
+
@solr_response_header ||= @solr_result['responseHeader'] || {}
|
251
|
+
end
|
252
|
+
|
253
|
+
def solr_docs
|
254
|
+
solr_response['docs']
|
272
255
|
end
|
273
256
|
|
274
257
|
def verified_hits
|
275
|
-
@verified_hits ||= paginate_collection(
|
258
|
+
@verified_hits ||= paginate_collection(super)
|
276
259
|
end
|
277
260
|
|
278
261
|
def paginate_collection(collection)
|
@@ -283,6 +266,11 @@ module Sunspot
|
|
283
266
|
@facets << facet
|
284
267
|
@facets_by_name[name.to_sym] = facet
|
285
268
|
end
|
269
|
+
|
270
|
+
def add_group(name, group)
|
271
|
+
@groups << group
|
272
|
+
@groups_by_name[name.to_sym] = group
|
273
|
+
end
|
286
274
|
|
287
275
|
# Clear out all the cached ivars so the search can be called again.
|
288
276
|
def reset
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Sunspot
|
2
|
+
module Search
|
3
|
+
class FieldGroup
|
4
|
+
def initialize(field, search, options) #:nodoc:
|
5
|
+
@field, @search, @options = field, search, options
|
6
|
+
end
|
7
|
+
|
8
|
+
def groups
|
9
|
+
@groups ||=
|
10
|
+
begin
|
11
|
+
if solr_response
|
12
|
+
solr_response['groups'].map do |group|
|
13
|
+
Group.new(group['groupValue'], group['doclist'], @search)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def matches
|
20
|
+
if solr_response
|
21
|
+
solr_response['matches'].to_i
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def solr_response
|
28
|
+
@search.group_response[@field.indexed_name.to_s]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'sunspot/search/hit_enumerable'
|
2
|
+
|
3
|
+
module Sunspot
|
4
|
+
module Search
|
5
|
+
class Group
|
6
|
+
attr_reader :value
|
7
|
+
|
8
|
+
include HitEnumerable
|
9
|
+
|
10
|
+
def initialize(value, doclist, search)
|
11
|
+
@value, @doclist, @search = value, doclist, search
|
12
|
+
end
|
13
|
+
|
14
|
+
def hits(options = {})
|
15
|
+
if options[:verify]
|
16
|
+
super
|
17
|
+
else
|
18
|
+
@hits ||= super
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def verified_hits
|
23
|
+
@verified_hits ||= super
|
24
|
+
end
|
25
|
+
|
26
|
+
def highlights_for(doc)
|
27
|
+
@search.highlights_for(doc)
|
28
|
+
end
|
29
|
+
|
30
|
+
def solr_docs
|
31
|
+
@doclist['docs']
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module Sunspot
|
2
|
+
module Search
|
3
|
+
module HitEnumerable #:nodoc:
|
4
|
+
def hits(options = {})
|
5
|
+
if options[:verify]
|
6
|
+
verified_hits
|
7
|
+
elsif solr_docs
|
8
|
+
solr_docs.map { |d| Hit.new(d, highlights_for(d), self) }
|
9
|
+
else
|
10
|
+
[]
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def verified_hits
|
15
|
+
hits.select { |h| h.result }
|
16
|
+
end
|
17
|
+
|
18
|
+
#
|
19
|
+
# Populate the Hit objects with their instances. This is invoked the first
|
20
|
+
# time any hit has its instance requested, and all hits are loaded as a
|
21
|
+
# batch.
|
22
|
+
#
|
23
|
+
def populate_hits #:nodoc:
|
24
|
+
id_hit_hash = Hash.new { |h, k| h[k] = {} }
|
25
|
+
hits.each do |hit|
|
26
|
+
id_hit_hash[hit.class_name][hit.primary_key] = hit
|
27
|
+
end
|
28
|
+
id_hit_hash.each_pair do |class_name, hits|
|
29
|
+
ids = hits.map { |id, hit| hit.primary_key }
|
30
|
+
data_accessor = data_accessor_for(Util.full_const_get(class_name))
|
31
|
+
hits_for_class = id_hit_hash[class_name]
|
32
|
+
data_accessor.load_all(ids).each do |result|
|
33
|
+
hit = hits_for_class.delete(Adapters::InstanceAdapter.adapt(result).id.to_s)
|
34
|
+
hit.result = result
|
35
|
+
end
|
36
|
+
hits_for_class.values.each { |hit| hit.result = nil }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
#
|
41
|
+
# Convenience method to iterate over hit and result objects. Block is
|
42
|
+
# yielded a Sunspot::Server::Hit instance and a Sunspot::Server::Result
|
43
|
+
# instance.
|
44
|
+
#
|
45
|
+
# Note that this method iterates over verified hits (see #hits method
|
46
|
+
# for more information).
|
47
|
+
#
|
48
|
+
def each_hit_with_result
|
49
|
+
verified_hits.each do |hit|
|
50
|
+
yield(hit, hit.result)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
#
|
55
|
+
# Get the data accessor that will be used to load a particular class out of
|
56
|
+
# persistent storage. Data accessors can implement any methods that may be
|
57
|
+
# useful for refining how data is loaded out of storage. When building a
|
58
|
+
# search manually (e.g., using the Sunspot#new_search method), this should
|
59
|
+
# be used before calling #execute(). Use the
|
60
|
+
# Sunspot::DSL::Search#data_accessor_for method when building searches using
|
61
|
+
# the block DSL.
|
62
|
+
#
|
63
|
+
def data_accessor_for(clazz) #:nodoc:
|
64
|
+
# FIXME: This method does not belong here, but I was getting bogged
|
65
|
+
# down trying to figure out where it should go. Punted for now.
|
66
|
+
# - AL 27 Nov 2011
|
67
|
+
(@data_accessors ||= {})[clazz.name.to_sym] ||=
|
68
|
+
Adapters::DataAccessor.create(clazz)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -4,8 +4,10 @@ module Sunspot
|
|
4
4
|
class PaginatedCollection
|
5
5
|
instance_methods.each { |m| undef_method m unless m =~ /^__|instance_eval|object_id/ }
|
6
6
|
|
7
|
-
attr_reader :
|
7
|
+
attr_reader :current_page, :per_page
|
8
|
+
attr_accessor :total_count
|
8
9
|
alias :total_entries :total_count
|
10
|
+
alias :total_entries= :total_count=
|
9
11
|
alias :limit_value :per_page
|
10
12
|
|
11
13
|
def initialize(collection, page, per_page, total)
|
@@ -34,12 +36,12 @@ module Sunspot
|
|
34
36
|
|
35
37
|
def next_page
|
36
38
|
current_page < total_pages ? (current_page + 1) : nil
|
37
|
-
end
|
39
|
+
end
|
38
40
|
|
39
41
|
def out_of_bounds?
|
40
42
|
current_page > total_pages
|
41
43
|
end
|
42
|
-
|
44
|
+
|
43
45
|
def offset
|
44
46
|
(current_page - 1) * per_page
|
45
47
|
end
|