ebeigarts-thinking-sphinx 1.1.22 → 1.2.10

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 (61) hide show
  1. data/README.textile +14 -0
  2. data/VERSION.yml +4 -0
  3. data/lib/thinking_sphinx.rb +60 -64
  4. data/lib/thinking_sphinx/active_record.rb +35 -7
  5. data/lib/thinking_sphinx/active_record/scopes.rb +39 -0
  6. data/lib/thinking_sphinx/adapters/postgresql_adapter.rb +3 -2
  7. data/lib/thinking_sphinx/attribute.rb +62 -22
  8. data/lib/thinking_sphinx/configuration.rb +21 -1
  9. data/lib/thinking_sphinx/core/array.rb +7 -0
  10. data/lib/thinking_sphinx/deltas/delayed_delta.rb +3 -0
  11. data/lib/thinking_sphinx/deploy/capistrano.rb +26 -8
  12. data/lib/thinking_sphinx/excerpter.rb +22 -0
  13. data/lib/thinking_sphinx/facet.rb +8 -2
  14. data/lib/thinking_sphinx/facet_search.rb +134 -0
  15. data/lib/thinking_sphinx/index.rb +2 -2
  16. data/lib/thinking_sphinx/index/builder.rb +0 -1
  17. data/lib/thinking_sphinx/property.rb +2 -0
  18. data/lib/thinking_sphinx/rails_additions.rb +14 -0
  19. data/lib/thinking_sphinx/search.rb +633 -671
  20. data/lib/thinking_sphinx/search_methods.rb +421 -0
  21. data/lib/thinking_sphinx/source.rb +5 -5
  22. data/lib/thinking_sphinx/source/internal_properties.rb +1 -1
  23. data/lib/thinking_sphinx/source/sql.rb +10 -8
  24. data/lib/thinking_sphinx/tasks.rb +14 -9
  25. data/spec/{unit → lib}/thinking_sphinx/active_record/delta_spec.rb +1 -1
  26. data/spec/{unit → lib}/thinking_sphinx/active_record/has_many_association_spec.rb +0 -0
  27. data/spec/lib/thinking_sphinx/active_record/scopes_spec.rb +96 -0
  28. data/spec/{unit → lib}/thinking_sphinx/active_record_spec.rb +44 -5
  29. data/spec/{unit → lib}/thinking_sphinx/association_spec.rb +0 -0
  30. data/spec/{unit → lib}/thinking_sphinx/attribute_spec.rb +110 -3
  31. data/spec/{unit → lib}/thinking_sphinx/configuration_spec.rb +87 -41
  32. data/spec/lib/thinking_sphinx/core/array_spec.rb +9 -0
  33. data/spec/{unit → lib}/thinking_sphinx/core/string_spec.rb +0 -0
  34. data/spec/lib/thinking_sphinx/excerpter_spec.rb +49 -0
  35. data/spec/lib/thinking_sphinx/facet_search_spec.rb +176 -0
  36. data/spec/{unit → lib}/thinking_sphinx/facet_spec.rb +34 -15
  37. data/spec/{unit → lib}/thinking_sphinx/field_spec.rb +0 -0
  38. data/spec/{unit → lib}/thinking_sphinx/index/builder_spec.rb +100 -0
  39. data/spec/{unit → lib}/thinking_sphinx/index/faux_column_spec.rb +0 -0
  40. data/spec/{unit → lib}/thinking_sphinx/index_spec.rb +0 -0
  41. data/spec/{unit → lib}/thinking_sphinx/rails_additions_spec.rb +12 -0
  42. data/spec/lib/thinking_sphinx/search_methods_spec.rb +152 -0
  43. data/spec/lib/thinking_sphinx/search_spec.rb +1066 -0
  44. data/spec/{unit → lib}/thinking_sphinx/source_spec.rb +10 -0
  45. data/spec/{unit → lib}/thinking_sphinx_spec.rb +10 -0
  46. data/tasks/distribution.rb +20 -38
  47. data/tasks/testing.rb +3 -1
  48. data/vendor/riddle/lib/riddle.rb +1 -1
  49. data/vendor/riddle/lib/riddle/client.rb +3 -0
  50. data/vendor/riddle/lib/riddle/client/message.rb +4 -3
  51. data/vendor/riddle/lib/riddle/configuration/section.rb +1 -1
  52. data/vendor/riddle/lib/riddle/controller.rb +17 -7
  53. metadata +63 -83
  54. data/lib/thinking_sphinx/active_record/search.rb +0 -57
  55. data/lib/thinking_sphinx/collection.rb +0 -148
  56. data/lib/thinking_sphinx/facet_collection.rb +0 -59
  57. data/lib/thinking_sphinx/search/facets.rb +0 -104
  58. data/spec/unit/thinking_sphinx/active_record/search_spec.rb +0 -107
  59. data/spec/unit/thinking_sphinx/collection_spec.rb +0 -15
  60. data/spec/unit/thinking_sphinx/facet_collection_spec.rb +0 -64
  61. data/spec/unit/thinking_sphinx/search_spec.rb +0 -228
