sunspot 0.9.7

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.
Files changed (101) hide show
  1. data/History.txt +83 -0
  2. data/LICENSE +18 -0
  3. data/README.rdoc +154 -0
  4. data/Rakefile +9 -0
  5. data/TODO +9 -0
  6. data/VERSION.yml +4 -0
  7. data/bin/sunspot-configure-solr +46 -0
  8. data/bin/sunspot-solr +62 -0
  9. data/lib/light_config.rb +40 -0
  10. data/lib/sunspot.rb +469 -0
  11. data/lib/sunspot/adapters.rb +265 -0
  12. data/lib/sunspot/composite_setup.rb +186 -0
  13. data/lib/sunspot/configuration.rb +38 -0
  14. data/lib/sunspot/data_extractor.rb +47 -0
  15. data/lib/sunspot/dsl.rb +3 -0
  16. data/lib/sunspot/dsl/field_query.rb +72 -0
  17. data/lib/sunspot/dsl/fields.rb +86 -0
  18. data/lib/sunspot/dsl/query.rb +59 -0
  19. data/lib/sunspot/dsl/query_facet.rb +31 -0
  20. data/lib/sunspot/dsl/restriction.rb +25 -0
  21. data/lib/sunspot/dsl/scope.rb +193 -0
  22. data/lib/sunspot/dsl/search.rb +30 -0
  23. data/lib/sunspot/facet.rb +16 -0
  24. data/lib/sunspot/facet_data.rb +120 -0
  25. data/lib/sunspot/facet_row.rb +10 -0
  26. data/lib/sunspot/field.rb +157 -0
  27. data/lib/sunspot/field_factory.rb +126 -0
  28. data/lib/sunspot/indexer.rb +123 -0
  29. data/lib/sunspot/instantiated_facet.rb +42 -0
  30. data/lib/sunspot/instantiated_facet_row.rb +22 -0
  31. data/lib/sunspot/query.rb +191 -0
  32. data/lib/sunspot/query/base_query.rb +90 -0
  33. data/lib/sunspot/query/connective.rb +126 -0
  34. data/lib/sunspot/query/dynamic_query.rb +69 -0
  35. data/lib/sunspot/query/field_facet.rb +151 -0
  36. data/lib/sunspot/query/field_query.rb +63 -0
  37. data/lib/sunspot/query/pagination.rb +39 -0
  38. data/lib/sunspot/query/query_facet.rb +73 -0
  39. data/lib/sunspot/query/query_facet_row.rb +19 -0
  40. data/lib/sunspot/query/query_field_facet.rb +13 -0
  41. data/lib/sunspot/query/restriction.rb +233 -0
  42. data/lib/sunspot/query/scope.rb +165 -0
  43. data/lib/sunspot/query/sort.rb +36 -0
  44. data/lib/sunspot/query/sort_composite.rb +33 -0
  45. data/lib/sunspot/schema.rb +165 -0
  46. data/lib/sunspot/search.rb +219 -0
  47. data/lib/sunspot/search/hit.rb +66 -0
  48. data/lib/sunspot/session.rb +201 -0
  49. data/lib/sunspot/setup.rb +271 -0
  50. data/lib/sunspot/type.rb +200 -0
  51. data/lib/sunspot/util.rb +164 -0
  52. data/solr/etc/jetty.xml +212 -0
  53. data/solr/etc/webdefault.xml +379 -0
  54. data/solr/lib/jetty-6.1.3.jar +0 -0
  55. data/solr/lib/jetty-util-6.1.3.jar +0 -0
  56. data/solr/lib/jsp-2.1/ant-1.6.5.jar +0 -0
  57. data/solr/lib/jsp-2.1/core-3.1.1.jar +0 -0
  58. data/solr/lib/jsp-2.1/jsp-2.1.jar +0 -0
  59. data/solr/lib/jsp-2.1/jsp-api-2.1.jar +0 -0
  60. data/solr/lib/servlet-api-2.5-6.1.3.jar +0 -0
  61. data/solr/solr/conf/elevate.xml +36 -0
  62. data/solr/solr/conf/protwords.txt +21 -0
  63. data/solr/solr/conf/schema.xml +50 -0
  64. data/solr/solr/conf/solrconfig.xml +696 -0
  65. data/solr/solr/conf/stopwords.txt +57 -0
  66. data/solr/solr/conf/synonyms.txt +31 -0
  67. data/solr/start.jar +0 -0
  68. data/solr/webapps/solr.war +0 -0
  69. data/spec/api/adapters_spec.rb +33 -0
  70. data/spec/api/build_search_spec.rb +1039 -0
  71. data/spec/api/indexer_spec.rb +311 -0
  72. data/spec/api/query_spec.rb +153 -0
  73. data/spec/api/search_retrieval_spec.rb +362 -0
  74. data/spec/api/session_spec.rb +157 -0
  75. data/spec/api/spec_helper.rb +1 -0
  76. data/spec/api/sunspot_spec.rb +18 -0
  77. data/spec/integration/dynamic_fields_spec.rb +55 -0
  78. data/spec/integration/faceting_spec.rb +169 -0
  79. data/spec/integration/keyword_search_spec.rb +83 -0
  80. data/spec/integration/scoped_search_spec.rb +289 -0
  81. data/spec/integration/spec_helper.rb +1 -0
  82. data/spec/integration/stored_fields_spec.rb +10 -0
  83. data/spec/integration/test_pagination.rb +32 -0
  84. data/spec/mocks/adapters.rb +32 -0
  85. data/spec/mocks/blog.rb +3 -0
  86. data/spec/mocks/comment.rb +19 -0
  87. data/spec/mocks/connection.rb +84 -0
  88. data/spec/mocks/mock_adapter.rb +30 -0
  89. data/spec/mocks/mock_record.rb +48 -0
  90. data/spec/mocks/photo.rb +8 -0
  91. data/spec/mocks/post.rb +73 -0
  92. data/spec/mocks/user.rb +8 -0
  93. data/spec/spec_helper.rb +47 -0
  94. data/tasks/gemspec.rake +25 -0
  95. data/tasks/rcov.rake +28 -0
  96. data/tasks/rdoc.rake +22 -0
  97. data/tasks/schema.rake +19 -0
  98. data/tasks/spec.rake +24 -0
  99. data/tasks/todo.rake +4 -0
  100. data/templates/schema.xml.haml +24 -0
  101. metadata +246 -0
