thinking-sphinx 1.2.13 → 1.4.0

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 (204) hide show
  1. data/README.textile +37 -4
  2. data/VERSION +1 -0
  3. data/features/abstract_inheritance.feature +10 -0
  4. data/features/alternate_primary_key.feature +1 -1
  5. data/features/attribute_updates.feature +49 -5
  6. data/features/deleting_instances.feature +3 -0
  7. data/features/excerpts.feature +8 -0
  8. data/features/facets.feature +15 -1
  9. data/features/facets_across_model.feature +2 -2
  10. data/features/field_sorting.feature +18 -0
  11. data/features/handling_edits.feature +1 -1
  12. data/features/searching_across_models.feature +2 -2
  13. data/features/searching_by_index.feature +40 -0
  14. data/features/searching_by_model.feature +1 -8
  15. data/features/sphinx_scopes.feature +33 -0
  16. data/features/step_definitions/alpha_steps.rb +14 -1
  17. data/features/step_definitions/beta_steps.rb +1 -1
  18. data/features/step_definitions/common_steps.rb +21 -2
  19. data/features/step_definitions/facet_steps.rb +4 -0
  20. data/features/step_definitions/scope_steps.rb +8 -0
  21. data/features/step_definitions/search_steps.rb +5 -0
  22. data/features/step_definitions/sphinx_steps.rb +8 -4
  23. data/features/sti_searching.feature +5 -0
  24. data/features/support/env.rb +7 -6
  25. data/features/{support → thinking_sphinx}/db/fixtures/betas.rb +1 -0
  26. data/features/{support → thinking_sphinx}/db/fixtures/comments.rb +1 -1
  27. data/features/{support → thinking_sphinx}/db/fixtures/developers.rb +2 -0
  28. data/features/thinking_sphinx/db/fixtures/foxes.rb +3 -0
  29. data/features/thinking_sphinx/db/fixtures/music.rb +4 -0
  30. data/features/{support → thinking_sphinx}/db/fixtures/people.rb +1 -1
  31. data/features/{support → thinking_sphinx}/db/fixtures/tags.rb +1 -1
  32. data/features/{support → thinking_sphinx}/db/migrations/create_alphas.rb +1 -0
  33. data/features/{support → thinking_sphinx}/db/migrations/create_developers.rb +0 -2
  34. data/features/thinking_sphinx/db/migrations/create_genres.rb +3 -0
  35. data/features/thinking_sphinx/db/migrations/create_music.rb +6 -0
  36. data/features/thinking_sphinx/models/alpha.rb +23 -0
  37. data/features/thinking_sphinx/models/andrew.rb +17 -0
  38. data/features/{support → thinking_sphinx}/models/beta.rb +1 -1
  39. data/features/{support → thinking_sphinx}/models/developer.rb +2 -2
  40. data/features/{support → thinking_sphinx}/models/extensible_beta.rb +1 -1
  41. data/features/thinking_sphinx/models/fox.rb +5 -0
  42. data/features/thinking_sphinx/models/genre.rb +3 -0
  43. data/features/thinking_sphinx/models/medium.rb +5 -0
  44. data/features/thinking_sphinx/models/music.rb +8 -0
  45. data/features/{support → thinking_sphinx}/models/person.rb +2 -1
  46. data/features/{support → thinking_sphinx}/models/post.rb +2 -1
  47. data/lib/cucumber/thinking_sphinx/external_world.rb +12 -0
  48. data/lib/cucumber/thinking_sphinx/internal_world.rb +13 -11
  49. data/lib/thinking_sphinx/active_record/attribute_updates.rb +17 -15
  50. data/lib/thinking_sphinx/active_record/delta.rb +0 -26
  51. data/lib/thinking_sphinx/active_record/has_many_association.rb +34 -11
  52. data/lib/thinking_sphinx/active_record/scopes.rb +46 -3
  53. data/lib/thinking_sphinx/active_record.rb +271 -193
  54. data/lib/thinking_sphinx/adapters/abstract_adapter.rb +45 -9
  55. data/lib/thinking_sphinx/adapters/mysql_adapter.rb +5 -1
  56. data/lib/thinking_sphinx/adapters/postgresql_adapter.rb +9 -1
  57. data/lib/thinking_sphinx/attribute.rb +67 -23
  58. data/lib/thinking_sphinx/auto_version.rb +24 -0
  59. data/lib/thinking_sphinx/bundled_search.rb +44 -0
  60. data/lib/thinking_sphinx/class_facet.rb +3 -2
  61. data/lib/thinking_sphinx/configuration.rb +78 -64
  62. data/lib/thinking_sphinx/context.rb +76 -0
  63. data/lib/thinking_sphinx/deltas/default_delta.rb +14 -20
  64. data/lib/thinking_sphinx/deltas.rb +0 -2
  65. data/lib/thinking_sphinx/deploy/capistrano.rb +1 -1
  66. data/lib/thinking_sphinx/excerpter.rb +1 -1
  67. data/lib/thinking_sphinx/facet.rb +6 -5
  68. data/lib/thinking_sphinx/facet_search.rb +54 -24
  69. data/lib/thinking_sphinx/field.rb +2 -4
  70. data/lib/thinking_sphinx/index/builder.rb +36 -20
  71. data/lib/thinking_sphinx/index/faux_column.rb +8 -0
  72. data/lib/thinking_sphinx/index.rb +77 -19
  73. data/lib/thinking_sphinx/join.rb +37 -0
  74. data/lib/thinking_sphinx/property.rb +9 -2
  75. data/lib/thinking_sphinx/rails_additions.rb +4 -4
  76. data/lib/thinking_sphinx/search.rb +212 -66
  77. data/lib/thinking_sphinx/search_methods.rb +22 -4
  78. data/lib/thinking_sphinx/source/internal_properties.rb +2 -2
  79. data/lib/thinking_sphinx/source/sql.rb +5 -3
  80. data/lib/thinking_sphinx/source.rb +21 -12
  81. data/lib/thinking_sphinx/tasks.rb +26 -58
  82. data/lib/thinking_sphinx/test.rb +55 -0
  83. data/lib/thinking_sphinx.rb +70 -38
  84. data/rails/init.rb +4 -2
  85. data/spec/{lib/thinking_sphinx → thinking_sphinx}/active_record/delta_spec.rb +6 -8
  86. data/spec/{lib/thinking_sphinx → thinking_sphinx}/active_record/has_many_association_spec.rb +26 -3
  87. data/spec/thinking_sphinx/active_record/scopes_spec.rb +176 -0
  88. data/spec/thinking_sphinx/active_record_spec.rb +618 -0
  89. data/spec/thinking_sphinx/adapters/abstract_adapter_spec.rb +134 -0
  90. data/spec/{lib/thinking_sphinx → thinking_sphinx}/association_spec.rb +1 -1
  91. data/spec/{lib/thinking_sphinx → thinking_sphinx}/attribute_spec.rb +87 -46
  92. data/spec/thinking_sphinx/auto_version_spec.rb +47 -0
  93. data/spec/{lib/thinking_sphinx → thinking_sphinx}/configuration_spec.rb +73 -63
  94. data/spec/thinking_sphinx/context_spec.rb +127 -0
  95. data/spec/{lib/thinking_sphinx → thinking_sphinx}/core/array_spec.rb +1 -1
  96. data/spec/{lib/thinking_sphinx → thinking_sphinx}/core/string_spec.rb +1 -1
  97. data/spec/{lib/thinking_sphinx → thinking_sphinx}/excerpter_spec.rb +1 -9
  98. data/spec/{lib/thinking_sphinx → thinking_sphinx}/facet_search_spec.rb +76 -82
  99. data/spec/{lib/thinking_sphinx → thinking_sphinx}/facet_spec.rb +5 -5
  100. data/spec/{lib/thinking_sphinx → thinking_sphinx}/field_spec.rb +1 -42
  101. data/spec/{lib/thinking_sphinx → thinking_sphinx}/index/builder_spec.rb +71 -31
  102. data/spec/{lib/thinking_sphinx → thinking_sphinx}/index/faux_column_spec.rb +8 -2
  103. data/spec/thinking_sphinx/index_spec.rb +183 -0
  104. data/spec/{lib/thinking_sphinx → thinking_sphinx}/rails_additions_spec.rb +5 -5
  105. data/spec/{lib/thinking_sphinx → thinking_sphinx}/search_methods_spec.rb +5 -1
  106. data/spec/{lib/thinking_sphinx → thinking_sphinx}/search_spec.rb +183 -31
  107. data/spec/{lib/thinking_sphinx → thinking_sphinx}/source_spec.rb +18 -2
  108. data/spec/thinking_sphinx/test_spec.rb +20 -0
  109. data/spec/thinking_sphinx_spec.rb +204 -0
  110. data/tasks/distribution.rb +7 -26
  111. data/tasks/testing.rb +32 -20
  112. metadata +488 -147
  113. data/VERSION.yml +0 -5
  114. data/features/datetime_deltas.feature +0 -66
  115. data/features/delayed_delta_indexing.feature +0 -37
  116. data/features/step_definitions/datetime_delta_steps.rb +0 -15
  117. data/features/step_definitions/delayed_delta_indexing_steps.rb +0 -7
  118. data/features/support/database.yml +0 -5
  119. data/features/support/db/active_record.rb +0 -40
  120. data/features/support/db/database.yml +0 -5
  121. data/features/support/db/fixtures/delayed_betas.rb +0 -10
  122. data/features/support/db/fixtures/thetas.rb +0 -10
  123. data/features/support/db/migrations/create_delayed_betas.rb +0 -17
  124. data/features/support/db/migrations/create_thetas.rb +0 -5
  125. data/features/support/db/mysql.rb +0 -3
  126. data/features/support/db/postgresql.rb +0 -3
  127. data/features/support/models/alpha.rb +0 -10
  128. data/features/support/models/delayed_beta.rb +0 -7
  129. data/features/support/models/theta.rb +0 -7
  130. data/features/support/post_database.rb +0 -43
  131. data/lib/thinking_sphinx/deltas/datetime_delta.rb +0 -50
  132. data/lib/thinking_sphinx/deltas/delayed_delta/delta_job.rb +0 -24
  133. data/lib/thinking_sphinx/deltas/delayed_delta/flag_as_deleted_job.rb +0 -27
  134. data/lib/thinking_sphinx/deltas/delayed_delta/job.rb +0 -26
  135. data/lib/thinking_sphinx/deltas/delayed_delta.rb +0 -30
  136. data/spec/lib/thinking_sphinx/active_record/scopes_spec.rb +0 -96
  137. data/spec/lib/thinking_sphinx/active_record_spec.rb +0 -353
  138. data/spec/lib/thinking_sphinx/deltas/job_spec.rb +0 -32
  139. data/spec/lib/thinking_sphinx/index_spec.rb +0 -45
  140. data/spec/lib/thinking_sphinx_spec.rb +0 -162
  141. data/vendor/after_commit/LICENSE +0 -20
  142. data/vendor/after_commit/README +0 -16
  143. data/vendor/after_commit/Rakefile +0 -22
  144. data/vendor/after_commit/init.rb +0 -8
  145. data/vendor/after_commit/lib/after_commit/active_record.rb +0 -114
  146. data/vendor/after_commit/lib/after_commit/connection_adapters.rb +0 -103
  147. data/vendor/after_commit/lib/after_commit.rb +0 -45
  148. data/vendor/after_commit/test/after_commit_test.rb +0 -53
  149. data/vendor/delayed_job/lib/delayed/job.rb +0 -251
  150. data/vendor/delayed_job/lib/delayed/message_sending.rb +0 -7
  151. data/vendor/delayed_job/lib/delayed/performable_method.rb +0 -55
  152. data/vendor/delayed_job/lib/delayed/worker.rb +0 -54
  153. data/vendor/riddle/lib/riddle/client/filter.rb +0 -53
  154. data/vendor/riddle/lib/riddle/client/message.rb +0 -66
  155. data/vendor/riddle/lib/riddle/client/response.rb +0 -84
  156. data/vendor/riddle/lib/riddle/client.rb +0 -635
  157. data/vendor/riddle/lib/riddle/configuration/distributed_index.rb +0 -48
  158. data/vendor/riddle/lib/riddle/configuration/index.rb +0 -142
  159. data/vendor/riddle/lib/riddle/configuration/indexer.rb +0 -19
  160. data/vendor/riddle/lib/riddle/configuration/remote_index.rb +0 -17
  161. data/vendor/riddle/lib/riddle/configuration/searchd.rb +0 -25
  162. data/vendor/riddle/lib/riddle/configuration/section.rb +0 -43
  163. data/vendor/riddle/lib/riddle/configuration/source.rb +0 -23
  164. data/vendor/riddle/lib/riddle/configuration/sql_source.rb +0 -34
  165. data/vendor/riddle/lib/riddle/configuration/xml_source.rb +0 -28
  166. data/vendor/riddle/lib/riddle/configuration.rb +0 -33
  167. data/vendor/riddle/lib/riddle/controller.rb +0 -53
  168. data/vendor/riddle/lib/riddle.rb +0 -30
  169. data/features/{support → thinking_sphinx}/database.example.yml +0 -0
  170. data/features/{support → thinking_sphinx}/db/fixtures/alphas.rb +0 -0
  171. data/features/{support → thinking_sphinx}/db/fixtures/authors.rb +0 -0
  172. data/features/{support → thinking_sphinx}/db/fixtures/boxes.rb +0 -0
  173. data/features/{support → thinking_sphinx}/db/fixtures/categories.rb +0 -0
  174. data/features/{support → thinking_sphinx}/db/fixtures/cats.rb +0 -0
  175. data/features/{support → thinking_sphinx}/db/fixtures/dogs.rb +0 -0
  176. data/features/{support → thinking_sphinx}/db/fixtures/extensible_betas.rb +0 -0
  177. data/features/{support → thinking_sphinx}/db/fixtures/gammas.rb +0 -0
  178. data/features/{support → thinking_sphinx}/db/fixtures/posts.rb +0 -0
  179. data/features/{support → thinking_sphinx}/db/fixtures/robots.rb +0 -0
  180. data/features/{support → thinking_sphinx}/db/migrations/create_animals.rb +0 -0
  181. data/features/{support → thinking_sphinx}/db/migrations/create_authors.rb +0 -0
  182. data/features/{support → thinking_sphinx}/db/migrations/create_authors_posts.rb +0 -0
  183. data/features/{support → thinking_sphinx}/db/migrations/create_betas.rb +0 -0
  184. data/features/{support → thinking_sphinx}/db/migrations/create_boxes.rb +0 -0
  185. data/features/{support → thinking_sphinx}/db/migrations/create_categories.rb +0 -0
  186. data/features/{support → thinking_sphinx}/db/migrations/create_comments.rb +0 -0
  187. data/features/{support → thinking_sphinx}/db/migrations/create_extensible_betas.rb +0 -0
  188. data/features/{support → thinking_sphinx}/db/migrations/create_gammas.rb +0 -0
  189. data/features/{support → thinking_sphinx}/db/migrations/create_people.rb +0 -0
  190. data/features/{support → thinking_sphinx}/db/migrations/create_posts.rb +0 -0
  191. data/features/{support → thinking_sphinx}/db/migrations/create_robots.rb +0 -0
  192. data/features/{support → thinking_sphinx}/db/migrations/create_taggings.rb +0 -0
  193. data/features/{support → thinking_sphinx}/db/migrations/create_tags.rb +0 -0
  194. data/features/{support → thinking_sphinx}/models/animal.rb +0 -0
  195. data/features/{support → thinking_sphinx}/models/author.rb +0 -0
  196. data/features/{support → thinking_sphinx}/models/box.rb +0 -0
  197. data/features/{support → thinking_sphinx}/models/cat.rb +0 -0
  198. data/features/{support → thinking_sphinx}/models/category.rb +0 -0
  199. data/features/{support → thinking_sphinx}/models/comment.rb +3 -3
  200. /data/features/{support → thinking_sphinx}/models/dog.rb +0 -0
  201. /data/features/{support → thinking_sphinx}/models/gamma.rb +0 -0
  202. /data/features/{support → thinking_sphinx}/models/robot.rb +0 -0
  203. /data/features/{support → thinking_sphinx}/models/tag.rb +0 -0
  204. /data/features/{support → thinking_sphinx}/models/tagging.rb +0 -0
