outoftime-sunspot 0.0.2 → 0.7.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.
- data/History.txt +4 -0
- data/README.rdoc +26 -33
- data/TODO +7 -0
- data/VERSION.yml +2 -2
- data/lib/light_config.rb +5 -5
- data/lib/sunspot/adapters.rb +209 -33
- data/lib/sunspot/configuration.rb +25 -10
- data/lib/sunspot/dsl/fields.rb +42 -11
- data/lib/sunspot/dsl/query.rb +150 -6
- data/lib/sunspot/dsl/scope.rb +16 -26
- data/lib/sunspot/facet.rb +37 -0
- data/lib/sunspot/facet_row.rb +34 -0
- data/lib/sunspot/facets.rb +21 -0
- data/lib/sunspot/field.rb +112 -56
- data/lib/sunspot/indexer.rb +49 -46
- data/lib/sunspot/query.rb +217 -49
- data/lib/sunspot/restriction.rb +158 -14
- data/lib/sunspot/search.rb +79 -28
- data/lib/sunspot/session.rb +75 -8
- data/lib/sunspot/setup.rb +171 -0
- data/lib/sunspot/type.rb +116 -32
- data/lib/sunspot/util.rb +141 -7
- data/lib/sunspot.rb +260 -31
- data/spec/api/build_search_spec.rb +139 -33
- data/spec/api/indexer_spec.rb +33 -2
- data/spec/api/search_retrieval_spec.rb +85 -2
- data/spec/api/session_spec.rb +14 -6
- data/spec/integration/faceting_spec.rb +39 -0
- data/spec/integration/keyword_search_spec.rb +1 -1
- data/spec/integration/scoped_search_spec.rb +175 -0
- data/spec/mocks/mock_adapter.rb +7 -10
- data/spec/mocks/post.rb +7 -2
- data/tasks/rdoc.rake +7 -0
- data/tasks/spec.rake +3 -0
- data/tasks/todo.rake +4 -0
- metadata +12 -7
- data/lib/sunspot/builder.rb +0 -78
- data/spec/api/standard_search_builder_spec.rb +0 -76
- data/spec/custom_expectation.rb +0 -53
- data/spec/integration/field_types_spec.rb +0 -62
data/lib/sunspot.rb
CHANGED
@@ -1,49 +1,278 @@
|
|
1
1
|
gem 'solr-ruby'
|
2
|
-
gem 'extlib'
|
3
2
|
require 'solr'
|
4
|
-
require 'extlib'
|
5
3
|
require File.join(File.dirname(__FILE__), 'light_config')
|
6
4
|
|
7
|
-
%w(adapters
|
5
|
+
%w(adapters restriction configuration setup field facets indexer query search facet facet_row session type util dsl).each do |filename|
|
8
6
|
require File.join(File.dirname(__FILE__), 'sunspot', filename)
|
9
7
|
end
|
10
8
|
|
9
|
+
#
|
10
|
+
# The Sunspot module provides class-method entry points to most of the
|
11
|
+
# functionality provided by the Sunspot library. Internally, the Sunspot
|
12
|
+
# singleton class contains a (non-thread-safe!) instance of Sunspot::Session,
|
13
|
+
# to which it delegates most of the class methods it exposes. In the method
|
14
|
+
# documentation below, this instance is referred to as the "singleton session".
|
15
|
+
#
|
16
|
+
# Though the singleton session provides a convenient entry point to Sunspot,
|
17
|
+
# it is by no means required to use the Sunspot class methods. Multiple sessions
|
18
|
+
# may be instantiated and used (if you need to connect to multiple Solr
|
19
|
+
# instances, for example.)
|
20
|
+
#
|
21
|
+
# Note that the configuration of classes for index/search (the +setup+
|
22
|
+
# method) is _not_ session-specific, but rather global.
|
23
|
+
#
|
11
24
|
module Sunspot
|
12
|
-
|
13
|
-
|
25
|
+
UnrecognizedFieldError = Class.new(Exception)
|
26
|
+
UnrecognizedRestrictionError = Class.new(Exception)
|
14
27
|
|
15
|
-
class <<
|
16
|
-
|
17
|
-
|
18
|
-
|
28
|
+
class <<self
|
29
|
+
# Configures indexing and search for a given class.
|
30
|
+
#
|
31
|
+
# ==== Parameters
|
32
|
+
#
|
33
|
+
# clazz<Class>:: class to configure
|
34
|
+
#
|
35
|
+
# ==== Example
|
36
|
+
#
|
37
|
+
# Sunspot.setup(Post) do
|
38
|
+
# text :title, :body
|
39
|
+
# string :author_name
|
40
|
+
# integer :blog_id
|
41
|
+
# integer :category_ids
|
42
|
+
# float :average_rating, :using => :ratings_average
|
43
|
+
# time :published_at
|
44
|
+
# string :sort_title do
|
45
|
+
# title.downcase.sub(/^(an?|the)\W+/, ''/) if title = self.title
|
46
|
+
# end
|
47
|
+
# end
|
48
|
+
#
|
49
|
+
# ====== Attribute Fields vs. Virtual Fields
|
50
|
+
#
|
51
|
+
# Attribute fields call a method on the indexed object and index the
|
52
|
+
# return value. All of the fields defined above except for the last one are
|
53
|
+
# attribute fields. By default, the field name will also be the attribute
|
54
|
+
# used; this can be overriden with the +:using+ option, as in
|
55
|
+
# +:average_rating+ above. In that case, the attribute +:ratings_average+
|
56
|
+
# will be indexed with the field name +:average_rating+.
|
57
|
+
#
|
58
|
+
# +:sort_title+ is a virtual field, which evaluates the block inside the
|
59
|
+
# context of the instance being indexed, and indexes the value returned
|
60
|
+
# by the block. If the block you pass takes an argument, it will be passed
|
61
|
+
# the instance rather than being evaluated inside of it; so, the following
|
62
|
+
# example is equivalent to the one above (assuming #title is public):
|
63
|
+
#
|
64
|
+
# Sunspot.setup(Post) do
|
65
|
+
# string :sort_title do |post|
|
66
|
+
# post.title.downcase.sub(/^(an?|the)\W+/, ''/) if title = self.title
|
67
|
+
# end
|
68
|
+
# end
|
69
|
+
#
|
70
|
+
# ===== Field Types
|
71
|
+
#
|
72
|
+
# The available types are:
|
73
|
+
#
|
74
|
+
# * +text+
|
75
|
+
# * +string+
|
76
|
+
# * +integer+
|
77
|
+
# * +float+
|
78
|
+
# * +time+
|
79
|
+
# * +boolean+
|
80
|
+
#
|
81
|
+
# Note that the +text+ type behaves quite differently from the others -
|
82
|
+
# this is the type that is indexed as fulltext, and is searched using the
|
83
|
+
# +keywords+ method inside the search DSL. Text fields cannot have
|
84
|
+
# restrictions set on them, nor can they be used in order statements or
|
85
|
+
# for facets. All other types are indexed literally, and thus can be used
|
86
|
+
# for all of those operations. They will not, however, be searched in
|
87
|
+
# fulltext. In this way, Sunspot provides a complete barrier between
|
88
|
+
# fulltext fields and value fields.
|
89
|
+
#
|
90
|
+
# It is fine to specify a field both as a text field and a string field;
|
91
|
+
# internally, the fields will have different names so there is no danger
|
92
|
+
# of conflict.
|
93
|
+
#
|
94
|
+
def setup(clazz, &block)
|
95
|
+
Setup.setup(clazz, &block)
|
96
|
+
end
|
19
97
|
|
20
|
-
|
21
|
-
|
22
|
-
|
98
|
+
# Indexes objects on the singleton session.
|
99
|
+
#
|
100
|
+
# ==== Parameters
|
101
|
+
#
|
102
|
+
# objects...<Object>:: objects to index (may pass an array or varargs)
|
103
|
+
#
|
104
|
+
# ==== Example
|
105
|
+
#
|
106
|
+
# post1, post2 = Array(2) { Post.create }
|
107
|
+
# Sunspot.index(post1, post2)
|
108
|
+
#
|
109
|
+
# Note that indexed objects won't be reflected in search until a commit is
|
110
|
+
# sent - see Sunspot.index! and Sunspot.commit
|
111
|
+
#
|
112
|
+
def index(*objects)
|
113
|
+
session.index(*objects)
|
114
|
+
end
|
23
115
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
116
|
+
# Indexes objects on the singleton session and commits immediately.
|
117
|
+
#
|
118
|
+
# See: Sunspot.index and Sunspot.commit
|
119
|
+
#
|
120
|
+
# ==== Parameters
|
121
|
+
#
|
122
|
+
# objects...<Object>:: objects to index (may pass an array or varargs)
|
123
|
+
#
|
124
|
+
def index!(*objects)
|
125
|
+
session.index!(*objects)
|
126
|
+
end
|
31
127
|
|
32
|
-
|
33
|
-
|
34
|
-
|
128
|
+
# Commits the singleton session
|
129
|
+
#
|
130
|
+
# When documents are added to or removed from Solr, the changes are
|
131
|
+
# initially stored in memory, and are not reflected in Solr's existing
|
132
|
+
# searcher instance. When a commit message is sent, the changes are written
|
133
|
+
# to disk, and a new searcher is spawned. Commits are thus fairly
|
134
|
+
# expensive, so if your application needs to index several documents as part
|
135
|
+
# of a single operation, it is advisable to index them all and then call
|
136
|
+
# commit at the end of the operation.
|
137
|
+
#
|
138
|
+
# Note that Solr can also be configured to automatically perform a commit
|
139
|
+
# after either a specified interval after the last change, or after a
|
140
|
+
# specified number of documents are added. See
|
141
|
+
# http://wiki.apache.org/solr/SolrConfigXml
|
142
|
+
#
|
143
|
+
def commit
|
144
|
+
session.commit
|
145
|
+
end
|
35
146
|
|
36
|
-
|
37
|
-
|
38
|
-
|
147
|
+
# Search for objects in the index.
|
148
|
+
#
|
149
|
+
# ==== Parameters
|
150
|
+
#
|
151
|
+
# types<Class>...::
|
152
|
+
# Zero, one, or more types to search for. If no types are passed, all
|
153
|
+
# configured types will be searched.
|
154
|
+
#
|
155
|
+
# ==== Returns
|
156
|
+
#
|
157
|
+
# Sunspot::Search:: Object containing results, facets, count, etc.
|
158
|
+
#
|
159
|
+
# The fields available for restriction, ordering, etc. are those that meet
|
160
|
+
# the following criteria:
|
161
|
+
#
|
162
|
+
# * They are not of type +text+.
|
163
|
+
# * They are defined for all of the classes being searched
|
164
|
+
# * They have the same data type for all of the classes being searched
|
165
|
+
# * They have the same multiple flag for all of the classes being searched.
|
166
|
+
#
|
167
|
+
# The restrictions available are the constants defined in the
|
168
|
+
# Sunspot::Restriction class. The standard restrictions are:
|
169
|
+
#
|
170
|
+
# with(:field_name).equal_to(value)
|
171
|
+
# with(:field_name, value) # shorthand for above
|
172
|
+
# with(:field_name).less_than(value)
|
173
|
+
# with(:field_name).greater_than(value)
|
174
|
+
# with(:field_name).between(value1..value2)
|
175
|
+
# with(:field_name).any_of([value1, value2, value3])
|
176
|
+
# with(:field_name).all_of([value1, value2, value3])
|
177
|
+
# without(some_instance) # exclude that particular instance
|
178
|
+
#
|
179
|
+
# +without+ can be substituted for +with+, causing the restriction to be
|
180
|
+
# negated. In the last example above, only +without+ works, as it does not
|
181
|
+
# make sense to search only for an instance you already have.
|
182
|
+
#
|
183
|
+
# Equality restrictions can take +nil+ as a value, which restricts the
|
184
|
+
# results to documents that have no value for the given field. Passing +nil+
|
185
|
+
# as a value to other restriction types is illegal. Thus:
|
186
|
+
#
|
187
|
+
# with(:field_name, nil) # ok
|
188
|
+
# with(:field_name).equal_to(nil) # ok
|
189
|
+
# with(:field_name).less_than(nil) # bad
|
190
|
+
#
|
191
|
+
# ==== Example
|
192
|
+
#
|
193
|
+
# Sunspot.search(Post) do
|
194
|
+
# keywords 'great pizza'
|
195
|
+
# with(:published_at).less_than Time.now
|
196
|
+
# with :blog_id, 1
|
197
|
+
# without current_post
|
198
|
+
# facet :category_ids
|
199
|
+
# order_by :published_at, :desc
|
200
|
+
# paginate 2, 15
|
201
|
+
# end
|
202
|
+
#
|
203
|
+
# See Sunspot::DSL::Query for the full API presented inside the block.
|
204
|
+
#
|
205
|
+
def search(*types, &block)
|
206
|
+
session.search(*types, &block)
|
207
|
+
end
|
39
208
|
|
40
|
-
|
41
|
-
|
42
|
-
|
209
|
+
# Remove objects from the index. Any time an object is destroyed, it must
|
210
|
+
# be removed from the index; otherwise, the index will contain broken
|
211
|
+
# references to objects that do not exist, which will cause errors when
|
212
|
+
# those objects are matched in search results.
|
213
|
+
#
|
214
|
+
# ==== Parameters
|
215
|
+
#
|
216
|
+
# objects...<Object>::
|
217
|
+
# Objects to remove from the index (may pass an array or varargs)
|
218
|
+
#
|
219
|
+
# ==== Example
|
220
|
+
#
|
221
|
+
# post.destroy
|
222
|
+
# Sunspot.remove(post)
|
223
|
+
#
|
224
|
+
def remove(*objects)
|
225
|
+
session.remove(*objects)
|
226
|
+
end
|
227
|
+
|
228
|
+
# Remove all objects of the given classes from the index. There isn't much
|
229
|
+
# use for this in general operations but it can be useful for maintenance,
|
230
|
+
# testing, etc.
|
231
|
+
#
|
232
|
+
# ==== Parameters
|
233
|
+
#
|
234
|
+
# classes...<Class>::
|
235
|
+
# classes for which to remove all instances from the index (may pass an
|
236
|
+
# array or varargs)
|
237
|
+
#
|
238
|
+
# ==== Example
|
239
|
+
#
|
240
|
+
# Sunspot.remove_all(Post, Blog)
|
241
|
+
#
|
242
|
+
def remove_all(*classes)
|
243
|
+
session.remove_all(*classes)
|
244
|
+
end
|
245
|
+
|
246
|
+
# Returns the configuration associated with the singleton session. See
|
247
|
+
# Sunspot::Configuration for details.
|
248
|
+
#
|
249
|
+
# ==== Returns
|
250
|
+
#
|
251
|
+
# LightConfig::Configuration:: configuration for singleton session
|
252
|
+
#
|
253
|
+
def config
|
254
|
+
session.config
|
255
|
+
end
|
256
|
+
|
257
|
+
#
|
258
|
+
# Resets the singleton session. This is useful for clearing out all
|
259
|
+
# static data between tests, but probably nowhere else.
|
260
|
+
#
|
261
|
+
def reset!
|
262
|
+
@session = nil
|
263
|
+
end
|
43
264
|
|
44
|
-
|
265
|
+
private
|
45
266
|
|
46
|
-
|
47
|
-
|
267
|
+
#
|
268
|
+
# Get the singleton session, creating it if none yet exists.
|
269
|
+
#
|
270
|
+
# ==== Returns
|
271
|
+
#
|
272
|
+
# Sunspot::Session:: the singleton session
|
273
|
+
#
|
274
|
+
def session #:nodoc:
|
275
|
+
@session ||= Session.new
|
276
|
+
end
|
48
277
|
end
|
49
278
|
end
|
@@ -13,50 +13,51 @@ describe 'Search' do
|
|
13
13
|
connection.should_receive(:query).with('(type:Post)', hash_including(:filter_queries => ['title_s:My\ Pet\ Post'])).twice
|
14
14
|
session.search Post, :conditions => { :title => 'My Pet Post' }
|
15
15
|
session.search Post do
|
16
|
-
with
|
16
|
+
with :title, 'My Pet Post'
|
17
17
|
end
|
18
18
|
end
|
19
19
|
|
20
20
|
it 'should ignore nonexistant fields in hash scope' do
|
21
|
-
connection.should_receive(:query).with('(type:Post)',
|
21
|
+
connection.should_receive(:query).with('(type:Post)', hash_not_including(:filter_queries))
|
22
22
|
session.search Post, :conditions => { :bogus => 'Field' }
|
23
23
|
end
|
24
24
|
|
25
|
-
it 'should raise an ArgumentError for nonexistant fields in block scope' do
|
26
|
-
lambda do
|
27
|
-
session.search Post do
|
28
|
-
with.bogus 'Field'
|
29
|
-
end
|
30
|
-
end.should raise_error(ArgumentError)
|
31
|
-
end
|
32
|
-
|
33
25
|
it 'should scope by exact match with time' do
|
34
26
|
connection.should_receive(:query).with('(type:Post)', hash_including(:filter_queries => ['published_at_d:1983\-07\-08T09\:00\:00Z'])).twice
|
35
27
|
time = Time.parse('1983-07-08 05:00:00 -0400')
|
36
28
|
session.search Post, :conditions => { :published_at => time }
|
37
29
|
session.search Post do
|
38
|
-
with
|
30
|
+
with :published_at, time
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'should scope by exact match with boolean' do
|
35
|
+
connection.should_receive(:query).with('(type:Post)', hash_including(:filter_queries => ['featured_b:false'])).twice
|
36
|
+
session.search Post, :conditions => { :featured => false }
|
37
|
+
session.search Post do
|
38
|
+
with :featured, false
|
39
39
|
end
|
40
40
|
end
|
41
41
|
|
42
42
|
it 'should scope by less than match with float' do
|
43
43
|
connection.should_receive(:query).with('(type:Post)', hash_including(:filter_queries => ['average_rating_f:[* TO 3\.0]']))
|
44
44
|
session.search Post do
|
45
|
-
with
|
45
|
+
with(:average_rating).less_than 3.0
|
46
46
|
end
|
47
47
|
end
|
48
48
|
|
49
49
|
it 'should scope by greater than match with float' do
|
50
50
|
connection.should_receive(:query).with('(type:Post)', hash_including(:filter_queries => ['average_rating_f:[3\.0 TO *]']))
|
51
51
|
session.search Post do
|
52
|
-
with
|
52
|
+
with(:average_rating).greater_than 3.0
|
53
53
|
end
|
54
54
|
end
|
55
55
|
|
56
56
|
it 'should scope by between match with float' do
|
57
|
-
connection.should_receive(:query).with('(type:Post)', hash_including(:filter_queries => ['average_rating_f:[2\.0 TO 4\.0]']))
|
57
|
+
connection.should_receive(:query).with('(type:Post)', hash_including(:filter_queries => ['average_rating_f:[2\.0 TO 4\.0]'])).twice
|
58
|
+
session.search Post, :conditions => { :average_rating => 2.0..4.0 }
|
58
59
|
session.search Post do
|
59
|
-
with
|
60
|
+
with(:average_rating).between 2.0..4.0
|
60
61
|
end
|
61
62
|
end
|
62
63
|
|
@@ -64,14 +65,95 @@ describe 'Search' do
|
|
64
65
|
connection.should_receive(:query).with('(type:Post)', hash_including(:filter_queries => ['category_ids_im:(2 OR 7 OR 12)'])).twice
|
65
66
|
session.search Post, :conditions => { :category_ids => [2, 7, 12] }
|
66
67
|
session.search Post do
|
67
|
-
with
|
68
|
+
with(:category_ids).any_of [2, 7, 12]
|
68
69
|
end
|
69
70
|
end
|
70
71
|
|
71
72
|
it 'should scope by all match with integer' do
|
72
73
|
connection.should_receive(:query).with('(type:Post)', hash_including(:filter_queries => ['category_ids_im:(2 AND 7 AND 12)']))
|
73
74
|
session.search Post do
|
74
|
-
with
|
75
|
+
with(:category_ids).all_of [2, 7, 12]
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'should scope by not equal match with string' do
|
80
|
+
connection.should_receive(:query).with('(type:Post)', hash_including(:filter_queries => ['-title_s:Bad\ Post']))
|
81
|
+
session.search Post do
|
82
|
+
without :title, 'Bad Post'
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
it 'should scope by not less than match with float' do
|
87
|
+
connection.should_receive(:query).with('(type:Post)', hash_including(:filter_queries => ['-average_rating_f:[* TO 3\.0]']))
|
88
|
+
session.search Post do
|
89
|
+
without(:average_rating).less_than 3.0
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'should scope by not greater than match with float' do
|
94
|
+
connection.should_receive(:query).with('(type:Post)', hash_including(:filter_queries => ['-average_rating_f:[3\.0 TO *]']))
|
95
|
+
session.search Post do
|
96
|
+
without(:average_rating).greater_than 3.0
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'should scope by not between match with float' do
|
101
|
+
connection.should_receive(:query).with('(type:Post)', hash_including(:filter_queries => ['-average_rating_f:[2\.0 TO 4\.0]']))
|
102
|
+
session.search Post do
|
103
|
+
without(:average_rating).between 2.0..4.0
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
it 'should scope by not any match with integer' do
|
108
|
+
connection.should_receive(:query).with('(type:Post)', hash_including(:filter_queries => ['-category_ids_im:(2 OR 7 OR 12)']))
|
109
|
+
session.search Post do
|
110
|
+
without(:category_ids).any_of [2, 7, 12]
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
|
115
|
+
it 'should scope by not all match with integer' do
|
116
|
+
connection.should_receive(:query).with('(type:Post)', hash_including(:filter_queries => ['-category_ids_im:(2 AND 7 AND 12)']))
|
117
|
+
session.search Post do
|
118
|
+
without(:category_ids).all_of [2, 7, 12]
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
it 'should scope by empty field' do
|
123
|
+
connection.should_receive(:query).with('(type:Post)', hash_including(:filter_queries => ['-average_rating_f:[* TO *]']))
|
124
|
+
session.search Post do
|
125
|
+
with :average_rating, nil
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
it 'should scope by non-empty field' do
|
130
|
+
connection.should_receive(:query).with('(type:Post)', hash_including(:filter_queries => ['average_rating_f:[* TO *]']))
|
131
|
+
session.search Post do
|
132
|
+
without :average_rating, nil
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
it 'should exclude by object identity' do
|
137
|
+
post = Post.new
|
138
|
+
connection.should_receive(:query).with('(type:Post)', hash_including(:filter_queries => ["-id:Post\\ #{post.id}"]))
|
139
|
+
session.search Post do
|
140
|
+
without post
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
it 'should exclude multiple objects passed as varargs by object identity' do
|
145
|
+
post1, post2 = Post.new, Post.new
|
146
|
+
connection.should_receive(:query).with('(type:Post)', hash_including(:filter_queries => ["-id:Post\\ #{post1.id}", "-id:Post\\ #{post2.id}"]))
|
147
|
+
session.search Post do
|
148
|
+
without post1, post2
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
it 'should exclude multiple objects passed as array by object identity' do
|
153
|
+
posts = [Post.new, Post.new]
|
154
|
+
connection.should_receive(:query).with('(type:Post)', hash_including(:filter_queries => ["-id:Post\\ #{posts.first.id}", "-id:Post\\ #{posts.last.id}"]))
|
155
|
+
session.search Post do
|
156
|
+
without posts
|
75
157
|
end
|
76
158
|
end
|
77
159
|
|
@@ -89,7 +171,7 @@ describe 'Search' do
|
|
89
171
|
end
|
90
172
|
|
91
173
|
it 'should paginate using provided per_page' do
|
92
|
-
connection.should_receive(:query).with('(type:Post)', :
|
174
|
+
connection.should_receive(:query).with('(type:Post)', hash_including(:rows => 15, :start => 45)).twice
|
93
175
|
session.search Post, :page => 4, :per_page => 15
|
94
176
|
session.search Post do
|
95
177
|
paginate :page => 4, :per_page => 15
|
@@ -104,6 +186,30 @@ describe 'Search' do
|
|
104
186
|
end
|
105
187
|
end
|
106
188
|
|
189
|
+
it 'should order by multiple columns' do
|
190
|
+
connection.should_receive(:query).with('(type:Post)', hash_including(:sort => [{ :average_rating_f => :descending },
|
191
|
+
{ :sort_title_s => :ascending }])).twice
|
192
|
+
session.search Post, :order => ['average_rating desc', 'sort_title asc']
|
193
|
+
session.search Post do
|
194
|
+
order_by :average_rating, :desc
|
195
|
+
order_by :sort_title, :asc
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
it 'should request single field facet' do
|
200
|
+
connection.should_receive(:query).with('(type:Post)', hash_including(:facets => { :fields => %w(category_ids_im) }))
|
201
|
+
session.search Post do
|
202
|
+
facet :category_ids
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
it 'should request multiple field facet' do
|
207
|
+
connection.should_receive(:query).with('(type:Post)', hash_including(:facets => { :fields => %w(category_ids_im blog_id_i) }))
|
208
|
+
session.search Post do
|
209
|
+
facet :category_ids, :blog_id
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
107
213
|
it 'should build search for multiple types' do
|
108
214
|
connection.should_receive(:query).with('(type:(Post OR Comment))', hash_including)
|
109
215
|
session.search(Post, Comment)
|
@@ -114,43 +220,43 @@ describe 'Search' do
|
|
114
220
|
time = Time.parse('1983-07-08 05:00:00 -0400')
|
115
221
|
session.search Post, Comment, :conditions => { :published_at => time }
|
116
222
|
session.search Post, Comment do
|
117
|
-
with
|
223
|
+
with :published_at, time
|
118
224
|
end
|
119
225
|
end
|
120
226
|
|
121
|
-
it 'should raise
|
227
|
+
it 'should raise Sunspot::UnrecognizedFieldError if search scoped to field not common to all types' do
|
122
228
|
lambda do
|
123
229
|
session.search Post, Comment do
|
124
|
-
with
|
230
|
+
with :blog_id, 1
|
125
231
|
end
|
126
|
-
end.should raise_error(
|
232
|
+
end.should raise_error(Sunspot::UnrecognizedFieldError)
|
127
233
|
end
|
128
234
|
|
129
|
-
it 'should raise
|
235
|
+
it 'should raise Sunspot::UnrecognizedFieldError if search scoped to field configured differently between types' do
|
130
236
|
lambda do
|
131
237
|
session.search Post, Comment do
|
132
|
-
with
|
238
|
+
with :average_rating, 2.2 # this is a float in Post but an integer in Comment
|
133
239
|
end
|
134
|
-
end.should raise_error(
|
240
|
+
end.should raise_error(Sunspot::UnrecognizedFieldError)
|
135
241
|
end
|
136
242
|
|
137
243
|
it 'should ignore condition if field is not common to all types' do
|
138
|
-
connection.should_receive(:query).with('(type:(Post OR Comment))',
|
244
|
+
connection.should_receive(:query).with('(type:(Post OR Comment))', hash_not_including(:filter_queries))
|
139
245
|
session.search Post, Comment, :conditions => { :blog_id => 1 }
|
140
246
|
end
|
141
247
|
|
142
|
-
it 'should raise
|
248
|
+
it 'should raise Sunspot::UnrecognizedFieldError for nonexistant fields in block scope' do
|
143
249
|
lambda do
|
144
250
|
session.search Post do
|
145
|
-
with
|
251
|
+
with :bogus, 'Field'
|
146
252
|
end
|
147
|
-
end.should raise_error(
|
253
|
+
end.should raise_error(Sunspot::UnrecognizedFieldError)
|
148
254
|
end
|
149
255
|
|
150
256
|
it 'should raise NoMethodError if bogus operator referenced' do
|
151
257
|
lambda do
|
152
258
|
session.search Post do
|
153
|
-
with
|
259
|
+
with(:category_ids).resembling :bogus_condition
|
154
260
|
end
|
155
261
|
end.should raise_error(NoMethodError)
|
156
262
|
end
|
@@ -171,12 +277,12 @@ describe 'Search' do
|
|
171
277
|
end.should raise_error(ArgumentError)
|
172
278
|
end
|
173
279
|
|
174
|
-
it 'should raise
|
280
|
+
it 'should raise ArgumentError if more than two arguments passed to scope method' do
|
175
281
|
lambda do
|
176
282
|
session.search Post do
|
177
|
-
with
|
283
|
+
with(:category_ids, 4, 5)
|
178
284
|
end
|
179
|
-
end.should raise_error(
|
285
|
+
end.should raise_error(ArgumentError)
|
180
286
|
end
|
181
287
|
|
182
288
|
private
|
data/spec/api/indexer_spec.rb
CHANGED
@@ -13,7 +13,7 @@ describe 'indexer' do
|
|
13
13
|
session.index post
|
14
14
|
end
|
15
15
|
|
16
|
-
it 'should correctly index a string attribute field' do
|
16
|
+
it 'should correctly index a string attribute field' do
|
17
17
|
post :title => 'A Title'
|
18
18
|
connection.should_receive(:add).with(hash_including(:title_s => 'A Title'))
|
19
19
|
session.index post
|
@@ -26,7 +26,7 @@ describe 'indexer' do
|
|
26
26
|
end
|
27
27
|
|
28
28
|
it 'should correctly index a float attribute field' do
|
29
|
-
post :
|
29
|
+
post :ratings_average => 2.23
|
30
30
|
connection.should_receive(:add).with(hash_including(:average_rating_f => '2.23'))
|
31
31
|
session.index post
|
32
32
|
end
|
@@ -43,12 +43,36 @@ describe 'indexer' do
|
|
43
43
|
session.index post
|
44
44
|
end
|
45
45
|
|
46
|
+
it 'should correctly index a boolean field' do
|
47
|
+
post :featured => true
|
48
|
+
connection.should_receive(:add).with(hash_including(:featured_b => 'true'))
|
49
|
+
session.index post
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'should correctly index a false boolean field' do
|
53
|
+
post :featured => false
|
54
|
+
connection.should_receive(:add).with(hash_including(:featured_b => 'false'))
|
55
|
+
session.index post
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'should not index a nil boolean field' do
|
59
|
+
post
|
60
|
+
connection.should_receive(:add).with(hash_not_including(:featured_b))
|
61
|
+
session.index post
|
62
|
+
end
|
63
|
+
|
46
64
|
it 'should correctly index a virtual field' do
|
47
65
|
post :title => 'The Blog Post'
|
48
66
|
connection.should_receive(:add).with(hash_including(:sort_title_s => 'blog post'))
|
49
67
|
session.index post
|
50
68
|
end
|
51
69
|
|
70
|
+
it 'should correctly index an external virtual field' do
|
71
|
+
post :category_ids => [1, 2, 3]
|
72
|
+
connection.should_receive(:add).with(hash_including(:primary_category_id_i => '1'))
|
73
|
+
session.index post
|
74
|
+
end
|
75
|
+
|
52
76
|
it 'should correctly index a field that is defined on a superclass' do
|
53
77
|
Sunspot.setup(BaseClass) { string :author_name }
|
54
78
|
post :author_name => 'Mat Brown'
|
@@ -56,6 +80,13 @@ describe 'indexer' do
|
|
56
80
|
session.index post
|
57
81
|
end
|
58
82
|
|
83
|
+
it 'should commit immediately after index! called' do
|
84
|
+
post :title => 'The Blog Post'
|
85
|
+
connection.should_receive(:add).ordered
|
86
|
+
connection.should_receive(:commit).ordered
|
87
|
+
session.index!(post)
|
88
|
+
end
|
89
|
+
|
59
90
|
it 'should remove an object from the index' do
|
60
91
|
connection.should_receive(:delete).with("Post #{post.id}")
|
61
92
|
session.remove(post)
|