thinking-sphinx 1.3.20 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (33) hide show
  1. data/README.textile +3 -0
  2. data/VERSION +1 -1
  3. data/features/excerpts.feature +8 -0
  4. data/features/field_sorting.feature +18 -0
  5. data/features/searching_across_models.feature +1 -1
  6. data/features/searching_by_model.feature +0 -7
  7. data/features/sphinx_scopes.feature +18 -0
  8. data/features/step_definitions/common_steps.rb +4 -0
  9. data/features/step_definitions/search_steps.rb +5 -0
  10. data/features/support/env.rb +3 -5
  11. data/features/thinking_sphinx/db/fixtures/people.rb +1 -1
  12. data/features/thinking_sphinx/models/andrew.rb +17 -0
  13. data/features/thinking_sphinx/models/person.rb +2 -1
  14. data/lib/thinking_sphinx.rb +2 -0
  15. data/lib/thinking_sphinx/active_record.rb +1 -1
  16. data/lib/thinking_sphinx/active_record/scopes.rb +7 -0
  17. data/lib/thinking_sphinx/adapters/abstract_adapter.rb +38 -8
  18. data/lib/thinking_sphinx/adapters/postgresql_adapter.rb +6 -2
  19. data/lib/thinking_sphinx/attribute.rb +5 -0
  20. data/lib/thinking_sphinx/configuration.rb +11 -7
  21. data/lib/thinking_sphinx/context.rb +4 -2
  22. data/lib/thinking_sphinx/property.rb +1 -0
  23. data/lib/thinking_sphinx/search.rb +66 -36
  24. data/lib/thinking_sphinx/tasks.rb +7 -0
  25. data/spec/thinking_sphinx/active_record/has_many_association_spec.rb +1 -0
  26. data/spec/thinking_sphinx/active_record/scopes_spec.rb +2 -3
  27. data/spec/thinking_sphinx/adapters/abstract_adapter_spec.rb +134 -0
  28. data/spec/thinking_sphinx/configuration_spec.rb +7 -0
  29. data/spec/thinking_sphinx/context_spec.rb +3 -2
  30. data/spec/thinking_sphinx/search_spec.rb +67 -25
  31. data/tasks/distribution.rb +0 -14
  32. data/tasks/testing.rb +29 -21
  33. metadata +227 -91
@@ -172,3 +172,6 @@ Since I first released this library, there's been quite a few people who have su
172
172
  * Ben Hutton
173
173
  * Alfonso Jiménez
174
174
  * Szymon Nowak
175
+ * Keith Pitt
176
+ * Lee Capps
177
+ * Sam Goldstein
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.3.20
1
+ 1.4.0
@@ -11,3 +11,11 @@ Feature: Generate excerpts for search results
11
11
  And I am searching on comments
12
12
  And I search for "lorem"
13
13
  Then calling content on the first result excerpts object should return "de un sitio mientras que mira su diseño. El punto de usar <span class="match">Lorem</span> Ipsum es que tiene una distribución"
14
+
15
+ Scenario: Excerpt Options
16
+ Given Sphinx is running
17
+ And I am searching on comments
18
+ And I search for "lorem"
19
+ And I provide excerpt option "before_match" with value "<em>"
20
+ And I provide excerpt option "after_match" with value "</em>"
21
+ Then calling content on the first result excerpts object should return "de un sitio mientras que mira su diseño. El punto de usar <em>Lorem</em> Ipsum es que tiene una distribución"
@@ -0,0 +1,18 @@
1
+ Feature: Field Sorting
2
+ In order to sort by strings
3
+ As a developer
4
+ I want to enable sorting by existing fields
5
+
6
+ Background:
7
+ Given Sphinx is running
8
+ And I am searching on people
9
+
10
+ Scenario: Searching with ordering on a sortable field
11
+ When I order by first_name
12
+ Then I should get 20 results
13
+ And the first_name of each result should indicate order
14
+
15
+ Scenario: Sort on a case insensitive sortable field
16
+ When I order by last_name
17
+ Then the first result's "last_name" should be "abbott"
18
+
@@ -7,7 +7,7 @@ Feature: Searching across multiple model
7
7
  Given Sphinx is running