@@ -12,7 +12,11 @@ module ThinkingSphinx
12
12
  def self.included(base)
13
13
  base.class_eval do
14
14
  class_inheritable_array :sphinx_indexes, :sphinx_facets
15
+
16
+ extend ThinkingSphinx::ActiveRecord::ClassMethods
17
+
15
18
  class << self
19
+ attr_accessor :sphinx_index_blocks
16
20
 
17
21
  def set_sphinx_primary_key(attribute)
18
22
  @sphinx_primary_key_attribute = attribute
@@ -22,93 +26,6 @@ module ThinkingSphinx
22
26
  @sphinx_primary_key_attribute || primary_key
23
27
  end
24
28
 
25
- # Allows creation of indexes for Sphinx. If you don't do this, there
26
- # isn't much point trying to search (or using this plugin at all,
27
- # really).
28
- #
29
- # An example or two:
30
- #
31
- # define_index
32
- # indexes :id, :as => :model_id
33
- # indexes name
34
- # end
35
- #
36
- # You can also grab fields from associations - multiple levels deep
37
- # if necessary.
38
- #
39
- # define_index do
40
- # indexes tags.name, :as => :tag
41
- # indexes articles.content
42
- # indexes orders.line_items.product.name, :as => :product
43
- # end
44
- #
45
- # And it will automatically concatenate multiple fields:
46
- #
47
- # define_index do
48
- # indexes [author.first_name, author.last_name], :as => :author
49
- # end
50
- #
51
- # The #indexes method is for fields - if you want attributes, use
52
- # #has instead. All the same rules apply - but keep in mind that
53
- # attributes are for sorting, grouping and filtering, not searching.
54
- #
55
- # define_index do
56
- # # fields ...
57
- #
58
- # has created_at, updated_at
59
- # end
60
- #
61
- # One last feature is the delta index. This requires the model to
62
- # have a boolean field named 'delta', and is enabled as follows:
63
- #
64
- # define_index do
65
- # # fields ...
66
- # # attributes ...
67
- #
68
- # set_property :delta => true
69
- # end
70
- #
71
- # Check out the more detailed documentation for each of these methods
72
- # at ThinkingSphinx::Index::Builder.
73
- #
74
- def define_index(&block)
75
- return unless ThinkingSphinx.define_indexes?
76
-
77
- self.sphinx_indexes ||= []
78
- self.sphinx_facets ||= []
79
- index = ThinkingSphinx::Index::Builder.generate(self, &block)
80
-
81
- self.sphinx_indexes << index
82
- unless ThinkingSphinx.indexed_models.include?(self.name)
83
- ThinkingSphinx.indexed_models << self.name
84
- end
85
-
86
- if index.delta?
87
- before_save :toggle_delta
88
- after_commit :index_delta
89
- end
90
-
91
- after_destroy :toggle_deleted
92
-
93
- include ThinkingSphinx::SearchMethods
94
- include ThinkingSphinx::ActiveRecord::AttributeUpdates
95
- include ThinkingSphinx::ActiveRecord::Scopes
96
-
97
- index
98
-
99
- # We want to make sure that if the database doesn't exist, then Thinking
100
- # Sphinx doesn't mind when running non-TS tasks (like db:create, db:drop
101
- # and db:migrate). It's a bit hacky, but I can't think of a better way.
102
- rescue StandardError => err
103
- case err.class.name
104
- when "Mysql::Error", "Java::JavaSql::SQLException", "ActiveRecord::StatementInvalid"
105
- return
106
- else
107
- raise err
108
- end
109
- end
110
- alias_method :sphinx_index, :define_index
111
-
112
29
  def sphinx_index_options
