friendlyfashion-thinking-sphinx 2.0.13

Sign up to get free protection for your applications and to get access to all the features.
Files changed (175) hide show
  1. data/HISTORY +244 -0
  2. data/LICENCE +20 -0
  3. data/README.textile +235 -0
  4. data/features/abstract_inheritance.feature +10 -0
  5. data/features/alternate_primary_key.feature +27 -0
  6. data/features/attribute_transformation.feature +22 -0
  7. data/features/attribute_updates.feature +77 -0
  8. data/features/deleting_instances.feature +67 -0
  9. data/features/direct_attributes.feature +11 -0
  10. data/features/excerpts.feature +21 -0
  11. data/features/extensible_delta_indexing.feature +9 -0
  12. data/features/facets.feature +88 -0
  13. data/features/facets_across_model.feature +29 -0
  14. data/features/field_sorting.feature +18 -0
  15. data/features/handling_edits.feature +94 -0
  16. data/features/retry_stale_indexes.feature +24 -0
  17. data/features/searching_across_models.feature +20 -0
  18. data/features/searching_by_index.feature +40 -0
  19. data/features/searching_by_model.feature +175 -0
  20. data/features/searching_with_find_arguments.feature +56 -0
  21. data/features/sphinx_detection.feature +25 -0
  22. data/features/sphinx_scopes.feature +68 -0
  23. data/features/step_definitions/alpha_steps.rb +16 -0
  24. data/features/step_definitions/beta_steps.rb +7 -0
  25. data/features/step_definitions/common_steps.rb +201 -0
  26. data/features/step_definitions/extensible_delta_indexing_steps.rb +7 -0
  27. data/features/step_definitions/facet_steps.rb +96 -0
  28. data/features/step_definitions/find_arguments_steps.rb +36 -0
  29. data/features/step_definitions/gamma_steps.rb +15 -0
  30. data/features/step_definitions/scope_steps.rb +19 -0
  31. data/features/step_definitions/search_steps.rb +94 -0
  32. data/features/step_definitions/sphinx_steps.rb +35 -0
  33. data/features/sti_searching.feature +19 -0
  34. data/features/support/env.rb +27 -0
  35. data/features/support/lib/generic_delta_handler.rb +8 -0
  36. data/features/thinking_sphinx/database.example.yml +3 -0
  37. data/features/thinking_sphinx/db/.gitignore +1 -0
  38. data/features/thinking_sphinx/db/fixtures/alphas.rb +8 -0
  39. data/features/thinking_sphinx/db/fixtures/authors.rb +1 -0
  40. data/features/thinking_sphinx/db/fixtures/betas.rb +11 -0
  41. data/features/thinking_sphinx/db/fixtures/boxes.rb +9 -0
  42. data/features/thinking_sphinx/db/fixtures/categories.rb +1 -0
  43. data/features/thinking_sphinx/db/fixtures/cats.rb +3 -0
  44. data/features/thinking_sphinx/db/fixtures/comments.rb +24 -0
  45. data/features/thinking_sphinx/db/fixtures/developers.rb +31 -0
  46. data/features/thinking_sphinx/db/fixtures/dogs.rb +3 -0
  47. data/features/thinking_sphinx/db/fixtures/extensible_betas.rb +10 -0
  48. data/features/thinking_sphinx/db/fixtures/foxes.rb +3 -0
  49. data/features/thinking_sphinx/db/fixtures/gammas.rb +10 -0
  50. data/features/thinking_sphinx/db/fixtures/music.rb +4 -0
  51. data/features/thinking_sphinx/db/fixtures/people.rb +1001 -0
  52. data/features/thinking_sphinx/db/fixtures/post_keywords.txt +1 -0
  53. data/features/thinking_sphinx/db/fixtures/posts.rb +10 -0
  54. data/features/thinking_sphinx/db/fixtures/robots.rb +8 -0
  55. data/features/thinking_sphinx/db/fixtures/tags.rb +27 -0
  56. data/features/thinking_sphinx/db/migrations/create_alphas.rb +8 -0
  57. data/features/thinking_sphinx/db/migrations/create_animals.rb +5 -0
  58. data/features/thinking_sphinx/db/migrations/create_authors.rb +3 -0
  59. data/features/thinking_sphinx/db/migrations/create_authors_posts.rb +6 -0
  60. data/features/thinking_sphinx/db/migrations/create_betas.rb +5 -0
  61. data/features/thinking_sphinx/db/migrations/create_boxes.rb +5 -0
  62. data/features/thinking_sphinx/db/migrations/create_categories.rb +3 -0
  63. data/features/thinking_sphinx/db/migrations/create_comments.rb +10 -0
  64. data/features/thinking_sphinx/db/migrations/create_developers.rb +7 -0
  65. data/features/thinking_sphinx/db/migrations/create_extensible_betas.rb +5 -0
  66. data/features/thinking_sphinx/db/migrations/create_gammas.rb +3 -0
  67. data/features/thinking_sphinx/db/migrations/create_genres.rb +3 -0
  68. data/features/thinking_sphinx/db/migrations/create_music.rb +6 -0
  69. data/features/thinking_sphinx/db/migrations/create_people.rb +13 -0
  70. data/features/thinking_sphinx/db/migrations/create_posts.rb +6 -0
  71. data/features/thinking_sphinx/db/migrations/create_robots.rb +4 -0
  72. data/features/thinking_sphinx/db/migrations/create_taggings.rb +5 -0
  73. data/features/thinking_sphinx/db/migrations/create_tags.rb +4 -0
  74. data/features/thinking_sphinx/models/alpha.rb +23 -0
  75. data/features/thinking_sphinx/models/andrew.rb +17 -0
  76. data/features/thinking_sphinx/models/animal.rb +5 -0
  77. data/features/thinking_sphinx/models/author.rb +3 -0
  78. data/features/thinking_sphinx/models/beta.rb +13 -0
  79. data/features/thinking_sphinx/models/box.rb +8 -0
  80. data/features/thinking_sphinx/models/cat.rb +3 -0
  81. data/features/thinking_sphinx/models/category.rb +4 -0
  82. data/features/thinking_sphinx/models/comment.rb +10 -0
  83. data/features/thinking_sphinx/models/developer.rb +21 -0
  84. data/features/thinking_sphinx/models/dog.rb +3 -0
  85. data/features/thinking_sphinx/models/extensible_beta.rb +9 -0
  86. data/features/thinking_sphinx/models/fox.rb +5 -0
  87. data/features/thinking_sphinx/models/gamma.rb +5 -0
  88. data/features/thinking_sphinx/models/genre.rb +3 -0
  89. data/features/thinking_sphinx/models/medium.rb +5 -0
  90. data/features/thinking_sphinx/models/music.rb +10 -0
  91. data/features/thinking_sphinx/models/person.rb +24 -0
  92. data/features/thinking_sphinx/models/post.rb +22 -0
  93. data/features/thinking_sphinx/models/robot.rb +12 -0
  94. data/features/thinking_sphinx/models/tag.rb +3 -0
  95. data/features/thinking_sphinx/models/tagging.rb +4 -0
  96. data/lib/cucumber/thinking_sphinx/external_world.rb +12 -0
  97. data/lib/cucumber/thinking_sphinx/internal_world.rb +137 -0
  98. data/lib/cucumber/thinking_sphinx/sql_logger.rb +28 -0
  99. data/lib/thinking-sphinx.rb +1 -0
  100. data/lib/thinking_sphinx/action_controller.rb +31 -0
  101. data/lib/thinking_sphinx/active_record/attribute_updates.rb +53 -0
  102. data/lib/thinking_sphinx/active_record/collection_proxy.rb +47 -0
  103. data/lib/thinking_sphinx/active_record/collection_proxy_with_scopes.rb +27 -0
  104. data/lib/thinking_sphinx/active_record/delta.rb +67 -0
  105. data/lib/thinking_sphinx/active_record/has_many_association.rb +44 -0
  106. data/lib/thinking_sphinx/active_record/has_many_association_with_scopes.rb +21 -0
  107. data/lib/thinking_sphinx/active_record/log_subscriber.rb +61 -0
  108. data/lib/thinking_sphinx/active_record/scopes.rb +110 -0
  109. data/lib/thinking_sphinx/active_record.rb +386 -0
  110. data/lib/thinking_sphinx/adapters/abstract_adapter.rb +87 -0
  111. data/lib/thinking_sphinx/adapters/mysql_adapter.rb +62 -0
  112. data/lib/thinking_sphinx/adapters/postgresql_adapter.rb +188 -0
  113. data/lib/thinking_sphinx/association.rb +230 -0
  114. data/lib/thinking_sphinx/attribute.rb +405 -0
  115. data/lib/thinking_sphinx/auto_version.rb +40 -0
  116. data/lib/thinking_sphinx/bundled_search.rb +44 -0
  117. data/lib/thinking_sphinx/class_facet.rb +20 -0
  118. data/lib/thinking_sphinx/configuration.rb +375 -0
  119. data/lib/thinking_sphinx/context.rb +76 -0
  120. data/lib/thinking_sphinx/core/string.rb +15 -0
  121. data/lib/thinking_sphinx/deltas/default_delta.rb +62 -0
  122. data/lib/thinking_sphinx/deltas.rb +28 -0
  123. data/lib/thinking_sphinx/deploy/capistrano.rb +99 -0
  124. data/lib/thinking_sphinx/excerpter.rb +23 -0
  125. data/lib/thinking_sphinx/facet.rb +135 -0
  126. data/lib/thinking_sphinx/facet_search.rb +170 -0
  127. data/lib/thinking_sphinx/field.rb +98 -0
  128. data/lib/thinking_sphinx/index/builder.rb +315 -0
  129. data/lib/thinking_sphinx/index/faux_column.rb +118 -0
  130. data/lib/thinking_sphinx/index.rb +159 -0
  131. data/lib/thinking_sphinx/join.rb +37 -0
  132. data/lib/thinking_sphinx/property.rb +187 -0
  133. data/lib/thinking_sphinx/railtie.rb +43 -0
  134. data/lib/thinking_sphinx/search.rb +1061 -0
  135. data/lib/thinking_sphinx/search_methods.rb +439 -0
  136. data/lib/thinking_sphinx/sinatra.rb +7 -0
  137. data/lib/thinking_sphinx/source/internal_properties.rb +51 -0
  138. data/lib/thinking_sphinx/source/sql.rb +174 -0
  139. data/lib/thinking_sphinx/source.rb +194 -0
  140. data/lib/thinking_sphinx/tasks.rb +142 -0
  141. data/lib/thinking_sphinx/test.rb +55 -0
  142. data/lib/thinking_sphinx/version.rb +3 -0
  143. data/lib/thinking_sphinx.rb +297 -0
  144. data/spec/fixtures/data.sql +32 -0
  145. data/spec/fixtures/database.yml.default +3 -0
  146. data/spec/fixtures/models.rb +164 -0
  147. data/spec/fixtures/structure.sql +146 -0
  148. data/spec/spec_helper.rb +61 -0
  149. data/spec/sphinx_helper.rb +60 -0
  150. data/spec/support/rails.rb +25 -0
  151. data/spec/thinking_sphinx/active_record/delta_spec.rb +122 -0
  152. data/spec/thinking_sphinx/active_record/has_many_association_spec.rb +173 -0
  153. data/spec/thinking_sphinx/active_record/scopes_spec.rb +176 -0
  154. data/spec/thinking_sphinx/active_record_spec.rb +573 -0
  155. data/spec/thinking_sphinx/adapters/abstract_adapter_spec.rb +145 -0
  156. data/spec/thinking_sphinx/association_spec.rb +250 -0
  157. data/spec/thinking_sphinx/attribute_spec.rb +552 -0
  158. data/spec/thinking_sphinx/auto_version_spec.rb +103 -0
  159. data/spec/thinking_sphinx/configuration_spec.rb +326 -0
  160. data/spec/thinking_sphinx/context_spec.rb +126 -0
  161. data/spec/thinking_sphinx/core/array_spec.rb +9 -0
  162. data/spec/thinking_sphinx/core/string_spec.rb +9 -0
  163. data/spec/thinking_sphinx/excerpter_spec.rb +49 -0
  164. data/spec/thinking_sphinx/facet_search_spec.rb +176 -0
  165. data/spec/thinking_sphinx/facet_spec.rb +359 -0
  166. data/spec/thinking_sphinx/field_spec.rb +127 -0
  167. data/spec/thinking_sphinx/index/builder_spec.rb +532 -0
  168. data/spec/thinking_sphinx/index/faux_column_spec.rb +36 -0
  169. data/spec/thinking_sphinx/index_spec.rb +189 -0
  170. data/spec/thinking_sphinx/search_methods_spec.rb +156 -0
  171. data/spec/thinking_sphinx/search_spec.rb +1455 -0
  172. data/spec/thinking_sphinx/source_spec.rb +267 -0
  173. data/spec/thinking_sphinx/test_spec.rb +20 -0
  174. data/spec/thinking_sphinx_spec.rb +204 -0
  175. metadata +524 -0