8
8
  When I search for James
9
9
  And I am retrieving the result count
10
- Then I should get a value of 3
10
+ Then I should get a value of 6
11
11
 
12
12
  Scenario: Confirming existance of a document id in a given index
13
13
  Given Sphinx is running
@@ -97,13 +97,6 @@ Feature: Searching on a single model
97
97
  Then I should get 10 results
98
98
  And the value of each result should indicate order
99
99
 
100
- Scenario: Searching with ordering on a sortable field
101
- Given Sphinx is running
102
- And I am searching on people
103
- And I order by first_name
104
- Then I should get 20 results
105
- And the first_name of each result should indicate order
106
-
107
100
  Scenario: Intepreting Sphinx Internal Identifiers
108
101
  Given Sphinx is running
109
102
  And I am searching on people
@@ -48,3 +48,21 @@ Feature: Sphinx Scopes
48
48
  And I am retrieving the scoped result count for "Byrne"
49
49
  Then I should get a value of 1
50
50
 
51
+ Scenario: Default Scope
52
+ Given Sphinx is running
53
+ And I am searching on andrews
54
+ Then I should get 7 results
55
+
56
+ Scenario: Default Scope and additional query terms
57
+ Given Sphinx is running
58
+ And I am searching on andrews
59
+ When I search for "Byrne"
60
+ Then I should get 1 result
61
+
62
+ Scenario: Explicit scope plus search over a default scope
63
+ Given Sphinx is running
64
+ And I am searching on andrews
65
+ When I use the locked_last_name scope
66
+ And I search for "Cecil"
67
+ Then I should get 1 result
68
+
@@ -155,6 +155,10 @@ Then /^the (\w+) of each result should indicate order$/ do |attribute|
155
155
  end
156
156
  end
157
157
 
158
+ Then /^the first result's "([^"]*)" should be "([^"]*)"$/ do |attribute, value|
159
+ results.first.send(attribute.to_sym).should == value
160
+ end
161
+
158
162
  Then /^I can iterate by result and (\w+)$/ do |attribute|
159
163
  iteration = lambda { |result, attr_value|
160
164
  result.should be_kind_of(@model)
@@ -87,3 +87,8 @@ end
87
87
  Then /^the first result should have a (\w+\s?\w*) of (\d+)$/ do |attribute, value|
88
88
  results.first.sphinx_attributes[attribute.gsub(/\s+/, '_')].should == value.to_i
89
89
  end
90
+
91
+ Given /^I provide excerpt option "([a-z_]*)" with value "([^"]*)"$/ do |k, v|
92
+ @options[:excerpt_options] ||= {}
93
+ @options[:excerpt_options][k.to_sym] = v
94
+ end
@@ -1,10 +1,8 @@
1
1
  require 'rubygems'
2
- require 'cucumber'
3
- require 'spec'
4
2
  require 'fileutils'
5
- require 'ginger'
6
- require 'will_paginate'
7
- require 'active_record'
3
+ require 'bundler'
4
+
5
+ Bundler.require :default, :development
8
6
 
9
7
  $:.unshift File.dirname(__FILE__) + '/../../lib'
10
8
  Dir[File.join(File.dirname(__FILE__), '../../vendor/*/lib')].each do |path|
@@ -18,7 +18,7 @@ Person.create :gender => "male", :first_name => "Peter", :middle_initial => "C",
18
18
  Person.create :gender => "female", :first_name => "Hollie", :middle_initial => "C", :last_name => "Hunter", :street_address => "34 Cornish Street", :city => "Kensington", :state => "VIC", :postcode => "3031", :email => "Hollie.C.Hunter@mailinator.com", :birthday => "1954/2/16 00:00:00"
