pixeltrix-thinking-sphinx 1.1.5 → 1.2.1

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 (76) hide show
  1. data/README.textile +147 -0
  2. data/lib/thinking_sphinx/active_record/attribute_updates.rb +48 -0
  3. data/lib/thinking_sphinx/active_record/delta.rb +14 -1
  4. data/lib/thinking_sphinx/active_record/scopes.rb +37 -0
  5. data/lib/thinking_sphinx/active_record.rb +46 -12
  6. data/lib/thinking_sphinx/adapters/abstract_adapter.rb +9 -1
  7. data/lib/thinking_sphinx/adapters/mysql_adapter.rb +3 -2
  8. data/lib/thinking_sphinx/adapters/postgresql_adapter.rb +12 -5
  9. data/lib/thinking_sphinx/association.rb +20 -0
  10. data/lib/thinking_sphinx/attribute.rb +187 -116
  11. data/lib/thinking_sphinx/class_facet.rb +15 -0
  12. data/lib/thinking_sphinx/configuration.rb +46 -14
  13. data/lib/thinking_sphinx/core/string.rb +3 -10
  14. data/lib/thinking_sphinx/deltas/datetime_delta.rb +3 -3
  15. data/lib/thinking_sphinx/deltas/default_delta.rb +9 -6
  16. data/lib/thinking_sphinx/deltas/delayed_delta/delta_job.rb +1 -1
  17. data/lib/thinking_sphinx/deltas/delayed_delta.rb +4 -2
  18. data/lib/thinking_sphinx/deltas.rb +14 -6
  19. data/lib/thinking_sphinx/deploy/capistrano.rb +98 -0
  20. data/lib/thinking_sphinx/excerpter.rb +22 -0
  21. data/lib/thinking_sphinx/facet.rb +68 -18
  22. data/lib/thinking_sphinx/facet_search.rb +134 -0
  23. data/lib/thinking_sphinx/field.rb +7 -97
  24. data/lib/thinking_sphinx/index/builder.rb +255 -201
  25. data/lib/thinking_sphinx/index.rb +28 -343
  26. data/lib/thinking_sphinx/property.rb +160 -0
  27. data/lib/thinking_sphinx/rails_additions.rb +7 -4
  28. data/lib/thinking_sphinx/search.rb +593 -587
  29. data/lib/thinking_sphinx/search_methods.rb +421 -0
  30. data/lib/thinking_sphinx/source/internal_properties.rb +46 -0
  31. data/lib/thinking_sphinx/source/sql.rb +128 -0
  32. data/lib/thinking_sphinx/source.rb +150 -0
  33. data/lib/thinking_sphinx/tasks.rb +45 -11
  34. data/lib/thinking_sphinx.rb +88 -14
  35. data/rails/init.rb +14 -0
  36. data/spec/{unit → lib}/thinking_sphinx/active_record/delta_spec.rb +7 -7
  37. data/spec/{unit → lib}/thinking_sphinx/active_record/has_many_association_spec.rb +0 -0
  38. data/spec/lib/thinking_sphinx/active_record/scopes_spec.rb +92 -0
  39. data/spec/{unit → lib}/thinking_sphinx/active_record_spec.rb +115 -42
  40. data/spec/{unit → lib}/thinking_sphinx/association_spec.rb +4 -5
  41. data/spec/lib/thinking_sphinx/attribute_spec.rb +465 -0
  42. data/spec/{unit → lib}/thinking_sphinx/configuration_spec.rb +118 -7
  43. data/spec/{unit → lib}/thinking_sphinx/core/string_spec.rb +0 -0
  44. data/spec/lib/thinking_sphinx/excerpter_spec.rb +49 -0
  45. data/spec/lib/thinking_sphinx/facet_search_spec.rb +176 -0
  46. data/spec/lib/thinking_sphinx/facet_spec.rb +302 -0
  47. data/spec/{unit → lib}/thinking_sphinx/field_spec.rb +26 -17
  48. data/spec/lib/thinking_sphinx/index/builder_spec.rb +355 -0
  49. data/spec/{unit → lib}/thinking_sphinx/index/faux_column_spec.rb +0 -0
  50. data/spec/{unit → lib}/thinking_sphinx/index_spec.rb +3 -12
  51. data/spec/lib/thinking_sphinx/rails_additions_spec.rb +191 -0
  52. data/spec/lib/thinking_sphinx/search_methods_spec.rb +152 -0
  53. data/spec/lib/thinking_sphinx/search_spec.rb +887 -0
  54. data/spec/lib/thinking_sphinx/source_spec.rb +217 -0
  55. data/spec/{unit → lib}/thinking_sphinx_spec.rb +30 -8
  56. data/tasks/distribution.rb +20 -1
  57. data/tasks/testing.rb +7 -15
  58. data/vendor/after_commit/init.rb +3 -0
  59. data/vendor/after_commit/lib/after_commit/active_record.rb +27 -4
  60. data/vendor/after_commit/lib/after_commit/connection_adapters.rb +1 -1
  61. data/vendor/after_commit/lib/after_commit.rb +4 -1
  62. data/vendor/riddle/lib/riddle/client/message.rb +4 -3
  63. data/vendor/riddle/lib/riddle/client.rb +3 -0
  64. data/vendor/riddle/lib/riddle/configuration/section.rb +8 -2
  65. data/vendor/riddle/lib/riddle/controller.rb +1 -1
  66. data/vendor/riddle/lib/riddle.rb +1 -1
  67. metadata +75 -39
  68. data/README +0 -107
  69. data/lib/thinking_sphinx/active_record/search.rb +0 -57
  70. data/lib/thinking_sphinx/collection.rb +0 -142
  71. data/lib/thinking_sphinx/facet_collection.rb +0 -44
  72. data/spec/unit/thinking_sphinx/active_record/search_spec.rb +0 -107
  73. data/spec/unit/thinking_sphinx/attribute_spec.rb +0 -212
  74. data/spec/unit/thinking_sphinx/collection_spec.rb +0 -14
  75. data/spec/unit/thinking_sphinx/index/builder_spec.rb +0 -5
  76. data/spec/unit/thinking_sphinx/search_spec.rb +0 -59
