DrMark-thinking-sphinx 1.1.15 → 1.2.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. data/README.textile +22 -0
  2. data/VERSION.yml +4 -0
  3. data/lib/thinking_sphinx/active_record/scopes.rb +39 -0
  4. data/lib/thinking_sphinx/active_record.rb +27 -7
  5. data/lib/thinking_sphinx/adapters/postgresql_adapter.rb +9 -3
  6. data/lib/thinking_sphinx/association.rb +4 -1
  7. data/lib/thinking_sphinx/attribute.rb +91 -30
  8. data/lib/thinking_sphinx/configuration.rb +51 -12
  9. data/lib/thinking_sphinx/deltas/datetime_delta.rb +2 -2
  10. data/lib/thinking_sphinx/deltas/default_delta.rb +1 -1
  11. data/lib/thinking_sphinx/deltas/delayed_delta/delta_job.rb +1 -1
  12. data/lib/thinking_sphinx/deltas/delayed_delta.rb +3 -0
  13. data/lib/thinking_sphinx/deploy/capistrano.rb +25 -8
  14. data/lib/thinking_sphinx/excerpter.rb +22 -0
  15. data/lib/thinking_sphinx/facet.rb +1 -1
  16. data/lib/thinking_sphinx/facet_search.rb +134 -0
  17. data/lib/thinking_sphinx/index.rb +2 -1
  18. data/lib/thinking_sphinx/rails_additions.rb +14 -0
  19. data/lib/thinking_sphinx/search.rb +599 -658
  20. data/lib/thinking_sphinx/search_methods.rb +421 -0
  21. data/lib/thinking_sphinx/source/internal_properties.rb +1 -1
  22. data/lib/thinking_sphinx/source/sql.rb +17 -13
  23. data/lib/thinking_sphinx/source.rb +6 -6
  24. data/lib/thinking_sphinx/tasks.rb +42 -8
  25. data/lib/thinking_sphinx.rb +82 -54
  26. data/rails/init.rb +14 -0
  27. data/spec/{unit → lib}/thinking_sphinx/active_record/delta_spec.rb +5 -5
  28. data/spec/{unit → lib}/thinking_sphinx/active_record/has_many_association_spec.rb +0 -0
  29. data/spec/lib/thinking_sphinx/active_record/scopes_spec.rb +96 -0
  30. data/spec/{unit → lib}/thinking_sphinx/active_record_spec.rb +51 -31
  31. data/spec/{unit → lib}/thinking_sphinx/association_spec.rb +4 -5
  32. data/spec/lib/thinking_sphinx/attribute_spec.rb +465 -0
  33. data/spec/{unit → lib}/thinking_sphinx/configuration_spec.rb +161 -29
  34. data/spec/{unit → lib}/thinking_sphinx/core/string_spec.rb +0 -0
  35. data/spec/lib/thinking_sphinx/excerpter_spec.rb +49 -0
  36. data/spec/lib/thinking_sphinx/facet_search_spec.rb +176 -0
  37. data/spec/{unit → lib}/thinking_sphinx/facet_spec.rb +24 -0
  38. data/spec/{unit → lib}/thinking_sphinx/field_spec.rb +8 -8
  39. data/spec/{unit → lib}/thinking_sphinx/index/builder_spec.rb +6 -2
  40. data/spec/{unit → lib}/thinking_sphinx/index/faux_column_spec.rb +0 -0
  41. data/spec/lib/thinking_sphinx/index_spec.rb +45 -0
  42. data/spec/{unit → lib}/thinking_sphinx/rails_additions_spec.rb +25 -5
  43. data/spec/lib/thinking_sphinx/search_methods_spec.rb +152 -0
  44. data/spec/lib/thinking_sphinx/search_spec.rb +960 -0
  45. data/spec/{unit → lib}/thinking_sphinx/source_spec.rb +63 -2
  46. data/spec/{unit → lib}/thinking_sphinx_spec.rb +32 -4
  47. data/tasks/distribution.rb +36 -35
  48. data/vendor/riddle/lib/riddle/client/message.rb +4 -3
  49. data/vendor/riddle/lib/riddle/client.rb +3 -0
  50. data/vendor/riddle/lib/riddle/configuration/section.rb +8 -2
  51. data/vendor/riddle/lib/riddle/controller.rb +17 -7
  52. data/vendor/riddle/lib/riddle.rb +1 -1
  53. metadata +79 -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 -98
  58. data/spec/unit/thinking_sphinx/active_record/search_spec.rb +0 -107
  59. data/spec/unit/thinking_sphinx/attribute_spec.rb +0 -232
  60. data/spec/unit/thinking_sphinx/collection_spec.rb +0 -14
  61. data/spec/unit/thinking_sphinx/facet_collection_spec.rb +0 -64
  62. data/spec/unit/thinking_sphinx/index_spec.rb +0 -139
  63. data/spec/unit/thinking_sphinx/search_spec.rb +0 -130
