sunspot 2.0.0 → 2.5.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.
Files changed (165) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +1 -0
  3. data/.rspec +2 -0
  4. data/Appraisals +7 -0
  5. data/Gemfile +0 -2
  6. data/History.txt +10 -0
  7. data/lib/sunspot.rb +55 -17
  8. data/lib/sunspot/adapters.rb +68 -18
  9. data/lib/sunspot/batcher.rb +1 -1
  10. data/lib/sunspot/configuration.rb +4 -2
  11. data/lib/sunspot/data_extractor.rb +36 -6
  12. data/lib/sunspot/dsl.rb +4 -3
  13. data/lib/sunspot/dsl/adjustable.rb +2 -2
  14. data/lib/sunspot/dsl/field_query.rb +69 -16
  15. data/lib/sunspot/dsl/field_stats.rb +25 -0
  16. data/lib/sunspot/dsl/fields.rb +28 -8
  17. data/lib/sunspot/dsl/fulltext.rb +9 -1
  18. data/lib/sunspot/dsl/group.rb +118 -0
  19. data/lib/sunspot/dsl/paginatable.rb +4 -1
  20. data/lib/sunspot/dsl/scope.rb +19 -10
  21. data/lib/sunspot/dsl/search.rb +1 -1
  22. data/lib/sunspot/dsl/spellcheckable.rb +14 -0
  23. data/lib/sunspot/dsl/standard_query.rb +63 -35
  24. data/lib/sunspot/field.rb +76 -4
  25. data/lib/sunspot/field_factory.rb +60 -11
  26. data/lib/sunspot/indexer.rb +70 -18
  27. data/lib/sunspot/query.rb +5 -4
  28. data/lib/sunspot/query/abstract_field_facet.rb +0 -2
  29. data/lib/sunspot/query/abstract_fulltext.rb +76 -0
  30. data/lib/sunspot/query/abstract_json_field_facet.rb +70 -0
  31. data/lib/sunspot/query/bbox.rb +5 -1
  32. data/lib/sunspot/query/common_query.rb +31 -6
  33. data/lib/sunspot/query/composite_fulltext.rb +58 -8
  34. data/lib/sunspot/query/date_field_json_facet.rb +25 -0
  35. data/lib/sunspot/query/dismax.rb +25 -71
  36. data/lib/sunspot/query/field_json_facet.rb +19 -0
  37. data/lib/sunspot/query/field_list.rb +15 -0
  38. data/lib/sunspot/query/field_stats.rb +61 -0
  39. data/lib/sunspot/query/function_query.rb +1 -2
  40. data/lib/sunspot/query/geo.rb +1 -1
  41. data/lib/sunspot/query/geofilt.rb +8 -3
  42. data/lib/sunspot/query/group.rb +46 -0
  43. data/lib/sunspot/query/group_query.rb +17 -0
  44. data/lib/sunspot/query/join.rb +88 -0
  45. data/lib/sunspot/query/more_like_this.rb +1 -1
  46. data/lib/sunspot/query/pagination.rb +12 -4
  47. data/lib/sunspot/query/range_json_facet.rb +28 -0
  48. data/lib/sunspot/query/restriction.rb +99 -13
  49. data/lib/sunspot/query/sort.rb +41 -0
  50. data/lib/sunspot/query/sort_composite.rb +7 -0
  51. data/lib/sunspot/query/spellcheck.rb +19 -0
  52. data/lib/sunspot/query/standard_query.rb +24 -2
  53. data/lib/sunspot/query/text_field_boost.rb +1 -3
  54. data/lib/sunspot/schema.rb +12 -3
  55. data/lib/sunspot/search.rb +4 -2
  56. data/lib/sunspot/search/abstract_search.rb +93 -43
  57. data/lib/sunspot/search/cursor_paginated_collection.rb +32 -0
  58. data/lib/sunspot/search/field_facet.rb +4 -4
  59. data/lib/sunspot/search/field_json_facet.rb +33 -0
  60. data/lib/sunspot/search/field_stats.rb +21 -0
  61. data/lib/sunspot/search/hit.rb +6 -1
  62. data/lib/sunspot/search/hit_enumerable.rb +4 -1
  63. data/lib/sunspot/search/json_facet_row.rb +40 -0
  64. data/lib/sunspot/search/json_facet_stats.rb +23 -0
  65. data/lib/sunspot/search/paginated_collection.rb +1 -0
  66. data/lib/sunspot/search/query_group.rb +74 -0
  67. data/lib/sunspot/search/standard_search.rb +70 -3
  68. data/lib/sunspot/search/stats_facet.rb +25 -0
  69. data/lib/sunspot/search/stats_json_row.rb +82 -0
  70. data/lib/sunspot/search/stats_row.rb +68 -0
  71. data/lib/sunspot/session.rb +62 -37
  72. data/lib/sunspot/session_proxy/class_sharding_session_proxy.rb +6 -4
  73. data/lib/sunspot/session_proxy/id_sharding_session_proxy.rb +16 -8
  74. data/lib/sunspot/session_proxy/master_slave_session_proxy.rb +2 -2
  75. data/lib/sunspot/session_proxy/retry_5xx_session_proxy.rb +1 -1
  76. data/lib/sunspot/session_proxy/sharding_session_proxy.rb +4 -2
  77. data/lib/sunspot/session_proxy/silent_fail_session_proxy.rb +1 -1
  78. data/lib/sunspot/session_proxy/thread_local_session_proxy.rb +6 -4
  79. data/lib/sunspot/setup.rb +42 -0
  80. data/lib/sunspot/type.rb +20 -0
  81. data/lib/sunspot/util.rb +78 -14
  82. data/lib/sunspot/version.rb +1 -1
  83. data/spec/api/adapters_spec.rb +40 -15
  84. data/spec/api/batcher_spec.rb +15 -15
  85. data/spec/api/binding_spec.rb +3 -3
  86. data/spec/api/class_set_spec.rb +6 -6
  87. data/spec/api/data_extractor_spec.rb +39 -0
  88. data/spec/api/hit_enumerable_spec.rb +32 -9
  89. data/spec/api/indexer/attributes_spec.rb +35 -30
  90. data/spec/api/indexer/batch_spec.rb +8 -7
  91. data/spec/api/indexer/dynamic_fields_spec.rb +8 -8
  92. data/spec/api/indexer/fixed_fields_spec.rb +16 -11
  93. data/spec/api/indexer/fulltext_spec.rb +8 -8
  94. data/spec/api/indexer/removal_spec.rb +24 -14
  95. data/spec/api/indexer_spec.rb +2 -2
  96. data/spec/api/query/advanced_manipulation_examples.rb +3 -3
  97. data/spec/api/query/connectives_examples.rb +26 -14
  98. data/spec/api/query/dsl_spec.rb +24 -6
  99. data/spec/api/query/dynamic_fields_examples.rb +18 -18
  100. data/spec/api/query/faceting_examples.rb +80 -61
  101. data/spec/api/query/fulltext_examples.rb +194 -40
  102. data/spec/api/query/function_spec.rb +116 -13
  103. data/spec/api/query/geo_examples.rb +8 -12
  104. data/spec/api/query/group_spec.rb +27 -5
  105. data/spec/api/query/highlighting_examples.rb +26 -26
  106. data/spec/api/query/join_spec.rb +19 -0
  107. data/spec/api/query/more_like_this_spec.rb +40 -27
  108. data/spec/api/query/ordering_pagination_examples.rb +37 -23
  109. data/spec/api/query/scope_examples.rb +39 -39
  110. data/spec/api/query/spatial_examples.rb +3 -3
  111. data/spec/api/query/spellcheck_examples.rb +20 -0
  112. data/spec/api/query/standard_spec.rb +3 -1
  113. data/spec/api/query/stats_examples.rb +66 -0
  114. data/spec/api/query/text_field_scoping_examples.rb +5 -5
  115. data/spec/api/query/types_spec.rb +4 -4
  116. data/spec/api/search/cursor_paginated_collection_spec.rb +35 -0
  117. data/spec/api/search/dynamic_fields_spec.rb +4 -4
  118. data/spec/api/search/faceting_spec.rb +55 -52
  119. data/spec/api/search/highlighting_spec.rb +7 -7
  120. data/spec/api/search/hits_spec.rb +43 -29
  121. data/spec/api/search/paginated_collection_spec.rb +19 -18
  122. data/spec/api/search/results_spec.rb +13 -13
  123. data/spec/api/search/search_spec.rb +3 -3
  124. data/spec/api/search/stats_spec.rb +94 -0
  125. data/spec/api/session_proxy/class_sharding_session_proxy_spec.rb +23 -16
  126. data/spec/api/session_proxy/id_sharding_session_proxy_spec.rb +16 -4
  127. data/spec/api/session_proxy/master_slave_session_proxy_spec.rb +10 -6
  128. data/spec/api/session_proxy/retry_5xx_session_proxy_spec.rb +11 -11
  129. data/spec/api/session_proxy/sharding_session_proxy_spec.rb +15 -14
  130. data/spec/api/session_proxy/silent_fail_session_proxy_spec.rb +3 -3
  131. data/spec/api/session_proxy/spec_helper.rb +1 -1
  132. data/spec/api/session_proxy/thread_local_session_proxy_spec.rb +40 -26
  133. data/spec/api/session_spec.rb +78 -38
  134. data/spec/api/sunspot_spec.rb +7 -4
  135. data/spec/helpers/integration_helper.rb +11 -1
  136. data/spec/helpers/query_helper.rb +1 -1
  137. data/spec/helpers/search_helper.rb +30 -0
  138. data/spec/integration/atomic_updates_spec.rb +58 -0
  139. data/spec/integration/dynamic_fields_spec.rb +31 -20
  140. data/spec/integration/faceting_spec.rb +252 -39
  141. data/spec/integration/field_grouping_spec.rb +47 -15
  142. data/spec/integration/field_lists_spec.rb +57 -0
  143. data/spec/integration/geospatial_spec.rb +34 -8
  144. data/spec/integration/highlighting_spec.rb +8 -8
  145. data/spec/integration/indexing_spec.rb +7 -6
  146. data/spec/integration/join_spec.rb +45 -0
  147. data/spec/integration/keyword_search_spec.rb +68 -38
  148. data/spec/integration/local_search_spec.rb +4 -4
  149. data/spec/integration/more_like_this_spec.rb +7 -7
  150. data/spec/integration/scoped_search_spec.rb +193 -74
  151. data/spec/integration/spellcheck_spec.rb +119 -0
  152. data/spec/integration/stats_spec.rb +88 -0
  153. data/spec/integration/stored_fields_spec.rb +1 -1
  154. data/spec/integration/test_pagination.rb +4 -4
  155. data/spec/integration/unicode_spec.rb +1 -1
  156. data/spec/mocks/adapters.rb +36 -0
  157. data/spec/mocks/connection.rb +5 -3
  158. data/spec/mocks/photo.rb +32 -1
  159. data/spec/mocks/post.rb +18 -3
  160. data/spec/spec_helper.rb +13 -8
  161. data/sunspot.gemspec +6 -4
  162. data/tasks/rdoc.rake +22 -14
  163. metadata +101 -44
  164. data/lib/sunspot/dsl/field_group.rb +0 -57
  165. data/lib/sunspot/query/field_group.rb +0 -37
