thinking-sphinx 1.4.6 → 1.4.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. data/README.textile +6 -1
  2. data/features/searching_by_model.feature +24 -30
  3. data/features/thinking_sphinx/db/.gitignore +1 -0
  4. data/features/thinking_sphinx/db/fixtures/post_keywords.txt +1 -0
  5. data/lib/cucumber/thinking_sphinx/internal_world.rb +26 -26
  6. data/lib/thinking_sphinx.rb +17 -26
  7. data/lib/thinking_sphinx/active_record.rb +69 -74
  8. data/lib/thinking_sphinx/active_record/attribute_updates.rb +11 -10
  9. data/lib/thinking_sphinx/active_record/has_many_association.rb +2 -1
  10. data/lib/thinking_sphinx/adapters/abstract_adapter.rb +11 -11
  11. data/lib/thinking_sphinx/adapters/postgresql_adapter.rb +34 -20
  12. data/lib/thinking_sphinx/association.rb +12 -7
  13. data/lib/thinking_sphinx/attribute.rb +64 -61
  14. data/lib/thinking_sphinx/configuration.rb +32 -36
  15. data/lib/thinking_sphinx/context.rb +3 -2
  16. data/lib/thinking_sphinx/deploy/capistrano.rb +7 -9
  17. data/lib/thinking_sphinx/search.rb +201 -178
  18. data/lib/thinking_sphinx/source/sql.rb +1 -1
  19. data/lib/thinking_sphinx/tasks.rb +21 -19
  20. data/lib/thinking_sphinx/version.rb +3 -0
  21. data/spec/fixtures/data.sql +32 -0
  22. data/spec/fixtures/database.yml.default +3 -0
  23. data/spec/fixtures/models.rb +161 -0
  24. data/spec/fixtures/structure.sql +146 -0
  25. data/spec/spec_helper.rb +57 -0
  26. data/spec/sphinx_helper.rb +61 -0
  27. data/spec/thinking_sphinx/active_record/delta_spec.rb +24 -24
  28. data/spec/thinking_sphinx/active_record/has_many_association_spec.rb +22 -0
  29. data/spec/thinking_sphinx/active_record/scopes_spec.rb +25 -25
  30. data/spec/thinking_sphinx/active_record_spec.rb +110 -109
  31. data/spec/thinking_sphinx/adapters/abstract_adapter_spec.rb +38 -38
  32. data/spec/thinking_sphinx/association_spec.rb +20 -2
  33. data/spec/thinking_sphinx/context_spec.rb +61 -64
  34. data/spec/thinking_sphinx/search_spec.rb +7 -0
  35. data/spec/thinking_sphinx_spec.rb +47 -46
  36. metadata +50 -98
  37. data/VERSION +0 -1
  38. data/tasks/distribution.rb +0 -34
  39. data/tasks/testing.rb +0 -80
@@ -13,16 +13,16 @@ module ThinkingSphinx
13
13
  def self.included(base)
14
14
  base.class_eval do
15
15
  class_inheritable_array :sphinx_indexes, :sphinx_facets
16
-
16
+
17
17
  extend ThinkingSphinx::ActiveRecord::ClassMethods
18
-
18
+
19
19
  class << self
20
20
  attr_accessor :sphinx_index_blocks
21
-
21
+
22
22
  def set_sphinx_primary_key(attribute)
23
23
  @sphinx_primary_key_attribute = attribute
24
24
  end
25
-
25
+
26
26
  def primary_key_for_sphinx
27
27
  @primary_key_for_sphinx ||= begin
28
28
  if custom_primary_key_for_sphinx?
@@ -33,44 +33,44 @@ module ThinkingSphinx
33
33
  end
34
34
  end
35
35
  end
36
-
36
+
37
37
  def custom_primary_key_for_sphinx?
38
38
  (
39
39
  superclass.respond_to?(:custom_primary_key_for_sphinx?) &&
40
40
  superclass.custom_primary_key_for_sphinx?
41
41
  ) || !@sphinx_primary_key_attribute.nil?
42
42
  end
43
-
43
+
44
44
  def clear_primary_key_for_sphinx
45
45
  @primary_key_for_sphinx = nil
46
46
  end
47
-
47
+
48
48
  def sphinx_index_options
49
49
  sphinx_indexes.last.options
50
50
  end
51
-
51
+
52
52
  # Generate a unique CRC value for the model's name, to use to
53
53
  # determine which Sphinx documents belong to which AR records.
54
- #
54
+ #
55
55
  # Really only written for internal use - but hey, if it's useful to
56
56
  # you in some other way, awesome.
57
- #
57
+ #
58
58
  def to_crc32
