thinking-sphinx 1.5.0 → 2.0.0.rc1

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 (104) hide show
  1. data/README.textile +15 -48
  2. data/VERSION +1 -0
  3. data/features/attribute_transformation.feature +7 -7
  4. data/features/attribute_updates.feature +16 -18
  5. data/features/deleting_instances.feature +13 -16
  6. data/features/excerpts.feature +0 -8
  7. data/features/facets.feature +19 -25
  8. data/features/handling_edits.feature +20 -25
  9. data/features/searching_across_models.feature +1 -1
  10. data/features/searching_by_index.feature +5 -6
  11. data/features/searching_by_model.feature +29 -29
  12. data/features/sphinx_scopes.feature +0 -26
  13. data/features/step_definitions/common_steps.rb +6 -18
  14. data/features/step_definitions/scope_steps.rb +0 -4
  15. data/features/step_definitions/search_steps.rb +4 -9
  16. data/features/support/env.rb +10 -3
  17. data/features/thinking_sphinx/db/fixtures/alphas.rb +10 -8
  18. data/features/thinking_sphinx/db/fixtures/cats.rb +1 -1
  19. data/features/thinking_sphinx/db/fixtures/dogs.rb +1 -1
  20. data/features/thinking_sphinx/db/fixtures/foxes.rb +1 -1
  21. data/features/thinking_sphinx/db/fixtures/people.rb +1 -1
  22. data/features/thinking_sphinx/db/fixtures/posts.rb +1 -5
  23. data/features/thinking_sphinx/db/migrations/create_posts.rb +0 -1
  24. data/features/thinking_sphinx/models/alpha.rb +0 -1
  25. data/features/thinking_sphinx/models/beta.rb +0 -5
  26. data/features/thinking_sphinx/models/developer.rb +1 -6
  27. data/features/thinking_sphinx/models/music.rb +1 -3
  28. data/features/thinking_sphinx/models/person.rb +1 -2
  29. data/features/thinking_sphinx/models/post.rb +0 -1
  30. data/lib/cucumber/thinking_sphinx/external_world.rb +4 -8
  31. data/lib/cucumber/thinking_sphinx/internal_world.rb +27 -36
  32. data/lib/thinking_sphinx.rb +60 -132
  33. data/lib/thinking_sphinx/active_record.rb +98 -124
  34. data/lib/thinking_sphinx/active_record/attribute_updates.rb +13 -17
  35. data/lib/thinking_sphinx/active_record/delta.rb +15 -21
  36. data/lib/thinking_sphinx/active_record/has_many_association.rb +23 -16
  37. data/lib/thinking_sphinx/active_record/scopes.rb +0 -18
  38. data/lib/thinking_sphinx/adapters/abstract_adapter.rb +15 -63
  39. data/lib/thinking_sphinx/adapters/mysql_adapter.rb +0 -4
  40. data/lib/thinking_sphinx/adapters/postgresql_adapter.rb +24 -65
  41. data/lib/thinking_sphinx/association.rb +11 -36
  42. data/lib/thinking_sphinx/attribute.rb +85 -92
  43. data/lib/thinking_sphinx/auto_version.rb +3 -21
  44. data/lib/thinking_sphinx/class_facet.rb +3 -8
  45. data/lib/thinking_sphinx/configuration.rb +58 -114
  46. data/lib/thinking_sphinx/context.rb +20 -22
  47. data/lib/thinking_sphinx/core/array.rb +13 -0
  48. data/lib/thinking_sphinx/deltas.rb +0 -2
  49. data/lib/thinking_sphinx/deltas/default_delta.rb +22 -18
  50. data/lib/thinking_sphinx/deploy/capistrano.rb +31 -30
  51. data/lib/thinking_sphinx/excerpter.rb +1 -2
  52. data/lib/thinking_sphinx/facet.rb +35 -45
  53. data/lib/thinking_sphinx/facet_search.rb +24 -58
  54. data/lib/thinking_sphinx/field.rb +0 -18
  55. data/lib/thinking_sphinx/index.rb +36 -38
  56. data/lib/thinking_sphinx/index/builder.rb +59 -74
  57. data/lib/thinking_sphinx/property.rb +45 -66
  58. data/lib/thinking_sphinx/railtie.rb +35 -0
  59. data/lib/thinking_sphinx/search.rb +250 -506
  60. data/lib/thinking_sphinx/source.rb +31 -50
  61. data/lib/thinking_sphinx/source/internal_properties.rb +3 -8
  62. data/lib/thinking_sphinx/source/sql.rb +31 -71
  63. data/lib/thinking_sphinx/tasks.rb +27 -48
  64. data/spec/thinking_sphinx/active_record/delta_spec.rb +41 -36
  65. data/spec/thinking_sphinx/active_record/has_many_association_spec.rb +0 -96
  66. data/spec/thinking_sphinx/active_record/scopes_spec.rb +29 -29
  67. data/spec/thinking_sphinx/active_record_spec.rb +169 -140
  68. data/spec/thinking_sphinx/association_spec.rb +2 -20
  69. data/spec/thinking_sphinx/attribute_spec.rb +97 -101
  70. data/spec/thinking_sphinx/auto_version_spec.rb +11 -75
  71. data/spec/thinking_sphinx/configuration_spec.rb +62 -63
  72. data/spec/thinking_sphinx/context_spec.rb +66 -66
  73. data/spec/thinking_sphinx/facet_search_spec.rb +99 -99
  74. data/spec/thinking_sphinx/facet_spec.rb +4 -30
  75. data/spec/thinking_sphinx/field_spec.rb +3 -17
  76. data/spec/thinking_sphinx/index/builder_spec.rb +132 -169
  77. data/spec/thinking_sphinx/index_spec.rb +39 -45
  78. data/spec/thinking_sphinx/search_methods_spec.rb +33 -37
  79. data/spec/thinking_sphinx/search_spec.rb +269 -491
  80. data/spec/thinking_sphinx/source_spec.rb +48 -62
  81. data/spec/thinking_sphinx_spec.rb +49 -49
  82. data/tasks/distribution.rb +46 -0
  83. data/tasks/testing.rb +74 -0
  84. metadata +123 -199
  85. data/features/field_sorting.feature +0 -18
  86. data/features/thinking_sphinx/db/.gitignore +0 -1
  87. data/features/thinking_sphinx/db/fixtures/post_keywords.txt +0 -1
  88. data/features/thinking_sphinx/models/andrew.rb +0 -17
  89. data/lib/thinking-sphinx.rb +0 -1
  90. data/lib/thinking_sphinx/active_record/has_many_association_with_scopes.rb +0 -21
  91. data/lib/thinking_sphinx/bundled_search.rb +0 -40
  92. data/lib/thinking_sphinx/connection.rb +0 -71
  93. data/lib/thinking_sphinx/deltas/delete_job.rb +0 -16
  94. data/lib/thinking_sphinx/deltas/index_job.rb +0 -17
  95. data/lib/thinking_sphinx/rails_additions.rb +0 -181
  96. data/spec/fixtures/data.sql +0 -32
  97. data/spec/fixtures/database.yml.default +0 -3
  98. data/spec/fixtures/models.rb +0 -161
  99. data/spec/fixtures/structure.sql +0 -146
  100. data/spec/spec_helper.rb +0 -54
  101. data/spec/sphinx_helper.rb +0 -67
  102. data/spec/thinking_sphinx/adapters/abstract_adapter_spec.rb +0 -163
  103. data/spec/thinking_sphinx/connection_spec.rb +0 -77
  104. data/spec/thinking_sphinx/rails_additions_spec.rb +0 -203