@@ -8,15 +8,16 @@ module ThinkingSphinx
8
8
  # generate SQL statements, you'll need to set the base model, and all the
9
9
  # associations. Which can get messy. Use Index.link!, it really helps.
10
10
  #
11
- class Attribute
12
- attr_accessor :alias, :columns, :associations, :model, :faceted
11
+ class Attribute < ThinkingSphinx::Property
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.
16
16
  #
17
17
  # Valid options are:
18
- # - :as => :alias_name
19
- # - :type => :attribute_type
18
+ # - :as => :alias_name
19
+ # - :type => :attribute_type
20
+ # - :source => :field, :query, :ranged_query
20
21
  #
21
22
  # Alias is only required in three circumstances: when there's
22
23
  # another attribute or field with the same name, when the column name is
@@ -28,6 +29,13 @@ module ThinkingSphinx
28
29
  # can't be figured out by the column - ie: when not actually using a
29
30
  # database column as your source.
30
31
  #
32
+ # Source is only used for multi-value attributes (MVA). By default this will
33
+ # use a left-join and a group_concat to obtain the values. For better performance
34
+ # during indexing it can be beneficial to let Sphinx use a separate query to retrieve
35
+ # all document,value-pairs.
36
+ # Either :query or :ranged_query will enable this feature, where :ranged_query will cause
37
+ # the query to be executed incremental.
38
+ #
31
39
  # Example usage:
32
40
  #
33
41
  # Attribute.new(
@@ -40,6 +48,12 @@ module ThinkingSphinx
40
48
  # )
41
49
  #
42
50
  # Attribute.new(
51
+ # Column.new(:posts, :id),
52
+ # :as => :post_ids,
53
+ # :source => :ranged_query
54
+ # )
55
+ #
56
+ # Attribute.new(
43
57
  # [Column.new(:pages, :id), Column.new(:articles, :id)],
44
58
  # :as => :content_ids
45
59
  # )
@@ -53,15 +67,17 @@ module ThinkingSphinx
53
67
  # If you're creating attributes for latitude and longitude, don't forget
54
68
  # that Sphinx expects these values to be in radians.
55
69
  #