@@ -0,0 +1,386 @@
1
+ require 'thinking_sphinx/active_record/attribute_updates'
2
+ require 'thinking_sphinx/active_record/collection_proxy'
3
+ require 'thinking_sphinx/active_record/collection_proxy_with_scopes'
4
+ require 'thinking_sphinx/active_record/delta'
5
+ require 'thinking_sphinx/active_record/has_many_association'
6
+ require 'thinking_sphinx/active_record/log_subscriber'
7
+ require 'thinking_sphinx/active_record/has_many_association_with_scopes'
8
+ require 'thinking_sphinx/active_record/scopes'
9
+
10
+ module ThinkingSphinx
11
+ # Core additions to ActiveRecord models - define_index for creating indexes
12
+ # for models. If you want to interrogate the index objects created for the
13
+ # model, you can use the class-level accessor :sphinx_indexes.
14
+ #
15
+ module ActiveRecord
16
+ def self.included(base)
17
+ base.class_eval do
18
+ if defined?(class_attribute)
19
+ class_attribute :sphinx_indexes, :sphinx_facets
20
+ else
21
+ class_inheritable_array :sphinx_indexes, :sphinx_facets
22
+ end
23
+
24
+ extend ThinkingSphinx::ActiveRecord::ClassMethods
25
+
26
+ class << self
27
+ attr_accessor :sphinx_index_blocks, :sphinx_types
28
+
29
+ def set_sphinx_primary_key(attribute)
30
+ @sphinx_primary_key_attribute = attribute
31
+ end
32
+
33
+ def primary_key_for_sphinx
34
+ @primary_key_for_sphinx ||= begin
35
+ if custom_primary_key_for_sphinx?
36
+ @sphinx_primary_key_attribute ||
37
+ superclass.primary_key_for_sphinx
38
+ else
39
+ primary_key || 'id'
40
+ end
41
+ end
42
+ end
43
+
44
+ def custom_primary_key_for_sphinx?
45
+ (
46
+ superclass.respond_to?(:custom_primary_key_for_sphinx?) &&
47
+ superclass.custom_primary_key_for_sphinx?
48
+ ) || !@sphinx_primary_key_attribute.nil?
49
+ end
50
+
51
+ def clear_primary_key_for_sphinx
52
+ @primary_key_for_sphinx = nil
53
+ end
54
+
55
+ def sphinx_index_options
56
+ sphinx_indexes.last.options
57
+ end
58
+
59
+ def set_sphinx_types(types)
60
+ @sphinx_types = types
61
+ end
62
+
63
+ # Generate a unique CRC value for the model's name, to use to
64
+ # determine which Sphinx documents belong to which AR records.
65
+ #
66
+ # Really only written for internal use - but hey, if it's useful to
67
+ # you in some other way, awesome.
68
+ #
69
+ def to_crc32
70
+ self.name.to_crc32
71
+ end
72
+
73
+ def to_crc32s
74
+ (descendants << self).collect { |klass| klass.to_crc32 }
75
+ end
76
+
77
+ def sphinx_database_adapter
78
+ ThinkingSphinx::AbstractAdapter.detect(self)
79
+ end
80
+
81
+ def sphinx_name
82
+ self.name.underscore.tr(':/\\', '_')
83
+ end
84
+
85
+ private
86
+
87
+ def defined_indexes?
88
+ @defined_indexes
89
+ end
90
+
91
+ def defined_indexes=(value)
92
+ @defined_indexes = value
93
+ end
94
+
95
+ def sphinx_delta?
96
+ self.sphinx_indexes.any? { |index| index.delta? }
97
+ end
98
+ end
99
+ end
100
+
101
+ if ThinkingSphinx.rails_3_1?
102
+ assoc_mixin = ThinkingSphinx::ActiveRecord::CollectionProxy
103
+ ::ActiveRecord::Associations::CollectionProxy.send(:include, assoc_mixin)
104
+ else
105
+ assoc_mixin = ThinkingSphinx::ActiveRecord::HasManyAssociation
106
+ ::ActiveRecord::Associations::HasManyAssociation.send(:include, assoc_mixin)
107
+ ::ActiveRecord::Associations::HasManyThroughAssociation.send(:include, assoc_mixin)
108
+ end
109
+ end
110
+
111
+ module ClassMethods
112
+ # Allows creation of indexes for Sphinx. If you don't do this, there
113
+ # isn't much point trying to search (or using this plugin at all,
114
+ # really).
115
+ #
116
+ # An example or two:
117
+ #
118
+ # define_index
119
+ # indexes :id, :as => :model_id
120
+ # indexes name
121
+ # end
122
+ #
123
+ # You can also grab fields from associations - multiple levels deep
124
+ # if necessary.
125
+ #
126
+ # define_index do
127
+ # indexes tags.name, :as => :tag
128
+ # indexes articles.content
129
+ # indexes orders.line_items.product.name, :as => :product
130
+ # end
131
+ #
132
+ # And it will automatically concatenate multiple fields:
133
+ #
134
+ # define_index do
135
+ # indexes [author.first_name, author.last_name], :as => :author
136
+ # end
137
+ #
138
+ # The #indexes method is for fields - if you want attributes, use
139
+ # #has instead. All the same rules apply - but keep in mind that
140
+ # attributes are for sorting, grouping and filtering, not searching.
141
+ #
142
+ # define_index do
143
+ # # fields ...
144
+ #
145
+ # has created_at, updated_at
146
+ # end
147
+ #
148
+ # One last feature is the delta index. This requires the model to
149
+ # have a boolean field named 'delta', and is enabled as follows:
150
+ #
151
+ # define_index do
152
+ # # fields ...
153
+ # # attributes ...
154
+ #
155
+ # set_property :delta => true
156
+ # end
157
+ #
158
+ # Check out the more detailed documentation for each of these methods
159
+ # at ThinkingSphinx::Index::Builder.
160
+ #
161
+ def define_index(name = nil, &block)
162
+ self.sphinx_index_blocks ||= []
163
+ self.sphinx_indexes ||= []
164
+ self.sphinx_facets ||= []
165
+
166
+ ThinkingSphinx.context.add_indexed_model self
167
+
168
+ if sphinx_index_blocks.empty?
169
+ before_validation :define_indexes
170
+ before_destroy :define_indexes
171
+ end
172
+
173
+ self.sphinx_index_blocks << lambda {
174
+ add_sphinx_index name, &block
175
+ }
176
+
177
+ include ThinkingSphinx::ActiveRecord::Scopes
178
+ include ThinkingSphinx::SearchMethods
179
+ end
180
+
181
+ def define_indexes
182
+ superclass.define_indexes unless superclass == ::ActiveRecord::Base
183
+
184
+ return if sphinx_index_blocks.nil? ||
185
+ defined_indexes? ||
186
+ !ThinkingSphinx.define_indexes?
187
+
188
+ sphinx_index_blocks.each do |block|
189
+ block.call
190
+ end
191
+
192
+ self.defined_indexes = true
193
+
194
+ # We want to make sure that if the database doesn't exist, then Thinking
195
+ # Sphinx doesn't mind when running non-TS tasks (like db:create, db:drop
196
+ # and db:migrate). It's a bit hacky, but I can't think of a better way.
197
+ rescue StandardError => err
198
+ case err.class.name
199
+ when "Mysql::Error", "Mysql2::Error", "Java::JavaSql::SQLException", "ActiveRecord::StatementInvalid"
200
+ return
201
+ else
202
+ raise err
203
+ end
204
+ end
205
+
206
+ def add_sphinx_index(name, &block)
207
+ index = ThinkingSphinx::Index::Builder.generate self, name, &block
208
+
209
+ unless sphinx_indexes.any? { |i| i.name == index.name }
210
+ add_sphinx_callbacks_and_extend(index.delta?)
211
+ insert_sphinx_index index
212
+ end
213
+ end
214
+
215
+ def insert_sphinx_index(index)
216
+ self.sphinx_indexes += [index]
217
+ end
218
+
219
+ def has_sphinx_indexes?
220
+ sphinx_indexes &&
221
+ sphinx_index_blocks &&
222
+ (sphinx_indexes.length > 0 || sphinx_index_blocks.length > 0)
223
+ end
224
+
225
+ def indexed_by_sphinx?
226
+ sphinx_indexes && sphinx_indexes.length > 0
227
+ end
228
+
229
+ def delta_indexed_by_sphinx?
230
+ sphinx_indexes && sphinx_indexes.any? { |index| index.delta? }
231
+ end
232
+
233
+ def sphinx_index_names
234
+ define_indexes
235
+ sphinx_indexes.collect(&:all_names).flatten
236
+ end
237
+
238
+ def core_index_names
239
+ define_indexes
240
+ sphinx_indexes.collect(&:core_name)
241
+ end
242
+
243
+ def delta_index_names
244
+ define_indexes
245
+ sphinx_indexes.select(&:delta?).collect(&:delta_name)
246
+ end
247
+
248
+ def to_riddle
249
+ define_indexes
250
+ sphinx_database_adapter.setup
251
+
252
+ local_sphinx_indexes.collect { |index|
253
+ index.to_riddle(sphinx_offset)
254
+ }.flatten
255
+ end
256
+
257
+ def source_of_sphinx_index
258
+ define_indexes
259
+ possible_models = self.sphinx_indexes.collect { |index| index.model }
260
+ return self if possible_models.include?(self)
261
+
262
+ parent = self.superclass
263
+ while !possible_models.include?(parent) && parent != ::ActiveRecord::Base
264
+ parent = parent.superclass
265
+ end
266
+
267
+ return parent
268
+ end
269
+
270
+ def delete_in_index(index, document_id)
271
+ return unless ThinkingSphinx.sphinx_running?
272
+
273
+ ThinkingSphinx::Configuration.instance.client.update(
274
+ index, ['sphinx_deleted'], {document_id => [1]}
275
+ )
276
+ rescue Riddle::ConnectionError, Riddle::ResponseError,
277
+ ThinkingSphinx::SphinxError, Errno::ETIMEDOUT, Timeout::Error
278
+ # Not the end of the world if Sphinx isn't running.
279
+ end
280
+
281
+ def sphinx_offset
282
+ ThinkingSphinx.context.superclass_indexed_models.
283
+ index eldest_indexed_ancestor
284
+ end
285
+
286
+ # Temporarily disable delta indexing inside a block, then perform a
287
+ # single rebuild of index at the end.
288
+ #
289
+ # Useful when performing updates to batches of models to prevent
290
+ # the delta index being rebuilt after each individual update.
291
+ #
292
+ # In the following example, the delta index will only be rebuilt
293
+ # once, not 10 times.
294
+ #
295
+ # SomeModel.suspended_delta do
296
+ # 10.times do
297
+ # SomeModel.create( ... )
298
+ # end
299
+ # end
300
+ #
301
+ def suspended_delta(reindex_after = true, &block)
302
+ define_indexes
303
+ original_setting = ThinkingSphinx.deltas_suspended?
304
+ ThinkingSphinx.deltas_suspended = true
305
+ begin
306
+ yield
307
+ ensure
308
+ ThinkingSphinx.deltas_suspended = original_setting
309
+ self.index_delta if reindex_after && !original_setting
310
+ end
311
+ end
312
+
313
+ private
314
+
315
+ def local_sphinx_indexes
316
+ (sphinx_indexes || []).select { |index|
317
+ index.model == self
318
+ }
319
+ end
320
+
321
+ def add_sphinx_callbacks_and_extend(delta = false)
322
+ unless indexed_by_sphinx?
323
+ after_destroy :toggle_deleted
324
+
325
+ include ThinkingSphinx::ActiveRecord::AttributeUpdates
326
+ end
327
+
328
+ if delta && !delta_indexed_by_sphinx?
329
+ include ThinkingSphinx::ActiveRecord::Delta
330
+
331
+ before_save :toggle_delta
332
+ after_commit :index_delta
333
+ end
334
+ end
335
+
336
+ def eldest_indexed_ancestor
337
+ ancestors.reverse.detect { |ancestor|
338
+ ThinkingSphinx.context.indexed_models.include?(ancestor.name)
339
+ }.name
340
+ end
341
+ end
342
+
343
+ attr_accessor :excerpts
344
+ attr_accessor :sphinx_attributes
345
+ attr_accessor :matching_fields
346
+
347
+ def toggle_deleted
348
+ return unless ThinkingSphinx.updates_enabled?
349
+
350
+ self.class.core_index_names.each do |index_name|
351
+ self.class.delete_in_index index_name, self.sphinx_document_id
352
+ end
353
+ self.class.delta_index_names.each do |index_name|
354
+ self.class.delete_in_index index_name, self.sphinx_document_id
355
+ end if self.class.delta_indexed_by_sphinx? && toggled_delta?
356
+
357
+ rescue ::ThinkingSphinx::ConnectionError
358
+ # nothing
359
+ end
360
+
361
+ # Returns the unique integer id for the object. This method uses the
362
+ # attribute hash to get around ActiveRecord always mapping the #id method
363
+ # to whatever the real primary key is (which may be a unique string hash).
364
+ #
365
+ # @return [Integer] Unique record id for the purposes of Sphinx.
366
+ #
367
+ def primary_key_for_sphinx
368
+ read_attribute(self.class.primary_key_for_sphinx)
369
+ end
370
+
371
+ def sphinx_document_id
372
+ primary_key_for_sphinx * ThinkingSphinx.context.indexed_models.size +
373
+ self.class.sphinx_offset
374
+ end
375
+
376
+ private
377
+
378
+ def sphinx_index_name(suffix)
379
+ "#{self.class.source_of_sphinx_index.name.underscore.tr(':/\\', '_')}_#{suffix}"
380
+ end
381
+
382
+ def define_indexes
383
+ self.class.define_indexes
384
+ end
385
+ end
386
+ end
@@ -0,0 +1,87 @@
1
+ module ThinkingSphinx
2
+ class AbstractAdapter
3
+ def initialize(model)
4
+ @model = model
5
+ end
6
+
7
+ def setup
8
+ # Deliberately blank - subclasses should do something though. Well, if
9
+ # they need to.
10
+ end
11
+
12
+ def self.detect(model)
13
+ adapter = adapter_for_model model
14
+ case adapter
15
+ when :mysql
16
+ ThinkingSphinx::MysqlAdapter.new model
17
+ when :postgresql
18
+ ThinkingSphinx::PostgreSQLAdapter.new model
19
+ when Class
20
+ adapter.new model
21
+ else
22
+ raise "Invalid Database Adapter: Sphinx only supports MySQL and PostgreSQL, not #{adapter}"
23
+ end
24
+ end
25
+
26
+ def self.adapter_for_model(model)
27
+ case ThinkingSphinx.database_adapter
28
+ when String
29
+ ThinkingSphinx.database_adapter.to_sym
30
+ when NilClass
31
+ standard_adapter_for_model model
32
+ when Proc
33
+ ThinkingSphinx.database_adapter.call model
34
+ else
35
+ ThinkingSphinx.database_adapter
36
+ end
37
+ end
38
+
39
+ def self.standard_adapter_for_model(model)
40
+ case model.connection.class.name
41
+ when "ActiveRecord::ConnectionAdapters::MysqlAdapter",
42
+ "ActiveRecord::ConnectionAdapters::MysqlplusAdapter",
43
+ "ActiveRecord::ConnectionAdapters::Mysql2Adapter",
44
+ "ActiveRecord::ConnectionAdapters::NullDBAdapter"
45
+ :mysql
46
+ when "ActiveRecord::ConnectionAdapters::PostgreSQLAdapter"
47
+ :postgresql
48
+ when "ActiveRecord::ConnectionAdapters::JdbcAdapter"
49
+ case model.connection.config[:adapter]
50
+ when "jdbcmysql"
51
+ :mysql
52
+ when "jdbcpostgresql"
53
+ :postgresql
54
+ else
55
+ model.connection.config[:adapter].to_sym
56
+ end
57
+ else
58
+ model.connection.class.name
59
+ end
60
+ end
61
+
62
+ def quote_with_table(column)
63
+ "#{@model.quoted_table_name}.#{@model.connection.quote_column_name(column)}"
64
+ end
65
+
66
+ def bigint_pattern
67
+ /bigint/i
68
+ end
69
+
70
+ def downcase(clause)
71
+ "LOWER(#{clause})"
72
+ end
73
+
74
+ def case(expression, pairs, default)
75
+ "CASE #{expression} " +
76
+ pairs.keys.inject('') { |string, key|
77
+ string + "WHEN '#{key}' THEN #{pairs[key]} "
78
+ } + "ELSE #{default} END"
79
+ end
80
+
81
+ protected
82
+
83
+ def connection
84
+ @connection ||= @model.connection
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,62 @@
1
+ module ThinkingSphinx
2
+ class MysqlAdapter < AbstractAdapter
3
+ def setup
4
+ # Does MySQL actually need to do anything?
5
+ end
6
+
7
+ def sphinx_identifier
8
+ "mysql"
9
+ end
10
+
11
+ def concatenate(clause, separator = ' ')
12
+ "CONCAT_WS('#{separator}', #{clause})"
13
+ end
14
+
15
+ def group_concatenate(clause, separator = ' ')
16
+ "GROUP_CONCAT(DISTINCT IFNULL(#{clause}, '0') SEPARATOR '#{separator}')"
17
+ end
18
+
19
+ def cast_to_string(clause)
20
+ "CAST(#{clause} AS CHAR)"
21
+ end
22
+
23
+ def cast_to_datetime(clause)
24
+ "UNIX_TIMESTAMP(#{clause})"
25
+ end
26
+
27
+ def cast_to_unsigned(clause)
28
+ "CAST(#{clause} AS UNSIGNED)"
29
+ end
30
+
31
+ def cast_to_int(clause)
32
+ "CAST(#{clause} AS SIGNED)"
33
+ end
34
+
35
+ def convert_nulls(clause, default = '')
36
+ default = "'#{default}'" if default.is_a?(String)
37
+
38
+ "IFNULL(#{clause}, #{default})"
39
+ end
40
+
41
+ def boolean(value)
42
+ value ? 1 : 0
43
+ end
44
+
45
+ def crc(clause, blank_to_null = false)
46
+ clause = "NULLIF(#{clause},'')" if blank_to_null
47
+ "CRC32(#{clause})"
48
+ end
49
+
50
+ def utf8_query_pre
51
+ "SET NAMES utf8"
52
+ end
53
+
54
+ def time_difference(diff)
55
+ "DATE_SUB(NOW(), INTERVAL #{diff} SECOND)"
56
+ end
57
+
58
+ def utc_query_pre
59
+ "SET TIME_ZONE = '+0:00'"
60
+ end
61
+ end
62
+ end