@@ -0,0 +1,32 @@
1
+ module Sunspot
2
+ module Search
3
+
4
+ class CursorPaginatedCollection < Array
5
+ attr_reader :per_page, :total_count, :current_cursor, :next_page_cursor
6
+ alias :total_entries :total_count
7
+ alias :limit_value :per_page
8
+
9
+ def initialize(collection, per_page, total, current_cursor, next_page_cursor)
10
+ @per_page = per_page
11
+ @total_count = total
12
+ @current_cursor = current_cursor
13
+ @next_page_cursor = next_page_cursor
14
+
15
+ replace collection
16
+ end
17
+
18
+ def total_pages
19
+ (total_count.to_f / per_page).ceil
20
+ end
21
+ alias :num_pages :total_pages
22
+
23
+ def first_page?
24
+ current_cursor == '*'
25
+ end
26
+
27
+ def last_page?
28
+ count < per_page
29
+ end
30
+ end
31
+ end
32
+ end
@@ -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,33 @@
1
+ module Sunspot
2
+ module Search
3
+ class FieldJsonFacet
4
+
5
+ attr_reader :name
6
+
7
+ def initialize(field, search, options)
8
+ @name, @search, @options = name, search, options
9
+ @field = field
10
+ end
11
+
12
+ def rows
13
+ @rows ||=
14
+ begin
15
+ json_facet_response = @search.json_facet_response[@field.name.to_s]
16
+ data = json_facet_response.nil? ? [] : json_facet_response['buckets']
17
+ rows = []
18
+ data.each do |d|
19
+ rows << JsonFacetRow.new(d, self)
20
+ end
21
+
22
+ if @options[:sort] == :count
23
+ rows.sort! { |lrow, rrow| rrow.count <=> lrow.count }
24
+ else
25
+ rows.sort! { |lrow, rrow| lrow.value <=> rrow.value }
26
+ end
27
+ rows
28
+ end
29
+
30
+ end
31
+ end
32
+ end
33
+ 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
@@ -17,6 +17,10 @@ module Sunspot
17
17
  # Class name of object associated with this hit, as string.