56
- def initialize(columns, options = {})
57
- @columns = Array(columns)
58
- @associations = {}
70
+ def initialize(source, columns, options = {})
71
+ super
59
72
 
60
- raise "Cannot define a field with no columns. Maybe you are trying to index a field with a reserved name (id, name). You can fix this error by using a symbol rather than a bare name (:id instead of id)." if @columns.empty? || @columns.any? { |column| !column.respond_to?(:__stack) }
73
+ @type = options[:type]
74
+ @query_source = options[:source]
75
+ @crc = options[:crc]
61
76
 
62
- @alias = options[:as]
63
- @type = options[:type]
64
- @faceted = options[:facet]
77
+ @type ||= :multi unless @query_source.nil?
78
+ @type = :integer if @type == :string && @crc
79
+
80
+ source.attributes << self
65
81
  end
66
82
 
67
83
  # Get the part of the SELECT clause related to this attribute. Don't forget
@@ -71,37 +87,30 @@ module ThinkingSphinx
71
87
  # datetimes to timestamps, as needed.
72
88
  #
73
89
  def to_select_sql
90
+ return nil unless include_as_association?
91
+
92
+ separator = all_ints? || all_datetimes? || @crc ? ',' : ' '
93
+
74
94
  clause = @columns.collect { |column|
75
- column_with_prefix(column)
95
+ part = column_with_prefix(column)
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
76
104
  }.join(', ')
77
105
 
78
- separator = all_ints? ? ',' : ' '
79
-
106
+ # clause = adapter.cast_to_datetime(clause) if type == :datetime
107
+ clause = adapter.crc(clause) if @crc
80
108
  clause = adapter.concatenate(clause, separator) if concat_ws?
81
109
  clause = adapter.group_concatenate(clause, separator) if is_many?
82
- clause = adapter.cast_to_datetime(clause) if type == :datetime
83
- clause = adapter.convert_nulls(clause) if type == :string
84
110
 
85
111
  "#{clause} AS #{quote_column(unique_name)}"
86
112
  end
87
113
 
88
- # Get the part of the GROUP BY clause related to this attribute - if one is
89
- # needed. If not, all you'll get back is nil. The latter will happen if
90
- # there isn't actually a real column to get data from, or if there's
91
- # multiple data values (read: a has_many or has_and_belongs_to_many
92
- # association).
93
- #
94
- def to_group_sql
95
- case
96
- when is_many?, is_string?, ThinkingSphinx.use_group_by_shortcut?
97
- nil
98
- else
99
- @columns.collect { |column|
100
- column_with_prefix(column)
101
- }
102
- end
103
- end
104
-
105
114
  def type_to_config
106
115
  {
107
116
  :multi => :sql_attr_multi,
@@ -113,127 +122,177 @@ module ThinkingSphinx
113
122
  }[type]
114
123
  end
115
124
 
116
- def config_value
117
- if type == :multi
118
- "uint #{unique_name} from field"
119
- else
120
- unique_name
121
- end
125
+ def include_as_association?
126
+ ! (type == :multi && (query_source == :query || query_source == :ranged_query))
122
127
  end
123
128
 
124
- # Returns the unique name of the attribute - which is either the alias of
125
- # the attribute, or the name of the only column - if there is only one. If
126
- # there isn't, there should be an alias. Else things probably won't work.
127
- # Consider yourself warned.
129
+ # Returns the configuration value that should be used for
130
+ # the attribute.
131
+ # Special case is the multi-valued attribute that needs some
132
+ # extra configuration.
128
133
  #
129
- def unique_name
130
- if @columns.length == 1
131
- @alias || @columns.first.__name
134
+ def config_value(offset = nil, delta = false)
135
+ if type == :multi
136
+ multi_config = include_as_association? ? "field" :
137
+ source_value(offset, delta).gsub(/\s+/m, " ").strip
138
+ "uint #{unique_name} from #{multi_config}"
132
139
  else
133
- @alias
140
+ unique_name
134
141
  end
135
142
  end
136
-
143
+
137
144
  # Returns the type of the column. If that's not already set, it returns
138
145
  # :multi if there's the possibility of more than one value, :string if
139
146
  # there's more than one association, otherwise it figures out what the
140
147
  # actual column's datatype is and returns that.
