thinking-sphinx 2.1.0 → 3.0.0.pre

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 (352) hide show
  1. data/.gitignore +8 -0
  2. data/.travis.yml +13 -0
  3. data/Appraisals +7 -0
  4. data/Gemfile +10 -0
  5. data/HISTORY +2 -267
  6. data/LICENCE +1 -1
  7. data/README.textile +194 -226
  8. data/Rakefile +24 -0
  9. data/gemfiles/.gitignore +1 -0
  10. data/gemfiles/rails_3_1.gemfile +11 -0
  11. data/gemfiles/rails_3_2.gemfile +11 -0
  12. data/lib/thinking-sphinx.rb +1 -1
  13. data/lib/thinking_sphinx.rb +34 -292
  14. data/lib/thinking_sphinx/active_record.rb +22 -383
  15. data/lib/thinking_sphinx/active_record/association.rb +9 -0
  16. data/lib/thinking_sphinx/active_record/association_proxy.rb +68 -0
  17. data/lib/thinking_sphinx/active_record/associations.rb +68 -0
  18. data/lib/thinking_sphinx/active_record/attribute.rb +20 -0
  19. data/lib/thinking_sphinx/active_record/attribute/sphinx_presenter.rb +32 -0
  20. data/lib/thinking_sphinx/active_record/attribute/type.rb +79 -0
  21. data/lib/thinking_sphinx/active_record/attribute/values.rb +18 -0
  22. data/lib/thinking_sphinx/active_record/base.rb +36 -0
  23. data/lib/thinking_sphinx/active_record/callbacks/delete_callbacks.rb +31 -0
  24. data/lib/thinking_sphinx/active_record/callbacks/delta_callbacks.rb +55 -0
  25. data/lib/thinking_sphinx/active_record/callbacks/update_callbacks.rb +59 -0
  26. data/lib/thinking_sphinx/active_record/column.rb +30 -0
  27. data/lib/thinking_sphinx/active_record/database_adapters.rb +51 -0
  28. data/lib/thinking_sphinx/active_record/database_adapters/abstract_adapter.rb +13 -0
  29. data/lib/thinking_sphinx/active_record/database_adapters/mysql_adapter.rb +23 -0
  30. data/lib/thinking_sphinx/active_record/database_adapters/postgresql_adapter.rb +25 -0
  31. data/lib/thinking_sphinx/active_record/field.rb +11 -0
  32. data/lib/thinking_sphinx/active_record/index.rb +55 -0
  33. data/lib/thinking_sphinx/active_record/interpreter.rb +47 -0
  34. data/lib/thinking_sphinx/active_record/log_subscriber.rb +10 -58
  35. data/lib/thinking_sphinx/active_record/property.rb +28 -0
  36. data/lib/thinking_sphinx/active_record/property_sql_presenter.rb +60 -0
  37. data/lib/thinking_sphinx/active_record/sql_builder.rb +159 -0
  38. data/lib/thinking_sphinx/active_record/sql_source.rb +138 -0
  39. data/lib/thinking_sphinx/active_record/sql_source/template.rb +46 -0
  40. data/lib/thinking_sphinx/batched_search.rb +26 -0
  41. data/lib/thinking_sphinx/callbacks.rb +15 -0
  42. data/lib/thinking_sphinx/configuration.rb +80 -331
  43. data/lib/thinking_sphinx/configuration/consistent_ids.rb +31 -0
  44. data/lib/thinking_sphinx/configuration/defaults.rb +5 -0
  45. data/lib/thinking_sphinx/core.rb +6 -0
  46. data/lib/thinking_sphinx/core/index.rb +68 -0
  47. data/lib/thinking_sphinx/core/interpreter.rb +19 -0
  48. data/lib/thinking_sphinx/deltas.rb +35 -26
  49. data/lib/thinking_sphinx/deltas/default_delta.rb +56 -56
  50. data/lib/thinking_sphinx/excerpter.rb +23 -21
  51. data/lib/thinking_sphinx/facet.rb +22 -127
  52. data/lib/thinking_sphinx/facet_search.rb +95 -162
  53. data/lib/thinking_sphinx/index.rb +39 -143
  54. data/lib/thinking_sphinx/index_set.rb +51 -0
  55. data/lib/thinking_sphinx/masks.rb +8 -0
  56. data/lib/thinking_sphinx/masks/group_enumerators_mask.rb +23 -0
  57. data/lib/thinking_sphinx/masks/pagination_mask.rb +60 -0
  58. data/lib/thinking_sphinx/masks/scopes_mask.rb +35 -0
  59. data/lib/thinking_sphinx/masks/weight_enumerator_mask.rb +11 -0
  60. data/lib/thinking_sphinx/middlewares.rb +36 -0
  61. data/lib/thinking_sphinx/middlewares/active_record_translator.rb +73 -0
  62. data/lib/thinking_sphinx/middlewares/geographer.rb +53 -0
  63. data/lib/thinking_sphinx/middlewares/glazier.rb +39 -0
  64. data/lib/thinking_sphinx/middlewares/ids_only.rb +13 -0
  65. data/lib/thinking_sphinx/middlewares/inquirer.rb +62 -0
  66. data/lib/thinking_sphinx/middlewares/middleware.rb +9 -0
  67. data/lib/thinking_sphinx/middlewares/sphinxql.rb +149 -0
  68. data/lib/thinking_sphinx/middlewares/stale_id_checker.rb +45 -0
  69. data/lib/thinking_sphinx/middlewares/stale_id_filter.rb +46 -0
  70. data/lib/thinking_sphinx/panes.rb +8 -0
  71. data/lib/thinking_sphinx/panes/attributes_pane.rb +9 -0
  72. data/lib/thinking_sphinx/panes/distance_pane.rb +13 -0
  73. data/lib/thinking_sphinx/panes/excerpts_pane.rb +37 -0
  74. data/lib/thinking_sphinx/panes/weight_pane.rb +9 -0
  75. data/lib/thinking_sphinx/railtie.rb +6 -40
  76. data/lib/thinking_sphinx/rake_interface.rb +47 -0
  77. data/lib/thinking_sphinx/real_time.rb +11 -0
  78. data/lib/thinking_sphinx/real_time/attribute.rb +5 -0
  79. data/lib/thinking_sphinx/real_time/callbacks/real_time_callbacks.rb +48 -0
  80. data/lib/thinking_sphinx/real_time/field.rb +3 -0
  81. data/lib/thinking_sphinx/real_time/index.rb +47 -0
  82. data/lib/thinking_sphinx/real_time/index/template.rb +33 -0
  83. data/lib/thinking_sphinx/real_time/interpreter.rb +23 -0
  84. data/lib/thinking_sphinx/real_time/property.rb +16 -0
  85. data/lib/thinking_sphinx/scopes.rb +22 -0
  86. data/lib/thinking_sphinx/search.rb +90 -1028
  87. data/lib/thinking_sphinx/search/batch_inquirer.rb +27 -0
  88. data/lib/thinking_sphinx/search/context.rb +26 -0
  89. data/lib/thinking_sphinx/search/glaze.rb +32 -0
  90. data/lib/thinking_sphinx/search/merger.rb +24 -0
  91. data/lib/thinking_sphinx/search/query.rb +43 -0
  92. data/lib/thinking_sphinx/search/stale_ids_exception.rb +11 -0
  93. data/lib/thinking_sphinx/search/translator.rb +50 -0
  94. data/lib/thinking_sphinx/tasks.rb +22 -125
  95. data/lib/thinking_sphinx/test.rb +9 -19
  96. data/sketchpad.rb +58 -0
  97. data/spec/acceptance/association_scoping_spec.rb +23 -0
  98. data/spec/acceptance/attribute_access_spec.rb +39 -0
  99. data/spec/acceptance/attribute_updates_spec.rb +16 -0
  100. data/spec/acceptance/batch_searching_spec.rb +21 -0
  101. data/spec/acceptance/big_integers_spec.rb +27 -0
  102. data/spec/acceptance/excerpts_spec.rb +14 -0
  103. data/spec/acceptance/facets_spec.rb +122 -0
  104. data/spec/acceptance/geosearching_spec.rb +39 -0
  105. data/spec/acceptance/grouping_by_attributes_spec.rb +77 -0
  106. data/spec/acceptance/paginating_search_results_spec.rb +24 -0
  107. data/spec/acceptance/remove_deleted_records_spec.rb +23 -0
  108. data/spec/acceptance/search_counts_spec.rb +18 -0
  109. data/spec/acceptance/search_for_just_ids_spec.rb +19 -0
  110. data/spec/acceptance/searching_across_models_spec.rb +28 -0
  111. data/spec/acceptance/searching_on_fields_spec.rb +56 -0
  112. data/spec/acceptance/searching_with_filters_spec.rb +109 -0
  113. data/spec/acceptance/searching_with_sti_spec.rb +55 -0
  114. data/spec/acceptance/searching_within_a_model_spec.rb +52 -0
  115. data/spec/acceptance/sorting_search_results_spec.rb +41 -0
  116. data/spec/acceptance/spec_helper.rb +4 -0
  117. data/spec/acceptance/specifying_sql_spec.rb +62 -0
  118. data/spec/acceptance/sphinx_scopes_spec.rb +49 -0
  119. data/spec/acceptance/sql_deltas_spec.rb +43 -0
  120. data/spec/acceptance/support/database_cleaner.rb +11 -0
  121. data/spec/acceptance/support/sphinx_controller.rb +39 -0
  122. data/spec/acceptance/support/sphinx_helpers.rb +24 -0
  123. data/spec/acceptance/suspended_deltas_spec.rb +20 -0
  124. data/spec/internal/.gitignore +1 -0
  125. data/spec/internal/app/indices/animal_index.rb +3 -0
  126. data/spec/internal/app/indices/article_index.rb +24 -0
  127. data/spec/internal/app/indices/book_index.rb +8 -0
  128. data/spec/internal/app/indices/city_index.rb +6 -0
  129. data/spec/internal/app/indices/product_index.rb +3 -0
  130. data/spec/internal/app/indices/tee_index.rb +4 -0
  131. data/spec/internal/app/indices/user_index.rb +5 -0
  132. data/spec/internal/app/models/animal.rb +2 -0
  133. data/spec/internal/app/models/article.rb +5 -0
  134. data/spec/internal/app/models/bird.rb +2 -0
  135. data/spec/internal/app/models/book.rb +11 -0
  136. data/spec/internal/app/models/city.rb +2 -0
  137. data/spec/internal/app/models/colour.rb +3 -0
  138. data/spec/internal/app/models/flightless_bird.rb +2 -0
  139. data/spec/internal/app/models/mammal.rb +2 -0
  140. data/spec/internal/app/models/product.rb +3 -0
  141. data/spec/internal/app/models/tag.rb +4 -0
  142. data/{features/thinking_sphinx → spec/internal/app}/models/tagging.rb +1 -1
  143. data/spec/internal/app/models/tee.rb +3 -0
  144. data/spec/internal/app/models/tweet.rb +3 -0
  145. data/spec/internal/app/models/user.rb +3 -0
  146. data/spec/internal/config/database.yml +5 -0
  147. data/spec/internal/db/schema.rb +65 -0
  148. data/spec/internal/log/.gitignore +1 -0
  149. data/spec/spec_helper.rb +8 -49
  150. data/spec/support/sphinx_yaml_helpers.rb +9 -0
  151. data/spec/thinking_sphinx/active_record/association_spec.rb +12 -0
  152. data/spec/thinking_sphinx/active_record/associations_spec.rb +184 -0
  153. data/spec/thinking_sphinx/active_record/attribute/type_spec.rb +147 -0
  154. data/spec/thinking_sphinx/active_record/base_spec.rb +61 -0
  155. data/spec/thinking_sphinx/active_record/callbacks/delete_callbacks_spec.rb +80 -0
  156. data/spec/thinking_sphinx/active_record/callbacks/delta_callbacks_spec.rb +147 -0
  157. data/spec/thinking_sphinx/active_record/callbacks/update_callbacks_spec.rb +69 -0
  158. data/spec/thinking_sphinx/active_record/column_spec.rb +47 -0
  159. data/spec/thinking_sphinx/active_record/database_adapters/abstract_adapter_spec.rb +31 -0
  160. data/spec/thinking_sphinx/active_record/database_adapters/mysql_adapter_spec.rb +43 -0
  161. data/spec/thinking_sphinx/active_record/database_adapters/postgresql_adapter_spec.rb +45 -0
  162. data/spec/thinking_sphinx/active_record/database_adapters_spec.rb +108 -0
  163. data/spec/thinking_sphinx/active_record/field_spec.rb +36 -0
  164. data/spec/thinking_sphinx/active_record/index_spec.rb +208 -0
  165. data/spec/thinking_sphinx/active_record/interpreter_spec.rb +293 -0
  166. data/spec/thinking_sphinx/active_record/property_sql_presenter_spec.rb +162 -0
  167. data/spec/thinking_sphinx/active_record/sql_builder_spec.rb +666 -0
  168. data/spec/thinking_sphinx/active_record/sql_source_spec.rb +401 -0
  169. data/spec/thinking_sphinx/configuration_spec.rb +264 -171
  170. data/spec/thinking_sphinx/deltas/default_delta_spec.rb +116 -0
  171. data/spec/thinking_sphinx/deltas_spec.rb +58 -0
  172. data/spec/thinking_sphinx/excerpter_spec.rb +40 -38
  173. data/spec/thinking_sphinx/facet_search_spec.rb +49 -151
  174. data/spec/thinking_sphinx/index_set_spec.rb +68 -0
  175. data/spec/thinking_sphinx/index_spec.rb +91 -155
  176. data/spec/thinking_sphinx/masks/pagination_mask_spec.rb +121 -0
  177. data/spec/thinking_sphinx/masks/scopes_mask_spec.rb +68 -0
  178. data/spec/thinking_sphinx/middlewares/active_record_translator_spec.rb +132 -0
  179. data/spec/thinking_sphinx/middlewares/geographer_spec.rb +89 -0
  180. data/spec/thinking_sphinx/middlewares/glazier_spec.rb +62 -0
  181. data/spec/thinking_sphinx/middlewares/inquirer_spec.rb +55 -0
  182. data/spec/thinking_sphinx/middlewares/sphinxql_spec.rb +271 -0
  183. data/spec/thinking_sphinx/middlewares/stale_id_checker_spec.rb +47 -0
  184. data/spec/thinking_sphinx/middlewares/stale_id_filter_spec.rb +91 -0
  185. data/spec/thinking_sphinx/panes/attributes_pane_spec.rb +21 -0
  186. data/spec/thinking_sphinx/panes/distance_pane_spec.rb +41 -0
  187. data/spec/thinking_sphinx/panes/excerpts_pane_spec.rb +53 -0
  188. data/spec/thinking_sphinx/panes/weight_pane_spec.rb +20 -0
  189. data/spec/thinking_sphinx/rake_interface_spec.rb +147 -0
  190. data/spec/thinking_sphinx/real_time/attribute_spec.rb +62 -0
  191. data/spec/thinking_sphinx/real_time/callbacks/real_time_callbacks_spec.rb +76 -0
  192. data/spec/thinking_sphinx/real_time/field_spec.rb +54 -0
  193. data/spec/thinking_sphinx/real_time/index_spec.rb +154 -0
  194. data/spec/thinking_sphinx/real_time/interpreter_spec.rb +147 -0
  195. data/spec/thinking_sphinx/scopes_spec.rb +38 -0
  196. data/spec/thinking_sphinx/search/glaze_spec.rb +55 -0
  197. data/spec/thinking_sphinx/search/query_spec.rb +46 -0
  198. data/spec/thinking_sphinx/search_spec.rb +65 -1357
  199. data/spec/thinking_sphinx_spec.rb +19 -182
  200. data/thinking-sphinx.gemspec +33 -0
  201. metadata +318 -431
  202. data/features/abstract_inheritance.feature +0 -10
  203. data/features/alternate_primary_key.feature +0 -27
  204. data/features/attribute_transformation.feature +0 -22
  205. data/features/attribute_updates.feature +0 -79
  206. data/features/deleting_instances.feature +0 -70
  207. data/features/direct_attributes.feature +0 -11
  208. data/features/excerpts.feature +0 -21
  209. data/features/extensible_delta_indexing.feature +0 -9
  210. data/features/facets.feature +0 -88
  211. data/features/facets_across_model.feature +0 -29
  212. data/features/field_sorting.feature +0 -18
  213. data/features/handling_edits.feature +0 -97
  214. data/features/retry_stale_indexes.feature +0 -24
  215. data/features/searching_across_models.feature +0 -20
  216. data/features/searching_by_index.feature +0 -41
  217. data/features/searching_by_model.feature +0 -175
  218. data/features/searching_with_find_arguments.feature +0 -56
  219. data/features/sphinx_detection.feature +0 -25
  220. data/features/sphinx_scopes.feature +0 -68
  221. data/features/step_definitions/alpha_steps.rb +0 -16
  222. data/features/step_definitions/beta_steps.rb +0 -7
  223. data/features/step_definitions/common_steps.rb +0 -205
  224. data/features/step_definitions/extensible_delta_indexing_steps.rb +0 -7
  225. data/features/step_definitions/facet_steps.rb +0 -96
  226. data/features/step_definitions/find_arguments_steps.rb +0 -36
  227. data/features/step_definitions/gamma_steps.rb +0 -15
  228. data/features/step_definitions/scope_steps.rb +0 -19
  229. data/features/step_definitions/search_steps.rb +0 -94
  230. data/features/step_definitions/sphinx_steps.rb +0 -35
  231. data/features/sti_searching.feature +0 -19
  232. data/features/support/env.rb +0 -24
  233. data/features/support/lib/generic_delta_handler.rb +0 -8
  234. data/features/thinking_sphinx/database.example.yml +0 -3
  235. data/features/thinking_sphinx/db/.gitignore +0 -1
  236. data/features/thinking_sphinx/db/fixtures/alphas.rb +0 -8
  237. data/features/thinking_sphinx/db/fixtures/authors.rb +0 -1
  238. data/features/thinking_sphinx/db/fixtures/betas.rb +0 -11
  239. data/features/thinking_sphinx/db/fixtures/boxes.rb +0 -9
  240. data/features/thinking_sphinx/db/fixtures/categories.rb +0 -1
  241. data/features/thinking_sphinx/db/fixtures/cats.rb +0 -3
  242. data/features/thinking_sphinx/db/fixtures/comments.rb +0 -24
  243. data/features/thinking_sphinx/db/fixtures/developers.rb +0 -31
  244. data/features/thinking_sphinx/db/fixtures/dogs.rb +0 -3
  245. data/features/thinking_sphinx/db/fixtures/extensible_betas.rb +0 -10
  246. data/features/thinking_sphinx/db/fixtures/foxes.rb +0 -3
  247. data/features/thinking_sphinx/db/fixtures/gammas.rb +0 -10
  248. data/features/thinking_sphinx/db/fixtures/music.rb +0 -4
  249. data/features/thinking_sphinx/db/fixtures/people.rb +0 -1001
  250. data/features/thinking_sphinx/db/fixtures/post_keywords.txt +0 -1
  251. data/features/thinking_sphinx/db/fixtures/posts.rb +0 -10
  252. data/features/thinking_sphinx/db/fixtures/robots.rb +0 -8
  253. data/features/thinking_sphinx/db/fixtures/tags.rb +0 -27
  254. data/features/thinking_sphinx/db/migrations/create_alphas.rb +0 -8
  255. data/features/thinking_sphinx/db/migrations/create_animals.rb +0 -5
  256. data/features/thinking_sphinx/db/migrations/create_authors.rb +0 -3
  257. data/features/thinking_sphinx/db/migrations/create_authors_posts.rb +0 -6
  258. data/features/thinking_sphinx/db/migrations/create_betas.rb +0 -5
  259. data/features/thinking_sphinx/db/migrations/create_boxes.rb +0 -5
  260. data/features/thinking_sphinx/db/migrations/create_categories.rb +0 -3
  261. data/features/thinking_sphinx/db/migrations/create_comments.rb +0 -10
  262. data/features/thinking_sphinx/db/migrations/create_developers.rb +0 -7
  263. data/features/thinking_sphinx/db/migrations/create_extensible_betas.rb +0 -5
  264. data/features/thinking_sphinx/db/migrations/create_gammas.rb +0 -3
  265. data/features/thinking_sphinx/db/migrations/create_genres.rb +0 -3
  266. data/features/thinking_sphinx/db/migrations/create_music.rb +0 -6
  267. data/features/thinking_sphinx/db/migrations/create_people.rb +0 -13
  268. data/features/thinking_sphinx/db/migrations/create_posts.rb +0 -6
  269. data/features/thinking_sphinx/db/migrations/create_robots.rb +0 -4
  270. data/features/thinking_sphinx/db/migrations/create_taggings.rb +0 -5
  271. data/features/thinking_sphinx/db/migrations/create_tags.rb +0 -4
  272. data/features/thinking_sphinx/models/alpha.rb +0 -23
  273. data/features/thinking_sphinx/models/andrew.rb +0 -17
  274. data/features/thinking_sphinx/models/animal.rb +0 -5
  275. data/features/thinking_sphinx/models/author.rb +0 -3
  276. data/features/thinking_sphinx/models/beta.rb +0 -13
  277. data/features/thinking_sphinx/models/box.rb +0 -8
  278. data/features/thinking_sphinx/models/cat.rb +0 -3
  279. data/features/thinking_sphinx/models/category.rb +0 -4
  280. data/features/thinking_sphinx/models/comment.rb +0 -10
  281. data/features/thinking_sphinx/models/developer.rb +0 -21
  282. data/features/thinking_sphinx/models/dog.rb +0 -3
  283. data/features/thinking_sphinx/models/extensible_beta.rb +0 -9
  284. data/features/thinking_sphinx/models/fox.rb +0 -5
  285. data/features/thinking_sphinx/models/gamma.rb +0 -5
  286. data/features/thinking_sphinx/models/genre.rb +0 -3
  287. data/features/thinking_sphinx/models/medium.rb +0 -5
  288. data/features/thinking_sphinx/models/music.rb +0 -10
  289. data/features/thinking_sphinx/models/person.rb +0 -24
  290. data/features/thinking_sphinx/models/post.rb +0 -22
  291. data/features/thinking_sphinx/models/robot.rb +0 -12
  292. data/features/thinking_sphinx/models/tag.rb +0 -3
  293. data/lib/cucumber/thinking_sphinx/external_world.rb +0 -12
  294. data/lib/cucumber/thinking_sphinx/internal_world.rb +0 -137
  295. data/lib/cucumber/thinking_sphinx/sql_logger.rb +0 -28
  296. data/lib/thinking_sphinx/action_controller.rb +0 -31
  297. data/lib/thinking_sphinx/active_record/attribute_updates.rb +0 -54
  298. data/lib/thinking_sphinx/active_record/collection_proxy.rb +0 -47
  299. data/lib/thinking_sphinx/active_record/collection_proxy_with_scopes.rb +0 -27
  300. data/lib/thinking_sphinx/active_record/delta.rb +0 -67
  301. data/lib/thinking_sphinx/active_record/has_many_association.rb +0 -44
  302. data/lib/thinking_sphinx/active_record/has_many_association_with_scopes.rb +0 -21
  303. data/lib/thinking_sphinx/active_record/scopes.rb +0 -110
  304. data/lib/thinking_sphinx/adapters/abstract_adapter.rb +0 -94
  305. data/lib/thinking_sphinx/adapters/mysql_adapter.rb +0 -62
  306. data/lib/thinking_sphinx/adapters/postgresql_adapter.rb +0 -188
  307. data/lib/thinking_sphinx/association.rb +0 -230
  308. data/lib/thinking_sphinx/attribute.rb +0 -405
  309. data/lib/thinking_sphinx/auto_version.rb +0 -40
  310. data/lib/thinking_sphinx/bundled_search.rb +0 -40
  311. data/lib/thinking_sphinx/class_facet.rb +0 -20
  312. data/lib/thinking_sphinx/connection.rb +0 -71
  313. data/lib/thinking_sphinx/context.rb +0 -81
  314. data/lib/thinking_sphinx/core/string.rb +0 -15
  315. data/lib/thinking_sphinx/deltas/delete_job.rb +0 -16
  316. data/lib/thinking_sphinx/deltas/index_job.rb +0 -17
  317. data/lib/thinking_sphinx/deploy/capistrano.rb +0 -99
  318. data/lib/thinking_sphinx/field.rb +0 -98
  319. data/lib/thinking_sphinx/index/builder.rb +0 -315
  320. data/lib/thinking_sphinx/index/faux_column.rb +0 -118
  321. data/lib/thinking_sphinx/join.rb +0 -37
  322. data/lib/thinking_sphinx/property.rb +0 -187
  323. data/lib/thinking_sphinx/search_methods.rb +0 -439
  324. data/lib/thinking_sphinx/sinatra.rb +0 -7
  325. data/lib/thinking_sphinx/source.rb +0 -194
  326. data/lib/thinking_sphinx/source/internal_properties.rb +0 -51
  327. data/lib/thinking_sphinx/source/sql.rb +0 -174
  328. data/spec/fixtures/data.sql +0 -32
  329. data/spec/fixtures/database.yml.default +0 -3
  330. data/spec/fixtures/models.rb +0 -164
  331. data/spec/fixtures/structure.sql +0 -146
  332. data/spec/sphinx_helper.rb +0 -60
  333. data/spec/support/rails.rb +0 -25
  334. data/spec/thinking_sphinx/active_record/delta_spec.rb +0 -123
  335. data/spec/thinking_sphinx/active_record/has_many_association_spec.rb +0 -173
  336. data/spec/thinking_sphinx/active_record/scopes_spec.rb +0 -177
  337. data/spec/thinking_sphinx/active_record_spec.rb +0 -573
  338. data/spec/thinking_sphinx/adapters/abstract_adapter_spec.rb +0 -163
  339. data/spec/thinking_sphinx/association_spec.rb +0 -250
  340. data/spec/thinking_sphinx/attribute_spec.rb +0 -552
  341. data/spec/thinking_sphinx/auto_version_spec.rb +0 -103
  342. data/spec/thinking_sphinx/connection_spec.rb +0 -77
  343. data/spec/thinking_sphinx/context_spec.rb +0 -127
  344. data/spec/thinking_sphinx/core/array_spec.rb +0 -9
  345. data/spec/thinking_sphinx/core/string_spec.rb +0 -9
  346. data/spec/thinking_sphinx/facet_spec.rb +0 -359
  347. data/spec/thinking_sphinx/field_spec.rb +0 -127
  348. data/spec/thinking_sphinx/index/builder_spec.rb +0 -532
  349. data/spec/thinking_sphinx/index/faux_column_spec.rb +0 -36
  350. data/spec/thinking_sphinx/search_methods_spec.rb +0 -156
  351. data/spec/thinking_sphinx/source_spec.rb +0 -267
  352. data/spec/thinking_sphinx/test_spec.rb +0 -20
