initforthe-thinking-sphinx 1.1.21

Sign up to get free protection for your applications and to get access to all the features.
Files changed (91) hide show
  1. data/LICENCE +20 -0
  2. data/README.textile +141 -0
  3. data/lib/thinking_sphinx.rb +215 -0
  4. data/lib/thinking_sphinx/active_record.rb +278 -0
  5. data/lib/thinking_sphinx/active_record/attribute_updates.rb +48 -0
  6. data/lib/thinking_sphinx/active_record/delta.rb +87 -0
  7. data/lib/thinking_sphinx/active_record/has_many_association.rb +29 -0
  8. data/lib/thinking_sphinx/active_record/search.rb +57 -0
  9. data/lib/thinking_sphinx/adapters/abstract_adapter.rb +42 -0
  10. data/lib/thinking_sphinx/adapters/mysql_adapter.rb +54 -0
  11. data/lib/thinking_sphinx/adapters/postgresql_adapter.rb +135 -0
  12. data/lib/thinking_sphinx/association.rb +164 -0
  13. data/lib/thinking_sphinx/attribute.rb +268 -0
  14. data/lib/thinking_sphinx/class_facet.rb +15 -0
  15. data/lib/thinking_sphinx/collection.rb +148 -0
  16. data/lib/thinking_sphinx/configuration.rb +262 -0
  17. data/lib/thinking_sphinx/core/string.rb +15 -0
  18. data/lib/thinking_sphinx/deltas.rb +30 -0
  19. data/lib/thinking_sphinx/deltas/datetime_delta.rb +50 -0
  20. data/lib/thinking_sphinx/deltas/default_delta.rb +68 -0
  21. data/lib/thinking_sphinx/deltas/delayed_delta.rb +27 -0
  22. data/lib/thinking_sphinx/deltas/delayed_delta/delta_job.rb +24 -0
  23. data/lib/thinking_sphinx/deltas/delayed_delta/flag_as_deleted_job.rb +27 -0
  24. data/lib/thinking_sphinx/deltas/delayed_delta/job.rb +26 -0
  25. data/lib/thinking_sphinx/deploy/capistrano.rb +82 -0
  26. data/lib/thinking_sphinx/facet.rb +108 -0
  27. data/lib/thinking_sphinx/facet_collection.rb +59 -0
  28. data/lib/thinking_sphinx/field.rb +82 -0
  29. data/lib/thinking_sphinx/index.rb +99 -0
  30. data/lib/thinking_sphinx/index/builder.rb +287 -0
  31. data/lib/thinking_sphinx/index/faux_column.rb +110 -0
  32. data/lib/thinking_sphinx/property.rb +160 -0
  33. data/lib/thinking_sphinx/rails_additions.rb +136 -0
  34. data/lib/thinking_sphinx/search.rb +727 -0
  35. data/lib/thinking_sphinx/search/facets.rb +104 -0
  36. data/lib/thinking_sphinx/source.rb +150 -0
  37. data/lib/thinking_sphinx/source/internal_properties.rb +46 -0
  38. data/lib/thinking_sphinx/source/sql.rb +126 -0
  39. data/lib/thinking_sphinx/tasks.rb +162 -0
  40. data/rails/init.rb +14 -0
  41. data/spec/unit/thinking_sphinx/active_record/delta_spec.rb +136 -0
  42. data/spec/unit/thinking_sphinx/active_record/has_many_association_spec.rb +53 -0
  43. data/spec/unit/thinking_sphinx/active_record/search_spec.rb +107 -0
  44. data/spec/unit/thinking_sphinx/active_record_spec.rb +329 -0
  45. data/spec/unit/thinking_sphinx/association_spec.rb +246 -0
  46. data/spec/unit/thinking_sphinx/attribute_spec.rb +338 -0
  47. data/spec/unit/thinking_sphinx/collection_spec.rb +15 -0
  48. data/spec/unit/thinking_sphinx/configuration_spec.rb +222 -0
  49. data/spec/unit/thinking_sphinx/core/string_spec.rb +9 -0
  50. data/spec/unit/thinking_sphinx/facet_collection_spec.rb +64 -0
  51. data/spec/unit/thinking_sphinx/facet_spec.rb +302 -0
  52. data/spec/unit/thinking_sphinx/field_spec.rb +154 -0
  53. data/spec/unit/thinking_sphinx/index/builder_spec.rb +355 -0
  54. data/spec/unit/thinking_sphinx/index/faux_column_spec.rb +30 -0
  55. data/spec/unit/thinking_sphinx/index_spec.rb +45 -0
  56. data/spec/unit/thinking_sphinx/rails_additions_spec.rb +191 -0
  57. data/spec/unit/thinking_sphinx/search_spec.rb +228 -0
  58. data/spec/unit/thinking_sphinx/source_spec.rb +217 -0
  59. data/spec/unit/thinking_sphinx_spec.rb +151 -0
  60. data/tasks/distribution.rb +67 -0
  61. data/tasks/rails.rake +1 -0
  62. data/tasks/testing.rb +78 -0
  63. data/vendor/after_commit/LICENSE +20 -0
  64. data/vendor/after_commit/README +16 -0
  65. data/vendor/after_commit/Rakefile +22 -0
  66. data/vendor/after_commit/init.rb +8 -0
  67. data/vendor/after_commit/lib/after_commit.rb +45 -0
  68. data/vendor/after_commit/lib/after_commit/active_record.rb +114 -0
  69. data/vendor/after_commit/lib/after_commit/connection_adapters.rb +103 -0
  70. data/vendor/after_commit/test/after_commit_test.rb +53 -0
  71. data/vendor/delayed_job/lib/delayed/job.rb +251 -0
  72. data/vendor/delayed_job/lib/delayed/message_sending.rb +7 -0
  73. data/vendor/delayed_job/lib/delayed/performable_method.rb +55 -0
  74. data/vendor/delayed_job/lib/delayed/worker.rb +54 -0
  75. data/vendor/riddle/lib/riddle.rb +30 -0
  76. data/vendor/riddle/lib/riddle/client.rb +619 -0
  77. data/vendor/riddle/lib/riddle/client/filter.rb +53 -0
  78. data/vendor/riddle/lib/riddle/client/message.rb +65 -0
  79. data/vendor/riddle/lib/riddle/client/response.rb +84 -0
  80. data/vendor/riddle/lib/riddle/configuration.rb +33 -0
  81. data/vendor/riddle/lib/riddle/configuration/distributed_index.rb +48 -0
  82. data/vendor/riddle/lib/riddle/configuration/index.rb +142 -0
  83. data/vendor/riddle/lib/riddle/configuration/indexer.rb +19 -0
  84. data/vendor/riddle/lib/riddle/configuration/remote_index.rb +17 -0
  85. data/vendor/riddle/lib/riddle/configuration/searchd.rb +25 -0
  86. data/vendor/riddle/lib/riddle/configuration/section.rb +43 -0
  87. data/vendor/riddle/lib/riddle/configuration/source.rb +23 -0
  88. data/vendor/riddle/lib/riddle/configuration/sql_source.rb +34 -0
  89. data/vendor/riddle/lib/riddle/configuration/xml_source.rb +28 -0
  90. data/vendor/riddle/lib/riddle/controller.rb +44 -0
  91. metadata +190 -0
