dpickett-thinking-sphinx 1.1.12 → 1.1.23

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. data/README.textile +19 -0
  2. data/lib/thinking_sphinx.rb +36 -2
  3. data/lib/thinking_sphinx/active_record.rb +18 -3
  4. data/lib/thinking_sphinx/adapters/postgresql_adapter.rb +9 -3
  5. data/lib/thinking_sphinx/association.rb +4 -1
  6. data/lib/thinking_sphinx/attribute.rb +85 -43
  7. data/lib/thinking_sphinx/configuration.rb +33 -12
  8. data/lib/thinking_sphinx/deltas.rb +9 -6
  9. data/lib/thinking_sphinx/deltas/datetime_delta.rb +3 -3
  10. data/lib/thinking_sphinx/deltas/default_delta.rb +4 -4
  11. data/lib/thinking_sphinx/deltas/delayed_delta/delta_job.rb +1 -1
  12. data/lib/thinking_sphinx/deploy/capistrano.rb +82 -64
  13. data/lib/thinking_sphinx/facet.rb +58 -21
  14. data/lib/thinking_sphinx/facet_collection.rb +12 -13
  15. data/lib/thinking_sphinx/field.rb +3 -1
  16. data/lib/thinking_sphinx/index.rb +28 -353
  17. data/lib/thinking_sphinx/index/builder.rb +255 -232
  18. data/lib/thinking_sphinx/property.rb +29 -2
  19. data/lib/thinking_sphinx/search.rb +32 -96
  20. data/lib/thinking_sphinx/search/facets.rb +104 -0
  21. data/lib/thinking_sphinx/source.rb +150 -0
  22. data/lib/thinking_sphinx/source/internal_properties.rb +46 -0
  23. data/lib/thinking_sphinx/source/sql.rb +128 -0
  24. data/lib/thinking_sphinx/tasks.rb +42 -8
  25. data/rails/init.rb +14 -0
  26. data/spec/unit/thinking_sphinx/active_record/delta_spec.rb +5 -5
  27. data/spec/unit/thinking_sphinx/active_record/search_spec.rb +4 -4
  28. data/spec/unit/thinking_sphinx/active_record_spec.rb +52 -39
  29. data/spec/unit/thinking_sphinx/association_spec.rb +4 -5
  30. data/spec/unit/thinking_sphinx/attribute_spec.rb +209 -19
  31. data/spec/unit/thinking_sphinx/collection_spec.rb +7 -6
  32. data/spec/unit/thinking_sphinx/configuration_spec.rb +93 -7
  33. data/spec/unit/thinking_sphinx/facet_spec.rb +256 -0
  34. data/spec/unit/thinking_sphinx/field_spec.rb +26 -17
  35. data/spec/unit/thinking_sphinx/index/builder_spec.rb +351 -1
  36. data/spec/unit/thinking_sphinx/index_spec.rb +3 -102
  37. data/spec/unit/thinking_sphinx/rails_additions_spec.rb +13 -5
  38. data/spec/unit/thinking_sphinx/search_spec.rb +154 -29
  39. data/spec/unit/thinking_sphinx/source_spec.rb +217 -0
  40. data/spec/unit/thinking_sphinx_spec.rb +22 -4
  41. data/tasks/distribution.rb +19 -0
  42. data/vendor/riddle/lib/riddle.rb +1 -1
  43. data/vendor/riddle/lib/riddle/configuration/section.rb +7 -1
  44. metadata +26 -3
@@ -25,11 +25,18 @@ Then install the ginger gem. The steps are the same, except that you might need
25
25
  cd ginger
26
26
  rake gem
27
27
  sudo gem install pkg/ginger-1.1.0.gem
28
+
29
+ Alternatively, install the ginger gem directly from the freelancing-god github repository
30
+
31
+ sudo gem sources -a http://gems.github.com
32
+ sudo gem install freelancing-god-ginger
28
33
 
29
34
  Then set up your database:
30
35
 
31
36
  cp spec/fixtures/database.yml.default spec/fixtures/database.yml
32
37
  mysqladmin -u root create thinking_sphinx
38
+
39
+ This last step can be done automatically by the contribute.rb script if all dependencies are met.
33
40
 
34
41
  Make sure you don't have another Sphinx daemon (searchd) running. If you do, quit it with "rake ts:stop"
35
42
  in the app root.
@@ -124,3 +131,15 @@ Since I first released this library, there's been quite a few people who have su
124
131
  * Mark Lane