@@ -1,7 +1,6 @@
1
1
  require 'thinking_sphinx/active_record/attribute_updates'
2
2
  require 'thinking_sphinx/active_record/delta'
3
3
  require 'thinking_sphinx/active_record/has_many_association'
4
- require 'thinking_sphinx/active_record/has_many_association_with_scopes'
5
4
  require 'thinking_sphinx/active_record/scopes'
6
5
 
7
6
  module ThinkingSphinx
@@ -13,98 +12,63 @@ module ThinkingSphinx
13
12
  def self.included(base)
14
13
  base.class_eval do
15
14
  class_inheritable_array :sphinx_indexes, :sphinx_facets
16
-
15
+
17
16
  extend ThinkingSphinx::ActiveRecord::ClassMethods
18
-
17
+
19
18
  class << self
20
- attr_accessor :sphinx_index_blocks, :sphinx_types
21
-
19
+ attr_accessor :sphinx_index_blocks
20
+
22
21
  def set_sphinx_primary_key(attribute)
23
22
  @sphinx_primary_key_attribute = attribute
24
23
  end
25
-
24
+
26
25
  def primary_key_for_sphinx
27
- @primary_key_for_sphinx ||= begin
28
- if custom_primary_key_for_sphinx?
29
- @sphinx_primary_key_attribute ||
30
- superclass.primary_key_for_sphinx
31
- else
32
- primary_key || 'id'
33
- end
34
- end
26
+ @sphinx_primary_key_attribute || primary_key
35
27
  end
