sunspot 2.1.0 → 2.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +4 -0
  3. data/History.txt +10 -0
  4. data/lib/sunspot.rb +6 -6
  5. data/lib/sunspot/batcher.rb +1 -1
  6. data/lib/sunspot/dsl.rb +1 -1
  7. data/lib/sunspot/dsl/field_query.rb +47 -7
  8. data/lib/sunspot/dsl/field_stats.rb +18 -0
  9. data/lib/sunspot/dsl/fields.rb +10 -1
  10. data/lib/sunspot/field.rb +15 -0
  11. data/lib/sunspot/field_factory.rb +33 -0
  12. data/lib/sunspot/indexer.rb +1 -0
  13. data/lib/sunspot/query.rb +1 -1
  14. data/lib/sunspot/query/common_query.rb +5 -0
  15. data/lib/sunspot/query/field_stats.rb +28 -0
  16. data/lib/sunspot/query/geofilt.rb +8 -3
  17. data/lib/sunspot/query/restriction.rb +5 -2
  18. data/lib/sunspot/query/sort.rb +35 -0
  19. data/lib/sunspot/search.rb +2 -1
  20. data/lib/sunspot/search/abstract_search.rb +57 -35
  21. data/lib/sunspot/search/field_facet.rb +4 -4
  22. data/lib/sunspot/search/field_stats.rb +21 -0
  23. data/lib/sunspot/search/stats_facet.rb +25 -0
  24. data/lib/sunspot/search/stats_row.rb +66 -0
  25. data/lib/sunspot/session.rb +6 -6
  26. data/lib/sunspot/session_proxy/class_sharding_session_proxy.rb +6 -4
  27. data/lib/sunspot/session_proxy/id_sharding_session_proxy.rb +16 -8
  28. data/lib/sunspot/setup.rb +6 -0
  29. data/lib/sunspot/version.rb +1 -1
  30. data/spec/api/class_set_spec.rb +3 -3
  31. data/spec/api/indexer/fixed_fields_spec.rb +5 -0
  32. data/spec/api/indexer/removal_spec.rb +13 -3
  33. data/spec/api/query/faceting_examples.rb +19 -0
  34. data/spec/api/query/join_spec.rb +19 -0
  35. data/spec/api/query/standard_spec.rb +1 -0
  36. data/spec/api/query/stats_examples.rb +66 -0
  37. data/spec/api/search/paginated_collection_spec.rb +1 -1
  38. data/spec/api/search/stats_spec.rb +94 -0
  39. data/spec/api/session_proxy/class_sharding_session_proxy_spec.rb +8 -2
  40. data/spec/api/session_proxy/id_sharding_session_proxy_spec.rb +14 -2
  41. data/spec/api/session_proxy/retry_5xx_session_proxy_spec.rb +3 -3
  42. data/spec/api/session_proxy/silent_fail_session_proxy_spec.rb +1 -1
  43. data/spec/helpers/search_helper.rb +30 -0
  44. data/spec/integration/highlighting_spec.rb +3 -3
  45. data/spec/integration/indexing_spec.rb +3 -2
  46. data/spec/integration/scoped_search_spec.rb +30 -0
  47. data/spec/integration/stats_spec.rb +47 -0
  48. data/spec/mocks/photo.rb +14 -1
  49. data/sunspot.gemspec +1 -2
  50. data/tasks/rdoc.rake +22 -14
  51. metadata +32 -41
@@ -1,5 +1,6 @@
1
1
  %w(abstract_search standard_search more_like_this_search query_facet field_facet
2
- date_facet range_facet facet_row hit highlight field_group group hit_enumerable).each do |file|
2
+ date_facet range_facet facet_row hit highlight field_group group hit_enumerable
3
+ stats_row field_stats stats_facet).each do |file|
3
4
  require File.join(File.dirname(__FILE__), 'search', file)
4
5
  end
5
6
 
@@ -3,24 +3,24 @@ require 'sunspot/search/hit_enumerable'
3
3
 
4
4
  module Sunspot
5
5
  module Search #:nodoc:
6
-
7
- #
6
+
7
+ #
8
8
  # This class encapsulates the results of a Solr search. It provides access
9
9
  # to search results, total result count, facets, and pagination information.
10
10
  # Instances of Search are returned by the Sunspot.search and
11
11
  # Sunspot.new_search methods.
12
12
  #
13
13
  class AbstractSearch