data/README.textile CHANGED
@@ -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.
@@ -125,3 +132,18 @@ Since I first released this library, there's been quite a few people who have su
125
132
  * Eric Lindvall
126
133
  * Lawrence Pit
127
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
146
+ * Josh Kalderimis
147
+ * J.D. Hollis
148
+ * Jeffrey Chupp
149
+ * Rob Anderton
data/VERSION.yml ADDED
@@ -0,0 +1,4 @@
1
+ ---
2
+ :major: 1
3
+ :minor: 2
4
+ :patch: 5
@@ -0,0 +1,39 @@
1
+ module ThinkingSphinx
2
+ module ActiveRecord
3
+ module Scopes
4
+ def self.included(base)
5
+ base.class_eval do
6
+ extend ThinkingSphinx::ActiveRecord::Scopes::ClassMethods
7
+ end
8
+ end
9
+
10
+ module ClassMethods
11
+ def sphinx_scope(method, &block)
12
+ @sphinx_scopes ||= []
13
+ @sphinx_scopes << method
14
+
15
+ metaclass.instance_eval do
16
+ define_method(method) do |*args|
17
+ options = {:classes => classes_option}
18
+ options.merge! block.call(*args)
19
+
20
+ ThinkingSphinx::Search.new(options)
21
+ end
22
+ end
23
+ end
24
+
25
+ def sphinx_scopes
26
+ @sphinx_scopes || []
27
+ end
28
+
29
+ def remove_sphinx_scopes
30
+ sphinx_scopes.each do |scope|
31
+ metaclass.send(:undef_method, scope)
32
+ end
33
+
34
+ sphinx_scopes.clear
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -1,7 +1,7 @@
1
1
  require 'thinking_sphinx/active_record/attribute_updates'
2
2
  require 'thinking_sphinx/active_record/delta'
3
- require 'thinking_sphinx/active_record/search'
4
3
  require 'thinking_sphinx/active_record/has_many_association'
4
+ require 'thinking_sphinx/active_record/scopes'
5
5
 
6
6
  module ThinkingSphinx
7
7
  # Core additions to ActiveRecord models - define_index for creating indexes
@@ -13,6 +13,15 @@ module ThinkingSphinx
13
13
  base.class_eval do
14
14
  class_inheritable_array :sphinx_indexes, :sphinx_facets
15
15
  class << self
16
+
17
+ def set_sphinx_primary_key(attribute)
18
+ @sphinx_primary_key_attribute = attribute
19
+ end
20
+
21
+ def primary_key_for_sphinx
22
+ @sphinx_primary_key_attribute || primary_key
23
+ end
24
+
16
25
  # Allows creation of indexes for Sphinx. If you don't do this, there
17
26
  # isn't much point trying to search (or using this plugin at all,
18
27
  # really).
@@ -80,7 +89,9 @@ module ThinkingSphinx
80
89
 
81
90
  after_destroy :toggle_deleted
82
91
 
92
+ include ThinkingSphinx::SearchMethods
83
93
  include ThinkingSphinx::ActiveRecord::AttributeUpdates
94
+ include ThinkingSphinx::ActiveRecord::Scopes
84
95
 
85
96
  index
86
97
 
@@ -140,12 +151,12 @@ module ThinkingSphinx
140
151
  ThinkingSphinx::AbstractAdapter.detect(self)
141
152
  end
142
153
 
143
- private
144
-
145
154
  def sphinx_name
146
155
  self.name.underscore.tr(':/\\', '_')
147
156
  end
148
157
 
158
+ private
159
+
149
160
  def sphinx_delta?
150
161
  self.sphinx_indexes.any? { |index| index.delta? }
151
162
  end
@@ -215,7 +226,6 @@ module ThinkingSphinx
215
226
  end
216
227
 
217
228
  base.send(:include, ThinkingSphinx::ActiveRecord::Delta)