36
-
37
- def custom_primary_key_for_sphinx?
38
- (
39
- superclass.respond_to?(:custom_primary_key_for_sphinx?) &&
40
- superclass.custom_primary_key_for_sphinx?
41
- ) || !@sphinx_primary_key_attribute.nil?
42
- end
43
-
44
- def clear_primary_key_for_sphinx
45
- @primary_key_for_sphinx = nil
46
- end
47
-
28
+
48
29
  def sphinx_index_options
49
30
  sphinx_indexes.last.options
50
31
  end
51
-
52
- def set_sphinx_types(types)
53
- @sphinx_types = types
54
- end
55
-
32
+
56
33
  # Generate a unique CRC value for the model's name, to use to
57
34
  # determine which Sphinx documents belong to which AR records.
58
- #
35
+ #
59
36
  # Really only written for internal use - but hey, if it's useful to
60
37
  # you in some other way, awesome.
61
- #
38
+ #
62
39
  def to_crc32
63
40
  self.name.to_crc32
64
41
  end
65
-
42
+
66
43
  def to_crc32s
67
44
  (subclasses << self).collect { |klass| klass.to_crc32 }
68
45
  end
69
-
46
+
70
47
  def sphinx_database_adapter
71
- ThinkingSphinx::AbstractAdapter.detect(self)
48
+ @sphinx_database_adapter ||=
49
+ ThinkingSphinx::AbstractAdapter.detect(self)
72
50
  end
73
-
51
+
74
52
  def sphinx_name
75
53
  self.name.underscore.tr(':/\\', '_')
76
54
  end
77
-
78
- #
79
- # The above method to_crc32s is dependant on the subclasses being loaded consistently
80
- # After a reset_subclasses is called (during a Dispatcher.cleanup_application in development)
81
- # Our subclasses will be lost but our context will not reload them for us.
82
- #
83
- # We reset the context which causes the subclasses to be reloaded next time the context is called.
84
- #
85
- def reset_subclasses_with_thinking_sphinx
86
- reset_subclasses_without_thinking_sphinx
87
- ThinkingSphinx.reset_context!
88
- end
89
-
90
- alias_method_chain :reset_subclasses, :thinking_sphinx
91
-
55
+
92
56
  private
93
-
57
+
94
58
  def defined_indexes?
95
59
  @defined_indexes
96
60
  end
97
-
61
+
98
62
  def defined_indexes=(value)
99
63
  @defined_indexes = value
100
64
  end
101
-
65
+
102
66
  def sphinx_delta?
103
67
  self.sphinx_indexes.any? { |index| index.delta? }
104
68
  end
105
69
  end
106
70
  end
107
-
71
+
108
72
  ::ActiveRecord::Associations::HasManyAssociation.send(
109
73
  :include, ThinkingSphinx::ActiveRecord::HasManyAssociation
110
74
  )
@@ -112,7 +76,7 @@ module ThinkingSphinx
112
76
  :include, ThinkingSphinx::ActiveRecord::HasManyAssociation
