outoftime-sunspot 0.9.2 → 0.9.3
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.
- data/TODO +7 -2
- data/VERSION.yml +1 -1
- data/lib/sunspot/query/connective.rb +52 -3
- data/lib/sunspot/query/restriction.rb +25 -17
- data/spec/api/build_search_spec.rb +80 -2
- data/spec/integration/scoped_search_spec.rb +101 -0
- metadata +4 -3
data/TODO
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
=== 0.9 ===
|
|
2
|
-
*
|
|
1
|
+
=== 0.9.X ===
|
|
2
|
+
* Deal with empty facet queries
|
|
3
|
+
* Passing an integer into the second argument of dynamic_facet() when multiple facets are requested gives the wrong value
|
|
3
4
|
=== 0.10 ===
|
|
5
|
+
* Highlighting
|
|
6
|
+
* LocalSolr
|
|
7
|
+
* Text field restrictions
|
|
8
|
+
* Prefixes
|
|
4
9
|
* Intelligently decide whether to instantiate all facet rows at once
|
data/VERSION.yml
CHANGED
|
@@ -5,8 +5,8 @@ module Sunspot
|
|
|
5
5
|
# Base class for connectives (conjunctions and disjunctions).
|
|
6
6
|
#
|
|
7
7
|
class Abstract < Scope
|
|
8
|
-
def initialize(setup) #:nodoc:
|
|
9
|
-
@setup = setup
|
|
8
|
+
def initialize(setup, negated = false) #:nodoc:
|
|
9
|
+
@setup, @negated = setup, negated
|
|
10
10
|
@components = []
|
|
11
11
|
end
|
|
12
12
|
|
|
@@ -21,7 +21,7 @@ module Sunspot
|
|
|
21
21
|
# Express the connective as a Lucene boolean phrase.
|
|
22
22
|
#
|
|
23
23
|
def to_boolean_phrase #:nodoc:
|
|
24
|
-
if @components.length == 1
|
|
24
|
+
phrase = if @components.length == 1
|
|
25
25
|
@components.first.to_boolean_phrase
|
|
26
26
|
else
|
|
27
27
|
component_phrases = @components.map do |component|
|
|
@@ -29,6 +29,11 @@ module Sunspot
|
|
|
29
29
|
end
|
|
30
30
|
"(#{component_phrases.join(" #{connector} ")})"
|
|
31
31
|
end
|
|
32
|
+
if negated?
|
|
33
|
+
"-#{phrase}"
|
|
34
|
+
else
|
|
35
|
+
phrase
|
|
36
|
+
end
|
|
32
37
|
end
|
|
33
38
|
|
|
34
39
|
#
|
|
@@ -38,12 +43,38 @@ module Sunspot
|
|
|
38
43
|
def add_component(component) #:nodoc:
|
|
39
44
|
@components << component
|
|
40
45
|
end
|
|
46
|
+
|
|
47
|
+
def negated?
|
|
48
|
+
@negated
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def negate
|
|
52
|
+
negated = self.class.new(@setup, !negated?)
|
|
53
|
+
for component in @components
|
|
54
|
+
negated.add_component(component)
|
|
55
|
+
end
|
|
56
|
+
negated
|
|
57
|
+
end
|
|
41
58
|
end
|
|
42
59
|
|
|
43
60
|
#
|
|
44
61
|
# Disjunctions combine their components with an OR operator.
|
|
45
62
|
#
|
|
46
63
|
class Disjunction < Abstract
|
|
64
|
+
class <<self
|
|
65
|
+
def inverse
|
|
66
|
+
Conjunction
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def to_boolean_phrase
|
|
71
|
+
if @components.any? { |component| component.negated? }
|
|
72
|
+
denormalize.to_boolean_phrase
|
|
73
|
+
else
|
|
74
|
+
super
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
47
78
|
#
|
|
48
79
|
# Add a conjunction to the disjunction. This overrides the method in
|
|
49
80
|
# the Scope class since scopes are implicitly conjunctive and thus
|
|
@@ -55,17 +86,35 @@ module Sunspot
|
|
|
55
86
|
conjunction
|
|
56
87
|
end
|
|
57
88
|
|
|
89
|
+
def add_disjunction
|
|
90
|
+
self
|
|
91
|
+
end
|
|
92
|
+
|
|
58
93
|
private
|
|
59
94
|
|
|
60
95
|
def connector
|
|
61
96
|
'OR'
|
|
62
97
|
end
|
|
98
|
+
|
|
99
|
+
def denormalize
|
|
100
|
+
denormalized = self.class.inverse.new(@setup, !negated?)
|
|
101
|
+
for component in @components
|
|
102
|
+
denormalized.add_component(component.negate)
|
|
103
|
+
end
|
|
104
|
+
denormalized
|
|
105
|
+
end
|
|
63
106
|
end
|
|
64
107
|
|
|
65
108
|
#
|
|
66
109
|
# Conjunctions combine their components with an AND operator.
|
|
67
110
|
#
|
|
68
111
|
class Conjunction < Abstract
|
|
112
|
+
class <<self
|
|
113
|
+
def inverse
|
|
114
|
+
Disjunction
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
69
118
|
private
|
|
70
119
|
|
|
71
120
|
def connector
|
|
@@ -26,17 +26,17 @@ module Sunspot
|
|
|
26
26
|
# API for instances of this class.
|
|
27
27
|
#
|
|
28
28
|
# Implementations of this class must respond to #to_params and
|
|
29
|
-
# #
|
|
29
|
+
# #to_negated_params. Instead of implementing those methods, they may
|
|
30
30
|
# choose to implement any of:
|
|
31
31
|
#
|
|
32
|
-
# * #to_positive_boolean_phrase, and optionally #
|
|
32
|
+
# * #to_positive_boolean_phrase, and optionally #to_negated_boolean_phrase
|
|
33
33
|
# * #to_solr_conditional
|
|
34
34
|
#
|
|
35
35
|
class Base #:nodoc:
|
|
36
36
|
include RSolr::Char
|
|
37
37
|
|
|
38
|
-
def initialize(field, value,
|
|
39
|
-
@field, @value, @
|
|
38
|
+
def initialize(field, value, negated = false)
|
|
39
|
+
@field, @value, @negated = field, value, negated
|
|
40
40
|
end
|
|
41
41
|
|
|
42
42
|
#
|
|
@@ -56,14 +56,14 @@ module Sunspot
|
|
|
56
56
|
|
|
57
57
|
#
|
|
58
58
|
# Return the boolean phrase associated with this restriction object.
|
|
59
|
-
# Differentiates between positive and
|
|
59
|
+
# Differentiates between positive and negated boolean phrases depending
|
|
60
60
|
# on whether this restriction is negated.
|
|
61
61
|
#
|
|
62
62
|
def to_boolean_phrase
|
|
63
|
-
unless
|
|
63
|
+
unless negated?
|
|
64
64
|
to_positive_boolean_phrase
|
|
65
65
|
else
|
|
66
|
-
|
|
66
|
+
to_negated_boolean_phrase
|
|
67
67
|
end
|
|
68
68
|
end
|
|
69
69
|
|
|
@@ -85,27 +85,31 @@ module Sunspot
|
|
|
85
85
|
end
|
|
86
86
|
|
|
87
87
|
#
|
|
88
|
-
# Boolean phrase representing this restriction in the
|
|
88
|
+
# Boolean phrase representing this restriction in the negated. Subclasses
|
|
89
89
|
# may choose to implement this method, but it is not necessary, as the
|
|
90
90
|
# base implementation delegates to #to_positive_boolean_phrase.
|
|
91
91
|
#
|
|
92
92
|
# ==== Returns
|
|
93
93
|
#
|
|
94
|
-
# String:: Boolean phrase for restriction in the
|
|
94
|
+
# String:: Boolean phrase for restriction in the negated
|
|
95
95
|
#
|
|
96
|
-
def
|
|
96
|
+
def to_negated_boolean_phrase
|
|
97
97
|
"-#{to_positive_boolean_phrase}"
|
|
98
98
|
end
|
|
99
99
|
|
|
100
|
-
protected
|
|
101
|
-
|
|
102
100
|
#
|
|
103
101
|
# Whether this restriction should be negated from its original meaning
|
|
104
102
|
#
|
|
105
|
-
def
|
|
106
|
-
!!@
|
|
103
|
+
def negated? #:nodoc:
|
|
104
|
+
!!@negated
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def negate
|
|
108
|
+
self.class.new(@field, @value, !@negated)
|
|
107
109
|
end
|
|
108
110
|
|
|
111
|
+
protected
|
|
112
|
+
|
|
109
113
|
#
|
|
110
114
|
# Return escaped Solr API representation of given value
|
|
111
115
|
#
|
|
@@ -136,7 +140,7 @@ module Sunspot
|
|
|
136
140
|
end
|
|
137
141
|
end
|
|
138
142
|
|
|
139
|
-
def
|
|
143
|
+
def to_negated_boolean_phrase
|
|
140
144
|
unless @value.nil?
|
|
141
145
|
super
|
|
142
146
|
else
|
|
@@ -211,14 +215,18 @@ module Sunspot
|
|
|
211
215
|
# Result must be the exact instance given (only useful when negated).
|
|
212
216
|
#
|
|
213
217
|
class SameAs < Base
|
|
214
|
-
def initialize(object,
|
|
215
|
-
@object, @
|
|
218
|
+
def initialize(object, negated = false)
|
|
219
|
+
@object, @negated = object, negated
|
|
216
220
|
end
|
|
217
221
|
|
|
218
222
|
def to_positive_boolean_phrase
|
|
219
223
|
adapter = Adapters::InstanceAdapter.adapt(@object)
|
|
220
224
|
"id:#{escape(adapter.index_id)}"
|
|
221
225
|
end
|
|
226
|
+
|
|
227
|
+
def negate
|
|
228
|
+
SameAs.new(@object, !@negated)
|
|
229
|
+
end
|
|
222
230
|
end
|
|
223
231
|
end
|
|
224
232
|
end
|
|
@@ -288,7 +288,85 @@ describe 'Search' do
|
|
|
288
288
|
end
|
|
289
289
|
end
|
|
290
290
|
connection.should have_last_search_with(
|
|
291
|
-
:fq => '(category_ids_im:1
|
|
291
|
+
:fq => '-(-category_ids_im:1 AND average_rating_f:[3\.0 TO *])'
|
|
292
|
+
)
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
it 'should create a disjunction with nested conjunction with negated restrictions' do
|
|
296
|
+
session.search Post do
|
|
297
|
+
any_of do
|
|
298
|
+
with :category_ids, 1
|
|
299
|
+
all_of do
|
|
300
|
+
without(:average_rating).greater_than(3.0)
|
|
301
|
+
with(:blog_id, 1)
|
|
302
|
+
end
|
|
303
|
+
end
|
|
304
|
+
end
|
|
305
|
+
connection.should have_last_search_with(
|
|
306
|
+
:fq => '(category_ids_im:1 OR (-average_rating_f:[3\.0 TO *] AND blog_id_i:1))'
|
|
307
|
+
)
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
it 'should create a disjunction with nested conjunction with nested disjunction with negated restriction' do
|
|
311
|
+
session.search(Post) do
|
|
312
|
+
any_of do
|
|
313
|
+
with(:title, 'Yes')
|
|
314
|
+
all_of do
|
|
315
|
+
with(:blog_id, 1)
|
|
316
|
+
any_of do
|
|
317
|
+
with(:category_ids, 4)
|
|
318
|
+
without(:average_rating, 2.0)
|
|
319
|
+
end
|
|
320
|
+
end
|
|
321
|
+
end
|
|
322
|
+
end
|
|
323
|
+
connection.should have_last_search_with(
|
|
324
|
+
:fq => '(title_ss:Yes OR (blog_id_i:1 AND -(-category_ids_im:4 AND average_rating_f:2\.0)))'
|
|
325
|
+
)
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
it 'should create a disjunction with a negated restriction and a nested disjunction in a conjunction with a negated restriction' do
|
|
329
|
+
session.search(Post) do
|
|
330
|
+
any_of do
|
|
331
|
+
without(:title, 'Yes')
|
|
332
|
+
all_of do
|
|
333
|
+
with(:blog_id, 1)
|
|
334
|
+
any_of do
|
|
335
|
+
with(:category_ids, 4)
|
|
336
|
+
without(:average_rating, 2.0)
|
|
337
|
+
end
|
|
338
|
+
end
|
|
339
|
+
end
|
|
340
|
+
end
|
|
341
|
+
connection.should have_last_search_with(
|
|
342
|
+
:fq => '-(title_ss:Yes AND -(blog_id_i:1 AND -(-category_ids_im:4 AND average_rating_f:2\.0)))'
|
|
343
|
+
)
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
#
|
|
347
|
+
# This is important because if a disjunction could be nested in another
|
|
348
|
+
# disjunction, then the inner disjunction could denormalize (and thus
|
|
349
|
+
# become negated) after the outer disjunction denormalized (checking to
|
|
350
|
+
# see if the inner one is negated). Since conjunctions never need to
|
|
351
|
+
# denormalize, if a disjunction can only contain conjunctions or restrictions,
|
|
352
|
+
# we can guarantee that the negation state of a disjunction's components will
|
|
353
|
+
# not change when #to_params is called on them.
|
|
354
|
+
#
|
|
355
|
+
# Since disjunction is associative, this behavior has no effect on the actual
|
|
356
|
+
# logical semantics of the disjunction.
|
|
357
|
+
#
|
|
358
|
+
it 'should create a single disjunction when disjunctions nested' do
|
|
359
|
+
session.search(Post) do
|
|
360
|
+
any_of do
|
|
361
|
+
with(:title, 'Yes')
|
|
362
|
+
any_of do
|
|
363
|
+
with(:blog_id, 1)
|
|
364
|
+
with(:category_ids, 4)
|
|
365
|
+
end
|
|
366
|
+
end
|
|
367
|
+
end
|
|
368
|
+
connection.should have_last_search_with(
|
|
369
|
+
:fq => '(title_ss:Yes OR blog_id_i:1 OR category_ids_im:4)'
|
|
292
370
|
)
|
|
293
371
|
end
|
|
294
372
|
|
|
@@ -301,7 +379,7 @@ describe 'Search' do
|
|
|
301
379
|
end
|
|
302
380
|
end
|
|
303
381
|
connection.should have_last_search_with(
|
|
304
|
-
:fq => "(
|
|
382
|
+
:fq => "-(id:Post\\ #{post.id} AND -category_ids_im:1)"
|
|
305
383
|
)
|
|
306
384
|
end
|
|
307
385
|
|
|
@@ -153,6 +153,107 @@ describe 'scoped_search' do
|
|
|
153
153
|
end
|
|
154
154
|
end
|
|
155
155
|
|
|
156
|
+
describe 'connectives' do
|
|
157
|
+
before :each do
|
|
158
|
+
Sunspot.remove_all
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
it 'should return results that match any restriction in a disjunction' do
|
|
162
|
+
posts = (1..3).map { |i| Post.new(:blog_id => i)}
|
|
163
|
+
Sunspot.index!(posts)
|
|
164
|
+
Sunspot.search(Post) do
|
|
165
|
+
any_of do
|
|
166
|
+
with(:blog_id, 1)
|
|
167
|
+
with(:blog_id, 2)
|
|
168
|
+
end
|
|
169
|
+
end.results.should == posts[0..1]
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
it 'should return results that match a nested conjunction in a disjunction' do
|
|
173
|
+
posts = [
|
|
174
|
+
Post.new(:title => 'No', :blog_id => 1),
|
|
175
|
+
Post.new(:title => 'Yes', :blog_id => 2),
|
|
176
|
+
Post.new(:title => 'Yes', :blog_id => 3),
|
|
177
|
+
Post.new(:title => 'No', :blog_id => 2)
|
|
178
|
+
]
|
|
179
|
+
Sunspot.index!(posts)
|
|
180
|
+
Sunspot.search(Post) do
|
|
181
|
+
any_of do
|
|
182
|
+
with(:blog_id, 1)
|
|
183
|
+
all_of do
|
|
184
|
+
with(:blog_id, 2)
|
|
185
|
+
with(:title, 'Yes')
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end.results.should == posts[0..1]
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
it 'should return results that match a conjunction with a negated restriction' do
|
|
192
|
+
posts = [
|
|
193
|
+
Post.new(:title => 'No', :blog_id => 1),
|
|
194
|
+
Post.new(:title => 'Yes', :blog_id => 2),
|
|
195
|
+
Post.new(:title => 'No', :blog_id => 2)
|
|
196
|
+
]
|
|
197
|
+
Sunspot.index!(posts)
|
|
198
|
+
search = Sunspot.search(Post) do
|
|
199
|
+
any_of do
|
|
200
|
+
with(:blog_id, 1)
|
|
201
|
+
without(:title, 'No')
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
search.results.should == posts[0..1]
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
it 'should return results that match a conjunction with a disjunction with a conjunction with a negated restriction' do
|
|
208
|
+
posts = [
|
|
209
|
+
Post.new(:title => 'Yes', :ratings_average => 2.0),
|
|
210
|
+
Post.new(:blog_id => 1, :category_ids => [4], :ratings_average => 2.0),
|
|
211
|
+
Post.new(:blog_id => 1),
|
|
212
|
+
Post.new(:blog_id => 2),
|
|
213
|
+
Post.new(:blog_id => 1, :ratings_average => 2.0)
|
|
214
|
+
]
|
|
215
|
+
Sunspot.index!(posts)
|
|
216
|
+
search = Sunspot.search(Post) do
|
|
217
|
+
any_of do
|
|
218
|
+
with(:title, 'Yes')
|
|
219
|
+
all_of do
|
|
220
|
+
with(:blog_id, 1)
|
|
221
|
+
any_of do
|
|
222
|
+
with(:category_ids, 4)
|
|
223
|
+
without(:average_rating, 2.0)
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
search.results.should == posts[0..2]
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
it 'should return results that match a disjunction with a negated restriction and a nested disjunction in a conjunction with a negated restriction' do
|
|
232
|
+
posts = [
|
|
233
|
+
Post.new,
|
|
234
|
+
Post.new(:title => 'Yes', :blog_id => 1, :category_ids => [4], :ratings_average => 2.0),
|
|
235
|
+
Post.new(:title => 'Yes', :blog_id => 1),
|
|
236
|
+
Post.new(:title => 'Yes'),
|
|
237
|
+
Post.new(:title => 'Yes', :category_ids => [4], :ratings_average => 2.0),
|
|
238
|
+
Post.new(:title => 'Yes', :blog_id => 1, :ratings_average => 2.0)
|
|
239
|
+
]
|
|
240
|
+
Sunspot.index!(posts)
|
|
241
|
+
search = Sunspot.search(Post) do
|
|
242
|
+
any_of do
|
|
243
|
+
without(:title, 'Yes')
|
|
244
|
+
all_of do
|
|
245
|
+
with(:blog_id, 1)
|
|
246
|
+
any_of do
|
|
247
|
+
with(:category_ids, 4)
|
|
248
|
+
without(:average_rating, 2.0)
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
search.results.should == posts[0..2]
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
|
|
156
257
|
describe 'multiple column ordering' do
|
|
157
258
|
before do
|
|
158
259
|
Sunspot.remove_all
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: outoftime-sunspot
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.9.
|
|
4
|
+
version: 0.9.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Mat Brown
|
|
@@ -12,7 +12,7 @@ autorequire:
|
|
|
12
12
|
bindir: bin
|
|
13
13
|
cert_chain: []
|
|
14
14
|
|
|
15
|
-
date: 2009-07-
|
|
15
|
+
date: 2009-07-29 00:00:00 -07:00
|
|
16
16
|
default_executable:
|
|
17
17
|
dependencies:
|
|
18
18
|
- !ruby/object:Gem::Dependency
|
|
@@ -189,6 +189,7 @@ files:
|
|
|
189
189
|
- templates/schema.xml.haml
|
|
190
190
|
has_rdoc: true
|
|
191
191
|
homepage: http://github.com/outoftime/sunspot
|
|
192
|
+
licenses:
|
|
192
193
|
post_install_message:
|
|
193
194
|
rdoc_options:
|
|
194
195
|
- --charset=UTF-8
|
|
@@ -214,7 +215,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
214
215
|
requirements: []
|
|
215
216
|
|
|
216
217
|
rubyforge_project:
|
|
217
|
-
rubygems_version: 1.
|
|
218
|
+
rubygems_version: 1.3.5
|
|
218
219
|
signing_key:
|
|
219
220
|
specification_version: 3
|
|
220
221
|
summary: Library for expressive, powerful interaction with the Solr search engine
|