59
59
  self.name.to_crc32
60
60
  end
61
-
61
+
62
62
  def to_crc32s
63
63
  (subclasses << self).collect { |klass| klass.to_crc32 }
64
64
  end
65
-
65
+
66
66
  def sphinx_database_adapter
67
67
  ThinkingSphinx::AbstractAdapter.detect(self)
68
68
  end
69
-
69
+
70
70
  def sphinx_name
71
71
  self.name.underscore.tr(':/\\', '_')
72
72
  end
73
-
73
+
74
74
  #
75
75
  # The above method to_crc32s is dependant on the subclasses being loaded consistently
76
76
  # After a reset_subclasses is called (during a Dispatcher.cleanup_application in development)
@@ -82,25 +82,25 @@ module ThinkingSphinx
82
82
  reset_subclasses_without_thinking_sphinx
83
83
  ThinkingSphinx.reset_context!
84
84
  end
85
-
85
+
86
86
  alias_method_chain :reset_subclasses, :thinking_sphinx
87
-
87
+
88
88
  private
89
-
89
+
90
90
  def defined_indexes?
91
91
  @defined_indexes
92
92
  end
93
-
93
+
94
94
  def defined_indexes=(value)
95
95
  @defined_indexes = value
96
96
  end
97
-
97
+
98
98
  def sphinx_delta?
99
99
  self.sphinx_indexes.any? { |index| index.delta? }
100
100
  end
101
101
  end
102
102
  end
103
-
103
+
104
104
  ::ActiveRecord::Associations::HasManyAssociation.send(
105
105
  :include, ThinkingSphinx::ActiveRecord::HasManyAssociation
106
106
  )
@@ -108,7 +108,7 @@ module ThinkingSphinx
108
108
  :include, ThinkingSphinx::ActiveRecord::HasManyAssociation
109
109
  )
110
110
  end
111
-
111
+
112
112
  module ClassMethods
113
113
  # Allows creation of indexes for Sphinx. If you don't do this, there
114
114
  # isn't much point trying to search (or using this plugin at all,
@@ -142,7 +142,7 @@ module ThinkingSphinx
142
142
  #
143
143
  # define_index do
144
144
  # # fields ...
145
- #
145
+ #
146
146
  # has created_at, updated_at
147
147
  # end
148
148
  #
@@ -152,58 +152,58 @@ module ThinkingSphinx
152
152
  # define_index do
153
153
  # # fields ...
154
154
  # # attributes ...
155
- #
155
+ #
156
156
  # set_property :delta => true
157
157
  # end
158
158
  #
159
159
  # Check out the more detailed documentation for each of these methods
160
160
  # at ThinkingSphinx::Index::Builder.
161
- #
161
+ #
162
162
  def define_index(name = nil, &block)
163
163
  self.sphinx_index_blocks ||= []
164
164
  self.sphinx_indexes ||= []
165
165
  self.sphinx_facets ||= []
166
-
166
+
167
167
  ThinkingSphinx.context.add_indexed_model self
168
-
168
+
169
169
  if sphinx_index_blocks.empty?
170
170
  before_validation :define_indexes
171
171
  before_destroy :define_indexes
172
172
  end
173
-
173
+
174
174
  self.sphinx_index_blocks << lambda {
175
175
  add_sphinx_index name, &block
176
176
  }
177
-
177
+
178
178
  include ThinkingSphinx::ActiveRecord::Scopes
179
179
  include ThinkingSphinx::SearchMethods
180
180
  end
181
-
181
+
182
182
  def define_indexes
183
183
  superclass.define_indexes unless superclass == ::ActiveRecord::Base
184
-
184
+
185
185
  return if sphinx_index_blocks.nil? ||
186
186
  defined_indexes? ||
187
187
  !ThinkingSphinx.define_indexes?
188
-
188
+
189
189
  sphinx_index_blocks.each do |block|
190
190
  block.call
191
191
  end
192
-
192
+
193
193
  self.defined_indexes = true
194
-
194
+
195
195
  # We want to make sure that if the database doesn't exist, then Thinking
196
196
  # Sphinx doesn't mind when running non-TS tasks (like db:create, db:drop
197
197
  # and db:migrate). It's a bit hacky, but I can't think of a better way.
198
198
  rescue StandardError => err
199
199
  case err.class.name
200
- when "Mysql::Error", "Java::JavaSql::SQLException", "ActiveRecord::StatementInvalid"
200
+ when "Mysql::Error", "Mysql2::Error", "Java::JavaSql::SQLException", "ActiveRecord::StatementInvalid"
201
201
  return
202
202
  else
203
203
  raise err
204
204
  end
205
205
  end
