friendlyfashion-thinking-sphinx 2.0.13

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