125
132
  * Eric Lindvall
126
133
  * Lawrence Pit
134
+ * Mike Bailey
135
+ * Bill Leeper
136
+ * Michael Reinsch
137
+ * Anderson Dias
138
+ * Jerome Riga
139
+ * Tien Dung
140
+ * Johannes Kaefer
141
+ * Paul Campbell
142
+ * Matthew Beale
143
+ * Tom Simnett
144
+ * Erik Ostrom
145
+ * Ole Riesenberg
@@ -18,6 +18,7 @@ require 'thinking_sphinx/class_facet'
18
18
  require 'thinking_sphinx/facet_collection'
19
19
  require 'thinking_sphinx/field'
20
20
  require 'thinking_sphinx/index'
21
+ require 'thinking_sphinx/source'
21
22
  require 'thinking_sphinx/rails_additions'
22
23
  require 'thinking_sphinx/search'
23
24
  require 'thinking_sphinx/deltas'
@@ -36,7 +37,7 @@ module ThinkingSphinx
36
37
  module Version #:nodoc:
37
38
  Major = 1
38
39
  Minor = 1
39
- Tiny = 12
40
+ Tiny = 23
40
41
 
41
42
  String = [Major, Minor, Tiny].join('.')
42
43
  end
@@ -137,7 +138,39 @@ module ThinkingSphinx
137
138
  )
138
139
  end
139
140
 
141
+ @@remote_sphinx = false
142
+
143
+ # An indication of whether Sphinx is running on a remote machine instead of
144
+ # the same machine.
145
+ #
146
+ def self.remote_sphinx?
147
+ @@remote_sphinx
148
+ end
149
+
150
+ # Tells Thinking Sphinx that Sphinx is running on a different machine, and
151
+ # thus it can't reliably guess whether it is running or not (ie: the
152
+ # #sphinx_running? method), and so just assumes it is.
153
+ #
154
+ # Useful for multi-machine deployments. Set it in your production.rb file.
155
+ #
156
+ # ThinkingSphinx.remote_sphinx = true
157
+ #
158
+ def self.remote_sphinx=(value)
159
+ @@remote_sphinx = value
160
+ end
161
+
162
+ # Check if Sphinx is running. If remote_sphinx is set to true (indicating
163
+ # Sphinx is on a different machine), this will always return true, and you
164
+ # will have to handle any connection errors yourself.
165
+ #
140
166
  def self.sphinx_running?
167
+ remote_sphinx? || sphinx_running_by_pid?
168
+ end
169
+
170
+ # Check if Sphinx is actually running, provided the pid is on the same
171
+ # machine as this code.
172
+ #
173
+ def self.sphinx_running_by_pid?
141
174
  !!sphinx_pid && pid_active?(sphinx_pid)
142
175
  end
143
176
 
@@ -174,7 +207,8 @@ module ThinkingSphinx
174
207
  end
175
208
 
176
209
  def self.mysql?
