sunspot 2.2.7 → 2.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.gitignore +1 -0
- data/.rspec +2 -0
- data/Appraisals +7 -0
- data/Gemfile +0 -8
- data/lib/sunspot/adapters.rb +4 -1
- data/lib/sunspot/configuration.rb +1 -0
- data/lib/sunspot/data_extractor.rb +36 -6
- data/lib/sunspot/dsl/field_query.rb +11 -0
- data/lib/sunspot/dsl/field_stats.rb +7 -0
- data/lib/sunspot/dsl/fields.rb +16 -0
- data/lib/sunspot/dsl/group.rb +10 -0
- data/lib/sunspot/dsl/scope.rb +23 -18
- data/lib/sunspot/field.rb +11 -0
- data/lib/sunspot/field_factory.rb +6 -2
- data/lib/sunspot/query/abstract_json_field_facet.rb +70 -0
- data/lib/sunspot/query/bbox.rb +5 -1
- data/lib/sunspot/query/date_field_json_facet.rb +25 -0
- data/lib/sunspot/query/field_json_facet.rb +19 -0
- data/lib/sunspot/query/field_stats.rb +35 -2
- data/lib/sunspot/query/group.rb +4 -5
- data/lib/sunspot/query/join.rb +2 -4
- data/lib/sunspot/query/range_json_facet.rb +28 -0
- data/lib/sunspot/query/restriction.rb +19 -4
- data/lib/sunspot/query.rb +3 -3
- data/lib/sunspot/schema.rb +10 -2
- data/lib/sunspot/search/abstract_search.rb +14 -1
- data/lib/sunspot/search/field_json_facet.rb +33 -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/standard_search.rb +2 -3
- data/lib/sunspot/search/stats_json_row.rb +82 -0
- data/lib/sunspot/search/stats_row.rb +3 -1
- data/lib/sunspot/search.rb +4 -3
- data/lib/sunspot/session.rb +13 -5
- data/lib/sunspot/setup.rb +31 -0
- data/lib/sunspot/util.rb +23 -0
- data/lib/sunspot/version.rb +1 -1
- data/spec/api/adapters_spec.rb +32 -19
- data/spec/api/batcher_spec.rb +15 -15
- data/spec/api/binding_spec.rb +3 -3
- data/spec/api/class_set_spec.rb +3 -3
- 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 +31 -31
- 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 +12 -12
- data/spec/api/indexer/fulltext_spec.rb +8 -8
- data/spec/api/indexer/removal_spec.rb +14 -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 +17 -9
- data/spec/api/query/dynamic_fields_examples.rb +18 -18
- data/spec/api/query/faceting_examples.rb +62 -62
- data/spec/api/query/fulltext_examples.rb +63 -58
- data/spec/api/query/function_spec.rb +26 -26
- data/spec/api/query/geo_examples.rb +6 -6
- data/spec/api/query/group_spec.rb +6 -6
- data/spec/api/query/highlighting_examples.rb +26 -26
- data/spec/api/query/join_spec.rb +2 -2
- data/spec/api/query/more_like_this_spec.rb +29 -29
- data/spec/api/query/ordering_pagination_examples.rb +25 -25
- 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 +3 -3
- data/spec/api/query/standard_spec.rb +1 -1
- data/spec/api/query/stats_examples.rb +8 -8
- 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 +12 -12
- 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 +18 -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 +10 -10
- data/spec/api/session_proxy/class_sharding_session_proxy_spec.rb +19 -18
- data/spec/api/session_proxy/id_sharding_session_proxy_spec.rb +9 -9
- data/spec/api/session_proxy/master_slave_session_proxy_spec.rb +10 -6
- data/spec/api/session_proxy/retry_5xx_session_proxy_spec.rb +10 -10
- data/spec/api/session_proxy/sharding_session_proxy_spec.rb +14 -13
- data/spec/api/session_proxy/silent_fail_session_proxy_spec.rb +2 -2
- data/spec/api/session_proxy/spec_helper.rb +1 -1
- data/spec/api/session_proxy/thread_local_session_proxy_spec.rb +9 -5
- data/spec/api/session_spec.rb +42 -42
- data/spec/api/sunspot_spec.rb +7 -4
- data/spec/helpers/integration_helper.rb +1 -0
- data/spec/integration/atomic_updates_spec.rb +25 -11
- data/spec/integration/dynamic_fields_spec.rb +10 -10
- data/spec/integration/faceting_spec.rb +252 -39
- data/spec/integration/field_grouping_spec.rb +35 -16
- data/spec/integration/field_lists_spec.rb +57 -0
- data/spec/integration/geospatial_spec.rb +34 -8
- data/spec/integration/highlighting_spec.rb +5 -5
- data/spec/integration/indexing_spec.rb +5 -5
- data/spec/integration/join_spec.rb +45 -0
- data/spec/integration/keyword_search_spec.rb +47 -45
- 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 +108 -108
- data/spec/integration/spellcheck_spec.rb +52 -7
- data/spec/integration/stats_spec.rb +54 -13
- 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 +33 -0
- data/spec/mocks/photo.rb +14 -4
- data/spec/mocks/post.rb +9 -1
- data/spec/spec_helper.rb +11 -10
- data/sunspot.gemspec +3 -1
- metadata +49 -6
@@ -16,7 +16,7 @@ module Sunspot
|
|
16
16
|
# Retrieve all facet objects defined for this search, in order they were
|
17
17
|
# defined. To retrieve an individual facet by name, use #facet()
|
18
18
|
#
|
19
|
-
attr_reader :facets, :groups
|
19
|
+
attr_reader :facets, :groups
|
20
20
|
attr_reader :query #:nodoc:
|
21
21
|
attr_accessor :request_handler
|
22
22
|
|
@@ -182,6 +182,10 @@ module Sunspot
|
|
182
182
|
end
|
183
183
|
end
|
184
184
|
|
185
|
+
def json_facet_stats(name, options = {})
|
186
|
+
JsonFacetStats.new(name, self, options)
|
187
|
+
end
|
188
|
+
|
185
189
|
#
|
186
190
|
# Deprecated in favor of optional second argument to #facet
|
187
191
|
#
|
@@ -193,6 +197,10 @@ module Sunspot
|
|
193
197
|
@solr_result['facet_counts']
|
194
198
|
end
|
195
199
|
|
200
|
+
def json_facet_response #:nodoc:
|
201
|
+
@solr_result['facets']
|
202
|
+
end
|
203
|
+
|
196
204
|
def stats_response #:nodoc:
|
197
205
|
@solr_result['stats']['stats_fields']
|
198
206
|
end
|
@@ -255,6 +263,11 @@ module Sunspot
|
|
255
263
|
add_stats(field.name, FieldStats.new(field, self))
|
256
264
|
end
|
257
265
|
|
266
|
+
def add_json_facet(field, options = {})
|
267
|
+
name = (options[:name] || field.name)
|
268
|
+
add_facet(name, FieldJsonFacet.new(field, self, options))
|
269
|
+
end
|
270
|
+
|
258
271
|
def highlights_for(doc) #:nodoc:
|
259
272
|
if @solr_result['highlighting']
|
260
273
|
@solr_result['highlighting'][doc['id']]
|
@@ -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
|
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
|
@@ -49,12 +49,11 @@ module Sunspot
|
|
49
49
|
# the index. Otherwise return Solr's suggested collation.
|
50
50
|
#
|
51
51
|
# Solr's suggested collation is more liberal, replacing even terms that
|
52
|
-
# are present in the index.
|
53
|
-
# misspelled and preventing useful results.
|
52
|
+
# are present in the index.
|
54
53
|
#
|
55
54
|
# Mix and match in your views for a blend of strict and liberal collations.
|
56
55
|
def spellcheck_collation(*terms)
|
57
|
-
if solr_spellcheck['suggestions'] && solr_spellcheck['suggestions'].length >
|
56
|
+
if solr_spellcheck['suggestions'] && solr_spellcheck['suggestions'].length > 0
|
58
57
|
collation = terms.join(" ").dup if terms
|
59
58
|
|
60
59
|
# If we are given a query string, tokenize it and strictly replace
|
@@ -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
|
@@ -59,7 +59,9 @@ module Sunspot
|
|
59
59
|
end
|
60
60
|
|
61
61
|
def inspect
|
62
|
-
"<Sunspot::Search::StatsRow:#{value.inspect} min=#{min} max=#{max}
|
62
|
+
"<Sunspot::Search::StatsRow:#{value.inspect} min=#{min} max=#{max}"\
|
63
|
+
" count=#{self.count} sum=#{sum} missing=#{missing} sum_of_squares=#{sum_of_squares}"\
|
64
|
+
" mean=#{mean} standard_deviation=#{standard_deviation}>"
|
63
65
|
end
|
64
66
|
end
|
65
67
|
end
|
data/lib/sunspot/search.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
|
-
%w(abstract_search standard_search more_like_this_search query_facet
|
2
|
-
date_facet range_facet
|
3
|
-
|
1
|
+
%w(abstract_search standard_search more_like_this_search query_facet
|
2
|
+
field_facet field_json_facet date_facet range_facet json_facet_stats
|
3
|
+
facet_row json_facet_row hit highlight field_group group hit_enumerable
|
4
|
+
stats_row stats_json_row field_stats stats_facet query_group).each do |file|
|
4
5
|
require File.join(File.dirname(__FILE__), 'search', file)
|
5
6
|
end
|
6
7
|
|
data/lib/sunspot/session.rb
CHANGED
@@ -254,11 +254,13 @@ module Sunspot
|
|
254
254
|
# RSolr::Connection::Base:: The connection for this session
|
255
255
|
#
|
256
256
|
def connection
|
257
|
-
@connection ||=
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
257
|
+
@connection ||= self.class.connection_class.connect(
|
258
|
+
url: config.solr.url,
|
259
|
+
read_timeout: config.solr.read_timeout,
|
260
|
+
open_timeout: config.solr.open_timeout,
|
261
|
+
proxy: config.solr.proxy,
|
262
|
+
update_format: update_format_generator
|
263
|
+
)
|
262
264
|
end
|
263
265
|
|
264
266
|
def indexer
|
@@ -275,5 +277,11 @@ module Sunspot
|
|
275
277
|
CompositeSetup.for(types)
|
276
278
|
end
|
277
279
|
end
|
280
|
+
|
281
|
+
def update_format_generator
|
282
|
+
if config.solr.update_format && RSolr.version.to_i > 1
|
283
|
+
config.solr.update_format.downcase.to_sym == :json ? RSolr::JSON::Generator : RSolr::Xml::Generator
|
284
|
+
end
|
285
|
+
end
|
278
286
|
end
|
279
287
|
end
|
data/lib/sunspot/setup.rb
CHANGED
@@ -15,6 +15,7 @@ module Sunspot
|
|
15
15
|
@more_like_this_field_factories_cache = Hash.new { |h, k| h[k] = [] }
|
16
16
|
@dsl = DSL::Fields.new(self)
|
17
17
|
@document_boost_extractor = nil
|
18
|
+
@id_prefix_extractor = nil
|
18
19
|
add_field_factory(:class, Type::ClassType.instance)
|
19
20
|
end
|
20
21
|
|
@@ -61,6 +62,7 @@ module Sunspot
|
|
61
62
|
field_factory = FieldFactory::Static.new(name, Type::TextType.instance, options, &block)
|
62
63
|
@text_field_factories[name] = field_factory
|
63
64
|
@text_field_factories_cache[field_factory.name] = field_factory
|
65
|
+
@field_factories_cache[field_factory.name] = field_factory
|
64
66
|
if stored
|
65
67
|
@stored_field_factories_cache[field_factory.name] << field_factory
|
66
68
|
end
|
@@ -81,6 +83,7 @@ module Sunspot
|
|
81
83
|
field_factory = FieldFactory::Dynamic.new(name, type, options, &block)
|
82
84
|
@dynamic_field_factories[field_factory.signature] = field_factory
|
83
85
|
@dynamic_field_factories_cache[field_factory.name] = field_factory
|
86
|
+
@field_factories_cache[field_factory.name] = field_factory
|
84
87
|
if stored
|
85
88
|
@stored_field_factories_cache[field_factory.name] << field_factory
|
86
89
|
end
|
@@ -107,6 +110,24 @@ module Sunspot
|
|
107
110
|
end
|
108
111
|
end
|
109
112
|
|
113
|
+
#
|
114
|
+
# Add id prefix for compositeId router
|
115
|
+
#
|
116
|
+
def add_id_prefix(attr_name, &block)
|
117
|
+
@id_prefix_extractor =
|
118
|
+
case attr_name
|
119
|
+
when Symbol
|
120
|
+
DataExtractor::AttributeExtractor.new(attr_name)
|
121
|
+
when String
|
122
|
+
DataExtractor::Constant.new(attr_name)
|
123
|
+
when nil
|
124
|
+
DataExtractor::BlockExtractor.new(&block) if block_given?
|
125
|
+
else
|
126
|
+
raise ArgumentError,
|
127
|
+
"The ID prefix has to be either a Symbol, a String or a Proc"
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
110
131
|
#
|
111
132
|
# Builder method for evaluating the setup DSL
|
112
133
|
#
|
@@ -271,6 +292,16 @@ module Sunspot
|
|
271
292
|
end
|
272
293
|
end
|
273
294
|
|
295
|
+
def id_prefix_for(model)
|
296
|
+
if @id_prefix_extractor
|
297
|
+
value = @id_prefix_extractor.value_for(model)
|
298
|
+
|
299
|
+
if value.is_a?(String) and value.size > 0
|
300
|
+
value[-1] == "!" ? value : "#{value}!"
|
301
|
+
end
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
274
305
|
protected
|
275
306
|
|
276
307
|
#
|
data/lib/sunspot/util.rb
CHANGED
@@ -188,6 +188,29 @@ module Sunspot
|
|
188
188
|
RSolr.solr_escape(value).gsub(/([\s\.])/, '\\\\\1')
|
189
189
|
end
|
190
190
|
|
191
|
+
def parse_json_facet(field_name, options, setup)
|
192
|
+
field = setup.field(field_name)
|
193
|
+
if options[:time_range]
|
194
|
+
unless field.type.is_a?(Sunspot::Type::TimeType)
|
195
|
+
raise(
|
196
|
+
ArgumentError,
|
197
|
+
':time_range can only be specified for Date or Time fields'
|
198
|
+
)
|
199
|
+
end
|
200
|
+
Sunspot::Query::DateFieldJsonFacet.new(field, options, setup)
|
201
|
+
elsif options[:range]
|
202
|
+
unless [Sunspot::Type::TimeType, Sunspot::Type::FloatType, Sunspot::Type::IntegerType ].find{|type| field.type.is_a?(type)}
|
203
|
+
raise(
|
204
|
+
ArgumentError,
|
205
|
+
':range can only be specified for date or numeric fields'
|
206
|
+
)
|
207
|
+
end
|
208
|
+
Sunspot::Query::RangeJsonFacet.new(field, options, setup)
|
209
|
+
else
|
210
|
+
Sunspot::Query::FieldJsonFacet.new(field, options, setup)
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
191
214
|
private
|
192
215
|
|
193
216
|
#
|
data/lib/sunspot/version.rb
CHANGED
data/spec/api/adapters_spec.rb
CHANGED
@@ -2,45 +2,58 @@ require File.expand_path('spec_helper', File.dirname(__FILE__))
|
|
2
2
|
|
3
3
|
describe Sunspot::Adapters::InstanceAdapter do
|
4
4
|
it "finds adapter by superclass" do
|
5
|
-
Sunspot::Adapters::InstanceAdapter::for(Model).
|
5
|
+
expect(Sunspot::Adapters::InstanceAdapter::for(Model)).to be(AbstractModelInstanceAdapter)
|
6
6
|
end
|
7
7
|
|
8
8
|
it "finds adapter by mixin" do
|
9
|
-
Sunspot::Adapters::InstanceAdapter::for(MixModel).
|
9
|
+
expect(Sunspot::Adapters::InstanceAdapter::for(MixModel)).to be(MixInModelInstanceAdapter)
|
10
10
|
end
|
11
11
|
|
12
12
|
it 'throws NoAdapterError if anonymous module passed in' do
|
13
|
-
|
13
|
+
expect do
|
14
14
|
Sunspot::Adapters::InstanceAdapter::for(Module.new)
|
15
|
-
end.
|
15
|
+
end.to raise_error(Sunspot::NoAdapterError)
|
16
16
|
end
|
17
17
|
|
18
18
|
it "registers adapters found by ancestor lookup with the descendant class" do
|
19
|
-
Sunspot::Adapters::InstanceAdapter::registered_adapter_for(UnseenModel).
|
19
|
+
expect(Sunspot::Adapters::InstanceAdapter::registered_adapter_for(UnseenModel)).to be(nil)
|
20
20
|
Sunspot::Adapters::InstanceAdapter::for(UnseenModel)
|
21
|
-
Sunspot::Adapters::InstanceAdapter::registered_adapter_for(UnseenModel).
|
21
|
+
expect(Sunspot::Adapters::InstanceAdapter::registered_adapter_for(UnseenModel)).to be(AbstractModelInstanceAdapter)
|
22
|
+
end
|
23
|
+
|
24
|
+
it "appends ID prefix when configured" do
|
25
|
+
expect(AbstractModelInstanceAdapter.new(ModelWithPrefixId.new).index_id).to eq "USERDATA!ModelWithPrefixId 1"
|
26
|
+
end
|
27
|
+
|
28
|
+
it "supports nested ID prefixes" do
|
29
|
+
expect(AbstractModelInstanceAdapter.
|
30
|
+
new(ModelWithNestedPrefixId.new).index_id).to eq "USER!USERDATA!ModelWithNestedPrefixId 1"
|
31
|
+
end
|
32
|
+
|
33
|
+
it "doesn't appends ID prefix when not configured" do
|
34
|
+
expect(AbstractModelInstanceAdapter.new(ModelWithoutPrefixId.new).index_id).to eq "ModelWithoutPrefixId 1"
|
22
35
|
end
|
23
36
|
end
|
24
37
|
|
25
38
|
describe Sunspot::Adapters::DataAccessor do
|
26
39
|
it "finds adapter by superclass" do
|
27
|
-
Sunspot::Adapters::DataAccessor::for(Model).
|
40
|
+
expect(Sunspot::Adapters::DataAccessor::for(Model)).to be(AbstractModelDataAccessor)
|
28
41
|
end
|
29
42
|
|
30
43
|
it "finds adapter by mixin" do
|
31
|
-
Sunspot::Adapters::DataAccessor::for(MixModel).
|
44
|
+
expect(Sunspot::Adapters::DataAccessor::for(MixModel)).to be(MixInModelDataAccessor)
|
32
45
|
end
|
33
46
|
|
34
47
|
it 'throws NoAdapterError if anonymous module passed in' do
|
35
|
-
|
48
|
+
expect do
|
36
49
|
Sunspot::Adapters::DataAccessor::for(Module.new)
|
37
|
-
end.
|
50
|
+
end.to raise_error(Sunspot::NoAdapterError)
|
38
51
|
end
|
39
52
|
|
40
53
|
it "registers adapters found by ancestor lookup with the descendant class" do
|
41
|
-
Sunspot::Adapters::DataAccessor::registered_accessor_for(UnseenModel).
|
54
|
+
expect(Sunspot::Adapters::DataAccessor::registered_accessor_for(UnseenModel)).to be(nil)
|
42
55
|
Sunspot::Adapters::DataAccessor::for(UnseenModel)
|
43
|
-
Sunspot::Adapters::DataAccessor::registered_accessor_for(UnseenModel).
|
56
|
+
expect(Sunspot::Adapters::DataAccessor::registered_accessor_for(UnseenModel)).to be(AbstractModelDataAccessor)
|
44
57
|
end
|
45
58
|
end
|
46
59
|
|
@@ -51,30 +64,30 @@ describe Sunspot::Adapters::Registry do
|
|
51
64
|
let(:mixin_accessor){ Sunspot::Adapters::DataAccessor::for(MixModel) }
|
52
65
|
|
53
66
|
it "registers and retrieves a data accessor for abstractclass" do
|
54
|
-
registry.retrieve(AbstractModel).
|
67
|
+
expect(registry.retrieve(AbstractModel)).to be_a(abstractclass_accessor)
|
55
68
|
end
|
56
69
|
|
57
70
|
it "registers and retrieves a data accessor for superclass" do
|
58
|
-
registry.retrieve(Model).
|
71
|
+
expect(registry.retrieve(Model)).to be_a(superclass_accessor)
|
59
72
|
end
|
60
73
|
|
61
74
|
it "registers and retrieves a data accessor for mixin" do
|
62
|
-
registry.retrieve(MixModel).
|
75
|
+
expect(registry.retrieve(MixModel)).to be_a(mixin_accessor)
|
63
76
|
end
|
64
77
|
|
65
78
|
it "injects inherited attributes" do
|
66
|
-
AbstractModelDataAccessor.
|
79
|
+
allow_any_instance_of(AbstractModelDataAccessor).to receive(:inherited_attributes).and_return([:to_be_injected])
|
67
80
|
in_registry_data_accessor = registry.retrieve(AbstractModel)
|
68
81
|
in_registry_data_accessor.to_be_injected = "value"
|
69
|
-
registry.retrieve(Model).to_be_injected.
|
82
|
+
expect(registry.retrieve(Model).to_be_injected).to eq("value")
|
70
83
|
end
|
71
84
|
|
72
85
|
it "not overrides inherited attributes" do
|
73
|
-
AbstractModelDataAccessor.
|
86
|
+
allow_any_instance_of(AbstractModelDataAccessor).to receive(:inherited_attributes).and_return([:to_be_injected])
|
74
87
|
parent_data_accessor = registry.retrieve(AbstractModel)
|
75
88
|
current_data_accessor = registry.retrieve(Model)
|
76
89
|
parent_data_accessor.to_be_injected = "value"
|
77
90
|
current_data_accessor.to_be_injected = "safe-value"
|
78
|
-
registry.retrieve(Model).to_be_injected.
|
91
|
+
expect(registry.retrieve(Model).to_be_injected).to eq("safe-value")
|
79
92
|
end
|
80
93
|
end
|
data/spec/api/batcher_spec.rb
CHANGED
@@ -2,12 +2,12 @@ require File.expand_path('spec_helper', File.dirname(__FILE__))
|
|
2
2
|
|
3
3
|
describe Sunspot::Batcher do
|
4
4
|
it "includes Enumerable" do
|
5
|
-
described_class.
|
5
|
+
expect(described_class).to include Enumerable
|
6
6
|
end
|
7
7
|
|
8
8
|
describe "#each" do
|
9
9
|
let(:current) { [:foo, :bar] }
|
10
|
-
before { subject.
|
10
|
+
before { allow(subject).to receive(:current).and_return current }
|
11
11
|
|
12
12
|
it "iterates over current" do
|
13
13
|
yielded_values = []
|
@@ -16,25 +16,25 @@ describe Sunspot::Batcher do
|
|
16
16
|
yielded_values << value
|
17
17
|
end
|
18
18
|
|
19
|
-
yielded_values.
|
19
|
+
expect(yielded_values).to eq current
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
23
23
|
describe "adding to current batch" do
|
24
24
|
it "#push pushes to current" do
|
25
25
|
subject.push :foo
|
26
|
-
subject.current.
|
26
|
+
expect(subject.current).to include :foo
|
27
27
|
end
|
28
28
|
|
29
29
|
it "#<< pushes to current" do
|
30
30
|
subject.push :foo
|
31
|
-
subject.current.
|
31
|
+
expect(subject.current).to include :foo
|
32
32
|
end
|
33
33
|
|
34
34
|
it "#concat concatinates on current batch" do
|
35
35
|
subject << :foo
|
36
36
|
subject.concat [:bar, :mix]
|
37
|
-
|
37
|
+
is_expected.to include :foo, :bar, :mix
|
38
38
|
end
|
39
39
|
end
|
40
40
|
|
@@ -46,7 +46,7 @@ describe Sunspot::Batcher do
|
|
46
46
|
end
|
47
47
|
|
48
48
|
it "is empty by default" do
|
49
|
-
subject.current.
|
49
|
+
expect(subject.current).to be_empty
|
50
50
|
end
|
51
51
|
end
|
52
52
|
|
@@ -58,7 +58,7 @@ describe Sunspot::Batcher do
|
|
58
58
|
end
|
59
59
|
|
60
60
|
it "returns the same as last time" do
|
61
|
-
subject.current.
|
61
|
+
expect(subject.current).to eq subject.current
|
62
62
|
end
|
63
63
|
end
|
64
64
|
end
|
@@ -71,7 +71,7 @@ describe Sunspot::Batcher do
|
|
71
71
|
it "changes current" do
|
72
72
|
subject << :foo
|
73
73
|
subject.start_new
|
74
|
-
|
74
|
+
is_expected.not_to include :foo
|
75
75
|
end
|
76
76
|
end
|
77
77
|
|
@@ -88,25 +88,25 @@ describe Sunspot::Batcher do
|
|
88
88
|
it "changes current" do
|
89
89
|
subject << :foo
|
90
90
|
subject.end_current
|
91
|
-
|
91
|
+
is_expected.not_to include :foo
|
92
92
|
end
|
93
93
|
|
94
94
|
it "returns current" do
|
95
95
|
subject << :foo
|
96
|
-
subject.end_current.
|
96
|
+
expect(subject.end_current).to include :foo
|
97
97
|
end
|
98
98
|
end
|
99
99
|
end
|
100
100
|
|
101
101
|
describe "#batching?" do
|
102
102
|
it "is false when depth is 0" do
|
103
|
-
subject.
|
104
|
-
|
103
|
+
expect(subject).to receive(:depth).and_return 0
|
104
|
+
is_expected.not_to be_batching
|
105
105
|
end
|
106
106
|
|
107
107
|
it "is true when depth is more than 0" do
|
108
|
-
subject.
|
109
|
-
|
108
|
+
expect(subject).to receive(:depth).and_return 1
|
109
|
+
is_expected.to be_batching
|
110
110
|
end
|
111
111
|
end
|
112
112
|
end
|
data/spec/api/binding_spec.rb
CHANGED
@@ -6,7 +6,7 @@ describe "DSL bindings" do
|
|
6
6
|
session.search(Post) do
|
7
7
|
value = test_method
|
8
8
|
end
|
9
|
-
value.
|
9
|
+
expect(value).to eq('value')
|
10
10
|
end
|
11
11
|
|
12
12
|
it 'should give access to calling context\'s id method in search DSL' do
|
@@ -14,7 +14,7 @@ describe "DSL bindings" do
|
|
14
14
|
session.search(Post) do
|
15
15
|
value = id
|
16
16
|
end
|
17
|
-
value.
|
17
|
+
expect(value).to eq(16)
|
18
18
|
end
|
19
19
|
|
20
20
|
it 'should give access to calling context\'s methods in nested DSL block' do
|
@@ -24,7 +24,7 @@ describe "DSL bindings" do
|
|
24
24
|
value = test_method
|
25
25
|
end
|
26
26
|
end
|
27
|
-
value.
|
27
|
+
expect(value).to eq('value')
|
28
28
|
end
|
29
29
|
|
30
30
|
it 'should give access to calling context\'s methods in double-nested DSL block' do
|