206
-
206
+
207
207
  def add_sphinx_index(name, &block)
208
208
  index = ThinkingSphinx::Index::Builder.generate self, name, &block
209
209
 
@@ -212,50 +212,50 @@ module ThinkingSphinx
212
212
  insert_sphinx_index index
213
213
  end
214
214
  end
215
-
215
+
216
216
  def insert_sphinx_index(index)
217
217
  self.sphinx_indexes << index
218
218
  subclasses.each { |klass| klass.insert_sphinx_index(index) }
219
219
  end
220
-
220
+
221
221
  def has_sphinx_indexes?
222
- sphinx_indexes &&
222
+ sphinx_indexes &&
223
223
  sphinx_index_blocks &&
224
224
  (sphinx_indexes.length > 0 || sphinx_index_blocks.length > 0)
225
225
  end
226
-
226
+
227
227
  def indexed_by_sphinx?
228
228
  sphinx_indexes && sphinx_indexes.length > 0
229
229
  end
230
-
230
+
231
231
  def delta_indexed_by_sphinx?
232
232
  sphinx_indexes && sphinx_indexes.any? { |index| index.delta? }
233
233
  end
234
-
234
+
235
235
  def sphinx_index_names
236
236
  define_indexes
237
237
  sphinx_indexes.collect(&:all_names).flatten
238
238
  end
239
-
239
+
240
240
  def core_index_names
241
241
  define_indexes
242
242
  sphinx_indexes.collect(&:core_name)
243
243
  end
244
-
244
+
245
245
  def delta_index_names
246
246
  define_indexes
247
247
  sphinx_indexes.select(&:delta?).collect(&:delta_name)
248
248
  end
249
-
249
+
250
250
  def to_riddle
251
251
  define_indexes
252
252
  sphinx_database_adapter.setup
253
-
253
+
254
254
  local_sphinx_indexes.collect { |index|
255
255
  index.to_riddle(sphinx_offset)
256
256
  }.flatten
257
257
  end
258
-
258
+
259
259
  def source_of_sphinx_index
260
260
  define_indexes
261
261
  possible_models = self.sphinx_indexes.collect { |index| index.model }
@@ -268,23 +268,24 @@ module ThinkingSphinx
268
268
 
269
269
  return parent
270
270
  end
271
-
271
+
272
272
  def delete_in_index(index, document_id)
273
273
  return unless ThinkingSphinx.sphinx_running? &&
274
274
  search_for_id(document_id, index)
275
-
275
+
276
276
  ThinkingSphinx::Configuration.instance.client.update(
277
277
  index, ['sphinx_deleted'], {document_id => [1]}
278
278
  )
279
- rescue Riddle::ConnectionError, ThinkingSphinx::SphinxError
279
+ rescue Riddle::ConnectionError, Riddle::ResponseError,
280
+ ThinkingSphinx::SphinxError, Errno::ETIMEDOUT
280
281
  # Not the end of the world if Sphinx isn't running.
281
282
  end
282
-
283
+
283
284
  def sphinx_offset
284
285
  ThinkingSphinx.context.superclass_indexed_models.
285
286
  index eldest_indexed_ancestor
286
287
  end
287
-
288
+
288
289
  # Temporarily disable delta indexing inside a block, then perform a
289
290
  # single rebuild of index at the end.
290
291
  #
@@ -308,74 +309,68 @@ module ThinkingSphinx
308
309
  yield
309
310
  ensure
310
311
  ThinkingSphinx.deltas_suspended = original_setting
311
- self.index_delta if reindex_after
312
+ self.index_delta if reindex_after && !original_setting
312
313
  end
313
314
  end
314
-
315
+
315
316
  private
316
-
317
+
317
318
  def local_sphinx_indexes
318
319
  sphinx_indexes.select { |index|
319
320
  index.model == self
320
321
  }
321
322
  end
322
-
323
+
323
324
  def add_sphinx_callbacks_and_extend(delta = false)
324
325
  unless indexed_by_sphinx?
325
326
  after_destroy :toggle_deleted
326
-
327
+
327
328
  include ThinkingSphinx::ActiveRecord::AttributeUpdates
328
329
  end
329
-
330
+
330
331
  if delta && !delta_indexed_by_sphinx?
331
332
  include ThinkingSphinx::ActiveRecord::Delta
332
-
333
+
333
334
  before_save :toggle_delta
334
335
  after_commit :index_delta
335
336
  end
336
337
  end
337
-
338
+
338
339
  def eldest_indexed_ancestor
339
340
  ancestors.reverse.detect { |ancestor|
340
341
  ThinkingSphinx.context.indexed_models.include?(ancestor.name)
341
342
  }.name