177
- ::ActiveRecord::Base.connection.class.name.demodulize == "MysqlAdapter" || (
210
+ ::ActiveRecord::Base.connection.class.name.demodulize == "MysqlAdapter" ||
211
+ ::ActiveRecord::Base.connection.class.name.demodulize == "MysqlplusAdapter" || (
178
212
  jruby? && ::ActiveRecord::Base.connection.config[:adapter] == "jdbcmysql"
179
213
  )
180
214
  end
@@ -66,7 +66,7 @@ module ThinkingSphinx
66
66
  return unless ThinkingSphinx.define_indexes?
67
67
 
68
68
  self.sphinx_indexes ||= []
69
- index = Index.new(self, &block)
69
+ index = ThinkingSphinx::Index::Builder.generate(self, &block)
70
70
 
71
71
  self.sphinx_indexes << index
72
72
  unless ThinkingSphinx.indexed_models.include?(self.name)
@@ -83,6 +83,17 @@ module ThinkingSphinx
83
83
  include ThinkingSphinx::ActiveRecord::AttributeUpdates
84
84
 
85
85
  index
86
+
87
+ # We want to make sure that if the database doesn't exist, then Thinking
88
+ # Sphinx doesn't mind when running non-TS tasks (like db:create, db:drop
89
+ # and db:migrate). It's a bit hacky, but I can't think of a better way.
90
+ rescue StandardError => err
91
+ case err.class.name
92
+ when "Mysql::Error", "Java::JavaSql::SQLException", "ActiveRecord::StatementInvalid"
93
+ return
94
+ else
95
+ raise err
96
+ end
86
97
  end
87
98
  alias_method :sphinx_index, :define_index
88
99
 
@@ -151,7 +162,9 @@ module ThinkingSphinx
151
162
  self.sphinx_indexes.select { |ts_index|
152
163
  ts_index.model == self
153
164
  }.each_with_index do |ts_index, i|
154
- index.sources << ts_index.to_riddle_for_core(offset, i)
165
+ index.sources += ts_index.sources.collect { |source|
166
+ source.to_riddle_for_core(offset, i)
167
+ }
155
168
  end
156
169
 
157
170
  index
@@ -163,7 +176,9 @@ module ThinkingSphinx
163
176
  index.path = File.join(ThinkingSphinx::Configuration.instance.searchd_file_path, index.name)
164
177
 
165
178
  self.sphinx_indexes.each_with_index do |ts_index, i|
166
- index.sources << ts_index.to_riddle_for_delta(offset, i) if ts_index.delta?
179
+ index.sources += ts_index.sources.collect { |source|
180
+ source.to_riddle_for_delta(offset, i)
181
+ } if ts_index.delta?
167
182
  end
168
183
 
169
184
  index
@@ -11,7 +11,12 @@ module ThinkingSphinx
11
11
 
12
12
  def concatenate(clause, separator = ' ')
13
13
  clause.split(', ').collect { |field|
14
- "COALESCE(CAST(#{field} as varchar), '')"
14
+ case field
15
+ when /COALESCE/, "'')"
16
+ field
17
+ else
18
+ "COALESCE(CAST(#{field} as varchar), '')"
19
+ end
15
20
  }.join(" || '#{separator}' || ")
16
21
  end
17
22
 
@@ -32,7 +37,8 @@ module ThinkingSphinx
32
37
  end
33
38
 
34
39
  def convert_nulls(clause, default = '')
35
- default = "'#{default}'" if default.is_a?(String)
40
+ default = "'#{default}'" if default.is_a?(String)
41
+ default = 'NULL' if default.nil?
36
42
 
37
43
  "COALESCE(#{clause}, #{default})"
38
44
  end
@@ -127,4 +133,4 @@ module ThinkingSphinx
127
133
  execute function, true
128
134
  end
129
135
  end
130
- end
136
+ end
@@ -103,13 +103,16 @@ module ThinkingSphinx
103
103
  if @reflection.options[:through]
104
104
  @reflection.source_reflection.options[:foreign_key] ||
105
105
  @reflection.source_reflection.primary_key_name
106
+ elsif @reflection.macro == :has_and_belongs_to_many
107
+ @reflection.association_foreign_key
106
108
  else
107
109
  nil
108
110
  end
109
111
  end
110
112
 
111
113
  def table
112
- if @reflection.options[:through]
114
+ if @reflection.options[:through] ||
115
+ @reflection.macro == :has_and_belongs_to_many
113
116
  @join.aliased_join_table_name
114
117
  else
115
118
  @join.aliased_table_name
@@ -9,7 +9,7 @@ module ThinkingSphinx
9
9
  # associations. Which can get messy. Use Index.link!, it really helps.
10
10
  #
11
11
  class Attribute < ThinkingSphinx::Property
12
- attr_accessor :source
12
+ attr_accessor :query_source
13
13
 
14
14
  # To create a new attribute, you'll need to pass in either a single Column
15
15
  # or an array of them, and some (optional) options.
@@ -67,15 +67,17 @@ module ThinkingSphinx
67
67
  # If you're creating attributes for latitude and longitude, don't forget
68
68
  # that Sphinx expects these values to be in radians.
69
69
  #
70
- def initialize(columns, options = {})
70
+ def initialize(source, columns, options = {})
71
71
  super
72
72
 
73
- @type = options[:type]
74
- @source = options[:source]
75
- @crc = options[:crc]
73
+ @type = options[:type]
74
+ @query_source = options[:source]
75
+ @crc = options[:crc]
76
76
 
77
- @type ||= :multi unless @source.nil?
78
- @type = :integer if @type == :string && @crc
77
+ @type ||= :multi unless @query_source.nil?
78
+ @type = :integer if @type == :string && @crc
79
+
80
+ source.attributes << self
79
81
  end
80
82
 
81
83
  # Get the part of the SELECT clause related to this attribute. Don't forget
@@ -87,17 +89,17 @@ module ThinkingSphinx
87
89
  def to_select_sql
88
90
  return nil unless include_as_association?
89
91
 
92
+ separator = all_ints? || @crc ? ',' : ' '
93
+
90
94
  clause = @columns.collect { |column|
91
- column_with_prefix(column)
95
+ part = column_with_prefix(column)
96
+ type == :string ? adapter.convert_nulls(part) : part
92
97
  }.join(', ')
93
98
 
94
- separator = all_ints? ? ',' : ' '
95
-
96
- clause = adapter.concatenate(clause, separator) if concat_ws?
97
- clause = adapter.group_concatenate(clause, separator) if is_many?
98
99
  clause = adapter.cast_to_datetime(clause) if type == :datetime
99
- clause = adapter.convert_nulls(clause) if type == :string
100
100
  clause = adapter.crc(clause) if @crc
101
+ clause = adapter.concatenate(clause, separator) if concat_ws?
102
+ clause = adapter.group_concatenate(clause, separator) if is_many?
101
103
 
102
104
  "#{clause} AS #{quote_column(unique_name)}"
103
105
  end
@@ -114,7 +116,7 @@ module ThinkingSphinx
114
116
  end
115
117
 
116
118
  def include_as_association?
117
- ! (type == :multi && (source == :query || source == :ranged_query))
119
+ ! (type == :multi && (query_source == :query || query_source == :ranged_query))
118
120
  end
119
121
 
120
122
  # Returns the configuration value that should be used for
@@ -122,10 +124,10 @@ module ThinkingSphinx
122
124
  # Special case is the multi-valued attribute that needs some
123
125
  # extra configuration.
124
126
  #
125
- def config_value(offset = nil)
127
+ def config_value(offset = nil, delta = false)
126
128
  if type == :multi
127
129
  multi_config = include_as_association? ? "field" :
128
- source_value(offset).gsub(/\n\s*/, " ")
130
+ source_value(offset, delta).gsub(/\s+/m, " ").strip
129
131
  "uint #{unique_name} from #{multi_config}"
130
132
  else
131
133
  unique_name
@@ -168,37 +170,65 @@ module ThinkingSphinx
168
170
  object.send(column.__name)
169
171
  end
170
172
 
173
+ def all_ints?
174
+ @columns.all? { |col|
175
+ klasses = @associations[col].empty? ? [@model] :
176
+ @associations[col].collect { |assoc| assoc.reflection.klass }
177
+ klasses.all? { |klass|
178
+ column = klass.columns.detect { |column| column.name == col.__name.to_s }
179
+ !column.nil? && column.type == :integer
180
+ }
181
+ }
182
+ end
183
+
171
184
  private
172
185
 
173
- def source_value(offset)
186
+ def source_value(offset, delta)
174
187
  if is_string?
175
- "#{source.to_s.dasherize}; #{columns.first.__name}"
176
- elsif source == :ranged_query
177
- "ranged-query; #{query offset} #{query_clause}; #{range_query}"
188
+ return "#{query_source.to_s.dasherize}; #{columns.first.__name}"
189
+ end
190
+
191
+ query = query(offset)
192
+
193
+ if query_source == :ranged_query
194
+ query += query_clause
195
+ query += " AND #{query_delta.strip}" if delta
196
+ "ranged-query; #{query}; #{range_query}"
178
197
  else
179
- "query; #{query offset}"
198
+ query += "WHERE #{query_delta.strip}" if delta
199
+ "query; #{query}"
180
200
  end
181
201
  end
182
202
 
183
203
  def query(offset)
184
- assoc = association_for_mva
185
- raise "Could not determine SQL for MVA" if assoc.nil?
204
+ base_assoc = base_association_for_mva
205
+ end_assoc = end_association_for_mva
206
+ raise "Could not determine SQL for MVA" if base_assoc.nil?
186
207
 
187
208
  <<-SQL
188
- SELECT #{foreign_key_for_mva assoc}
209
+ SELECT #{foreign_key_for_mva base_assoc}
189
210
  #{ThinkingSphinx.unique_id_expression(offset)} AS #{quote_column('id')},
190
- #{primary_key_for_mva(assoc)} AS #{quote_column(unique_name)}
191
- FROM #{quote_table_name assoc.table}
211
+ #{primary_key_for_mva(end_assoc)} AS #{quote_column(unique_name)}
212
+ FROM #{quote_table_name base_assoc.table} #{association_joins}
192
213
  SQL
193
214
  end
194
215
 
195
216
  def query_clause
196
- foreign_key = foreign_key_for_mva association_for_mva
217
+ foreign_key = foreign_key_for_mva base_association_for_mva
197
218
  "WHERE #{foreign_key} >= $start AND #{foreign_key} <= $end"
198
219
  end
199
220
 
221
+ def query_delta
222
+ foreign_key = foreign_key_for_mva base_association_for_mva
223
+ <<-SQL
224
+ #{foreign_key} IN (SELECT #{quote_column model.primary_key}
225
+ FROM #{model.quoted_table_name}
226
+ WHERE #{@source.index.delta_object.clause(model, true)})
227
+ SQL
228
+ end
229
+
200
230
  def range_query
201
- assoc = association_for_mva
231
+ assoc = base_association_for_mva
202
232
  foreign_key = foreign_key_for_mva assoc
203
233
  "SELECT MIN(#{foreign_key}), MAX(#{foreign_key}) FROM #{quote_table_name assoc.table}"
204
234
  end
@@ -213,27 +243,38 @@ FROM #{quote_table_name assoc.table}
213
243
  quote_with_table assoc.table, assoc.reflection.primary_key_name
214
244
  end
215
245
 
216
- def association_for_mva
246
+ def end_association_for_mva
217
247
  @association_for_mva ||= associations[columns.first].detect { |assoc|
218
248
  assoc.has_column?(columns.first.__name)
219
249
  }
220
250
  end
221
251
 
252
+ def base_association_for_mva
253
+ @first_association_for_mva ||= begin
254
+ assoc = end_association_for_mva
255
+ while !assoc.parent.nil?
256
+ assoc = assoc.parent
257
+ end
258
+
259
+ assoc
260
+ end
261
+ end
262
+
263
+ def association_joins
264
+ joins = []
265
+ assoc = end_association_for_mva
266
+ while assoc != base_association_for_mva
267
+ joins << assoc.to_sql
268
+ assoc = assoc.parent
269
+ end
270
+
271
+ joins.join(' ')
272
+ end
273
+
222
274
  def is_many_ints?
223
275
  concat_ws? && all_ints?
224
276
  end
225
277
 
226
- def all_ints?
227
- @columns.all? { |col|
228
- klasses = @associations[col].empty? ? [@model] :
229
- @associations[col].collect { |assoc| assoc.reflection.klass }
230
- klasses.all? { |klass|
231
- column = klass.columns.detect { |column| column.name == col.__name.to_s }
232
- !column.nil? && column.type == :integer
233
- }
234
- }
235
- end
236
-
237
278
  def type_from_database
238
279
  klass = @associations.values.flatten.first ?
239
280
  @associations.values.flatten.first.reflection.klass : @model
@@ -254,9 +295,10 @@ FROM #{quote_table_name assoc.table}
254
295
  else
255
296
  raise <<-MESSAGE
256
297
 
257
- Cannot automatically map column type #{type_from_db} to an equivalent Sphinx
258
- type (integer, float, boolean, datetime, string as ordinal). You could try to
259
- explicitly convert the column's value in your define_index block:
298
+ Cannot automatically map attribute #{unique_name} in #{@model.name} to an
299
+ equivalent Sphinx type (integer, float, boolean, datetime, string as ordinal).
300
+ You could try to explicitly convert the column's value in your define_index
301
+ block:
260
302
  has "CAST(column AS INT)", :type => :integer, :as => :column
261
303
  MESSAGE
262
304
  end
@@ -19,12 +19,14 @@ module ThinkingSphinx
19
19
  # min infix length:: 1
20
20
  # mem limit:: 64M
21
21
  # max matches:: 1000
22
- # morphology:: stem_en
22
+ # morphology:: nil
23
23
  # charset type:: utf-8
24
24
  # charset table:: nil
25
25
  # ignore chars:: nil
26
26
  # html strip:: false
27
27
  # html remove elements:: ''
28
+ # searchd_binary_name:: searchd
29
+ # indexer_binary_name:: indexer
28
30
  #
29
31
  # If you want to change these settings, create a YAML file at
30
32
  # config/sphinx.yml with settings for each environment, in a similar
@@ -32,7 +34,8 @@ module ThinkingSphinx
32
34
  # searchd_log_file, query_log_file, pid_file, searchd_file_path, port,
33
35
  # allow_star, enable_star, min_prefix_len, min_infix_len, mem_limit,
34
36
  # max_matches, morphology, charset_type, charset_table, ignore_chars,
35
- # html_strip, html_remove_elements, delayed_job_priority.
37
+ # html_strip, html_remove_elements, delayed_job_priority,
38
+ # searchd_binary_name, indexer_binary_name.
36
39
  #
37
40
  # I think you've got the idea.
38
41
  #
@@ -54,11 +57,13 @@ module ThinkingSphinx
54
57
  min_infix_len min_prefix_len min_word_len mlock morphology ngram_chars
55
58
  ngram_len phrase_boundary phrase_boundary_step preopen stopwords
56
59
  wordforms )