113
77
  )
114
78
  end
115
-
79
+
116
80
  module ClassMethods
117
81
  # Allows creation of indexes for Sphinx. If you don't do this, there
118
82
  # isn't much point trying to search (or using this plugin at all,
@@ -146,7 +110,7 @@ module ThinkingSphinx
146
110
  #
147
111
  # define_index do
148
112
  # # fields ...
149
- #
113
+ #
150
114
  # has created_at, updated_at
151
115
  # end
152
116
  #
@@ -156,58 +120,58 @@ module ThinkingSphinx
156
120
  # define_index do
157
121
  # # fields ...
158
122
  # # attributes ...
159
- #
123
+ #
160
124
  # set_property :delta => true
161
125
  # end
162
126
  #
163
127
  # Check out the more detailed documentation for each of these methods
164
128
  # at ThinkingSphinx::Index::Builder.
165
- #
129
+ #
166
130
  def define_index(name = nil, &block)
167
131
  self.sphinx_index_blocks ||= []
168
132
  self.sphinx_indexes ||= []
169
133
  self.sphinx_facets ||= []
170
-
134
+
171
135
  ThinkingSphinx.context.add_indexed_model self
172
-
136
+
173
137
  if sphinx_index_blocks.empty?
174
138
  before_validation :define_indexes
175
139
  before_destroy :define_indexes
176
140
  end
177
-
141
+
178
142
  self.sphinx_index_blocks << lambda {
179
143
  add_sphinx_index name, &block
180
144
  }
181
-
145
+
182
146
  include ThinkingSphinx::ActiveRecord::Scopes
183
147
  include ThinkingSphinx::SearchMethods
184
148
  end
185
-
149
+
186
150
  def define_indexes
187
151
  superclass.define_indexes unless superclass == ::ActiveRecord::Base
188
-
152
+
189
153
  return if sphinx_index_blocks.nil? ||
190
154
  defined_indexes? ||
191
155
  !ThinkingSphinx.define_indexes?
192
-
156
+
193
157
  sphinx_index_blocks.each do |block|
194
158
  block.call
195
159
  end
196
-
160
+
197
161
  self.defined_indexes = true
198
-
162
+
199
163
  # We want to make sure that if the database doesn't exist, then Thinking
200
164
  # Sphinx doesn't mind when running non-TS tasks (like db:create, db:drop
201
165
  # and db:migrate). It's a bit hacky, but I can't think of a better way.
202
166
  rescue StandardError => err
203
167
  case err.class.name
204
- when "Mysql::Error", "Mysql2::Error", "Java::JavaSql::SQLException", "ActiveRecord::StatementInvalid"
168
+ when "Mysql::Error", "Java::JavaSql::SQLException", "ActiveRecord::StatementInvalid"
205
169
  return
206
170
  else
207
171
  raise err
208
172
  end
209
173
  end
210
-
174
+
211
175
  def add_sphinx_index(name, &block)
212
176
  index = ThinkingSphinx::Index::Builder.generate self, name, &block
213
177
 
@@ -216,50 +180,50 @@ module ThinkingSphinx
216
180
  insert_sphinx_index index
217
181
  end
218
182
  end
219
-
183
+
220
184
  def insert_sphinx_index(index)
221
185
  self.sphinx_indexes << index
222
186
  subclasses.each { |klass| klass.insert_sphinx_index(index) }
223
187
  end
224
-
188
+
225
189
  def has_sphinx_indexes?
226
- sphinx_indexes &&
190
+ sphinx_indexes &&
227
191
  sphinx_index_blocks &&
228
192
  (sphinx_indexes.length > 0 || sphinx_index_blocks.length > 0)
229
193
  end
230
-
194
+
231
195
  def indexed_by_sphinx?
232
196
  sphinx_indexes && sphinx_indexes.length > 0
233
197
  end
234
-
198
+
235
199
  def delta_indexed_by_sphinx?
236
200
  sphinx_indexes && sphinx_indexes.any? { |index| index.delta? }