148
+ #
141
149
  def type
142
- @type ||= case
143
- when is_many?, is_many_ints?
144
- :multi
145
- when @associations.values.flatten.length > 1
146
- :string
150
+ @type ||= begin
151
+ base_type = case
152
+ when is_many_datetimes?
153
+ :datetime
154
+ when is_many?, is_many_ints?
155
+ :multi
156
+ when @associations.values.flatten.length > 1
157
+ :string
158
+ else
159
+ translated_type_from_database
160
+ end
161
+
162
+ if base_type == :string && @crc
163
+ :integer
164
+ else
165
+ @crc = false
166
+ base_type
167
+ end
168
+ end
169
+ end
170
+
171
+ def updatable?
172
+ [:integer, :datetime, :boolean].include?(type) && !is_string?
173
+ end
174
+
175
+ def live_value(instance)
176
+ object = instance
177
+ column = @columns.first
178
+ column.__stack.each { |method| object = object.send(method) }
179
+ object.send(column.__name)
180
+ end
181
+
182
+ def all_ints?
183
+ all_of_type?(:integer)
184
+ end
185
+
186
+ def all_datetimes?
187
+ all_of_type?(:datetime, :date, :timestamp)
188
+ end
189
+
190
+ private
191
+
192
+ def source_value(offset, delta)
193
+ if is_string?
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}"
147
203
  else
148
- translated_type_from_database
204
+ query += "WHERE #{query_delta.strip}" if delta
205
+ "query; #{query}"
149
206
  end
150
207
  end
151
208
 
152
- def to_facet
153
- return nil unless @faceted
209
+ def query(offset)
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?
154
213
 
155
- ThinkingSphinx::Facet.new(self)
214
+ <<-SQL
215
+ SELECT #{foreign_key_for_mva base_assoc}
216
+ #{ThinkingSphinx.unique_id_expression(offset)} AS #{quote_column('id')},
217
+ #{primary_key_for_mva(end_assoc)} AS #{quote_column(unique_name)}
218
+ FROM #{quote_table_name base_assoc.table} #{association_joins}
219
+ SQL
156
220
  end
157
221
 
158
- private
222
+ def query_clause
223
+ foreign_key = foreign_key_for_mva base_association_for_mva
224
+ "WHERE #{foreign_key} >= $start AND #{foreign_key} <= $end"
225
+ end
159
226
 
160
- def adapter
161
- @adapter ||= @model.sphinx_database_adapter
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
162
234
  end
163
235
 
164
- def quote_column(column)
165
- @model.connection.quote_column_name(column)
236
+ def range_query
237
+ assoc = base_association_for_mva
238
+ foreign_key = foreign_key_for_mva assoc
239
+ "SELECT MIN(#{foreign_key}), MAX(#{foreign_key}) FROM #{quote_table_name assoc.table}"
166
240
  end
167
241
 
168
- # Indication of whether the columns should be concatenated with a space
169
- # between each value. True if there's either multiple sources or multiple
170
- # associations.
171
- #
172
- def concat_ws?
173
- multiple_associations? || @columns.length > 1
242
+ def primary_key_for_mva(assoc)
243
+ quote_with_table(
244
+ assoc.table, assoc.primary_key_from_reflection || columns.first.__name
245
+ )
174
246
  end
175
-
176
- # Checks whether any column requires multiple associations (which only
177
- # happens for polymorphic situations).
178
- #
179
- def multiple_associations?
180
- associations.any? { |col,assocs| assocs.length > 1 }
247
+
248
+ def foreign_key_for_mva(assoc)
249
+ quote_with_table assoc.table, assoc.reflection.primary_key_name
181
250
  end
182
251
 
183
- # Builds a column reference tied to the appropriate associations. This
184
- # dives into the associations hash and their corresponding joins to
185
- # figure out how to correctly reference a column in SQL.
186
- #
187
- def column_with_prefix(column)
188
- if column.is_string?
189
- column.__name
190
- elsif associations[column].empty?
191
- "#{@model.quoted_table_name}.#{quote_column(column.__name)}"
192
- else
193
- associations[column].collect { |assoc|
194
- assoc.has_column?(column.__name) ?
195
- "#{@model.connection.quote_table_name(assoc.join.aliased_table_name)}" +
196
- ".#{quote_column(column.__name)}" :
197
- nil
198
- }.compact.join(', ')
252
+ def end_association_for_mva
253
+ @association_for_mva ||= associations[columns.first].detect { |assoc|
254
+ assoc.has_column?(columns.first.__name)
255
+ }
256
+ end
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
199
266
  end
