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.
- data/README.textile +6 -1
- data/features/searching_by_model.feature +24 -30
- data/features/thinking_sphinx/db/.gitignore +1 -0
- data/features/thinking_sphinx/db/fixtures/post_keywords.txt +1 -0
- data/lib/cucumber/thinking_sphinx/internal_world.rb +26 -26
- data/lib/thinking_sphinx.rb +17 -26
- data/lib/thinking_sphinx/active_record.rb +69 -74
- data/lib/thinking_sphinx/active_record/attribute_updates.rb +11 -10
- data/lib/thinking_sphinx/active_record/has_many_association.rb +2 -1
- data/lib/thinking_sphinx/adapters/abstract_adapter.rb +11 -11
- data/lib/thinking_sphinx/adapters/postgresql_adapter.rb +34 -20
- data/lib/thinking_sphinx/association.rb +12 -7
- data/lib/thinking_sphinx/attribute.rb +64 -61
- data/lib/thinking_sphinx/configuration.rb +32 -36
- data/lib/thinking_sphinx/context.rb +3 -2
- data/lib/thinking_sphinx/deploy/capistrano.rb +7 -9
- data/lib/thinking_sphinx/search.rb +201 -178
- data/lib/thinking_sphinx/source/sql.rb +1 -1
- data/lib/thinking_sphinx/tasks.rb +21 -19
- data/lib/thinking_sphinx/version.rb +3 -0
- data/spec/fixtures/data.sql +32 -0
- data/spec/fixtures/database.yml.default +3 -0
- data/spec/fixtures/models.rb +161 -0
- data/spec/fixtures/structure.sql +146 -0
- data/spec/spec_helper.rb +57 -0
- data/spec/sphinx_helper.rb +61 -0
- data/spec/thinking_sphinx/active_record/delta_spec.rb +24 -24
- data/spec/thinking_sphinx/active_record/has_many_association_spec.rb +22 -0
- data/spec/thinking_sphinx/active_record/scopes_spec.rb +25 -25
- data/spec/thinking_sphinx/active_record_spec.rb +110 -109
- data/spec/thinking_sphinx/adapters/abstract_adapter_spec.rb +38 -38
- data/spec/thinking_sphinx/association_spec.rb +20 -2
- data/spec/thinking_sphinx/context_spec.rb +61 -64
- data/spec/thinking_sphinx/search_spec.rb +7 -0
- data/spec/thinking_sphinx_spec.rb +47 -46
- metadata +50 -98
- data/VERSION +0 -1
- data/tasks/distribution.rb +0 -34
- 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,
|
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
|
-
}
|
47
|
-
rescue Riddle::ConnectionError,
|
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
|