237
201
  end
238
-
202
+
239
203
  def sphinx_index_names
240
204
  define_indexes
241
205
  sphinx_indexes.collect(&:all_names).flatten
242
206
  end
243
-
207
+
244
208
  def core_index_names
245
209
  define_indexes
246
210
  sphinx_indexes.collect(&:core_name)
247
211
  end
248
-
212
+
249
213
  def delta_index_names
250
214
  define_indexes
251
215
  sphinx_indexes.select(&:delta?).collect(&:delta_name)
252
216
  end
253
-
217
+
254
218
  def to_riddle
255
219
  define_indexes
256
220
  sphinx_database_adapter.setup
257
-
221
+
258
222
  local_sphinx_indexes.collect { |index|
259
223
  index.to_riddle(sphinx_offset)
260
224
  }.flatten
261
225
  end
262
-
226
+
263
227
  def source_of_sphinx_index
264
228
  define_indexes
265
229
  possible_models = self.sphinx_indexes.collect { |index| index.model }
@@ -272,31 +236,29 @@ module ThinkingSphinx
272
236
 
273
237
  return parent
274
238
  end
275
-
239
+
276
240
  def delete_in_index(index, document_id)
277
- return unless ThinkingSphinx.sphinx_running?
278
-
279
- ThinkingSphinx::Connection.take do |client|
280
- client.update index, ['sphinx_deleted'], {document_id => [1]}
281
- end
282
- rescue Riddle::ConnectionError, Riddle::ResponseError,
283
- ThinkingSphinx::SphinxError, Errno::ETIMEDOUT, Timeout::Error
284
- # Not the end of the world if Sphinx isn't running.
241
+ return unless ThinkingSphinx.sphinx_running? &&
242
+ search_for_id(document_id, index)
243
+
244
+ ThinkingSphinx::Configuration.instance.client.update(
245
+ index, ['sphinx_deleted'], {document_id => [1]}
246
+ )
285
247
  end
286
-
248
+
287
249
  def sphinx_offset
288
250
  ThinkingSphinx.context.superclass_indexed_models.
289
251
  index eldest_indexed_ancestor
290
252
  end
291
-
292
- # Temporarily disable delta indexing inside a block, then perform a
293
- # single rebuild of index at the end.
253
+
254
+ # Temporarily disable delta indexing inside a block, then perform a single
255
+ # rebuild of index at the end.
294
256
  #
295
257
  # Useful when performing updates to batches of models to prevent
296
258
  # the delta index being rebuilt after each individual update.
297
259
  #
298
- # In the following example, the delta index will only be rebuilt
299
- # once, not 10 times.
260
+ # In the following example, the delta index will only be rebuilt once,
261
+ # not 10 times.
300
262
  #
301
263
  # SomeModel.suspended_delta do
302
264
  # 10.times do
@@ -306,74 +268,86 @@ module ThinkingSphinx
306
268
  #
307
269
  def suspended_delta(reindex_after = true, &block)
308
270
  define_indexes
309
- original_setting = ThinkingSphinx.deltas_suspended?
310
- ThinkingSphinx.deltas_suspended = true
271
+ original_setting = ThinkingSphinx.deltas_enabled?
272
+ ThinkingSphinx.deltas_enabled = false
311
273
  begin
312
274
  yield
313
275
  ensure
314
- ThinkingSphinx.deltas_suspended = original_setting
315
- self.index_delta if reindex_after && !original_setting
276
+ ThinkingSphinx.deltas_enabled = original_setting
277
+ self.index_delta if reindex_after
316
278
  end
317
279
  end
318
-
280
+
319
281
  private
320
-
282
+
321
283
  def local_sphinx_indexes