342
343
  end
343
344
  end
344
-
345
+
345
346
  attr_accessor :excerpts
346
347
  attr_accessor :sphinx_attributes
347
348
  attr_accessor :matching_fields
348
-
349
- def in_index?(index)
350
- self.class.search_for_id self.sphinx_document_id, index
351
- rescue Riddle::ResponseError
352
- true
353
- end
354
-
349
+
355
350
  def toggle_deleted
356
351
  return unless ThinkingSphinx.updates_enabled?
357
-
352
+
358
353
  self.class.core_index_names.each do |index_name|
359
354
  self.class.delete_in_index index_name, self.sphinx_document_id
360
355
  end
361
356
  self.class.delta_index_names.each do |index_name|
362
357
  self.class.delete_in_index index_name, self.sphinx_document_id
363
358
  end if self.class.delta_indexed_by_sphinx? && toggled_delta?
364
-
359
+
365
360
  rescue ::ThinkingSphinx::ConnectionError
366
361
  # nothing
367
362
  end
368
-
363
+
369
364
  # Returns the unique integer id for the object. This method uses the
370
365
  # attribute hash to get around ActiveRecord always mapping the #id method
371
366
  # to whatever the real primary key is (which may be a unique string hash).
372
- #
367
+ #
373
368
  # @return [Integer] Unique record id for the purposes of Sphinx.
374
- #
369
+ #
375
370
  def primary_key_for_sphinx
376
371
  read_attribute(self.class.primary_key_for_sphinx)
377
372
  end
378
-
373
+
379
374
  def sphinx_document_id
380
375
  primary_key_for_sphinx * ThinkingSphinx.context.indexed_models.size +
381
376
  self.class.sphinx_offset
@@ -386,7 +381,7 @@ module ThinkingSphinx
386
381
  def sphinx_index_name(suffix)
387
382
  "#{self.class.source_of_sphinx_index.name.underscore.tr(':/\\', '_')}_#{suffix}"
388
383
  end
389
-
384
+
390
385
  def define_indexes
391
386
  self.class.define_indexes
392
387
  end
@@ -6,45 +6,46 @@ module ThinkingSphinx
6
6
  after_commit :update_attribute_values
7
7
  end
8
8
  end
9
-
9
+
10
10
  private
11
-
11
+
12
12
  def update_attribute_values
13
13
  return true unless ThinkingSphinx.updates_enabled? &&
14
14
  ThinkingSphinx.sphinx_running?
15
-
15
+
16
16
  self.class.sphinx_indexes.each do |index|
17
17
  attribute_pairs = attribute_values_for_index(index)
18
18
  attribute_names = attribute_pairs.keys
19
19
  attribute_values = attribute_names.collect { |key|
20
20
  attribute_pairs[key]
21
21
  }
22
-
22
+
23
23
  update_index index.core_name, attribute_names, attribute_values
24
24
  next unless index.delta?
25
25
  update_index index.delta_name, attribute_names, attribute_values
26
26
  end
27
-
27
+
28
28
  true
29
29
  end
30
-
30
+
31
31
  def updatable_attributes(index)
32
32
  index.attributes.select { |attrib| attrib.updatable? }
33
33
  end
34
-
34
+
35
35
  def attribute_values_for_index(index)
36
36
  updatable_attributes(index).inject({}) { |hash, attrib|
37
37
  hash[attrib.unique_name.to_s] = attrib.live_value self
38
38
  hash
39
39
  }
40
40
  end
41
-
41
+
42
42
  def update_index(index_name, attribute_names, attribute_values)
43
43
  config = ThinkingSphinx::Configuration.instance
44
44
  config.client.update index_name, attribute_names, {
45
45
  sphinx_document_id => attribute_values
46
- } if in_index?(index_name)
47
- rescue Riddle::ConnectionError, ThinkingSphinx::SphinxError
46
+ }
47
+ rescue Riddle::ConnectionError, Riddle::ResponseError,
48
+ ThinkingSphinx::SphinxError, Errno::ETIMEDOUT
48
49
  # Not the end of the world if Sphinx isn't running.
49
50
  end
50
51
  end
@@ -20,7 +20,8 @@ module ThinkingSphinx
20
20
  (@reflection.klass.sphinx_indexes || []).each do |index|
21
21
  attribute = index.attributes.detect { |attrib|
22
22
  attrib.columns.length == 1 &&
23
- attrib.columns.first.__name == foreign_key.to_sym
23
+ attrib.columns.first.__name == foreign_key.to_sym ||
24
+ attrib.alias == foreign_key.to_sym
24
25
  }
25
26
  return attribute unless attribute.nil?
26
27
  end