218
- base.send(:include, ThinkingSphinx::ActiveRecord::Search)
219
229
 
220
230
  ::ActiveRecord::Associations::HasManyAssociation.send(
221
231
  :include, ThinkingSphinx::ActiveRecord::HasManyAssociation
@@ -264,13 +274,23 @@ module ThinkingSphinx
264
274
  # nothing
265
275
  end
266
276
 
277
+ # Returns the unique integer id for the object. This method uses the
278
+ # attribute hash to get around ActiveRecord always mapping the #id method
279
+ # to whatever the real primary key is (which may be a unique string hash).
280
+ #
281
+ # @return [Integer] Unique record id for the purposes of Sphinx.
282
+ #
283
+ def primary_key_for_sphinx
284
+ attributes[self.class.primary_key_for_sphinx.to_s]
285
+ end
286
+
267
287
  def sphinx_document_id
268
- (self.id * ThinkingSphinx.indexed_models.size) +
288
+ primary_key_for_sphinx * ThinkingSphinx.indexed_models.size +
269
289
  ThinkingSphinx.indexed_models.index(self.class.source_of_sphinx_index.name)
270
290
  end
271
-
291
+
272
292
  private
273
-
293
+
274
294
  def sphinx_index_name(suffix)
275
295
  "#{self.class.source_of_sphinx_index.name.underscore.tr(':/\\', '_')}_#{suffix}"
276
296
  end
@@ -11,7 +11,12 @@ module ThinkingSphinx
11
11
 
12
12
  def concatenate(clause, separator = ' ')
13
13
  clause.split(', ').collect { |field|
14
- field[/COALESCE/] ? field : "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
@@ -89,14 +89,21 @@ module ThinkingSphinx
89
89
  def to_select_sql
90
90
  return nil unless include_as_association?
91
91
 
92
+ separator = all_ints? || all_datetimes? || @crc ? ',' : ' '
93
+
92
94
  clause = @columns.collect { |column|
93
95
  part = column_with_prefix(column)
94
- type == :string ? adapter.convert_nulls(part) : part
96
+ case type
97
+ when :string
98
+ adapter.convert_nulls(part)
99
+ when :datetime
100
+ adapter.cast_to_datetime(part)
101
+ else
102
+ part
103
+ end
95
104
  }.join(', ')
96
105
 
97
- separator = all_ints? || @crc ? ',' : ' '
98
-
99
- clause = adapter.cast_to_datetime(clause) if type == :datetime
106
+ # clause = adapter.cast_to_datetime(clause) if type == :datetime
100
107
  clause = adapter.crc(clause) if @crc
101
108
  clause = adapter.concatenate(clause, separator) if concat_ws?
102
109
  clause = adapter.group_concatenate(clause, separator) if is_many?
@@ -124,10 +131,10 @@ module ThinkingSphinx
124
131
  # Special case is the multi-valued attribute that needs some
125
132
  # extra configuration.
126
133
  #
127
- def config_value(offset = nil)
134
+ def config_value(offset = nil, delta = false)
128
135
  if type == :multi
129
136
  multi_config = include_as_association? ? "field" :
130
- source_value(offset).gsub(/\n\s*/, " ")
137
+ source_value(offset, delta).gsub(/\s+/m, " ").strip
131
138
  "uint #{unique_name} from #{multi_config}"
132
139
  else
133
140
  unique_name
@@ -142,6 +149,8 @@ module ThinkingSphinx
142
149
  def type
143
150
  @type ||= begin
144
151
  base_type = case
152
+ when is_many_datetimes?
153
+ :datetime
145
154
  when is_many?, is_many_ints?
146
155
  :multi
147
156
  when @associations.values.flatten.length > 1
@@ -171,47 +180,61 @@ module ThinkingSphinx
171
180
  end
172
181
 
173
182
  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
- }
183
+ all_of_type?(:integer)
184
+ end
185
+
186
+ def all_datetimes?
187
+ all_of_type?(:datetime, :date, :timestamp)
182
188
  end
183
189
 
184
190
  private
185
191
 
186
- def source_value(offset)
192
+ def source_value(offset, delta)
187
193
  if is_string?
188
- "#{query_source.to_s.dasherize}; #{columns.first.__name}"
189
- elsif query_source == :ranged_query
190
- "ranged-query; #{query offset} #{query_clause}; #{range_query}"
194
+ return "#{query_source.to_s.dasherize}; #{columns.first.__name}"
195
+ end
196
+
197
+ query = query(offset)
198
+
199
+ if query_source == :ranged_query
200
+ query += query_clause
201
+ query += " AND #{query_delta.strip}" if delta
202
+ "ranged-query; #{query}; #{range_query}"
191
203
  else
192
- "query; #{query offset}"
204
+ query += "WHERE #{query_delta.strip}" if delta
205
+ "query; #{query}"
193
206
  end
194
207
  end
195
208
 
196
209
  def query(offset)
197
- assoc = association_for_mva
198
- raise "Could not determine SQL for MVA" if assoc.nil?
210
+ base_assoc = base_association_for_mva
211
+ end_assoc = end_association_for_mva
212
+ raise "Could not determine SQL for MVA" if base_assoc.nil?
199
213
 
200
214
  <<-SQL
201
- SELECT #{foreign_key_for_mva assoc}
215
+ SELECT #{foreign_key_for_mva base_assoc}
202
216
  #{ThinkingSphinx.unique_id_expression(offset)} AS #{quote_column('id')},
203
- #{primary_key_for_mva(assoc)} AS #{quote_column(unique_name)}
204
- FROM #{quote_table_name assoc.table}
217
+ #{primary_key_for_mva(end_assoc)} AS #{quote_column(unique_name)}
218
+ FROM #{quote_table_name base_assoc.table} #{association_joins}
205
219
  SQL
206
220
  end
207
221
 
208
222
  def query_clause
209
- foreign_key = foreign_key_for_mva association_for_mva
223
+ foreign_key = foreign_key_for_mva base_association_for_mva
210
224
  "WHERE #{foreign_key} >= $start AND #{foreign_key} <= $end"
211
225
  end
212
226
 
227
+ def query_delta
228
+ foreign_key = foreign_key_for_mva base_association_for_mva
229
+ <<-SQL
230
+ #{foreign_key} IN (SELECT #{quote_column model.primary_key}
231
+ FROM #{model.quoted_table_name}
232
+ WHERE #{@source.index.delta_object.clause(model, true)})
233
+ SQL
234
+ end
235
+
213
236
  def range_query
214
- assoc = association_for_mva
237
+ assoc = base_association_for_mva
215
238
  foreign_key = foreign_key_for_mva assoc
216
239
  "SELECT MIN(#{foreign_key}), MAX(#{foreign_key}) FROM #{quote_table_name assoc.table}"
217
240
  end
@@ -226,23 +249,50 @@ FROM #{quote_table_name assoc.table}
226
249
  quote_with_table assoc.table, assoc.reflection.primary_key_name
227
250
  end
228
251
 
229
- def association_for_mva
252
+ def end_association_for_mva
230
253
  @association_for_mva ||= associations[columns.first].detect { |assoc|
231
254
  assoc.has_column?(columns.first.__name)
232
255
  }
233
256
  end
234
257
 
258
+ def base_association_for_mva
259
+ @first_association_for_mva ||= begin
260
+ assoc = end_association_for_mva
261
+ while !assoc.parent.nil?
262
+ assoc = assoc.parent
263
+ end
264
+
265
+ assoc
266
+ end
267
+ end
268
+
269
+ def association_joins
270
+ joins = []
271
+ assoc = end_association_for_mva
272
+ while assoc != base_association_for_mva
273
+ joins << assoc.to_sql
274
+ assoc = assoc.parent
275
+ end
276
+
277
+ joins.join(' ')
278
+ end
279
+
235
280
  def is_many_ints?
236
281
  concat_ws? && all_ints?
237
282
  end
238
-
283
+
284
+ def is_many_datetimes?
285
+ is_many? && all_datetimes?
286
+ end
287
+
239
288
  def type_from_database
240
289
  klass = @associations.values.flatten.first ?
241
290
  @associations.values.flatten.first.reflection.klass : @model
242
291
 
243
- klass.columns.detect { |col|
292
+ column = klass.columns.detect { |col|
244
293
  @columns.collect { |c| c.__name.to_s }.include? col.name
245
- }.type
294
+ }
295
+ column.nil? ? nil : column.type
246
296
  end
247
297
 
248
298
  def translated_type_from_database
@@ -264,5 +314,16 @@ block:
264
314
  MESSAGE
265
315
  end
266
316
  end
317
+
318
+ def all_of_type?(*column_types)
319
+ @columns.all? { |col|
320
+ klasses = @associations[col].empty? ? [@model] :
321
+ @associations[col].collect { |assoc| assoc.reflection.klass }
322
+ klasses.all? { |klass|
323
+ column = klass.columns.detect { |column| column.name == col.__name.to_s }
324
+ !column.nil? && column_types.include?(column.type)
325
+ }
326
+ }
327
+ end
267
328
  end
268
329
  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
@@ -201,6 +221,24 @@ module ThinkingSphinx
201
221
  @configuration.searchd.query_log = file
202
222
  end
203
223
 
224
+ def client
225
+ client = Riddle::Client.new address, port
226
+ client.max_matches = configuration.searchd.max_matches || 1000
227
+ client
228
+ end
229
+
230
+ def models_by_crc
231
+ @models_by_crc ||= begin
232
+ ThinkingSphinx.indexed_models.inject({}) do |hash, model|
233
+ hash[model.constantize.to_crc32] = model
234
+ Object.subclasses_of(model.constantize).each { |subclass|
235
+ hash[subclass.to_crc32] = subclass.name
236
+ }
237
+ hash
238
+ end
239
+ end
240
+ end
241
+
204
242
  private
205
243
 
206
244
  # Parse the config/sphinx.yml file - if it exists - then use the attribute
@@ -213,10 +251,11 @@ module ThinkingSphinx
213
251
  conf = YAML::load(ERB.new(IO.read(path)).result)[environment]
214
252
 
215
253
  conf.each do |key,value|
216
- self.send("#{key}=", value) if self.methods.include?("#{key}=")
254
+ self.send("#{key}=", value) if self.respond_to?("#{key}=")
217
255
 
218
256
  set_sphinx_setting self.source_options, key, value, SourceOptions
219
257
  set_sphinx_setting self.index_options, key, value, IndexOptions
258
+ set_sphinx_setting self.index_options, key, value, CustomOptions
220
259
  set_sphinx_setting @configuration.searchd, key, value
221
260
  set_sphinx_setting @configuration.indexer, key, value
222
261
  end unless conf.nil?
@@ -233,8 +272,8 @@ module ThinkingSphinx
233
272
  if object.is_a?(Hash)
234
273
  object[key.to_sym] = value if allowed.include?(key.to_s)
235
274
  else
236
- object.send("#{key}=", value) if object.methods.include?("#{key}")
237
- send("#{key}=", value) if self.methods.include?("#{key}")
275
+ object.send("#{key}=", value) if object.respond_to?("#{key}")
276
+ send("#{key}=", value) if self.respond_to?("#{key}")
238
277
  end
239
278
  end
240
279
  end
@@ -18,8 +18,8 @@ module ThinkingSphinx
18
18
  config = ThinkingSphinx::Configuration.instance
19
19
  rotate = ThinkingSphinx.sphinx_running? ? "--rotate" : ""
20
20
 
21
- output = `#{config.bin_path}indexer --config #{config.config_file} #{rotate} #{delta_index_name model}`
22
- output += `#{config.bin_path}indexer --config #{config.config_file} #{rotate} --merge #{core_index_name model} #{delta_index_name model} --merge-dst-range sphinx_deleted 0 0`
21
+ output = `#{config.bin_path}#{config.indexer_binary_name} --config #{config.config_file} #{rotate} #{delta_index_name model}`
22
+ output += `#{config.bin_path}#{config.indexer_binary_name} --config #{config.config_file} #{rotate} --merge #{core_index_name model} #{delta_index_name model} --merge-dst-range sphinx_deleted 0 0`
23
23
  puts output unless ThinkingSphinx.suppress_delta_output?
24
24
 
25
25
  true
@@ -17,7 +17,7 @@ module ThinkingSphinx
17
17
  client = Riddle::Client.new config.address, config.port
18
18
  rotate = ThinkingSphinx.sphinx_running? ? "--rotate" : ""
19
19
 
20
- output = `#{config.bin_path}indexer --config #{config.config_file} #{rotate} #{delta_index_name model}`
20
+ output = `#{config.bin_path}#{config.indexer_binary_name} --config #{config.config_file} #{rotate} #{delta_index_name model}`
21
21
  puts(output) unless ThinkingSphinx.suppress_delta_output?
22
22
 
23
23
  client.update(
@@ -14,7 +14,7 @@ module ThinkingSphinx
14
14
  config = ThinkingSphinx::Configuration.instance
15
15
  client = Riddle::Client.new config.address, config.port
16
16
 
17
- output = `#{config.bin_path}indexer --config #{config.config_file} --rotate #{index}`
17
+ output = `#{config.bin_path}#{config.indexer_binary_name} --config #{config.config_file} --rotate #{index}`
18
18
  puts output unless ThinkingSphinx.suppress_delta_output?
19
19
 
20
20
  true
@@ -8,6 +8,9 @@ module ThinkingSphinx
8
8
  module Deltas
9
9
  class DelayedDelta < ThinkingSphinx::Deltas::DefaultDelta
10
10
  def index(model, instance = nil)
11
+ return true unless ThinkingSphinx.updates_enabled? && ThinkingSphinx.deltas_enabled?
12
+ return true if instance && !toggled(instance)
13
+
11
14
  ThinkingSphinx::Deltas::Job.enqueue(
12
15
  ThinkingSphinx::Deltas::DeltaJob.new(delta_index_name(model)),
13
16
  ThinkingSphinx::Configuration.instance.delayed_job_priority
@@ -1,11 +1,26 @@
1
1
  Capistrano::Configuration.instance(:must_exist).load do
2
2
  namespace :thinking_sphinx do
3
3
  namespace :install do
4
- desc "Install Sphinx by source"
4
+ desc <<-DESC
5
+ Install Sphinx by source
6
+
7
+ If Postgres is available, Sphinx will use it.
8
+
9
+ If the variable :thinking_sphinx_configure_args is set, it will
10
+ be passed to the Sphinx configure script. You can use this to
11
+ install Sphinx in a non-standard location:
12
+
13
+ set :thinking_sphinx_configure_args, "--prefix=$HOME/software"
14
+ DESC
15
+
5
16
  task :sphinx do
6
17
  with_postgres = false
7
- run "which pg_config" do |channel, stream, data|
8
- with_postgres = !(data.nil? || data == "")
18
+ begin
19
+ run "which pg_config" do |channel, stream, data|
20
+ with_postgres = !(data.nil? || data == "")
21
+ end
22
+ rescue Capistrano::CommandError => e
23
+ puts "Continuing despite error: #{e.message}"
9
24
  end
10
25
 
11
26
  args = []
@@ -14,14 +29,15 @@ Capistrano::Configuration.instance(:must_exist).load do
14
29
  args << "--with-pgsql=#{data}"
15
30
  end
16
31
  end
17
-
32
+ args << fetch(:thinking_sphinx_configure_args, '')
33
+
18
34
  commands = <<-CMD
19
35
  wget -q http://www.sphinxsearch.com/downloads/sphinx-0.9.8.1.tar.gz >> sphinx.log
20
36
  tar xzvf sphinx-0.9.8.1.tar.gz
21
37
  cd sphinx-0.9.8.1
22
38
  ./configure #{args.join(" ")}
23
39
  make
24
- sudo make install
40
+ #{try_sudo} make install
25
41
  rm -rf sphinx-0.9.8.1 sphinx-0.9.8.1.tar.gz
26
42
  CMD
27
43
  run commands.split(/\n\s+/).join(" && ")
@@ -29,7 +45,7 @@ Capistrano::Configuration.instance(:must_exist).load do
29
45
 
30
46
  desc "Install Thinking Sphinx as a gem from GitHub"
31
47
  task :ts do
32
- sudo "gem install freelancing-god-thinking-sphinx --source http://gems.github.com"
48
+ run "#{try_sudo} gem install freelancing-god-thinking-sphinx --source http://gems.github.com"
33
49
  end
34
50
  end
35
51
 
@@ -70,12 +86,13 @@ Capistrano::Configuration.instance(:must_exist).load do
70
86
 
71
87
  desc "Add the shared folder for sphinx files for the production environment"
72
88
  task :shared_sphinx_folder, :roles => :web do
73
- sudo "mkdir -p #{shared_path}/db/sphinx/production"
89
+ run "mkdir -p #{shared_path}/db/sphinx/production"
74
90
  end
75
91
 
76
92
  def rake(*tasks)
93
+ rails_env = fetch(:rails_env, "production")
77
94
  tasks.each do |t|
78
- run "cd #{current_path} && rake #{t} RAILS_ENV=production"
95
+ run "cd #{current_path} && rake #{t} RAILS_ENV=#{rails_env}"
79
96
  end
80
97
  end
81
98
  end