113
30
  sphinx_indexes.last.options
114
31
  end
@@ -127,26 +44,6 @@ module ThinkingSphinx
127
44
  (subclasses << self).collect { |klass| klass.to_crc32 }
128
45
  end
129
46
 
130
- def source_of_sphinx_index
131
- possible_models = self.sphinx_indexes.collect { |index| index.model }
132
- return self if possible_models.include?(self)
133
-
134
- parent = self.superclass
135
- while !possible_models.include?(parent) && parent != ::ActiveRecord::Base
136
- parent = parent.superclass
137
- end
138
-
139
- return parent
140
- end
141
-
142
- def to_riddle(offset)
143
- sphinx_database_adapter.setup
144
-
145
- indexes = [to_riddle_for_core(offset)]
146
- indexes << to_riddle_for_delta(offset) if sphinx_delta?
147
- indexes << to_riddle_for_distributed
148
- end
149
-
150
47
  def sphinx_database_adapter
151
48
  @sphinx_database_adapter ||=
152
49
  ThinkingSphinx::AbstractAdapter.detect(self)
@@ -156,86 +53,36 @@ module ThinkingSphinx
156
53
  self.name.underscore.tr(':/\\', '_')
157
54
  end
158
55
 
159
- def sphinx_index_names
160
- klass = source_of_sphinx_index
161
- names = ["#{klass.sphinx_name}_core"]
162
- names << "#{klass.sphinx_name}_delta" if sphinx_delta?
163
-
164
- names
56
+ #
57
+ # The above method to_crc32s is dependant on the subclasses being loaded consistently
58
+ # After a reset_subclasses is called (during a Dispatcher.cleanup_application in development)
59
+ # Our subclasses will be lost but our context will not reload them for us.
60
+ #
61
+ # We reset the context which causes the subclasses to be reloaded next time the context is called.
62
+ #
63
+ def reset_subclasses_with_thinking_sphinx
64
+ reset_subclasses_without_thinking_sphinx
65
+ ThinkingSphinx.reset_context!
165
66
  end