200
267
  end
201
268
 
202
- # Could there be more than one value related to the parent record? If so,
203
- # then this will return true. If not, false. It's that simple.
204
- #
205
- def is_many?
206
- associations.values.flatten.any? { |assoc| assoc.is_many? }
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(' ')
207
278
  end
208
279
 
209
280
  def is_many_ints?
210
281
  concat_ws? && all_ints?
211
282
  end
212
283
 
213
- # Returns true if any of the columns are string values, instead of database
214
- # column references.
215
- def is_string?
216
- columns.all? { |col| col.is_string? }
284
+ def is_many_datetimes?
285
+ is_many? && all_datetimes?
217
286
  end
218
-
219
- def all_ints?
220
- @columns.all? { |col|
221
- klasses = @associations[col].empty? ? [@model] :
222
- @associations[col].collect { |assoc| assoc.reflection.klass }
223
- klasses.all? { |klass|
224
- column = klass.columns.detect { |column| column.name == col.__name.to_s }
225
- !column.nil? && column.type == :integer
226
- }
227
- }
228
- end
229
-
287
+
230
288
  def type_from_database
231
289
  klass = @associations.values.flatten.first ?
232
290
  @associations.values.flatten.first.reflection.klass : @model
233
291
 
234
- klass.columns.detect { |col|
292
+ column = klass.columns.detect { |col|
235
293
  @columns.collect { |c| c.__name.to_s }.include? col.name
236
- }.type
294
+ }
295
+ column.nil? ? nil : column.type
237
296
  end
238
297
 
239
298
  def translated_type_from_database
@@ -247,12 +306,24 @@ module ThinkingSphinx
247
306
  else
248
307
  raise <<-MESSAGE
249
308
 
250
- Cannot automatically map column type #{type_from_db} to an equivalent Sphinx
251
- type (integer, float, boolean, datetime, string as ordinal). You could try to
252
- explicitly convert the column's value in your define_index block:
309
+ Cannot automatically map attribute #{unique_name} in #{@model.name} to an
310
+ equivalent Sphinx type (integer, float, boolean, datetime, string as ordinal).
311
+ You could try to explicitly convert the column's value in your define_index
312
+ block:
253
313
  has "CAST(column AS INT)", :type => :integer, :as => :column
254
314
  MESSAGE
255
315
  end
256
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
257
328
  end
258
329
  end
@@ -0,0 +1,15 @@
1
+ module ThinkingSphinx
2
+ class ClassFacet < ThinkingSphinx::Facet
3
+ def name
4
+ :class
5
+ end
6
+
7
+ def attribute_name
8
+ "class_crc"
9
+ end
10
+
11
+ def value(object, attribute_value)
12
+ object.class.name
13
+ end
14
+ end
15
+ end
@@ -19,20 +19,25 @@ 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
31
33
  # fashion to database.yml - using the following keys: config_file,
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
- # max_matches, # morphology, charset_type, charset_table, ignore_chars,
35
- # html_strip, # html_remove_elements. I think you've got the idea.
36
+ # max_matches, morphology, charset_type, charset_table, ignore_chars,
37
+ # html_strip, html_remove_elements, delayed_job_priority,
38
+ # searchd_binary_name, indexer_binary_name.
39
+ #
40
+ # I think you've got the idea.
36
41
  #
37
42
  # Each setting in the YAML file is optional - so only put in the ones you
38
43
  # want to change.
@@ -52,10 +57,13 @@ module ThinkingSphinx
52
57
  min_infix_len min_prefix_len min_word_len mlock morphology ngram_chars
53
58
  ngram_len phrase_boundary phrase_boundary_step preopen stopwords
54
59
  wordforms )
60
+
61
+ CustomOptions = %w( disable_range )
55
62
 
56
63
  attr_accessor :config_file, :searchd_log_file, :query_log_file,