60
+
61
+ CustomOptions = %w( disable_range )
57
62
 
58
63
  attr_accessor :config_file, :searchd_log_file, :query_log_file,
59
64
  :pid_file, :searchd_file_path, :address, :port, :allow_star,
60
65
  :database_yml_file, :app_root, :bin_path, :model_directories,
61
- :delayed_job_priority
66
+ :delayed_job_priority, :searchd_binary_name, :indexer_binary_name
62
67
 
63
68
  attr_accessor :source_options, :index_options
64
69
 
@@ -71,10 +76,19 @@ module ThinkingSphinx
71
76
  self.reset
72
77
  end
73
78
 
74
- def reset
75
- self.app_root = RAILS_ROOT if defined?(RAILS_ROOT)
76
- self.app_root = Merb.root if defined?(Merb)
77
- self.app_root ||= app_root
79
+ def self.configure(&block)
80
+ yield instance
81
+ instance.reset(instance.app_root)
82
+ end
83
+
84
+ def reset(custom_app_root=nil)
85
+ if custom_app_root
86
+ self.app_root = custom_app_root
87
+ else
88
+ self.app_root = RAILS_ROOT if defined?(RAILS_ROOT)
89
+ self.app_root = Merb.root if defined?(Merb)
90
+ self.app_root ||= app_root
91
+ end
78
92
 
