sunspot 2.3.0 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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)
|