18
18
  #
19
19
  attr_reader :class_name
20
+ #
21
+ # ID prefix used for compositeId shard router
22
+ #
23
+ attr_reader :id_prefix
20
24
  #
21
25
  # Keyword relevance score associated with this result. Nil if this hit
22
26
  # is not from a keyword search.
@@ -27,7 +31,8 @@ module Sunspot
27
31
  attr_writer :result #:nodoc:
28
32
 
29
33
  def initialize(raw_hit, highlights, search) #:nodoc:
30
- @class_name, @primary_key = *raw_hit['id'].match(/([^ ]+) (.+)/)[1..2]
34
+ @id_prefix, @class_name, @primary_key =
35
+ *raw_hit['id'].match(/((?:[^!]+!)+)*([^\s]+)\s(.+)/)[1..3]
31
36
  @score = raw_hit['score']
32
37
  @search = search
33
38
  @stored_values = raw_hit
@@ -11,6 +11,9 @@ module Sunspot
11
11
  end
12
12
  end
13
13
 
14
+ #
15
+ # Returns all of the hits that have a result
16
+ #
14
17
  def verified_hits
15
18
  hits.select { |h| h.result }
16
19
  end
@@ -31,7 +34,7 @@ module Sunspot
31
34
  hits_for_class = id_hit_hash[class_name]
