thinking-sphinx 3.1.0 → 3.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -1
  3. data/.travis.yml +4 -7
  4. data/HISTORY +27 -0
  5. data/README.textile +38 -218
  6. data/gemfiles/rails_3_2.gemfile +2 -3
  7. data/gemfiles/rails_4_0.gemfile +2 -3
  8. data/gemfiles/rails_4_1.gemfile +2 -3
  9. data/lib/thinking_sphinx.rb +1 -0
  10. data/lib/thinking_sphinx/active_record.rb +1 -0
  11. data/lib/thinking_sphinx/active_record/association_proxy.rb +1 -0
  12. data/lib/thinking_sphinx/active_record/association_proxy/attribute_finder.rb +5 -10
  13. data/lib/thinking_sphinx/active_record/association_proxy/attribute_matcher.rb +38 -0
  14. data/lib/thinking_sphinx/active_record/attribute/type.rb +19 -8
  15. data/lib/thinking_sphinx/active_record/base.rb +3 -1
  16. data/lib/thinking_sphinx/active_record/callbacks/delta_callbacks.rb +1 -1
  17. data/lib/thinking_sphinx/active_record/callbacks/update_callbacks.rb +4 -1
  18. data/lib/thinking_sphinx/active_record/index.rb +4 -4
  19. data/lib/thinking_sphinx/active_record/property_query.rb +57 -27
  20. data/lib/thinking_sphinx/active_record/simple_many_query.rb +35 -0
  21. data/lib/thinking_sphinx/capistrano/v3.rb +11 -10
  22. data/lib/thinking_sphinx/configuration.rb +23 -6
  23. data/lib/thinking_sphinx/connection.rb +8 -9
  24. data/lib/thinking_sphinx/errors.rb +7 -2
  25. data/lib/thinking_sphinx/facet.rb +2 -2
  26. data/lib/thinking_sphinx/facet_search.rb +4 -2
  27. data/lib/thinking_sphinx/logger.rb +7 -0
  28. data/lib/thinking_sphinx/masks/group_enumerators_mask.rb +4 -4
  29. data/lib/thinking_sphinx/middlewares/inquirer.rb +2 -2
  30. data/lib/thinking_sphinx/middlewares/sphinxql.rb +6 -2
  31. data/lib/thinking_sphinx/middlewares/stale_id_filter.rb +1 -1
  32. data/lib/thinking_sphinx/real_time/callbacks/real_time_callbacks.rb +6 -1
  33. data/lib/thinking_sphinx/real_time/property.rb +2 -1
  34. data/lib/thinking_sphinx/real_time/transcriber.rb +7 -3
  35. data/lib/thinking_sphinx/search.rb +14 -4
  36. data/lib/thinking_sphinx/search/context.rb +0 -6
  37. data/lib/thinking_sphinx/test.rb +11 -2
  38. data/lib/thinking_sphinx/wildcard.rb +7 -1
  39. data/spec/acceptance/association_scoping_spec.rb +55 -15
  40. data/spec/acceptance/geosearching_spec.rb +8 -2
  41. data/spec/acceptance/real_time_updates_spec.rb +9 -0
  42. data/spec/acceptance/specifying_sql_spec.rb +31 -17
  43. data/spec/internal/app/indices/car_index.rb +5 -0
  44. data/spec/internal/app/models/car.rb +5 -0
  45. data/spec/internal/app/models/category.rb +2 -1
  46. data/spec/internal/app/models/manufacturer.rb +3 -0
  47. data/spec/internal/db/schema.rb +9 -0
  48. data/spec/thinking_sphinx/active_record/attribute/type_spec.rb +7 -0
  49. data/spec/thinking_sphinx/active_record/base_spec.rb +17 -0
  50. data/spec/thinking_sphinx/active_record/callbacks/update_callbacks_spec.rb +2 -1
  51. data/spec/thinking_sphinx/configuration_spec.rb +40 -2
  52. data/spec/thinking_sphinx/errors_spec.rb +21 -0
  53. data/spec/thinking_sphinx/facet_search_spec.rb +6 -6
  54. data/spec/thinking_sphinx/middlewares/inquirer_spec.rb +0 -4
  55. data/spec/thinking_sphinx/middlewares/sphinxql_spec.rb +25 -0
  56. data/spec/thinking_sphinx/middlewares/stale_id_filter_spec.rb +2 -2
  57. data/spec/thinking_sphinx/real_time/callbacks/real_time_callbacks_spec.rb +2 -1
  58. data/spec/thinking_sphinx/search_spec.rb +56 -0
  59. data/spec/thinking_sphinx/wildcard_spec.rb +5 -0
  60. data/thinking-sphinx.gemspec +2 -2
  61. metadata +40 -32
  62. data/sketchpad.rb +0 -58
  63. data/spec/internal/log/.gitignore +0 -1
