thinking-sphinx 1.5.0 → 2.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
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