166
67
 
167
- private
168
-
169
- def sphinx_delta?
170
- self.sphinx_indexes.any? { |index| index.delta? }
171
- end
68
+ alias_method_chain :reset_subclasses, :thinking_sphinx
172
69
 
173
- def to_riddle_for_core(offset)
174
- index = Riddle::Configuration::Index.new("#{sphinx_name}_core")
175
- index.path = File.join(
176
- ThinkingSphinx::Configuration.instance.searchd_file_path, index.name
177
- )
178
-
179
- set_configuration_options_for_indexes index
180
- set_field_settings_for_indexes index
181
-
182
- self.sphinx_indexes.select { |ts_index|
183
- ts_index.model == self
184
- }.each_with_index do |ts_index, i|
185
- index.sources += ts_index.sources.collect { |source|
186
- source.to_riddle_for_core(offset, i)
187
- }
188
- end
189
-
190
- index
191
- end
192
-
193
- def to_riddle_for_delta(offset)
194
- index = Riddle::Configuration::Index.new("#{sphinx_name}_delta")
195
- index.parent = "#{sphinx_name}_core"
196
- index.path = File.join(ThinkingSphinx::Configuration.instance.searchd_file_path, index.name)
197
-
198
- self.sphinx_indexes.each_with_index do |ts_index, i|
199
- index.sources += ts_index.sources.collect { |source|
200
- source.to_riddle_for_delta(offset, i)
201
- } if ts_index.delta?
202
- end
203
-
204
- index
205
- end
70
+ private
206
71
 
