sunspot 2.1.0 → 2.1.1

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.
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
  #