sunspot 2.3.0 → 2.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Appraisals +4 -4
- data/lib/sunspot/adapters.rb +15 -1
- data/lib/sunspot/data_extractor.rb +36 -6
- data/lib/sunspot/dsl/fields.rb +16 -0
- data/lib/sunspot/dsl/fulltext.rb +1 -1
- data/lib/sunspot/dsl/group.rb +10 -0
- data/lib/sunspot/dsl/scope.rb +17 -17
- data/lib/sunspot/dsl/standard_query.rb +29 -1
- data/lib/sunspot/dsl.rb +2 -2
- data/lib/sunspot/field.rb +15 -4
- data/lib/sunspot/indexer.rb +37 -8
- data/lib/sunspot/query/abstract_fulltext.rb +7 -3
- data/lib/sunspot/query/abstract_json_field_facet.rb +3 -0
- data/lib/sunspot/query/composite_fulltext.rb +21 -2
- data/lib/sunspot/query/date_field_json_facet.rb +2 -16
- data/lib/sunspot/query/dismax.rb +10 -4
- data/lib/sunspot/query/function_query.rb +25 -1
- data/lib/sunspot/query/group.rb +4 -5
- data/lib/sunspot/query/join.rb +3 -5
- data/lib/sunspot/query/range_json_facet.rb +5 -2
- data/lib/sunspot/query/restriction.rb +18 -13
- data/lib/sunspot/query/standard_query.rb +12 -0
- data/lib/sunspot/search/abstract_search.rb +1 -1
- data/lib/sunspot/search/field_json_facet.rb +14 -3
- data/lib/sunspot/search/hit.rb +6 -1
- data/lib/sunspot/session.rb +9 -1
- data/lib/sunspot/setup.rb +69 -0
- data/lib/sunspot/util.rb +4 -11
- data/lib/sunspot/version.rb +1 -1
- data/lib/sunspot.rb +9 -1
- data/spec/api/adapters_spec.rb +13 -0
- data/spec/api/data_extractor_spec.rb +39 -0
- data/spec/api/indexer/removal_spec.rb +87 -0
- data/spec/api/query/connective_boost_examples.rb +85 -0
- data/spec/api/query/fulltext_examples.rb +6 -12
- data/spec/api/query/join_spec.rb +2 -2
- data/spec/api/query/standard_spec.rb +10 -0
- data/spec/api/search/hits_spec.rb +14 -0
- data/spec/api/setup_spec.rb +99 -0
- data/spec/api/sunspot_spec.rb +3 -0
- data/spec/helpers/indexer_helper.rb +22 -0
- data/spec/integration/atomic_updates_spec.rb +169 -5
- data/spec/integration/faceting_spec.rb +68 -34
- data/spec/integration/field_grouping_spec.rb +19 -0
- data/spec/integration/field_lists_spec.rb +16 -0
- data/spec/integration/geospatial_spec.rb +15 -0
- data/spec/integration/join_spec.rb +64 -0
- data/spec/integration/scoped_search_spec.rb +78 -0
- data/spec/mocks/adapters.rb +33 -0
- data/spec/mocks/connection.rb +6 -0
- data/spec/mocks/photo.rb +19 -5
- data/spec/mocks/post.rb +35 -1
- data/sunspot.gemspec +0 -2
- metadata +14 -8
- data/gemfiles/.gitkeep +0 -0
@@ -78,14 +78,14 @@ module Sunspot
|
|
78
78
|
# on whether this restriction is negated.
|
79
79
|
#
|
80
80
|
def to_boolean_phrase
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
81
|
+
value = if negated?
|
82
|
+
to_negated_boolean_phrase
|
83
|
+
else
|
84
|
+
to_positive_boolean_phrase
|
85
|
+
end
|
86
|
+
|
87
|
+
@field.respond_to?(:local_params) ?
|
88
|
+
@field.local_params(value) : value
|
89
89
|
end
|
90
90
|
|
91
91
|
#
|
@@ -135,7 +135,7 @@ module Sunspot
|
|
135
135
|
|
136
136
|
protected
|
137
137
|
|
138
|
-
#
|
138
|
+
#
|
139
139
|
# Return escaped Solr API representation of given value
|
140
140
|
#
|
141
141
|
# ==== Parameters
|
@@ -158,9 +158,13 @@ module Sunspot
|
|
158
158
|
end
|
159
159
|
|
160
160
|
class InRadius < Base
|
161
|
-
def initialize(negated, field,
|
162
|
-
@lat, @lon, @radius =
|
163
|
-
super negated, field,
|
161
|
+
def initialize(negated, field, *value)
|
162
|
+
@lat, @lon, @radius = value
|
163
|
+
super negated, field, value
|
164
|
+
end
|
165
|
+
|
166
|
+
def negate
|
167
|
+
self.class.new(!@negated, @field, *@value)
|
164
168
|
end
|
165
169
|
|
166
170
|
private
|
@@ -204,7 +208,8 @@ module Sunspot
|
|
204
208
|
private
|
205
209
|
|
206
210
|
def to_solr_conditional
|
207
|
-
|
211
|
+
@field.respond_to?(:to_solr_conditional) ?
|
212
|
+
@field.to_solr_conditional(solr_value) : "#{solr_value}"
|
208
213
|
end
|
209
214
|
end
|
210
215
|
|
@@ -16,6 +16,18 @@ module Sunspot
|
|
16
16
|
@fulltext.add_join(keywords, target, from, to)
|
17
17
|
end
|
18
18
|
|
19
|
+
def add_boost_query(factor)
|
20
|
+
@fulltext.add_boost_query(factor)
|
21
|
+
end
|
22
|
+
|
23
|
+
def add_boost_function(function)
|
24
|
+
@fulltext.add_boost_function(function)
|
25
|
+
end
|
26
|
+
|
27
|
+
def add_multiplicative_boost_function(function)
|
28
|
+
@fulltext.add_multiplicative_boost_function(function)
|
29
|
+
end
|
30
|
+
|
19
31
|
def disjunction
|
20
32
|
parent_fulltext = @fulltext
|
21
33
|
@fulltext = @fulltext.add_disjunction
|
@@ -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
|
|
@@ -5,15 +5,14 @@ module Sunspot
|
|
5
5
|
attr_reader :name
|
6
6
|
|
7
7
|
def initialize(field, search, options)
|
8
|
-
@name, @search, @options = name, search, options
|
8
|
+
@name, @search, @options = (options[:name] || field.name), search, options
|
9
9
|
@field = field
|
10
10
|
end
|
11
11
|
|
12
12
|
def rows
|
13
13
|
@rows ||=
|
14
14
|
begin
|
15
|
-
|
16
|
-
data = json_facet_response.nil? ? [] : json_facet_response['buckets']
|
15
|
+
data = no_data? ? [] : @search.json_facet_response[@field.name.to_s]['buckets']
|
17
16
|
rows = []
|
18
17
|
data.each do |d|
|
19
18
|
rows << JsonFacetRow.new(d, self)
|
@@ -28,6 +27,18 @@ module Sunspot
|
|
28
27
|
end
|
29
28
|
|
30
29
|
end
|
30
|
+
|
31
|
+
def no_data?
|
32
|
+
@search.json_facet_response[@field.name.to_s].nil?
|
33
|
+
end
|
34
|
+
|
35
|
+
def other_count(type)
|
36
|
+
json_facet_for_field = @search.json_facet_response[@field.name.to_s]
|
37
|
+
return 0 if json_facet_for_field.nil?
|
38
|
+
|
39
|
+
other = json_facet_for_field[type.to_s] || {}
|
40
|
+
other['count']
|
41
|
+
end
|
31
42
|
end
|
32
43
|
end
|
33
44
|
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
|
data/lib/sunspot/session.rb
CHANGED
@@ -259,7 +259,7 @@ module Sunspot
|
|
259
259
|
read_timeout: config.solr.read_timeout,
|
260
260
|
open_timeout: config.solr.open_timeout,
|
261
261
|
proxy: config.solr.proxy,
|
262
|
-
update_format:
|
262
|
+
update_format: update_format
|
263
263
|
)
|
264
264
|
end
|
265
265
|
|
@@ -277,5 +277,13 @@ module Sunspot
|
|
277
277
|
CompositeSetup.for(types)
|
278
278
|
end
|
279
279
|
end
|
280
|
+
|
281
|
+
def update_format
|
282
|
+
if config.solr.update_format && config.solr.update_format.to_s.match(/xml|json/i)
|
283
|
+
config.solr.update_format.downcase.to_sym
|
284
|
+
else
|
285
|
+
:xml
|
286
|
+
end
|
287
|
+
end
|
280
288
|
end
|
281
289
|
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,54 @@ 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
|
+
|
305
|
+
#
|
306
|
+
# Get value for `id_prefix` defined as String
|
307
|
+
#
|
308
|
+
# ==== Returns
|
309
|
+
#
|
310
|
+
# String:: value for `id_prefix` defined as String
|
311
|
+
#
|
312
|
+
def id_prefix_for_class
|
313
|
+
return if !id_prefix_defined? || id_prefix_requires_instance?
|
314
|
+
|
315
|
+
@id_prefix_extractor.value_for(nil)
|
316
|
+
end
|
317
|
+
|
318
|
+
#
|
319
|
+
# Check if `id_prefix` is defined for class associated with this setup.
|
320
|
+
#
|
321
|
+
# ==== Returns
|
322
|
+
#
|
323
|
+
# Boolean:: True if class associated with this setup has defined `id_prefix`
|
324
|
+
#
|
325
|
+
def id_prefix_defined?
|
326
|
+
!@id_prefix_extractor.nil?
|
327
|
+
end
|
328
|
+
|
329
|
+
#
|
330
|
+
# Check if instance is required to get `id_prefix` value (instance is required for Proc and
|
331
|
+
# Symbol `id_prefix` only. Value for String `id_prefix` can be get on class level)
|
332
|
+
#
|
333
|
+
# ==== Returns
|
334
|
+
#
|
335
|
+
# Boolean:: True if instance is required to get `id_prefix` value
|
336
|
+
#
|
337
|
+
def id_prefix_requires_instance?
|
338
|
+
return false unless id_prefix_defined?
|
339
|
+
|
340
|
+
!@id_prefix_extractor.is_a?(DataExtractor::Constant)
|
341
|
+
end
|
342
|
+
|
274
343
|
protected
|
275
344
|
|
276
345
|
#
|
data/lib/sunspot/util.rb
CHANGED
@@ -190,22 +190,15 @@ module Sunspot
|
|
190
190
|
|
191
191
|
def parse_json_facet(field_name, options, setup)
|
192
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]
|
193
|
+
if options[:range] || options[:time_range]
|
202
194
|
unless [Sunspot::Type::TimeType, Sunspot::Type::FloatType, Sunspot::Type::IntegerType ].find{|type| field.type.is_a?(type)}
|
203
195
|
raise(
|
204
196
|
ArgumentError,
|
205
|
-
':range can only be specified for date or numeric fields'
|
197
|
+
':range can only be specified for date, time, or numeric fields'
|
206
198
|
)
|
207
199
|
end
|
208
|
-
Sunspot::Query::
|
200
|
+
facet_klass = field.type.is_a?(Sunspot::Type::TimeType) ? Sunspot::Query::DateFieldJsonFacet : Sunspot::Query::RangeJsonFacet
|
201
|
+
facet_klass.new(field, options, setup)
|
209
202
|
else
|
210
203
|
Sunspot::Query::FieldJsonFacet.new(field, options, setup)
|
211
204
|
end
|
data/lib/sunspot/version.rb
CHANGED
data/lib/sunspot.rb
CHANGED
@@ -40,6 +40,12 @@ module Sunspot
|
|
40
40
|
NoSetupError = Class.new(StandardError)
|
41
41
|
IllegalSearchError = Class.new(StandardError)
|
42
42
|
NotImplementedError = Class.new(StandardError)
|
43
|
+
AtomicUpdateRequireInstanceForCompositeIdMessage = lambda do |class_name|
|
44
|
+
"WARNING: `id_prefix` is defined for #{class_name}. Use instance as key for `atomic_update` instead of ID."
|
45
|
+
end
|
46
|
+
RemoveByIdNotSupportCompositeIdMessage = lambda do |class_name|
|
47
|
+
"WARNING: `id_prefix` is defined for #{class_name}. `remove_by_id` does not support it. Use `remove` instead."
|
48
|
+
end
|
43
49
|
|
44
50
|
autoload :Installer, File.join(File.dirname(__FILE__), 'sunspot', 'installer')
|
45
51
|
|
@@ -208,6 +214,8 @@ module Sunspot
|
|
208
214
|
#
|
209
215
|
# post1, post2 = new Array(2) { Post.create }
|
210
216
|
# Sunspot.atomic_update(Post, post1.id => {title: 'New Title'}, post2.id => {description: 'new description'})
|
217
|
+
# Or
|
218
|
+
# Sunspot.atomic_update(Post, post1 => {title: 'New Title'}, post2 => {description: 'new description'})
|
211
219
|
#
|
212
220
|
# Note that indexed objects won't be reflected in search until a commit is
|
213
221
|
# sent - see Sunspot.index! and Sunspot.commit
|
@@ -223,7 +231,7 @@ module Sunspot
|
|
223
231
|
# ==== Parameters
|
224
232
|
#
|
225
233
|
# clazz<Class>:: the class of the objects to be updated
|
226
|
-
# updates<Hash>:: hash of updates where keys are model ids
|
234
|
+
# updates<Hash>:: hash of updates where keys are models or model ids
|
227
235
|
# and values are hash with property name/values to be updated
|
228
236
|
#
|
229
237
|
def atomic_update!(clazz, updates = {})
|
data/spec/api/adapters_spec.rb
CHANGED
@@ -20,6 +20,19 @@ describe Sunspot::Adapters::InstanceAdapter do
|
|
20
20
|
Sunspot::Adapters::InstanceAdapter::for(UnseenModel)
|
21
21
|
expect(Sunspot::Adapters::InstanceAdapter::registered_adapter_for(UnseenModel)).to be(AbstractModelInstanceAdapter)
|
22
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"
|
35
|
+
end
|
23
36
|
end
|
24
37
|
|
25
38
|
describe Sunspot::Adapters::DataAccessor do
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require File.expand_path('spec_helper', File.dirname(__FILE__))
|
2
|
+
|
3
|
+
describe Sunspot::DataExtractor do
|
4
|
+
it "removes special characters from strings" do
|
5
|
+
extractor = Sunspot::DataExtractor::AttributeExtractor.new(:name)
|
6
|
+
blog = Blog.new(:name => "Te\x0\x1\x7\x6\x8st\xB\xC\xE Bl\x1Fo\x7fg")
|
7
|
+
|
8
|
+
expect(extractor.value_for(blog)).to eq "Test Blog"
|
9
|
+
end
|
10
|
+
|
11
|
+
it "removes special characters from arrays" do
|
12
|
+
extractor = Sunspot::DataExtractor::BlockExtractor.new { tags }
|
13
|
+
post = Post.new(:tags => ["Te\x0\x1\x7\x6\x8st Ta\x1Fg\x7f 1", "Test\xB\xC\xE Tag 2"])
|
14
|
+
|
15
|
+
expect(extractor.value_for(post)).to eq ["Test Tag 1", "Test Tag 2"]
|
16
|
+
end
|
17
|
+
|
18
|
+
it "removes special characters from hashes" do
|
19
|
+
extractor = Sunspot::DataExtractor::Constant.new({ "Te\x0\x1\x7\x6\x8st" => "Ta\x1Fg\x7f" })
|
20
|
+
|
21
|
+
expect(extractor.value_for(Post.new)).to eq({ "Test" => "Tag" })
|
22
|
+
end
|
23
|
+
|
24
|
+
it "skips other data types" do
|
25
|
+
[
|
26
|
+
:"Te\x0\x1\x7\x6\x8st",
|
27
|
+
123,
|
28
|
+
123.0,
|
29
|
+
nil,
|
30
|
+
false,
|
31
|
+
true,
|
32
|
+
Sunspot::Util::Coordinates.new(40.7, -73.5)
|
33
|
+
].each do |value|
|
34
|
+
extractor = Sunspot::DataExtractor::Constant.new(value)
|
35
|
+
|
36
|
+
expect(extractor.value_for(Post.new)).to eq value
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -60,4 +60,91 @@ describe 'document removal', :type => :indexer do
|
|
60
60
|
end
|
61
61
|
expect(connection).to have_delete_by_query("(type:Post AND title_ss:monkeys)")
|
62
62
|
end
|
63
|
+
|
64
|
+
context 'when call #remove_by_id' do
|
65
|
+
let(:post) { clazz.new(title: 'A Title') }
|
66
|
+
before(:each) { index_post(post) }
|
67
|
+
|
68
|
+
context 'and `id_prefix` is defined on model' do
|
69
|
+
context 'as Proc' do
|
70
|
+
let(:clazz) { PostWithProcPrefixId }
|
71
|
+
let(:id_prefix) { lambda { |post| "USERDATA-#{post.id}!" } }
|
72
|
+
|
73
|
+
it 'prints warning' do
|
74
|
+
expect do
|
75
|
+
session.remove_by_id(clazz, post.id)
|
76
|
+
end.to output(Sunspot::RemoveByIdNotSupportCompositeIdMessage.call(clazz) + "\n").to_stderr
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'does not remove record' do
|
80
|
+
session.remove_by_id(clazz, post.id)
|
81
|
+
expect(connection).to have_no_delete(post_solr_id)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
context 'as Symbol' do
|
86
|
+
let(:clazz) { PostWithSymbolPrefixId }
|
87
|
+
let(:id_prefix) { lambda { |post| "#{post.title}!" } }
|
88
|
+
|
89
|
+
it 'prints warning' do
|
90
|
+
expect do
|
91
|
+
session.remove_by_id(clazz, post.id)
|
92
|
+
end.to output(Sunspot::RemoveByIdNotSupportCompositeIdMessage.call(clazz) + "\n").to_stderr
|
93
|
+
end
|
94
|
+
|
95
|
+
it 'does not remove record' do
|
96
|
+
session.remove_by_id(clazz, post.id)
|
97
|
+
expect(connection).to have_no_delete(post_solr_id)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
context 'as String' do
|
102
|
+
let(:clazz) { PostWithStringPrefixId }
|
103
|
+
let(:id_prefix) { 'USERDATA!' }
|
104
|
+
|
105
|
+
it 'does not print warning' do
|
106
|
+
expect do
|
107
|
+
session.remove_by_id(clazz, post.id)
|
108
|
+
end.to_not output(Sunspot::RemoveByIdNotSupportCompositeIdMessage.call(clazz) + "\n").to_stderr
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'removes record' do
|
112
|
+
session.remove_by_id(clazz, post.id)
|
113
|
+
expect(connection).to have_delete(post_solr_id)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
context 'and `id_prefix` is not defined on model' do
|
119
|
+
let(:clazz) { PostWithoutPrefixId }
|
120
|
+
let(:id_prefix) { nil }
|
121
|
+
|
122
|
+
it 'does not print warning' do
|
123
|
+
expect do
|
124
|
+
session.remove_by_id(clazz, post.id)
|
125
|
+
end.to_not output(Sunspot::RemoveByIdNotSupportCompositeIdMessage.call(clazz) + "\n").to_stderr
|
126
|
+
end
|
127
|
+
|
128
|
+
it 'removes record' do
|
129
|
+
session.remove_by_id(clazz, post.id)
|
130
|
+
expect(connection).to have_delete(post_solr_id)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
context 'and `id_prefix` is passed along with `class_name`' do
|
135
|
+
let(:clazz) { PostWithProcPrefixId }
|
136
|
+
let(:id_prefix) { lambda { |post| "USERDATA-#{post.id}!" } }
|
137
|
+
|
138
|
+
it 'does not print warning' do
|
139
|
+
expect do
|
140
|
+
session.remove_by_id("USERDATA-#{post.id}!#{clazz.name}", post.id)
|
141
|
+
end.to_not output(Sunspot::RemoveByIdNotSupportCompositeIdMessage.call(clazz) + "\n").to_stderr
|
142
|
+
end
|
143
|
+
|
144
|
+
it 'removes record' do
|
145
|
+
session.remove_by_id("USERDATA-#{post.id}!#{clazz.name}", post.id)
|
146
|
+
expect(connection).to have_delete(post_solr_id)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
63
150
|
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
shared_examples_for "query with connective scope and boost" do
|
2
|
+
it 'creates a boost query' do
|
3
|
+
search do
|
4
|
+
boost(10) do
|
5
|
+
any_of do
|
6
|
+
with(:coordinates_new).in_bounding_box([23, -46], [25, -44])
|
7
|
+
with(:coordinates_new).in_bounding_box([42, 56], [43, 58])
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
expect(connection).to have_last_search_including(
|
13
|
+
:bq, '(coordinates_new_ll:[23,-46 TO 25,-44] OR coordinates_new_ll:[42,56 TO 43,58])^10'
|
14
|
+
)
|
15
|
+
|
16
|
+
expect(connection).to have_last_search_including(
|
17
|
+
:defType, 'edismax'
|
18
|
+
)
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'creates a boost function' do
|
22
|
+
search do
|
23
|
+
boost(function() { field(:average_rating) })
|
24
|
+
end
|
25
|
+
|
26
|
+
expect(connection).to have_last_search_including(
|
27
|
+
:bf, 'field(average_rating_ft)'
|
28
|
+
)
|
29
|
+
|
30
|
+
expect(connection).to have_last_search_including(
|
31
|
+
:defType, 'edismax'
|
32
|
+
)
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'creates a multiplicative boost function' do
|
36
|
+
search do
|
37
|
+
boost_multiplicative(function() { field(:average_rating) })
|
38
|
+
end
|
39
|
+
|
40
|
+
expect(connection).to have_last_search_including(
|
41
|
+
:boost, 'field(average_rating_ft)'
|
42
|
+
)
|
43
|
+
|
44
|
+
expect(connection).to have_last_search_including(
|
45
|
+
:defType, 'edismax'
|
46
|
+
)
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'creates combined boost search' do
|
50
|
+
search do
|
51
|
+
boost(10) do
|
52
|
+
any_of do
|
53
|
+
with(:coordinates_new).in_bounding_box([23, -46], [25, -44])
|
54
|
+
with(:coordinates_new).in_bounding_box([42, 56], [43, 58])
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
boost(function() { field(:average_rating) })
|
59
|
+
boost_multiplicative(function() { field(:average_rating) })
|
60
|
+
end
|
61
|
+
|
62
|
+
expect(connection).to have_last_search_including(
|
63
|
+
:bq, '(coordinates_new_ll:[23,-46 TO 25,-44] OR coordinates_new_ll:[42,56 TO 43,58])^10'
|
64
|
+
)
|
65
|
+
|
66
|
+
expect(connection).to have_last_search_including(
|
67
|
+
:bf, 'field(average_rating_ft)'
|
68
|
+
)
|
69
|
+
|
70
|
+
expect(connection).to have_last_search_including(
|
71
|
+
:boost, 'field(average_rating_ft)'
|
72
|
+
)
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'avoids duplicate boost functions' do
|
76
|
+
search do
|
77
|
+
boost(function() { field(:average_rating) })
|
78
|
+
boost(function() { field(:average_rating) })
|
79
|
+
boost_multiplicative(function() { field(:average_rating) })
|
80
|
+
end
|
81
|
+
|
82
|
+
expect(connection.searches.last[:bf]).to eq ['field(average_rating_ft)']
|
83
|
+
expect(connection.searches.last[:boost]).to eq ['field(average_rating_ft)']
|
84
|
+
end
|
85
|
+
end
|
@@ -418,11 +418,9 @@ shared_examples_for 'fulltext query' do
|
|
418
418
|
|
419
419
|
obj_id = find_ob_id(srch)
|
420
420
|
q_name = "qPhoto#{obj_id}"
|
421
|
-
fq_name = "f#{q_name}"
|
422
421
|
|
423
|
-
expect(connection.searches.last[:q]).to eq "(_query_:\"{!join from=photo_container_id_i to=id_i v=$#{q_name}
|
424
|
-
expect(connection.searches.last[q_name]).to eq "_query_:\"{!edismax qf='caption_text'}keyword1\""
|
425
|
-
expect(connection.searches.last[fq_name]).to eq "type:Photo"
|
422
|
+
expect(connection.searches.last[:q]).to eq "(_query_:\"{!join from=photo_container_id_i to=id_i v=$#{q_name}}\" OR _query_:\"{!edismax qf='description_text^1.2'}keyword2\")"
|
423
|
+
expect(connection.searches.last[q_name]).to eq "_query_:\"{!field f=type}Photo\"+_query_:\"{!edismax qf='caption_text'}keyword1\""
|
426
424
|
end
|
427
425
|
|
428
426
|
it "should be able to resolve name conflicts with the :prefix option" do
|
@@ -435,11 +433,9 @@ shared_examples_for 'fulltext query' do
|
|
435
433
|
|
436
434
|
obj_id = find_ob_id(srch)
|
437
435
|
q_name = "qPhoto#{obj_id}"
|
438
|
-
fq_name = "f#{q_name}"
|
439
436
|
|
440
|
-
expect(connection.searches.last[:q]).to eq "(_query_:\"{!edismax qf='description_text^1.2'}keyword1\" OR _query_:\"{!join from=photo_container_id_i to=id_i v=$#{q_name}
|
441
|
-
expect(connection.searches.last[q_name]).to eq "_query_:\"{!edismax qf='description_text'}keyword2\""
|
442
|
-
expect(connection.searches.last[fq_name]).to eq "type:Photo"
|
437
|
+
expect(connection.searches.last[:q]).to eq "(_query_:\"{!edismax qf='description_text^1.2'}keyword1\" OR _query_:\"{!join from=photo_container_id_i to=id_i v=$#{q_name}}\")"
|
438
|
+
expect(connection.searches.last[q_name]).to eq "_query_:\"{!field f=type}Photo\"+_query_:\"{!edismax qf='description_text'}keyword2\""
|
443
439
|
end
|
444
440
|
|
445
441
|
it "should recognize fields when adding from DSL, e.g. when calling boost_fields" do
|
@@ -453,11 +449,9 @@ shared_examples_for 'fulltext query' do
|
|
453
449
|
|
454
450
|
obj_id = find_ob_id(srch)
|
455
451
|
q_name = "qPhoto#{obj_id}"
|
456
|
-
fq_name = "f#{q_name}"
|
457
452
|
|
458
|
-
expect(connection.searches.last[:q]).to eq "(_query_:\"{!edismax qf='description_text^1.5'}keyword1\" OR _query_:\"{!join from=photo_container_id_i to=id_i v=$#{q_name}
|
459
|
-
expect(connection.searches.last[q_name]).to eq "_query_:\"{!edismax qf='description_text^1.3'}keyword1\""
|
460
|
-
expect(connection.searches.last[fq_name]).to eq "type:Photo"
|
453
|
+
expect(connection.searches.last[:q]).to eq "(_query_:\"{!edismax qf='description_text^1.5'}keyword1\" OR _query_:\"{!join from=photo_container_id_i to=id_i v=$#{q_name}}\")"
|
454
|
+
expect(connection.searches.last[q_name]).to eq "_query_:\"{!field f=type}Photo\"+_query_:\"{!edismax qf='description_text^1.3'}keyword1\""
|
461
455
|
end
|
462
456
|
|
463
457
|
private
|
data/spec/api/query/join_spec.rb
CHANGED
@@ -6,7 +6,7 @@ describe 'join' do
|
|
6
6
|
with(:caption, 'blah')
|
7
7
|
end
|
8
8
|
expect(connection).to have_last_search_including(
|
9
|
-
:fq, "{!join from=photo_container_id_i to=id_i
|
9
|
+
:fq, "{!join from=photo_container_id_i to=id_i v='type:\"Photo\" AND caption_s:\"blah\"'}")
|
10
10
|
end
|
11
11
|
|
12
12
|
it 'should greater_than search by join' do
|
@@ -14,6 +14,6 @@ describe 'join' do
|
|
14
14
|
with(:photo_rating).greater_than(3)
|
15
15
|
end
|
16
16
|
expect(connection).to have_last_search_including(
|
17
|
-
:fq, "{!join from=photo_container_id_i to=id_i
|
17
|
+
:fq, "{!join from=photo_container_id_i to=id_i v='type:\"Photo\" AND average_rating_ft:{3\\.0 TO *}'}")
|
18
18
|
end
|
19
19
|
end
|
@@ -4,6 +4,7 @@ describe 'standard query', :type => :query do
|
|
4
4
|
it_should_behave_like "scoped query"
|
5
5
|
it_should_behave_like "query with advanced manipulation"
|
6
6
|
it_should_behave_like "query with connective scope"
|
7
|
+
it_should_behave_like "query with connective scope and boost"
|
7
8
|
it_should_behave_like "query with dynamic field support"
|
8
9
|
it_should_behave_like "facetable query"
|
9
10
|
it_should_behave_like "fulltext query"
|
@@ -22,6 +23,15 @@ describe 'standard query', :type => :query do
|
|
22
23
|
expect(connection).to have_last_search_with(:q => '*:*')
|
23
24
|
end
|
24
25
|
|
26
|
+
it 'adds a no-op query to :q parameter when only a boost query provided' do
|
27
|
+
session.search Post do
|
28
|
+
boost(2) do
|
29
|
+
with :title, 'My Pet Post'
|
30
|
+
end
|
31
|
+
end
|
32
|
+
expect(connection).to have_last_search_with(:q => '*:*')
|
33
|
+
end
|
34
|
+
|
25
35
|
private
|
26
36
|
|
27
37
|
def search(*classes, &block)
|