79
93
  @configuration = Riddle::Configuration.new
80
94
  @configuration.searchd.address = "127.0.0.1"
@@ -94,9 +108,11 @@ module ThinkingSphinx
94
108
 
95
109
  self.source_options = {}
96
110
  self.index_options = {
97
- :charset_type => "utf-8",
98
- :morphology => "stem_en"
111
+ :charset_type => "utf-8"
99
112
  }
113
+
114
+ self.searchd_binary_name = "searchd"
115
+ self.indexer_binary_name = "indexer"
100
116
 
101
117
  parse_config
102
118
 
@@ -141,6 +157,8 @@ module ThinkingSphinx
141
157
  # messy dependencies issues).
142
158
  #
143
159
  def load_models
160
+ return if defined?(Rails) && Rails.configuration.cache_classes
161
+
144
162
  self.model_directories.each do |base|
145
163
  Dir["#{base}**/*.rb"].each do |file|
146
164
  model_name = file.gsub(/^#{base}([\w_\/\\]+)\.rb/, '\1')
@@ -156,6 +174,8 @@ module ThinkingSphinx
156
174
  model_name.gsub!(/.*[\/\\]/, '').nil? ? next : retry
157
175
  rescue NameError
158
176
  next
177
+ rescue StandardError
178
+ puts "Warning: Error loading #{file}"
159
179
  end
160
180
  end
161
181
  end
@@ -213,10 +233,11 @@ module ThinkingSphinx
213
233
  conf = YAML::load(ERB.new(IO.read(path)).result)[environment]
214
234
 
215
235
  conf.each do |key,value|
216
- self.send("#{key}=", value) if self.methods.include?("#{key}=")
236
+ self.send("#{key}=", value) if self.respond_to?("#{key}=")
217
237
 
218
238
  set_sphinx_setting self.source_options, key, value, SourceOptions
219
239
  set_sphinx_setting self.index_options, key, value, IndexOptions
240
+ set_sphinx_setting self.index_options, key, value, CustomOptions
220
241
  set_sphinx_setting @configuration.searchd, key, value
221
242
  set_sphinx_setting @configuration.indexer, key, value
222
243
  end unless conf.nil?
@@ -233,8 +254,8 @@ module ThinkingSphinx
233
254
  if object.is_a?(Hash)
234
255
  object[key.to_sym] = value if allowed.include?(key.to_s)
235
256
  else
236
- object.send("#{key}=", value) if object.methods.include?("#{key}")
237
- send("#{key}=", value) if self.methods.include?("#{key}")
257
+ object.send("#{key}=", value) if object.respond_to?("#{key}")
258
+ send("#{key}=", value) if self.respond_to?("#{key}")
238
259
  end
239
260
  end
240
261
  end