@@ -0,0 +1,5 @@
1
+ class ThinkingSphinx::RealTime::Attribute < ThinkingSphinx::RealTime::Property
2
+ def type
3
+ @options[:type]
4
+ end
5
+ end
@@ -0,0 +1,48 @@
1
+ class ThinkingSphinx::RealTime::Callbacks::RealTimeCallbacks <
2
+ ThinkingSphinx::Callbacks
3
+
4
+ callbacks :after_save
5
+
6
+ def after_save
7
+ return unless real_time_indices?
8
+
9
+ real_time_indices.each do |index|
10
+ columns, values = ['id'], [index.document_id_for_key(instance.id)]
11
+ (index.fields + index.attributes).each do |property|
12
+ columns << property.name
13
+ values << property.translate(instance)
14
+ end
15
+
16
+ sphinxql = Riddle::Query::Insert.new(index.name, columns, values).replace!
17
+ connection.query sphinxql.to_sql
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def config
24
+ ThinkingSphinx::Configuration.instance
25
+ end
26
+
27
+ def connection
28
+ connection = config.connection
29
+ end
30
+
31
+ def indices
32
+ @indices ||= config.indices_for_references reference
33
+ end
34
+
35
+ def real_time_indices?
36
+ real_time_indices.any?
37
+ end
38
+
39
+ def real_time_indices
40
+ @real_time_indices ||= indices.select { |index|
41
+ index.is_a? ThinkingSphinx::RealTime::Index
42
+ }
43
+ end
44
+
45
+ def reference
46
+ instance.class.name.underscore.to_sym
47
+ end
48
+ end
@@ -0,0 +1,3 @@
1
+ class ThinkingSphinx::RealTime::Field < ThinkingSphinx::RealTime::Property
2
+ #
3
+ end
@@ -0,0 +1,47 @@
1
+ class ThinkingSphinx::RealTime::Index < Riddle::Configuration::RealtimeIndex
2
+ include ThinkingSphinx::Core::Index
3
+
4
+ attr_accessor :fields, :attributes
5
+
6
+ def initialize(reference, options = {})
7
+ @fields = []
8
+ @attributes = []
9
+
10
+ Template.new(self).apply
11
+
12
+ super reference, options
13
+ end
14
+
15
+ def unique_attribute_names
16
+ attributes.collect(&:name)
17
+ end
18
+
19
+ private
20
+
21
+ def interpreter
22
+ ThinkingSphinx::RealTime::Interpreter
23
+ end
24
+
25
+ def pre_render
26
+ super
27
+
28
+ @rt_field = fields.collect &:name
29
+
30
+ attributes.each do |attribute|
31
+ case attribute.type
32
+ when :integer, :boolean
33
+ @rt_attr_uint << attribute.name unless @rt_attr_uint.include?(attribute.name)
34
+ when :string
35
+ @rt_attr_string << attribute.name unless @rt_attr_string.include?(attribute.name)
36
+ when :timestamp
37
+ @rt_attr_timestamp << attribute.name unless @rt_attr_timestamp.include?(attribute.name)
38
+ when :float
39
+ @rt_attr_float << attribute.name unless @rt_attr_float.include?(attribute.name)
40
+ else
41
+ raise "Unknown attribute type '#{attribute.type(model)}'"
42
+ end
43
+ end
44
+ end
45
+ end
46
+
47
+ require 'thinking_sphinx/real_time/index/template'
@@ -0,0 +1,33 @@
1
+ class ThinkingSphinx::RealTime::Index::Template
2
+ attr_reader :index
3
+
4
+ def initialize(index)
5
+ @index = index
6
+ end
7
+
8
+ def apply
9
+ add_field class_column, :sphinx_internal_class
10
+
11
+ add_attribute :id, :sphinx_internal_id, :integer
12
+ add_attribute 0, :sphinx_deleted, :integer
13
+ end
14
+
15
+ private
16
+
17
+ def add_attribute(column, name, type)
18
+ index.attributes << ThinkingSphinx::RealTime::Attribute.new(
19
+ ThinkingSphinx::ActiveRecord::Column.new(*column),
20
+ :as => name, :type => type
21
+ )
22
+ end
23
+
24
+ def add_field(column, name)
25
+ index.fields << ThinkingSphinx::RealTime::Field.new(
26
+ ThinkingSphinx::ActiveRecord::Column.new(*column), :as => name
27
+ )
28
+ end
29
+
30
+ def class_column
31
+ [:class, :name]
32
+ end
33
+ end
@@ -0,0 +1,23 @@
1
+ class ThinkingSphinx::RealTime::Interpreter <
2
+ ::ThinkingSphinx::Core::Interpreter
3
+
4
+ def has(*columns)
5
+ options = columns.extract_options!
6
+ @index.attributes += columns.collect { |column|
7
+ ::ThinkingSphinx::RealTime::Attribute.new column, options
8
+ }
9
+ end
10
+
11
+ def indexes(*columns)
12
+ options = columns.extract_options!
13
+ @index.fields += columns.collect { |column|
14
+ ::ThinkingSphinx::RealTime::Field.new column, options
15
+ }
16
+ end
17
+
18
+ def set_property(properties)
19
+ properties.each do |key, value|
20
+ @index.send("#{key}=", value) if @index.class.settings.include?(key)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,16 @@
1
+ class ThinkingSphinx::RealTime::Property
2
+ def initialize(column, options = {})
3
+ @column, @options = column, options
4
+ end
5
+
6
+ def name
7
+ (@options[:as] || @column.__name).to_s
8
+ end
9
+
10
+ def translate(object)
11
+ return @column.__name unless @column.__name.is_a?(Symbol)
12
+
13
+ base = @column.__stack.inject(object) { |base, node| base.try(node) }
14
+ base.try(@column.__name)
15
+ end
16
+ end
@@ -0,0 +1,22 @@
1
+ module ThinkingSphinx::Scopes
2
+ extend ActiveSupport::Concern
3
+
4
+ module ClassMethods
5
+ def sphinx_scope(name, &block)
6
+ sphinx_scopes[name] = block
7
+ end
8
+
9
+ def sphinx_scopes
10
+ @sphinx_scopes ||= {}
11
+ end
12
+
13
+ private
14
+
15
+ def method_missing(method, *args, &block)
16
+ return super unless sphinx_scopes.keys.include?(method)
17
+
18
+ query, options = sphinx_scopes[method].call(*args)
19
+ search query, (options || {})
20
+ end
21
+ end
22
+ end
@@ -1,1054 +1,116 @@
1
- # encoding: UTF-8
2
- module ThinkingSphinx
3
- # Once you've got those indexes in and built, this is the stuff that
4
- # matters - how to search! This class provides a generic search
5
- # interface - which you can use to search all your indexed models at once.
6
- # Most times, you will just want a specific model's results - to search and
7
- # search_for_ids methods will do the job in exactly the same manner when
8
- # called from a model.
9
- #
10
- class Search < Array
11
- CoreMethods = %w( == class class_eval extend frozen? id instance_eval
12
- instance_of? instance_values instance_variable_defined?
13
- instance_variable_get instance_variable_set instance_variables is_a?
14
- kind_of? member? method methods nil? object_id respond_to?
15
- respond_to_missing? send should tap type )
16
- SafeMethods = %w( partition private_methods protected_methods
17
- public_methods send class )
18
-
19
- instance_methods.select { |method|
20
- method.to_s[/^__/].nil? && !CoreMethods.include?(method.to_s)
21
- }.each { |method|
22
- undef_method method
23
- }
24
-
25
- HashOptions = [:conditions, :with, :without, :with_all, :without_any]
26
- ArrayOptions = [:classes, :without_ids]
27
-
28
- attr_reader :args, :options
29
-
30
- # Deprecated. Use ThinkingSphinx.search
31
- def self.search(*args)
32
- warn 'ThinkingSphinx::Search.search is deprecated. Please use ThinkingSphinx.search instead.'
33
- ThinkingSphinx.search(*args)
34
- end
35
-
36
- # Deprecated. Use ThinkingSphinx.search_for_ids
37
- def self.search_for_ids(*args)
38
- warn 'ThinkingSphinx::Search.search_for_ids is deprecated. Please use ThinkingSphinx.search_for_ids instead.'
39
- ThinkingSphinx.search_for_ids(*args)
40
- end
41
-
42
- # Deprecated. Use ThinkingSphinx.search_for_ids
43
- def self.search_for_id(*args)
44
- warn 'ThinkingSphinx::Search.search_for_id is deprecated. Please use ThinkingSphinx.search_for_id instead.'
45
- ThinkingSphinx.search_for_id(*args)
46
- end
47
-
48
- # Deprecated. Use ThinkingSphinx.count
49
- def self.count(*args)
50
- warn 'ThinkingSphinx::Search.count is deprecated. Please use ThinkingSphinx.count instead.'
51
- ThinkingSphinx.count(*args)
52
- end
53
-
54
- # Deprecated. Use ThinkingSphinx.facets
55
- def self.facets(*args)
56
- warn 'ThinkingSphinx::Search.facets is deprecated. Please use ThinkingSphinx.facets instead.'
57
- ThinkingSphinx.facets(*args)
58
- end
59
-
60
- def self.warn(message)
61
- ::ActiveSupport::Deprecation.warn message
62
- end
63
-
64
- def self.bundle_searches(enum = nil)
65
- bundle = ThinkingSphinx::BundledSearch.new
66
-
67
- if enum.nil?
68
- yield bundle
69
- else
70
- enum.each { |item| yield bundle, item }
71
- end
72
-
73
- bundle.searches
74
- end
75
-
76
- def self.matching_fields(fields, bitmask)
77
- matches = []
78
- bitstring = bitmask.to_s(2).rjust(32, '0').reverse
79
-
80
- fields.each_with_index do |field, index|
81
- matches << field if bitstring[index, 1] == '1'
82
- end
83
- matches
84
- end
85
-
86
- def initialize(*args)
87
- ThinkingSphinx.context.define_indexes
88
-
89
- @array = []
90
- @options = args.extract_options!
91
- @args = args
92
-
93
- add_default_scope unless options[:ignore_default]
94
-
95
- populate if @options[:populate]
96
- end
97
-
98
- def ==(object)
99
- populate
100
- super
101
- end
102
-
103
- def to_a
104
- populate
105
- @array
106
- end
107
-
108
- # Populates the search result set
109
- def all
110
- populate
111
- self
112
- end
113
-
114
- def freeze
115
- populate
116
- @array.freeze
117
- self
118
- end
119
-
120
- def as_json(*args)
121
- populate
122
- @array.as_json(*args)
123
- end
124
-
125
- # Indication of whether the request has been made to Sphinx for the search
126
- # query.
127
- #
128
- # @return [Boolean] true if the results have been requested.
129
- #
130
- def populated?
131
- !!@populated
132
- end
133
-
134
- # Indication of whether the request resulted in an error from Sphinx.
135
- #
136
- # @return [Boolean] true if Sphinx reports query error
137
- #
138
- def error?
139
- !!error
140
- end
141
-
142
- # The Sphinx-reported error, if any.
143
- #
144
- # @return [String, nil]
145
- #
146
- def error
147
- populate
148
- @results[:error]
149
- end
150
-
151
- # Indication of whether the request resulted in a warning from Sphinx.
152
- #
153
- # @return [Boolean] true if Sphinx reports query warning
154
- #
155
- def warning?
156
- !!warning
157
- end
158
-
159
- # The Sphinx-reported warning, if any.
160
- #
161
- # @return [String, nil]
162
- #
163
- def warning
164
- populate
165
- @results[:warning]
166
- end
167
-
168
- # The query result hash from Riddle.
169
- #
170
- # @return [Hash] Raw Sphinx results
171
- #
172
- def results
173
- populate
174
- @results
175
- end
176
-
177
- def method_missing(method, *args, &block)
178
- if is_scope?(method)
179
- add_scope(method, *args, &block)
180
- return self
181
- elsif method == :search_count
182
- merge_search one_class.search(*args), self.args, options
183
- return scoped_count
184
- elsif method.to_s[/^each_with_.*/].nil? && !@array.respond_to?(method)
185
- super
186
- elsif !SafeMethods.include?(method.to_s)
187
- populate
188
- end
189
-
190
- if method.to_s[/^each_with_.*/] && !@array.respond_to?(method)
191
- each_with_attribute method.to_s.gsub(/^each_with_/, ''), &block
192
- else
193
- @array.send(method, *args, &block)
194
- end
195
- end
196
-
197
- # Returns true if the Search object or the underlying Array object respond
198
- # to the requested method.
199
- #
200
- # @param [Symbol] method The method name
201
- # @return [Boolean] true if either Search or Array responds to the method.
202
- #
203
- def respond_to?(method, include_private = false)
204
- super || @array.respond_to?(method, include_private)
205
- end
206
-
207
- # The current page number of the result set. Defaults to 1 if no page was
208
- # explicitly requested.
209
- #
210
- # @return [Integer]
211
- #
212
- def current_page
213
- @options[:page].blank? ? 1 : @options[:page].to_i
214
- end
215
-
216
- def first_page?
217
- current_page == 1
218
- end
219
-
220
- # Kaminari support
221
- def page(page_number)
222
- @options[:page] = page_number
223
- self
224
- end
225
-
226
- # The next page number of the result set. If there are no more pages
227
- # available, nil is returned.
228
- #
229
- # @return [Integer, nil]
230
- #
231
- def next_page
232
- current_page >= total_pages ? nil : current_page + 1
233
- end
234
-
235
- def next_page?
236
- !next_page.nil?
237
- end
238
-
239
- def last_page?
240
- next_page.nil?
241
- end
242
-
243
- # The previous page number of the result set. If this is the first page,
244
- # then nil is returned.
245
- #
246
- # @return [Integer, nil]
247
- #
248
- def previous_page
249
- current_page == 1 ? nil : current_page - 1
250
- end
251
-
252
- # The amount of records per set of paged results. Defaults to 20 unless a
253
- # specific page size is requested.
254
- #
255
- # @return [Integer]
256
- #
257
- def per_page
258
- @options[:limit] ||= @options[:per_page]
259
- @options[:limit] ||= 20
260
- @options[:limit].to_i
261
- end
262
- # Kaminari support
263
- alias_method :limit_value, :per_page
264
-
265
- # Kaminari support
266
- def per(limit)
267
- @options[:limit] = limit
268
- self
269
- end
270
-
271
- # The total number of pages available if the results are paginated.
272
- #
273
- # @return [Integer]
274
- #
275
- def total_pages
276
- populate
277
- return 0 if @results.nil? || @results[:total].nil?
278
-
279
- @total_pages ||= (@results[:total] / per_page.to_f).ceil
280
- end
281
- # Compatibility with kaminari and older versions of will_paginate
282
- alias_method :page_count, :total_pages
283
- alias_method :num_pages, :total_pages
284
-
285
- # Query time taken
286
- #
287
- # @return [Integer]
288
- #
289
- def query_time
290
- populate
291
- return 0 if @results[:time].nil?
292
-
293
- @query_time ||= @results[:time]
294
- end
295
-
296
- # The total number of search results available.
297
- #
298
- # @return [Integer]
299
- #
300
- def total_entries
301
- populate
302
- return 0 if @results.nil? || @results[:total_found].nil?
303
-
304
- @total_entries ||= @results[:total_found]
305
- end
306
-
307
- # Compatibility with kaminari
308
- alias_method :total_count, :total_entries
309
-
310
- # The current page's offset, based on the number of records per page.
311
- # Or explicit :offset if given.
312
- #
313
- # @return [Integer]
314
- #
315
- def offset
316
- @options[:offset] || ((current_page - 1) * per_page)
317
- end
318
-
319
- alias_method :offset_value, :offset
320
-
321
- def indexes
322
- return options[:index] if options[:index]
323
- return '*' if classes.empty?
324
-
325
- classes.collect { |klass|
326
- klass.sphinx_index_names
327
- }.flatten.uniq.join(',')
328
- end
329
-
330
- def each_with_groupby_and_count(&block)
331
- populate
332
- results[:matches].each_with_index do |match, index|
333
- yield self[index],
334
- match[:attributes]["@groupby"],
335
- match[:attributes]["@count"]
336
- end
337
- end
338
- alias_method :each_with_group_and_count, :each_with_groupby_and_count
339
-
340
- def each_with_weighting(&block)
341
- populate
342
- results[:matches].each_with_index do |match, index|
343
- yield self[index], match[:weight]
344
- end
345
- end
346
-
347
- def each_with_match(&block)
348
- populate
349
- results[:matches].each_with_index do |match, index|
350
- yield self[index], match
351
- end
352
- end
353
-
354
- def excerpt_for(string, model = nil)
355
- if model.nil? && one_class
356
- model ||= one_class
357
- end
358
-
359
- populate
360
-
361
- index = options[:index] || "#{model.core_index_names.first}"
362
- take_client do |client|
363
- client.excerpts(
364
- {
365
- :docs => [string.to_s],
366
- :words => query,
367
- :index => index.split(',').first.strip
368
- }.merge(options[:excerpt_options] || {})
369
- ).first
370
- end
371
- end
372
-
373
- def search(*args)
374
- args << args.extract_options!.merge(:ignore_default => true)
375
- merge_search ThinkingSphinx::Search.new(*args), self.args, options
376
- self
377
- end
378
-
379
- def search_for_ids(*args)
380
- args << args.extract_options!.merge(
381
- :ignore_default => true,
382
- :ids_only => true
383
- )
384
- merge_search ThinkingSphinx::Search.new(*args), self.args, options
385
- self
386
- end
387
-
388
- def facets(*args)
389
- options = args.extract_options!
390
- merge_search self, args, options
391
- args << options
392
-
393
- ThinkingSphinx::FacetSearch.new(*args)
394
- end
395
-
396
- def take_client
397
- if options[:client]
398
- prepare options[:client]
399
- yield options[:client]
400
- else
401
- ThinkingSphinx::Connection.take do |client|
402
- prepare client
403
- yield client
404
- end
405
- end
406
- end
407
-
408
- def append_to(client)
409
- prepare client
410
- client.append_query query, indexes, comment
411
- client.reset
412
- end
413
-
414
- def populate_from_queue(results)
415
- return if @populated
416
- @populated = true
417
- @results = results
418
-
419
- compose_results
420
- end
421
-
422
- private
1
+ class ThinkingSphinx::Search < Array
2
+ CORE_METHODS = %w( == class class_eval extend frozen? id instance_eval
3
+ instance_of? instance_values instance_variable_defined?
4
+ instance_variable_get instance_variable_set instance_variables is_a?
5
+ kind_of? member? method methods nil? object_id respond_to?
6
+ respond_to_missing? send should should_not type )
7
+ SAFE_METHODS = %w( partition private_methods protected_methods public_methods
8
+ send class )
9
+ DEFAULT_MASKS = [
10
+ ThinkingSphinx::Masks::PaginationMask,
11
+ ThinkingSphinx::Masks::ScopesMask
12
+ ]
13
+
14
+ instance_methods.select { |method|
15
+ method.to_s[/^__/].nil? && !CORE_METHODS.include?(method.to_s)
16
+ }.each { |method|
17
+ undef_method method
18
+ }
19
+
20
+ attr_reader :options, :masks
21
+ attr_accessor :query
22
+
23
+ def initialize(query = nil, options = {})
24
+ query, options = nil, query if query.is_a?(Hash)
25
+ @query, @options = query, options
26
+ @masks = @options.delete(:masks) || DEFAULT_MASKS
27
+ @middleware = @options.delete(:middleware)
28
+
29
+ populate if options[:populate]
30
+ end
423
31
 
