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,375 @@
1
+ require 'erb'
2
+ require 'singleton'
3
+
4
+ module ThinkingSphinx
5
+ # This class both keeps track of the configuration settings for Sphinx and
6
+ # also generates the resulting file for Sphinx to use.
7
+ #
8
+ # Here are the default settings, relative to Rails.root where relevant:
9
+ #
10
+ # config file:: config/#{environment}.sphinx.conf
11
+ # searchd log file:: log/searchd.log
12
+ # query log file:: log/searchd.query.log
13
+ # pid file:: log/searchd.#{environment}.pid
14
+ # searchd files:: db/sphinx/#{environment}/
15
+ # address:: 127.0.0.1
16
+ # port:: 9312
17
+ # allow star:: false
18
+ # stop timeout:: 5
19
+ # min prefix length:: 1
20
+ # min infix length:: 1
21
+ # mem limit:: 64M
22
+ # max matches:: 1000
23
+ # morphology:: nil
24
+ # charset type:: utf-8
25
+ # charset table:: nil
26
+ # ignore chars:: nil
27
+ # html strip:: false
28
+ # html remove elements:: ''
29
+ # searchd_binary_name:: searchd
30
+ # indexer_binary_name:: indexer
31
+ # hard_retry_count:: 0
32
+ #
33
+ # If you want to change these settings, create a YAML file at
34
+ # config/sphinx.yml with settings for each environment, in a similar
35
+ # fashion to database.yml - using the following keys: config_file,
36
+ # searchd_log_file, query_log_file, pid_file, searchd_file_path, port,
37
+ # allow_star, enable_star, min_prefix_len, min_infix_len, mem_limit,
38
+ # max_matches, morphology, charset_type, charset_table, ignore_chars,
39
+ # html_strip, html_remove_elements, delayed_job_priority,
40
+ # searchd_binary_name, indexer_binary_name.
41
+ #
42
+ # I think you've got the idea.
43
+ #
44
+ # Each setting in the YAML file is optional - so only put in the ones you
45
+ # want to change.
46
+ #
47
+ # Keep in mind, if for some particular reason you're using a version of
48
+ # Sphinx older than 0.9.8 r871 (that's prior to the proper 0.9.8 release),
49
+ # don't set allow_star to true.
50
+ #
51
+ class Configuration
52
+ include Singleton
53
+
54
+ SourceOptions = Riddle::Configuration::SQLSource.settings.map { |setting|
55
+ setting.to_s
56
+ } - %w( type sql_query_pre sql_query sql_joined_field sql_file_field
57
+ sql_query_range sql_attr_uint sql_attr_bool sql_attr_bigint sql_query_info
58
+ sql_attr_timestamp sql_attr_str2ordinal sql_attr_float sql_attr_multi
59
+ sql_attr_string sql_attr_str2wordcount sql_column_buffers sql_field_string
60
+ sql_field_str2wordcount )
61
+ IndexOptions = Riddle::Configuration::Index.settings.map { |setting|
62
+ setting.to_s
63
+ } - %w( source prefix_fields infix_fields )
64
+ CustomOptions = %w( disable_range use_64_bit )
65
+
66
+ attr_accessor :searchd_file_path, :allow_star, :app_root,
67
+ :model_directories, :delayed_job_priority, :indexed_models, :use_64_bit,
68
+ :touched_reindex_file, :stop_timeout, :version, :shuffle,
69
+ :hard_retry_count
70
+
71
+ attr_accessor :source_options, :index_options
72
+
73
+ attr_reader :configuration, :controller
74
+
75
+ @@environment = nil
76
+
77
+ # Load in the configuration settings - this will look for config/sphinx.yml
78
+ # and parse it according to the current environment.
79
+ #
80
+ def initialize(app_root = Dir.pwd)
81
+ self.reset
82
+ end
83
+
84
+ def self.configure(&block)
85
+ yield instance
86
+ instance.reset(instance.app_root)
87
+ end
88
+
89
+ def reset(custom_app_root=nil)
90
+ if custom_app_root
91
+ self.app_root = custom_app_root
92
+ else
93
+ self.app_root = Merb.root if defined?(Merb)
94
+ self.app_root = Sinatra::Application.root if defined?(Sinatra)
95
+ self.app_root = Rails.root if defined?(Rails)
96
+ self.app_root ||= app_root
97
+ end
98
+
99
+ @configuration = Riddle::Configuration.new
100
+ @configuration.searchd.pid_file = "#{self.app_root}/log/searchd.#{environment}.pid"
101
+ @configuration.searchd.log = "#{self.app_root}/log/searchd.log"
102
+ @configuration.searchd.query_log = "#{self.app_root}/log/searchd.query.log"
103
+
104
+ @controller = Riddle::Controller.new @configuration,
105
+ "#{self.app_root}/config/#{environment}.sphinx.conf"
106
+
107
+ self.address = "127.0.0.1"
108
+ self.port = 9312
109
+ self.searchd_file_path = "#{self.app_root}/db/sphinx/#{environment}"
110
+ self.allow_star = false
111
+ self.stop_timeout = 5
112
+ self.model_directories = initial_model_directories
113
+ self.delayed_job_priority = 0
114
+ self.indexed_models = []
115
+ self.shuffle = false
116
+ self.hard_retry_count = 0
117
+
118
+ self.source_options = {}
119
+ self.index_options = {
120
+ :charset_type => "utf-8"
121
+ }
122
+
123
+ self.version = nil
124
+ parse_config
125
+ self.version ||= @controller.sphinx_version
126
+
127
+ ThinkingSphinx::Attribute::SphinxTypeMappings.merge!(
128
+ :string => :sql_attr_string
129
+ ) if Riddle.loaded_version.to_i > 1
130
+
131
+ self
132
+ end
133
+
134
+ def self.environment
135
+ @@environment ||= if defined?(Merb)
136
+ Merb.environment
137
+ elsif defined?(Rails)
138
+ Rails.env
139
+ elsif defined?(Sinatra)
140
+ Sinatra::Application.environment.to_s
141
+ else
142
+ ENV['RAILS_ENV'] || 'development'
143
+ end
144
+ end
145
+
146
+ def self.reset_environment
147
+ ThinkingSphinx.mutex.synchronize do
148
+ @@environment = nil
149
+ end
150
+ end
151
+
152
+ def environment
153
+ self.class.environment
154
+ end
155
+
156
+ def generate
157
+ @configuration.indices.clear
158
+
159
+ ThinkingSphinx.context.indexed_models.each do |model|
160
+ model = model.constantize
161
+ model.define_indexes
162
+ @configuration.indices.concat model.to_riddle
163
+
164
+ enforce_common_attribute_types
165
+ end
166
+ end
167
+
168
+ # Generate the config file for Sphinx by using all the settings defined and
169
+ # looping through all the models with indexes to build the relevant
170
+ # indexer and searchd configuration, and sources and indexes details.
171
+ #
172
+ def build(file_path=nil)
173
+ file_path ||= "#{self.config_file}"
174
+
175
+ generate
176
+
177
+ open(file_path, "w") do |file|
178
+ file.write @configuration.render
179
+ end
180
+ end
181
+
182
+ def address
183
+ @address
184
+ end
185
+
186
+ def address=(address)
187
+ @address = address
188
+ @configuration.searchd.address = address
189
+ end
190
+
191
+ def port
192
+ @port
193
+ end
194
+
195
+ def port=(port)
196
+ @port = port
197
+ @configuration.searchd.port = port
198
+ end
199
+
200
+ def use_socket=(use_socket)
201
+ if use_socket
202
+ socket = "#{app_root}/tmp/sockets/searchd.#{self.environment}.sock"
203
+ @configuration.searchd.listen = socket
204
+ self.address = socket
205
+ end
206
+ end
207
+
208
+ def pid_file
209
+ @configuration.searchd.pid_file
210
+ end
211
+
212
+ def pid_file=(pid_file)
213
+ @configuration.searchd.pid_file = pid_file
214
+ end
215
+
216
+ def searchd_log_file
217
+ @configuration.searchd.log
218
+ end
219
+
220
+ def searchd_log_file=(file)
221
+ @configuration.searchd.log = file
222
+ end
223
+
224
+ def query_log_file
225
+ @configuration.searchd.query_log
226
+ end
227
+
228
+ def query_log_file=(file)
229
+ @configuration.searchd.query_log = file
230
+ end
231
+
232
+ def config_file
233
+ @controller.path
234
+ end
235
+
236
+ def config_file=(file)
237
+ @controller.path = file
238
+ end
239
+
240
+ def bin_path
241
+ @controller.bin_path
242
+ end
243
+
244
+ def bin_path=(path)
245
+ @controller.bin_path = path
246
+ end
247
+
248
+ def searchd_binary_name
249
+ @controller.searchd_binary_name
250
+ end
251
+
252
+ def searchd_binary_name=(name)
253
+ @controller.searchd_binary_name = name
254
+ end
255
+
256
+ def indexer_binary_name
257
+ @controller.indexer_binary_name
258
+ end
259
+
260
+ def indexer_binary_name=(name)
261
+ @controller.indexer_binary_name = name
262
+ end
263
+
264
+ attr_accessor :timeout
265
+
266
+ def client
267
+ client = Riddle::Client.new shuffled_addresses, port,
268
+ configuration.searchd.client_key
269
+ client.max_matches = configuration.searchd.max_matches || 1000
270
+ client.timeout = timeout || 0
271
+ client
272
+ end
273
+
274
+ def models_by_crc
275
+ @models_by_crc ||= begin
276
+ ThinkingSphinx.context.indexed_models.inject({}) do |hash, model|
277
+ hash[model.constantize.to_crc32] = model
278
+ model.constantize.descendants.each { |subclass|
279
+ hash[subclass.to_crc32] = subclass.name
280
+ }
281
+ hash
282
+ end
283
+ end
284
+ end
285
+
286
+ def touch_reindex_file(output)
287
+ return FileUtils.touch(@touched_reindex_file) if @touched_reindex_file and output =~ /succesfully sent SIGHUP to searchd/
288
+ false
289
+ end
290
+
291
+ private
292
+
293
+ # Parse the config/sphinx.yml file - if it exists - then use the attribute
294
+ # accessors to set the appropriate values. Nothing too clever.
295
+ #
296
+ def parse_config
297
+ path = "#{app_root}/config/sphinx.yml"
298
+ return unless File.exists?(path)
299
+
300
+ conf = YAML::load(ERB.new(IO.read(path)).result)[environment]
301
+
302
+ conf.each do |key,value|
303
+ self.send("#{key}=", value) if self.respond_to?("#{key}=")
304
+
305
+ set_sphinx_setting self.source_options, key, value, SourceOptions
306
+ set_sphinx_setting self.index_options, key, value, IndexOptions
307
+ set_sphinx_setting self.index_options, key, value, CustomOptions
308
+ set_sphinx_setting @configuration.searchd, key, value
309
+ set_sphinx_setting @configuration.indexer, key, value
310
+ end unless conf.nil?
311
+
312
+ self.bin_path += '/' unless self.bin_path.blank?
313
+
314
+ if self.allow_star
315
+ self.index_options[:enable_star] = true
316
+ self.index_options[:min_prefix_len] = 1
317
+ end
318
+ end
319
+
320
+ def set_sphinx_setting(object, key, value, allowed = {})
321
+ if object.is_a?(Hash)
322
+ object[key.to_sym] = value if allowed.include?(key.to_s)
323
+ else
324
+ object.send("#{key}=", value) if object.respond_to?("#{key}")
325
+ send("#{key}=", value) if self.respond_to?("#{key}")
326
+ end
327
+ end
328
+
329
+ def enforce_common_attribute_types
330
+ sql_indexes = configuration.indices.reject { |index|
331
+ index.is_a? Riddle::Configuration::DistributedIndex
332
+ }
333
+
334
+ return unless sql_indexes.any? { |index|
335
+ index.sources.any? { |source|
336
+ source.sql_attr_bigint.include? :sphinx_internal_id
337
+ }
338
+ }
339
+
340
+ sql_indexes.each { |index|
341
+ index.sources.each { |source|
342
+ next if source.sql_attr_bigint.include? :sphinx_internal_id
343
+
344
+ source.sql_attr_bigint << :sphinx_internal_id
345
+ source.sql_attr_uint.delete :sphinx_internal_id
346
+ }
347
+ }
348
+ end
349
+
350
+ def shuffled_addresses
351
+ return address unless shuffle
352
+
353
+ addresses = Array(address)
354
+ if addresses.respond_to?(:shuffle)
355
+ addresses.shuffle
356
+ else
357
+ address.sort_by { rand }
358
+ end
359
+ end
360
+
361
+ def initial_model_directories
362
+ directories = ["#{app_root}/app/models/"] +
363
+ Dir.glob("#{app_root}/vendor/plugins/*/app/models/")
364
+
365
+ if defined?(Rails) && Rails.application
366
+ directories += Rails.application.paths['app/models'].to_a
367
+ directories += Rails.application.railties.engines.collect { |engine|
368
+ engine.paths['app/models'].to_a
369
+ }.flatten
370
+ end
371
+
372
+ directories
373
+ end
374
+ end
375
+ end
@@ -0,0 +1,76 @@
1
+ class ThinkingSphinx::Context
2
+ attr_reader :indexed_models
3
+
4
+ def initialize(*models)
5
+ @indexed_models = []
6
+ end
7
+
8
+ def prepare
9
+ ThinkingSphinx::Configuration.instance.indexed_models.each do |model|
10
+ add_indexed_model model
11
+ end
12
+
13
+ return unless indexed_models.empty?
14
+
15
+ load_models
16
+ add_indexed_models
17
+ end
18
+
19
+ def define_indexes
20
+ indexed_models.each { |model|
21
+ model.constantize.define_indexes
22
+ }
23
+ end
24
+
25
+ def add_indexed_model(model)
26
+ model = model.name if model.is_a?(Class)
27
+
28
+ indexed_models << model
29
+ indexed_models.uniq!
30
+ indexed_models.sort!
31
+ end
32
+
33
+ def superclass_indexed_models
34
+ klasses = indexed_models.collect { |name| name.constantize }
35
+ klasses.reject { |klass|
36
+ klass.superclass.ancestors.any? { |ancestor| klasses.include?(ancestor) }
37
+ }.collect { |klass| klass.name }
38
+ end
39
+
40
+ private
41
+
42
+ def add_indexed_models
43
+ ActiveRecord::Base.descendants.each do |klass|
44
+ add_indexed_model klass if klass.has_sphinx_indexes?
45
+ end
46
+ end
47
+
48
+ # Make sure all models are loaded - without reloading any that
49
+ # ActiveRecord::Base is already aware of (otherwise we start to hit some
50
+ # messy dependencies issues).
51
+ #
52
+ def load_models
53
+ ThinkingSphinx::Configuration.instance.model_directories.each do |base|
54
+ Dir["#{base}**/*.rb"].each do |file|
55
+ model_name = file.gsub(/^#{base}([\w_\/\\]+)\.rb/, '\1')
56
+
57
+ next if model_name.nil?
58
+ camelized_model = model_name.camelize
59
+ next if ::ActiveRecord::Base.descendants.detect { |model|
60
+ model.name == camelized_model
61
+ }
62
+
63
+ begin
64
+ camelized_model.constantize
65
+ rescue LoadError
66
+ # Make sure that STI subclasses in subfolders are loaded.
67
+ model_name.gsub!(/.*[\/\\]/, '').nil? ? next : retry
68
+ rescue Exception => err
69
+ STDERR.puts "Warning: Error loading #{file}:"
70
+ STDERR.puts err.message
71
+ STDERR.puts err.backtrace.join("\n"), ''
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,15 @@
1
+ require 'zlib'
2
+
3
+ module ThinkingSphinx
4
+ module Core
5
+ module String
6
+ def to_crc32
7
+ Zlib.crc32 self
8
+ end
9
+ end
10
+ end
11
+ end
12
+
13
+ class String
14
+ include ThinkingSphinx::Core::String
15
+ end
@@ -0,0 +1,62 @@
1
+ module ThinkingSphinx
2
+ module Deltas
3
+ class DefaultDelta
4
+ attr_accessor :column
5
+
6
+ def initialize(index, options)
7
+ @index = index
8
+ @column = options.delete(:delta_column) || :delta
9
+ end
10
+
11
+ def index(model, instance = nil)
12
+ return true unless ThinkingSphinx.updates_enabled? &&
13
+ ThinkingSphinx.deltas_enabled?
14
+ return true if instance && !toggled(instance)
15
+
16
+ update_delta_indexes model
17
+ delete_from_core model, instance if instance
18
+
19
+ true
20
+ end
21
+
22
+ def toggle(instance)
23
+ instance.send "#{@column}=", true
24
+ end
25
+
26
+ def toggled(instance)
27
+ instance.send "#{@column}"
28
+ end
29
+
30
+ def reset_query(model)
31
+ "UPDATE #{model.quoted_table_name} SET " +
32
+ "#{model.connection.quote_column_name(@column.to_s)} = #{adapter.boolean(false)} " +
33
+ "WHERE #{model.connection.quote_column_name(@column.to_s)} = #{adapter.boolean(true)}"
34
+ end
35
+
36
+ def clause(model, toggled)
37
+ "#{model.quoted_table_name}.#{model.connection.quote_column_name(@column.to_s)}" +
38
+ " = #{adapter.boolean(toggled)}"
39
+ end
40
+
41
+ private
42
+
43
+ def update_delta_indexes(model)
44
+ config = ThinkingSphinx::Configuration.instance
45
+ rotate = ThinkingSphinx.sphinx_running? ? "--rotate" : ""
46
+
47
+ output = `#{config.bin_path}#{config.indexer_binary_name} --config "#{config.config_file}" #{rotate} #{model.delta_index_names.join(' ')}`
48
+ puts(output) unless ThinkingSphinx.suppress_delta_output?
49
+ end
50
+
51
+ def delete_from_core(model, instance)
52
+ model.core_index_names.each do |index_name|
53
+ model.delete_in_index index_name, instance.sphinx_document_id
54
+ end
55
+ end
56
+
57
+ def adapter
58
+ @adapter = @index.model.sphinx_database_adapter
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,28 @@
1
+ require 'thinking_sphinx/deltas/default_delta'
2
+
3
+ module ThinkingSphinx
4
+ module Deltas
5
+ def self.parse(index)
6
+ delta_option = index.local_options.delete(:delta)
7
+ case delta_option
8
+ when TrueClass, :default
9
+ DefaultDelta.new index, index.local_options
10
+ when :delayed
11
+ DelayedDelta.new index, index.local_options
12
+ when :datetime
13
+ DatetimeDelta.new index, index.local_options
14
+ when FalseClass, nil
15
+ nil
16
+ else
17
+ if delta_option.is_a?(String)
18
+ delta_option = Kernel.const_get(delta_option)
19
+ end
20
+ if delta_option.ancestors.include?(ThinkingSphinx::Deltas::DefaultDelta)
21
+ delta_option.new index, index.local_options
22
+ else
23
+ raise "Unknown delta type"
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,99 @@
1
+ Capistrano::Configuration.instance(:must_exist).load do
2
+ namespace :thinking_sphinx do
3
+ namespace :install do
4
+ desc <<-DESC
5
+ Install Sphinx by source
6
+
7
+ If Postgres is available, Sphinx will use it.
8
+
9
+ If the variable :thinking_sphinx_configure_args is set, it will
10
+ be passed to the Sphinx configure script. You can use this to
11
+ install Sphinx in a non-standard location:
12
+
13
+ set :thinking_sphinx_configure_args, "--prefix=$HOME/software"
14
+ DESC
15
+
16
+ task :sphinx do
17
+ with_postgres = false
18
+ begin
19
+ run "which pg_config" do |channel, stream, data|
20
+ with_postgres = !(data.nil? || data == "")
21
+ end
22
+ rescue Capistrano::CommandError => e
23
+ puts "Continuing despite error: #{e.message}"
24
+ end
25
+
26
+ args = []
27
+ if with_postgres
28
+ run "pg_config --pkgincludedir" do |channel, stream, data|
29
+ args << "--with-pgsql=#{data}"
30
+ end
31
+ end
32
+ args << fetch(:thinking_sphinx_configure_args, '')
33
+
34
+ commands = <<-CMD
35
+ wget -q http://sphinxsearch.com/downloads/sphinx-0.9.9.tar.gz >> sphinx.log
36
+ tar xzvf sphinx-0.9.9.tar.gz
37
+ cd sphinx-0.9.9
38
+ ./configure #{args.join(" ")}
39
+ make
40
+ #{try_sudo} make install
41
+ rm -rf sphinx-0.9.9 sphinx-0.9.9.tar.gz
42
+ CMD
43
+ run commands.split(/\n\s+/).join(" && ")
44
+ end
45
+
46
+ desc "Install Thinking Sphinx as a gem"
47
+ task :ts do
48
+ run "#{try_sudo} gem install thinking-sphinx"
49
+ end
50
+ end
51
+
52
+ desc "Generate the Sphinx configuration file"
53
+ task :configure do
54
+ rake "thinking_sphinx:configure"
55
+ end
56
+
57
+ desc "Index data"
58
+ task :index do
59
+ rake "thinking_sphinx:index"
60
+ end
61
+
62
+ desc "Start the Sphinx daemon"
63
+ task :start do
64
+ rake "thinking_sphinx:configure thinking_sphinx:start"
65
+ end
66
+
67
+ desc "Stop the Sphinx daemon"
68
+ task :stop do
69
+ rake "thinking_sphinx:configure thinking_sphinx:stop"
70
+ end
71
+
72
+ desc "Stop and then start the Sphinx daemon"
73
+ task :restart do
74
+ rake "thinking_sphinx:configure thinking_sphinx:stop \
75
+ thinking_sphinx:start"
76
+ end
77
+
78
+ desc "Stop, re-index and then start the Sphinx daemon"
79
+ task :rebuild do
80
+ rake "thinking_sphinx:configure thinking_sphinx:stop \
81
+ thinking_sphinx:reindex \
82
+ thinking_sphinx:start"
83
+ end
84
+
85
+ desc "Add the shared folder for sphinx files"
86
+ task :shared_sphinx_folder, :roles => :web do
87
+ rails_env = fetch(:rails_env, "production")
88
+ run "mkdir -p #{shared_path}/sphinx/#{rails_env}"
89
+ end
90
+
91
+ def rake(*tasks)
92
+ rails_env = fetch(:rails_env, "production")
93
+ rake = fetch(:rake, "rake")
94
+ tasks.each do |t|
95
+ run "if [ -d #{release_path} ]; then cd #{release_path}; else cd #{current_path}; fi; if [ -f Rakefile ]; then #{rake} RAILS_ENV=#{rails_env} #{t}; fi;"
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,23 @@
1
+ module ThinkingSphinx
2
+ class Excerpter
3
+ CoreMethods = %w( kind_of? object_id respond_to? respond_to_missing? should
4
+ should_not stub! )
5
+ # Hide most methods, to allow them to be passed through to the instance.
6
+ instance_methods.select { |method|
7
+ method.to_s[/^__/].nil? && !CoreMethods.include?(method.to_s)
8
+ }.each { |method|
9
+ undef_method method
10
+ }
11
+
12
+ def initialize(search, instance)
13
+ @search = search
14
+ @instance = instance
15
+ end
16
+
17
+ def method_missing(method, *args, &block)
18
+ string = @instance.send(method, *args, &block).to_s
19
+
20
+ @search.excerpt_for(string, @instance.class)
21
+ end
22
+ end
23
+ end