warp-thinking-sphinx 1.2.12

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 (190) hide show
  1. data/LICENCE +20 -0
  2. data/README.textile +144 -0
  3. data/VERSION.yml +4 -0
  4. data/features/a.rb +17 -0
  5. data/features/alternate_primary_key.feature +27 -0
  6. data/features/attribute_transformation.feature +22 -0
  7. data/features/attribute_updates.feature +33 -0
  8. data/features/datetime_deltas.feature +66 -0
  9. data/features/delayed_delta_indexing.feature +37 -0
  10. data/features/deleting_instances.feature +64 -0
  11. data/features/direct_attributes.feature +11 -0
  12. data/features/excerpts.feature +13 -0
  13. data/features/extensible_delta_indexing.feature +9 -0
  14. data/features/facets.feature +76 -0
  15. data/features/facets_across_model.feature +29 -0
  16. data/features/handling_edits.feature +92 -0
  17. data/features/retry_stale_indexes.feature +24 -0
  18. data/features/searching_across_models.feature +20 -0
  19. data/features/searching_by_model.feature +175 -0
  20. data/features/searching_with_find_arguments.feature +56 -0
  21. data/features/sphinx_detection.feature +25 -0
  22. data/features/sphinx_scopes.feature +35 -0
  23. data/features/step_definitions/alpha_steps.rb +3 -0
  24. data/features/step_definitions/beta_steps.rb +7 -0
  25. data/features/step_definitions/common_steps.rb +178 -0
  26. data/features/step_definitions/datetime_delta_steps.rb +15 -0
  27. data/features/step_definitions/delayed_delta_indexing_steps.rb +7 -0
  28. data/features/step_definitions/extensible_delta_indexing_steps.rb +7 -0
  29. data/features/step_definitions/facet_steps.rb +92 -0
  30. data/features/step_definitions/find_arguments_steps.rb +36 -0
  31. data/features/step_definitions/gamma_steps.rb +15 -0
  32. data/features/step_definitions/scope_steps.rb +11 -0
  33. data/features/step_definitions/search_steps.rb +89 -0
  34. data/features/step_definitions/sphinx_steps.rb +31 -0
  35. data/features/sti_searching.feature +14 -0
  36. data/features/support/db/active_record.rb +40 -0
  37. data/features/support/db/database.example.yml +3 -0
  38. data/features/support/db/fixtures/alphas.rb +10 -0
  39. data/features/support/db/fixtures/authors.rb +1 -0
  40. data/features/support/db/fixtures/betas.rb +10 -0
  41. data/features/support/db/fixtures/boxes.rb +9 -0
  42. data/features/support/db/fixtures/categories.rb +1 -0
  43. data/features/support/db/fixtures/cats.rb +3 -0
  44. data/features/support/db/fixtures/comments.rb +24 -0
  45. data/features/support/db/fixtures/delayed_betas.rb +10 -0
  46. data/features/support/db/fixtures/developers.rb +29 -0
  47. data/features/support/db/fixtures/dogs.rb +3 -0
  48. data/features/support/db/fixtures/extensible_betas.rb +10 -0
  49. data/features/support/db/fixtures/gammas.rb +10 -0
  50. data/features/support/db/fixtures/people.rb +1001 -0
  51. data/features/support/db/fixtures/posts.rb +6 -0
  52. data/features/support/db/fixtures/robots.rb +14 -0
  53. data/features/support/db/fixtures/tags.rb +27 -0
  54. data/features/support/db/fixtures/thetas.rb +10 -0
  55. data/features/support/db/migrations/create_alphas.rb +7 -0
  56. data/features/support/db/migrations/create_animals.rb +5 -0
  57. data/features/support/db/migrations/create_authors.rb +3 -0
  58. data/features/support/db/migrations/create_authors_posts.rb +6 -0
  59. data/features/support/db/migrations/create_betas.rb +5 -0
  60. data/features/support/db/migrations/create_boxes.rb +5 -0
  61. data/features/support/db/migrations/create_categories.rb +3 -0
  62. data/features/support/db/migrations/create_comments.rb +10 -0
  63. data/features/support/db/migrations/create_delayed_betas.rb +17 -0
  64. data/features/support/db/migrations/create_developers.rb +9 -0
  65. data/features/support/db/migrations/create_extensible_betas.rb +5 -0
  66. data/features/support/db/migrations/create_gammas.rb +3 -0
  67. data/features/support/db/migrations/create_people.rb +13 -0
  68. data/features/support/db/migrations/create_posts.rb +5 -0
  69. data/features/support/db/migrations/create_robots.rb +5 -0
  70. data/features/support/db/migrations/create_taggings.rb +5 -0
  71. data/features/support/db/migrations/create_tags.rb +4 -0
  72. data/features/support/db/migrations/create_thetas.rb +5 -0
  73. data/features/support/db/mysql.rb +3 -0
  74. data/features/support/db/postgresql.rb +3 -0
  75. data/features/support/env.rb +6 -0
  76. data/features/support/lib/generic_delta_handler.rb +8 -0
  77. data/features/support/models/alpha.rb +10 -0
  78. data/features/support/models/animal.rb +5 -0
  79. data/features/support/models/author.rb +3 -0
  80. data/features/support/models/beta.rb +8 -0
  81. data/features/support/models/box.rb +8 -0
  82. data/features/support/models/cat.rb +3 -0
  83. data/features/support/models/category.rb +4 -0
  84. data/features/support/models/comment.rb +10 -0
  85. data/features/support/models/delayed_beta.rb +7 -0
  86. data/features/support/models/developer.rb +16 -0
  87. data/features/support/models/dog.rb +3 -0
  88. data/features/support/models/extensible_beta.rb +9 -0
  89. data/features/support/models/gamma.rb +5 -0
  90. data/features/support/models/person.rb +23 -0
  91. data/features/support/models/post.rb +20 -0
  92. data/features/support/models/robot.rb +8 -0
  93. data/features/support/models/tag.rb +3 -0
  94. data/features/support/models/tagging.rb +4 -0
  95. data/features/support/models/theta.rb +7 -0
  96. data/features/support/post_database.rb +43 -0
  97. data/features/support/z.rb +19 -0
  98. data/lib/thinking_sphinx.rb +212 -0
  99. data/lib/thinking_sphinx/active_record.rb +306 -0
  100. data/lib/thinking_sphinx/active_record/attribute_updates.rb +48 -0
  101. data/lib/thinking_sphinx/active_record/delta.rb +87 -0
  102. data/lib/thinking_sphinx/active_record/has_many_association.rb +28 -0
  103. data/lib/thinking_sphinx/active_record/scopes.rb +39 -0
  104. data/lib/thinking_sphinx/adapters/abstract_adapter.rb +42 -0
  105. data/lib/thinking_sphinx/adapters/mysql_adapter.rb +54 -0
  106. data/lib/thinking_sphinx/adapters/postgresql_adapter.rb +136 -0
  107. data/lib/thinking_sphinx/association.rb +243 -0
  108. data/lib/thinking_sphinx/attribute.rb +340 -0
  109. data/lib/thinking_sphinx/class_facet.rb +15 -0
  110. data/lib/thinking_sphinx/configuration.rb +282 -0
  111. data/lib/thinking_sphinx/core/array.rb +7 -0
  112. data/lib/thinking_sphinx/core/string.rb +15 -0
  113. data/lib/thinking_sphinx/deltas.rb +30 -0
  114. data/lib/thinking_sphinx/deltas/datetime_delta.rb +50 -0
  115. data/lib/thinking_sphinx/deltas/default_delta.rb +68 -0
  116. data/lib/thinking_sphinx/deltas/delayed_delta.rb +30 -0
  117. data/lib/thinking_sphinx/deltas/delayed_delta/delta_job.rb +24 -0
  118. data/lib/thinking_sphinx/deltas/delayed_delta/flag_as_deleted_job.rb +27 -0
  119. data/lib/thinking_sphinx/deltas/delayed_delta/job.rb +26 -0
  120. data/lib/thinking_sphinx/deploy/capistrano.rb +100 -0
  121. data/lib/thinking_sphinx/excerpter.rb +22 -0
  122. data/lib/thinking_sphinx/facet.rb +125 -0
  123. data/lib/thinking_sphinx/facet_search.rb +134 -0
  124. data/lib/thinking_sphinx/field.rb +81 -0
  125. data/lib/thinking_sphinx/index.rb +99 -0
  126. data/lib/thinking_sphinx/index/builder.rb +286 -0
  127. data/lib/thinking_sphinx/index/faux_column.rb +110 -0
  128. data/lib/thinking_sphinx/property.rb +163 -0
  129. data/lib/thinking_sphinx/rails_additions.rb +150 -0
  130. data/lib/thinking_sphinx/search.rb +708 -0
  131. data/lib/thinking_sphinx/search_methods.rb +421 -0
  132. data/lib/thinking_sphinx/source.rb +152 -0
  133. data/lib/thinking_sphinx/source/internal_properties.rb +46 -0
  134. data/lib/thinking_sphinx/source/sql.rb +127 -0
  135. data/lib/thinking_sphinx/tasks.rb +165 -0
  136. data/rails/init.rb +14 -0
  137. data/spec/lib/thinking_sphinx/active_record/delta_spec.rb +130 -0
  138. data/spec/lib/thinking_sphinx/active_record/has_many_association_spec.rb +49 -0
  139. data/spec/lib/thinking_sphinx/active_record/scopes_spec.rb +96 -0
  140. data/spec/lib/thinking_sphinx/active_record_spec.rb +353 -0
  141. data/spec/lib/thinking_sphinx/association_spec.rb +239 -0
  142. data/spec/lib/thinking_sphinx/attribute_spec.rb +507 -0
  143. data/spec/lib/thinking_sphinx/configuration_spec.rb +268 -0
  144. data/spec/lib/thinking_sphinx/core/array_spec.rb +9 -0
  145. data/spec/lib/thinking_sphinx/core/string_spec.rb +9 -0
  146. data/spec/lib/thinking_sphinx/deltas/job_spec.rb +32 -0
  147. data/spec/lib/thinking_sphinx/excerpter_spec.rb +57 -0
  148. data/spec/lib/thinking_sphinx/facet_search_spec.rb +176 -0
  149. data/spec/lib/thinking_sphinx/facet_spec.rb +333 -0
  150. data/spec/lib/thinking_sphinx/field_spec.rb +154 -0
  151. data/spec/lib/thinking_sphinx/index/builder_spec.rb +455 -0
  152. data/spec/lib/thinking_sphinx/index/faux_column_spec.rb +30 -0
  153. data/spec/lib/thinking_sphinx/index_spec.rb +45 -0
  154. data/spec/lib/thinking_sphinx/rails_additions_spec.rb +203 -0
  155. data/spec/lib/thinking_sphinx/search_methods_spec.rb +152 -0
  156. data/spec/lib/thinking_sphinx/search_spec.rb +1101 -0
  157. data/spec/lib/thinking_sphinx/source_spec.rb +227 -0
  158. data/spec/lib/thinking_sphinx_spec.rb +162 -0
  159. data/tasks/distribution.rb +55 -0
  160. data/tasks/rails.rake +1 -0
  161. data/tasks/testing.rb +83 -0
  162. data/vendor/after_commit/LICENSE +20 -0
  163. data/vendor/after_commit/README +16 -0
  164. data/vendor/after_commit/Rakefile +22 -0
  165. data/vendor/after_commit/init.rb +8 -0
  166. data/vendor/after_commit/lib/after_commit.rb +45 -0
  167. data/vendor/after_commit/lib/after_commit/active_record.rb +114 -0
  168. data/vendor/after_commit/lib/after_commit/connection_adapters.rb +103 -0
  169. data/vendor/after_commit/test/after_commit_test.rb +53 -0
  170. data/vendor/delayed_job/lib/delayed/job.rb +251 -0
  171. data/vendor/delayed_job/lib/delayed/message_sending.rb +7 -0
  172. data/vendor/delayed_job/lib/delayed/performable_method.rb +55 -0
  173. data/vendor/delayed_job/lib/delayed/worker.rb +54 -0
  174. data/vendor/riddle/lib/riddle.rb +30 -0
  175. data/vendor/riddle/lib/riddle/client.rb +635 -0
  176. data/vendor/riddle/lib/riddle/client/filter.rb +53 -0
  177. data/vendor/riddle/lib/riddle/client/message.rb +66 -0
  178. data/vendor/riddle/lib/riddle/client/response.rb +84 -0
  179. data/vendor/riddle/lib/riddle/configuration.rb +33 -0
  180. data/vendor/riddle/lib/riddle/configuration/distributed_index.rb +48 -0
  181. data/vendor/riddle/lib/riddle/configuration/index.rb +142 -0
  182. data/vendor/riddle/lib/riddle/configuration/indexer.rb +19 -0
  183. data/vendor/riddle/lib/riddle/configuration/remote_index.rb +17 -0
  184. data/vendor/riddle/lib/riddle/configuration/searchd.rb +25 -0
  185. data/vendor/riddle/lib/riddle/configuration/section.rb +43 -0
  186. data/vendor/riddle/lib/riddle/configuration/source.rb +23 -0
  187. data/vendor/riddle/lib/riddle/configuration/sql_source.rb +34 -0
  188. data/vendor/riddle/lib/riddle/configuration/xml_source.rb +28 -0
  189. data/vendor/riddle/lib/riddle/controller.rb +53 -0
  190. metadata +267 -0