19
19
  Person.create :gender => "male", :first_name => "Jonathan", :middle_initial => "C", :last_name => "Turner", :street_address => "2 Kopkes Road", :city => "Carngham", :state => "VIC", :postcode => "3351", :email => "Jonathan.C.Turner@trashymail.com", :birthday => "1963/8/26 00:00:00"
20
20
  Person.create :gender => "female", :first_name => "Kate", :middle_initial => "S", :last_name => "Doyle", :street_address => "42 Gregory Way", :city => "Mungalup", :state => "WA", :postcode => "6225", :email => "Kate.S.Doyle@mailinator.com", :birthday => "1974/1/5 00:00:00"
21
- Person.create :gender => "male", :first_name => "Harley", :middle_initial => "M", :last_name => "Abbott", :street_address => "39 Faulkner Street", :city => "Tilbuster", :state => "NSW", :postcode => "2350", :email => "Harley.M.Abbott@trashymail.com", :birthday => "1953/10/4 00:00:00"
21
+ Person.create :gender => "male", :first_name => "Harley", :middle_initial => "M", :last_name => "abbott", :street_address => "39 Faulkner Street", :city => "Tilbuster", :state => "NSW", :postcode => "2350", :email => "Harley.M.Abbott@trashymail.com", :birthday => "1953/10/4 00:00:00"
22
22
  Person.create :gender => "male", :first_name => "Morgan", :middle_initial => "E", :last_name => "Iqbal", :street_address => "64 Carlisle Street", :city => "Dysart", :state => "VIC", :postcode => "3660", :email => "Morgan.E.Iqbal@spambob.com", :birthday => "1954/7/6 00:00:00"
23
23
  Person.create :gender => "female", :first_name => "Phoebe", :middle_initial => "T", :last_name => "Wells", :street_address => "10 Mnimbah Road", :city => "Eccleston", :state => "NSW", :postcode => "2311", :email => "Phoebe.T.Wells@trashymail.com", :birthday => "1949/5/27 00:00:00"
24
24
  Person.create :gender => "male", :first_name => "Finley", :middle_initial => "I", :last_name => "Martin", :street_address => "15 Thomas Lane", :city => "Epping", :state => "VIC", :postcode => "3076", :email => "Finley.I.Martin@dodgit.com", :birthday => "1983/3/12 00:00:00"
@@ -0,0 +1,17 @@
1
+ require "#{File.dirname(__FILE__)}/person"
2
+
3
+ class Andrew < ActiveRecord::Base
4
+ set_table_name 'people'
5
+
6
+ define_index do
7
+ indexes first_name, last_name, street_address
8
+ end
9
+
10
+ sphinx_scope(:locked_first_name) {
11
+ {:conditions => {:first_name => 'Andrew'}}
12
+ }
13
+ sphinx_scope(:locked_last_name) {
14
+ {:conditions => {:last_name => 'Byrne'}}
15
+ }
16
+ default_sphinx_scope :locked_first_name
17
+ end
@@ -1,6 +1,7 @@
1
1
  class Person < ActiveRecord::Base
2
2
  define_index do
3
- indexes first_name, last_name, :sortable => true
3
+ indexes first_name, :sortable => true
4
+ indexes last_name, :sortable => :insensitive
4
5
 
5
6
  has [first_name, middle_initial, last_name], :as => :name_sort
6
7
  has birthday
@@ -37,6 +37,8 @@ Merb::Plugins.add_rakefiles(
37
37
  ) if defined?(Merb)
38
38
 
39
39
  module ThinkingSphinx
40
+ mattr_accessor :database_adapter
41
+
40
42
  # A ConnectionError will get thrown when a connection to Sphinx can't be
41
43
  # made.
42
44
  class ConnectionError < StandardError
@@ -363,7 +363,7 @@ module ThinkingSphinx
363
363
  # @return [Integer] Unique record id for the purposes of Sphinx.
364
364
  #
365
365
  def primary_key_for_sphinx
