outoftime-sunspot 0.0.2 → 0.7.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/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
|