sunspot_matchers 1.1.0.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/.gitignore +3 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +33 -0
- data/README.markdown +229 -0
- data/Rakefile +2 -0
- data/lib/sunspot_matchers/matchers.rb +237 -0
- data/lib/sunspot_matchers/sunspot_session_spy.rb +111 -0
- data/lib/sunspot_matchers/version.rb +3 -0
- data/lib/sunspot_matchers.rb +2 -0
- data/spec/sunspot_matchers_spec.rb +651 -0
- data/sunspot_matchers.gemspec +22 -0
- metadata +128 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
sunspot_matchers (1.1.0.0)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: http://rubygems.org/
|
8
|
+
specs:
|
9
|
+
builder (3.0.0)
|
10
|
+
diff-lcs (1.1.2)
|
11
|
+
escape (0.0.4)
|
12
|
+
rsolr (0.12.1)
|
13
|
+
builder (>= 2.1.2)
|
14
|
+
rspec (2.1.0)
|
15
|
+
rspec-core (~> 2.1.0)
|
16
|
+
rspec-expectations (~> 2.1.0)
|
17
|
+
rspec-mocks (~> 2.1.0)
|
18
|
+
rspec-core (2.1.0)
|
19
|
+
rspec-expectations (2.1.0)
|
20
|
+
diff-lcs (~> 1.1.2)
|
21
|
+
rspec-mocks (2.1.0)
|
22
|
+
sunspot (1.1.0)
|
23
|
+
escape (= 0.0.4)
|
24
|
+
rsolr (= 0.12.1)
|
25
|
+
|
26
|
+
PLATFORMS
|
27
|
+
ruby
|
28
|
+
|
29
|
+
DEPENDENCIES
|
30
|
+
bundler (>= 1.0.0)
|
31
|
+
rspec (~> 2.1.0)
|
32
|
+
sunspot (~> 1.1.0)
|
33
|
+
sunspot_matchers!
|
data/README.markdown
ADDED
@@ -0,0 +1,229 @@
|
|
1
|
+
# Sunspot Matchers
|
2
|
+
|
3
|
+
[Sunspot](http://outoftime.github.com/sunspot/) is a great Ruby library for constructing searches against Solr. However,
|
4
|
+
because of the way the Sunspot DSL is constructed, it can be difficult to do simple assertions about your searches
|
5
|
+
without doing full integration tests.
|
6
|
+
|
7
|
+
The goal of these matchers are to make it easier to unit test search logic without having to construct the individual
|
8
|
+
fixture scenarios inside of Solr and then actually perform a search against Solr.
|
9
|
+
|
10
|
+
# Installation
|
11
|
+
|
12
|
+
You will need to replace the Sunspot Session object with the spy provided. You can do this globally by putting the
|
13
|
+
following in your spec_helper.
|
14
|
+
|
15
|
+
config.before do
|
16
|
+
Sunspot.session = SunspotMatchers::SunspotSessionSpy.new(Sunspot.session)
|
17
|
+
end
|
18
|
+
|
19
|
+
Keep in mind, this will prevent any test from actually hitting Solr, so if you have integration tests, you'll either
|
20
|
+
need to be more careful which tests you replace the session for, or you'll need to restore the original session before
|
21
|
+
those tests
|
22
|
+
|
23
|
+
Sunspot.session = Sunspot.session.original_session
|
24
|
+
|
25
|
+
You will also need to include the matchers in your specs. Again, this can be done globally in your spec_helper.
|
26
|
+
|
27
|
+
config.include SunspotMatchers
|
28
|
+
|
29
|
+
Alternately, you could include them into individual tests if needed.
|
30
|
+
|
31
|
+
# Matchers
|
32
|
+
|
33
|
+
## be_a_search_for
|
34
|
+
|
35
|
+
If you perform a search against your Post model, you could write this expectation:
|
36
|
+
|
37
|
+
`Sunspot.session.should be_a_search_for(Post)`
|
38
|
+
|
39
|
+
Individual searches are stored in an array, so if you perform multiple, you'll have to match against them manually. Without
|
40
|
+
an explicit search specified, it will use the last one.
|
41
|
+
|
42
|
+
`Sunspot.session.searches.first.should be_a_search_for(Post)`
|
43
|
+
|
44
|
+
## have_search_params
|
45
|
+
|
46
|
+
This is where the bulk of the functionality lies. There are seven types of search matches you can perform: `keywords`,
|
47
|
+
`with`, `without`, `paginate`, `order_by`, `facet`, and `boost`.
|
48
|
+
|
49
|
+
In all of the examples below, the expectation fully matches the search terms. This is not expected or required. You can
|
50
|
+
have a dozen `with` restrictions on a search and still write an expectation on a single one of them.
|
51
|
+
|
52
|
+
Negative expectations also work correctly. `should_not` will fail if the search actually includes the expectation.
|
53
|
+
|
54
|
+
With all matchers, you can specify a `Proc` as the second argument, and perform multi statement expectations inside the
|
55
|
+
Proc. Keep in mind, that only the expectation type specified in the first argument will actually be checked. So if
|
56
|
+
you specify `keywords` and `with` restrictions in the same Proc, but you said `have_search_params(:keywords, ...`
|
57
|
+
the `with` restrictions are simply ignored.
|
58
|
+
|
59
|
+
### wildcard matching
|
60
|
+
|
61
|
+
keywords, with, and without support wildcard expectations using the `any_param` parameter:
|
62
|
+
|
63
|
+
Sunspot.search(Post) do
|
64
|
+
with :blog_id, 4
|
65
|
+
end
|
66
|
+
|
67
|
+
Sunspot.session.should have_search_params(:with, :blog_id, any_param)
|
68
|
+
|
69
|
+
### :keywords
|
70
|
+
|
71
|
+
You can match against a keyword search:
|
72
|
+
|
73
|
+
Sunspot.search(Post) do
|
74
|
+
keywords 'great pizza'
|
75
|
+
end
|
76
|
+
|
77
|
+
Sunspot.session.should have_search_params(:keywords, 'great pizza')
|
78
|
+
|
79
|
+
### :with
|
80
|
+
|
81
|
+
You can match against a with restriction:
|
82
|
+
|
83
|
+
Sunspot.search(Post) do
|
84
|
+
with :author_name, 'Mark Twain'
|
85
|
+
end
|
86
|
+
|
87
|
+
Sunspot.session.should have_search_params(:with, :author_name, 'Mark Twain')
|
88
|
+
|
89
|
+
Complex conditions can be matched by using a Proc instead of a value. Be aware that order does matter, not for
|
90
|
+
the actual results that would come out of Solr, but the matcher will fail of the order of `with` restrictions is
|
91
|
+
different.
|
92
|
+
|
93
|
+
Sunspot.search(Post) do
|
94
|
+
any_of do
|
95
|
+
with :category_ids, 1
|
96
|
+
with :category_ids, 2
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
Sunspot.session.should have_search_params(:with, Proc.new {
|
101
|
+
any_of do
|
102
|
+
with :category_ids, 1
|
103
|
+
with :category_ids, 2
|
104
|
+
end
|
105
|
+
})
|
106
|
+
|
107
|
+
### :without
|
108
|
+
|
109
|
+
Without is nearly identical to with:
|
110
|
+
|
111
|
+
Sunspot.search(Post) do
|
112
|
+
without :author_name, 'Mark Twain'
|
113
|
+
end
|
114
|
+
|
115
|
+
Sunspot.session.should have_search_params(:without, :author_name, 'Mark Twain')
|
116
|
+
|
117
|
+
### :paginate
|
118
|
+
|
119
|
+
You can also specify only page or per_page, both are not required.
|
120
|
+
|
121
|
+
Sunspot.search(Post) do
|
122
|
+
paginate :page => 3, :per_page => 15
|
123
|
+
end
|
124
|
+
|
125
|
+
Sunspot.session.should have_search_params(:paginate, :page => 3, :per_page => 15)
|
126
|
+
|
127
|
+
### :order_by
|
128
|
+
|
129
|
+
Expectations on multiple orderings are supported using using the Proc format mentioned above.
|
130
|
+
|
131
|
+
Sunspot.search(Post) do
|
132
|
+
order_by :published_at, :desc
|
133
|
+
end
|
134
|
+
|
135
|
+
Sunspot.session.should have_search_params(:order_by, :published_at, :desc)
|
136
|
+
|
137
|
+
### :facet
|
138
|
+
|
139
|
+
Standard faceting expectation:
|
140
|
+
|
141
|
+
Sunspot.search(Post) do
|
142
|
+
facet :category_ids
|
143
|
+
end
|
144
|
+
|
145
|
+
Sunspot.session.should have_search_params(:facet, :category_ids)
|
146
|
+
|
147
|
+
Faceting where a query is excluded:
|
148
|
+
|
149
|
+
Sunspot.search(Post) do
|
150
|
+
category_filter = with(:category_ids, 2)
|
151
|
+
facet(:category_ids, :exclude => category_filter)
|
152
|
+
end
|
153
|
+
|
154
|
+
Sunspot.session.should have_search_params(:facet, Proc.new {
|
155
|
+
category_filter = with(:category_ids, 2)
|
156
|
+
facet(:category_ids, :exclude => category_filter)
|
157
|
+
})
|
158
|
+
|
159
|
+
Query faceting:
|
160
|
+
|
161
|
+
Sunspot.search(Post) do
|
162
|
+
facet(:average_rating) do
|
163
|
+
row(1.0..2.0) do
|
164
|
+
with(:average_rating, 1.0..2.0)
|
165
|
+
end
|
166
|
+
row(2.0..3.0) do
|
167
|
+
with(:average_rating, 2.0..3.0)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
Sunspot.session.should have_search_params(:facet, Proc.new {
|
173
|
+
facet(:average_rating) do
|
174
|
+
row(1.0..2.0) do
|
175
|
+
with(:average_rating, 1.0..2.0)
|
176
|
+
end
|
177
|
+
row(2.0..3.0) do
|
178
|
+
with(:average_rating, 2.0..3.0)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
})
|
182
|
+
|
183
|
+
### :boost
|
184
|
+
|
185
|
+
Field boost matching:
|
186
|
+
|
187
|
+
Sunspot.search(Post) do
|
188
|
+
keywords 'great pizza' do
|
189
|
+
boost_fields :body => 2.0
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
Sunspot.session.should have_search_params(:boost, Proc.new {
|
194
|
+
keywords 'great pizza' do
|
195
|
+
boost_fields :body => 2.0
|
196
|
+
end
|
197
|
+
})
|
198
|
+
|
199
|
+
Boost query matching:
|
200
|
+
|
201
|
+
Sunspot.search(Post) do
|
202
|
+
keywords 'great pizza' do
|
203
|
+
boost(2.0) do
|
204
|
+
with :blog_id, 4
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
Sunspot.session.should have_search_params(:boost, Proc.new {
|
210
|
+
keywords 'great pizza' do
|
211
|
+
boost(2.0) do
|
212
|
+
with :blog_id, 4
|
213
|
+
end
|
214
|
+
end
|
215
|
+
})
|
216
|
+
|
217
|
+
Boost function matching:
|
218
|
+
|
219
|
+
Sunspot.search(Post) do
|
220
|
+
keywords 'great pizza' do
|
221
|
+
boost(function { sum(:average_rating, product(:popularity, 10)) })
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
Sunspot.session.should have_search_params(:boost, Proc.new {
|
226
|
+
keywords 'great pizza' do
|
227
|
+
boost(function { sum(:average_rating, product(:popularity, 10)) })
|
228
|
+
end
|
229
|
+
})
|
data/Rakefile
ADDED
@@ -0,0 +1,237 @@
|
|
1
|
+
module SunspotMatchers
|
2
|
+
class BaseMatcher
|
3
|
+
def initialize(actual_search, comparison_search, args)
|
4
|
+
@args = args
|
5
|
+
@actual_search, @comparison_search = actual_search, comparison_search
|
6
|
+
end
|
7
|
+
|
8
|
+
def search_tuple
|
9
|
+
search_tuple = @actual.is_a?(Array) ? @actual : @actual.searches.last
|
10
|
+
raise 'no search found' unless search_tuple
|
11
|
+
search_tuple
|
12
|
+
end
|
13
|
+
|
14
|
+
def actual_search
|
15
|
+
search_tuple.last
|
16
|
+
end
|
17
|
+
|
18
|
+
def search_types
|
19
|
+
search_tuple.first
|
20
|
+
end
|
21
|
+
|
22
|
+
def wildcard?
|
23
|
+
@args && @args.last == any_param
|
24
|
+
end
|
25
|
+
|
26
|
+
def field
|
27
|
+
@args && @args.first
|
28
|
+
end
|
29
|
+
|
30
|
+
def query_params_for_search(search)
|
31
|
+
search.instance_variable_get(:@query).to_params
|
32
|
+
end
|
33
|
+
|
34
|
+
def actual_params
|
35
|
+
@actual_params ||= query_params_for_search(@actual_search)
|
36
|
+
end
|
37
|
+
|
38
|
+
def comparison_params
|
39
|
+
@comparison_params ||= query_params_for_search(@comparison_search)
|
40
|
+
end
|
41
|
+
|
42
|
+
def match?
|
43
|
+
differences.empty?
|
44
|
+
end
|
45
|
+
|
46
|
+
def missing_param_error_message
|
47
|
+
missing_params = differences
|
48
|
+
actual_values = missing_params.keys.collect {|key| "#{key} => #{actual_params[key]}"}
|
49
|
+
missing_values = missing_params.collect{ |key, value| "#{key} => #{value}"}
|
50
|
+
"expected search params: #{actual_values.join(' and ')} to match expected: #{missing_values.join(' and ')}"
|
51
|
+
end
|
52
|
+
|
53
|
+
def unexpected_match_error_message
|
54
|
+
actual_values = keys_to_compare.collect {|key| "#{key} => #{actual_params[key]}"}
|
55
|
+
comparison_values = keys_to_compare.collect {|key| "#{key} => #{comparison_params[key]}"}
|
56
|
+
"expected search params: #{actual_values.join(' and ')} NOT to match expected: #{comparison_values.join(' and ')}"
|
57
|
+
end
|
58
|
+
|
59
|
+
def differences
|
60
|
+
keys_to_compare.inject({}) do |hsh, key|
|
61
|
+
result = compare_key(key)
|
62
|
+
hsh[key] = result unless result.empty?
|
63
|
+
hsh
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def compare_key(key)
|
68
|
+
if(actual_params[key].is_a?(Array) || comparison_params[key].is_a?(Array))
|
69
|
+
compare_multi_value(actual_params[key], comparison_params[key])
|
70
|
+
else
|
71
|
+
compare_single_value(actual_params[key], comparison_matcher_for_key(key))
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def comparison_matcher_for_key(key)
|
76
|
+
if wildcard? && wildcard_matcher_for_keys.has_key?(key)
|
77
|
+
wildcard_matcher_for_keys[key]
|
78
|
+
else
|
79
|
+
comparison_params[key]
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def compare_single_value(actual, comparison)
|
84
|
+
if comparison.is_a?(Regexp)
|
85
|
+
return [] if comparison =~ actual
|
86
|
+
return [comparison]
|
87
|
+
end
|
88
|
+
return [comparison] unless actual == comparison
|
89
|
+
[]
|
90
|
+
end
|
91
|
+
|
92
|
+
def compare_multi_value(actual, comparison)
|
93
|
+
filter_values(comparison).reject do |value|
|
94
|
+
next false unless actual
|
95
|
+
value_matcher = Regexp.new(Regexp.escape(value))
|
96
|
+
actual.any?{ |actual_value| actual_value =~ value_matcher }
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def normalize_value(value)
|
101
|
+
|
102
|
+
end
|
103
|
+
|
104
|
+
def filter_values(values)
|
105
|
+
return values unless wildcard?
|
106
|
+
field_matcher = Regexp.new(field.to_s)
|
107
|
+
values.select{ |value| field_matcher =~ value }.collect{|value| value.gsub(/:.*/, '')}
|
108
|
+
end
|
109
|
+
|
110
|
+
def wildcard_matcher_for_keys
|
111
|
+
{}
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
class HaveSearchParams < BaseMatcher
|
116
|
+
def initialize(method, *args)
|
117
|
+
@method = method
|
118
|
+
@args = args
|
119
|
+
end
|
120
|
+
|
121
|
+
def matches?(actual)
|
122
|
+
@actual = actual
|
123
|
+
@matcher = build_matcher
|
124
|
+
@matcher.match?
|
125
|
+
end
|
126
|
+
|
127
|
+
def failure_message_for_should
|
128
|
+
@matcher.missing_param_error_message
|
129
|
+
end
|
130
|
+
|
131
|
+
def failure_message_for_should_not
|
132
|
+
@matcher.unexpected_match_error_message
|
133
|
+
end
|
134
|
+
|
135
|
+
def build_matcher
|
136
|
+
comparison_search = if(@args.last.is_a?(Proc))
|
137
|
+
SunspotMatchers::SunspotSessionSpy.new(nil).build_search(search_types, &@args.last)
|
138
|
+
else
|
139
|
+
method = @method
|
140
|
+
args = @args
|
141
|
+
SunspotMatchers::SunspotSessionSpy.new(nil).build_search(search_types) do
|
142
|
+
send(method, *args)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
get_matcher.new(actual_search, comparison_search, @args)
|
147
|
+
end
|
148
|
+
|
149
|
+
def get_matcher
|
150
|
+
case @method
|
151
|
+
when :with, :without
|
152
|
+
WithMatcher
|
153
|
+
when :keywords
|
154
|
+
KeywordsMatcher
|
155
|
+
when :boost
|
156
|
+
BoostMatcher
|
157
|
+
when :facet
|
158
|
+
FacetMatcher
|
159
|
+
when :order_by
|
160
|
+
SortMatcher
|
161
|
+
when :paginate
|
162
|
+
PaginationMatcher
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
def have_search_params(method, *args)
|
168
|
+
HaveSearchParams.new(method, *args)
|
169
|
+
end
|
170
|
+
|
171
|
+
class WithMatcher < BaseMatcher
|
172
|
+
def keys_to_compare
|
173
|
+
[:fq]
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
class KeywordsMatcher < BaseMatcher
|
178
|
+
def keys_to_compare
|
179
|
+
[:q, :qf]
|
180
|
+
end
|
181
|
+
|
182
|
+
def wildcard_matcher_for_keys
|
183
|
+
{:q => /./, :qf => /./}
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
class BoostMatcher < BaseMatcher
|
188
|
+
def keys_to_compare
|
189
|
+
[:qf, :bq, :bf]
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
class FacetMatcher < BaseMatcher
|
194
|
+
def keys_to_compare
|
195
|
+
comparison_params.keys.select {|key| /facet/ =~ key.to_s}
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
class SortMatcher < BaseMatcher
|
200
|
+
def keys_to_compare
|
201
|
+
[:sort]
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
class PaginationMatcher < BaseMatcher
|
206
|
+
def keys_to_compare
|
207
|
+
[:rows, :start]
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
class BeASearchFor < BaseMatcher
|
212
|
+
def initialize(expected_class)
|
213
|
+
@expected_class = expected_class
|
214
|
+
end
|
215
|
+
|
216
|
+
def matches?(actual)
|
217
|
+
@actual = actual
|
218
|
+
search_types.include?(@expected_class)
|
219
|
+
end
|
220
|
+
|
221
|
+
def failure_message_for_should
|
222
|
+
"expected search class: #{search_types.join(' and ')} to match expected class: #{@expected_class}"
|
223
|
+
end
|
224
|
+
|
225
|
+
def failure_message_for_should_not
|
226
|
+
"expected search class: #{search_types.join(' and ')} NOT to match expected class: #{@expected_class}"
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
def be_a_search_for(expected_class)
|
231
|
+
BeASearchFor.new(expected_class)
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
def any_param
|
236
|
+
"ANY_PARAM"
|
237
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
module SunspotMatchers
|
2
|
+
class SunspotSearchSpy < Sunspot::Search::StandardSearch
|
3
|
+
def execute
|
4
|
+
self
|
5
|
+
end
|
6
|
+
def solr_response
|
7
|
+
{}
|
8
|
+
end
|
9
|
+
def facet_response
|
10
|
+
{'facet_queries' => {}}
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class SunspotSessionSpy < Sunspot::Session
|
15
|
+
attr_reader :original_session
|
16
|
+
attr_reader :current_search_class
|
17
|
+
|
18
|
+
attr_accessor :searches
|
19
|
+
|
20
|
+
def initialize(original_session)
|
21
|
+
@searches = []
|
22
|
+
@original_session = original_session
|
23
|
+
@config = Sunspot::Configuration.build
|
24
|
+
end
|
25
|
+
|
26
|
+
def inspect
|
27
|
+
'Solr Search'
|
28
|
+
end
|
29
|
+
|
30
|
+
def index(*objects)
|
31
|
+
end
|
32
|
+
|
33
|
+
def index!(*objects)
|
34
|
+
end
|
35
|
+
|
36
|
+
def remove(*objects)
|
37
|
+
end
|
38
|
+
|
39
|
+
def remove!(*objects)
|
40
|
+
end
|
41
|
+
|
42
|
+
def remove_by_id(clazz, id)
|
43
|
+
end
|
44
|
+
|
45
|
+
def remove_by_id!(clazz, id)
|
46
|
+
end
|
47
|
+
|
48
|
+
def remove_all(clazz = nil)
|
49
|
+
end
|
50
|
+
|
51
|
+
def remove_all!(clazz = nil)
|
52
|
+
end
|
53
|
+
|
54
|
+
def dirty?
|
55
|
+
false
|
56
|
+
end
|
57
|
+
|
58
|
+
def delete_dirty?
|
59
|
+
false
|
60
|
+
end
|
61
|
+
|
62
|
+
def commit_if_dirty
|
63
|
+
end
|
64
|
+
|
65
|
+
def commit_if_delete_dirty
|
66
|
+
end
|
67
|
+
|
68
|
+
def commit
|
69
|
+
end
|
70
|
+
|
71
|
+
def search(*types, &block)
|
72
|
+
new_search(*types, &block)
|
73
|
+
end
|
74
|
+
|
75
|
+
def new_search(*types, &block)
|
76
|
+
search = build_search(*types, &block)
|
77
|
+
@searches << [types, search]
|
78
|
+
search
|
79
|
+
end
|
80
|
+
|
81
|
+
def build_search(*types, &block)
|
82
|
+
types.flatten!
|
83
|
+
search = SunspotSearchSpy.new(
|
84
|
+
nil,
|
85
|
+
setup_for_types(types),
|
86
|
+
Sunspot::Query::StandardQuery.new(types),
|
87
|
+
@config
|
88
|
+
)
|
89
|
+
search.build(&block) if block
|
90
|
+
search
|
91
|
+
end
|
92
|
+
|
93
|
+
def setup_for_types(types)
|
94
|
+
if types.empty?
|
95
|
+
raise(ArgumentError, "You must specify at least one type to search")
|
96
|
+
end
|
97
|
+
if types.length == 1
|
98
|
+
Sunspot::Setup.for(types.first)
|
99
|
+
else
|
100
|
+
CompositeSetup.for(types)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Support Sunspot random field in test -- Sunspot originally generate a random number for the field
|
107
|
+
class Sunspot::Query::Sort::RandomSort < Sunspot::Query::Sort::Abstract
|
108
|
+
def to_param
|
109
|
+
"random #{direction_for_solr}"
|
110
|
+
end
|
111
|
+
end
|