thinking-sphinx-099 1.2.12
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENCE +20 -0
- data/README.textile +157 -0
- data/VERSION.yml +4 -0
- data/lib/thinking_sphinx.rb +211 -0
- data/lib/thinking_sphinx/active_record.rb +307 -0
- data/lib/thinking_sphinx/active_record/attribute_updates.rb +48 -0
- data/lib/thinking_sphinx/active_record/delta.rb +87 -0
- data/lib/thinking_sphinx/active_record/has_many_association.rb +28 -0
- data/lib/thinking_sphinx/active_record/scopes.rb +39 -0
- data/lib/thinking_sphinx/adapters/abstract_adapter.rb +42 -0
- data/lib/thinking_sphinx/adapters/mysql_adapter.rb +54 -0
- data/lib/thinking_sphinx/adapters/postgresql_adapter.rb +136 -0
- data/lib/thinking_sphinx/association.rb +164 -0
- data/lib/thinking_sphinx/attribute.rb +342 -0
- data/lib/thinking_sphinx/class_facet.rb +15 -0
- data/lib/thinking_sphinx/configuration.rb +282 -0
- data/lib/thinking_sphinx/core/array.rb +7 -0
- data/lib/thinking_sphinx/core/string.rb +15 -0
- data/lib/thinking_sphinx/deltas.rb +30 -0
- data/lib/thinking_sphinx/deltas/datetime_delta.rb +50 -0
- data/lib/thinking_sphinx/deltas/default_delta.rb +68 -0
- data/lib/thinking_sphinx/deltas/delayed_delta.rb +30 -0
- data/lib/thinking_sphinx/deltas/delayed_delta/delta_job.rb +24 -0
- data/lib/thinking_sphinx/deltas/delayed_delta/flag_as_deleted_job.rb +27 -0
- data/lib/thinking_sphinx/deltas/delayed_delta/job.rb +26 -0
- data/lib/thinking_sphinx/deploy/capistrano.rb +100 -0
- data/lib/thinking_sphinx/excerpter.rb +22 -0
- data/lib/thinking_sphinx/facet.rb +125 -0
- data/lib/thinking_sphinx/facet_search.rb +134 -0
- data/lib/thinking_sphinx/field.rb +82 -0
- data/lib/thinking_sphinx/index.rb +99 -0
- data/lib/thinking_sphinx/index/builder.rb +286 -0
- data/lib/thinking_sphinx/index/faux_column.rb +110 -0
- data/lib/thinking_sphinx/property.rb +162 -0
- data/lib/thinking_sphinx/rails_additions.rb +150 -0
- data/lib/thinking_sphinx/search.rb +707 -0
- data/lib/thinking_sphinx/search_methods.rb +421 -0
- data/lib/thinking_sphinx/source.rb +150 -0
- data/lib/thinking_sphinx/source/internal_properties.rb +46 -0
- data/lib/thinking_sphinx/source/sql.rb +128 -0
- data/lib/thinking_sphinx/tasks.rb +165 -0
- data/rails/init.rb +14 -0
- data/spec/lib/thinking_sphinx/active_record/delta_spec.rb +130 -0
- data/spec/lib/thinking_sphinx/active_record/has_many_association_spec.rb +49 -0
- data/spec/lib/thinking_sphinx/active_record/scopes_spec.rb +96 -0
- data/spec/lib/thinking_sphinx/active_record_spec.rb +364 -0
- data/spec/lib/thinking_sphinx/association_spec.rb +239 -0
- data/spec/lib/thinking_sphinx/attribute_spec.rb +500 -0
- data/spec/lib/thinking_sphinx/configuration_spec.rb +268 -0
- data/spec/lib/thinking_sphinx/core/array_spec.rb +9 -0
- data/spec/lib/thinking_sphinx/core/string_spec.rb +9 -0
- data/spec/lib/thinking_sphinx/excerpter_spec.rb +49 -0
- data/spec/lib/thinking_sphinx/facet_search_spec.rb +176 -0
- data/spec/lib/thinking_sphinx/facet_spec.rb +333 -0
- data/spec/lib/thinking_sphinx/field_spec.rb +154 -0
- data/spec/lib/thinking_sphinx/index/builder_spec.rb +455 -0
- data/spec/lib/thinking_sphinx/index/faux_column_spec.rb +30 -0
- data/spec/lib/thinking_sphinx/index_spec.rb +45 -0
- data/spec/lib/thinking_sphinx/rails_additions_spec.rb +203 -0
- data/spec/lib/thinking_sphinx/search_methods_spec.rb +152 -0
- data/spec/lib/thinking_sphinx/search_spec.rb +1092 -0
- data/spec/lib/thinking_sphinx/source_spec.rb +227 -0
- data/spec/lib/thinking_sphinx_spec.rb +162 -0
- data/tasks/distribution.rb +50 -0
- data/tasks/rails.rake +1 -0
- data/tasks/testing.rb +83 -0
- data/vendor/after_commit/LICENSE +20 -0
- data/vendor/after_commit/README +16 -0
- data/vendor/after_commit/Rakefile +22 -0
- data/vendor/after_commit/init.rb +8 -0
- data/vendor/after_commit/lib/after_commit.rb +45 -0
- data/vendor/after_commit/lib/after_commit/active_record.rb +114 -0
- data/vendor/after_commit/lib/after_commit/connection_adapters.rb +103 -0
- data/vendor/after_commit/test/after_commit_test.rb +53 -0
- data/vendor/delayed_job/lib/delayed/job.rb +251 -0
- data/vendor/delayed_job/lib/delayed/message_sending.rb +7 -0
- data/vendor/delayed_job/lib/delayed/performable_method.rb +55 -0
- data/vendor/delayed_job/lib/delayed/worker.rb +54 -0
- data/vendor/riddle/lib/riddle.rb +30 -0
- data/vendor/riddle/lib/riddle/client.rb +735 -0
- data/vendor/riddle/lib/riddle/client/filter.rb +53 -0
- data/vendor/riddle/lib/riddle/client/message.rb +70 -0
- data/vendor/riddle/lib/riddle/client/response.rb +94 -0
- data/vendor/riddle/lib/riddle/configuration.rb +33 -0
- data/vendor/riddle/lib/riddle/configuration/distributed_index.rb +49 -0
- data/vendor/riddle/lib/riddle/configuration/index.rb +146 -0
- data/vendor/riddle/lib/riddle/configuration/indexer.rb +19 -0
- data/vendor/riddle/lib/riddle/configuration/remote_index.rb +17 -0
- data/vendor/riddle/lib/riddle/configuration/searchd.rb +46 -0
- data/vendor/riddle/lib/riddle/configuration/section.rb +43 -0
- data/vendor/riddle/lib/riddle/configuration/source.rb +23 -0
- data/vendor/riddle/lib/riddle/configuration/sql_source.rb +39 -0
- data/vendor/riddle/lib/riddle/configuration/xml_source.rb +28 -0
- data/vendor/riddle/lib/riddle/controller.rb +53 -0
- metadata +172 -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.
|
data/README.textile
ADDED
@@ -0,0 +1,157 @@
|
|
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 install the cucumber, yard, jeweler and rspec gems. Make sure you have a git install version 1.6.0.0 or higher, otherwise the jeweler gem won't install.
|
35
|
+
|
36
|
+
sudo gem install cucumber yard jeweler rspec
|
37
|
+
|
38
|
+
Then set up your database:
|
39
|
+
|
40
|
+
cp spec/fixtures/database.yml.default spec/fixtures/database.yml
|
41
|
+
mysqladmin -u root create thinking_sphinx
|
42
|
+
|
43
|
+
This last step can be done automatically by the contribute.rb script if all dependencies are met.
|
44
|
+
|
45
|
+
Make sure you don't have another Sphinx daemon (searchd) running. If you do, quit it with "rake ts:stop"
|
46
|
+
in the app root.
|
47
|
+
|
48
|
+
You should now have a passing test suite from which to build your patch on.
|
49
|
+
|
50
|
+
rake spec
|
51
|
+
|
52
|
+
If you get the message "Failed to start searchd daemon", run the spec with sudo:
|
53
|
+
|
54
|
+
sudo rake spec
|
55
|
+
|
56
|
+
If you quit the spec suite before it's completed, you may be left with data in the test
|
57
|
+
database, causing the next run to have failures. Let that run complete and then try again.
|
58
|
+
|
59
|
+
h2. Contributors
|
60
|
+
|
61
|
+
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:
|
62
|
+
|
63
|
+
* Joost Hietbrink
|
64
|
+
* Jonathan Conway
|
65
|
+
* Gregory Mirzayantz
|
66
|
+
* Tung Nguyen
|
67
|
+
* Sean Cribbs
|
68
|
+
* Benoit Caccinolo
|
69
|
+
* John Barton
|
70
|
+
* Oliver Beddows
|
71
|
+
* Arthur Zapparoli
|
72
|
+
* Dusty Doris
|
73
|
+
* Marcus Crafter
|
74
|
+
* Patrick Lenz
|
75
|
+
* Björn Andreasson
|
76
|
+
* James Healy
|
77
|
+
* Jae-Jun Hwang
|
78
|
+
* Xavier Shay
|
79
|
+
* Jason Rust
|
80
|
+
* Gopal Patel
|
81
|
+
* Chris Heald
|
82
|
+
* Peter Vandenberk
|
83
|
+
* Josh French
|
84
|
+
* Andrew Bennett
|
85
|
+
* Jordan Fowler
|
86
|
+
* Seth Walker
|
87
|
+
* Joe Noon
|
88
|
+
* Wolfgang Postler
|
89
|
+
* Rick Olson
|
90
|
+
* Killian Murphy
|
91
|
+
* Morten Primdahl
|
92
|
+
* Ryan Bates
|
93
|
+
* David Eisinger
|
94
|
+
* Shay Arnett
|
95
|
+
* Minh Tran
|
96
|
+
* Jeremy Durham
|
97
|
+
* Piotr Sarnacki
|
98
|
+
* Matt Johnson
|
99
|
+
* Nicolas Blanco
|
100
|
+
* Max Lapshin
|
101
|
+
* Josh Natanson
|
102
|
+
* Philip Hallstrom
|
103
|
+
* Christian Rishøj
|
104
|
+
* Mike Flester
|
105
|
+
* Jim Remsik
|
106
|
+
* Kennon Ballou
|
107
|
+
* Henrik Nyh
|
108
|
+
* Emil Tin
|
109
|
+
* Doug Cole
|
110
|
+
* Ed Hickey
|
111
|
+
* Evan Weaver
|
112
|
+
* Thibaut Barrere
|
113
|
+
* Kristopher Chambers
|
114
|
+
* Dmitrij Smalko
|
115
|
+
* Aleksey Yeschenko
|
116
|
+
* Lachie Cox
|
117
|
+
* Lourens Naude
|
118
|
+
* Tom Davies
|
119
|
+
* Dan Pickett
|
120
|
+
* Alex Caudill
|
121
|
+
* Jim Benton
|
122
|
+
* John Aughey
|
123
|
+
* Keith Pitty
|
124
|
+
* Jeff Talbot
|
125
|
+
* Dana Contreras
|
126
|
+
* Menno van der Sman
|
127
|
+
* Bill Harding
|
128
|
+
* Isaac Feliu
|
129
|
+
* Andrei Bocan
|
130
|
+
* László Bácsi
|
131
|
+
* Peter Wagenet
|
132
|
+
* Max Lapshin
|
133
|
+
* Martin Emde
|
134
|
+
* David Wennergren
|
135
|
+
* Mark Lane
|
136
|
+
* Eric Lindvall
|
137
|
+
* Lawrence Pit
|
138
|
+
* Mike Bailey
|
139
|
+
* Bill Leeper
|
140
|
+
* Michael Reinsch
|
141
|
+
* Anderson Dias
|
142
|
+
* Jerome Riga
|
143
|
+
* Tien Dung
|
144
|
+
* Johannes Kaefer
|
145
|
+
* Paul Campbell
|
146
|
+
* Matthew Beale
|
147
|
+
* Tom Simnett
|
148
|
+
* Erik Ostrom
|
149
|
+
* Ole Riesenberg
|
150
|
+
* Josh Kalderimis
|
151
|
+
* J.D. Hollis
|
152
|
+
* Jeffrey Chupp
|
153
|
+
* Rob Anderton
|
154
|
+
* Zach Inglis
|
155
|
+
* Ward Bekker
|
156
|
+
* Brian Terlson
|
157
|
+
* Christian Aust
|
data/VERSION.yml
ADDED
@@ -0,0 +1,211 @@
|
|
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
|
+
|
10
|
+
require 'thinking_sphinx/core/array'
|
11
|
+
require 'thinking_sphinx/core/string'
|
12
|
+
require 'thinking_sphinx/property'
|
13
|
+
require 'thinking_sphinx/active_record'
|
14
|
+
require 'thinking_sphinx/association'
|
15
|
+
require 'thinking_sphinx/attribute'
|
16
|
+
require 'thinking_sphinx/configuration'
|
17
|
+
require 'thinking_sphinx/excerpter'
|
18
|
+
require 'thinking_sphinx/facet'
|
19
|
+
require 'thinking_sphinx/class_facet'
|
20
|
+
require 'thinking_sphinx/facet_search'
|
21
|
+
require 'thinking_sphinx/field'
|
22
|
+
require 'thinking_sphinx/index'
|
23
|
+
require 'thinking_sphinx/source'
|
24
|
+
require 'thinking_sphinx/rails_additions'
|
25
|
+
require 'thinking_sphinx/search'
|
26
|
+
require 'thinking_sphinx/search_methods'
|
27
|
+
require 'thinking_sphinx/deltas'
|
28
|
+
|
29
|
+
require 'thinking_sphinx/adapters/abstract_adapter'
|
30
|
+
require 'thinking_sphinx/adapters/mysql_adapter'
|
31
|
+
require 'thinking_sphinx/adapters/postgresql_adapter'
|
32
|
+
|
33
|
+
ActiveRecord::Base.send(:include, ThinkingSphinx::ActiveRecord)
|
34
|
+
|
35
|
+
Merb::Plugins.add_rakefiles(
|
36
|
+
File.join(File.dirname(__FILE__), "thinking_sphinx", "tasks")
|
37
|
+
) if defined?(Merb)
|
38
|
+
|
39
|
+
module ThinkingSphinx
|
40
|
+
# A ConnectionError will get thrown when a connection to Sphinx can't be
|
41
|
+
# made.
|
42
|
+
class ConnectionError < StandardError
|
43
|
+
end
|
44
|
+
|
45
|
+
# A StaleIdsException is thrown by Collection.instances_from_matches if there
|
46
|
+
# are records in Sphinx but not in the database, so the search can be retried.
|
47
|
+
class StaleIdsException < StandardError
|
48
|
+
attr_accessor :ids
|
49
|
+
def initialize(ids)
|
50
|
+
self.ids = ids
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# The current version of Thinking Sphinx.
|
55
|
+
#
|
56
|
+
# @return [String] The version number as a string
|
57
|
+
#
|
58
|
+
def self.version
|
59
|
+
hash = YAML.load_file File.join(File.dirname(__FILE__), '../VERSION.yml')
|
60
|
+
[hash[:major], hash[:minor], hash[:patch]].join('.')
|
61
|
+
end
|
62
|
+
|
63
|
+
# The collection of indexed models. Keep in mind that Rails lazily loads
|
64
|
+
# its classes, so this may not actually be populated with _all_ the models
|
65
|
+
# that have Sphinx indexes.
|
66
|
+
def self.indexed_models
|
67
|
+
@@indexed_models ||= []
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.unique_id_expression(offset = nil)
|
71
|
+
"* #{ThinkingSphinx.indexed_models.size} + #{offset || 0}"
|
72
|
+
end
|
73
|
+
|
74
|
+
# Check if index definition is disabled.
|
75
|
+
#
|
76
|
+
def self.define_indexes?
|
77
|
+
@@define_indexes = true unless defined?(@@define_indexes)
|
78
|
+
@@define_indexes == true
|
79
|
+
end
|
80
|
+
|
81
|
+
# Enable/disable indexes - you may want to do this while migrating data.
|
82
|
+
#
|
83
|
+
# ThinkingSphinx.define_indexes = false
|
84
|
+
#
|
85
|
+
def self.define_indexes=(value)
|
86
|
+
@@define_indexes = value
|
87
|
+
end
|
88
|
+
|
89
|
+
@@deltas_enabled = nil
|
90
|
+
|
91
|
+
# Check if delta indexing is enabled.
|
92
|
+
#
|
93
|
+
def self.deltas_enabled?
|
94
|
+
@@deltas_enabled = (ThinkingSphinx::Configuration.environment != 'test') if @@deltas_enabled.nil?
|
95
|
+
@@deltas_enabled
|
96
|
+
end
|
97
|
+
|
98
|
+
# Enable/disable all delta indexing.
|
99
|
+
#
|
100
|
+
# ThinkingSphinx.deltas_enabled = false
|
101
|
+
#
|
102
|
+
def self.deltas_enabled=(value)
|
103
|
+
@@deltas_enabled = value
|
104
|
+
end
|
105
|
+
|
106
|
+
@@updates_enabled = nil
|
107
|
+
|
108
|
+
# Check if updates are enabled. True by default, unless within the test
|
109
|
+
# environment.
|
110
|
+
#
|
111
|
+
def self.updates_enabled?
|
112
|
+
@@updates_enabled = (ThinkingSphinx::Configuration.environment != 'test') if @@updates_enabled.nil?
|
113
|
+
@@updates_enabled
|
114
|
+
end
|
115
|
+
|
116
|
+
# Enable/disable updates to Sphinx
|
117
|
+
#
|
118
|
+
# ThinkingSphinx.updates_enabled = false
|
119
|
+
#
|
120
|
+
def self.updates_enabled=(value)
|
121
|
+
@@updates_enabled = value
|
122
|
+
end
|
123
|
+
|
124
|
+
@@suppress_delta_output = false
|
125
|
+
|
126
|
+
def self.suppress_delta_output?
|
127
|
+
@@suppress_delta_output
|
128
|
+
end
|
129
|
+
|
130
|
+
def self.suppress_delta_output=(value)
|
131
|
+
@@suppress_delta_output = value
|
132
|
+
end
|
133
|
+
|
134
|
+
# Checks to see if MySQL will allow simplistic GROUP BY statements. If not,
|
135
|
+
# or if not using MySQL, this will return false.
|
136
|
+
#
|
137
|
+
def self.use_group_by_shortcut?
|
138
|
+
!!(
|
139
|
+
mysql? && ::ActiveRecord::Base.connection.select_all(
|
140
|
+
"SELECT @@global.sql_mode, @@session.sql_mode;"
|
141
|
+
).all? { |key,value| value.nil? || value[/ONLY_FULL_GROUP_BY/].nil? }
|
142
|
+
)
|
143
|
+
end
|
144
|
+
|
145
|
+
@@remote_sphinx = false
|
146
|
+
|
147
|
+
# An indication of whether Sphinx is running on a remote machine instead of
|
148
|
+
# the same machine.
|
149
|
+
#
|
150
|
+
def self.remote_sphinx?
|
151
|
+
@@remote_sphinx
|
152
|
+
end
|
153
|
+
|
154
|
+
# Tells Thinking Sphinx that Sphinx is running on a different machine, and
|
155
|
+
# thus it can't reliably guess whether it is running or not (ie: the
|
156
|
+
# #sphinx_running? method), and so just assumes it is.
|
157
|
+
#
|
158
|
+
# Useful for multi-machine deployments. Set it in your production.rb file.
|
159
|
+
#
|
160
|
+
# ThinkingSphinx.remote_sphinx = true
|
161
|
+
#
|
162
|
+
def self.remote_sphinx=(value)
|
163
|
+
@@remote_sphinx = value
|
164
|
+
end
|
165
|
+
|
166
|
+
# Check if Sphinx is running. If remote_sphinx is set to true (indicating
|
167
|
+
# Sphinx is on a different machine), this will always return true, and you
|
168
|
+
# will have to handle any connection errors yourself.
|
169
|
+
#
|
170
|
+
def self.sphinx_running?
|
171
|
+
remote_sphinx? || sphinx_running_by_pid?
|
172
|
+
end
|
173
|
+
|
174
|
+
# Check if Sphinx is actually running, provided the pid is on the same
|
175
|
+
# machine as this code.
|
176
|
+
#
|
177
|
+
def self.sphinx_running_by_pid?
|
178
|
+
!!sphinx_pid && pid_active?(sphinx_pid)
|
179
|
+
end
|
180
|
+
|
181
|
+
def self.sphinx_pid
|
182
|
+
if File.exists?(ThinkingSphinx::Configuration.instance.pid_file)
|
183
|
+
File.read(ThinkingSphinx::Configuration.instance.pid_file)[/\d+/]
|
184
|
+
else
|
185
|
+
nil
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
def self.pid_active?(pid)
|
190
|
+
!!Process.kill(0, pid.to_i)
|
191
|
+
rescue Exception => e
|
192
|
+
false
|
193
|
+
end
|
194
|
+
|
195
|
+
def self.microsoft?
|
196
|
+
RUBY_PLATFORM =~ /mswin/
|
197
|
+
end
|
198
|
+
|
199
|
+
def self.jruby?
|
200
|
+
defined?(JRUBY_VERSION)
|
201
|
+
end
|
202
|
+
|
203
|
+
def self.mysql?
|
204
|
+
::ActiveRecord::Base.connection.class.name.demodulize == "MysqlAdapter" ||
|
205
|
+
::ActiveRecord::Base.connection.class.name.demodulize == "MysqlplusAdapter" || (
|
206
|
+
jruby? && ::ActiveRecord::Base.connection.config[:adapter] == "jdbcmysql"
|
207
|
+
)
|
208
|
+
end
|
209
|
+
|
210
|
+
extend ThinkingSphinx::SearchMethods::ClassMethods
|
211
|
+
end
|
@@ -0,0 +1,307 @@
|
|
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 ThinkingSphinx.deltas_enabled? &&
|
280
|
+
self.class.sphinx_indexes.any? { |index| index.delta? } &&
|
281
|
+
self.toggled_delta?
|
282
|
+
rescue ::ThinkingSphinx::ConnectionError
|
283
|
+
# nothing
|
284
|
+
end
|
285
|
+
|
286
|
+
# Returns the unique integer id for the object. This method uses the
|
287
|
+
# attribute hash to get around ActiveRecord always mapping the #id method
|
288
|
+
# to whatever the real primary key is (which may be a unique string hash).
|
289
|
+
#
|
290
|
+
# @return [Integer] Unique record id for the purposes of Sphinx.
|
291
|
+
#
|
292
|
+
def primary_key_for_sphinx
|
293
|
+
@primary_key_for_sphinx ||= read_attribute(self.class.primary_key_for_sphinx)
|
294
|
+
end
|
295
|
+
|
296
|
+
def sphinx_document_id
|
297
|
+
primary_key_for_sphinx * ThinkingSphinx.indexed_models.size +
|
298
|
+
ThinkingSphinx.indexed_models.index(self.class.source_of_sphinx_index.name)
|
299
|
+
end
|
300
|
+
|
301
|
+
private
|
302
|
+
|
303
|
+
def sphinx_index_name(suffix)
|
304
|
+
"#{self.class.source_of_sphinx_index.name.underscore.tr(':/\\', '_')}_#{suffix}"
|
305
|
+
end
|
306
|
+
end
|
307
|
+
end
|