thinking-sphinx 1.4.6 → 1.4.7

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 (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