366
- @primary_key_for_sphinx ||= read_attribute(self.class.primary_key_for_sphinx)
366
+ read_attribute(self.class.primary_key_for_sphinx)
367
367
  end
368
368
 
369
369
  def sphinx_document_id
@@ -53,6 +53,13 @@ module ThinkingSphinx
53
53
 
54
54
  ThinkingSphinx::Search.new(options)
55
55
  end
56
+
57
+ define_method("#{method}_without_default".to_sym) do |*args|
58
+ options = {:classes => classes_option, :ignore_default => true}
59
+ options.merge! block.call(*args)
60
+
61
+ ThinkingSphinx::Search.new(options)
62
+ end
56
63
  end
57
64
  end
58
65
 
@@ -10,24 +10,50 @@ module ThinkingSphinx
10
10
  end
11
11
 
12
12
  def self.detect(model)
13
+ adapter = adapter_for_model model
14
+ case adapter
15
+ when :mysql
16
+ ThinkingSphinx::MysqlAdapter.new model
17
+ when :postgresql
18
+ ThinkingSphinx::PostgreSQLAdapter.new model
19
+ else
20
+ raise "Invalid Database Adapter: Sphinx only supports MySQL and PostgreSQL, not #{adapter}"
21
+ end
22
+ end
23
+
24
+ def self.adapter_for_model(model)
25
+ case ThinkingSphinx.database_adapter
26
+ when String
27
+ ThinkingSphinx.database_adapter.to_sym
28
+ when NilClass
29
+ standard_adapter_for_model model
30
+ when Proc
31
+ ThinkingSphinx.database_adapter.call model
32
+ else
33
+ ThinkingSphinx.database_adapter
34
+ end
35
+ end
36
+
37
+ def self.standard_adapter_for_model(model)
13
38
  case model.connection.class.name
14
39
  when "ActiveRecord::ConnectionAdapters::MysqlAdapter",
15
40
  "ActiveRecord::ConnectionAdapters::MysqlplusAdapter",
16
41
  "ActiveRecord::ConnectionAdapters::Mysql2Adapter",
17
42
  "ActiveRecord::ConnectionAdapters::NullDBAdapter"
18
- ThinkingSphinx::MysqlAdapter.new model
43
+ :mysql
19
44
  when "ActiveRecord::ConnectionAdapters::PostgreSQLAdapter"
20
- ThinkingSphinx::PostgreSQLAdapter.new model
45
+ :postgresql
21
46
  when "ActiveRecord::ConnectionAdapters::JdbcAdapter"
22
- if model.connection.config[:adapter] == "jdbcmysql"
23
- ThinkingSphinx::MysqlAdapter.new model
24
- elsif model.connection.config[:adapter] == "jdbcpostgresql"
25
- ThinkingSphinx::PostgreSQLAdapter.new model
47
+ case model.connection.config[:adapter]
48
+ when "jdbcmysql"
49
+ :mysql
50
+ when "jdbcpostgresql"
51
+ :postgresql
26
52
  else
27
- raise "Invalid Database Adapter: Sphinx only supports MySQL and PostgreSQL"
53
+ model.connection.config[:adapter]
28
54
  end
29
55
  else
30
- raise "Invalid Database Adapter: Sphinx only supports MySQL and PostgreSQL, not #{model.connection.class.name}"
56
+ model.connection.class.name
31
57
  end
32
58
  end
33
59
 
@@ -39,6 +65,10 @@ module ThinkingSphinx
39
65
  /bigint/i
40
66
  end
41
67
 
68
+ def downcase(clause)
69
+ "LOWER(#{clause})"
70
+ end
71
+
42
72
  protected
43
73
 
44
74
  def connection
@@ -68,7 +68,7 @@ module ThinkingSphinx
68
68
  end
69
69
 
70
70
  def utc_query_pre
71
- 'SET TIME ZONE UTC'
71
+ "SET TIME ZONE 'UTC'"
72
72
  end
73
73
 
74
74
  private
