sunspot 2.0.0 → 2.5.0

Sign up to get free protection for your applications and to get access to all the features.
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