14
- #
14
+ #
15
15
  # Retrieve all facet objects defined for this search, in order they were
16
16
  # defined. To retrieve an individual facet by name, use #facet()
17
17
  #
18
- attr_reader :facets, :groups
18
+ attr_reader :facets, :groups, :stats
19
19
  attr_reader :query #:nodoc:
20
20
  attr_accessor :request_handler
21
21
 
22
22
  include HitEnumerable
23
-
23
+
24
24
  def initialize(connection, setup, query, configuration) #:nodoc:
25
25
  @connection, @setup, @query = connection, setup, query
26
26
  @query.paginate(1, configuration.pagination.default_per_page)
@@ -30,8 +30,11 @@ module Sunspot
30
30
 
31
31
  @groups_by_name = {}
32
32
  @groups = []
33
+
34
+ @stats_by_name = {}
35
+ @stats = []
33
36
  end
34
-
37
+
35
38
  #
36
39
  # Execute the search on the Solr instance and store the results. If you
37
40
  # use Sunspot#search() to construct your searches, there is no need to call
@@ -49,8 +52,8 @@ module Sunspot
49
52
  def execute! #:nodoc: deprecated
50
53
  execute
51
54
  end
52
-
53
- #
55
+
56
+ #
54
57
  # Get the collection of results as instantiated objects. If WillPaginate is
55
58
  # available, the results will be a WillPaginate::Collection instance; if
56
59
  # not, it will be a vanilla Array.
@@ -65,8 +68,8 @@ module Sunspot
65
68
  def results
66
69
  @results ||= paginate_collection(verified_hits.map { |hit| hit.instance })
67
70
  end
68
-
69
- #
71
+
72
+ #
70
73
  # Access raw Solr result information. Returns a collection of Hit objects
71
74
  # that contain the class name, primary key, keyword relevance score (if
72
75
  # applicable), and any stored fields.
@@ -92,7 +95,7 @@ module Sunspot
92
95
  end
93
96
  alias_method :raw_results, :hits
94
97
 
95
- #
98
+ #
96
99
  # The total number of documents matching the query parameters
97
100
  #
98
101
  # ==== Returns
@@ -102,8 +105,8 @@ module Sunspot
102
105
  def total
103
106
  @total ||= solr_response['numFound'] || 0
104
107
  end
105
-
106
- #
108
+
109
+ #
107
110
  # The time elapsed to generate the Solr response
108
111
  #
109
112
  # ==== Returns
@@ -113,8 +116,8 @@ module Sunspot
113
116
  def query_time
114
117
  @query_time ||= solr_response_header['QTime']
115
118
  end
116
-
117
- #
119
+
120
+ #
118
121
  # Get the facet object for the given name. `name` can either be the name
119
122
  # given to a query facet, or the field name of a field facet. Returns a
120
123
  # Sunspot::Facet object.
@@ -164,28 +167,38 @@ module Sunspot
164
167
  end
165
168
  end
166
169
 
170
+ def stats(name)
171
+ if name
172
+ @stats_by_name[name.to_sym]
173
+ end
174
+ end
175
+
167
176
  def group(name)
168
177
  if name
169
178
  @groups_by_name[name.to_sym]
170
179
  end
171
180
  end
172
-
173
- #
181
+
182
+ #
174
183
  # Deprecated in favor of optional second argument to #facet
175
184
  #
176
185
  def dynamic_facet(base_name, dynamic_name) #:nodoc:
177
186
  facet(base_name, dynamic_name)
178
187
  end
179
-
188
+
180
189
  def facet_response #:nodoc:
181
190
  @solr_result['facet_counts']
182
191
  end
183
192
 
193
+ def stats_response #:nodoc:
194
+ @solr_result['stats']['stats_fields']
195
+ end
196
+
184
197
  def group_response #:nodoc:
185
198
  @solr_result['grouped']
186
199
  end
187
-
188
- #
200
+
201
+ #
189
202
  # Build this search using a DSL block. This method can be called more than
190
203
  # once on an unexecuted search (e.g., Sunspot.new_search) in order to build
191
204
  # a search incrementally.
@@ -202,8 +215,8 @@ module Sunspot
202
215
  Util.instance_eval_or_call(dsl, &block)
203
216
  self
204
217
  end
205
-
206
-
218
+
219
+
207
220
  def inspect #:nodoc:
208
221
  "<Sunspot::Search:#{query.to_params.inspect}>"
209
222
  end
