dpickett-thinking-sphinx 1.1.4
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.
- data/LICENCE +20 -0
- data/README +107 -0
- data/lib/thinking_sphinx/active_record/delta.rb +74 -0
- data/lib/thinking_sphinx/active_record/has_many_association.rb +29 -0
- data/lib/thinking_sphinx/active_record/search.rb +57 -0
- data/lib/thinking_sphinx/active_record.rb +245 -0
- data/lib/thinking_sphinx/adapters/abstract_adapter.rb +34 -0
- data/lib/thinking_sphinx/adapters/mysql_adapter.rb +53 -0
- data/lib/thinking_sphinx/adapters/postgresql_adapter.rb +129 -0
- data/lib/thinking_sphinx/association.rb +144 -0
- data/lib/thinking_sphinx/attribute.rb +254 -0
- data/lib/thinking_sphinx/class_facet.rb +20 -0
- data/lib/thinking_sphinx/collection.rb +142 -0
- data/lib/thinking_sphinx/configuration.rb +236 -0
- data/lib/thinking_sphinx/core/string.rb +22 -0
- data/lib/thinking_sphinx/deltas/datetime_delta.rb +50 -0
- data/lib/thinking_sphinx/deltas/default_delta.rb +65 -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/deltas/delayed_delta.rb +25 -0
- data/lib/thinking_sphinx/deltas.rb +22 -0
- data/lib/thinking_sphinx/facet.rb +58 -0
- data/lib/thinking_sphinx/facet_collection.rb +45 -0
- data/lib/thinking_sphinx/field.rb +172 -0
- data/lib/thinking_sphinx/index/builder.rb +233 -0
- data/lib/thinking_sphinx/index/faux_column.rb +110 -0
- data/lib/thinking_sphinx/index.rb +432 -0
- data/lib/thinking_sphinx/rails_additions.rb +133 -0
- data/lib/thinking_sphinx/search.rb +654 -0
- data/lib/thinking_sphinx/tasks.rb +128 -0
- data/lib/thinking_sphinx.rb +145 -0
- data/spec/unit/thinking_sphinx/active_record/delta_spec.rb +136 -0
- data/spec/unit/thinking_sphinx/active_record/has_many_association_spec.rb +53 -0
- data/spec/unit/thinking_sphinx/active_record/search_spec.rb +107 -0
- data/spec/unit/thinking_sphinx/active_record_spec.rb +256 -0
- data/spec/unit/thinking_sphinx/association_spec.rb +247 -0
- data/spec/unit/thinking_sphinx/attribute_spec.rb +212 -0
- data/spec/unit/thinking_sphinx/collection_spec.rb +14 -0
- data/spec/unit/thinking_sphinx/configuration_spec.rb +136 -0
- data/spec/unit/thinking_sphinx/core/string_spec.rb +9 -0
- data/spec/unit/thinking_sphinx/field_spec.rb +145 -0
- data/spec/unit/thinking_sphinx/index/builder_spec.rb +5 -0
- data/spec/unit/thinking_sphinx/index/faux_column_spec.rb +30 -0
- data/spec/unit/thinking_sphinx/index_spec.rb +54 -0
- data/spec/unit/thinking_sphinx/search_spec.rb +59 -0
- data/spec/unit/thinking_sphinx_spec.rb +129 -0
- data/tasks/distribution.rb +48 -0
- data/tasks/rails.rake +1 -0
- data/tasks/testing.rb +86 -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 +5 -0
- data/vendor/after_commit/lib/after_commit/active_record.rb +91 -0
- data/vendor/after_commit/lib/after_commit/connection_adapters.rb +103 -0
- data/vendor/after_commit/lib/after_commit.rb +42 -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/client/filter.rb +53 -0
- data/vendor/riddle/lib/riddle/client/message.rb +65 -0
- data/vendor/riddle/lib/riddle/client/response.rb +84 -0
- data/vendor/riddle/lib/riddle/client.rb +619 -0
- data/vendor/riddle/lib/riddle/configuration/distributed_index.rb +48 -0
- data/vendor/riddle/lib/riddle/configuration/index.rb +142 -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 +25 -0
- data/vendor/riddle/lib/riddle/configuration/section.rb +37 -0
- data/vendor/riddle/lib/riddle/configuration/source.rb +23 -0
- data/vendor/riddle/lib/riddle/configuration/sql_source.rb +34 -0
- data/vendor/riddle/lib/riddle/configuration/xml_source.rb +28 -0
- data/vendor/riddle/lib/riddle/configuration.rb +33 -0
- data/vendor/riddle/lib/riddle/controller.rb +44 -0
- data/vendor/riddle/lib/riddle.rb +30 -0
- metadata +158 -0
@@ -0,0 +1,236 @@
|
|
1
|
+
require 'erb'
|
2
|
+
require 'singleton'
|
3
|
+
|
4
|
+
module ThinkingSphinx
|
5
|
+
# This class both keeps track of the configuration settings for Sphinx and
|
6
|
+
# also generates the resulting file for Sphinx to use.
|
7
|
+
#
|
8
|
+
# Here are the default settings, relative to RAILS_ROOT where relevant:
|
9
|
+
#
|
10
|
+
# config file:: config/#{environment}.sphinx.conf
|
11
|
+
# searchd log file:: log/searchd.log
|
12
|
+
# query log file:: log/searchd.query.log
|
13
|
+
# pid file:: log/searchd.#{environment}.pid
|
14
|
+
# searchd files:: db/sphinx/#{environment}/
|
15
|
+
# address:: 127.0.0.1
|
16
|
+
# port:: 3312
|
17
|
+
# allow star:: false
|
18
|
+
# min prefix length:: 1
|
19
|
+
# min infix length:: 1
|
20
|
+
# mem limit:: 64M
|
21
|
+
# max matches:: 1000
|
22
|
+
# morphology:: stem_en
|
23
|
+
# charset type:: utf-8
|
24
|
+
# charset table:: nil
|
25
|
+
# ignore chars:: nil
|
26
|
+
# html strip:: false
|
27
|
+
# html remove elements:: ''
|
28
|
+
#
|
29
|
+
# If you want to change these settings, create a YAML file at
|
30
|
+
# config/sphinx.yml with settings for each environment, in a similar
|
31
|
+
# fashion to database.yml - using the following keys: config_file,
|
32
|
+
# searchd_log_file, query_log_file, pid_file, searchd_file_path, port,
|
33
|
+
# allow_star, enable_star, min_prefix_len, min_infix_len, mem_limit,
|
34
|
+
# max_matches, # morphology, charset_type, charset_table, ignore_chars,
|
35
|
+
# html_strip, # html_remove_elements. I think you've got the idea.
|
36
|
+
#
|
37
|
+
# Each setting in the YAML file is optional - so only put in the ones you
|
38
|
+
# want to change.
|
39
|
+
#
|
40
|
+
# Keep in mind, if for some particular reason you're using a version of
|
41
|
+
# Sphinx older than 0.9.8 r871 (that's prior to the proper 0.9.8 release),
|
42
|
+
# don't set allow_star to true.
|
43
|
+
#
|
44
|
+
class Configuration
|
45
|
+
include Singleton
|
46
|
+
|
47
|
+
SourceOptions = %w( mysql_connect_flags sql_range_step sql_query_pre
|
48
|
+
sql_query_post sql_ranged_throttle sql_query_post_index )
|
49
|
+
|
50
|
+
IndexOptions = %w( charset_table charset_type docinfo enable_star
|
51
|
+
exceptions html_index_attrs html_remove_elements html_strip ignore_chars
|
52
|
+
min_infix_len min_prefix_len min_word_len mlock morphology ngram_chars
|
53
|
+
ngram_len phrase_boundary phrase_boundary_step preopen stopwords
|
54
|
+
wordforms )
|
55
|
+
|
56
|
+
attr_accessor :config_file, :searchd_log_file, :query_log_file,
|
57
|
+
:pid_file, :searchd_file_path, :address, :port, :allow_star,
|
58
|
+
:database_yml_file, :app_root, :bin_path, :model_directories
|
59
|
+
|
60
|
+
attr_accessor :source_options, :index_options
|
61
|
+
|
62
|
+
attr_reader :environment, :configuration
|
63
|
+
|
64
|
+
# Load in the configuration settings - this will look for config/sphinx.yml
|
65
|
+
# and parse it according to the current environment.
|
66
|
+
#
|
67
|
+
def initialize(app_root = Dir.pwd)
|
68
|
+
self.reset
|
69
|
+
end
|
70
|
+
|
71
|
+
def reset
|
72
|
+
self.app_root = RAILS_ROOT if defined?(RAILS_ROOT)
|
73
|
+
self.app_root = Merb.root if defined?(Merb)
|
74
|
+
self.app_root ||= app_root
|
75
|
+
|
76
|
+
@configuration = Riddle::Configuration.new
|
77
|
+
@configuration.searchd.address = "127.0.0.1"
|
78
|
+
@configuration.searchd.port = 3312
|
79
|
+
@configuration.searchd.pid_file = "#{self.app_root}/log/searchd.#{environment}.pid"
|
80
|
+
@configuration.searchd.log = "#{self.app_root}/log/searchd.log"
|
81
|
+
@configuration.searchd.query_log = "#{self.app_root}/log/searchd.query.log"
|
82
|
+
|
83
|
+
self.database_yml_file = "#{self.app_root}/config/database.yml"
|
84
|
+
self.config_file = "#{self.app_root}/config/#{environment}.sphinx.conf"
|
85
|
+
self.searchd_file_path = "#{self.app_root}/db/sphinx/#{environment}"
|
86
|
+
self.allow_star = false
|
87
|
+
self.bin_path = ""
|
88
|
+
self.model_directories = ["#{app_root}/app/models/"]
|
89
|
+
|
90
|
+
self.source_options = {}
|
91
|
+
self.index_options = {
|
92
|
+
:charset_type => "utf-8",
|
93
|
+
:morphology => "stem_en"
|
94
|
+
}
|
95
|
+
|
96
|
+
parse_config
|
97
|
+
|
98
|
+
self
|
99
|
+
end
|
100
|
+
|
101
|
+
def self.environment
|
102
|
+
@@environment ||= (
|
103
|
+
defined?(Merb) ? Merb.environment : ENV['RAILS_ENV']
|
104
|
+
) || "development"
|
105
|
+
end
|
106
|
+
|
107
|
+
def environment
|
108
|
+
self.class.environment
|
109
|
+
end
|
110
|
+
|
111
|
+
def controller
|
112
|
+
@controller ||= Riddle::Controller.new(@configuration, self.config_file)
|
113
|
+
end
|
114
|
+
|
115
|
+
# Generate the config file for Sphinx by using all the settings defined and
|
116
|
+
# looping through all the models with indexes to build the relevant
|
117
|
+
# indexer and searchd configuration, and sources and indexes details.
|
118
|
+
#
|
119
|
+
def build(file_path=nil)
|
120
|
+
load_models
|
121
|
+
file_path ||= "#{self.config_file}"
|
122
|
+
|
123
|
+
@configuration.indexes.clear
|
124
|
+
|
125
|
+
ThinkingSphinx.indexed_models.each_with_index do |model, model_index|
|
126
|
+
@configuration.indexes.concat model.constantize.to_riddle(model_index)
|
127
|
+
end
|
128
|
+
|
129
|
+
open(file_path, "w") do |file|
|
130
|
+
file.write @configuration.render
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# Make sure all models are loaded - without reloading any that
|
135
|
+
# ActiveRecord::Base is already aware of (otherwise we start to hit some
|
136
|
+
# messy dependencies issues).
|
137
|
+
#
|
138
|
+
def load_models
|
139
|
+
self.model_directories.each do |base|
|
140
|
+
Dir["#{base}**/*.rb"].each do |file|
|
141
|
+
model_name = file.gsub(/^#{base}([\w_\/\\]+)\.rb/, '\1')
|
142
|
+
|
143
|
+
next if model_name.nil?
|
144
|
+
next if ::ActiveRecord::Base.send(:subclasses).detect { |model|
|
145
|
+
model.name == model_name
|
146
|
+
}
|
147
|
+
|
148
|
+
begin
|
149
|
+
model_name.camelize.constantize
|
150
|
+
rescue LoadError
|
151
|
+
model_name.gsub!(/.*[\/\\]/, '').nil? ? next : retry
|
152
|
+
rescue NameError
|
153
|
+
next
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def address
|
160
|
+
@configuration.searchd.address
|
161
|
+
end
|
162
|
+
|
163
|
+
def address=(address)
|
164
|
+
@configuration.searchd.address = address
|
165
|
+
end
|
166
|
+
|
167
|
+
def port
|
168
|
+
@configuration.searchd.port
|
169
|
+
end
|
170
|
+
|
171
|
+
def port=(port)
|
172
|
+
@configuration.searchd.port = port
|
173
|
+
end
|
174
|
+
|
175
|
+
def pid_file
|
176
|
+
@configuration.searchd.pid_file
|
177
|
+
end
|
178
|
+
|
179
|
+
def pid_file=(pid_file)
|
180
|
+
@configuration.searchd.pid_file = pid_file
|
181
|
+
end
|
182
|
+
|
183
|
+
def searchd_log_file
|
184
|
+
@configuration.searchd.log
|
185
|
+
end
|
186
|
+
|
187
|
+
def searchd_log_file=(file)
|
188
|
+
@configuration.searchd.log = file
|
189
|
+
end
|
190
|
+
|
191
|
+
def query_log_file
|
192
|
+
@configuration.searchd.query_log
|
193
|
+
end
|
194
|
+
|
195
|
+
def query_log_file=(file)
|
196
|
+
@configuration.searchd.query_log = file
|
197
|
+
end
|
198
|
+
|
199
|
+
private
|
200
|
+
|
201
|
+
# Parse the config/sphinx.yml file - if it exists - then use the attribute
|
202
|
+
# accessors to set the appropriate values. Nothing too clever.
|
203
|
+
#
|
204
|
+
def parse_config
|
205
|
+
path = "#{app_root}/config/sphinx.yml"
|
206
|
+
return unless File.exists?(path)
|
207
|
+
|
208
|
+
conf = YAML::load(ERB.new(IO.read(path)).result)[environment]
|
209
|
+
|
210
|
+
conf.each do |key,value|
|
211
|
+
self.send("#{key}=", value) if self.methods.include?("#{key}=")
|
212
|
+
|
213
|
+
set_sphinx_setting self.source_options, key, value, SourceOptions
|
214
|
+
set_sphinx_setting self.index_options, key, value, IndexOptions
|
215
|
+
set_sphinx_setting @configuration.searchd, key, value
|
216
|
+
set_sphinx_setting @configuration.indexer, key, value
|
217
|
+
end unless conf.nil?
|
218
|
+
|
219
|
+
self.bin_path += '/' unless self.bin_path.blank?
|
220
|
+
|
221
|
+
if self.allow_star
|
222
|
+
self.index_options[:enable_star] = true
|
223
|
+
self.index_options[:min_prefix_len] = 1
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
def set_sphinx_setting(object, key, value, allowed = {})
|
228
|
+
if object.is_a?(Hash)
|
229
|
+
object[key.to_sym] = value if allowed.include?(key.to_s)
|
230
|
+
else
|
231
|
+
object.send("#{key}=", value) if object.methods.include?("#{key}")
|
232
|
+
send("#{key}=", value) if self.methods.include?("#{key}")
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module ThinkingSphinx
|
2
|
+
module Core
|
3
|
+
module String
|
4
|
+
|
5
|
+
def to_crc32
|
6
|
+
result = 0xFFFFFFFF
|
7
|
+
self.each_byte do |byte|
|
8
|
+
result ^= byte
|
9
|
+
8.times do
|
10
|
+
result = (result >> 1) ^ (0xEDB88320 * (result & 1))
|
11
|
+
end
|
12
|
+
end
|
13
|
+
result ^ 0xFFFFFFFF
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class String
|
21
|
+
include ThinkingSphinx::Core::String
|
22
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module ThinkingSphinx
|
2
|
+
module Deltas
|
3
|
+
class DatetimeDelta < ThinkingSphinx::Deltas::DefaultDelta
|
4
|
+
attr_accessor :column, :threshold
|
5
|
+
|
6
|
+
def initialize(index, options)
|
7
|
+
@index = index
|
8
|
+
@column = options.delete(:delta_column) || :updated_at
|
9
|
+
@threshold = options.delete(:threshold) || 1.day
|
10
|
+
end
|
11
|
+
|
12
|
+
def index(model, instance = nil)
|
13
|
+
# do nothing
|
14
|
+
true
|
15
|
+
end
|
16
|
+
|
17
|
+
def delayed_index(model)
|
18
|
+
config = ThinkingSphinx::Configuration.instance
|
19
|
+
rotate = ThinkingSphinx.sphinx_running? ? "--rotate" : ""
|
20
|
+
|
21
|
+
output = `#{config.bin_path}indexer --config #{config.config_file} #{rotate} #{delta_index_name model}`
|
22
|
+
output += `#{config.bin_path}indexer --config #{config.config_file} #{rotate} --merge #{core_index_name model} #{delta_index_name model} --merge-dst-range sphinx_deleted 0 0`
|
23
|
+
puts output unless ThinkingSphinx.suppress_delta_output?
|
24
|
+
|
25
|
+
true
|
26
|
+
end
|
27
|
+
|
28
|
+
def toggle(instance)
|
29
|
+
# do nothing
|
30
|
+
end
|
31
|
+
|
32
|
+
def toggled(instance)
|
33
|
+
instance.send(@column) > @threshold.ago
|
34
|
+
end
|
35
|
+
|
36
|
+
def reset_query(model)
|
37
|
+
nil
|
38
|
+
end
|
39
|
+
|
40
|
+
def clause(model, toggled)
|
41
|
+
if toggled
|
42
|
+
"#{model.quoted_table_name}.#{@index.quote_column(@column.to_s)}" +
|
43
|
+
" > #{adapter.time_difference(@threshold)}"
|
44
|
+
else
|
45
|
+
nil
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module ThinkingSphinx
|
2
|
+
module Deltas
|
3
|
+
class DefaultDelta
|
4
|
+
attr_accessor :column
|
5
|
+
|
6
|
+
def initialize(index, options)
|
7
|
+
@index = index
|
8
|
+
@column = options.delete(:delta_column) || :delta
|
9
|
+
end
|
10
|
+
|
11
|
+
def index(model, instance = nil)
|
12
|
+
return true unless ThinkingSphinx.updates_enabled? &&
|
13
|
+
ThinkingSphinx.deltas_enabled?
|
14
|
+
|
15
|
+
config = ThinkingSphinx::Configuration.instance
|
16
|
+
client = Riddle::Client.new config.address, config.port
|
17
|
+
|
18
|
+
client.update(
|
19
|
+
core_index_name(model),
|
20
|
+
['sphinx_deleted'],
|
21
|
+
{instance.sphinx_document_id => [1]}
|
22
|
+
) if instance && ThinkingSphinx.sphinx_running? && instance.in_core_index?
|
23
|
+
|
24
|
+
output = `#{config.bin_path}indexer --config #{config.config_file} --rotate #{delta_index_name model}`
|
25
|
+
puts output unless ThinkingSphinx.suppress_delta_output?
|
26
|
+
|
27
|
+
true
|
28
|
+
end
|
29
|
+
|
30
|
+
def toggle(instance)
|
31
|
+
instance.delta = true
|
32
|
+
end
|
33
|
+
|
34
|
+
def toggled(instance)
|
35
|
+
instance.delta
|
36
|
+
end
|
37
|
+
|
38
|
+
def reset_query(model)
|
39
|
+
"UPDATE #{model.quoted_table_name} SET " +
|
40
|
+
"#{@index.quote_column(@column.to_s)} = #{adapter.boolean(false)}"
|
41
|
+
end
|
42
|
+
|
43
|
+
def clause(model, toggled)
|
44
|
+
"#{model.quoted_table_name}.#{@index.quote_column(@column.to_s)}" +
|
45
|
+
" = #{adapter.boolean(toggled)}"
|
46
|
+
end
|
47
|
+
|
48
|
+
protected
|
49
|
+
|
50
|
+
def core_index_name(model)
|
51
|
+
"#{model.source_of_sphinx_index.name.underscore.tr(':/\\', '_')}_core"
|
52
|
+
end
|
53
|
+
|
54
|
+
def delta_index_name(model)
|
55
|
+
"#{model.source_of_sphinx_index.name.underscore.tr(':/\\', '_')}_delta"
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def adapter
|
61
|
+
@adapter = @index.model.sphinx_database_adapter
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module ThinkingSphinx
|
2
|
+
module Deltas
|
3
|
+
class DeltaJob
|
4
|
+
attr_accessor :index
|
5
|
+
|
6
|
+
def initialize(index)
|
7
|
+
@index = index
|
8
|
+
end
|
9
|
+
|
10
|
+
def perform
|
11
|
+
return true unless ThinkingSphinx.updates_enabled? &&
|
12
|
+
ThinkingSphinx.deltas_enabled?
|
13
|
+
|
14
|
+
config = ThinkingSphinx::Configuration.instance
|
15
|
+
client = Riddle::Client.new config.address, config.port
|
16
|
+
|
17
|
+
output = `#{config.bin_path}indexer --config #{config.config_file} --rotate #{index}`
|
18
|
+
puts output unless ThinkingSphinx.suppress_delta_output?
|
19
|
+
|
20
|
+
true
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module ThinkingSphinx
|
2
|
+
module Deltas
|
3
|
+
class FlagAsDeletedJob
|
4
|
+
attr_accessor :index, :document_id
|
5
|
+
|
6
|
+
def initialize(index, document_id)
|
7
|
+
@index, @document_id = index, document_id
|
8
|
+
end
|
9
|
+
|
10
|
+
def perform
|
11
|
+
return true unless ThinkingSphinx.updates_enabled?
|
12
|
+
|
13
|
+
config = ThinkingSphinx::Configuration.instance
|
14
|
+
client = Riddle::Client.new config.address, config.port
|
15
|
+
|
16
|
+
client.update(
|
17
|
+
@index,
|
18
|
+
['sphinx_deleted'],
|
19
|
+
{@document_id => [1]}
|
20
|
+
) if ThinkingSphinx.sphinx_running? &&
|
21
|
+
ThinkingSphinx::Search.search_for_id(@document_id, @index)
|
22
|
+
|
23
|
+
true
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module ThinkingSphinx
|
2
|
+
module Deltas
|
3
|
+
class Job < Delayed::Job
|
4
|
+
def self.enqueue(object, priority = 0)
|
5
|
+
super unless duplicates_exist(object)
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.cancel_thinking_sphinx_jobs
|
9
|
+
if connection.tables.include?("delayed_jobs")
|
10
|
+
delete_all("handler LIKE '--- !ruby/object:ThinkingSphinx::Deltas::%'")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def self.duplicates_exist(object)
|
17
|
+
count(
|
18
|
+
:conditions => {
|
19
|
+
:handler => object.to_yaml,
|
20
|
+
:locked_at => nil
|
21
|
+
}
|
22
|
+
) > 0
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'delayed/job'
|
2
|
+
|
3
|
+
require 'thinking_sphinx/deltas/delayed_delta/delta_job'
|
4
|
+
require 'thinking_sphinx/deltas/delayed_delta/flag_as_deleted_job'
|
5
|
+
require 'thinking_sphinx/deltas/delayed_delta/job'
|
6
|
+
|
7
|
+
module ThinkingSphinx
|
8
|
+
module Deltas
|
9
|
+
class DelayedDelta < ThinkingSphinx::Deltas::DefaultDelta
|
10
|
+
def index(model, instance = nil)
|
11
|
+
ThinkingSphinx::Deltas::Job.enqueue(
|
12
|
+
ThinkingSphinx::Deltas::DeltaJob.new(delta_index_name(model))
|
13
|
+
)
|
14
|
+
|
15
|
+
Delayed::Job.enqueue(
|
16
|
+
ThinkingSphinx::Deltas::FlagAsDeletedJob.new(
|
17
|
+
core_index_name(model), instance.sphinx_document_id
|
18
|
+
)
|
19
|
+
) if instance
|
20
|
+
|
21
|
+
true
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'thinking_sphinx/deltas/default_delta'
|
2
|
+
require 'thinking_sphinx/deltas/delayed_delta'
|
3
|
+
require 'thinking_sphinx/deltas/datetime_delta'
|
4
|
+
|
5
|
+
module ThinkingSphinx
|
6
|
+
module Deltas
|
7
|
+
def self.parse(index, options)
|
8
|
+
case options.delete(:delta)
|
9
|
+
when TrueClass, :default
|
10
|
+
DefaultDelta.new index, options
|
11
|
+
when :delayed
|
12
|
+
DelayedDelta.new index, options
|
13
|
+
when :datetime
|
14
|
+
DatetimeDelta.new index, options
|
15
|
+
when FalseClass, nil
|
16
|
+
nil
|
17
|
+
else
|
18
|
+
raise "Unknown delta type"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module ThinkingSphinx
|
2
|
+
class Facet
|
3
|
+
attr_reader :reference
|
4
|
+
|
5
|
+
def initialize(reference)
|
6
|
+
@reference = reference
|
7
|
+
|
8
|
+
if reference.columns.length != 1
|
9
|
+
raise "Can't translate Facets on multiple-column field or attribute"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def name
|
14
|
+
reference.unique_name
|
15
|
+
end
|
16
|
+
|
17
|
+
def attribute_name
|
18
|
+
@attribute_name ||= case @reference
|
19
|
+
when Attribute
|
20
|
+
@reference.unique_name.to_s
|
21
|
+
when Field
|
22
|
+
@reference.unique_name.to_s + "_sort"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def value(object, attribute_value)
|
27
|
+
return translate(object, attribute_value) if @reference.is_a?(Field)
|
28
|
+
|
29
|
+
case @reference.type
|
30
|
+
when :string, :multi
|
31
|
+
translate(object, attribute_value)
|
32
|
+
when :datetime
|
33
|
+
Time.at(attribute_value)
|
34
|
+
when :boolean
|
35
|
+
attribute_value > 0
|
36
|
+
else
|
37
|
+
attribute_value
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def to_s
|
42
|
+
name
|
43
|
+
end
|
44
|
+
|
45
|
+
protected
|
46
|
+
|
47
|
+
def translate(object, attribute_value)
|
48
|
+
column.__stack.each { |method|
|
49
|
+
object = object.send(method)
|
50
|
+
}
|
51
|
+
object.send(column.__name)
|
52
|
+
end
|
53
|
+
|
54
|
+
def column
|
55
|
+
@reference.columns.first
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module ThinkingSphinx
|
2
|
+
class FacetCollection < Hash
|
3
|
+
attr_accessor :arguments
|
4
|
+
|
5
|
+
def initialize(arguments)
|
6
|
+
@arguments = arguments.clone
|
7
|
+
@attribute_values = {}
|
8
|
+
@facets = []
|
9
|
+
end
|
10
|
+
|
11
|
+
def add_from_results(facet, results)
|
12
|
+
self[facet.name] ||= {}
|
13
|
+
@attribute_values[facet.name] = {}
|
14
|
+
@facets << facet
|
15
|
+
|
16
|
+
results.each_with_groupby_and_count { |result, group, count|
|
17
|
+
facet_value = facet.value(result, group)
|
18
|
+
|
19
|
+
self[facet.name][facet_value] ||= 0
|
20
|
+
self[facet.name][facet_value] += count
|
21
|
+
@attribute_values[facet.name][facet_value] ||= group
|
22
|
+
}
|
23
|
+
end
|
24
|
+
|
25
|
+
def for(hash = {})
|
26
|
+
arguments = @arguments.clone
|
27
|
+
options = arguments.extract_options!
|
28
|
+
options[:with] ||= {}
|
29
|
+
|
30
|
+
hash.each do |key, value|
|
31
|
+
attrib = facet_for_key(key).attribute_name
|
32
|
+
options[:with][attrib] = @attribute_values[key][value]
|
33
|
+
end
|
34
|
+
|
35
|
+
arguments << options
|
36
|
+
ThinkingSphinx::Search.search *arguments
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def facet_for_key(key)
|
42
|
+
@facets.detect { |facet| facet.name == key }
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|