@@ -119,6 +119,10 @@ module ThinkingSphinx
119
119
  DECLARE j int;
120
120
  DECLARE word_array bytea;
121
121
  BEGIN
122
+ IF COALESCE(word, '') = '' THEN
123
+ return 0;
124
+ END IF;
125
+
122
126
  i = 0;
123
127
  tmp = 4294967295;
124
128
  word_array = decode(replace(word, E'\\\\', E'\\\\\\\\'), 'escape');
@@ -139,7 +143,7 @@ module ThinkingSphinx
139
143
  END LOOP;
140
144
  return (tmp # 4294967295);
141
145
  END
142
- $$ IMMUTABLE STRICT LANGUAGE plpgsql;
146
+ $$ IMMUTABLE LANGUAGE plpgsql;
143
147
  SQL
144
148
  execute function, true
145
149
  end
@@ -111,6 +111,7 @@ module ThinkingSphinx
111
111
  clause = adapter.crc(clause) if @crc
112
112
  clause = adapter.concatenate(clause, separator) if concat_ws?
113
113
  clause = adapter.group_concatenate(clause, separator) if is_many?
114
+ clause = adapter.downcase(clause) if insensitive?
114
115
 
115
116
  "#{clause} AS #{quote_column(unique_name)}"
116
117
  end
@@ -376,5 +377,9 @@ block:
376
377
  value
377
378
  end
378
379
  end
380
+
381
+ def insensitive?
382
+ @sortable == :insensitive
383
+ end
379
384
  end
380
385
  end
@@ -54,13 +54,14 @@ module ThinkingSphinx
54
54
  sql_query_killlist sql_ranged_throttle sql_query_post_index unpack_zlib
55
55
  unpack_mysqlcompress unpack_mysqlcompress_maxsize )
56
56
 
57
- IndexOptions = %w( charset_table charset_type charset_dictpath docinfo
58
- enable_star exceptions html_index_attrs html_remove_elements html_strip
59
- index_exact_words ignore_chars inplace_docinfo_gap inplace_enable
60
- inplace_hit_gap inplace_reloc_factor inplace_write_factor min_infix_len
61
- min_prefix_len min_stemming_len min_word_len mlock morphology ngram_chars
62
- ngram_len ondisk_dict overshort_step phrase_boundary phrase_boundary_step
63
- preopen stopwords stopwords_step wordforms )
57
+ IndexOptions = %w( blend_chars charset_table charset_type charset_dictpath
58
+ docinfo enable_star exceptions expand_keywords hitless_words
59
+ html_index_attrs html_remove_elements html_strip index_exact_words
60
+ ignore_chars inplace_docinfo_gap inplace_enable inplace_hit_gap
61
+ inplace_reloc_factor inplace_write_factor min_infix_len min_prefix_len
62
+ min_stemming_len min_word_len mlock morphology ngram_chars ngram_len
63
+ ondisk_dict overshort_step phrase_boundary phrase_boundary_step preopen
64
+ stopwords stopwords_step wordforms )
64
65
 
65
66
  CustomOptions = %w( disable_range )
66
67
 
@@ -233,9 +234,12 @@ module ThinkingSphinx
233
234
  @controller.indexer_binary_name = name
234
235
  end
235
236
 
237
+ attr_accessor :timeout
238
+
236
239
  def client
237
240
  client = Riddle::Client.new address, port
238
241
  client.max_matches = configuration.searchd.max_matches || 1000
242
+ client.timeout = timeout || 0
239
243
  client
240
244
  end
241
245
 
@@ -65,8 +65,10 @@ class ThinkingSphinx::Context
65
65
  model_name.gsub!(/.*[\/\\]/, '').nil? ? next : retry
66
66
  rescue NameError
67
67
  next
68
- rescue StandardError
69
- STDERR.puts "Warning: Error loading #{file}"
68
+ rescue StandardError => err
69
+ STDERR.puts "Warning: Error loading #{file}:"
70
+ STDERR.puts err.message
71
+ STDERR.puts err.backtrace.join("\n"), ''
70
72
  end