32
35
  data_accessor.load_all(ids).each do |result|
33
36
  hit = hits_for_class.delete(Adapters::InstanceAdapter.adapt(result).id.to_s)
34
- hit.result = result
37
+ hit.result = result if hit
35
38
  end
36
39
  hits_for_class.values.each { |hit| hit.result = nil }
37
40
  end
@@ -0,0 +1,40 @@
1
+ module Sunspot
2
+ module Search
3
+ class JsonFacetRow
4
+ attr_reader :value, :count, :nested
5
+ attr_writer :instance #:nodoc:
6
+
7
+ def initialize(data, facet) #:nodoc:
8
+ @value = data['val']
9
+ @count = data['distinct'] || data['count']
10
+ @facet = facet
11
+ @nested_key = data.keys.select { |k| data[k].is_a?(Hash) }.first
12
+ @nested = recursive_nested_initialization(data) unless @nested_key.nil?
13
+ end
14
+
15
+ #
16
+ # Return the instance referenced by this facet row. Only valid for field
17
+ # facets whose fields are defined with the :references key.
18
+ #
19
+ def instance
20
+ if !defined?(@instance)
21
+ @facet.populate_instances
22
+ end
23
+ @instance
24
+ end
25
+
26
+ def inspect
27
+ "<Sunspot::Search::FacetRow:#{value.inspect} (#{count}) #{nested.nil? ? '' : " nested_count=#{nested.size}"}>"
28
+ end
29
+
30
+ private
31
+
32
+ def recursive_nested_initialization(data)
33
+ data[@nested_key]['buckets'].map do |d|
34
+ JsonFacetRow.new(d, @facet)
35
+ end
36
+ end
37
+
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,23 @@
1
+ module Sunspot
2
+ module Search
3
+ class JsonFacetStats
4
+ def initialize(field, search, options)
5
+ @field, @search, @options = field, search, options
6
+ end
7
+
8
+ def rows
9
+ @rows ||=
10
+ begin
11
+ json_facet_response = @search.json_facet_response[@field.to_s]
12
+ data = json_facet_response.nil? ? [] : json_facet_response['buckets']
13
+ rows = []
14
+ data.each do |d|
15
+ rows << StatsJsonRow.new(d, nil, d['val'])
16
+ end
17
+ rows
18
+ end
19
+
20
+ end
21
+ end
22
+ end
23
+ end
@@ -32,6 +32,7 @@ module Sunspot
32
32
  def previous_page