424
- def config
32
+ def context
33
+ @context ||= ThinkingSphinx::Search::Context.new self,
425
34
  ThinkingSphinx::Configuration.instance
426
- end
427
-
428
- def populate
429
- return if @populated
430
- @populated = true
431
- retries = hard_retries
432
-
433
- begin
434
- retry_on_stale_index do
435
- begin
436
- @results = nil
437
- log query do
438
- take_client do |client|
439
- @results = client.query query, indexes, comment
440
- end
441
- end
442
- total = @results[:total_found].to_i
443
- log "Found #{total} result#{'s' unless total == 1}"
444
- log "Sphinx Daemon returned warning: #{warning}" if warning?
445
-
446
- if error?
447
- log "Sphinx Daemon returned error: #{error}"
448
- raise SphinxError.new(error, @results) unless options[:ignore_errors]
449
- end
450
- rescue Errno::ECONNREFUSED => err
451
- raise ThinkingSphinx::ConnectionError,
452
- 'Connection to Sphinx Daemon (searchd) failed.'
453
- end
454
-
455
- compose_results
456
- end
457
- rescue => e
458
- log 'Caught Sphinx exception: %s (%s %s left)' % [
459
- e.message, retries, (retries == 1 ? 'try' : 'tries')
460
- ]
461
- retries -= 1
462
- if retries >= 0
463
- retry
464
- else
465
- raise e
466
- end
467
- end
468
- end
469
-
470
- def compose_results
471
- if options[:ids_only]
472
- compose_ids_results
473
- elsif options[:attributes_only]
474
- compose_attributes_results
475
- elsif options[:only]
476
- compose_only_results
477
- else
478
- replace instances_from_matches
479
- add_excerpter
480
- add_sphinx_attributes
481
- add_matching_fields if options[:rank_mode] == :fieldmask
482
- end
483
- end
484
-
485
- def compose_ids_results
486
- replace @results[:matches].collect { |match|
487
- match[:attributes]['sphinx_internal_id']
488
- }
489
- end
490
-
491
- def compose_attributes_results
492
- replace @results[:matches].collect { |match|
493
- attributes = {}
494
- match[:attributes].each do |name, value|
495
- attributes[name.to_sym] = match[:attributes][name]
496
- end
497
- attributes
498
- }
499
- end
500
-
501
- def compose_only_results
502
- replace @results[:matches].collect { |match|
503
- case only = options[:only]
504
- when String, Symbol
505
- match[:attributes][only.to_s]
506
- when Array
507
- only.inject({}) do |hash, attribute|
508
- hash[attribute.to_sym] = match[:attributes][attribute.to_s]
509
- hash
510
- end
511
- else
512
- raise "Unexpected object for :only argument. String or Array is expected, #{only.class} was received."
513
- end
514
- }
515
- end
516
-
517
- def add_excerpter
518
- each do |object|
519
- next if object.nil?
520
-
521
- object.excerpts = ThinkingSphinx::Excerpter.new self, object
522
- end
523
- end
524
-
525
- def add_sphinx_attributes
526
- each do |object|
527
- next if object.nil?
528
-
529
- match = match_hash object
530
- next if match.nil?
531
-
532
- object.sphinx_attributes = match[:attributes]
533
- end
534
- end
535
-
536
- def add_matching_fields
537
- each do |object|
538
- next if object.nil?
539
-
540
- match = match_hash object
541
- next if match.nil?
542
- object.matching_fields = ThinkingSphinx::Search.matching_fields(
543
- @results[:fields], match[:weight]
544
- )
545
- end
546
- end
547
-
548
- def match_hash(object)
549
- @results[:matches].detect { |match|
550
- class_crc = object.class.name
551
- class_crc = object.class.to_crc32 if Riddle.loaded_version.to_i < 2
552
-
553
- match[:attributes]['sphinx_internal_id'] == object.
554
- primary_key_for_sphinx &&
555
- match[:attributes][crc_attribute] == class_crc
556
- }
557
- end
558
-
559
- def self.log(message, &block)
560
- if ThinkingSphinx::ActiveRecord::LogSubscriber.logger.nil?
561
- yield if block_given?
562
- return
563
- end
564
-
565
- if block_given?
566
- ::ActiveSupport::Notifications.
567
- instrument('query.thinking_sphinx', :query => message, &block)
568
- else
569
- ::ActiveSupport::Notifications.
570
- instrument('message.thinking_sphinx', :message => message)
571
- end
572
- end
573
-
574
- def log(query, &block)
575
- self.class.log(query, &block)
576
- end
577
-
578
- def prepare(client)
579
- index_options = {}
580
- if one_class && one_class.sphinx_indexes && one_class.sphinx_indexes.first
581
- index_options = one_class.sphinx_indexes.first.local_options
582
- end
583
-
584
- [
585
- :max_matches, :group_by, :group_function, :group_clause,
586
- :group_distinct, :id_range, :cut_off, :retry_count, :retry_delay,
587
- :rank_mode, :rank_expr, :max_query_time, :field_weights
588
- ].each do |key|
589
- value = options[key] || index_options[key]
590
- client.send("#{key}=", value) if value
591
- end
592
-
593
- # treated non-standard as :select is already used for AR queries
594
- client.select = options[:sphinx_select] || '*'
595
-
596
- client.limit = per_page
597
- client.offset = offset
598
- client.match_mode = match_mode
599
- client.filters = filters
600
- client.sort_mode = sort_mode
601
- client.sort_by = sort_by
602
- client.group_by = group_by if group_by
603
- client.group_function = group_function if group_function
604
- client.index_weights = index_weights
605
- client.anchor = anchor
606
-
607
- client
608
- end
609
-
610
- def retry_on_stale_index(&block)
611
- stale_ids = []
612
- retries = stale_retries
613
-
614
- begin
615
- options[:raise_on_stale] = retries > 0
616
- block.call
617
-
618
- # If ThinkingSphinx::Search#instances_from_matches found records in
619
- # Sphinx but not in the DB and the :raise_on_stale option is set, this
620
- # exception is raised. We retry a limited number of times, excluding the
621
- # stale ids from the search.
622
- rescue StaleIdsException => err
623
- retries -= 1
624
-
625
- # For logging
626
- stale_ids |= err.ids
627
- # ID exclusion
628
- options[:without_ids] = Array(options[:without_ids]) | err.ids
629
-
630
- log 'Stale Ids (%s %s left): %s' % [
631
- retries, (retries == 1 ? 'try' : 'tries'), stale_ids.join(', ')
632
- ]
633
- retry
634
- end
635
- end
636
-
637
- def classes
638
- @classes ||= options[:classes] || []
639
- end
640
-
641
- def one_class
642
- @one_class ||= classes.length != 1 ? nil : classes.first
643
- end
644
-
645
- def query
646
- @query ||= begin
647
- q = @args.join(' ') << conditions_as_query
648
- (options[:star] ? star_query(q) : q).strip
649
- end
650
- end
651
-
652
- def conditions_as_query
653
- return '' if @options[:conditions].blank?
654
-
655
- ' ' + @options[:conditions].keys.collect { |key|
656
- search_key = key.is_a?(::Array) ? "(#{key.join(',')})" : key
657
- "@#{search_key} #{options[:conditions][key]}"
658
- }.join(' ')
659
- end
660
-
661
- def star_query(query)
662
- token = options[:star].is_a?(Regexp) ? options[:star] : default_star_token
663
-
664
- query.gsub(/("#{token}(.*?#{token})?"|(?![!-])#{token})/u) do
665
- pre, proper, post = $`, $&, $'
666
- # E.g. "@foo", "/2", "~3", but not as part of a token
667
- is_operator = pre.match(%r{(\W|^)[@~/]\Z}) ||
668
- pre.match(%r{(\W|^)@\([^\)]*$})
669
- # E.g. "foo bar", with quotes
670
- is_quote = proper.starts_with?('"') && proper.ends_with?('"')
671
- has_star = pre.ends_with?("*") || post.starts_with?("*")
672
- if is_operator || is_quote || has_star
673
- proper
674
- else
675
- "*#{proper}*"
676
- end
677
- end
678
- end
679
-
680
- if Regexp.instance_methods.include?(:encoding)
681
- DefaultStarToken = Regexp.new('\p{Word}+')
682
- else
683
- DefaultStarToken = Regexp.new('\w+', nil, 'u')
684
- end
685
-
686
- def default_star_token
687
- DefaultStarToken
688
- end
689
-
690
- def comment
691
- options[:comment] || ''
692
- end
693
-
694
- def match_mode
695
- options[:match_mode] || (options[:conditions].blank? ? :all : :extended)
696
- end
697
-
698
- def sort_mode
699
- @sort_mode ||= case options[:sort_mode]
700
- when :asc
701
- :attr_asc
702
- when :desc
703
- :attr_desc
704
- when nil
705
- case options[:order]
706
- when String
707
- :extended
708
- when Symbol
709
- :attr_asc
710
- else
711
- :relevance
712
- end
713
- else
714
- options[:sort_mode]
715
- end
716
- end
717
-
718
- def sort_by
719
- case @sort_by = (options[:sort_by] || options[:order])
720
- when String
721
- sorted_fields_to_attributes(@sort_by.clone)
722
- when Symbol
723
- field_names.include?(@sort_by) ?
724
- @sort_by.to_s.concat('_sort') : @sort_by.to_s
725
- else
726
- ''
727
- end
728
- end
729
-
730
- def field_names
731
- return [] unless one_class
732
-
733
- one_class.sphinx_indexes.collect { |index|
734
- index.fields.collect { |field| field.unique_name }
735
- }.flatten
736
- end
737
-
738
- def sorted_fields_to_attributes(order_string)
739
- field_names.each { |field|
740
- order_string.gsub!(/(^|\s)#{field}(,?\s|$)/) { |match|
741
- match.gsub field.to_s, field.to_s.concat("_sort")
742
- }
743
- }
744
-
745
- order_string
746
- end
747
-
748
- # Turn :index_weights => { "foo" => 2, User => 1 } into :index_weights =>
749
- # { "foo" => 2, "user_core" => 1, "user_delta" => 1 }
750
- #
751
- def index_weights
752
- weights = options[:index_weights] || {}
753
- weights.keys.inject({}) do |hash, key|
754
- if key.is_a?(Class)
755
- name = ThinkingSphinx::Index.name_for(key)
756
- hash["#{name}_core"] = weights[key]
757
- hash["#{name}_delta"] = weights[key]
758
- else
759
- hash[key] = weights[key]
760
- end
761
-
762
- hash
763
- end
764
- end
765
-
766
- def group_by
767
- options[:group] ? options[:group].to_s : nil
768
- end
769
-
770
- def group_function
771
- options[:group] ? :attr : nil
772
- end
773
-
774
- def internal_filters
775
- filters = [Riddle::Client::Filter.new('sphinx_deleted', [0])]
776
-
777
- class_crcs = classes.collect { |klass|
778
- klass.to_crc32s
779
- }.flatten
780
-
781
- unless class_crcs.empty?
782
- filters << Riddle::Client::Filter.new('class_crc', class_crcs)
783
- end
784
-
785
- filters << Riddle::Client::Filter.new(
786
- 'sphinx_internal_id', filter_value(options[:without_ids]), true
787
- ) unless options[:without_ids].nil? || options[:without_ids].empty?
788
-
789
- filters
790
- end
791
-
792
- def filters
793
- internal_filters +
794
- (options[:with] || {}).collect { |attrib, value|
795
- Riddle::Client::Filter.new attrib.to_s, filter_value(value)
796
- } +
797
- (options[:without] || {}).collect { |attrib, value|
798
- Riddle::Client::Filter.new attrib.to_s, filter_value(value), true
799
- } +
800
- (options[:with_all] || {}).collect { |attrib, values|
801
- Array(values).collect { |value|
802
- Riddle::Client::Filter.new attrib.to_s, filter_value(value)
803
- }
804
- }.flatten +
805
- (options[:without_any] || {}).collect { |attrib, values|
806
- Array(values).collect { |value|
807
- Riddle::Client::Filter.new attrib.to_s, filter_value(value), true
808
- }
809
- }.flatten
810
- end
811
-
812
- # When passed a Time instance, returns the integer timestamp.
813
- def filter_value(value)
814
- case value
815
- when Range
816
- filter_value(value.first).first..filter_value(value.last).first
817
- when Array
818
- value.collect { |v| filter_value(v) }.flatten
819
- when Time
820
- [value.to_i]
821
- when Date
822
- [Time.utc(value.year, value.month, value.day).to_i]
823
- when NilClass
824
- 0
825
- else
826
- Array(value)
827
- end
828
- end
829
-
830
- def anchor
831
- return {} unless options[:geo] || (options[:lat] && options[:lng])
832
-
833
- {
834
- :latitude => options[:geo] ? options[:geo].first : options[:lat],
835
- :longitude => options[:geo] ? options[:geo].last : options[:lng],
836
- :latitude_attribute => latitude_attr.to_s,
837
- :longitude_attribute => longitude_attr.to_s
838
- }
839
- end
840
-
841
- def latitude_attr
842
- options[:latitude_attr] ||
843
- index_option(:latitude_attr) ||
844
- attribute(:lat, :latitude)
845
- end
846
-
847
- def longitude_attr
848
- options[:longitude_attr] ||
849
- index_option(:longitude_attr) ||
850
- attribute(:lon, :lng, :longitude)
851
- end
35
+ end
852
36
 
853
- def index_option(key)
854
- return nil unless one_class
37
+ def meta
38
+ populate
39
+ context[:meta]
40
+ end
855
41
 
856
- one_class.sphinx_indexes.collect { |index|
857
- index.local_options[key]
858
- }.compact.first
859
- end
42
+ def offset
43
+ @options[:offset] || ((current_page - 1) * per_page)
44
+ end
860
45
 
861
- def attribute(*keys)
862
- return nil unless one_class
46
+ alias_method :offset_value, :offset
863
47
 
864
- keys.detect { |key|
865
- attributes.include?(key)
866
- }
867
- end
48
+ def per_page
49
+ @options[:limit] ||= (@options[:per_page] || 20)
50
+ @options[:limit].to_i
51
+ end
868
52
 
869
- def attributes
870
- return [] unless one_class
53
+ alias_method :limit_value, :per_page
871
54
 
872
- attributes = one_class.sphinx_indexes.collect { |index|
873
- index.attributes.collect { |attrib| attrib.unique_name }
874
- }.flatten
875
- end
55
+ def populate
56
+ return self if @populated
876
57
 
877
- def stale_retries
878
- case options[:retry_stale]
879
- when TrueClass
880
- 3
881
- when nil, FalseClass
882
- 0
883
- else
884
- options[:retry_stale].to_i
885
- end
886
- end
887
-
888
- def hard_retries
889
- options[:hard_retry_count] || config.hard_retry_count
890
- end
58
+ middleware.call [context]
59
+ @populated = true
891
60
 
892
- def include_for_class(klass)
893
- includes = options[:include] || klass.sphinx_index_options[:include]
894
-
895
- case includes
896
- when NilClass
897
- nil
898
- when Array
899
- include_from_array includes, klass
900
- when Symbol
901
- klass.reflections[includes].nil? ? nil : includes
902
- when Hash
903
- include_from_hash includes, klass
904
- else
905
- includes
906
- end
907
- end
61
+ self
62
+ end
908
63
 
909
- def include_from_array(array, klass)
910
- scoped_array = []
911
- array.each do |value|
912
- case value
913
- when Hash
914
- scoped_hash = include_from_hash(value, klass)
915
- scoped_array << scoped_hash unless scoped_hash.nil?
916
- else
917
- scoped_array << value unless klass.reflections[value].nil?
918
- end
919
- end
920
- scoped_array.empty? ? nil : scoped_array
921
- end
64
+ def populated!
65
+ @populated = true
66
+ end
922
67
 
923
- def include_from_hash(hash, klass)
924
- scoped_hash = {}
925
- hash.keys.each do |key|
926
- scoped_hash[key] = hash[key] unless klass.reflections[key].nil?
927
- end
928
- scoped_hash.empty? ? nil : scoped_hash
929
- end
68
+ def raw
69
+ populate
70
+ context[:raw]
71
+ end
930
72
 
931
- def instances_from_class(klass, matches)
932
- index_options = klass.sphinx_index_options
73
+ def respond_to?(method, include_private = false)
74
+ super || context[:results].respond_to?(method, include_private)
75
+ end
933
76
 
934
- ids = matches.collect { |match| match[:attributes]["sphinx_internal_id"] }
935
- instances = ids.length > 0 ? klass.unscoped.find(
936
- :all,
937
- :joins => options[:joins],
938
- :conditions => {klass.primary_key_for_sphinx.to_sym => ids},
939
- :include => include_for_class(klass),
940
- :select => (options[:select] || index_options[:select]),
941
- :order => (options[:sql_order] || index_options[:sql_order])
942
- ) : []
77
+ def to_a
78
+ populate
79
+ context[:results].collect { |result|
80
+ result.respond_to?(:unglazed) ? result.unglazed : result
81
+ }
82
+ end
943
83
 
944
- # Raise an exception if we find records in Sphinx but not in the DB, so
945
- # the search method can retry without them. See
946
- # ThinkingSphinx::Search.retry_search_on_stale_index.
947
- if options[:raise_on_stale] && instances.length < ids.length
948
- stale_ids = ids - instances.map { |i| i.id }
949
- raise StaleIdsException, stale_ids
950
- end
84
+ private
951
85
 
952
- # if the user has specified an SQL order, return the collection
953
- # without rearranging it into the Sphinx order
954
- return instances if (options[:sql_order] || index_options[:sql_order])
86
+ def mask_stack
87
+ @mask_stack ||= masks.collect { |klass| klass.new self }
88
+ end
955
89
 
956
- ids.collect { |obj_id|
957
- instances.detect do |obj|
958
- obj.primary_key_for_sphinx == obj_id
959
- end
960
- }
90
+ def method_missing(method, *args, &block)
91
+ mask_stack.each do |mask|
92
+ return mask.send(method, *args, &block) if mask.respond_to?(method)
961
93
  end
962
94
 
963
- # Group results by class and call #find(:all) once for each group to reduce
964
- # the number of #find's in multi-model searches.
965
- #
966
- def instances_from_matches
967
- return single_class_results if one_class
95
+ populate if !SAFE_METHODS.include?(method.to_s)
968
96
 
969
- groups = results[:matches].group_by { |match|
970
- match[:attributes][crc_attribute]
971
- }
972
- groups.each do |crc, group|
973
- group.replace(
974
- instances_from_class(class_from_crc(crc), group)
975
- )
976
- end
977
-
978
- results[:matches].collect do |match|
979
- groups.detect { |crc, group|
980
- crc == match[:attributes][crc_attribute]
981
- }[1].compact.detect { |obj|
982
- obj.primary_key_for_sphinx == match[:attributes]["sphinx_internal_id"]
983
- }
984
- end
985
- end
986
-
987
- def single_class_results
988
- instances_from_class one_class, results[:matches]
989
- end
97
+ context[:results].send(method, *args, &block)
98
+ end
990
99
 
991
- def class_from_crc(crc)
992
- if Riddle.loaded_version.to_i < 2
993
- config.models_by_crc[crc].constantize
100
+ def middleware
101
+ @middleware ||= begin
102
+ if options[:ids_only]
103
+ ThinkingSphinx::Middlewares::IDS_ONLY
994
104
  else
995
- crc.constantize
996
- end
997
- end
998
-
999
- def each_with_attribute(attribute, &block)
1000
- populate
1001
- results[:matches].each_with_index do |match, index|
1002
- yield self[index],
1003
- (match[:attributes][attribute] || match[:attributes]["@#{attribute}"])
105
+ ThinkingSphinx::Middlewares::DEFAULT
1004
106
  end
1005
107
  end
1006
-
1007
- def is_scope?(method)
1008
- one_class && one_class.sphinx_scopes.include?(method)
1009
- end
1010
-
1011
- # Adds the default_sphinx_scope if set.
1012
- def add_default_scope
1013
- return unless one_class && one_class.has_default_sphinx_scope?
1014
- add_scope(one_class.get_default_sphinx_scope.to_sym)
1015
- end
1016
-
1017
- def add_scope(method, *args, &block)
1018
- method = "#{method}_without_default".to_sym
1019
- merge_search one_class.send(method, *args, &block), self.args, options
1020
- end
1021
-
1022
- def merge_search(search, args, options)
1023
- search.args.each { |arg| args << arg }
1024
-
1025
- search.options.keys.each do |key|
1026
- if HashOptions.include?(key)
1027
- options[key] ||= {}
1028
- options[key].merge! search.options[key]
1029
- elsif ArrayOptions.include?(key)
1030
- options[key] ||= []
1031
- options[key] += search.options[key]
1032
- options[key].uniq!
1033
- else
1034
- options[key] = search.options[key]
1035
- end
1036
- end
1037
- end
1038
-
1039
- def scoped_count
1040
- return self.total_entries if(@options[:ids_only] || @options[:only])
1041
-
1042
- @options[:ids_only] = true
1043
- results_count = self.total_entries
1044
- @options[:ids_only] = false
1045
- @populated = false
1046
-
1047
- results_count
1048
- end
1049
-
1050
- def crc_attribute
1051
- Riddle.loaded_version.to_i < 2 ? 'class_crc' : 'sphinx_internal_class'
1052
- end
1053
108
  end
1054
109
  end
110
+
111
+ require 'thinking_sphinx/search/batch_inquirer'
112
+ require 'thinking_sphinx/search/context'
113
+ require 'thinking_sphinx/search/glaze'
114
+ require 'thinking_sphinx/search/merger'
115
+ require 'thinking_sphinx/search/query'
116
+ require 'thinking_sphinx/search/stale_ids_exception'