71
73
  end
72
74
  end
@@ -13,6 +13,7 @@ module ThinkingSphinx
13
13
  @alias = options[:as]
14
14
  @faceted = options[:facet]
15
15
  @admin = options[:admin]
16
+ @sortable = options[:sortable] || false
16
17
 
17
18
  @alias = @alias.to_sym unless @alias.blank?
18
19
 
@@ -86,6 +86,8 @@ module ThinkingSphinx
86
86
  @options = args.extract_options!
87
87
  @args = args
88
88
 
89
+ add_default_scope unless options[:ignore_default]
90
+
89
91
  populate if @options[:populate]
90
92
  end
91
93
 
@@ -123,7 +125,7 @@ module ThinkingSphinx
123
125
  add_scope(method, *args, &block)
124
126
  return self
125
127
  elsif method == :search_count
126
- merge_search one_class.search(*args)
128
+ merge_search one_class.search(*args), self.args, options
127
129
  return scoped_count
128
130
  elsif method.to_s[/^each_with_.*/].nil? && !@array.respond_to?(method)
129
131
  super
@@ -270,18 +272,43 @@ module ThinkingSphinx
270
272
 
271
273
  populate
272
274
  client.excerpts(
273
- :docs => [string],
274
- :words => results[:words].keys.join(' '),
275
- :index => options[:index] || "#{model.source_of_sphinx_index.sphinx_name}_core"
275
+ {
276
+ :docs => [string.to_s],
277
+ :words => results[:words].keys.join(' '),
278
+ :index => options[:index] || "#{model.source_of_sphinx_index.sphinx_name}_core"
279
+ }.merge(options[:excerpt_options] || {})
276
280
  ).first
277
281
  end
278
282
 
279
283
  def search(*args)
280
- add_default_scope
281
- merge_search ThinkingSphinx::Search.new(*args)
284
+ args << args.extract_options!.merge(:ignore_default => true)
285
+ merge_search ThinkingSphinx::Search.new(*args), self.args, options
282
286
  self
283
287
  end
284
288
 
289
+ def search_for_ids(*args)
290
+ args << args.extract_options!.merge(
291
+ :ignore_default => true,
292
+ :ids_only => true
293
+ )
294
+ merge_search ThinkingSphinx::Search.new(*args), self.args, options
295
+ self
296
+ end
297
+
298
+ def facets(*args)
299
+ options = args.extract_options!
300
+ merge_search self, args, options
301
+ args << options
302
+
303
+ ThinkingSphinx::FacetSearch.new *args
304
+ end
305
+
306
+ def client
307
+ client = options[:client] || config.client
308
+
309
+ prepare client
310
+ end
311
+
285
312
  def append_to(client)
286
313
  prepare client
287
314
  client.append_query query, indexes, comment
@@ -382,9 +409,16 @@ module ThinkingSphinx
382
409
 
383
410
  def self.log(message, method = :debug, identifier = 'Sphinx')
384
411
  return if ::ActiveRecord::Base.logger.nil?
385
- identifier_color, message_color = "4;32;1", "0" # 0;1 = Bold
386
- info = " \e[#{identifier_color}m#{identifier}\e[0m "
387
- info << "\e[#{message_color}m#{message}\e[0m"
412
+
413
+ info = ''
414
+ if ::ActiveRecord::Base.colorize_logging
415
+ identifier_color, message_color = "4;32;1", "0" # 0;1 = Bold
416
+ info << " \e[#{identifier_color}m#{identifier}\e[0m "
417
+ info << "\e[#{message_color}m#{message}\e[0m"
418
+ else
419
+ info = "#{identifier} #{message}"
420
+ end
421
+
388
422
  ::ActiveRecord::Base.logger.send method, info
389
423
  end
390
424
 
@@ -392,12 +426,6 @@ module ThinkingSphinx
392
426
  self.class.log(*args)