207
- def to_riddle_for_distributed
208
- index = Riddle::Configuration::DistributedIndex.new(sphinx_name)
209
- index.local_indexes << "#{sphinx_name}_core"
210
- index.local_indexes.unshift "#{sphinx_name}_delta" if sphinx_delta?
211
- index
72
+ def defined_indexes?
73
+ @defined_indexes
212
74
  end
213
75
 
214
- def set_configuration_options_for_indexes(index)
215
- ThinkingSphinx::Configuration.instance.index_options.each do |key, value|
216
- index.send("#{key}=".to_sym, value)
217
- end
218
-
219
- self.sphinx_indexes.each do |ts_index|
220
- ts_index.options.each do |key, value|
221
- index.send("#{key}=".to_sym, value) if ThinkingSphinx::Configuration::IndexOptions.include?(key.to_s) && !value.nil?
222
- end
223
- end
76
+ def defined_indexes=(value)
77
+ @defined_indexes = value
224
78
  end
225
79
 
226
- def set_field_settings_for_indexes(index)
227
- field_names = lambda { |field| field.unique_name.to_s }
228
-
229
- self.sphinx_indexes.each do |ts_index|
230
- index.prefix_field_names += ts_index.prefix_fields.collect(&field_names)
231
- index.infix_field_names += ts_index.infix_fields.collect(&field_names)
232
- end
80
+ def sphinx_delta?
81
+ self.sphinx_indexes.any? { |index| index.delta? }
233
82
  end
234
83
  end
235
84
  end
236
85
 
237
- base.send(:include, ThinkingSphinx::ActiveRecord::Delta)
238
-
239
86
  ::ActiveRecord::Associations::HasManyAssociation.send(
240
87
  :include, ThinkingSphinx::ActiveRecord::HasManyAssociation
241
88
  )
