freelancing-god-thinking-sphinx 0.9.8 → 0.9.10
Sign up to get free protection for your applications and to get access to all the features.
- data/README +20 -1
- data/lib/thinking_sphinx.rb +30 -2
- data/lib/thinking_sphinx/active_record.rb +25 -11
- data/lib/thinking_sphinx/active_record/delta.rb +46 -53
- data/lib/thinking_sphinx/active_record/has_many_association.rb +1 -1
- data/lib/thinking_sphinx/active_record/search.rb +8 -1
- data/lib/thinking_sphinx/adapters/abstract_adapter.rb +27 -0
- data/lib/thinking_sphinx/adapters/mysql_adapter.rb +9 -0
- data/lib/thinking_sphinx/adapters/postgresql_adapter.rb +84 -0
- data/lib/thinking_sphinx/association.rb +4 -0
- data/lib/thinking_sphinx/attribute.rb +4 -2
- data/lib/thinking_sphinx/collection.rb +105 -0
- data/lib/thinking_sphinx/configuration.rb +112 -75
- data/lib/thinking_sphinx/field.rb +11 -3
- data/lib/thinking_sphinx/index.rb +119 -26
- data/lib/thinking_sphinx/index/builder.rb +30 -22
- data/lib/thinking_sphinx/index/faux_column.rb +13 -0
- data/lib/thinking_sphinx/rails_additions.rb +13 -1
- data/lib/thinking_sphinx/search.rb +40 -81
- data/spec/unit/thinking_sphinx/active_record/delta_spec.rb +73 -127
- data/spec/unit/thinking_sphinx/active_record/has_many_association_spec.rb +2 -2
- data/spec/unit/thinking_sphinx/active_record/search_spec.rb +26 -0
- data/spec/unit/thinking_sphinx/active_record_spec.rb +94 -22
- data/spec/unit/thinking_sphinx/attribute_spec.rb +8 -4
- data/spec/unit/thinking_sphinx/collection_spec.rb +71 -0
- data/spec/unit/thinking_sphinx/configuration_spec.rb +149 -113
- data/spec/unit/thinking_sphinx/field_spec.rb +13 -4
- data/spec/unit/thinking_sphinx/index/builder_spec.rb +1 -0
- data/spec/unit/thinking_sphinx/index/faux_column_spec.rb +27 -0
- data/spec/unit/thinking_sphinx/index_spec.rb +79 -29
- data/spec/unit/thinking_sphinx/search_spec.rb +114 -74
- data/spec/unit/thinking_sphinx_spec.rb +21 -0
- data/tasks/thinking_sphinx_tasks.rb +24 -10
- metadata +21 -8
- data/lib/riddle.rb +0 -26
- data/lib/riddle/client.rb +0 -639
- data/lib/riddle/client/filter.rb +0 -44
- data/lib/riddle/client/message.rb +0 -65
- data/lib/riddle/client/response.rb +0 -84
- data/lib/test.rb +0 -46
@@ -9,7 +9,8 @@ module ThinkingSphinx
|
|
9
9
|
# Enjoy.
|
10
10
|
#
|
11
11
|
class Index
|
12
|
-
attr_accessor :model, :fields, :attributes, :conditions, :
|
12
|
+
attr_accessor :model, :fields, :attributes, :conditions, :groupings,
|
13
|
+
:delta, :options
|
13
14
|
|
14
15
|
# Create a new index instance by passing in the model it is tied to, and
|
15
16
|
# a block to build it with (optional but recommended). For documentation
|
@@ -31,6 +32,7 @@ module ThinkingSphinx
|
|
31
32
|
@fields = []
|
32
33
|
@attributes = []
|
33
34
|
@conditions = []
|
35
|
+
@groupings = []
|
34
36
|
@options = {}
|
35
37
|
@delta = false
|
36
38
|
|
@@ -38,16 +40,21 @@ module ThinkingSphinx
|
|
38
40
|
end
|
39
41
|
|
40
42
|
def name
|
43
|
+
self.class.name(@model)
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.name(model)
|
41
47
|
model.name.underscore.tr(':/\\', '_')
|
42
48
|
end
|
43
49
|
|
44
50
|
def empty?(part = :core)
|
45
|
-
config = ThinkingSphinx::Configuration.
|
51
|
+
config = ThinkingSphinx::Configuration.instance
|
46
52
|
File.size?("#{config.searchd_file_path}/#{self.name}_#{part}.spa").nil?
|
47
53
|
end
|
48
54
|
|
49
|
-
def to_config(index, database_conf,
|
55
|
+
def to_config(model, index, database_conf, offset)
|
50
56
|
# Set up associations and joins
|
57
|
+
add_internal_attributes
|
51
58
|
link!
|
52
59
|
|
53
60
|
attr_sources = attributes.collect { |attrib|
|
@@ -65,33 +72,36 @@ module ThinkingSphinx
|
|
65
72
|
|
66
73
|
config = <<-SOURCE
|
67
74
|
|
68
|
-
source #{
|
75
|
+
source #{self.class.name(model)}_#{index}_core
|
69
76
|
{
|
70
77
|
type = #{db_adapter}
|
71
78
|
sql_host = #{database_conf[:host] || "localhost"}
|
72
|
-
sql_user = #{database_conf[:username]}
|
73
|
-
sql_pass = #{database_conf[:password]}
|
79
|
+
sql_user = #{database_conf[:username] || database_conf[:user]}
|
80
|
+
sql_pass = #{(database_conf[:password] || "").gsub('#', '\#')}
|
74
81
|
sql_db = #{database_conf[:database]}
|
82
|
+
#{"sql_port = #{database_conf[:port]}" unless database_conf[:port].blank? }
|
83
|
+
#{"sql_sock = #{database_conf[:socket]}" unless database_conf[:socket].blank? }
|
75
84
|
|
76
|
-
sql_query_pre = #{
|
85
|
+
sql_query_pre = #{utf8? && adapter == :mysql ? "SET NAMES utf8" : ""}
|
77
86
|
#{"sql_query_pre = SET SESSION group_concat_max_len = #{@options[:group_concat_max_len]}" if @options[:group_concat_max_len]}
|
78
87
|
sql_query_pre = #{to_sql_query_pre}
|
79
|
-
sql_query = #{to_sql.gsub(/\n/, ' ')}
|
88
|
+
sql_query = #{to_sql(:offset => offset).gsub(/\n/, ' ')}
|
80
89
|
sql_query_range = #{to_sql_query_range}
|
81
|
-
sql_query_info = #{to_sql_query_info}
|
90
|
+
sql_query_info = #{to_sql_query_info(offset)}
|
82
91
|
#{attr_sources}
|
92
|
+
#{ThinkingSphinx::Configuration.instance.hash_to_config(self.source_options)}
|
83
93
|
}
|
84
94
|
SOURCE
|
85
95
|
|
86
96
|
if delta?
|
87
97
|
config += <<-SOURCE
|
88
98
|
|
89
|
-
source #{
|
99
|
+
source #{self.class.name(model)}_#{index}_delta : #{self.class.name(model)}_#{index}_core
|
90
100
|
{
|
91
101
|
sql_query_pre =
|
92
|
-
sql_query_pre = #{
|
102
|
+
sql_query_pre = #{utf8? && adapter == :mysql ? "SET NAMES utf8" : ""}
|
93
103
|
#{"sql_query_pre = SET SESSION group_concat_max_len = #{@options[:group_concat_max_len]}" if @options[:group_concat_max_len]}
|
94
|
-
sql_query = #{to_sql(:delta => true).gsub(/\n/, ' ')}
|
104
|
+
sql_query = #{to_sql(:delta => true, :offset => offset).gsub(/\n/, ' ')}
|
95
105
|
sql_query_range = #{to_sql_query_range :delta => true}
|
96
106
|
}
|
97
107
|
SOURCE
|
@@ -149,9 +159,16 @@ sql_query_range = #{to_sql_query_range :delta => true}
|
|
149
159
|
where_clause << " AND " << @conditions.join(" AND ")
|
150
160
|
end
|
151
161
|
|
162
|
+
internal_groupings = []
|
163
|
+
if @model.column_names.include?(@model.inheritance_column)
|
164
|
+
internal_groupings << "#{@model.quoted_table_name}.#{quote_column(@model.inheritance_column)}"
|
165
|
+
end
|
166
|
+
|
167
|
+
unique_id_expr = "* #{ThinkingSphinx.indexed_models.size} + #{options[:offset] || 0}"
|
168
|
+
|
152
169
|
sql = <<-SQL
|
153
170
|
SELECT #{ (
|
154
|
-
["#{@model.quoted_table_name}.#{quote_column(@model.primary_key)}"] +
|
171
|
+
["#{@model.quoted_table_name}.#{quote_column(@model.primary_key)} #{unique_id_expr} AS #{quote_column(@model.primary_key)} "] +
|
155
172
|
@fields.collect { |field| field.to_select_sql } +
|
156
173
|
@attributes.collect { |attribute| attribute.to_select_sql }
|
157
174
|
).join(", ") }
|
@@ -163,7 +180,8 @@ WHERE #{@model.quoted_table_name}.#{quote_column(@model.primary_key)} >= $start
|
|
163
180
|
GROUP BY #{ (
|
164
181
|
["#{@model.quoted_table_name}.#{quote_column(@model.primary_key)}"] +
|
165
182
|
@fields.collect { |field| field.to_group_sql }.compact +
|
166
|
-
@attributes.collect { |attribute| attribute.to_group_sql }.compact
|
183
|
+
@attributes.collect { |attribute| attribute.to_group_sql }.compact +
|
184
|
+
@groupings + internal_groupings
|
167
185
|
).join(", ") }
|
168
186
|
SQL
|
169
187
|
|
@@ -177,9 +195,9 @@ GROUP BY #{ (
|
|
177
195
|
# Simple helper method for the query info SQL - which is a statement that
|
178
196
|
# returns the single row for a corresponding id.
|
179
197
|
#
|
180
|
-
def to_sql_query_info
|
198
|
+
def to_sql_query_info(offset)
|
181
199
|
"SELECT * FROM #{@model.quoted_table_name} WHERE " +
|
182
|
-
" #{quote_column(@model.primary_key)} = $id"
|
200
|
+
" #{quote_column(@model.primary_key)} = (($id - #{offset}) / #{ThinkingSphinx.indexed_models.size})"
|
183
201
|
end
|
184
202
|
|
185
203
|
# Simple helper method for the query range SQL - which is a statement that
|
@@ -228,6 +246,10 @@ GROUP BY #{ (
|
|
228
246
|
end
|
229
247
|
end
|
230
248
|
|
249
|
+
def adapter_object
|
250
|
+
@adapter_object ||= ThinkingSphinx::AbstractAdapter.detect(@model)
|
251
|
+
end
|
252
|
+
|
231
253
|
def prefix_fields
|
232
254
|
@fields.select { |field| field.prefixes }
|
233
255
|
end
|
@@ -236,8 +258,37 @@ GROUP BY #{ (
|
|
236
258
|
@fields.select { |field| field.infixes }
|
237
259
|
end
|
238
260
|
|
261
|
+
def local_index_options
|
262
|
+
@options.keys.inject({}) do |local_options, key|
|
263
|
+
if ThinkingSphinx::Configuration::IndexOptions.include?(key.to_s)
|
264
|
+
local_options[key.to_sym] = @options[key]
|
265
|
+
end
|
266
|
+
local_options
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
def index_options
|
271
|
+
all_index_options = ThinkingSphinx::Configuration.instance.index_options.clone
|
272
|
+
@options.keys.select { |key|
|
273
|
+
ThinkingSphinx::Configuration::IndexOptions.include?(key.to_s)
|
274
|
+
}.each { |key| all_index_options[key.to_sym] = @options[key] }
|
275
|
+
all_index_options
|
276
|
+
end
|
277
|
+
|
278
|
+
def source_options
|
279
|
+
all_source_options = ThinkingSphinx::Configuration.instance.source_options.clone
|
280
|
+
@options.keys.select { |key|
|
281
|
+
ThinkingSphinx::Configuration::SourceOptions.include?(key.to_s)
|
282
|
+
}.each { |key| all_source_options[key.to_sym] = @options[key] }
|
283
|
+
all_source_options
|
284
|
+
end
|
285
|
+
|
239
286
|
private
|
240
287
|
|
288
|
+
def utf8?
|
289
|
+
self.index_options[:charset_type] == "utf-8"
|
290
|
+
end
|
291
|
+
|
241
292
|
def quote_column(column)
|
242
293
|
@model.connection.quote_column_name(column)
|
243
294
|
end
|
@@ -263,19 +314,20 @@ GROUP BY #{ (
|
|
263
314
|
@fields = builder.fields
|
264
315
|
@attributes = builder.attributes
|
265
316
|
@conditions = builder.conditions
|
317
|
+
@groupings = builder.groupings
|
266
318
|
@delta = builder.properties[:delta]
|
267
319
|
@options = builder.properties.except(:delta)
|
268
320
|
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
321
|
+
# We want to make sure that if the database doesn't exist, then Thinking
|
322
|
+
# Sphinx doesn't mind when running non-TS tasks (like db:create, db:drop
|
323
|
+
# and db:migrate). It's a bit hacky, but I can't think of a better way.
|
324
|
+
rescue StandardError => err
|
325
|
+
case err.class.name
|
326
|
+
when "Mysql::Error", "ActiveRecord::StatementInvalid"
|
327
|
+
return
|
328
|
+
else
|
329
|
+
raise err
|
330
|
+
end
|
279
331
|
end
|
280
332
|
|
281
333
|
# Returns all associations used amongst all the fields and attributes.
|
@@ -335,5 +387,46 @@ GROUP BY #{ (
|
|
335
387
|
val ? '1' : '0'
|
336
388
|
end
|
337
389
|
end
|
390
|
+
|
391
|
+
def crc_column
|
392
|
+
if @model.column_names.include?(@model.inheritance_column)
|
393
|
+
case adapter
|
394
|
+
when :postgres
|
395
|
+
"COALESCE(crc32(#{@model.quoted_table_name}.#{quote_column(@model.inheritance_column)}), #{@model.to_crc32.to_s})"
|
396
|
+
when :mysql
|
397
|
+
"IFNULL(CRC32(#{@model.quoted_table_name}.#{quote_column(@model.inheritance_column)}), #{@model.to_crc32.to_s})"
|
398
|
+
end
|
399
|
+
else
|
400
|
+
@model.to_crc32.to_s
|
401
|
+
end
|
402
|
+
end
|
403
|
+
|
404
|
+
def add_internal_attributes
|
405
|
+
@attributes << Attribute.new(
|
406
|
+
FauxColumn.new(@model.primary_key.to_sym),
|
407
|
+
:type => :integer,
|
408
|
+
:as => :sphinx_internal_id
|
409
|
+
) unless @attributes.detect { |attr| attr.alias == :sphinx_internal_id }
|
410
|
+
|
411
|
+
@attributes << Attribute.new(
|
412
|
+
FauxColumn.new(crc_column),
|
413
|
+
:type => :integer,
|
414
|
+
:as => :class_crc
|
415
|
+
) unless @attributes.detect { |attr| attr.alias == :class_crc }
|
416
|
+
|
417
|
+
@attributes << Attribute.new(
|
418
|
+
FauxColumn.new("'" + (@model.send(:subclasses).collect { |klass|
|
419
|
+
klass.to_crc32.to_s
|
420
|
+
} << @model.to_crc32.to_s).join(",") + "'"),
|
421
|
+
:type => :multi,
|
422
|
+
:as => :subclass_crcs
|
423
|
+
) unless @attributes.detect { |attr| attr.alias == :subclass_crcs }
|
424
|
+
|
425
|
+
@attributes << Attribute.new(
|
426
|
+
FauxColumn.new("0"),
|
427
|
+
:type => :integer,
|
428
|
+
:as => :sphinx_deleted
|
429
|
+
) unless @attributes.detect { |attr| attr.alias == :sphinx_deleted }
|
430
|
+
end
|
338
431
|
end
|
339
432
|
end
|
@@ -18,9 +18,13 @@ module ThinkingSphinx
|
|
18
18
|
# rails documentation. It's not needed though, so it gets undef'd.
|
19
19
|
# Hopefully the list of methods that get in the way doesn't get too
|
20
20
|
# long.
|
21
|
-
undef_method :parent
|
21
|
+
undef_method :parent if respond_to?(:parent)
|
22
|
+
undef_method :name if respond_to?(:name)
|
23
|
+
undef_method :id if respond_to?(:id)
|
24
|
+
undef_method :type if respond_to?(:type)
|
22
25
|
|
23
|
-
attr_accessor :fields, :attributes, :properties, :conditions
|
26
|
+
attr_accessor :fields, :attributes, :properties, :conditions,
|
27
|
+
:groupings
|
24
28
|
|
25
29
|
# Set up all the collections. Consider this the equivalent of an
|
26
30
|
# instance's initialize method.
|
@@ -30,6 +34,7 @@ module ThinkingSphinx
|
|
30
34
|
@attributes = []
|
31
35
|
@properties = {}
|
32
36
|
@conditions = []
|
37
|
+
@groupings = []
|
33
38
|
end
|
34
39
|
|
35
40
|
# This is how you add fields - the strings Sphinx looks at - to your
|
@@ -81,8 +86,7 @@ module ThinkingSphinx
|
|
81
86
|
def indexes(*args)
|
82
87
|
options = args.extract_options!
|
83
88
|
args.each do |columns|
|
84
|
-
|
85
|
-
fields << Field.new(columns, options)
|
89
|
+
fields << Field.new(FauxColumn.coerce(columns), options)
|
86
90
|
|
87
91
|
if fields.last.sortable
|
88
92
|
attributes << Attribute.new(
|
@@ -137,23 +141,7 @@ module ThinkingSphinx
|
|
137
141
|
def has(*args)
|
138
142
|
options = args.extract_options!
|
139
143
|
args.each do |columns|
|
140
|
-
|
141
|
-
when Symbol, String
|
142
|
-
FauxColumn.new(columns)
|
143
|
-
when Array
|
144
|
-
columns.collect { |col|
|
145
|
-
case col
|
146
|
-
when Symbol, String
|
147
|
-
FauxColumn.new(col)
|
148
|
-
else
|
149
|
-
col
|
150
|
-
end
|
151
|
-
}
|
152
|
-
else
|
153
|
-
columns
|
154
|
-
end
|
155
|
-
|
156
|
-
attributes << Attribute.new(columns, options)
|
144
|
+
attributes << Attribute.new(FauxColumn.coerce(columns), options)
|
157
145
|
end
|
158
146
|
end
|
159
147
|
alias_method :attribute, :has
|
@@ -169,6 +157,16 @@ module ThinkingSphinx
|
|
169
157
|
@conditions += args
|
170
158
|
end
|
171
159
|
|
160
|
+
# Use this method to add some manual SQL strings to the GROUP BY
|
161
|
+
# clause. You can pass in as many strings as you'd like, they'll get
|
162
|
+
# joined together with commas later on.
|
163
|
+
#
|
164
|
+
# group_by "lat", "lng"
|
165
|
+
#
|
166
|
+
def group_by(*args)
|
167
|
+
@groupings += args
|
168
|
+
end
|
169
|
+
|
172
170
|
# This is what to use to set properties on the index. Chief amongst
|
173
171
|
# those is the delta property - to allow automatic updates to your
|
174
172
|
# indexes as new models are added and edited - but also you can
|
@@ -185,7 +183,7 @@ module ThinkingSphinx
|
|
185
183
|
# when defining the index, so you don't need to specify them for every
|
186
184
|
# geo-related search.
|
187
185
|
#
|
188
|
-
# set_property :latitude_attr => "lt", :
|
186
|
+
# set_property :latitude_attr => "lt", :longitude_attr => "lg"
|
189
187
|
#
|
190
188
|
# Please don't forget to add a boolean field named 'delta' to your
|
191
189
|
# model's database table if enabling the delta index for it.
|
@@ -206,6 +204,16 @@ module ThinkingSphinx
|
|
206
204
|
def method_missing(method, *args)
|
207
205
|
FauxColumn.new(method, *args)
|
208
206
|
end
|
207
|
+
|
208
|
+
# A method to allow adding fields from associations which have names
|
209
|
+
# that clash with method names in the Builder class (ie: properties,
|
210
|
+
# fields, attributes).
|
211
|
+
#
|
212
|
+
# Example: indexes assoc(:properties).column
|
213
|
+
#
|
214
|
+
def assoc(assoc)
|
215
|
+
FauxColumn.new(method)
|
216
|
+
end
|
209
217
|
end
|
210
218
|
end
|
211
219
|
end
|
@@ -15,6 +15,19 @@ module ThinkingSphinx
|
|
15
15
|
@stack = stack
|
16
16
|
end
|
17
17
|
|
18
|
+
def self.coerce(columns)
|
19
|
+
case columns
|
20
|
+
when Symbol, String
|
21
|
+
FauxColumn.new(columns)
|
22
|
+
when Array
|
23
|
+
columns.collect { |col| FauxColumn.coerce(col) }
|
24
|
+
when FauxColumn
|
25
|
+
columns
|
26
|
+
else
|
27
|
+
nil
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
18
31
|
# Can't use normal method name, as that could be an association or
|
19
32
|
# column name.
|
20
33
|
#
|
@@ -53,4 +53,16 @@ end
|
|
53
53
|
|
54
54
|
ActiveRecord::Base.extend(
|
55
55
|
ThinkingSphinx::ActiveRecordQuotedName
|
56
|
-
) unless ActiveRecord::Base.respond_to?("quoted_table_name")
|
56
|
+
) unless ActiveRecord::Base.respond_to?("quoted_table_name")
|
57
|
+
|
58
|
+
module ThinkingSphinx
|
59
|
+
module ActiveRecordStoreFullSTIClass
|
60
|
+
def store_full_sti_class
|
61
|
+
false
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
ActiveRecord::Base.extend(
|
67
|
+
ThinkingSphinx::ActiveRecordStoreFullSTIClass
|
68
|
+
) unless ActiveRecord::Base.respond_to?(:store_full_sti_class)
|
@@ -17,16 +17,8 @@ module ThinkingSphinx
|
|
17
17
|
|
18
18
|
options = args.extract_options!
|
19
19
|
page = options[:page] ? options[:page].to_i : 1
|
20
|
-
|
21
|
-
|
22
|
-
pager = WillPaginate::Collection.create(page,
|
23
|
-
client.limit, results[:total_found] || 0) do |collection|
|
24
|
-
collection.replace results[:matches].collect { |match| match[:doc] }
|
25
|
-
collection.instance_variable_set :@total_entries, results[:total_found]
|
26
|
-
end
|
27
|
-
rescue
|
28
|
-
results[:matches].collect { |match| match[:doc] }
|
29
|
-
end
|
20
|
+
|
21
|
+
ThinkingSphinx::Collection.ids_from_results(results, page, client.limit, options)
|
30
22
|
end
|
31
23
|
|
32
24
|
# Searches through the Sphinx indexes for relevant matches. There's
|
@@ -149,8 +141,8 @@ module ThinkingSphinx
|
|
149
141
|
# attributes. To search with that point, you can then use one of the
|
150
142
|
# following syntax examples:
|
151
143
|
#
|
152
|
-
# Address.search "Melbourne", :geo => [1.4, -2.217]
|
153
|
-
# Address.search "Australia", :geo => [-0.55, 3.108],
|
144
|
+
# Address.search "Melbourne", :geo => [1.4, -2.217], :order => "@geodist asc"
|
145
|
+
# Address.search "Australia", :geo => [-0.55, 3.108], :order => "@geodist asc"
|
154
146
|
# :latitude_attr => "latit", :longitude_attr => "longit"
|
155
147
|
#
|
156
148
|
# The first example applies when your latitude and longitude attributes
|
@@ -168,9 +160,9 @@ module ThinkingSphinx
|
|
168
160
|
#
|
169
161
|
# Now, geo-location searching really only has an affect if you have a
|
170
162
|
# filter, sort or grouping clause related to it - otherwise it's just a
|
171
|
-
# normal search
|
172
|
-
#
|
173
|
-
# clauses.
|
163
|
+
# normal search, and _will not_ return a distance value otherwise. To
|
164
|
+
# make use of the positioning difference, use the special attribute
|
165
|
+
# "@geodist" in any of your filters or sorting or grouping clauses.
|
174
166
|
#
|
175
167
|
# And don't forget - both the latitude and longitude you use in your
|
176
168
|
# search, and the values in your indexes, need to be stored as a float in radians,
|
@@ -182,6 +174,16 @@ module ThinkingSphinx
|
|
182
174
|
# # ...
|
183
175
|
# end
|
184
176
|
#
|
177
|
+
# Once you've got your results set, you can access the distances as
|
178
|
+
# follows:
|
179
|
+
#
|
180
|
+
# @results.each_with_geodist do |result, distance|
|
181
|
+
# # ...
|
182
|
+
# end
|
183
|
+
#
|
184
|
+
# The distance value is returned as a float, representing the distance in
|
185
|
+
# metres.
|
186
|
+
#
|
185
187
|
def search(*args)
|
186
188
|
results, client = search_results(*args.clone)
|
187
189
|
|
@@ -193,17 +195,14 @@ module ThinkingSphinx
|
|
193
195
|
klass = options[:class]
|
194
196
|
page = options[:page] ? options[:page].to_i : 1
|
195
197
|
|
196
|
-
|
197
|
-
pager = WillPaginate::Collection.create(page,
|
198
|
-
client.limit, results[:total] || 0) do |collection|
|
199
|
-
collection.replace instances_from_results(results[:matches], options, klass)
|
200
|
-
collection.instance_variable_set :@total_entries, results[:total_found]
|
201
|
-
end
|
202
|
-
rescue StandardError => err
|
203
|
-
instances_from_results(results[:matches], options, klass)
|
204
|
-
end
|
198
|
+
ThinkingSphinx::Collection.create_from_results(results, page, client.limit, options)
|
205
199
|
end
|
206
|
-
|
200
|
+
|
201
|
+
def count(*args)
|
202
|
+
results, client = search_results(*args.clone)
|
203
|
+
results[:total] || 0
|
204
|
+
end
|
205
|
+
|
207
206
|
# Checks if a document with the given id exists within a specific index.
|
208
207
|
# Expected parameters:
|
209
208
|
#
|
@@ -259,7 +258,7 @@ module ThinkingSphinx
|
|
259
258
|
begin
|
260
259
|
::ActiveRecord::Base.logger.debug "Sphinx: #{query}"
|
261
260
|
results = client.query query
|
262
|
-
::ActiveRecord::Base.logger.debug "Sphinx Result: #{results[:matches].collect{|m| m[:
|
261
|
+
::ActiveRecord::Base.logger.debug "Sphinx Result: #{results[:matches].collect{|m| m[:attributes]["sphinx_internal_id"]}.inspect}"
|
263
262
|
rescue Errno::ECONNREFUSED => err
|
264
263
|
raise ThinkingSphinx::ConnectionError, "Connection to Sphinx Daemon (searchd) failed."
|
265
264
|
end
|
@@ -267,53 +266,14 @@ module ThinkingSphinx
|
|
267
266
|
return results, client
|
268
267
|
end
|
269
268
|
|
270
|
-
def instances_from_results(results, options = {}, klass = nil)
|
271
|
-
if klass.nil?
|
272
|
-
results.collect { |result| instance_from_result result, options }
|
273
|
-
else
|
274
|
-
ids = results.collect { |result| result[:doc] }
|
275
|
-
instances = klass.find(
|
276
|
-
:all,
|
277
|
-
:conditions => {klass.primary_key.to_sym => ids},
|
278
|
-
:include => options[:include],
|
279
|
-
:select => options[:select]
|
280
|
-
)
|
281
|
-
ids.collect { |obj_id| instances.detect { |obj| obj.id == obj_id } }
|
282
|
-
end
|
283
|
-
end
|
284
|
-
|
285
|
-
# Either use the provided class to instantiate a result from a model, or
|
286
|
-
# get the result's CRC value and determine the class from that.
|
287
|
-
#
|
288
|
-
def instance_from_result(result, options)
|
289
|
-
class_from_crc(result[:attributes]["class_crc"]).find(
|
290
|
-
result[:doc], :include => options[:include], :select => options[:select]
|
291
|
-
)
|
292
|
-
end
|
293
|
-
|
294
|
-
# Convert a CRC value to the corresponding class.
|
295
|
-
#
|
296
|
-
def class_from_crc(crc)
|
297
|
-
unless @models_by_crc
|
298
|
-
Configuration.new.load_models
|
299
|
-
|
300
|
-
@models_by_crc = ThinkingSphinx.indexed_models.inject({}) do |hash, model|
|
301
|
-
hash[model.constantize.to_crc32] = model
|
302
|
-
hash
|
303
|
-
end
|
304
|
-
end
|
305
|
-
|
306
|
-
@models_by_crc[crc].constantize
|
307
|
-
end
|
308
|
-
|
309
269
|
# Set all the appropriate settings for the client, using the provided
|
310
270
|
# options hash.
|
311
271
|
#
|
312
272
|
def client_from_options(options = {})
|
313
|
-
config = ThinkingSphinx::Configuration.
|
273
|
+
config = ThinkingSphinx::Configuration.instance
|
314
274
|
client = Riddle::Client.new config.address, config.port
|
315
275
|
klass = options[:class]
|
316
|
-
index_options = klass ? klass.
|
276
|
+
index_options = klass ? klass.sphinx_indexes.last.options : {}
|
317
277
|
|
318
278
|
[
|
319
279
|
:max_matches, :match_mode, :sort_mode, :sort_by, :id_range,
|
@@ -327,14 +287,17 @@ module ThinkingSphinx
|
|
327
287
|
)
|
328
288
|
end
|
329
289
|
|
290
|
+
options[:classes] = [klass] if klass
|
291
|
+
|
330
292
|
client.anchor = anchor_conditions(klass, options) || {} if client.anchor.empty?
|
331
293
|
|
332
294
|
client.filters << Riddle::Client::Filter.new(
|
333
295
|
"sphinx_deleted", [0]
|
334
296
|
)
|
297
|
+
|
335
298
|
# class filters
|
336
299
|
client.filters << Riddle::Client::Filter.new(
|
337
|
-
"class_crc", options[:classes].collect { |
|
300
|
+
"class_crc", options[:classes].collect { |k| k.to_crc32s }.flatten
|
338
301
|
) if options[:classes]
|
339
302
|
|
340
303
|
# normal attribute filters
|
@@ -365,7 +328,7 @@ module ThinkingSphinx
|
|
365
328
|
# and filters.
|
366
329
|
#
|
367
330
|
def search_conditions(klass, conditions={})
|
368
|
-
attributes = klass ? klass.
|
331
|
+
attributes = klass ? klass.sphinx_indexes.collect { |index|
|
369
332
|
index.attributes.collect { |attrib| attrib.unique_name }
|
370
333
|
}.flatten : []
|
371
334
|
|
@@ -375,18 +338,13 @@ module ThinkingSphinx
|
|
375
338
|
conditions.each do |key,val|
|
376
339
|
if attributes.include?(key.to_sym)
|
377
340
|
filters << Riddle::Client::Filter.new(
|
378
|
-
key.to_s,
|
379
|
-
val.is_a?(Range) ? val : Array(val)
|
341
|
+
key.to_s, filter_value(val)
|
380
342
|
)
|
381
343
|
else
|
382
344
|
search_string << "@#{key} #{val} "
|
383
345
|
end
|
384
346
|
end
|
385
347
|
|
386
|
-
filters << Riddle::Client::Filter.new(
|
387
|
-
"class_crc", [klass.to_crc32]
|
388
|
-
) if klass
|
389
|
-
|
390
348
|
return search_string, filters
|
391
349
|
end
|
392
350
|
|
@@ -395,15 +353,15 @@ module ThinkingSphinx
|
|
395
353
|
# there's actually any values.
|
396
354
|
#
|
397
355
|
def anchor_conditions(klass, options)
|
398
|
-
attributes = klass ? klass.
|
356
|
+
attributes = klass ? klass.sphinx_indexes.collect { |index|
|
399
357
|
index.attributes.collect { |attrib| attrib.unique_name }
|
400
358
|
}.flatten : []
|
401
359
|
|
402
|
-
lat_attr = klass ? klass.
|
360
|
+
lat_attr = klass ? klass.sphinx_indexes.collect { |index|
|
403
361
|
index.options[:latitude_attr]
|
404
362
|
}.compact.first : nil
|
405
363
|
|
406
|
-
lon_attr = klass ? klass.
|
364
|
+
lon_attr = klass ? klass.sphinx_indexes.collect { |index|
|
407
365
|
index.options[:longitude_attr]
|
408
366
|
}.compact.first : nil
|
409
367
|
|
@@ -412,6 +370,7 @@ module ThinkingSphinx
|
|
412
370
|
lat_attr ||= :latitude if attributes.include?(:latitude)
|
413
371
|
|
414
372
|
lon_attr = options[:longitude_attr] if options[:longitude_attr]
|
373
|
+
lon_attr ||= :lng if attributes.include?(:lng)
|
415
374
|
lon_attr ||= :lon if attributes.include?(:lon)
|
416
375
|
lon_attr ||= :long if attributes.include?(:long)
|
417
376
|
lon_attr ||= :longitude if attributes.include?(:longitude)
|
@@ -425,9 +384,9 @@ module ThinkingSphinx
|
|
425
384
|
end
|
426
385
|
|
427
386
|
lat && lon ? {
|
428
|
-
:latitude_attribute => lat_attr,
|
387
|
+
:latitude_attribute => lat_attr.to_s,
|
429
388
|
:latitude => lat,
|
430
|
-
:longitude_attribute => lon_attr,
|
389
|
+
:longitude_attribute => lon_attr.to_s,
|
431
390
|
:longitude => lon
|
432
391
|
} : nil
|
433
392
|
end
|
@@ -437,7 +396,7 @@ module ThinkingSphinx
|
|
437
396
|
#
|
438
397
|
def set_sort_options!(client, options)
|
439
398
|
klass = options[:class]
|
440
|
-
fields = klass ? klass.
|
399
|
+
fields = klass ? klass.sphinx_indexes.collect { |index|
|
441
400
|
index.fields.collect { |field| field.unique_name }
|
442
401
|
}.flatten : []
|
443
402
|
|