57
64
  :pid_file, :searchd_file_path, :address, :port, :allow_star,
58
- :database_yml_file, :app_root, :bin_path, :model_directories
65
+ :database_yml_file, :app_root, :bin_path, :model_directories,
66
+ :delayed_job_priority, :searchd_binary_name, :indexer_binary_name
59
67
 
60
68
  attr_accessor :source_options, :index_options
61
69
 
@@ -68,10 +76,19 @@ module ThinkingSphinx
68
76
  self.reset
69
77
  end
70
78
 
71
- def reset
72
- self.app_root = RAILS_ROOT if defined?(RAILS_ROOT)
73
- self.app_root = Merb.root if defined?(Merb)
74
- 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
75
92
 
76
93
  @configuration = Riddle::Configuration.new
77
94
  @configuration.searchd.address = "127.0.0.1"
@@ -85,13 +102,17 @@ module ThinkingSphinx
85
102
  self.searchd_file_path = "#{self.app_root}/db/sphinx/#{environment}"
86
103
  self.allow_star = false
87
104
  self.bin_path = ""
88
- self.model_directories = ["#{app_root}/app/models/"]
105
+ self.model_directories = ["#{app_root}/app/models/"] +
106
+ Dir.glob("#{app_root}/vendor/plugins/*/app/models/")
107
+ self.delayed_job_priority = 0
89
108
 
90
109
  self.source_options = {}
91
110
  self.index_options = {
92
- :charset_type => "utf-8",
93
- :morphology => "stem_en"
111
+ :charset_type => "utf-8"
94
112
  }
113
+
114
+ self.searchd_binary_name = "searchd"
115
+ self.indexer_binary_name = "indexer"
95
116
 
96
117
  parse_config
97
118
 
@@ -136,6 +157,8 @@ module ThinkingSphinx
136
157
  # messy dependencies issues).
137
158
  #
138
159
  def load_models
160
+ return if defined?(Rails) && Rails.configuration.cache_classes
161
+
139
162
  self.model_directories.each do |base|
140
163
  Dir["#{base}**/*.rb"].each do |file|
