outoftime-sunspot 0.0.2 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +4 -0
- data/README.rdoc +26 -33
- data/TODO +7 -0
- data/VERSION.yml +2 -2
- data/lib/light_config.rb +5 -5
- data/lib/sunspot/adapters.rb +209 -33
- data/lib/sunspot/configuration.rb +25 -10
- data/lib/sunspot/dsl/fields.rb +42 -11
- data/lib/sunspot/dsl/query.rb +150 -6
- data/lib/sunspot/dsl/scope.rb +16 -26
- data/lib/sunspot/facet.rb +37 -0
- data/lib/sunspot/facet_row.rb +34 -0
- data/lib/sunspot/facets.rb +21 -0
- data/lib/sunspot/field.rb +112 -56
- data/lib/sunspot/indexer.rb +49 -46
- data/lib/sunspot/query.rb +217 -49
- data/lib/sunspot/restriction.rb +158 -14
- data/lib/sunspot/search.rb +79 -28
- data/lib/sunspot/session.rb +75 -8
- data/lib/sunspot/setup.rb +171 -0
- data/lib/sunspot/type.rb +116 -32
- data/lib/sunspot/util.rb +141 -7
- data/lib/sunspot.rb +260 -31
- data/spec/api/build_search_spec.rb +139 -33
- data/spec/api/indexer_spec.rb +33 -2
- data/spec/api/search_retrieval_spec.rb +85 -2
- data/spec/api/session_spec.rb +14 -6
- data/spec/integration/faceting_spec.rb +39 -0
- data/spec/integration/keyword_search_spec.rb +1 -1
- data/spec/integration/scoped_search_spec.rb +175 -0
- data/spec/mocks/mock_adapter.rb +7 -10
- data/spec/mocks/post.rb +7 -2
- data/tasks/rdoc.rake +7 -0
- data/tasks/spec.rake +3 -0
- data/tasks/todo.rake +4 -0
- metadata +12 -7
- data/lib/sunspot/builder.rb +0 -78
- data/spec/api/standard_search_builder_spec.rb +0 -76
- data/spec/custom_expectation.rb +0 -53
- data/spec/integration/field_types_spec.rb +0 -62
data/lib/sunspot/query.rb
CHANGED
@@ -1,96 +1,232 @@
|
|
1
1
|
module Sunspot
|
2
|
-
|
3
|
-
|
2
|
+
#
|
3
|
+
# This class encapsulates a query that is to be sent to Solr. The query is
|
4
|
+
# constructed in the block passed to the Sunspot.search method, using the
|
5
|
+
# Sunspot::DSL::Query interface. Instances of Query, as well as all of the
|
6
|
+
# components it contains, respond to the #to_params method, which returns
|
7
|
+
# a hash of parameters in the format recognized by the solr-ruby API.
|
8
|
+
#
|
9
|
+
class Query #:nodoc:
|
10
|
+
attr_writer :keywords # <String> full-text keyword boolean phrase
|
4
11
|
|
5
12
|
def initialize(types, params, configuration)
|
6
13
|
@types, @configuration = types, configuration
|
7
|
-
|
14
|
+
@rows = @configuration.pagination.default_per_page
|
15
|
+
apply_params(params)
|
8
16
|
end
|
9
17
|
|
10
|
-
|
18
|
+
#
|
19
|
+
# Representation of this query as solr-ruby parameters. Constructs the hash
|
20
|
+
# by deep-merging scope and facet parameters, adding in various other
|
21
|
+
# parameters from instance data.
|
22
|
+
#
|
23
|
+
# Note that solr-ruby takes the :q parameter as a separate argument; for
|
24
|
+
# the sake of consistency, the Query object ignores this fact (the Search
|
25
|
+
# object extracts it back out).
|
26
|
+
#
|
27
|
+
# ==== Returns
|
28
|
+
#
|
29
|
+
# Hash:: Representation of query in solr-ruby form
|
30
|
+
#
|
31
|
+
def to_params
|
32
|
+
params = {}
|
11
33
|
query_components = []
|
12
|
-
query_components << keywords if keywords
|
13
|
-
query_components <<
|
14
|
-
query_components.map { |component| "(#{component})"} * ' AND '
|
34
|
+
query_components << @keywords if @keywords
|
35
|
+
query_components << types_phrase if types_phrase
|
36
|
+
params[:q] = query_components.map { |component| "(#{component})"} * ' AND '
|
37
|
+
params[:sort] = @sort if @sort
|
38
|
+
params[:start] = @start if @start
|
39
|
+
params[:rows] = @rows if @rows
|
40
|
+
for component in components
|
41
|
+
Util.deep_merge!(params, component.to_params)
|
42
|
+
end
|
43
|
+
params
|
15
44
|
end
|
16
45
|
|
17
|
-
|
18
|
-
|
46
|
+
#
|
47
|
+
# Add a query component
|
48
|
+
#
|
49
|
+
# ==== Parameters
|
50
|
+
#
|
51
|
+
# component<~to_params>:: A restriction query component
|
52
|
+
#
|
53
|
+
def add_component(component)
|
54
|
+
components << component
|
19
55
|
end
|
20
56
|
|
21
|
-
|
22
|
-
|
57
|
+
#
|
58
|
+
# Add instance of Sunspot::Restriction::Base to query components. This
|
59
|
+
# method is exposed to the DSL because the Query instance holds field
|
60
|
+
# definitions and is able to translate field names into full field
|
61
|
+
# definitions, and memoize # the result.
|
62
|
+
#
|
63
|
+
# ==== Parameters
|
64
|
+
#
|
65
|
+
# field_name<Symbol>:: Name of the field to which the restriction applies
|
66
|
+
# restriction_clazz<Class>::
|
67
|
+
# Subclass of Sunspot::Restriction::Base to instantiate
|
68
|
+
# value<Object>::
|
69
|
+
# Value against which the restriction applies (e.g. less_than(2) has a
|
70
|
+
# value of 2)
|
71
|
+
# negative:: Whether this restriction should be negated
|
72
|
+
#
|
73
|
+
# ==== Returns
|
74
|
+
#
|
75
|
+
# Sunspot::Restriction::Base:: Restriction instance
|
76
|
+
#
|
77
|
+
def add_restriction(field_name, restriction_clazz, value, negative = false)
|
78
|
+
add_component(restriction_clazz.new(field(field_name), value, negative))
|
23
79
|
end
|
24
80
|
|
25
|
-
|
26
|
-
|
81
|
+
#
|
82
|
+
# Add a field facet
|
83
|
+
#
|
84
|
+
# ==== Parameters
|
85
|
+
#
|
86
|
+
# field_name<Symbol>:: Name of the field on which to get a facet
|
87
|
+
#
|
88
|
+
def add_field_facet(field_name)
|
89
|
+
add_component(Facets::FieldFacet.new(field(field_name)))
|
27
90
|
end
|
28
91
|
|
29
|
-
|
30
|
-
|
31
|
-
|
92
|
+
#
|
93
|
+
# Sets @start and @rows instance variables using pagination semantics
|
94
|
+
#
|
95
|
+
# ==== Parameters
|
96
|
+
#
|
97
|
+
# page<Integer>:: Page on which to start
|
98
|
+
# per_page<Integer>::
|
99
|
+
# How many rows to display per page. Default taken from
|
100
|
+
# Sunspot.config.pagination.default_per_page
|
101
|
+
#
|
102
|
+
def paginate(page, per_page = nil)
|
103
|
+
per_page ||= @configuration.pagination.default_per_page
|
32
104
|
@start = (page - 1) * per_page
|
33
105
|
@rows = per_page
|
34
106
|
end
|
35
107
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
108
|
+
#
|
109
|
+
# Set result ordering.
|
110
|
+
#
|
111
|
+
# ==== Parameters
|
112
|
+
#
|
113
|
+
# field_name<Symbol>:: Name of the field on which to order
|
114
|
+
# direction<Symbol>:: :asc or :desc (default :asc)
|
115
|
+
#
|
40
116
|
def order_by(field_name, direction = nil)
|
41
117
|
direction ||= :asc
|
42
|
-
@sort
|
118
|
+
(@sort ||= []) << { field(field_name).indexed_name.to_sym => (direction.to_s == 'asc' ? :ascending : :descending) }
|
43
119
|
end
|
44
120
|
|
121
|
+
#
|
122
|
+
# Page that this query will return (used by Sunspot::Search to expose
|
123
|
+
# pagination)
|
124
|
+
#
|
125
|
+
# ==== Returns
|
126
|
+
#
|
127
|
+
# Integer:: Page number
|
128
|
+
#
|
45
129
|
def page
|
46
|
-
|
47
|
-
|
130
|
+
if @start && @rows
|
131
|
+
@start / @rows + 1
|
132
|
+
else
|
133
|
+
1
|
134
|
+
end
|
48
135
|
end
|
49
136
|
|
50
|
-
|
51
|
-
|
137
|
+
#
|
138
|
+
# Number of rows per page that this query will return (used by
|
139
|
+
# Sunspot::Search to expose pagination)
|
140
|
+
#
|
141
|
+
# ==== Returns
|
142
|
+
#
|
143
|
+
# Integer:: Rows per page
|
144
|
+
#
|
145
|
+
def per_page
|
146
|
+
@rows
|
52
147
|
end
|
53
148
|
|
54
|
-
|
55
|
-
|
149
|
+
#
|
150
|
+
# Get a DSL instance for building this query.
|
151
|
+
#
|
152
|
+
# ==== Returns
|
153
|
+
#
|
154
|
+
# Sunspot::DSL::Query:: DSL instance
|
155
|
+
#
|
156
|
+
def dsl
|
157
|
+
@dsl ||= DSL::Query.new(self)
|
56
158
|
end
|
57
159
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
160
|
+
#
|
161
|
+
# Get a Sunspot::Field::Base instance corresponding to the given field name
|
162
|
+
#
|
163
|
+
# ==== Parameters
|
164
|
+
#
|
165
|
+
# field_name<Symbol>:: The field name for which to find a field
|
166
|
+
#
|
167
|
+
# ==== Returns
|
168
|
+
#
|
169
|
+
# Sunspot::Field::Base:: The field object corresponding to the given name
|
170
|
+
#
|
171
|
+
# ==== Raises
|
172
|
+
#
|
173
|
+
# ArgumentError::
|
174
|
+
# If the given field name is not configured for the types being queried
|
175
|
+
#
|
176
|
+
def field(field_name)
|
177
|
+
fields_hash[field_name.to_sym] || raise(UnrecognizedFieldError, "No field configured for #{@types * ', '} with name '#{field_name}'")
|
178
|
+
end
|
62
179
|
|
63
180
|
private
|
64
181
|
|
65
|
-
|
66
|
-
|
182
|
+
# ==== Returns
|
183
|
+
#
|
184
|
+
# Array:: Collection of query components
|
185
|
+
#
|
186
|
+
def components
|
187
|
+
@components ||= []
|
67
188
|
end
|
68
189
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
190
|
+
#
|
191
|
+
# Boolean phrase that restricts results to objects of the type(s) under
|
192
|
+
# query. If this is an open query (no types specified) then it sends a
|
193
|
+
# no-op phrase because Solr requires that the :q parameter not be empty.
|
194
|
+
#
|
195
|
+
# TODO don't send a noop if we have a keyword phrase
|
196
|
+
# TODO this should be sent as a filter query when possible, especially
|
197
|
+
# if there is a single type, so that Solr can cache it
|
198
|
+
#
|
199
|
+
# ==== Returns
|
200
|
+
#
|
201
|
+
# String:: Boolean phrase for type restriction
|
202
|
+
#
|
203
|
+
def types_phrase
|
204
|
+
if @types.nil? || @types.empty? then "type:[* TO *]"
|
205
|
+
elsif @types.length == 1 then "type:#{@types.first}"
|
206
|
+
else "type:(#{@types * ' OR '})"
|
77
207
|
end
|
78
208
|
end
|
79
209
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
210
|
+
#
|
211
|
+
# Return a hash of field names to field objects, containing all fields
|
212
|
+
# that are common to all of the classes under search. In order for fields
|
213
|
+
# to be common, they must be of the same type and have the same
|
214
|
+
# value for allow_multiple?. This method is memoized.
|
215
|
+
#
|
216
|
+
# ==== Returns
|
217
|
+
#
|
218
|
+
# Hash:: field names keyed to field objects
|
219
|
+
#
|
84
220
|
def fields_hash
|
85
221
|
@fields_hash ||= begin
|
86
|
-
fields_hash = types.inject({}) do |hash, type|
|
87
|
-
|
88
|
-
(hash[field.name.
|
222
|
+
fields_hash = @types.inject({}) do |hash, type|
|
223
|
+
Setup.for(type).fields.each do |field|
|
224
|
+
(hash[field.name.to_sym] ||= {})[type.name] = field
|
89
225
|
end
|
90
226
|
hash
|
91
227
|
end
|
92
228
|
fields_hash.each_pair do |field_name, field_configurations_hash|
|
93
|
-
if types.any? { |type| field_configurations_hash[type.name].nil? } # at least one type doesn't have this field configured
|
229
|
+
if @types.any? { |type| field_configurations_hash[type.name].nil? } # at least one type doesn't have this field configured
|
94
230
|
fields_hash.delete(field_name)
|
95
231
|
elsif field_configurations_hash.values.map { |configuration| configuration.indexed_name }.uniq.length != 1 # fields with this name have different configs
|
96
232
|
fields_hash.delete(field_name)
|
@@ -100,5 +236,37 @@ module Sunspot
|
|
100
236
|
end
|
101
237
|
end
|
102
238
|
end
|
239
|
+
|
240
|
+
def apply_params(params)
|
241
|
+
if params.has_key?(:keywords)
|
242
|
+
self.keywords = params[:keywords]
|
243
|
+
end
|
244
|
+
if params.has_key?(:conditions)
|
245
|
+
params[:conditions].each_pair do |field_name, value|
|
246
|
+
begin
|
247
|
+
restriction_type =
|
248
|
+
case value
|
249
|
+
when Array
|
250
|
+
Restriction::AnyOf
|
251
|
+
when Range
|
252
|
+
Restriction::Between
|
253
|
+
else
|
254
|
+
Restriction::EqualTo
|
255
|
+
end
|
256
|
+
add_restriction(field_name, restriction_type, value)
|
257
|
+
rescue UnrecognizedFieldError
|
258
|
+
# ignore fields we don't recognize
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|
262
|
+
if params.has_key?(:order)
|
263
|
+
for order in Array(params[:order])
|
264
|
+
order_by(*order.split(' '))
|
265
|
+
end
|
266
|
+
end
|
267
|
+
if params.has_key?(:page)
|
268
|
+
paginate(params[:page], params[:per_page])
|
269
|
+
end
|
270
|
+
end
|
103
271
|
end
|
104
272
|
end
|
data/lib/sunspot/restriction.rb
CHANGED
@@ -1,27 +1,141 @@
|
|
1
1
|
module Sunspot
|
2
|
-
module Restriction
|
3
|
-
class
|
4
|
-
|
5
|
-
|
2
|
+
module Restriction #:nodoc:
|
3
|
+
class <<self
|
4
|
+
#
|
5
|
+
# Return the names of all of the restriction classes that should be made
|
6
|
+
# available to the DSL.
|
7
|
+
#
|
8
|
+
# ==== Returns
|
9
|
+
#
|
10
|
+
# Array:: Collection of restriction class names
|
11
|
+
#
|
12
|
+
def names
|
13
|
+
constants - %w(Base SameAs) #XXX this seems ugly
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
#
|
18
|
+
# Subclasses of this class represent restrictions that can be applied to
|
19
|
+
# a Sunspot query. The Sunspot::DSL::Restriction class presents a builder
|
20
|
+
# API for instances of this class.
|
21
|
+
#
|
22
|
+
# Implementations of this class must respond to #to_params and
|
23
|
+
# #to_negative_params. Instead of implementing those methods, they may
|
24
|
+
# choose to implement any of:
|
25
|
+
#
|
26
|
+
# * #to_positive_boolean_phrase, and optionally #to_negative_boolean_phrase
|
27
|
+
# * #to_solr_conditional
|
28
|
+
#
|
29
|
+
class Base #:nodoc:
|
30
|
+
def initialize(field, value, negative = false)
|
31
|
+
@field, @value, @negative = field, value, negative
|
32
|
+
end
|
33
|
+
|
34
|
+
#
|
35
|
+
# A hash representing this restriction in solr-ruby's parameter format.
|
36
|
+
# All restriction implementations must respond to this method; however,
|
37
|
+
# the base implementation delegates to the #to_positive_boolean_phrase method, so
|
38
|
+
# subclasses may (and probably should) choose to implement that method
|
39
|
+
# instead.
|
40
|
+
#
|
41
|
+
# ==== Returns
|
42
|
+
#
|
43
|
+
# Hash:: Representation of this restriction as solr-ruby parameters
|
44
|
+
#
|
45
|
+
def to_params
|
46
|
+
{ :filter_queries => [to_boolean_phrase] }
|
47
|
+
end
|
48
|
+
|
49
|
+
#
|
50
|
+
# Return the boolean phrase associated with this restriction object.
|
51
|
+
# Differentiates between positive and negative boolean phrases depending
|
52
|
+
# on whether this restriction is negated.
|
53
|
+
#
|
54
|
+
def to_boolean_phrase
|
55
|
+
unless negative?
|
56
|
+
to_positive_boolean_phrase
|
57
|
+
else
|
58
|
+
to_negative_boolean_phrase
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
#
|
63
|
+
# Boolean phrase representing this restriction in the positive. Subclasses
|
64
|
+
# may choose to implement this method rather than #to_params; however,
|
65
|
+
# this method delegates to the abstract #to_solr_conditional method, which
|
66
|
+
# in most cases will be what subclasses will want to implement.
|
67
|
+
# #to_solr_conditional contains the boolean phrase representing the
|
68
|
+
# condition but leaves out the field name (see built-in implementations
|
69
|
+
# for examples)
|
70
|
+
#
|
71
|
+
# ==== Returns
|
72
|
+
#
|
73
|
+
# String:: Boolean phrase for restriction in the positive
|
74
|
+
#
|
75
|
+
def to_positive_boolean_phrase
|
76
|
+
"#{@field.indexed_name}:#{to_solr_conditional}"
|
6
77
|
end
|
7
78
|
|
8
|
-
|
9
|
-
|
79
|
+
#
|
80
|
+
# Boolean phrase representing this restriction in the negative. Subclasses
|
81
|
+
# may choose to implement this method, but it is not necessary, as the
|
82
|
+
# base implementation delegates to #to_positive_boolean_phrase.
|
83
|
+
#
|
84
|
+
# ==== Returns
|
85
|
+
#
|
86
|
+
# String:: Boolean phrase for restriction in the negative
|
87
|
+
#
|
88
|
+
def to_negative_boolean_phrase
|
89
|
+
"-#{to_positive_boolean_phrase}"
|
10
90
|
end
|
11
91
|
|
12
92
|
protected
|
13
|
-
attr_accessor :field, :value
|
14
93
|
|
15
|
-
|
16
|
-
|
94
|
+
#
|
95
|
+
# Whether this restriction should be negated from its original meaning
|
96
|
+
#
|
97
|
+
def negative?
|
98
|
+
!!@negative
|
17
99
|
end
|
18
100
|
|
19
|
-
|
20
|
-
|
101
|
+
#
|
102
|
+
# Return escaped Solr API representation of given value
|
103
|
+
#
|
104
|
+
# ==== Parameters
|
105
|
+
#
|
106
|
+
# value<Object>::
|
107
|
+
# value to convert to Solr representation (default: @value)
|
108
|
+
#
|
109
|
+
# ==== Returns
|
110
|
+
#
|
111
|
+
# String:: Solr API representation of given value
|
112
|
+
#
|
113
|
+
def solr_value(value = @value)
|
114
|
+
Solr::Util.query_parser_escape(@field.to_indexed(value))
|
21
115
|
end
|
22
116
|
end
|
23
117
|
|
118
|
+
#
|
119
|
+
# Results must have field with value equal to given value. If the value
|
120
|
+
# is nil, results must have no value for the given field.
|
121
|
+
#
|
24
122
|
class EqualTo < Base
|
123
|
+
def to_positive_boolean_phrase
|
124
|
+
unless @value.nil?
|
125
|
+
super
|
126
|
+
else
|
127
|
+
"-#{@field.indexed_name}:[* TO *]"
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def to_negative_boolean_phrase
|
132
|
+
unless @value.nil?
|
133
|
+
super
|
134
|
+
else
|
135
|
+
"#{@field.indexed_name}:[* TO *]"
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
25
139
|
private
|
26
140
|
|
27
141
|
def to_solr_conditional
|
@@ -29,6 +143,9 @@ module Sunspot
|
|
29
143
|
end
|
30
144
|
end
|
31
145
|
|
146
|
+
#
|
147
|
+
# Results must have field with value less than given value
|
148
|
+
#
|
32
149
|
class LessThan < Base
|
33
150
|
private
|
34
151
|
|
@@ -37,6 +154,9 @@ module Sunspot
|
|
37
154
|
end
|
38
155
|
end
|
39
156
|
|
157
|
+
#
|
158
|
+
# Results must have field with value greater than given value
|
159
|
+
#
|
40
160
|
class GreaterThan < Base
|
41
161
|
private
|
42
162
|
|
@@ -45,27 +165,51 @@ module Sunspot
|
|
45
165
|
end
|
46
166
|
end
|
47
167
|
|
168
|
+
#
|
169
|
+
# Results must have field with value in given range
|
170
|
+
#
|
48
171
|
class Between < Base
|
49
172
|
private
|
50
173
|
|
51
174
|
def to_solr_conditional
|
52
|
-
"[#{solr_value
|
175
|
+
"[#{solr_value(@value.first)} TO #{solr_value(@value.last)}]"
|
53
176
|
end
|
54
177
|
end
|
55
178
|
|
179
|
+
#
|
180
|
+
# Results must have field with value included in given collection
|
181
|
+
#
|
56
182
|
class AnyOf < Base
|
57
183
|
private
|
58
184
|
|
59
185
|
def to_solr_conditional
|
60
|
-
"(#{value.map { |v| solr_value v } * ' OR '})"
|
186
|
+
"(#{@value.map { |v| solr_value v } * ' OR '})"
|
61
187
|
end
|
62
188
|
end
|
63
189
|
|
190
|
+
#
|
191
|
+
# Results must have field with values matching all values in given
|
192
|
+
# collection (only makes sense for fields with multiple values)
|
193
|
+
#
|
64
194
|
class AllOf < Base
|
65
195
|
private
|
66
196
|
|
67
197
|
def to_solr_conditional
|
68
|
-
"(#{value.map { |v| solr_value v } * ' AND '})"
|
198
|
+
"(#{@value.map { |v| solr_value v } * ' AND '})"
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
#
|
203
|
+
# Result must be the exact instance given (only useful when negated).
|
204
|
+
#
|
205
|
+
class SameAs < Base
|
206
|
+
def initialize(object, negative = false)
|
207
|
+
@object, @negative = object, negative
|
208
|
+
end
|
209
|
+
|
210
|
+
def to_positive_boolean_phrase
|
211
|
+
adapter = Adapters::InstanceAdapter.adapt(@object)
|
212
|
+
"id:#{Solr::Util.query_parser_escape(adapter.index_id)}"
|
69
213
|
end
|
70
214
|
end
|
71
215
|
end
|
data/lib/sunspot/search.rb
CHANGED
@@ -1,33 +1,41 @@
|
|
1
1
|
module Sunspot
|
2
|
+
#
|
3
|
+
# This class encapsulates the results of a Solr search. It provides access
|
4
|
+
# to search results, total result count, facets, and pagination information.
|
5
|
+
# Instances of Search are returned by the Sunspot.search method.
|
6
|
+
#
|
2
7
|
class Search
|
3
|
-
|
8
|
+
RawResult = Struct.new(:class_name, :primary_key)
|
4
9
|
|
5
|
-
def initialize(connection, configuration, *types, &block)
|
10
|
+
def initialize(connection, configuration, *types, &block) #:nodoc:
|
6
11
|
@connection = connection
|
7
12
|
params = types.last.is_a?(Hash) ? types.pop : {}
|
8
|
-
@query =
|
9
|
-
@builder = build_with(::Sunspot::Builder::StandardBuilder, params)
|
13
|
+
@query = Query.new(types, params, configuration)
|
10
14
|
@query.dsl.instance_eval(&block) if block
|
11
15
|
@types = types
|
12
16
|
end
|
13
17
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
query_options[:filter_queries] = query.filter_queries
|
21
|
-
query_options[:rows] = query.rows
|
22
|
-
query_options[:start] = query.start if query.start
|
23
|
-
query_options[:sort] = query.sort if query.sort
|
24
|
-
@solr_result = connection.query(query.to_solr, query_options)
|
18
|
+
#
|
19
|
+
# Execute the search on the Solr instance and store the results
|
20
|
+
#
|
21
|
+
def execute! #:nodoc:
|
22
|
+
params = @query.to_params
|
23
|
+
@solr_result = @connection.query(params.delete(:q), params)
|
25
24
|
self
|
26
25
|
end
|
27
26
|
|
27
|
+
#
|
28
|
+
# Get the collection of results as instantiated objects. If WillPaginate is
|
29
|
+
# available, the results will be a WillPaginate::Collection instance; if
|
30
|
+
# not, it will be a vanilla Array.
|
31
|
+
#
|
32
|
+
# ==== Returns
|
33
|
+
#
|
34
|
+
# WillPaginate::Collection or Array:: Instantiated result objects
|
35
|
+
#
|
28
36
|
def results
|
29
|
-
@results ||= if query.page && defined?(WillPaginate::Collection)
|
30
|
-
WillPaginate::Collection.create(query.page, query.per_page, @solr_result.total_hits) do |pager|
|
37
|
+
@results ||= if @query.page && defined?(WillPaginate::Collection)
|
38
|
+
WillPaginate::Collection.create(@query.page, @query.per_page, @solr_result.total_hits) do |pager|
|
31
39
|
pager.replace(result_objects)
|
32
40
|
end
|
33
41
|
else
|
@@ -35,32 +43,75 @@ module Sunspot
|
|
35
43
|
end
|
36
44
|
end
|
37
45
|
|
46
|
+
#
|
47
|
+
# Access raw results without instantiating objects from persistent storage.
|
48
|
+
# This may be useful if you are using search as an intermediate step in data
|
49
|
+
# retrieval. Returns an ordered collection of objects that respond to
|
50
|
+
# #class_name and #primary_key
|
51
|
+
#
|
52
|
+
# ==== Returns
|
53
|
+
#
|
54
|
+
# Array:: Ordered collection of raw results
|
55
|
+
#
|
56
|
+
def raw_results
|
57
|
+
@raw_results ||= hit_ids.map { |hit_id| RawResult.new(*hit_id.match(/([^ ]+) (.+)/)[1..2]) }
|
58
|
+
end
|
59
|
+
|
60
|
+
#
|
61
|
+
# The total number of documents matching the query parameters
|
62
|
+
#
|
63
|
+
# ==== Returns
|
64
|
+
#
|
65
|
+
# Integer:: Total matching documents
|
66
|
+
#
|
38
67
|
def total
|
39
68
|
@total ||= @solr_result.total_hits
|
40
69
|
end
|
41
70
|
|
42
|
-
|
43
|
-
|
71
|
+
#
|
72
|
+
# Get the facet object for the given field. This field will need to have
|
73
|
+
# been requested as a field facet inside the search block.
|
74
|
+
#
|
75
|
+
# ==== Parameters
|
76
|
+
#
|
77
|
+
# field_name<Symbol>:: field name for which to get the facet
|
78
|
+
#
|
79
|
+
# ==== Returns
|
80
|
+
#
|
81
|
+
# Sunspot::Facet:: Facet object for the given field
|
82
|
+
#
|
83
|
+
def facet(field_name)
|
84
|
+
(@facets_cache ||= {})[field_name.to_sym] ||=
|
85
|
+
begin
|
86
|
+
field = @query.field(field_name)
|
87
|
+
Facet.new(@solr_result.field_facets(field.indexed_name), field)
|
88
|
+
end
|
89
|
+
end
|
44
90
|
|
45
91
|
private
|
46
92
|
|
93
|
+
#
|
94
|
+
# Collection of instantiated result objects corresponding to the results
|
95
|
+
# returned by Solr.
|
96
|
+
#
|
97
|
+
# ==== Returns
|
98
|
+
#
|
99
|
+
# Array:: Collection of instantiated result objects
|
100
|
+
#
|
47
101
|
def result_objects
|
48
|
-
|
49
|
-
|
50
|
-
match = /([^ ]+) (.+)/.match hit_id
|
51
|
-
(type_id_hash[match[1]] ||= []) << match[2]
|
102
|
+
raw_results.inject({}) do |type_id_hash, raw_result|
|
103
|
+
(type_id_hash[raw_result.class_name] ||= []) << raw_result.primary_key
|
52
104
|
type_id_hash
|
53
105
|
end.inject([]) do |results, pair|
|
54
106
|
type_name, ids = pair
|
55
|
-
results.concat
|
107
|
+
results.concat(Adapters::DataAccessor.create(Util.full_const_get(type_name)).load_all(ids))
|
56
108
|
end.sort_by do |result|
|
57
|
-
hit_ids.index(::
|
109
|
+
hit_ids.index(Adapters::InstanceAdapter.adapt(result).index_id)
|
58
110
|
end
|
59
111
|
end
|
60
112
|
|
61
|
-
def
|
62
|
-
@
|
63
|
-
@types_cache[type_name] ||= type_name.split('::').inject(Module) { |namespace, name| namespace.const_get(name) }
|
113
|
+
def hit_ids
|
114
|
+
@hit_ids ||= @solr_result.hits.map { |hit| hit['id'] }
|
64
115
|
end
|
65
116
|
end
|
66
117
|
end
|