thinking-sphinx 1.2.12

Sign up to get free protection for your applications and to get access to all the features.
Files changed (95) hide show
  1. data/LICENCE +20 -0
  2. data/README.textile +157 -0
  3. data/VERSION.yml +4 -0
  4. data/lib/thinking_sphinx.rb +211 -0
  5. data/lib/thinking_sphinx/active_record.rb +307 -0
  6. data/lib/thinking_sphinx/active_record/attribute_updates.rb +48 -0
  7. data/lib/thinking_sphinx/active_record/delta.rb +87 -0
  8. data/lib/thinking_sphinx/active_record/has_many_association.rb +28 -0
  9. data/lib/thinking_sphinx/active_record/scopes.rb +39 -0
  10. data/lib/thinking_sphinx/adapters/abstract_adapter.rb +42 -0
  11. data/lib/thinking_sphinx/adapters/mysql_adapter.rb +54 -0
  12. data/lib/thinking_sphinx/adapters/postgresql_adapter.rb +136 -0
  13. data/lib/thinking_sphinx/association.rb +164 -0
  14. data/lib/thinking_sphinx/attribute.rb +342 -0
  15. data/lib/thinking_sphinx/class_facet.rb +15 -0
  16. data/lib/thinking_sphinx/configuration.rb +282 -0
  17. data/lib/thinking_sphinx/core/array.rb +7 -0
  18. data/lib/thinking_sphinx/core/string.rb +15 -0
  19. data/lib/thinking_sphinx/deltas.rb +30 -0
  20. data/lib/thinking_sphinx/deltas/datetime_delta.rb +50 -0
  21. data/lib/thinking_sphinx/deltas/default_delta.rb +68 -0
  22. data/lib/thinking_sphinx/deltas/delayed_delta.rb +30 -0
  23. data/lib/thinking_sphinx/deltas/delayed_delta/delta_job.rb +24 -0
  24. data/lib/thinking_sphinx/deltas/delayed_delta/flag_as_deleted_job.rb +27 -0
  25. data/lib/thinking_sphinx/deltas/delayed_delta/job.rb +26 -0
  26. data/lib/thinking_sphinx/deploy/capistrano.rb +100 -0
  27. data/lib/thinking_sphinx/excerpter.rb +22 -0
  28. data/lib/thinking_sphinx/facet.rb +125 -0
  29. data/lib/thinking_sphinx/facet_search.rb +134 -0
  30. data/lib/thinking_sphinx/field.rb +82 -0
  31. data/lib/thinking_sphinx/index.rb +99 -0
  32. data/lib/thinking_sphinx/index/builder.rb +286 -0
  33. data/lib/thinking_sphinx/index/faux_column.rb +110 -0
  34. data/lib/thinking_sphinx/property.rb +162 -0
  35. data/lib/thinking_sphinx/rails_additions.rb +150 -0
  36. data/lib/thinking_sphinx/search.rb +707 -0
  37. data/lib/thinking_sphinx/search_methods.rb +421 -0
  38. data/lib/thinking_sphinx/source.rb +150 -0
  39. data/lib/thinking_sphinx/source/internal_properties.rb +46 -0
  40. data/lib/thinking_sphinx/source/sql.rb +128 -0
  41. data/lib/thinking_sphinx/tasks.rb +165 -0
  42. data/rails/init.rb +14 -0
  43. data/spec/lib/thinking_sphinx/active_record/delta_spec.rb +130 -0
  44. data/spec/lib/thinking_sphinx/active_record/has_many_association_spec.rb +49 -0
  45. data/spec/lib/thinking_sphinx/active_record/scopes_spec.rb +96 -0
  46. data/spec/lib/thinking_sphinx/active_record_spec.rb +364 -0
  47. data/spec/lib/thinking_sphinx/association_spec.rb +239 -0
  48. data/spec/lib/thinking_sphinx/attribute_spec.rb +500 -0
  49. data/spec/lib/thinking_sphinx/configuration_spec.rb +268 -0
  50. data/spec/lib/thinking_sphinx/core/array_spec.rb +9 -0
  51. data/spec/lib/thinking_sphinx/core/string_spec.rb +9 -0
  52. data/spec/lib/thinking_sphinx/excerpter_spec.rb +49 -0
  53. data/spec/lib/thinking_sphinx/facet_search_spec.rb +176 -0
  54. data/spec/lib/thinking_sphinx/facet_spec.rb +333 -0
  55. data/spec/lib/thinking_sphinx/field_spec.rb +154 -0
  56. data/spec/lib/thinking_sphinx/index/builder_spec.rb +455 -0
  57. data/spec/lib/thinking_sphinx/index/faux_column_spec.rb +30 -0
  58. data/spec/lib/thinking_sphinx/index_spec.rb +45 -0
  59. data/spec/lib/thinking_sphinx/rails_additions_spec.rb +203 -0
  60. data/spec/lib/thinking_sphinx/search_methods_spec.rb +152 -0
  61. data/spec/lib/thinking_sphinx/search_spec.rb +1092 -0
  62. data/spec/lib/thinking_sphinx/source_spec.rb +227 -0
  63. data/spec/lib/thinking_sphinx_spec.rb +162 -0
  64. data/tasks/distribution.rb +50 -0
  65. data/tasks/rails.rake +1 -0
  66. data/tasks/testing.rb +83 -0
  67. data/vendor/after_commit/LICENSE +20 -0
  68. data/vendor/after_commit/README +16 -0
  69. data/vendor/after_commit/Rakefile +22 -0
  70. data/vendor/after_commit/init.rb +8 -0
  71. data/vendor/after_commit/lib/after_commit.rb +45 -0
  72. data/vendor/after_commit/lib/after_commit/active_record.rb +114 -0
  73. data/vendor/after_commit/lib/after_commit/connection_adapters.rb +103 -0
  74. data/vendor/after_commit/test/after_commit_test.rb +53 -0
  75. data/vendor/delayed_job/lib/delayed/job.rb +251 -0
  76. data/vendor/delayed_job/lib/delayed/message_sending.rb +7 -0
  77. data/vendor/delayed_job/lib/delayed/performable_method.rb +55 -0
  78. data/vendor/delayed_job/lib/delayed/worker.rb +54 -0
  79. data/vendor/riddle/lib/riddle.rb +30 -0
  80. data/vendor/riddle/lib/riddle/client.rb +635 -0
  81. data/vendor/riddle/lib/riddle/client/filter.rb +53 -0
  82. data/vendor/riddle/lib/riddle/client/message.rb +66 -0
  83. data/vendor/riddle/lib/riddle/client/response.rb +84 -0
  84. data/vendor/riddle/lib/riddle/configuration.rb +33 -0
  85. data/vendor/riddle/lib/riddle/configuration/distributed_index.rb +48 -0
  86. data/vendor/riddle/lib/riddle/configuration/index.rb +142 -0
  87. data/vendor/riddle/lib/riddle/configuration/indexer.rb +19 -0
  88. data/vendor/riddle/lib/riddle/configuration/remote_index.rb +17 -0
  89. data/vendor/riddle/lib/riddle/configuration/searchd.rb +25 -0
  90. data/vendor/riddle/lib/riddle/configuration/section.rb +43 -0
  91. data/vendor/riddle/lib/riddle/configuration/source.rb +23 -0
  92. data/vendor/riddle/lib/riddle/configuration/sql_source.rb +34 -0
  93. data/vendor/riddle/lib/riddle/configuration/xml_source.rb +28 -0
  94. data/vendor/riddle/lib/riddle/controller.rb +53 -0
  95. 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,4 @@
1
+ ---
2
+ :major: 1
3
+ :minor: 2
4
+ :patch: 12
@@ -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