141
164
  model_name = file.gsub(/^#{base}([\w_\/\\]+)\.rb/, '\1')
@@ -151,6 +174,8 @@ module ThinkingSphinx
151
174
  model_name.gsub!(/.*[\/\\]/, '').nil? ? next : retry
152
175
  rescue NameError
153
176
  next
177
+ rescue StandardError
178
+ puts "Warning: Error loading #{file}"
154
179
  end
155
180
  end
156
181
  end
@@ -196,6 +221,12 @@ module ThinkingSphinx
196
221
  @configuration.searchd.query_log = file
197
222
  end
198
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
+
199
230
  private
200
231
 
201
232
  # Parse the config/sphinx.yml file - if it exists - then use the attribute
@@ -208,10 +239,11 @@ module ThinkingSphinx
208
239
  conf = YAML::load(ERB.new(IO.read(path)).result)[environment]
209
240
 
210
241
  conf.each do |key,value|
211
- self.send("#{key}=", value) if self.methods.include?("#{key}=")
242
+ self.send("#{key}=", value) if self.respond_to?("#{key}=")
212
243
 
213
244
  set_sphinx_setting self.source_options, key, value, SourceOptions
214
245
  set_sphinx_setting self.index_options, key, value, IndexOptions
246
+ set_sphinx_setting self.index_options, key, value, CustomOptions
215
247
  set_sphinx_setting @configuration.searchd, key, value
216
248
  set_sphinx_setting @configuration.indexer, key, value
217
249
  end unless conf.nil?
@@ -228,8 +260,8 @@ module ThinkingSphinx
228
260
  if object.is_a?(Hash)
229
261
  object[key.to_sym] = value if allowed.include?(key.to_s)
230
262
  else
231
- object.send("#{key}=", value) if object.methods.include?("#{key}")
232
- send("#{key}=", value) if self.methods.include?("#{key}")
263
+ object.send("#{key}=", value) if object.respond_to?("#{key}")
264
+ send("#{key}=", value) if self.respond_to?("#{key}")
233
265
  end
234
266
  end
235
267
  end
@@ -1,18 +1,11 @@
1
+ require 'zlib'
2
+
1
3
  module ThinkingSphinx
2
4
  module Core
3
5
  module String
4
-
5
6
  def to_crc32
6
- result = 0xFFFFFFFF
7
- self.each_byte do |byte|
8
- result ^= byte
9
- 8.times do
10
- result = (result >> 1) ^ (0xEDB88320 * (result & 1))
11
- end
12
- end
13
- result ^ 0xFFFFFFFF
7
+ Zlib.crc32 self
14
8
  end
15
-
16
9
  end
17
10
  end
18
11
  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
@@ -39,7 +39,7 @@ module ThinkingSphinx
39
39
 
40
40
  def clause(model, toggled)
41
41
  if toggled
42
- "#{model.quoted_table_name}.#{@index.quote_column(@column.to_s)}" +
42
+ "#{model.quoted_table_name}.#{model.connection.quote_column_name(@column.to_s)}" +
43
43
  " > #{adapter.time_difference(@threshold)}"
44
44
  else
45
45
  nil
@@ -11,18 +11,20 @@ module ThinkingSphinx
11
11
  def index(model, instance = nil)
12
12
  return true unless ThinkingSphinx.updates_enabled? &&
13
13
  ThinkingSphinx.deltas_enabled?
14
+ return true if instance && !toggled(instance)
14
15
 
15
16
  config = ThinkingSphinx::Configuration.instance
16
17
  client = Riddle::Client.new config.address, config.port
18
+ rotate = ThinkingSphinx.sphinx_running? ? "--rotate" : ""
19
+
20
+ output = `#{config.bin_path}#{config.indexer_binary_name} --config #{config.config_file} #{rotate} #{delta_index_name model}`
21
+ puts(output) unless ThinkingSphinx.suppress_delta_output?
17
22
 
18
23
  client.update(
19
24
  core_index_name(model),
20
25
  ['sphinx_deleted'],
21
26
  {instance.sphinx_document_id => [1]}
22
- ) if instance && ThinkingSphinx.sphinx_running? && instance.in_core_index?
23
-
24
- output = `#{config.bin_path}indexer --config #{config.config_file} --rotate #{delta_index_name model}`
25
- puts output unless ThinkingSphinx.suppress_delta_output?
27
+ ) if instance && ThinkingSphinx.sphinx_running? && instance.in_both_indexes?
26
28
 
27
29
  true
28
30
  end
@@ -37,11 +39,12 @@ module ThinkingSphinx
37
39
 
38
40
  def reset_query(model)
39
41
  "UPDATE #{model.quoted_table_name} SET " +
40
- "#{@index.quote_column(@column.to_s)} = #{adapter.boolean(false)}"
42
+ "#{model.connection.quote_column_name(@column.to_s)} = #{adapter.boolean(false)} " +
43
+ "WHERE #{model.connection.quote_column_name(@column.to_s)} = #{adapter.boolean(true)}"
41
44
  end
42
45
 
43
46
  def clause(model, toggled)
44
- "#{model.quoted_table_name}.#{@index.quote_column(@column.to_s)}" +
47
+ "#{model.quoted_table_name}.#{model.connection.quote_column_name(@column.to_s)}" +
45
48
  " = #{adapter.boolean(toggled)}"
46
49
  end
47
50
 
@@ -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
@@ -9,13 +9,15 @@ module ThinkingSphinx
9
9
  class DelayedDelta < ThinkingSphinx::Deltas::DefaultDelta
10
10
  def index(model, instance = nil)
11
11
  ThinkingSphinx::Deltas::Job.enqueue(
12
- ThinkingSphinx::Deltas::DeltaJob.new(delta_index_name(model))
12
+ ThinkingSphinx::Deltas::DeltaJob.new(delta_index_name(model)),
13
+ ThinkingSphinx::Configuration.instance.delayed_job_priority
13
14
  )
14
15
 
15
16
  Delayed::Job.enqueue(
16
17
  ThinkingSphinx::Deltas::FlagAsDeletedJob.new(
17
18
  core_index_name(model), instance.sphinx_document_id
18
- )
19
+ ),
20
+ ThinkingSphinx::Configuration.instance.delayed_job_priority
19
21
  ) if instance
20
22
 
21
23
  true