@@ -0,0 +1,19 @@
1
+ # This file exists because Cucumber likes to auto-load all ruby files
2
+ puts <<-MESSAGE
3
+ Cucumber 0.1.13 defaults to loading all ruby files within the features folder,
4
+ with something approaching reverse-alphabetical order, and preferring the
5
+ features/support folder over everything else. This is annoying, because some
6
+ files need to be loaded before others (and others perhaps not at all, given
7
+ missing dependencies). Hence this place-holder imaginatively named 'z.rb', to
8
+ force this message.
9
+
10
+ A work-around is to use cucumber profiles. You will find the default profile in
11
+ cucumber.yml should serve your needs fine, unless you add new step definitions.
12
+ When you do that, you can regenerate the YAML file by running:
13
+ rake cucumber_defaults
14
+
15
+ And then run specific features as follows is slightly more verbose, but it
16
+ works, whereas this doesn't.
17
+ cucumber -p default features/something.feature
18
+ MESSAGE
19
+ exit 0
@@ -0,0 +1,212 @@
1
+ Dir[File.join(File.dirname(__FILE__), '../vendor/*/lib')].each do |path|
2
+ $LOAD_PATH.unshift path
3
+ end
4
+
5
+ require 'active_record'
6
+ require 'riddle'
7
+ require 'after_commit'
8
+ require 'yaml'
9
+ require 'cgi'
10
+
11
+ require 'thinking_sphinx/core/array'
12
+ require 'thinking_sphinx/core/string'
13
+ require 'thinking_sphinx/property'
14
+ require 'thinking_sphinx/active_record'
15
+ require 'thinking_sphinx/association'
16
+ require 'thinking_sphinx/attribute'
17
+ require 'thinking_sphinx/configuration'
18
+ require 'thinking_sphinx/excerpter'
19
+ require 'thinking_sphinx/facet'
20
+ require 'thinking_sphinx/class_facet'
21
+ require 'thinking_sphinx/facet_search'
22
+ require 'thinking_sphinx/field'
23
+ require 'thinking_sphinx/index'
24
+ require 'thinking_sphinx/source'
25
+ require 'thinking_sphinx/rails_additions'
26
+ require 'thinking_sphinx/search'
27
+ require 'thinking_sphinx/search_methods'
28
+ require 'thinking_sphinx/deltas'
29
+
30
+ require 'thinking_sphinx/adapters/abstract_adapter'
31
+ require 'thinking_sphinx/adapters/mysql_adapter'
32
+ require 'thinking_sphinx/adapters/postgresql_adapter'
33
+
34
+ ActiveRecord::Base.send(:include, ThinkingSphinx::ActiveRecord)
35
+
36
+ Merb::Plugins.add_rakefiles(
37
+ File.join(File.dirname(__FILE__), "thinking_sphinx", "tasks")
38
+ ) if defined?(Merb)
39
+
40
+ module ThinkingSphinx
41
+ # A ConnectionError will get thrown when a connection to Sphinx can't be
42
+ # made.
43
+ class ConnectionError < StandardError
44
+ end
45
+
46
+ # A StaleIdsException is thrown by Collection.instances_from_matches if there
47
+ # are records in Sphinx but not in the database, so the search can be retried.
48
+ class StaleIdsException < StandardError
49
+ attr_accessor :ids
50
+ def initialize(ids)
51
+ self.ids = ids
52
+ end
53
+ end
54
+
55
+ # The current version of Thinking Sphinx.
56
+ #
57
+ # @return [String] The version number as a string
58
+ #
59
+ def self.version
60
+ hash = YAML.load_file File.join(File.dirname(__FILE__), '../VERSION.yml')
61
+ [hash[:major], hash[:minor], hash[:patch]].join('.')
62
+ end
63
+
64
+ # The collection of indexed models. Keep in mind that Rails lazily loads
65
+ # its classes, so this may not actually be populated with _all_ the models
66
+ # that have Sphinx indexes.
67
+ def self.indexed_models
68
+ @@indexed_models ||= []
69
+ end
70
+
71
+ def self.unique_id_expression(offset = nil)
72
+ "* #{ThinkingSphinx.indexed_models.size} + #{offset || 0}"
73
+ end
74
+
75
+ # Check if index definition is disabled.
76
+ #
77
+ def self.define_indexes?
78
+ @@define_indexes = true unless defined?(@@define_indexes)
79
+ @@define_indexes == true
80
+ end
81
+
82
+ # Enable/disable indexes - you may want to do this while migrating data.
83
+ #
84
+ # ThinkingSphinx.define_indexes = false
85
+ #
86
+ def self.define_indexes=(value)
87
+ @@define_indexes = value
88
+ end
89
+
90
+ @@deltas_enabled = nil
91
+
92
+ # Check if delta indexing is enabled.
93
+ #
94
+ def self.deltas_enabled?
95
+ @@deltas_enabled = (ThinkingSphinx::Configuration.environment != 'test') if @@deltas_enabled.nil?
96
+ @@deltas_enabled
97
+ end
98
+
99
+ # Enable/disable all delta indexing.
100
+ #
101
+ # ThinkingSphinx.deltas_enabled = false
102
+ #
103
+ def self.deltas_enabled=(value)
104
+ @@deltas_enabled = value
105
+ end
106
+
107
+ @@updates_enabled = nil
108
+
109
+ # Check if updates are enabled. True by default, unless within the test
110
+ # environment.
111
+ #
112
+ def self.updates_enabled?
113
+ @@updates_enabled = (ThinkingSphinx::Configuration.environment != 'test') if @@updates_enabled.nil?
114
+ @@updates_enabled
115
+ end
116
+
117
+ # Enable/disable updates to Sphinx
118
+ #
119
+ # ThinkingSphinx.updates_enabled = false
120
+ #
121
+ def self.updates_enabled=(value)
122
+ @@updates_enabled = value
123
+ end
124
+
125
+ @@suppress_delta_output = false
126
+
127
+ def self.suppress_delta_output?
128
+ @@suppress_delta_output
129
+ end
130
+
131
+ def self.suppress_delta_output=(value)
132
+ @@suppress_delta_output = value
133
+ end
134
+
135
+ # Checks to see if MySQL will allow simplistic GROUP BY statements. If not,
136
+ # or if not using MySQL, this will return false.
137
+ #
138
+ def self.use_group_by_shortcut?
139
+ !!(
140
+ mysql? && ::ActiveRecord::Base.connection.select_all(
141
+ "SELECT @@global.sql_mode, @@session.sql_mode;"
142
+ ).all? { |key,value| value.nil? || value[/ONLY_FULL_GROUP_BY/].nil? }
143
+ )
144
+ end
145
+
146
+ @@remote_sphinx = false
147
+
148
+ # An indication of whether Sphinx is running on a remote machine instead of
149
+ # the same machine.
150
+ #
151
+ def self.remote_sphinx?
152
+ @@remote_sphinx
153
+ end
154
+
155
+ # Tells Thinking Sphinx that Sphinx is running on a different machine, and
156
+ # thus it can't reliably guess whether it is running or not (ie: the
157
+ # #sphinx_running? method), and so just assumes it is.
158
+ #
159
+ # Useful for multi-machine deployments. Set it in your production.rb file.
160
+ #
161
+ # ThinkingSphinx.remote_sphinx = true
162
+ #
163
+ def self.remote_sphinx=(value)
164
+ @@remote_sphinx = value
165
+ end
166
+
167
+ # Check if Sphinx is running. If remote_sphinx is set to true (indicating
168
+ # Sphinx is on a different machine), this will always return true, and you
169
+ # will have to handle any connection errors yourself.
170
+ #
171
+ def self.sphinx_running?
172
+ remote_sphinx? || sphinx_running_by_pid?
173
+ end
174
+
175
+ # Check if Sphinx is actually running, provided the pid is on the same
176
+ # machine as this code.
177
+ #
178
+ def self.sphinx_running_by_pid?
179
+ !!sphinx_pid && pid_active?(sphinx_pid)
180
+ end
181
+
182
+ def self.sphinx_pid
183
+ if File.exists?(ThinkingSphinx::Configuration.instance.pid_file)
184
+ File.read(ThinkingSphinx::Configuration.instance.pid_file)[/\d+/]
185
+ else
186
+ nil
187
+ end
188
+ end
189
+
190
+ def self.pid_active?(pid)
191
+ !!Process.kill(0, pid.to_i)
192
+ rescue Exception => e
193
+ false
194
+ end
195
+
196
+ def self.microsoft?
197
+ RUBY_PLATFORM =~ /mswin/
198
+ end
199
+
200
+ def self.jruby?
201
+ defined?(JRUBY_VERSION)
202
+ end
203
+
204
+ def self.mysql?
205
+ ::ActiveRecord::Base.connection.class.name.demodulize == "MysqlAdapter" ||
206
+ ::ActiveRecord::Base.connection.class.name.demodulize == "MysqlplusAdapter" || (
207
+ jruby? && ::ActiveRecord::Base.connection.config[:adapter] == "jdbcmysql"
208
+ )
209
+ end
210
+
211
+ extend ThinkingSphinx::SearchMethods::ClassMethods
212
+ end
@@ -0,0 +1,306 @@
1
+ require 'thinking_sphinx/active_record/attribute_updates'
2
+ require 'thinking_sphinx/active_record/delta'
3
+ require 'thinking_sphinx/active_record/has_many_association'
4
+ require 'thinking_sphinx/active_record/scopes'
5
+
6
+ module ThinkingSphinx
7
+ # Core additions to ActiveRecord models - define_index for creating indexes
8
+ # for models. If you want to interrogate the index objects created for the
9
+ # model, you can use the class-level accessor :sphinx_indexes.
10
+ #
11
+ module ActiveRecord
12
+ def self.included(base)
13
+ base.class_eval do
14
+ class_inheritable_array :sphinx_indexes, :sphinx_facets
15
+ class << self
16
+
17
+ def set_sphinx_primary_key(attribute)
18
+ @sphinx_primary_key_attribute = attribute
19
+ end
20
+
21
+ def primary_key_for_sphinx
22
+ @sphinx_primary_key_attribute || primary_key
23
+ end
24
+
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
+ def sphinx_index_options
113
+ sphinx_indexes.last.options
114
+ end
115
+
116
+ # Generate a unique CRC value for the model's name, to use to
117
+ # determine which Sphinx documents belong to which AR records.
118
+ #
119
+ # Really only written for internal use - but hey, if it's useful to
120
+ # you in some other way, awesome.
121
+ #
122
+ def to_crc32
123
+ self.name.to_crc32
124
+ end
125
+
126
+ def to_crc32s
127
+ (subclasses << self).collect { |klass| klass.to_crc32 }
128
+ end
129
+
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
+ def sphinx_database_adapter
151
+ @sphinx_database_adapter ||=
152
+ ThinkingSphinx::AbstractAdapter.detect(self)
153
+ end
154
+
155
+ def sphinx_name
156
+ self.name.underscore.tr(':/\\', '_')
157
+ end
158
+
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
165
+ end
166
+
167
+ private
168
+
169
+ def sphinx_delta?
170
+ self.sphinx_indexes.any? { |index| index.delta? }
171
+ end
172
+
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
206
+
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
212
+ end
213
+
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
224
+ end
225
+
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
233
+ end
234
+ end
235
+ end
236
+
237
+ base.send(:include, ThinkingSphinx::ActiveRecord::Delta)
238
+
239
+ ::ActiveRecord::Associations::HasManyAssociation.send(
240
+ :include, ThinkingSphinx::ActiveRecord::HasManyAssociation
241
+ )
242
+ ::ActiveRecord::Associations::HasManyThroughAssociation.send(
243
+ :include, ThinkingSphinx::ActiveRecord::HasManyAssociation
244
+ )
245
+ end
246
+
247
+ def in_index?(suffix)
248
+ self.class.search_for_id self.sphinx_document_id, sphinx_index_name(suffix)
249
+ end
250
+
251
+ def in_core_index?
252
+ in_index? "core"
253
+ end
254
+
255
+ def in_delta_index?
256
+ in_index? "delta"
257
+ end
258
+
259
+ def in_both_indexes?
260
+ in_core_index? && in_delta_index?
261
+ end
262
+
263
+ 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
268
+
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?
274
+
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
+ rescue ::ThinkingSphinx::ConnectionError
282
+ # nothing
283
+ end
284
+
285
+ # Returns the unique integer id for the object. This method uses the
286
+ # attribute hash to get around ActiveRecord always mapping the #id method
287
+ # to whatever the real primary key is (which may be a unique string hash).
288
+ #
289
+ # @return [Integer] Unique record id for the purposes of Sphinx.
290
+ #
291
+ def primary_key_for_sphinx
292
+ @primary_key_for_sphinx ||= read_attribute(self.class.primary_key_for_sphinx)
293
+ end
294
+
295
+ 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)
298
+ end
299
+
300
+ private
301
+
302
+ def sphinx_index_name(suffix)
303
+ "#{self.class.source_of_sphinx_index.name.underscore.tr(':/\\', '_')}_#{suffix}"
304
+ end
305
+ end
306
+ end