sunspot 2.0.0 → 2.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +1 -0
- data/.rspec +2 -0
- data/Appraisals +7 -0
- data/Gemfile +0 -2
- data/History.txt +10 -0
- data/lib/sunspot.rb +55 -17
- data/lib/sunspot/adapters.rb +68 -18
- data/lib/sunspot/batcher.rb +1 -1
- data/lib/sunspot/configuration.rb +4 -2
- data/lib/sunspot/data_extractor.rb +36 -6
- data/lib/sunspot/dsl.rb +4 -3
- data/lib/sunspot/dsl/adjustable.rb +2 -2
- data/lib/sunspot/dsl/field_query.rb +69 -16
- data/lib/sunspot/dsl/field_stats.rb +25 -0
- data/lib/sunspot/dsl/fields.rb +28 -8
- data/lib/sunspot/dsl/fulltext.rb +9 -1
- data/lib/sunspot/dsl/group.rb +118 -0
- data/lib/sunspot/dsl/paginatable.rb +4 -1
- data/lib/sunspot/dsl/scope.rb +19 -10
- data/lib/sunspot/dsl/search.rb +1 -1
- data/lib/sunspot/dsl/spellcheckable.rb +14 -0
- data/lib/sunspot/dsl/standard_query.rb +63 -35
- data/lib/sunspot/field.rb +76 -4
- data/lib/sunspot/field_factory.rb +60 -11
- data/lib/sunspot/indexer.rb +70 -18
- data/lib/sunspot/query.rb +5 -4
- data/lib/sunspot/query/abstract_field_facet.rb +0 -2
- data/lib/sunspot/query/abstract_fulltext.rb +76 -0
- data/lib/sunspot/query/abstract_json_field_facet.rb +70 -0
- data/lib/sunspot/query/bbox.rb +5 -1
- data/lib/sunspot/query/common_query.rb +31 -6
- data/lib/sunspot/query/composite_fulltext.rb +58 -8
- data/lib/sunspot/query/date_field_json_facet.rb +25 -0
- data/lib/sunspot/query/dismax.rb +25 -71
- data/lib/sunspot/query/field_json_facet.rb +19 -0
- data/lib/sunspot/query/field_list.rb +15 -0
- data/lib/sunspot/query/field_stats.rb +61 -0
- data/lib/sunspot/query/function_query.rb +1 -2
- data/lib/sunspot/query/geo.rb +1 -1
- data/lib/sunspot/query/geofilt.rb +8 -3
- data/lib/sunspot/query/group.rb +46 -0
- data/lib/sunspot/query/group_query.rb +17 -0
- data/lib/sunspot/query/join.rb +88 -0
- data/lib/sunspot/query/more_like_this.rb +1 -1
- data/lib/sunspot/query/pagination.rb +12 -4
- data/lib/sunspot/query/range_json_facet.rb +28 -0
- data/lib/sunspot/query/restriction.rb +99 -13
- data/lib/sunspot/query/sort.rb +41 -0
- data/lib/sunspot/query/sort_composite.rb +7 -0
- data/lib/sunspot/query/spellcheck.rb +19 -0
- data/lib/sunspot/query/standard_query.rb +24 -2
- data/lib/sunspot/query/text_field_boost.rb +1 -3
- data/lib/sunspot/schema.rb +12 -3
- data/lib/sunspot/search.rb +4 -2
- data/lib/sunspot/search/abstract_search.rb +93 -43
- data/lib/sunspot/search/cursor_paginated_collection.rb +32 -0
- data/lib/sunspot/search/field_facet.rb +4 -4
- data/lib/sunspot/search/field_json_facet.rb +33 -0
- data/lib/sunspot/search/field_stats.rb +21 -0
- data/lib/sunspot/search/hit.rb +6 -1
- data/lib/sunspot/search/hit_enumerable.rb +4 -1
- data/lib/sunspot/search/json_facet_row.rb +40 -0
- data/lib/sunspot/search/json_facet_stats.rb +23 -0
- data/lib/sunspot/search/paginated_collection.rb +1 -0
- data/lib/sunspot/search/query_group.rb +74 -0
- data/lib/sunspot/search/standard_search.rb +70 -3
- data/lib/sunspot/search/stats_facet.rb +25 -0
- data/lib/sunspot/search/stats_json_row.rb +82 -0
- data/lib/sunspot/search/stats_row.rb +68 -0
- data/lib/sunspot/session.rb +62 -37
- data/lib/sunspot/session_proxy/class_sharding_session_proxy.rb +6 -4
- data/lib/sunspot/session_proxy/id_sharding_session_proxy.rb +16 -8
- data/lib/sunspot/session_proxy/master_slave_session_proxy.rb +2 -2
- data/lib/sunspot/session_proxy/retry_5xx_session_proxy.rb +1 -1
- data/lib/sunspot/session_proxy/sharding_session_proxy.rb +4 -2
- data/lib/sunspot/session_proxy/silent_fail_session_proxy.rb +1 -1
- data/lib/sunspot/session_proxy/thread_local_session_proxy.rb +6 -4
- data/lib/sunspot/setup.rb +42 -0
- data/lib/sunspot/type.rb +20 -0
- data/lib/sunspot/util.rb +78 -14
- data/lib/sunspot/version.rb +1 -1
- data/spec/api/adapters_spec.rb +40 -15
- data/spec/api/batcher_spec.rb +15 -15
- data/spec/api/binding_spec.rb +3 -3
- data/spec/api/class_set_spec.rb +6 -6
- data/spec/api/data_extractor_spec.rb +39 -0
- data/spec/api/hit_enumerable_spec.rb +32 -9
- data/spec/api/indexer/attributes_spec.rb +35 -30
- data/spec/api/indexer/batch_spec.rb +8 -7
- data/spec/api/indexer/dynamic_fields_spec.rb +8 -8
- data/spec/api/indexer/fixed_fields_spec.rb +16 -11
- data/spec/api/indexer/fulltext_spec.rb +8 -8
- data/spec/api/indexer/removal_spec.rb +24 -14
- data/spec/api/indexer_spec.rb +2 -2
- data/spec/api/query/advanced_manipulation_examples.rb +3 -3
- data/spec/api/query/connectives_examples.rb +26 -14
- data/spec/api/query/dsl_spec.rb +24 -6
- data/spec/api/query/dynamic_fields_examples.rb +18 -18
- data/spec/api/query/faceting_examples.rb +80 -61
- data/spec/api/query/fulltext_examples.rb +194 -40
- data/spec/api/query/function_spec.rb +116 -13
- data/spec/api/query/geo_examples.rb +8 -12
- data/spec/api/query/group_spec.rb +27 -5
- data/spec/api/query/highlighting_examples.rb +26 -26
- data/spec/api/query/join_spec.rb +19 -0
- data/spec/api/query/more_like_this_spec.rb +40 -27
- data/spec/api/query/ordering_pagination_examples.rb +37 -23
- data/spec/api/query/scope_examples.rb +39 -39
- data/spec/api/query/spatial_examples.rb +3 -3
- data/spec/api/query/spellcheck_examples.rb +20 -0
- data/spec/api/query/standard_spec.rb +3 -1
- data/spec/api/query/stats_examples.rb +66 -0
- data/spec/api/query/text_field_scoping_examples.rb +5 -5
- data/spec/api/query/types_spec.rb +4 -4
- data/spec/api/search/cursor_paginated_collection_spec.rb +35 -0
- data/spec/api/search/dynamic_fields_spec.rb +4 -4
- data/spec/api/search/faceting_spec.rb +55 -52
- data/spec/api/search/highlighting_spec.rb +7 -7
- data/spec/api/search/hits_spec.rb +43 -29
- data/spec/api/search/paginated_collection_spec.rb +19 -18
- data/spec/api/search/results_spec.rb +13 -13
- data/spec/api/search/search_spec.rb +3 -3
- data/spec/api/search/stats_spec.rb +94 -0
- data/spec/api/session_proxy/class_sharding_session_proxy_spec.rb +23 -16
- data/spec/api/session_proxy/id_sharding_session_proxy_spec.rb +16 -4
- data/spec/api/session_proxy/master_slave_session_proxy_spec.rb +10 -6
- data/spec/api/session_proxy/retry_5xx_session_proxy_spec.rb +11 -11
- data/spec/api/session_proxy/sharding_session_proxy_spec.rb +15 -14
- data/spec/api/session_proxy/silent_fail_session_proxy_spec.rb +3 -3
- data/spec/api/session_proxy/spec_helper.rb +1 -1
- data/spec/api/session_proxy/thread_local_session_proxy_spec.rb +40 -26
- data/spec/api/session_spec.rb +78 -38
- data/spec/api/sunspot_spec.rb +7 -4
- data/spec/helpers/integration_helper.rb +11 -1
- data/spec/helpers/query_helper.rb +1 -1
- data/spec/helpers/search_helper.rb +30 -0
- data/spec/integration/atomic_updates_spec.rb +58 -0
- data/spec/integration/dynamic_fields_spec.rb +31 -20
- data/spec/integration/faceting_spec.rb +252 -39
- data/spec/integration/field_grouping_spec.rb +47 -15
- data/spec/integration/field_lists_spec.rb +57 -0
- data/spec/integration/geospatial_spec.rb +34 -8
- data/spec/integration/highlighting_spec.rb +8 -8
- data/spec/integration/indexing_spec.rb +7 -6
- data/spec/integration/join_spec.rb +45 -0
- data/spec/integration/keyword_search_spec.rb +68 -38
- data/spec/integration/local_search_spec.rb +4 -4
- data/spec/integration/more_like_this_spec.rb +7 -7
- data/spec/integration/scoped_search_spec.rb +193 -74
- data/spec/integration/spellcheck_spec.rb +119 -0
- data/spec/integration/stats_spec.rb +88 -0
- data/spec/integration/stored_fields_spec.rb +1 -1
- data/spec/integration/test_pagination.rb +4 -4
- data/spec/integration/unicode_spec.rb +1 -1
- data/spec/mocks/adapters.rb +36 -0
- data/spec/mocks/connection.rb +5 -3
- data/spec/mocks/photo.rb +32 -1
- data/spec/mocks/post.rb +18 -3
- data/spec/spec_helper.rb +13 -8
- data/sunspot.gemspec +6 -4
- data/tasks/rdoc.rake +22 -14
- metadata +101 -44
- data/lib/sunspot/dsl/field_group.rb +0 -57
- 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
|
data/lib/sunspot/search/hit.rb
CHANGED
@@ -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 =
|
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
|
@@ -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
|