393
427
  end
394
428
 
395
- def client
396
- client = config.client
397
-
398
- prepare client
399
- end
400
-
401
429
  def prepare(client)
402
430
  index_options = one_class ?
403
431
  one_class.sphinx_indexes.first.local_options : {}
@@ -489,7 +517,8 @@ module ThinkingSphinx
489
517
  query.gsub(/("#{token}(.*?#{token})?"|(?![!-])#{token})/u) do
490
518
  pre, proper, post = $`, $&, $'
491
519
  # E.g. "@foo", "/2", "~3", but not as part of a token
492
- is_operator = pre.match(%r{(\W|^)[@~/]\Z})
520
+ is_operator = pre.match(%r{(\W|^)[@~/]\Z}) ||
521
+ pre.match(%r{(\W|^)@\([^\)]*$})
493
522
  # E.g. "foo bar", with quotes
494
523
  is_quote = proper.starts_with?('"') && proper.ends_with?('"')
495
524
  has_star = pre.ends_with?("*") || post.starts_with?("*")
@@ -603,24 +632,8 @@ module ThinkingSphinx
603
632
  filters
604
633
  end
605
634
 
606
- def condition_filters
607
- (options[:conditions] || {}).collect { |attrib, value|
608
- if attributes.include?(attrib.to_sym)
609
- puts <<-MSG
610
- Deprecation Warning: filters on attributes should be done using the :with
611
- option, not :conditions. For example:
612
- :with => {:#{attrib} => #{value.inspect}}
613
- MSG
614
- Riddle::Client::Filter.new attrib.to_s, filter_value(value)
615
- else
616
- nil
617
- end
618
- }.compact
619
- end
620
-
621
635
  def filters
622
636
  internal_filters +
623
- condition_filters +
624
637
  (options[:with] || {}).collect { |attrib, value|
625
638
  Riddle::Client::Filter.new attrib.to_s, filter_value(value)
626
639
  } +
@@ -708,6 +721,21 @@ MSG
708
721
  end
709
722
  end
710
723
 
724
+ def include_for_class(klass)
725
+ includes = options[:include] || klass.sphinx_index_options[:include]
726
+
727
+ case includes
728
+ when NilClass
729
+ nil
730
+ when Array
731
+ includes.select { |inc| klass.reflections[inc] }
732
+ when Symbol
733
+ klass.reflections[includes].nil? ? nil : includes
734
+ else
735
+ includes
736
+ end
737
+ end
738
+
711
739
  def instances_from_class(klass, matches)
712
740
  index_options = klass.sphinx_index_options
713
741
 
@@ -716,7 +744,7 @@ MSG
716
744
  :all,
717
745
  :joins => options[:joins],
718
746
  :conditions => {klass.primary_key_for_sphinx.to_sym => ids},
719
- :include => (options[:include] || index_options[:include]),
747
+ :include => include_for_class(klass),
720
748
  :select => (options[:select] || index_options[:select]),
721
749
  :order => (options[:sql_order] || index_options[:sql_order])
722
750
  ) : []
@@ -786,14 +814,16 @@ MSG
786
814
 
787
815
  # Adds the default_sphinx_scope if set.
788
816
  def add_default_scope
789
- add_scope(one_class.get_default_sphinx_scope) if one_class && one_class.has_default_sphinx_scope?
817
+ return unless one_class && one_class.has_default_sphinx_scope?
818
+ add_scope(one_class.get_default_sphinx_scope.to_sym)
790
819
  end
791
820
 
792
821
  def add_scope(method, *args, &block)
793
- merge_search one_class.send(method, *args, &block)
822
+ method = "#{method}_without_default".to_sym
823
+ merge_search one_class.send(method, *args, &block), self.args, options
794
824
  end
795
825
 
796
- def merge_search(search)
826
+ def merge_search(search, args, options)
797
827
  search.args.each { |arg| args << arg }
798
828
 
799
829
  search.options.keys.each do |key|