data/LICENCE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 Pat Allan
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,141 @@
1
+ h1. Thinking Sphinx
2
+
3
+ h2. Usage
4
+
5
+ First, if you haven't done so already, check out the main "usage":http://ts.freelancing-gods.com/usage.html page. Once you've done that, the next place to look for information is the specific method docs - ThinkingSphinx::Search and ThinkingSphinx::Index::Builder in particular.
6
+
7
+ Keep in mind that while Thinking Sphinx works for ActiveRecord with Merb, it doesn't yet support DataMapper (although that is planned).
8
+
9
+ h2. Contributing
10
+
11
+ Fork on GitHub and after you've committed tested patches, send a pull request.
12
+
13
+ To quickly see if your system is ready to run the thinking sphinx specs, run the contribute.rb script found in the project root directory. Use the following instructions to install any missing requirements.
14
+
15
+ To get the spec suite running, you will need to install the not-a-mock gem if you don't already have it:
16
+
17
+ git clone git://github.com/freelancing-god/not-a-mock.git
18
+ cd not-a-mock
19
+ rake gem
20
+ gem install pkg/not_a_mock-1.1.0.gem
21
+
22
+ Then install the ginger gem. The steps are the same, except that you might need to sudo the gem install:
23
+
24
+ git clone git://github.com/freelancing-god/ginger.git
25
+ cd ginger
26
+ rake gem
27
+ sudo gem install pkg/ginger-1.1.0.gem
28
+
29
+ Alternatively, install the ginger gem directly from the freelancing-god github repository
30
+
31
+ sudo gem sources -a http://gems.github.com
32
+ sudo gem install freelancing-god-ginger
33
+
34
+ Then set up your database:
35
+
36
+ cp spec/fixtures/database.yml.default spec/fixtures/database.yml
37
+ mysqladmin -u root create thinking_sphinx
38
+
39
+ This last step can be done automatically by the contribute.rb script if all dependencies are met.
40
+
41
+ Make sure you don't have another Sphinx daemon (searchd) running. If you do, quit it with "rake ts:stop"
42
+ in the app root.
43
+
44
+ You should now have a passing test suite from which to build your patch on.
45
+
46
+ rake spec
47
+
48
+ If you get the message "Failed to start searchd daemon", run the spec with sudo:
49
+
50
+ sudo rake spec
51
+
52
+ If you quit the spec suite before it's completed, you may be left with data in the test
53
+ database, causing the next run to have failures. Let that run complete and then try again.
54
+
55
+ h2. Contributors
56
+
57
+ Since I first released this library, there's been quite a few people who have submitted patches, to my immense gratitude. Others have suggested syntax changes and general improvements. So my thanks to the following people:
58
+
59
+ * Joost Hietbrink
60
+ * Jonathan Conway
61
+ * Gregory Mirzayantz
62
+ * Tung Nguyen
63
+ * Sean Cribbs
64
+ * Benoit Caccinolo
65
+ * John Barton
66
+ * Oliver Beddows
67
+ * Arthur Zapparoli
68
+ * Dusty Doris
69
+ * Marcus Crafter
70
+ * Patrick Lenz
71
+ * Björn Andreasson
72
+ * James Healy
73
+ * Jae-Jun Hwang
74
+ * Xavier Shay
75
+ * Jason Rust
76
+ * Gopal Patel
77
+ * Chris Heald
78
+ * Peter Vandenberk
79
+ * Josh French
80
+ * Andrew Bennett
81
+ * Jordan Fowler
82
+ * Seth Walker
83
+ * Joe Noon
84
+ * Wolfgang Postler
85
+ * Rick Olson
86
+ * Killian Murphy
87
+ * Morten Primdahl
88
+ * Ryan Bates
89
+ * David Eisinger
90
+ * Shay Arnett
91
+ * Minh Tran
92
+ * Jeremy Durham
93
+ * Piotr Sarnacki
94
+ * Matt Johnson
95
+ * Nicolas Blanco
96
+ * Max Lapshin
97
+ * Josh Natanson
98
+ * Philip Hallstrom
99
+ * Christian Rishøj
100
+ * Mike Flester
101
+ * Jim Remsik
102
+ * Kennon Ballou
103
+ * Henrik Nyh
104
+ * Emil Tin
105
+ * Doug Cole
106
+ * Ed Hickey
107
+ * Evan Weaver
108
+ * Thibaut Barrere
109
+ * Kristopher Chambers
110
+ * Dmitrij Smalko
111
+ * Aleksey Yeschenko
112
+ * Lachie Cox
113
+ * Lourens Naude
114
+ * Tom Davies
115
+ * Dan Pickett
116
+ * Alex Caudill
117
+ * Jim Benton
118
+ * John Aughey
119
+ * Keith Pitty
120
+ * Jeff Talbot
121
+ * Dana Contreras
122
+ * Menno van der Sman
123
+ * Bill Harding
124
+ * Isaac Feliu
125
+ * Andrei Bocan
126
+ * László Bácsi
127
+ * Peter Wagenet
128
+ * Max Lapshin
129
+ * Martin Emde
130
+ * David Wennergren
131
+ * Mark Lane
132
+ * Eric Lindvall
133
+ * Lawrence Pit
134
+ * Mike Bailey
135
+ * Bill Leeper
136
+ * Michael Reinsch
137
+ * Anderson Dias
138
+ * Jerome Riga
139
+ * Tien Dung
140
+ * Johannes Kaefer
141
+ * Paul Campbell
@@ -0,0 +1,215 @@
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
+
9
+ require 'thinking_sphinx/core/string'
10
+ require 'thinking_sphinx/property'
11
+ require 'thinking_sphinx/active_record'
12
+ require 'thinking_sphinx/association'
13
+ require 'thinking_sphinx/attribute'
14
+ require 'thinking_sphinx/collection'
15
+ require 'thinking_sphinx/configuration'
16
+ require 'thinking_sphinx/facet'
17
+ require 'thinking_sphinx/class_facet'
18
+ require 'thinking_sphinx/facet_collection'
19
+ require 'thinking_sphinx/field'
20
+ require 'thinking_sphinx/index'
21
+ require 'thinking_sphinx/source'
22
+ require 'thinking_sphinx/rails_additions'
23
+ require 'thinking_sphinx/search'
24
+ require 'thinking_sphinx/deltas'
25
+
26
+ require 'thinking_sphinx/adapters/abstract_adapter'
27
+ require 'thinking_sphinx/adapters/mysql_adapter'
28
+ require 'thinking_sphinx/adapters/postgresql_adapter'
29
+
30
+ ActiveRecord::Base.send(:include, ThinkingSphinx::ActiveRecord)
31
+
32
+ Merb::Plugins.add_rakefiles(
33
+ File.join(File.dirname(__FILE__), "thinking_sphinx", "tasks")
34
+ ) if defined?(Merb)
35
+
36
+ module ThinkingSphinx
37
+ module Version #:nodoc:
38
+ Major = 1
39
+ Minor = 1
40
+ Tiny = 21
41
+
42
+ String = [Major, Minor, Tiny].join('.')
43
+ end
44
+
45
+ # A ConnectionError will get thrown when a connection to Sphinx can't be
46
+ # made.
47
+ class ConnectionError < StandardError
48
+ end
49
+
50
+ # A StaleIdsException is thrown by Collection.instances_from_matches if there
51
+ # are records in Sphinx but not in the database, so the search can be retried.
52
+ class StaleIdsException < StandardError
53
+ attr_accessor :ids
54
+ def initialize(ids)
55
+ self.ids = ids
56
+ end
57
+ end
58
+
59
+ # The collection of indexed models. Keep in mind that Rails lazily loads
60
+ # its classes, so this may not actually be populated with _all_ the models
61
+ # that have Sphinx indexes.
62
+ def self.indexed_models
63
+ @@indexed_models ||= []
64
+ end
65
+
66
+ def self.unique_id_expression(offset = nil)
67
+ "* #{ThinkingSphinx.indexed_models.size} + #{offset || 0}"
68
+ end
69
+
70
+ # Check if index definition is disabled.
71
+ #
72
+ def self.define_indexes?
73
+ @@define_indexes = true unless defined?(@@define_indexes)
74
+ @@define_indexes == true
75
+ end
76
+
77
+ # Enable/disable indexes - you may want to do this while migrating data.
78
+ #
79
+ # ThinkingSphinx.define_indexes = false
80
+ #
81
+ def self.define_indexes=(value)
82
+ @@define_indexes = value
83
+ end
84
+
85
+ @@deltas_enabled = nil
86
+
87
+ # Check if delta indexing is enabled.
88
+ #
89
+ def self.deltas_enabled?
90
+ @@deltas_enabled = (ThinkingSphinx::Configuration.environment != 'test') if @@deltas_enabled.nil?
91
+ @@deltas_enabled
92
+ end
93
+
94
+ # Enable/disable all delta indexing.
95
+ #
96
+ # ThinkingSphinx.deltas_enabled = false
97
+ #
98
+ def self.deltas_enabled=(value)
99
+ @@deltas_enabled = value
100
+ end
101
+
102
+ @@updates_enabled = nil
103
+
104
+ # Check if updates are enabled. True by default, unless within the test
105
+ # environment.
106
+ #
107
+ def self.updates_enabled?
108
+ @@updates_enabled = (ThinkingSphinx::Configuration.environment != 'test') if @@updates_enabled.nil?
109
+ @@updates_enabled
110
+ end
111
+
112
+ # Enable/disable updates to Sphinx
113
+ #
114
+ # ThinkingSphinx.updates_enabled = false
115
+ #
116
+ def self.updates_enabled=(value)
117
+ @@updates_enabled = value
118
+ end
119
+
120
+ @@suppress_delta_output = false
121
+
122
+ def self.suppress_delta_output?
123
+ @@suppress_delta_output
124
+ end
125
+
126
+ def self.suppress_delta_output=(value)
127
+ @@suppress_delta_output = value
128
+ end
129
+
130
+ # Checks to see if MySQL will allow simplistic GROUP BY statements. If not,
131
+ # or if not using MySQL, this will return false.
132
+ #
133
+ def self.use_group_by_shortcut?
134
+ !!(
135
+ mysql? && ::ActiveRecord::Base.connection.select_all(
136
+ "SELECT @@global.sql_mode, @@session.sql_mode;"
137
+ ).all? { |key,value| value.nil? || value[/ONLY_FULL_GROUP_BY/].nil? }
138
+ )
139
+ end
140
+
141
+ @@remote_sphinx = false
142
+
143
+ # An indication of whether Sphinx is running on a remote machine instead of
144
+ # the same machine.
145
+ #
146
+ def self.remote_sphinx?
147
+ @@remote_sphinx
148
+ end
149
+
150
+ # Tells Thinking Sphinx that Sphinx is running on a different machine, and
151
+ # thus it can't reliably guess whether it is running or not (ie: the
152
+ # #sphinx_running? method), and so just assumes it is.
153
+ #
154
+ # Useful for multi-machine deployments. Set it in your production.rb file.
155
+ #
156
+ # ThinkingSphinx.remote_sphinx = true
157
+ #
158
+ def self.remote_sphinx=(value)
159
+ @@remote_sphinx = value
160
+ end
161
+
162
+ # Check if Sphinx is running. If remote_sphinx is set to true (indicating
163
+ # Sphinx is on a different machine), this will always return true, and you
164
+ # will have to handle any connection errors yourself.
165
+ #
166
+ def self.sphinx_running?
167
+ remote_sphinx? || sphinx_running_by_pid?
168
+ end
169
+
170
+ # Check if Sphinx is actually running, provided the pid is on the same
171
+ # machine as this code.
172
+ #
173
+ def self.sphinx_running_by_pid?
174
+ !!sphinx_pid && pid_active?(sphinx_pid)
175
+ end
176
+
177
+ def self.sphinx_pid
178
+ pid_file = ThinkingSphinx::Configuration.instance.pid_file
179
+ cat_command = 'cat'
180
+ return nil unless File.exists?(pid_file)
181
+
182
+ if microsoft?
183
+ pid_file.gsub!('/', '\\')
184
+ cat_command = 'type'
185
+ end
186
+
187
+ `#{cat_command} #{pid_file}`[/\d+/]
188
+ end
189
+
190
+ def self.pid_active?(pid)
191
+ return true if microsoft?
192
+
193
+ begin
194
+ # In JRuby this returns -1 if the process doesn't exist
195
+ Process.getpgid(pid.to_i) != -1
196
+ rescue Exception => e
197
+ false
198
+ end
199
+ end
200
+
201
+ def self.microsoft?
202
+ RUBY_PLATFORM =~ /mswin/
203
+ end
204
+
205
+ def self.jruby?
206
+ defined?(JRUBY_VERSION)
207
+ end
208
+
209
+ def self.mysql?
210
+ ::ActiveRecord::Base.connection.class.name.demodulize == "MysqlAdapter" ||
211
+ ::ActiveRecord::Base.connection.class.name.demodulize == "MysqlplusAdapter" || (
212
+ jruby? && ::ActiveRecord::Base.connection.config[:adapter] == "jdbcmysql"
213
+ )
214
+ end
215
+ end
@@ -0,0 +1,278 @@
1
+ require 'thinking_sphinx/active_record/attribute_updates'
2
+ require 'thinking_sphinx/active_record/delta'
3
+ require 'thinking_sphinx/active_record/search'
4
+ require 'thinking_sphinx/active_record/has_many_association'
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
+ # Allows creation of indexes for Sphinx. If you don't do this, there
17
+ # isn't much point trying to search (or using this plugin at all,
18
+ # really).
19
+ #
20
+ # An example or two:
21
+ #
22
+ # define_index
23
+ # indexes :id, :as => :model_id
24
+ # indexes name
25
+ # end
26
+ #
27
+ # You can also grab fields from associations - multiple levels deep
28
+ # if necessary.
29
+ #
30
+ # define_index do
31
+ # indexes tags.name, :as => :tag
32
+ # indexes articles.content
33
+ # indexes orders.line_items.product.name, :as => :product
34
+ # end
35
+ #
36
+ # And it will automatically concatenate multiple fields:
37
+ #
38
+ # define_index do
39
+ # indexes [author.first_name, author.last_name], :as => :author
40
+ # end
41
+ #
42
+ # The #indexes method is for fields - if you want attributes, use
43
+ # #has instead. All the same rules apply - but keep in mind that
44
+ # attributes are for sorting, grouping and filtering, not searching.
45
+ #
46
+ # define_index do
47
+ # # fields ...
48
+ #
49
+ # has created_at, updated_at
50
+ # end
51
+ #
52
+ # One last feature is the delta index. This requires the model to
53
+ # have a boolean field named 'delta', and is enabled as follows:
54
+ #
55
+ # define_index do
56
+ # # fields ...
57
+ # # attributes ...
58
+ #
59
+ # set_property :delta => true
60
+ # end
61
+ #
62
+ # Check out the more detailed documentation for each of these methods
63
+ # at ThinkingSphinx::Index::Builder.
64
+ #
65
+ def define_index(&block)
66
+ return unless ThinkingSphinx.define_indexes?
67
+
68
+ self.sphinx_indexes ||= []
69
+ index = ThinkingSphinx::Index::Builder.generate(self, &block)
70
+
71
+ self.sphinx_indexes << index
72
+ unless ThinkingSphinx.indexed_models.include?(self.name)
73
+ ThinkingSphinx.indexed_models << self.name
74
+ end
75
+
76
+ if index.delta?
77
+ before_save :toggle_delta
78
+ after_commit :index_delta
79
+ end
80
+
81
+ after_destroy :toggle_deleted
82
+
83
+ include ThinkingSphinx::ActiveRecord::AttributeUpdates
84
+
85
+ index
86
+
87
+ # We want to make sure that if the database doesn't exist, then Thinking
88
+ # Sphinx doesn't mind when running non-TS tasks (like db:create, db:drop
89
+ # and db:migrate). It's a bit hacky, but I can't think of a better way.
90
+ rescue StandardError => err
91
+ case err.class.name
92
+ when "Mysql::Error", "Java::JavaSql::SQLException", "ActiveRecord::StatementInvalid"
93
+ return
94
+ else
95
+ raise err
96
+ end
97
+ end
98
+ alias_method :sphinx_index, :define_index
99
+
100
+ def sphinx_index_options
101
+ sphinx_indexes.last.options
102
+ end
103
+
104
+ # Generate a unique CRC value for the model's name, to use to
105
+ # determine which Sphinx documents belong to which AR records.
106
+ #
107
+ # Really only written for internal use - but hey, if it's useful to
108
+ # you in some other way, awesome.
109
+ #
110
+ def to_crc32
111
+ self.name.to_crc32
112
+ end
113
+
114
+ def to_crc32s
115
+ (subclasses << self).collect { |klass| klass.to_crc32 }
116
+ end
117
+
118
+ def source_of_sphinx_index
119
+ possible_models = self.sphinx_indexes.collect { |index| index.model }
120
+ return self if possible_models.include?(self)
121
+
122
+ parent = self.superclass
123
+ while !possible_models.include?(parent) && parent != ::ActiveRecord::Base
124
+ parent = parent.superclass
125
+ end
126
+
127
+ return parent
128
+ end
129
+
130
+ def to_riddle(offset)
131
+ sphinx_database_adapter.setup
132
+
133
+ indexes = [to_riddle_for_core(offset)]
134
+ indexes << to_riddle_for_delta(offset) if sphinx_delta?
135
+ indexes << to_riddle_for_distributed
136
+ end
137
+
138
+ def sphinx_database_adapter
139
+ @sphinx_database_adapter ||=
140
+ ThinkingSphinx::AbstractAdapter.detect(self)
141
+ end
142
+
143
+ private
144
+
145
+ def sphinx_name
146
+ self.name.underscore.tr(':/\\', '_')
147
+ end
148
+
149
+ def sphinx_delta?
150
+ self.sphinx_indexes.any? { |index| index.delta? }
151
+ end
152
+
153
+ def to_riddle_for_core(offset)
154
+ index = Riddle::Configuration::Index.new("#{sphinx_name}_core")
155
+ index.path = File.join(
156
+ ThinkingSphinx::Configuration.instance.searchd_file_path, index.name
157
+ )
158
+
159
+ set_configuration_options_for_indexes index
160
+ set_field_settings_for_indexes index
161
+
162
+ self.sphinx_indexes.select { |ts_index|
163
+ ts_index.model == self
164
+ }.each_with_index do |ts_index, i|
165
+ index.sources += ts_index.sources.collect { |source|
166
+ source.to_riddle_for_core(offset, i)
167
+ }
168
+ end
169
+
170
+ index
171
+ end
172
+
173
+ def to_riddle_for_delta(offset)
174
+ index = Riddle::Configuration::Index.new("#{sphinx_name}_delta")
175
+ index.parent = "#{sphinx_name}_core"
176
+ index.path = File.join(ThinkingSphinx::Configuration.instance.searchd_file_path, index.name)
177
+
178
+ self.sphinx_indexes.each_with_index do |ts_index, i|
179
+ index.sources += ts_index.sources.collect { |source|
180
+ source.to_riddle_for_delta(offset, i)
181
+ } if ts_index.delta?
182
+ end
183
+
184
+ index
185
+ end
186
+
187
+ def to_riddle_for_distributed
188
+ index = Riddle::Configuration::DistributedIndex.new(sphinx_name)
189
+ index.local_indexes << "#{sphinx_name}_core"
190
+ index.local_indexes.unshift "#{sphinx_name}_delta" if sphinx_delta?
191
+ index
192
+ end
193
+
194
+ def set_configuration_options_for_indexes(index)
195
+ ThinkingSphinx::Configuration.instance.index_options.each do |key, value|
196
+ index.send("#{key}=".to_sym, value)
197
+ end
198
+
199
+ self.sphinx_indexes.each do |ts_index|
200
+ ts_index.options.each do |key, value|
201
+ index.send("#{key}=".to_sym, value) if ThinkingSphinx::Configuration::IndexOptions.include?(key.to_s) && !value.nil?
202
+ end
203
+ end
204
+ end
205
+
206
+ def set_field_settings_for_indexes(index)
207
+ field_names = lambda { |field| field.unique_name.to_s }
208
+
209
+ self.sphinx_indexes.each do |ts_index|
210
+ index.prefix_field_names += ts_index.prefix_fields.collect(&field_names)
211
+ index.infix_field_names += ts_index.infix_fields.collect(&field_names)
212
+ end
213
+ end
214
+ end
215
+ end
216
+
217
+ base.send(:include, ThinkingSphinx::ActiveRecord::Delta)
218
+ base.send(:include, ThinkingSphinx::ActiveRecord::Search)
219
+
220
+ ::ActiveRecord::Associations::HasManyAssociation.send(
221
+ :include, ThinkingSphinx::ActiveRecord::HasManyAssociation
222
+ )
223
+ ::ActiveRecord::Associations::HasManyThroughAssociation.send(
224
+ :include, ThinkingSphinx::ActiveRecord::HasManyAssociation
225
+ )
226
+ end
227
+
228
+ def in_index?(suffix)
229
+ self.class.search_for_id self.sphinx_document_id, sphinx_index_name(suffix)
230
+ end
231
+
232
+ def in_core_index?
233
+ in_index? "core"
234
+ end
235
+
236
+ def in_delta_index?
237
+ in_index? "delta"
238
+ end
239
+
240
+ def in_both_indexes?
241
+ in_core_index? && in_delta_index?
242
+ end
243
+
244
+ def toggle_deleted
245
+ return unless ThinkingSphinx.updates_enabled? && ThinkingSphinx.sphinx_running?
246
+
247
+ config = ThinkingSphinx::Configuration.instance
248
+ client = Riddle::Client.new config.address, config.port
249
+
250
+ client.update(
251
+ "#{self.class.sphinx_indexes.first.name}_core",
252
+ ['sphinx_deleted'],
253
+ {self.sphinx_document_id => 1}
254
+ ) if self.in_core_index?
255
+
256
+ client.update(
257
+ "#{self.class.sphinx_indexes.first.name}_delta",
258
+ ['sphinx_deleted'],
259
+ {self.sphinx_document_id => 1}
260
+ ) if ThinkingSphinx.deltas_enabled? &&
261
+ self.class.sphinx_indexes.any? { |index| index.delta? } &&
262
+ self.toggled_delta?
263
+ rescue ::ThinkingSphinx::ConnectionError
264
+ # nothing
265
+ end
266
+
267
+ def sphinx_document_id
268
+ (self.id * ThinkingSphinx.indexed_models.size) +
269
+ ThinkingSphinx.indexed_models.index(self.class.source_of_sphinx_index.name)
270
+ end
271
+
272
+ private
273
+
274
+ def sphinx_index_name(suffix)
275
+ "#{self.class.source_of_sphinx_index.name.underscore.tr(':/\\', '_')}_#{suffix}"
276
+ end
277
+ end
278
+ end