@@ -244,6 +91,241 @@ module ThinkingSphinx
244
91
  )
245
92
  end
246
93
 
94
+ module ClassMethods
95
+ # Allows creation of indexes for Sphinx. If you don't do this, there
96
+ # isn't much point trying to search (or using this plugin at all,
97
+ # really).
98
+ #
99
+ # An example or two:
100
+ #
101
+ # define_index
102
+ # indexes :id, :as => :model_id
103
+ # indexes name
104
+ # end
105
+ #
106
+ # You can also grab fields from associations - multiple levels deep
107
+ # if necessary.
108
+ #
109
+ # define_index do
110
+ # indexes tags.name, :as => :tag
111
+ # indexes articles.content
112
+ # indexes orders.line_items.product.name, :as => :product
113
+ # end
114
+ #
115
+ # And it will automatically concatenate multiple fields:
116
+ #
117
+ # define_index do
118
+ # indexes [author.first_name, author.last_name], :as => :author
119
+ # end
120
+ #
121
+ # The #indexes method is for fields - if you want attributes, use
122
+ # #has instead. All the same rules apply - but keep in mind that
123
+ # attributes are for sorting, grouping and filtering, not searching.
124
+ #
125
+ # define_index do
126
+ # # fields ...
127
+ #
128
+ # has created_at, updated_at
129
+ # end
130
+ #
131
+ # One last feature is the delta index. This requires the model to
132
+ # have a boolean field named 'delta', and is enabled as follows:
133
+ #
134
+ # define_index do
135
+ # # fields ...
136
+ # # attributes ...
137
+ #
138
+ # set_property :delta => true
139
+ # end
140
+ #
141
+ # Check out the more detailed documentation for each of these methods
142
+ # at ThinkingSphinx::Index::Builder.
143
+ #
144
+ def define_index(name = nil, &block)
145
+ self.sphinx_index_blocks ||= []
146
+ self.sphinx_indexes ||= []
147
+ self.sphinx_facets ||= []
148
+
149
+ ThinkingSphinx.context.add_indexed_model self
150
+
151
+ if sphinx_index_blocks.empty?
152
+ before_validation :define_indexes
153
+ before_destroy :define_indexes
154
+ end
155
+
156
+ self.sphinx_index_blocks << lambda {
157
+ add_sphinx_index name, &block
158
+ }
159
+
160
+ include ThinkingSphinx::ActiveRecord::Scopes
161
+ include ThinkingSphinx::SearchMethods
162
+ end
163
+
164
+ def define_indexes
165
+ superclass.define_indexes unless superclass == ::ActiveRecord::Base
166
+
167
+ return if sphinx_index_blocks.nil? ||
168
+ defined_indexes? ||
169
+ !ThinkingSphinx.define_indexes?
170
+
171
+ sphinx_index_blocks.each do |block|
172
+ block.call
173
+ end
174
+
175
+ self.defined_indexes = true
176
+
177
+ # We want to make sure that if the database doesn't exist, then Thinking
178
+ # Sphinx doesn't mind when running non-TS tasks (like db:create, db:drop
179
+ # and db:migrate). It's a bit hacky, but I can't think of a better way.
180
+ rescue StandardError => err
181
+ case err.class.name
182
+ when "Mysql::Error", "Java::JavaSql::SQLException", "ActiveRecord::StatementInvalid"
183
+ return
184
+ else
185
+ raise err
186
+ end
187
+ end
188
+
189
+ def add_sphinx_index(name, &block)
190
+ index = ThinkingSphinx::Index::Builder.generate self, name, &block
191
+
192
+ unless sphinx_indexes.any? { |i| i.name == index.name }
193
+ add_sphinx_callbacks_and_extend(index.delta?)
194
+ insert_sphinx_index index
195
+ end
196
+ end
197
+
198
+ def insert_sphinx_index(index)
199
+ self.sphinx_indexes << index
200
+ subclasses.each { |klass| klass.insert_sphinx_index(index) }
201
+ end
202
+
203
+ def has_sphinx_indexes?
204
+ sphinx_indexes &&
205
+ sphinx_index_blocks &&
206
+ (sphinx_indexes.length > 0 || sphinx_index_blocks.length > 0)
207
+ end
208
+
209
+ def indexed_by_sphinx?
210
+ sphinx_indexes && sphinx_indexes.length > 0
211
+ end
212
+
213
+ def delta_indexed_by_sphinx?
214
+ sphinx_indexes && sphinx_indexes.any? { |index| index.delta? }
215
+ end
216
+
217
+ def sphinx_index_names
218
+ define_indexes
219
+ sphinx_indexes.collect(&:all_names).flatten
220
+ end
221
+
222
+ def core_index_names
223
+ define_indexes
224
+ sphinx_indexes.collect(&:core_name)
225
+ end
226
+
227
+ def delta_index_names
228
+ define_indexes
229
+ sphinx_indexes.select(&:delta?).collect(&:delta_name)
230
+ end
231
+
232
+ def to_riddle
233
+ define_indexes
234
+ sphinx_database_adapter.setup
235
+
236
+ local_sphinx_indexes.collect { |index|
237
+ index.to_riddle(sphinx_offset)
238
+ }.flatten
239
+ end
240
+
241
+ def source_of_sphinx_index
242
+ define_indexes
243
+ possible_models = self.sphinx_indexes.collect { |index| index.model }
244
+ return self if possible_models.include?(self)
245
+
246
+ parent = self.superclass
247
+ while !possible_models.include?(parent) && parent != ::ActiveRecord::Base
248
+ parent = parent.superclass
249
+ end
250
+
251
+ return parent
252
+ end
253
+
254
+ def delete_in_index(index, document_id)
255
+ return unless ThinkingSphinx.sphinx_running? &&
256
+ search_for_id(document_id, index)
257
+
258
+ ThinkingSphinx::Configuration.instance.client.update(
259
+ index, ['sphinx_deleted'], {document_id => [1]}
260
+ )
261
+ end
262
+
263
+ def sphinx_offset
264
+ ThinkingSphinx.context.superclass_indexed_models.
265
+ index eldest_indexed_ancestor
266
+ end
267
+
268
+ # Temporarily disable delta indexing inside a block, then perform a single
269
+ # rebuild of index at the end.
270
+ #
271
+ # Useful when performing updates to batches of models to prevent
272
+ # the delta index being rebuilt after each individual update.
273
+ #
274
+ # In the following example, the delta index will only be rebuilt once,
275
+ # not 10 times.
276
+ #
277
+ # SomeModel.suspended_delta do
278
+ # 10.times do
279
+ # SomeModel.create( ... )
280
+ # end
281
+ # end
282
+ #
283
+ def suspended_delta(reindex_after = true, &block)
284
+ define_indexes
285
+ original_setting = ThinkingSphinx.deltas_enabled?
286
+ ThinkingSphinx.deltas_enabled = false
287
+ begin
288
+ yield
289
+ ensure
290
+ ThinkingSphinx.deltas_enabled = original_setting
291
+ self.index_delta if reindex_after
292
+ end
293
+ end
294
+
295
+ private
296
+
297
+ def local_sphinx_indexes
298
+ sphinx_indexes.select { |index|
299
+ index.model == self
300
+ }
301
+ end
302
+
303
+ def add_sphinx_callbacks_and_extend(delta = false)
304
+ unless indexed_by_sphinx?
305
+ after_destroy :toggle_deleted
306
+
307
+ include ThinkingSphinx::ActiveRecord::AttributeUpdates
308
+ end
309
+
310
+ if delta && !delta_indexed_by_sphinx?
311
+ include ThinkingSphinx::ActiveRecord::Delta
312
+
313
+ before_save :toggle_delta
314
+ after_commit :index_delta
315
+ end
316
+ end
317
+
318
+ def eldest_indexed_ancestor
319
+ ancestors.reverse.detect { |ancestor|
320
+ ThinkingSphinx.context.indexed_models.include?(ancestor.name)
321
+ }.name
322
+ end
323
+ end
324
+
325
+ attr_accessor :excerpts
326
+ attr_accessor :sphinx_attributes
327
+ attr_accessor :matching_fields
328
+
247
329
  def in_index?(suffix)