33
33
  current_page > 1 ? (current_page - 1) : nil
34
34
  end
35
+ alias :prev_page :previous_page
35
36
 
36
37
  def next_page
37
38
  current_page < total_pages ? (current_page + 1) : nil
@@ -0,0 +1,74 @@
1
+ require 'sunspot/search/paginated_collection'
2
+
3
+ module Sunspot
4
+ module Search
5
+ class QueryGroup
6
+ def initialize(queries, search) #:nodoc:
7
+ @queries, @search = queries, search
8
+ end
9
+
10
+ def groups
11
+ @groups ||=
12
+ if solr_response_for_first
13
+ groups = @queries.map { |query| Group.new(query.label, doclist_for(query), @search) }
14
+
15
+ paginate_collection(groups)
16
+ end
17
+ end
18
+
19
+ def matches
20
+ if solr_response_for_first
21
+ solr_response_for_first['matches'].to_i
22
+ end
23
+ end
24
+
25
+ def total
26
+ @queries.count
27
+ end
28
+
29
+ #
30
+ # It populates all grouped hits at once.
31
+ # Useful for eager loading fall grouped results at once.
32
+ #
33
+ def populate_all_hits
34
+ # Init a 2 dimension Hash that contains an array per key
35
+ id_hit_hash = Hash.new { |hash, key| hash[key] = Hash.new{ |h, k| h[k] = [] } }
36
+ groups.each do |g|
37
+ # Take all hits to being populated later on
38
+ g.hits.each do |hit|
39
+ id_hit_hash[hit.class_name][hit.primary_key] |= [hit]
40
+ end
41
+ end
42
+ # Go for each class and load the results' objects into each of the hits
43
+ id_hit_hash.each_pair do |class_name, many_hits|
44
+ ids = many_hits.keys
45
+ data_accessor = @search.data_accessor_for(Util.full_const_get(class_name))
46
+ hits_for_class = id_hit_hash[class_name]
47
+ data_accessor.load_all(ids).each do |result|
48
+ hits = hits_for_class.delete(Adapters::InstanceAdapter.adapt(result).id.to_s)
49
+ hits.each{ |hit| hit.result = result }
50
+ end
51
+ hits_for_class.values.each { |hits| hits.each{|hit| hit.result = nil } }
52
+ end
53
+ end
54
+
55
+ private
56
+
57
+ def doclist_for(query)
58
+ solr_response_for(query.to_boolean_phrase)['doclist']
59
+ end
60
+
61
+ def solr_response_for(query_string)
62
+ @search.group_response[query_string]
63
+ end
64
+
65
+ def solr_response_for_first
66
+ solr_response_for(@queries[0].to_boolean_phrase)
67
+ end
68
+
69
+ def paginate_collection(collection)
70
+ PaginatedCollection.new(collection, @search.query.page, @search.query.per_page, total)
71
+ end
72
+ end
73
+ end
74
+ end
@@ -1,6 +1,6 @@
1
1
  module Sunspot
2
2
  module Search
3
- #
3
+ #
4
4
  # This class encapsulates the results of a Solr search. It provides access
5
5
  # to search results, total result count, facets, and pagination information.
6
6
  # Instances of Search are returned by the Sunspot.search and
@@ -10,9 +10,76 @@ module Sunspot
10
10
  def request_handler
11
11
  super || :select
12
12
  end