@@ -2,7 +2,7 @@ module ThinkingSphinx
2
2
  class Source
3
3
  module InternalProperties
4
4
  def add_internal_attributes_and_facets
5
- add_internal_attribute :sphinx_internal_id, :integer, @model.primary_key.to_sym
5
+ add_internal_attribute :sphinx_internal_id, :integer, @model.primary_key_for_sphinx.to_sym
6
6
  add_internal_attribute :class_crc, :integer, crc_column, true
7
7
  add_internal_attribute :subclass_crcs, :multi, subclasses_to_s
8
8
  add_internal_attribute :sphinx_deleted, :integer, "0"
@@ -33,10 +33,10 @@ GROUP BY #{ sql_group_clause }
33
33
  return nil if @index.options[:disable_range]
34
34
 
35
35
  min_statement = adapter.convert_nulls(
36
- "MIN(#{quote_column(@model.primary_key)})", 1
36
+ "MIN(#{quote_column(@model.primary_key_for_sphinx)})", 1
37
37
  )
38
38
  max_statement = adapter.convert_nulls(
39
- "MAX(#{quote_column(@model.primary_key)})", 1
39
+ "MAX(#{quote_column(@model.primary_key_for_sphinx)})", 1
40
40
  )
41
41
 
42
42
  sql = "SELECT #{min_statement}, #{max_statement} " +
@@ -53,14 +53,14 @@ GROUP BY #{ sql_group_clause }
53
53
  #
54
54
  def to_sql_query_info(offset)
55
55
  "SELECT * FROM #{@model.quoted_table_name} WHERE " +
56
- "#{quote_column(@model.primary_key)} = (($id - #{offset}) / #{ThinkingSphinx.indexed_models.size})"
56
+ "#{quote_column(@model.primary_key_for_sphinx)} = (($id - #{offset}) / #{ThinkingSphinx.indexed_models.size})"
57
57
  end
58
58
 
59
59
  def sql_select_clause(offset)
60
60
  unique_id_expr = ThinkingSphinx.unique_id_expression(offset)
61
61
 
62
62
  (
63
- ["#{@model.quoted_table_name}.#{quote_column(@model.primary_key)} #{unique_id_expr} AS #{quote_column(@model.primary_key)} "] +
63
+ ["#{@model.quoted_table_name}.#{quote_column(@model.primary_key_for_sphinx)} #{unique_id_expr} AS #{quote_column(@model.primary_key_for_sphinx)} "] +
64
64
  @fields.collect { |field| field.to_select_sql } +
65
65
  @attributes.collect { |attribute| attribute.to_select_sql }
66
66
  ).compact.join(", ")
@@ -69,8 +69,8 @@ GROUP BY #{ sql_group_clause }
69
69
  def sql_where_clause(options)
70
70
  logic = []
71
71
  logic += [
72
- "#{@model.quoted_table_name}.#{quote_column(@model.primary_key)} >= $start",
73
- "#{@model.quoted_table_name}.#{quote_column(@model.primary_key)} <= $end"
72
+ "#{@model.quoted_table_name}.#{quote_column(@model.primary_key_for_sphinx)} >= $start",
73
+ "#{@model.quoted_table_name}.#{quote_column(@model.primary_key_for_sphinx)} <= $end"
74
74
  ] unless @index.options[:disable_range]