@@ -175,7 +175,7 @@ describe 'separate queries for MVAs' do
175
175
  declaration, query = attribute.split(/;\s+/)
176
176
 
177
177
  declaration.should == 'uint tag_ids from query'
178
- query.should match(/^SELECT .taggings.\..article_id. \* #{count} \+ #{source.offset} AS .id., .taggings.\..tag_id. AS .tag_ids. FROM .taggings.\s?$/)
178
+ query.should match(/^SELECT .taggings.\..article_id. \* #{count} \+ #{source.offset} AS .id., .taggings.\..tag_id. AS .tag_ids. FROM .taggings.\s? WHERE \(.taggings.\..article_id. IS NOT NULL\)$/)
179
179
  end
180
180
 
181
181
  it "generates a SQL query with joins when appropriate for MVAs" do
@@ -191,7 +191,7 @@ describe 'separate queries for MVAs' do
191
191
  declaration, query = attribute.split(/;\s+/)
192
192
 
193
193
  declaration.should == 'uint tag_ids from query'
194
- query.should match(/^SELECT .taggings.\..article_id. \* #{count} \+ #{source.offset} AS .id., .tags.\..id. AS .tag_ids. FROM .taggings. INNER JOIN .tags. ON .tags.\..id. = .taggings.\..tag_id.\s?$/)
194
+ query.should match(/^SELECT .taggings.\..article_id. \* #{count} \+ #{source.offset} AS .id., .tags.\..id. AS .tag_ids. FROM .taggings. INNER JOIN .tags. ON .tags.\..id. = .taggings.\..tag_id. WHERE \(.taggings.\..article_id. IS NOT NULL\)\s?$/)
195
195
  end
196
196
 
197
197
  it "respects has_many :through joins for MVA queries" do
@@ -207,7 +207,7 @@ describe 'separate queries for MVAs' do
207
207
  declaration, query = attribute.split(/;\s+/)
208
208
 
209
209
  declaration.should == 'uint tag_ids from query'
210
- query.should match(/^SELECT .taggings.\..article_id. \* #{count} \+ #{source.offset} AS .id., .tags.\..id. AS .tag_ids. FROM .taggings. INNER JOIN .tags. ON .tags.\..id. = .taggings.\..tag_id.\s?$/)
210
+ query.should match(/^SELECT .taggings.\..article_id. \* #{count} \+ #{source.offset} AS .id., .tags.\..id. AS .tag_ids. FROM .taggings. INNER JOIN .tags. ON .tags.\..id. = .taggings.\..tag_id. WHERE \(.taggings.\..article_id. IS NOT NULL\)\s?$/)
211
211
  end
212
212
 
213
213
  it "can handle multiple joins for MVA queries" do
@@ -225,15 +225,10 @@ describe 'separate queries for MVAs' do
225
225
  declaration, query = attribute.split(/;\s+/)
226
226
 
227
227
  declaration.should == 'uint tag_ids from query'
228
- query.should match(/^SELECT .articles.\..user_id. \* #{count} \+ #{source.offset} AS .id., .tags.\..id. AS .tag_ids. FROM .articles. INNER JOIN .taggings. ON .taggings.\..article_id. = .articles.\..id. INNER JOIN .tags. ON .tags.\..id. = .taggings.\..tag_id.\s?$/)
228
+ query.should match(/^SELECT .articles.\..user_id. \* #{count} \+ #{source.offset} AS .id., .tags.\..id. AS .tag_ids. FROM .articles. INNER JOIN .taggings. ON .taggings.\..article_id. = .articles.\..id. INNER JOIN .tags. ON .tags.\..id. = .taggings.\..tag_id. WHERE \(.articles.\..user_id. IS NOT NULL\)\s?$/)
229
229
  end
230
230
 
231
- it "can handle HABTM joins for MVA queries" do
232
- pending "Efficient HABTM queries are tricky."
233
- # We don't really have any need for other tables, but that doesn't lend
234
- # itself nicely to Thinking Sphinx's DSL, nor ARel SQL generation. This is
235
- # a low priority - manual SQL queries for this situation may work better.
236
-
231
+ it "can handle simple HABTM joins for MVA queries" do
237
232
  index = ThinkingSphinx::ActiveRecord::Index.new(:book)
238
233
  index.definition_block = Proc.new {
239
234
  indexes title
@@ -248,7 +243,7 @@ describe 'separate queries for MVAs' do
248
243
  declaration, query = attribute.split(/;\s+/)
249
244
 
250
245
  declaration.should == 'uint genre_ids from query'
251
- query.should match(/^SELECT .books_genres.\..book_id. \* #{count} \+ #{source.offset} AS .id., .genres.\..id. AS .genre_ids. FROM .books_genres. INNER JOIN .genres. ON .genres.\..id. = .books_genres.\..genre_id.\s?$/)
246
+ query.should match(/^SELECT .books_genres.\..book_id. \* #{count} \+ #{source.offset} AS .id., .books_genres.\..genre_id. AS .genre_ids. FROM .books_genres.\s?$/)
252
247
  end
253
248
 
254
249
  it "generates an appropriate range SQL queries for an MVA" do
@@ -264,7 +259,7 @@ describe 'separate queries for MVAs' do
264
259
  declaration, query, range = attribute.split(/;\s+/)
265
260
 
266
261
  declaration.should == 'uint tag_ids from ranged-query'
267
- query.should match(/^SELECT .taggings.\..article_id. \* #{count} \+ #{source.offset} AS .id., .taggings.\..tag_id. AS .tag_ids. FROM .taggings. \s?WHERE \(.taggings.\..article_id. BETWEEN \$start AND \$end\)$/)
262
+ query.should match(/^SELECT .taggings.\..article_id. \* #{count} \+ #{source.offset} AS .id., .taggings.\..tag_id. AS .tag_ids. FROM .taggings. \s?WHERE \(.taggings.\..article_id. BETWEEN \$start AND \$end\) AND \(.taggings.\..article_id. IS NOT NULL\)$/)
268
263
  range.should match(/^SELECT MIN\(.taggings.\..article_id.\), MAX\(.taggings.\..article_id.\) FROM .taggings.\s?$/)
269
264
  end
270
265
 
@@ -281,10 +276,29 @@ describe 'separate queries for MVAs' do
281
276
  declaration, query, range = attribute.split(/;\s+/)
282
277
 
283
278
  declaration.should == 'uint tag_ids from ranged-query'
284
- query.should match(/^SELECT .taggings.\..article_id. \* #{count} \+ #{source.offset} AS .id., .tags.\..id. AS .tag_ids. FROM .taggings. INNER JOIN .tags. ON .tags.\..id. = .taggings.\..tag_id. \s?WHERE \(.taggings.\..article_id. BETWEEN \$start AND \$end\)$/)
279
+ query.should match(/^SELECT .taggings.\..article_id. \* #{count} \+ #{source.offset} AS .id., .tags.\..id. AS .tag_ids. FROM .taggings. INNER JOIN .tags. ON .tags.\..id. = .taggings.\..tag_id. \s?WHERE \(.taggings.\..article_id. BETWEEN \$start AND \$end\) AND \(.taggings.\..article_id. IS NOT NULL\)$/)
285
280
  range.should match(/^SELECT MIN\(.taggings.\..article_id.\), MAX\(.taggings.\..article_id.\) FROM .taggings.\s?$/)
286
281
  end
287
282
 
283
+ it "can handle ranged queries for simple HABTM joins for MVA queries" do
284
+ index = ThinkingSphinx::ActiveRecord::Index.new(:book)
285
+ index.definition_block = Proc.new {
286
+ indexes title
287
+ has genres.id, :as => :genre_ids, :source => :ranged_query
288
+ }
289
+ index.render
290
+ source = index.sources.first
291
+
292
+ attribute = source.sql_attr_multi.detect { |attribute|
293
+ attribute[/genre_ids/]
294
+ }
295
+ declaration, query, range = attribute.split(/;\s+/)
296
+
297
+ declaration.should == 'uint genre_ids from ranged-query'
298
+ query.should match(/^SELECT .books_genres.\..book_id. \* #{count} \+ #{source.offset} AS .id., .books_genres.\..genre_id. AS .genre_ids. FROM .books_genres. WHERE \(.books_genres.\..book_id. BETWEEN \$start AND \$end\)$/)
299
+ range.should match(/^SELECT MIN\(.books_genres.\..book_id.\), MAX\(.books_genres.\..book_id.\) FROM .books_genres.$/)
300
+ end
301
+
288
302
  it "respects custom SQL snippets as the query value" do
289
303
  index.definition_block = Proc.new {
290
304
  indexes title
@@ -355,7 +369,7 @@ describe 'separate queries for field' do
355
369
  declaration, query = field.split(/;\s+/)
356
370
 
357
371
  declaration.should == 'tags from query'
358
- query.should match(/^SELECT .taggings.\..article_id. \* #{count} \+ #{source.offset} AS .id., .tags.\..name. AS .tags. FROM .taggings. INNER JOIN .tags. ON .tags.\..id. = .taggings.\..tag_id.\s? ORDER BY .taggings.\..article_id. ASC\s?$/)
372
+ query.should match(/^SELECT .taggings.\..article_id. \* #{count} \+ #{source.offset} AS .id., .tags.\..name. AS .tags. FROM .taggings. INNER JOIN .tags. ON .tags.\..id. = .taggings.\..tag_id.\s? WHERE \(.taggings.\..article_id. IS NOT NULL\)\s? ORDER BY .taggings.\..article_id. ASC\s?$/)
359
373
  end
360
374
 
361
375
  it "respects has_many :through joins for MVF queries" do
@@ -368,7 +382,7 @@ describe 'separate queries for field' do
368
382
  declaration, query = field.split(/;\s+/)
369
383
 
370
384
  declaration.should == 'tags from query'
371
- query.should match(/^SELECT .taggings.\..article_id. \* #{count} \+ #{source.offset} AS .id., .tags.\..name. AS .tags. FROM .taggings. INNER JOIN .tags. ON .tags.\..id. = .taggings.\..tag_id.\s? ORDER BY .taggings.\..article_id. ASC\s?$/)
385
+ query.should match(/^SELECT .taggings.\..article_id. \* #{count} \+ #{source.offset} AS .id., .tags.\..name. AS .tags. FROM .taggings. INNER JOIN .tags. ON .tags.\..id. = .taggings.\..tag_id.\s? WHERE \(.taggings.\..article_id. IS NOT NULL\)\s? ORDER BY .taggings.\..article_id. ASC\s?$/)
372
386
  end
373
387
 
374
388
  it "can handle multiple joins for MVF queries" do
@@ -383,7 +397,7 @@ describe 'separate queries for field' do
383
397
  declaration, query = field.split(/;\s+/)
384
398
 
385
399
  declaration.should == 'tags from query'
386
- query.should match(/^SELECT .articles.\..user_id. \* #{count} \+ #{source.offset} AS .id., .tags.\..name. AS .tags. FROM .articles. INNER JOIN .taggings. ON .taggings.\..article_id. = .articles.\..id. INNER JOIN .tags. ON .tags.\..id. = .taggings.\..tag_id.\s? ORDER BY .articles.\..user_id. ASC\s?$/)
400
+ query.should match(/^SELECT .articles.\..user_id. \* #{count} \+ #{source.offset} AS .id., .tags.\..name. AS .tags. FROM .articles. INNER JOIN .taggings. ON .taggings.\..article_id. = .articles.\..id. INNER JOIN .tags. ON .tags.\..id. = .taggings.\..tag_id.\s? WHERE \(.articles.\..user_id. IS NOT NULL\)\s? ORDER BY .articles.\..user_id. ASC\s?$/)
387
401
  end
388
402
 
389
403
  it "generates a SQL query with joins when appropriate for MVFs" do
@@ -396,7 +410,7 @@ describe 'separate queries for field' do
396
410
  declaration, query, range = field.split(/;\s+/)
397
411
 
398
412
  declaration.should == 'tags from ranged-query'
399
- query.should match(/^SELECT .taggings.\..article_id. \* #{count} \+ #{source.offset} AS .id., .tags.\..name. AS .tags. FROM .taggings. INNER JOIN .tags. ON .tags.\..id. = .taggings.\..tag_id. \s?WHERE \(.taggings.\..article_id. BETWEEN \$start AND \$end\)\s? ORDER BY .taggings.\..article_id. ASC$/)
413
+ query.should match(/^SELECT .taggings.\..article_id. \* #{count} \+ #{source.offset} AS .id., .tags.\..name. AS .tags. FROM .taggings. INNER JOIN .tags. ON .tags.\..id. = .taggings.\..tag_id. \s?WHERE \(.taggings.\..article_id. BETWEEN \$start AND \$end\) AND \(.taggings.\..article_id. IS NOT NULL\)\s? ORDER BY .taggings.\..article_id. ASC$/)
400
414
  range.should match(/^SELECT MIN\(.taggings.\..article_id.\), MAX\(.taggings.\..article_id.\) FROM .taggings.\s?$/)
401
415
  end
402
416
 
@@ -0,0 +1,5 @@
1
+ ThinkingSphinx::Index.define :car, :with => :real_time do
2
+ indexes name, :sortable => true
3
+
4
+ has manufacturer_id, :type => :integer
5
+ end
@@ -0,0 +1,5 @@
1
+ class Car < ActiveRecord::Base
2
+ belongs_to :manufacturer
3
+
4
+ after_save ThinkingSphinx::RealTime.callback_for(:car)
5
+ end
@@ -1,3 +1,4 @@
1
1
  class Category < ActiveRecord::Base
2
- #
2
+ has_many :categorisations
3
+ has_many :products, :through => :categorisations
3
4
  end
@@ -0,0 +1,3 @@
1
+ class Manufacturer < ActiveRecord::Base
2
+ has_many :cars
3
+ end
@@ -17,6 +17,15 @@ ActiveRecord::Schema.define do
17
17
  t.timestamps
18
18
  end
19
19
 
20
+ create_table(:manufacturers, :force => true) do |t|
21
+ t.string :name
22
+ end
23
+
24
+ create_table(:cars, :force => true) do |t|
25
+ t.integer :manufacturer_id
26
+ t.string :name
27
+ end
28
+
20
29
  create_table(:books, :force => true) do |t|
21
30
  t.string :title
22
31
  t.string :author
@@ -4,6 +4,7 @@ module ThinkingSphinx
4
4
  end
5
5
  end
6
6
 
7
+ require 'thinking_sphinx/errors'
7
8
  require 'thinking_sphinx/active_record/attribute/type'
8
9
 
9
10
  describe ThinkingSphinx::ActiveRecord::Attribute::Type do
@@ -143,5 +144,11 @@ describe ThinkingSphinx::ActiveRecord::Attribute::Type do
143
144
 
144
145
  type.type.should == :timestamp
145
146
  end
147
+
148
+ it 'raises an error if the database column does not exist' do
149
+ model.columns.clear
150
+
151
+ expect { type.type }.to raise_error(ThinkingSphinx::MissingColumnError)
152
+ end
146
153
  end
147
154
  end
@@ -48,6 +48,12 @@ describe ThinkingSphinx::ActiveRecord::Base do
48
48
  end
49
49
 
50
50
  describe '.search' do
51
+ let(:stack) { double('stack', :call => true) }
52
+
53
+ before :each do
54
+ stub_const 'ThinkingSphinx::Middlewares::DEFAULT', stack
55
+ end
56
+
51
57
  it "returns a new search object" do
52
58
  model.search.should be_a(ThinkingSphinx::Search)
53
59
  end
@@ -60,6 +66,17 @@ describe ThinkingSphinx::ActiveRecord::Base do
60
66
  model.search('pancakes').options[:classes].should == [model]
61
67
  end
62
68
 
69
+ it "passes through options to the search object" do
70
+ model.search('pancakes', populate: true).
71
+ options[:populate].should be_true
72
+ end
73
+
74
+ it "should automatically populate when :populate is set to true" do
75
+ stack.should_receive(:call).and_return(true)
76
+
77
+ model.search('pancakes', populate: true)
78
+ end
79
+
63
80
  it "merges the :classes option with the model" do
64
81
  model.search('pancakes', :classes => [sub_model]).
65
82
  options[:classes].should == [sub_model, model]
@@ -20,7 +20,8 @@ describe ThinkingSphinx::ActiveRecord::Callbacks::UpdateCallbacks do
20
20
  :indices_for_references => [index]) }
21
21
  let(:connection) { double('connection', :execute => '') }
22
22
  let(:index) { double 'index', :name => 'article_core',
23
- :sources => [source], :document_id_for_key => 3, :distributed? => false }
23
+ :sources => [source], :document_id_for_key => 3, :distributed? => false,
24
+ :type => 'plain'}
24
25
  let(:source) { double('source', :attributes => []) }
25
26
 
26
27
  before :each do
@@ -128,6 +128,10 @@ describe ThinkingSphinx::Configuration do
128
128
  end
129
129
 
130
130
  describe '#initialize' do
131
+ before :each do
132
+ FileUtils.rm_rf Rails.root.join('log')
133
+ end
134
+
131
135
  it "sets the daemon pid file within log for the Rails app" do
132
136
  config.searchd.pid_file.
133
137
  should == File.join(Rails.root, 'log', 'test.sphinx.pid')
@@ -154,6 +158,19 @@ describe ThinkingSphinx::Configuration do
154
158
 
155
159
  config.searchd.workers.should == 'none'
156
160
  end
161
+
162
+ it 'adds settings to indexer without common section' do
163
+ write_configuration 'lemmatizer_base' => 'foo'
164
+
165
+ expect(config.indexer.lemmatizer_base).to eq('foo')
166
+ end
167
+
168
+ it 'adds settings to common section if requested' do
169
+ write_configuration 'lemmatizer_base' => 'foo',
170
+ 'common_sphinx_configuration' => true
171
+
172
+ expect(config.common.lemmatizer_base).to eq('foo')
173
+ end
157
174
  end
158
175
 
159
176
  describe '#next_offset' do
@@ -176,6 +193,13 @@ describe ThinkingSphinx::Configuration do
176
193
  end
177
194
 
178
195
  describe '#preload_indices' do
196
+ let(:distributor) { double :reconcile => true }
197
+
198
+ before :each do
199
+ stub_const 'ThinkingSphinx::Configuration::DistributedIndices',
200
+ double(:new => distributor)
201
+ end
202
+
179
203
  it "searches each index path for ruby files" do
180
204
  config.index_paths.replace ['/path/to/indices', '/path/to/other/indices']
181
205
 
@@ -217,6 +241,20 @@ describe ThinkingSphinx::Configuration do
217
241
  config.preload_indices
218
242
  config.preload_indices
219
243
  end
244
+
245
+ it 'adds distributed indices' do
246
+ distributor.should_receive(:reconcile)
247
+
248
+ config.preload_indices
249
+ end
250
+
251
+ it 'does not add distributed indices if disabled' do
252
+ write_configuration('distributed_indices' => false)
253
+
254
+ distributor.should_not_receive(:reconcile)
255
+
256
+ config.preload_indices
257
+ end
220
258
  end
221
259
 
222
260
  describe '#render' do
@@ -373,8 +411,8 @@ describe ThinkingSphinx::Configuration do
373
411
  end
374
412
 
375
413
  describe '#version' do
376
- it "defaults to 2.0.6" do
377
- config.version.should == '2.0.6'
414
+ it "defaults to 2.1.4" do
415
+ config.version.should == '2.1.4'
378
416
  end
379
417
 
380
418
  it "respects supplied YAML versions" do
@@ -33,6 +33,27 @@ describe ThinkingSphinx::SphinxError do
33
33
  should be_a(ThinkingSphinx::ConnectionError)
34
34
  end
35
35
 
36
+ it 'prefixes the connection error message' do
37
+ error.stub :message => "Can't connect to MySQL server on '127.0.0.1' (61)"
38
+
39
+ ThinkingSphinx::SphinxError.new_from_mysql(error).message.
40
+ should == "Error connecting to Sphinx via the MySQL protocol. Can't connect to MySQL server on '127.0.0.1' (61)"
41
+ end
42
+
43
+ it "translates jdbc connection errors" do
44
+ error.stub :message => "Communications link failure"
45
+
46
+ ThinkingSphinx::SphinxError.new_from_mysql(error).
47
+ should be_a(ThinkingSphinx::ConnectionError)
48
+ end
49
+
50
+ it 'prefixes the jdbc connection error message' do
51
+ error.stub :message => "Communications link failure"
52
+
53
+ ThinkingSphinx::SphinxError.new_from_mysql(error).message.
54
+ should == "Error connecting to Sphinx via the MySQL protocol. Communications link failure"
55
+ end
56
+
36
57
  it "defaults to sphinx errors" do
37
58
  error.stub :message => 'index foo: unknown error: something is wrong'
38
59
 
@@ -26,12 +26,12 @@ describe ThinkingSphinx::FacetSearch do
26
26
  DumbSearch = ::Struct.new(:query, :options) do
27
27
  def raw
28
28
  [{
29
- 'sphinx_internal_class' => 'Foo',
30
- 'price_bracket' => 3,
31
- 'tag_ids' => '1,2',
32
- 'category_id' => 11,
33
- ThinkingSphinx::SphinxQL.count => 5,
34
- ThinkingSphinx::SphinxQL.group_by => 2
29
+ 'sphinx_internal_class' => 'Foo',
30
+ 'price_bracket' => 3,
31
+ 'tag_ids' => '1,2',
32
+ 'category_id' => 11,
33
+ 'sphinx_internal_count' => 5,
34
+ 'sphinx_internal_group' => 2
35
35
  }]
36
36
  end
37
37
  end
@@ -15,10 +15,6 @@ describe ThinkingSphinx::Middlewares::Inquirer do
15
15
  :results => [[:raw], [{'Variable_name' => 'meta', 'Value' => 'value'}]]) }
16
16
 
17
17
  before :each do
18
- context.stub(:log) do |notification, message, &block|
19
- block.call unless block.nil?
20
- end
21
-
22
18
  batch_class = double
23
19
  batch_class.stub(:new).and_return(batch_inquirer)
24
20
 
@@ -6,6 +6,9 @@ module ActiveRecord
6
6
  class Base; end
7
7
  end
8
8
 
9
+ class SphinxQLSubclass
10
+ end
11
+
9
12
  require 'active_support/core_ext/module/attribute_accessors'
10
13
  require 'active_support/core_ext/module/delegation'
11
14
  require 'active_support/core_ext/object/blank'
@@ -185,6 +188,28 @@ describe ThinkingSphinx::Middlewares::SphinxQL do
185
188
  middleware.call [context]
186
189
  end
187
190
 
191
+ it "ignores blank subclasses" do
192
+ db_connection = double('db connection', :select_values => [''],
193
+ :schema_cache => double('cache', :table_exists? => false))
194
+ supermodel = Class.new(ActiveRecord::Base) do
195
+ def self.name; 'Cat'; end
196
+ def self.inheritance_column; 'type'; end
197
+ end
198
+ supermodel.stub :connection => db_connection, :column_names => ['type']
199
+ submodel = Class.new(supermodel) do
200
+ def self.name; 'Lion'; end
201
+ def self.inheritance_column; 'type'; end
202
+ def self.table_name; 'cats'; end
203
+ end
204
+ submodel.stub :connection => db_connection, :column_names => ['type'],
205
+ :descendants => []
206
+ index_set.first.stub :reference => :cat
207
+
208
+ search.options[:classes] = [submodel]
209
+
210
+ expect { middleware.call [context] }.to_not raise_error
211
+ end
212
+
188
213
  it "filters out deleted values by default" do
189
214
  sphinx_sql.should_receive(:where).with(:sphinx_deleted => false).
190
215
  and_return(sphinx_sql)
@@ -15,7 +15,7 @@ describe ThinkingSphinx::Middlewares::StaleIdFilter do
15
15
 
16
16
  describe '#call' do
17
17
  before :each do
18
- context.stub :search => search, :log => true
18
+ context.stub :search => search
19
19
  end
20
20
 
21
21
  context 'one stale ids exception' do
@@ -88,4 +88,4 @@ describe ThinkingSphinx::Middlewares::StaleIdFilter do
88
88
  end
89
89
  end
90
90
  end
91
- end
91
+ end
@@ -5,7 +5,8 @@ describe ThinkingSphinx::RealTime::Callbacks::RealTimeCallbacks do
5
5
  ThinkingSphinx::RealTime::Callbacks::RealTimeCallbacks.new :article
6
6
  }
7
7
  let(:instance) { double('instance', :id => 12) }
8
- let(:config) { double('config', :indices_for_references => [index]) }
8
+ let(:config) { double('config', :indices_for_references => [index],
9
+ :settings => {}) }
9
10
  let(:index) { double('index', :name => 'my_index', :is_a? => true,
10
11
  :document_id_for_key => 123, :fields => [], :attributes => [],
11
12
  :conditions => []) }
@@ -5,6 +5,34 @@ describe ThinkingSphinx::Search do
5
5
  let(:context) { {:results => []} }
6
6
  let(:stack) { double('stack', :call => true) }
7
7
 
8
+ let(:pagination_mask_methods) do
9
+ [:first_page?,
10
+ :last_page?,
11
+ :next_page,
12
+ :next_page?,
13
+ :page,
14
+ :per,
15
+ :previous_page,
16
+ :total_entries,
17
+ :total_count,
18
+ :count,
19
+ :total_pages,
20
+ :page_count,
21
+ :num_pages]
22
+ end
23
+
24
+ let(:scopes_mask_methods) do
25
+ [:facets,
26
+ :search,
27
+ :search_for_ids]
28
+ end
29
+
30
+ let(:group_enumerator_mask_methods) do
31
+ [:each_with_count,
32
+ :each_with_group,
33
+ :each_with_group_and_count]
34
+ end
35
+
8
36
  before :each do
9
37
  ThinkingSphinx::Search::Context.stub :new => context
10
38
 
@@ -141,6 +169,24 @@ describe ThinkingSphinx::Search do
141
169
  it "should respond to Search methods" do
142
170
  search.respond_to?(:per_page).should be_true
143
171
  end
172
+
173
+ it "should return true for methods delegated to pagination mask by method_missing" do
174
+ pagination_mask_methods.each do |method|
175
+ expect(search).to respond_to method
176
+ end
177
+ end
178
+
179
+ it "should return true for methods delegated to scopes mask by method_missing" do
180
+ scopes_mask_methods.each do |method|
181
+ expect(search).to respond_to method
182
+ end
183
+ end
184
+
185
+ it "should return true for methods delegated to group enumerators mask by method_missing" do
186
+ group_enumerator_mask_methods.each do |method|
187
+ expect(search).to respond_to method
188
+ end
189
+ end
144
190
  end
145
191
 
146
192
  describe '#to_a' do
@@ -153,4 +199,14 @@ describe ThinkingSphinx::Search do
153
199
  search.to_a.first.__id__.should == unglazed.__id__
154
200
  end
155
201
  end
202
+
203
+ it "correctly handles access to methods delegated to masks through 'method' call" do
204
+ [
205
+ pagination_mask_methods,
206
+ scopes_mask_methods,
207
+ group_enumerator_mask_methods
208
+ ].flatten.each do |method|
209
+ expect { search.method method }.to_not raise_exception
210
+ end
211
+ end
156
212
  end