248
330
  self.class.search_for_id self.sphinx_document_id, sphinx_index_name(suffix)
249
331
  end
@@ -261,23 +343,15 @@ module ThinkingSphinx
261
343
  end
262
344
 
263
345
  def toggle_deleted
264
- return unless ThinkingSphinx.updates_enabled? && ThinkingSphinx.sphinx_running?
265
-
266
- config = ThinkingSphinx::Configuration.instance
267
- client = Riddle::Client.new config.address, config.port
346
+ return unless ThinkingSphinx.updates_enabled?
268
347
 
269
- client.update(
270
- "#{self.class.sphinx_indexes.first.name}_core",
271
- ['sphinx_deleted'],
272
- {self.sphinx_document_id => 1}
273
- ) if self.in_core_index?
348
+ self.class.core_index_names.each do |index_name|
349
+ self.class.delete_in_index index_name, self.sphinx_document_id
350
+ end
351
+ self.class.delta_index_names.each do |index_name|
352
+ self.class.delete_in_index index_name, self.sphinx_document_id
353
+ end if self.class.delta_indexed_by_sphinx? && toggled_delta?
274
354
 
275
- client.update(
276
- "#{self.class.sphinx_indexes.first.name}_delta",
277
- ['sphinx_deleted'],
278
- {self.sphinx_document_id => 1}
279
- ) if self.class.sphinx_indexes.any? { |index| index.delta? } &&
280
- self.toggled_delta?
281
355
  rescue ::ThinkingSphinx::ConnectionError
282
356
  # nothing
283
357
  end
@@ -289,12 +363,12 @@ module ThinkingSphinx
289
363
  # @return [Integer] Unique record id for the purposes of Sphinx.
290
364
  #
291
365
  def primary_key_for_sphinx
292
- @primary_key_for_sphinx ||= read_attribute(self.class.primary_key_for_sphinx)
366
+ read_attribute(self.class.primary_key_for_sphinx)
293
367
  end
294
368
 
295
369
  def sphinx_document_id