322
- (sphinx_indexes || []).select { |index|
284
+ sphinx_indexes.select { |index|
323
285
  index.model == self
324
286
  }
325
287
  end
326
-
288
+
327
289
  def add_sphinx_callbacks_and_extend(delta = false)
328
290
  unless indexed_by_sphinx?
329
291
  after_destroy :toggle_deleted
330
-
292
+
331
293
  include ThinkingSphinx::ActiveRecord::AttributeUpdates
332
294
  end
333
-
295
+
334
296
  if delta && !delta_indexed_by_sphinx?
335
297
  include ThinkingSphinx::ActiveRecord::Delta
336
-
337
- before_save :toggle_delta
338
- after_commit :index_delta
298
+
299
+ before_save :toggle_delta
300
+ after_commit :index_delta
339
301
  end
340
302
  end
341
-
303
+
342
304
  def eldest_indexed_ancestor
343
305
  ancestors.reverse.detect { |ancestor|
344
306
  ThinkingSphinx.context.indexed_models.include?(ancestor.name)
345
307
  }.name
346
308
  end
347
309
  end
348
-
349
- attr_accessor :excerpts
350
- attr_accessor :sphinx_attributes
351
- attr_accessor :matching_fields
352
-
310
+
311
+ def in_index?(suffix)
312
+ self.class.search_for_id self.sphinx_document_id, sphinx_index_name(suffix)
313
+ end
314
+
315
+ def in_core_index?
316
+ in_index? "core"
317
+ end
318
+
319
+ def in_delta_index?
320
+ in_index? "delta"
321
+ end
322
+
323
+ def in_both_indexes?
324
+ in_core_index? && in_delta_index?
325
+ end
326
+
353
327
  def toggle_deleted
354
328
  return unless ThinkingSphinx.updates_enabled?
355
-
329
+
356
330
  self.class.core_index_names.each do |index_name|
357
331
  self.class.delete_in_index index_name, self.sphinx_document_id
358
332
  end
359
333
  self.class.delta_index_names.each do |index_name|
360
334
  self.class.delete_in_index index_name, self.sphinx_document_id
361
335
  end if self.class.delta_indexed_by_sphinx? && toggled_delta?
362
-
336
+
363
337
  rescue ::ThinkingSphinx::ConnectionError
364
338
  # nothing
365
339
  end
366
-
340
+
367
341
  # Returns the unique integer id for the object. This method uses the
368
342
  # attribute hash to get around ActiveRecord always mapping the #id method
369
343
  # to whatever the real primary key is (which may be a unique string hash).
370
- #
344
+ #
371
345
  # @return [Integer] Unique record id for the purposes of Sphinx.
372
- #
346
+ #
373
347
  def primary_key_for_sphinx
374
- read_attribute(self.class.primary_key_for_sphinx)
348
+ @primary_key_for_sphinx ||= read_attribute(self.class.primary_key_for_sphinx)
375
349
  end
376
-
350
+
377
351
  def sphinx_document_id
378
352
  primary_key_for_sphinx * ThinkingSphinx.context.indexed_models.size +
379
353
  self.class.sphinx_offset
@@ -384,7 +358,7 @@ module ThinkingSphinx
384
358
  def sphinx_index_name(suffix)
385
359
  "#{self.class.source_of_sphinx_index.name.underscore.tr(':/\\', '_')}_#{suffix}"
386
360
  end
387
-
361
+
388
362
  def define_indexes
389
363
  self.class.define_indexes
390
364
  end
@@ -3,51 +3,47 @@ module ThinkingSphinx
3
3
  module AttributeUpdates
4
4
  def self.included(base)
5
5
  base.class_eval do
6
- after_commit :update_attribute_values
6
+ after_save :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
- ThinkingSphinx::Connection.take do |client|
44
- client.update index_name, attribute_names, {
45
- sphinx_document_id => attribute_values
46
- }
47
- end
48
- rescue Riddle::ConnectionError, Riddle::ResponseError,
49
- ThinkingSphinx::SphinxError, Errno::ETIMEDOUT
50
- # Not the end of the world if Sphinx isn't running.
43
+ config = ThinkingSphinx::Configuration.instance
44
+ config.client.update index_name, attribute_names, {
45
+ sphinx_document_id => attribute_values
46
+ } if self.class.search_for_id(sphinx_document_id, index_name)
51
47
  end
52
48
  end
53
49
  end