@@ -0,0 +1,311 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe 'indexer' do
4
+ describe 'when indexing an object' do
5
+ it 'should index id' do
6
+ session.index post
7
+ connection.should have_add_with(:id => "Post #{post.id}")
8
+ end
9
+
10
+ it 'should index type' do
11
+ session.index post
12
+ connection.should have_add_with(:type => ['Post', 'MockRecord'])
13
+ end
14
+
15
+ it 'should index class name' do
16
+ session.index post
17
+ connection.should have_add_with(:class_name => 'Post')
18
+ end
19
+
20
+ it 'should index the array of objects supplied' do
21
+ posts = Array.new(2) { Post.new }
22
+ session.index posts
23
+ connection.should have_add_with(
24
+ { :id => "Post #{posts.first.id}" },
25
+ { :id => "Post #{posts.last.id}" }
26
+ )
27
+ end
28
+
29
+ it 'should index an array containing more than one type of object' do
30
+ post1, comment, post2 = objects = [Post.new, Namespaced::Comment.new, Post.new]
31
+ session.index objects
32
+ connection.should have_add_with(
33
+ { :id => "Post #{post1.id}", :type => ['Post', 'MockRecord'] },
34
+ { :id => "Post #{post2.id}", :type => ['Post', 'MockRecord'] }
35
+ )
36
+ connection.should have_add_with(:id => "Namespaced::Comment #{comment.id}", :type => ['Namespaced::Comment', 'MockRecord'])
37
+ end
38
+
39
+ it 'should index text' do
40
+ session.index(post(:title => 'A Title', :body => 'A Post'))
41
+ connection.should have_add_with(:title_text => 'A Title', :body_text => 'A Post')
42
+ end
43
+
44
+ it 'should index text with boost' do
45
+ session.index(post(:title => 'A Title'))
46
+ connection.adds.last.first.field_by_name(:title_text).attrs[:boost].should == 2
47
+ end
48
+
49
+ it 'should index multiple values for a text field' do
50
+ session.index(post(:body => %w(some title)))
51
+ connection.should have_add_with(:body_text => %w(some title))
52
+ end
53
+
54
+ it 'should index text via a virtual field' do
55
+ session.index(post(:title => 'backwards'))
56
+ connection.should have_add_with(:backwards_title_text => 'backwards'.reverse)
57
+ end
58
+
59
+ it 'should correctly index a stored string attribute field' do
60
+ session.index(post(:title => 'A Title'))
61
+ connection.should have_add_with(:title_ss => 'A Title')
62
+ end
63
+
64
+ it 'should correctly index an integer attribute field' do
65
+ session.index(post(:blog_id => 4))
66
+ connection.should have_add_with(:blog_id_i => '4')
67
+ end
68
+
69
+ it 'should correctly index a float attribute field' do
70
+ session.index(post(:ratings_average => 2.23))
71
+ connection.should have_add_with(:average_rating_f => '2.23')
72
+ end
73
+
74
+ it 'should allow indexing by a multiple-value field' do
75
+ session.index(post(:category_ids => [3, 14]))
76
+ connection.should have_add_with(:category_ids_im => ['3', '14'])
77
+ end
78
+
79
+ it 'should correctly index a time field' do
80
+ session.index(
81
+ post(:published_at => Time.parse('1983-07-08 05:00:00 -0400'))
82
+ )
83
+ connection.should have_add_with(:published_at_d => '1983-07-08T09:00:00Z')
84
+ end
85
+
86
+ it 'should correctly index a date field' do
87
+ session.index(post(:expire_date => Date.new(2009, 07, 13)))
88
+ connection.should have_add_with(:expire_date_d => '2009-07-13T00:00:00Z')
89
+ end
90
+
91
+ it 'should correctly index a boolean field' do
92
+ session.index(post(:featured => true))
93
+ connection.should have_add_with(:featured_b => 'true')
94
+ end
95
+
96
+ it 'should correctly index a false boolean field' do
97
+ session.index(post(:featured => false))
98
+ connection.should have_add_with(:featured_b => 'false')
99
+ end
100
+
101
+ it 'should not index a nil boolean field' do
102
+ session.index(post)
103
+ connection.should_not have_add_with(:featured_b)
104
+ end
105
+
106
+ it 'should correctly index a virtual field' do
107
+ session.index(post(:title => 'The Blog Post'))
108
+ connection.should have_add_with(:sort_title_s => 'blog post')
109
+ end
110
+
111
+ it 'should correctly index an external virtual field' do
112
+ session.index(post(:category_ids => [1, 2, 3]))
113
+ connection.should have_add_with(:primary_category_id_i => '1')
114
+ end
115
+
116
+ it 'should correctly index a field that is defined on a superclass' do
117
+ Sunspot.setup(MockRecord) { string :author_name }
118
+ session.index(post(:author_name => 'Mat Brown'))
119
+ connection.should have_add_with(:author_name_s => 'Mat Brown')
120
+ end
121
+
122
+ it 'should commit immediately after index! called' do
123
+ connection.should_receive(:add).ordered
124
+ connection.should_receive(:commit).ordered
125
+ session.index!(post)
126
+ end
127
+
128
+ it 'should remove an object from the index' do
129
+ session.remove(post)
130
+ connection.should have_delete("Post #{post.id}")
131
+ end
132
+
133
+ it 'should remove an object by type and id' do
134
+ session.remove_by_id(Post, 1)
135
+ connection.should have_delete('Post 1')
136
+ end
137
+
138
+ it 'should remove an object by type and id and immediately commit' do
139
+ connection.should_receive(:delete_by_id).with('Post 1').ordered
140
+ connection.should_receive(:commit).ordered
141
+ session.remove_by_id!(Post, 1)
142
+ end
143
+
144
+ it 'should remove an object from the index and immediately commit' do
145
+ connection.should_receive(:delete_by_id).ordered
146
+ connection.should_receive(:commit).ordered
147
+ session.remove!(post)
148
+ end
149
+
150
+ it 'should remove everything from the index' do
151
+ session.remove_all
152
+ connection.should have_delete_by_query("type:[* TO *]")
153
+ end
154
+
155
+ it 'should remove everything from the index and immediately commit' do
156
+ connection.should_receive(:delete_by_query).ordered
157
+ connection.should_receive(:commit).ordered
158
+ session.remove_all!
159
+ end
160
+
161
+ it 'should be able to remove everything of a given class from the index' do
162
+ session.remove_all(Post)
163
+ connection.should have_delete_by_query("type:Post")
164
+ end
165
+
166
+ it 'should correctly escape namespaced classes when removing everything from the index' do
167
+ connection.should_receive(:delete_by_query).with('type:Namespaced\:\:Comment')
168
+ session.remove_all(Namespaced::Comment)
169
+ end
170
+ end
171
+
172
+ describe 'batches' do
173
+ it 'should send all batched adds in a single request' do
174
+ posts = Array.new(2) { Post.new }
175
+ session.batch do
176
+ for post in posts
177
+ session.index(post)
178
+ end
179
+ end
180
+ connection.adds.length.should == 1
181
+ end
182
+
183
+ it 'should add all batched adds' do
184
+ posts = Array.new(2) { Post.new }
185
+ session.batch do
186
+ for post in posts
187
+ session.index(post)
188
+ end
189
+ end
190
+ add = connection.adds.last
191
+ connection.adds.first.map { |add| add.field_by_name(:id).value }.should ==
192
+ posts.map { |post| "Post #{post.id}" }
193
+ end
194
+
195
+ it 'should not index changes to models that happen after index call' do
196
+ post = Post.new
197
+ session.batch do
198
+ session.index(post)
199
+ post.title = 'Title'
200
+ end
201
+ connection.adds.first.first.field_by_name(:title_ss).should be_nil
202
+ end
203
+ end
204
+
205
+ describe 'dynamic fields' do
206
+ it 'should index string data' do
207
+ session.index(post(:custom_string => { :test => 'string' }))
208
+ connection.should have_add_with(:"custom_string:test_s" => 'string')
209
+ end
210
+
211
+ it 'should index integer data with virtual accessor' do
212
+ session.index(post(:category_ids => [1, 2]))
213
+ connection.should have_add_with(:"custom_integer:1_i" => '1', :"custom_integer:2_i" => '1')
214
+ end
215
+
216
+ it 'should index float data' do
217
+ session.index(post(:custom_fl => { :test => 1.5 }))
218
+ connection.should have_add_with(:"custom_float:test_fm" => '1.5')
219
+ end
220
+
221
+ it 'should index time data' do
222
+ session.index(post(:custom_time => { :test => Time.parse('2009-05-18 18:05:00 -0400') }))
223
+ connection.should have_add_with(:"custom_time:test_d" => '2009-05-18T22:05:00Z')
224
+ end
225
+
226
+ it 'should index boolean data' do
227
+ session.index(post(:custom_boolean => { :test => false }))
228
+ connection.should have_add_with(:"custom_boolean:test_b" => 'false')
229
+ end
230
+
231
+ it 'should index multiple values for a field' do
232
+ session.index(post(:custom_fl => { :test => [1.0, 2.1, 3.2] }))
233
+ connection.should have_add_with(:"custom_float:test_fm" => %w(1.0 2.1 3.2))
234
+ end
235
+ end
236
+
237
+ it 'should index document level boost using block' do
238
+ session.index(post(:ratings_average => 4.0))
239
+ connection.adds.last.first.attrs[:boost].should == 1.25
240
+ end
241
+
242
+ it 'should index document level boost using attribute' do
243
+ session.index(Namespaced::Comment.new(:boost => 1.5))
244
+ connection.adds.last.first.attrs[:boost].should == 1.5
245
+ end
246
+
247
+ it 'should index document level boost defined statically' do
248
+ session.index(Photo.new)
249
+ connection.adds.last.first.attrs[:boost].should == 0.75
250
+ end
251
+
252
+ it 'should throw a NoMethodError only if a nonexistent type is defined' do
253
+ lambda { Sunspot.setup(Post) { string :author_name }}.should_not raise_error
254
+ lambda { Sunspot.setup(Post) { bogus :journey }}.should raise_error(NoMethodError)
255
+ end
256
+
257
+ it 'should throw a NoMethodError if a nonexistent field argument is passed' do
258
+ lambda { Sunspot.setup(Post) { string :author_name, :bogus => :argument }}.should raise_error(ArgumentError)
259
+ end
260
+
261
+ it 'should throw an ArgumentError if an attempt is made to index an object that has no configuration' do
262
+ lambda { session.index(Blog.new) }.should raise_error(Sunspot::NoSetupError)
263
+ end
264
+
265
+ it 'should throw an ArgumentError if single-value field tries to index multiple values' do
266
+ lambda do
267
+ Sunspot.setup(Post) { string :author_name }
268
+ session.index(post(:author_name => ['Mat Brown', 'Matthew Brown']))
269
+ end.should raise_error(ArgumentError)
270
+ end
271
+
272
+ it 'should throw a NoAdapterError if class without adapter is indexed' do
273
+ lambda { session.index(User.new) }.should raise_error(Sunspot::NoAdapterError)
274
+ end
275
+
276
+ it 'should throw an ArgumentError if a non-word character is included in the field name' do
277
+ lambda do
278
+ Sunspot.setup(Post) { string :"bad name" }
279
+ end.should raise_error(ArgumentError)
280
+ end
281
+
282
+ private
283
+
284
+ def config
285
+ Sunspot::Configuration.build
286
+ end
287
+
288
+ def connection
289
+ @connection ||= Mock::Connection.new
290
+ end
291
+
292
+ def session
293
+ @session ||= Sunspot::Session.new(config, connection)
294
+ end
295
+
296
+ def post(attrs = {})
297
+ @post ||= Post.new(attrs)
298
+ end
299
+
300
+ def last_add
301
+ @connection.adds.last
302
+ end
303
+
304
+ def value_in_last_document_for(field_name)
305
+ @connection.adds.last.last.field_by_name(field_name).value
306
+ end
307
+
308
+ def values_in_last_document_for(field_name)
309
+ @connection.adds.last.last.fields_by_name(field_name).map { |field| field.value }
310
+ end
311
+ end
@@ -0,0 +1,153 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe Sunspot::Query do
4
+ before :each do
5
+ @config ||= Sunspot::Configuration.build
6
+ @connection ||= Mock::Connection.new
7
+ @session ||= Sunspot::Session.new(@config, @connection)
8
+ @search = @session.new_search(Post)
9
+ end
10
+
11
+ it 'should perform keyword search' do
12
+ @search.query.keywords = 'keyword search'
13
+ @search.execute!
14
+ @connection.should have_last_search_with(:q => 'keyword search')
15
+ end
16
+
17
+ it 'should add equality restriction' do
18
+ @search.query.add_restriction(:title, :equal_to, 'My Pet Post')
19
+ @search.execute!
20
+ @connection.should have_last_search_with(:fq => ['title_ss:My\ Pet\ Post'])
21
+ end
22
+
23
+ it 'should add less than restriction' do
24
+ @search.query.add_restriction(:average_rating, :less_than, 3.0)
25
+ @search.execute!
26
+ @connection.should have_last_search_with(:fq => ['average_rating_f:[* TO 3\.0]'])
27
+ end
28
+
29
+ it 'should add greater than restriction' do
30
+ @search.query.add_restriction(:average_rating, :greater_than, 3.0)
31
+ @search.execute!
32
+ @connection.should have_last_search_with(:fq => ['average_rating_f:[3\.0 TO *]'])
33
+ end
34
+
35
+ it 'should add between restriction' do
36
+ @search.query.add_restriction(:average_rating, :between, 2.0..4.0)
37
+ @search.execute!
38
+ @connection.should have_last_search_with(:fq => ['average_rating_f:[2\.0 TO 4\.0]'])
39
+ end
40
+
41
+ it 'should add any restriction' do
42
+ @search.query.add_restriction(:category_ids, :any_of, [2, 7, 12])
43
+ @search.execute!
44
+ @connection.should have_last_search_with(:fq => ['category_ids_im:(2 OR 7 OR 12)'])
45
+ end
46
+
47
+ it 'should add all restriction' do
48
+ @search.query.add_restriction(:category_ids, :all_of, [2, 7, 12])
49
+ @search.execute!
50
+ @connection.should have_last_search_with(:fq => ['category_ids_im:(2 AND 7 AND 12)'])
51
+ end
52
+
53
+ it 'should negate restriction' do
54
+ @search.query.add_negated_restriction(:title, :equal_to, 'Bad Post')
55
+ @search.execute!
56
+ @connection.should have_last_search_with(:fq => ['-title_ss:Bad\ Post'])
57
+ end
58
+
59
+ it 'should exclude instance' do
60
+ post = Post.new
61
+ @search.query.exclude_instance(post)
62
+ @search.execute!
63
+ @connection.should have_last_search_with(:fq => ["-id:Post\\ #{post.id}"])
64
+ end
65
+
66
+ it 'should paginate using default per-page' do
67
+ @search.query.paginate(2)
68
+ @search.execute!
69
+ @connection.should have_last_search_with(:rows => 30)
70
+ end
71
+
72
+ it 'should paginate using provided per-page' do
73
+ @search.query.paginate(4, 15)
74
+ @search.execute!
75
+ @connection.should have_last_search_with(:rows => 15, :start => 45)
76
+ end
77
+
78
+ it 'should order ascending by default' do
79
+ @search.query.order_by(:average_rating)
80
+ @search.execute!
81
+ @connection.should have_last_search_with(:sort => 'average_rating_f asc')
82
+ end
83
+
84
+ it 'should order descending if specified' do
85
+ @search.query.order_by(:average_rating, :desc)
86
+ @search.execute!
87
+ @connection.should have_last_search_with(:sort => 'average_rating_f desc')
88
+ end
89
+
90
+ it 'should request a field facet' do
91
+ @search.query.add_field_facet(:category_ids)
92
+ @search.execute!
93
+ @connection.should have_last_search_with(:"facet.field" => %w(category_ids_im))
94
+ end
95
+
96
+ it 'should restrict by dynamic string field with equality restriction' do
97
+ @search.query.dynamic_query(:custom_string).add_restriction(:test, :equal_to, 'string')
98
+ @search.execute!
99
+ @connection.should have_last_search_with(:fq => ['custom_string\:test_s:string'])
100
+ end
101
+
102
+ it 'should restrict by dynamic integer field with less than restriction' do
103
+ @search.query.dynamic_query(:custom_integer).add_restriction(:test, :less_than, 1)
104
+ @search.execute!
105
+ @connection.should have_last_search_with(:fq => ['custom_integer\:test_i:[* TO 1]'])
106
+ end
107
+
108
+ it 'should restrict by dynamic float field with between restriction' do
109
+ @search.query.dynamic_query(:custom_float).add_restriction(:test, :between, 2.2..3.3)
110
+ @search.execute!
111
+ @connection.should have_last_search_with(:fq => ['custom_float\:test_fm:[2\.2 TO 3\.3]'])
112
+ end
113
+
114
+ it 'should restrict by dynamic time field with any of restriction' do
115
+ @search.query.dynamic_query(:custom_time).add_restriction(
116
+ :test,
117
+ :any_of,
118
+ [Time.parse('2009-02-10 14:00:00 UTC'),
119
+ Time.parse('2009-02-13 18:00:00 UTC')]
120
+ )
121
+ @search.execute!
122
+ @connection.should have_last_search_with(
123
+ :fq => ['custom_time\:test_d:(2009\-02\-10T14\:00\:00Z OR 2009\-02\-13T18\:00\:00Z)']
124
+ )
125
+ end
126
+
127
+ it 'should restrict by dynamic boolean field with equality restriction' do
128
+ @search.query.dynamic_query(:custom_boolean).add_restriction(:test, :equal_to, false)
129
+ @search.execute!
130
+ @connection.should have_last_search_with(:fq => ['custom_boolean\:test_b:false'])
131
+ end
132
+
133
+ it 'should negate a dynamic field restriction' do
134
+ @search.query.dynamic_query(:custom_string).add_negated_restriction(:test, :equal_to, 'foo')
135
+ @search.execute!
136
+ @connection.should have_last_search_with(:fq => ['-custom_string\:test_s:foo'])
137
+ end
138
+
139
+ it 'should order by a dynamic field' do
140
+ @search.query.dynamic_query(:custom_integer).order_by(:test, :desc)
141
+ @search.execute!
142
+ @connection.should have_last_search_with(:sort => 'custom_integer:test_i desc')
143
+ end
144
+
145
+ it 'should order by a dynamic field and static field, with given precedence' do
146
+ @search.query.dynamic_query(:custom_integer).order_by(:test, :desc)
147
+ @search.query.order_by(:sort_title, :asc)
148
+ @search.execute!
149
+ @connection.should have_last_search_with(
150
+ :sort => 'custom_integer:test_i desc, sort_title_s asc'
151
+ )
152
+ end
153
+ end