296
- primary_key_for_sphinx * ThinkingSphinx.indexed_models.size +
297
- ThinkingSphinx.indexed_models.index(self.class.source_of_sphinx_index.name)
370
+ primary_key_for_sphinx * ThinkingSphinx.context.indexed_models.size +
371
+ self.class.sphinx_offset
298
372
  end
299
373
 
300
374
  private
@@ -302,5 +376,9 @@ module ThinkingSphinx
302
376
  def sphinx_index_name(suffix)
303
377
  "#{self.class.source_of_sphinx_index.name.underscore.tr(':/\\', '_')}_#{suffix}"
304
378
  end
379
+
380
+ def define_indexes
381
+ self.class.define_indexes
382
+ end
305
383
  end
306
384
  end
@@ -10,22 +10,50 @@ module ThinkingSphinx
10
10
  end
11
11
 
12
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
+ else
20
+ raise "Invalid Database Adapter: Sphinx only supports MySQL and PostgreSQL, not #{adapter}"
21
+ end
22
+ end
23
+
24
+ def self.adapter_for_model(model)
25
+ case ThinkingSphinx.database_adapter
26
+ when String
27
+ ThinkingSphinx.database_adapter.to_sym
28
+ when NilClass
29
+ standard_adapter_for_model model
30
+ when Proc
31
+ ThinkingSphinx.database_adapter.call model
32
+ else
33
+ ThinkingSphinx.database_adapter
34
+ end
35
+ end
36
+
37
+ def self.standard_adapter_for_model(model)
13
38
  case model.connection.class.name
14
39
  when "ActiveRecord::ConnectionAdapters::MysqlAdapter",
15
- "ActiveRecord::ConnectionAdapters::MysqlplusAdapter"
16
- ThinkingSphinx::MysqlAdapter.new model
40
+ "ActiveRecord::ConnectionAdapters::MysqlplusAdapter",
41
+ "ActiveRecord::ConnectionAdapters::Mysql2Adapter",
42
+ "ActiveRecord::ConnectionAdapters::NullDBAdapter"
43
+ :mysql
17
44
  when "ActiveRecord::ConnectionAdapters::PostgreSQLAdapter"
18
- ThinkingSphinx::PostgreSQLAdapter.new model
45
+ :postgresql
19
46
  when "ActiveRecord::ConnectionAdapters::JdbcAdapter"
20
- if model.connection.config[:adapter] == "jdbcmysql"
21
- ThinkingSphinx::MysqlAdapter.new model
22
- elsif model.connection.config[:adapter] == "jdbcpostgresql"
23
- ThinkingSphinx::PostgreSQLAdapter.new model
47
+ case model.connection.config[:adapter]
48
+ when "jdbcmysql"
49
+ :mysql
50
+ when "jdbcpostgresql"
51
+ :postgresql
24
52
  else
25
- raise "Invalid Database Adapter: Sphinx only supports MySQL and PostgreSQL"
53
+ model.connection.config[:adapter]
26
54
  end
27
55
  else
28
- raise "Invalid Database Adapter: Sphinx only supports MySQL and PostgreSQL, not #{model.connection.class.name}"
56
+ model.connection.class.name
29
57
  end
30
58
  end
31
59
 
@@ -33,6 +61,14 @@ module ThinkingSphinx
33
61
  "#{@model.quoted_table_name}.#{@model.connection.quote_column_name(column)}"
34
62
  end
35
63
 
64
+ def bigint_pattern
65
+ /bigint/i
66
+ end
67
+
68
+ def downcase(clause)
69
+ "LOWER(#{clause})"
70
+ end
71
+
36
72
  protected
37
73
 
38
74
  def connection
@@ -50,5 +50,9 @@ module ThinkingSphinx
50
50
  def time_difference(diff)
51
51
  "DATE_SUB(NOW(), INTERVAL #{diff} SECOND)"
52
52
  end
53
+
54
+ def utc_query_pre
55
+ "SET TIME_ZONE = '+0:00'"
56
+ end
53
57
  end
54
- end
58
+ end
@@ -67,6 +67,10 @@ module ThinkingSphinx
67
67
  "current_timestamp - interval '#{diff} seconds'"
68
68
  end
69
69
 
70
+ def utc_query_pre
71
+ "SET TIME ZONE 'UTC'"
72
+ end
73
+
70
74
  private
71
75
 
72
76
  def execute(command, output_error = false)
@@ -115,6 +119,10 @@ module ThinkingSphinx
115
119
  DECLARE j int;
116
120
  DECLARE word_array bytea;
117
121
  BEGIN
122
+ IF COALESCE(word, '') = '' THEN
123
+ return 0;
124
+ END IF;
125
+
118
126
  i = 0;
119
127
  tmp = 4294967295;
120
128
  word_array = decode(replace(word, E'\\\\', E'\\\\\\\\'), 'escape');
@@ -135,7 +143,7 @@ module ThinkingSphinx
135
143
  END LOOP;
136
144
  return (tmp # 4294967295);
137
145
  END
138
- $$ IMMUTABLE STRICT LANGUAGE plpgsql;
146
+ $$ IMMUTABLE LANGUAGE plpgsql;
139
147
  SQL
140
148
  execute function, true
141
149
  end