@@ -211,16 +224,16 @@ module Sunspot
211
224
  def add_field_group(field) #:nodoc:
212
225
  add_group(field.name, FieldGroup.new(field, self))
213
226
  end
214
-
227
+
215
228
  def add_field_facet(field, options = {}) #:nodoc:
216
229
  name = (options[:name] || field.name)
217
230
  add_facet(name, FieldFacet.new(field, self, options))
218
231
  end
219
-
232
+
220
233
  def add_query_facet(name, options) #:nodoc:
221
234
  add_facet(name, QueryFacet.new(name, self, options))
222
235
  end
223
-
236
+
224
237
  def add_date_facet(field, options) #:nodoc:
225
238
  name = (options[:name] || field.name)
226
239
  add_facet(name, DateFacet.new(field, self, options))
@@ -231,26 +244,30 @@ module Sunspot
231
244
  add_facet(name, RangeFacet.new(field, self, options))
232
245
  end
233
246
 
247
+ def add_field_stats(field) #:nodoc:
248
+ add_stats(field.name, FieldStats.new(field, self))
249
+ end
250
+
234
251
  def highlights_for(doc) #:nodoc:
235
252
  if @solr_result['highlighting']
236
253
  @solr_result['highlighting'][doc['id']]
237
254
  end
238
255
  end
239
-
256
+
240
257
  private
241
-
258
+
242
259
  def dsl
243
- raise NotImplementedError
260
+ raise NotImplementedError
244
261
  end
245
-
262
+
246
263
  def execute_request(params)
247
264
  raise NotImplementedError
248
265
  end
249
-
266
+
250
267
  def solr_response
251
268
  @solr_response ||= @solr_result['response'] || {}
252
269
  end
253
-
270
+
254
271
  def solr_response_header
255
272
  @solr_response_header ||= @solr_result['responseHeader'] || {}
256
273
  end
@@ -258,25 +275,30 @@ module Sunspot
258
275
  def solr_docs
259
276
  solr_response['docs']
260
277
  end
261
-
278
+
262
279
  def verified_hits
263
280
  @verified_hits ||= paginate_collection(super)
264
281
  end
265
-
282
+
266
283
  def paginate_collection(collection)
267
284
  PaginatedCollection.new(collection, @query.page, @query.per_page, total)
268
285
  end
269
-
286
+
270
287
  def add_facet(name, facet)
271
288
  @facets << facet
272
289
  @facets_by_name[name.to_sym] = facet
273
290
  end
274
291
 
292
+ def add_stats(name, stats)
293
+ @stats << stats
294
+ @stats_by_name[name.to_sym] = stats
295
+ end
296
+
275
297
  def add_group(name, group)
276
298
  @groups << group
277
299
  @groups_by_name[name.to_sym] = group
278
300
  end
279
-
301
+
280
302
  # Clear out all the cached ivars so the search can be called again.
281
303
  def reset
282
304
  @results = @hits = @verified_hits = @total = @solr_response = @doc_ids = nil
@@ -1,6 +1,6 @@
1
1
  module Sunspot
2
2
  module Search
3
- #
3
+ #
4
4
  # A FieldFacet is a facet whose rows are all values for a certain field, in
5
5
  # contrast to a QueryFacet, whose rows represent arbitrary queries.
6
6
  #
@@ -14,7 +14,7 @@ module Sunspot
14
14
  @field.name
15
15
  end
16
16
 
17
- #
17
+ #
18
18
  # Get the rows returned for this facet.
19
19
  #
20
20
  # ==== Options (options)
@@ -50,7 +50,7 @@ module Sunspot
50
50
  end
51
51
  end
52
52
 
53
- #
53
+ #
54
54
  # If this facet references a model class, populate the rows with instances
55
55
  # of the model class by loading them out of the appropriate adapter.
56
56
  #
@@ -79,7 +79,7 @@ module Sunspot
79
79
  rows
80
80
  end
81
81
  end
82
-
82
+
83
83
  def key
84
84
  @key ||= (@options[:name] || @field.indexed_name).to_s
85
85
  end