75
75
 
76
76
  if self.delta? && !@index.delta_object.clause(@model, options[:delta]).blank?
@@ -88,7 +88,7 @@ GROUP BY #{ sql_group_clause }
88
88
  end
89
89
 
90
90
  (
91
- ["#{@model.quoted_table_name}.#{quote_column(@model.primary_key)}"] +
91
+ ["#{@model.quoted_table_name}.#{quote_column(@model.primary_key_for_sphinx)}"] +
92
92
  @fields.collect { |field| field.to_group_sql }.compact +
93
93
  @attributes.collect { |attribute| attribute.to_group_sql }.compact +
94
94
  @groupings + internal_groupings
@@ -112,7 +112,9 @@ GROUP BY #{ sql_group_clause }
112
112
  end
113
113
 
114
114
  def crc_column
115
- if @model.column_names.include?(@model.inheritance_column)
115
+ if @model.table_exists? &&
116
+ @model.column_names.include?(@model.inheritance_column)
117
+
116
118
  adapter.cast_to_unsigned(adapter.convert_nulls(
117
119
  adapter.crc(adapter.quote_with_table(@model.inheritance_column), true),
118
120
  @model.to_crc32
@@ -12,7 +12,7 @@ namespace :thinking_sphinx do
12
12
 
13
13
  desc "Output the current Thinking Sphinx version"
14
14
  task :version => :app_env do
15
- puts "Thinking Sphinx v" + ThinkingSphinx::Version::String
15
+ puts "Thinking Sphinx v" + ThinkingSphinx.version
16
16
  end
17
17
 
18
18
  desc "Stop if running, then start a Sphinx searchd daemon using Thinking Sphinx's settings"
@@ -30,7 +30,7 @@ namespace :thinking_sphinx do
30
30
 
31
31
  Dir["#{config.searchd_file_path}/*.spl"].each { |file| File.delete(file) }
32
32
 
33
- system! "#{config.bin_path}#{config.searchd_binary_name} --pidfile --config #{config.config_file}"
33
+ system! "#{config.bin_path}#{config.searchd_binary_name} --pidfile --config \"#{config.config_file}\""
34
34
 
35
35
  sleep(2)
36
36
 
@@ -43,11 +43,14 @@ namespace :thinking_sphinx do
43
43
 
44
44
  desc "Stop Sphinx using Thinking Sphinx's settings"
45
45
  task :stop => :app_env do
46
- raise RuntimeError, "searchd is not running." unless sphinx_running?
47
- config = ThinkingSphinx::Configuration.instance
48
- pid = sphinx_pid
49
- system! "#{config.bin_path}#{config.searchd_binary_name} --stop --config #{config.config_file}"
50
- puts "Stopped search daemon (pid #{pid})."
46
+ unless sphinx_running?
47
+ puts "searchd is not running"
48
+ else
49
+ config = ThinkingSphinx::Configuration.instance
50
+ pid = sphinx_pid
51
+ system! "#{config.bin_path}#{config.searchd_binary_name} --stop --config \"#{config.config_file}\""
52
+ puts "Stopped search daemon (pid #{pid})."
53
+ end
51
54
  end
52
55
 
53
56
  desc "Restart Sphinx"
@@ -71,7 +74,7 @@ namespace :thinking_sphinx do
71
74
  end
72
75
 
73
76
  FileUtils.mkdir_p config.searchd_file_path
74
- cmd = "#{config.bin_path}#{config.indexer_binary_name} --config #{config.config_file} --all"
77
+ cmd = "#{config.bin_path}#{config.indexer_binary_name} --config \"#{config.config_file}\" --all"
75
78
  cmd << " --rotate" if sphinx_running?
76
79
 
77
80
  system! cmd
@@ -111,6 +114,7 @@ namespace :thinking_sphinx do
111
114
  # http://www.sphinxsearch.com/docs/current.html#xmlpipe2
112
115
  desc "Streams XML data to STDOUT"
113
116
  task :xml => :app_env do
117
+ ThinkingSphinx::Configuration.instance.load_models
114
118
  source_name = ENV["NAME"]
115
119
  # STDERR.puts "Source name: #{source_name}"
116
120
  source_name =~ /^(.+)_(delta|core)_(\d+)$/
@@ -141,7 +145,8 @@ namespace :thinking_sphinx do
141
145
  # STDERR.puts "fetching #{start_id}.. "
142
146
  # STDERR.puts query
143
147
  model_klass.sphinx_database_adapter.select_each(query) do |values|
144
- id = values.delete "id"
148
+ pk_name = model_klass.primary_key_for_sphinx.to_s
149
+ id = values.delete(pk_name)
145
150
  puts %{<sphinx:document id="#{id.to_i}">}
146
151
  values.each do |k, v|
147
152
  if source.sql_attr_bool.include?(k.to_sym)
@@ -18,7 +18,7 @@ describe "ThinkingSphinx::ActiveRecord::Delta" do
18
18
 
19
19
  @beta.should have_received(:toggle_delta)
20
20
  end
21
-
21
+
22
22
  describe "suspended_delta method" do
23
23
  before :each do
24
24
  ThinkingSphinx.stub_method(:deltas_enabled? => true)
@@ -0,0 +1,96 @@
1
+ require 'spec/spec_helper'
2
+
3
+ describe ThinkingSphinx::ActiveRecord::Scopes do
4
+ after :each do
5
+ Alpha.remove_sphinx_scopes
6
+ end
7
+
8
+ it "should be included into models with indexes" do
9
+ Alpha.included_modules.should include(ThinkingSphinx::ActiveRecord::Scopes)
10
+ end
11
+
12
+ it "should not be included into models without indexes" do
13
+ Gamma.included_modules.should_not include(
14
+ ThinkingSphinx::ActiveRecord::Scopes
15
+ )
16
+ end
17
+
18
+ describe '.sphinx_scope' do
19
+ before :each do
20
+ Alpha.sphinx_scope(:by_name) { |name| {:conditions => {:name => name}} }
21
+ end
22
+
23
+ it "should define a method on the model" do
24
+ Alpha.should respond_to(:by_name)
25
+ end
26
+ end
27
+
28
+ describe '.sphinx_scopes' do
29
+ before :each do
30
+ Alpha.sphinx_scope(:by_name) { |name| {:conditions => {:name => name}} }
31
+ end
32
+
33
+ it "should return an array of defined scope names as symbols" do
34
+ Alpha.sphinx_scopes.should == [:by_name]
35
+ end
36
+ end
37
+
38
+ describe '.remove_sphinx_scopes' do
39
+ before :each do
40
+ Alpha.sphinx_scope(:by_name) { |name| {:conditions => {:name => name}} }
41
+ Alpha.remove_sphinx_scopes
42
+ end
43
+
44
+ it "should remove sphinx scope methods" do
45
+ Alpha.should_not respond_to(:by_name)
46
+ end
47
+
48
+ it "should empty the list of sphinx scopes" do
49
+ Alpha.sphinx_scopes.should be_empty
50
+ end
51
+ end
52
+
53
+ describe '.example_scope' do
54
+ before :each do
55
+ Alpha.sphinx_scope(:by_name) { |name| {:conditions => {:name => name}} }
56
+ Alpha.sphinx_scope(:by_foo) { |foo| {:conditions => {:foo => foo}} }
57
+ Alpha.sphinx_scope(:with_betas) { {:classes => [Beta]} }
58
+ end
59
+
60
+ it "should return a ThinkingSphinx::Search object" do
61
+ Alpha.by_name('foo').should be_a(ThinkingSphinx::Search)
62
+ end
63
+
64
+ it "should set the classes option" do
65
+ Alpha.by_name('foo').options[:classes].should == [Alpha]
66
+ end
67
+
68
+ it "should be able to be called on a ThinkingSphinx::Search object" do
69
+ search = ThinkingSphinx::Search.new(:classes => [Alpha])
70
+ lambda {
71
+ search.by_name('foo')
72
+ }.should_not raise_error
73
+ end
74
+
75
+ it "should return the search object it gets called upon" do
76
+ search = ThinkingSphinx::Search.new(:classes => [Alpha])
77
+ search.by_name('foo').should == search
78
+ end
79
+
80
+ it "should apply the scope options to the underlying search object" do
81
+ search = ThinkingSphinx::Search.new(:classes => [Alpha])
82
+ search.by_name('foo').options[:conditions].should == {:name => 'foo'}
83
+ end
84
+
85
+ it "should combine hash option scopes such as :conditions" do
86
+ search = ThinkingSphinx::Search.new(:classes => [Alpha])
87
+ search.by_name('foo').by_foo('bar').options[:conditions].
88
+ should == {:name => 'foo', :foo => 'bar'}
89
+ end
90
+
91
+ it "should combine array option scopes such as :classes" do
92
+ search = ThinkingSphinx::Search.new(:classes => [Alpha])
93
+ search.with_betas.options[:classes].should == [Alpha, Beta]
94
+ end
95
+ end
96
+ end
@@ -1,7 +1,7 @@
1
1
  require 'spec/spec_helper'
2
2
 
3
- describe "ThinkingSphinx::ActiveRecord" do
4
- describe "define_index method" do
3
+ describe ThinkingSphinx::ActiveRecord do
4
+ describe '.define_index' do
5
5
  before :each do
6
6
  module ::TestModule
7
7
  class TestModel < ActiveRecord::Base; end
@@ -154,7 +154,7 @@ describe "ThinkingSphinx::ActiveRecord" do
154
154
  end
155
155
  end
156
156
 
157
- describe "source_of_sphinx_index method" do
157
+ describe '.source_of_sphinx_index' do
158
158
  it "should return self if model defines an index" do
159
159
  Person.source_of_sphinx_index.should == Person
160
160
  end
@@ -164,13 +164,13 @@ describe "ThinkingSphinx::ActiveRecord" do
164
164
  end
165
165
  end
166
166
 
167
- describe "to_crc32 method" do
167
+ describe '.to_crc32' do
168
168
  it "should return an integer" do
169
169
  Person.to_crc32.should be_a_kind_of(Integer)
170
170
  end
171
171
  end
172
172
 
173
- describe "to_crc32s method" do
173
+ describe '.to_crc32s' do
174
174
  it "should return an array" do
175
175
  Person.to_crc32s.should be_a_kind_of(Array)
176
176
  end
@@ -326,4 +326,43 @@ describe "ThinkingSphinx::ActiveRecord" do
326
326
 
327
327
  (beta.id * model_count + offset).should == beta.sphinx_document_id
328
328
  end
329
+
330
+ describe '#primary_key_for_sphinx' do
331
+ before :each do
332
+ @person = Person.find(:first)
333
+ end
334
+
335
+ after :each do
336
+ Person.set_sphinx_primary_key nil
337
+ end
338
+
339
+ it "should return the id by default" do
340
+ @person.primary_key_for_sphinx.should == @person.id
341
+ end
342
+
343
+ it "should use the sphinx primary key to determine the value" do
344
+ Person.set_sphinx_primary_key :first_name
345
+ @person.primary_key_for_sphinx.should == @person.first_name
346
+ end
347
+
348
+ it "should not use accessor methods but the attributes hash" do
349
+ id = @person.id
350
+ @person.stub!(:id => 'unique_hash')
351
+ @person.primary_key_for_sphinx.should == id
352
+ end
353
+ end
354
+
355
+ describe '.sphinx_index_names' do
356
+ it "should return the core index" do
357
+ Alpha.sphinx_index_names.should == ['alpha_core']
358
+ end
359
+
360
+ it "should return the delta index if enabled" do
361
+ Beta.sphinx_index_names.should == ['beta_core', 'beta_delta']
362
+ end
363
+
364
+ it "should return the superclass with an index definition" do
365
+ Parent.sphinx_index_names.should == ['person_core', 'person_delta']
366
+ end
367
+ end
329
368
  end
@@ -4,6 +4,8 @@ describe ThinkingSphinx::Attribute do
4
4
  before :each do
5
5
  @index = ThinkingSphinx::Index.new(Person)
6
6
  @source = ThinkingSphinx::Source.new(@index)
7
+
8
+ @index.delta_object = ThinkingSphinx::Deltas::DefaultDelta.new @index, @index.local_options
7
9
  end
8
10
 
9
11
  describe '#initialize' do
@@ -86,6 +88,19 @@ describe ThinkingSphinx::Attribute do
86
88
  end
87
89
  end
88
90
 
91
+ describe '#to_select_sql' do
92
+ it "should convert a mixture of dates and datetimes to timestamps" do
93
+ attribute = ThinkingSphinx::Attribute.new(@source,
94
+ [ ThinkingSphinx::Index::FauxColumn.new(:created_at),
95
+ ThinkingSphinx::Index::FauxColumn.new(:created_on) ],
96
+ :as => :times
97
+ )
98
+ attribute.model = Friendship
99
+
100
+ attribute.to_select_sql.should == "CONCAT_WS(',', UNIX_TIMESTAMP(`friendships`.`created_at`), UNIX_TIMESTAMP(`friendships`.`created_on`)) AS `times`"
101
+ end
102
+ end
103
+
89
104
  describe "is_many? method" do
90
105
  before :each do
91
106
  @assoc_a = Object.stub_instance(:is_many? => true)
@@ -189,7 +204,7 @@ describe ThinkingSphinx::Attribute do
189
204
  attribute.model = Person
190
205
  attribute.columns.each { |col| attribute.associations[col] = [] }
191
206
 
192
- attribute.send(:all_ints?).should be_true
207
+ attribute.should be_all_ints
193
208
  end
194
209
 
195
210
  it "should return false if only some columns are integers" do
@@ -200,7 +215,7 @@ describe ThinkingSphinx::Attribute do
200
215
  attribute.model = Person
201
216
  attribute.columns.each { |col| attribute.associations[col] = [] }
202
217
 
203
- attribute.send(:all_ints?).should be_false
218
+ attribute.should_not be_all_ints
204
219
  end
205
220
 
206
221
  it "should return false if no columns are integers" do
@@ -211,7 +226,42 @@ describe ThinkingSphinx::Attribute do
211
226
  attribute.model = Person
212
227
  attribute.columns.each { |col| attribute.associations[col] = [] }
213
228
 
214
- attribute.send(:all_ints?).should be_false
229
+ attribute.should_not be_all_ints
230
+ end
231
+ end
232
+
233
+ describe "all_datetimes? method" do
234
+ it "should return true if all columns are datetimes" do
235
+ attribute = ThinkingSphinx::Attribute.new(@source,
236
+ [ ThinkingSphinx::Index::FauxColumn.new(:created_at),
237
+ ThinkingSphinx::Index::FauxColumn.new(:updated_at) ]
238
+ )
239
+ attribute.model = Friendship
240
+ attribute.columns.each { |col| attribute.associations[col] = [] }
241
+
242
+ attribute.should be_all_datetimes
243
+ end
244
+
245
+ it "should return false if only some columns are datetimes" do
246
+ attribute = ThinkingSphinx::Attribute.new(@source,
247
+ [ ThinkingSphinx::Index::FauxColumn.new(:id),
248
+ ThinkingSphinx::Index::FauxColumn.new(:created_at) ]
249
+ )
250
+ attribute.model = Friendship
251
+ attribute.columns.each { |col| attribute.associations[col] = [] }
252
+
253
+ attribute.should_not be_all_datetimes
254
+ end
255
+
256
+ it "should return true if all columns can be " do
257
+ attribute = ThinkingSphinx::Attribute.new(@source,
258
+ [ ThinkingSphinx::Index::FauxColumn.new(:created_at),
259
+ ThinkingSphinx::Index::FauxColumn.new(:created_on) ]
260
+ )
261
+ attribute.model = Friendship
262
+ attribute.columns.each { |col| attribute.associations[col] = [] }
263
+
264
+ attribute.should be_all_datetimes
215
265
  end
216
266
  end
217
267
 
@@ -232,6 +282,23 @@ describe ThinkingSphinx::Attribute do
232
282
  end
233
283
  end
234
284
 
285
+ describe "MVA with source query for a delta source" do
286
+ before :each do
287
+ @attribute = ThinkingSphinx::Attribute.new(@source,
288
+ [ThinkingSphinx::Index::FauxColumn.new(:tags, :id)],
289
+ :as => :tag_ids, :source => :query
290
+ )
291
+ end
292
+
293
+ it "should use a query" do
294
+ @attribute.type_to_config.should == :sql_attr_multi
295
+
296
+ declaration, query = @attribute.config_value(nil, true).split('; ')
297
+ declaration.should == "uint tag_ids from query"
298
+ query.should == "SELECT `tags`.`person_id` #{ThinkingSphinx.unique_id_expression} AS `id`, `tags`.`id` AS `tag_ids` FROM `tags` WHERE `tags`.`person_id` IN (SELECT `id` FROM `people` WHERE `people`.`delta` = 1)"
299
+ end
300
+ end
301
+
235
302
  describe "MVA via a HABTM association with a source query" do
236
303
  before :each do
237
304
  @attribute = ThinkingSphinx::Attribute.new(@source,
@@ -267,6 +334,24 @@ describe ThinkingSphinx::Attribute do
267
334
  end
268
335
  end
269
336
 
337
+ describe "MVA with ranged source query for a delta source" do
338
+ before :each do
339
+ @attribute = ThinkingSphinx::Attribute.new(@source,
340
+ [ThinkingSphinx::Index::FauxColumn.new(:tags, :id)],
341
+ :as => :tag_ids, :source => :ranged_query
342
+ )
343
+ end
344
+
345
+ it "should use a ranged query" do
346
+ @attribute.type_to_config.should == :sql_attr_multi
347
+
348
+ declaration, query, range_query = @attribute.config_value(nil, true).split('; ')
349
+ declaration.should == "uint tag_ids from ranged-query"
350
+ query.should == "SELECT `tags`.`person_id` #{ThinkingSphinx.unique_id_expression} AS `id`, `tags`.`id` AS `tag_ids` FROM `tags` WHERE `tags`.`person_id` >= $start AND `tags`.`person_id` <= $end AND `tags`.`person_id` IN (SELECT `id` FROM `people` WHERE `people`.`delta` = 1)"
351
+ range_query.should == "SELECT MIN(`tags`.`person_id`), MAX(`tags`.`person_id`) FROM `tags`"
352
+ end
353
+ end
354
+
270
355
  describe "MVA via a has-many :through with a ranged source query" do
271
356
  before :each do
272
357
  @attribute = ThinkingSphinx::Attribute.new(@source,
@@ -341,6 +426,28 @@ describe ThinkingSphinx::Attribute do
341
426
  end
342
427
  end
343
428
 
429
+ describe "MVA via two has-many associations with a ranged source query for a delta source" do
430
+ before :each do
431
+ @index = ThinkingSphinx::Index.new(Alpha)
432
+ @source = ThinkingSphinx::Source.new(@index)
433
+ @attribute = ThinkingSphinx::Attribute.new(@source,
434
+ [ThinkingSphinx::Index::FauxColumn.new(:betas, :gammas, :value)],
435
+ :as => :gamma_values, :source => :ranged_query
436
+ )
437
+
438
+ @index.delta_object = ThinkingSphinx::Deltas::DefaultDelta.new @index, @index.local_options
439
+ end
440
+
441
+ it "should use a ranged query" do
442
+ @attribute.type_to_config.should == :sql_attr_multi
443
+
444
+ declaration, query, range_query = @attribute.config_value(nil, true).split('; ')
445
+ declaration.should == "uint gamma_values from ranged-query"
446
+ query.should == "SELECT `betas`.`alpha_id` #{ThinkingSphinx.unique_id_expression} AS `id`, `gammas`.`value` AS `gamma_values` FROM `betas` LEFT OUTER JOIN `gammas` ON gammas.beta_id = betas.id WHERE `betas`.`alpha_id` >= $start AND `betas`.`alpha_id` <= $end AND `betas`.`alpha_id` IN (SELECT `id` FROM `alphas` WHERE `alphas`.`delta` = 1)"
447
+ range_query.should == "SELECT MIN(`betas`.`alpha_id`), MAX(`betas`.`alpha_id`) FROM `betas`"
448
+ end
449
+ end
450
+
344
451
  describe "with custom queries" do
345
452
  before :each do
346
453
  index = CricketTeam.sphinx_indexes.first