13
-
13
+
14
+ # Return the raw spellcheck block from the Solr response
15
+ def solr_spellcheck
16
+ @solr_spellcheck ||= @solr_result['spellcheck'] || {}
17
+ end
18
+
19
+ # Reformat the oddly-formatted spellcheck suggestion array into a
20
+ # more useful hash.
21
+ #
22
+ # Original: [term, suggestion, term, suggestion, ..., "correctlySpelled", bool, "collation", str]
23
+ # "collation" is only included if spellcheck.collation was set to true
24
+ # Returns: { term => suggestion, term => suggestion }
25
+ def spellcheck_suggestions
26
+ unless defined?(@spellcheck_suggestions)
27
+ @spellcheck_suggestions = {}
28
+ count = ((solr_spellcheck['suggestions'] || []).length) / 2
29
+ (0..(count - 1)).each do |i|
30
+ break if ["correctlySpelled", "collation"].include? solr_spellcheck[i]
31
+ term = solr_spellcheck['suggestions'][i * 2]
32
+ suggestion = solr_spellcheck['suggestions'][(i * 2) + 1]
33
+ @spellcheck_suggestions[term] = suggestion
34
+ end
35
+ end
36
+ @spellcheck_suggestions
37
+ end
38
+
39
+ # Return the suggestion with the single highest frequency.
40
+ # Requires the extended results format.
41
+ def spellcheck_suggestion_for(term)
42
+ spellcheck_suggestions[term]['suggestion'].sort_by do |suggestion|
43
+ suggestion['freq']
44
+ end.last['word']
45
+ end
46
+
47
+ # Provide a collated query. If the user provides a query string,
48
+ # tokenize it on whitespace and replace terms strictly not present in
49
+ # the index. Otherwise return Solr's suggested collation.
50
+ #
51
+ # Solr's suggested collation is more liberal, replacing even terms that
52
+ # are present in the index.
53
+ #
54
+ # Mix and match in your views for a blend of strict and liberal collations.
55
+ def spellcheck_collation(*terms)
56
+ if solr_spellcheck['suggestions'] && solr_spellcheck['suggestions'].length > 0
57
+ collation = terms.join(" ").dup if terms
58
+
59
+ # If we are given a query string, tokenize it and strictly replace
60
+ # the terms that aren't present in the index
61
+ if terms.length > 0
62
+ terms.each do |term|
63
+ if (spellcheck_suggestions[term]||{})['origFreq'] == 0
64
+ collation[term] = spellcheck_suggestion_for(term)
65
+ end
66
+ end
67
+ end
68
+
69
+ # If no query was given, or all terms are present in the index,
70
+ # return Solr's suggested collation.
71
+ if terms.length == 0
72
+ collation = solr_spellcheck['collations'][-1]
73
+ end
74
+
75
+ collation
76
+ else
77
+ nil
78
+ end
79
+ end
80
+
14
81
  private
15
-
82
+
16
83
  def dsl
17
84
  DSL::Search.new(self, @setup)
18
85
  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,82 @@
1
+ module Sunspot
2
+ module Search
3
+ class StatsJsonRow
4
+ attr_reader :data, :value, :nested
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
+ @nested_key = data.keys.select { |k| data[k].is_a?(Hash) }.first
11
+ @nested = recursive_nested_initialization(data) unless @nested_key.nil?
12
+ end
13
+
14
+ def min
15
+ data['min']
16
+ end
17
+
18
+ def max
19
+ data['max']
20
+ end
21
+
22
+ def count
23
+ data['count']
24
+ end
25
+
26
+ def sum
27
+ data['sum']
28
+ end
29
+
30
+ def missing
31
+ data['missing']
32
+ end
33
+
34
+ def sum_of_squares
35
+ data['sumsq']
36
+ end
37
+ alias :sumsq :sum_of_squares
38
+
39
+ def mean
40
+ data['avg']
41
+ end
42
+ alias :avg :mean
43
+
44
+ def standard_deviation
45
+ data['stddev']
46
+ end
47
+
48
+ def facet name
49
+ facets.find { |facet| facet.field.name == name.to_sym }
50
+ end
51
+
52
+ def facets
53
+ @facets ||= @facet_fields.map do |field|
54
+ StatsFacet.new(field, data['facets'][field.indexed_name])
55
+ end
56
+ end
57
+
58
+ def instance
59
+ if !defined?(@instance)
60
+ @facet.populate_instances
61
+ end
62
+ @instance
63
+ end
64
+
65
+ def inspect
66
+ "<Sunspot::Search::StatsJsonRow:#{value.inspect} min=#{min} max=#{max}"\
67
+ " count=#{count} sum=#{sum} missing=#{missing} sum_of_squares=#{sum_of_squares}"\
68
+ " mean=#{mean} standard_deviation=#{standard_deviation}"\
69
+ " #{nested.nil? ? '' : "nested_count=#{nested.size}"}>"
70
+ end
71
+
72
+ private
73
+
74
+ def recursive_nested_initialization(data)
75
+ data[@nested_key]['buckets'].map do |d|
76
+ StatsJsonRow.new(d, @facet, d['val'])
77
+ end
78
+ end
79
+
80
+ end
81
+ end
82
+ end