@@ -0,0 +1,21 @@
1
+ module Sunspot
2
+ module Search
3
+ class FieldStats < StatsRow
4
+ def initialize(field, search) #:nodoc:
5
+ @field, @search, @facet_fields = field, search, []
6
+ end
7
+
8
+ def add_facet field
9
+ @facet_fields << field
10
+ end
11
+
12
+ def field_name
13
+ @field.name
14
+ end
15
+
16
+ def data
17
+ @search.stats_response[@field.indexed_name]
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,25 @@
1
+ module Sunspot
2
+ module Search
3
+ class StatsFacet < FieldFacet
4
+ attr_reader :field
5
+
6
+ def initialize(field, data) #:nodoc:
7
+ @field, @data = field, data
8
+ end
9
+
10
+ def rows(options = {})
11
+ if options[:verified]
12
+ verified_rows
13
+ else
14
+ @rows ||= @data.map do |value, data|
15
+ StatsRow.new(data, self, @field.type.cast(value))
16
+ end.sort_by { |row| row.value.to_s }
17
+ end
18
+ end
19
+
20
+ def inspect
21
+ "<Sunspot::Search::StatsFacet:#{@field}>"
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,66 @@
1
+ module Sunspot
2
+ module Search
3
+ class StatsRow
4
+ attr_reader :data, :value
5
+ attr_writer :instance #:nodoc:
6
+
7
+ def initialize(data, facet = nil, value = nil) #:nodoc:
8
+ @data, @facet, @value = data, facet, value
9
+ @facet_fields = []
10
+ end
11
+
12
+ def min
13
+ data['min']
14
+ end
15
+
16
+ def max
17
+ data['max']
18
+ end
19
+
20
+ def count
21
+ data['count']
22
+ end
23
+
24
+ def sum
25
+ data['sum']
26
+ end
27
+
28
+ def missing
29
+ data['missing']
30
+ end
31
+
32
+ def sum_of_squares
33
+ data['sumOfSquares']
34
+ end
35
+
36
+ def mean
37
+ data['mean']
38
+ end
39
+
40
+ def standard_deviation
41
+ data['stddev']
42
+ end
43
+
44
+ def facet name
45
+ facets.find { |facet| facet.field.name == name.to_sym }
46
+ end
47
+
48
+ def facets
49
+ @facets ||= @facet_fields.map do |field|
50
+ StatsFacet.new(field, data['facets'][field.indexed_name])
51
+ end
52
+ end
53
+
54
+ def instance
55
+ if !defined?(@instance)
56
+ @facet.populate_instances
57
+ end
58
+ @instance
59
+ end
60
+
61
+ def inspect
62
+ "<Sunspot::Search::StatsRow:#{value.inspect} min=#{min} max=#{max} count=#{count}>"
63
+ end
64
+ end
65
+ end
66
+ end
@@ -142,29 +142,29 @@ module Sunspot
142
142
  #
143
143
  # See Sunspot.remove!
144
144
  #
145
- def remove!(*objects)
146
- remove(*objects)
145
+ def remove!(*objects, &block)
146
+ remove(*objects, &block)
147
147
  commit
148
148
  end
149
149
 
150
150
  #
151
151
  # See Sunspot.remove_by_id
152
152
  #
153
- def remove_by_id(clazz, id)
153
+ def remove_by_id(clazz, *ids)
154
154
  class_name =
155
155
  if clazz.is_a?(Class)
156
156
  clazz.name
157
157
  else
158
158
  clazz.to_s
159
159
  end
160
- indexer.remove_by_id(class_name, id)
160
+ indexer.remove_by_id(class_name, ids)
161
161
  end
162
162
 
163
163
  #
164
164
  # See Sunspot.remove_by_id!
165
165
  #
166
- def remove_by_id!(clazz, id)
167
- remove_by_id(clazz, id)
166
+ def remove_by_id!(clazz, *ids)
167
+ remove_by_id(clazz, ids)
168
168
  commit
169
169
  end
170
170
 
@@ -23,15 +23,17 @@ module Sunspot
23
23
  #
24
24
  # See Sunspot.remove_by_id
25
25
  #
26
- def remove_by_id(clazz, id)
27
- session_for_class(clazz).remove_by_id(clazz, id)
26
+ def remove_by_id(clazz, *ids)
27
+ ids.flatten!
28
+ session_for_class(clazz).remove_by_id(clazz, ids)
28
29
  end
29
30
 
30
31
  #
31
32
  # See Sunspot.remove_by_id!
32
33
  #
33
- def remove_by_id!(clazz, id)
34
- session_for_class(clazz).remove_by_id!(clazz, id)
34
+ def remove_by_id!(clazz, *ids)
35
+ ids.flatten!
36
+ session_for_class(clazz).remove_by_id!(clazz, ids)
35
37
  end
36
38
 
37
39
  #