thinking-sphinx 2.0.7 → 2.0.8
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/HISTORY +23 -0
- data/README.textile +11 -3
- data/features/attribute_transformation.feature +4 -4
- data/features/step_definitions/common_steps.rb +6 -2
- data/features/thinking_sphinx/db/fixtures/robots.rb +7 -13
- data/lib/cucumber/thinking_sphinx/internal_world.rb +5 -0
- data/lib/cucumber/thinking_sphinx/sql_logger.rb +3 -7
- data/lib/thinking_sphinx/active_record.rb +1 -1
- data/lib/thinking_sphinx/active_record/collection_proxy_with_scopes.rb +1 -1
- data/lib/thinking_sphinx/adapters/postgresql_adapter.rb +10 -2
- data/lib/thinking_sphinx/association.rb +1 -0
- data/lib/thinking_sphinx/attribute.rb +6 -8
- data/lib/thinking_sphinx/configuration.rb +52 -23
- data/lib/thinking_sphinx/context.rb +1 -5
- data/lib/thinking_sphinx/index/builder.rb +64 -69
- data/lib/thinking_sphinx/railtie.rb +2 -5
- data/lib/thinking_sphinx/search.rb +6 -2
- data/lib/thinking_sphinx/tasks.rb +15 -5
- data/lib/thinking_sphinx/version.rb +1 -1
- data/spec/sphinx_helper.rb +2 -9
- data/spec/support/rails.rb +9 -2
- data/spec/thinking_sphinx/configuration_spec.rb +72 -34
- data/spec/thinking_sphinx/context_spec.rb +7 -5
- data/spec/thinking_sphinx_spec.rb +3 -3
- metadata +103 -98
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'blankslate'
|
2
|
+
|
1
3
|
module ThinkingSphinx
|
2
4
|
class Index
|
3
5
|
# The Builder class is the core for the index definition block processing.
|
@@ -11,36 +13,29 @@ module ThinkingSphinx
|
|
11
13
|
# your indexes. #where provides a method to add manual SQL conditions, and
|
12
14
|
# set_property allows you to set some settings on a per-index basis. Check
|
13
15
|
# out each method's documentation for better ideas of usage.
|
14
|
-
#
|
15
|
-
class Builder
|
16
|
-
instance_methods.grep(/^[^_]/).each { |method|
|
17
|
-
next if method.to_s == "instance_eval"
|
18
|
-
define_method(method) {
|
19
|
-
caller.grep(/irb.completion/).empty? ? method_missing(method) : super
|
20
|
-
}
|
21
|
-
}
|
22
|
-
|
16
|
+
#
|
17
|
+
class Builder < BlankSlate
|
23
18
|
def self.generate(model, name = nil, &block)
|
24
19
|
index = ThinkingSphinx::Index.new(model)
|
25
20
|
index.name = name unless name.nil?
|
26
|
-
|
21
|
+
|
27
22
|
Builder.new(index, &block) if block_given?
|
28
|
-
|
23
|
+
|
29
24
|
index.delta_object = ThinkingSphinx::Deltas.parse index
|
30
25
|
index
|
31
26
|
end
|
32
|
-
|
27
|
+
|
33
28
|
def initialize(index, &block)
|
34
29
|
@index = index
|
35
30
|
@explicit_source = false
|
36
|
-
|
31
|
+
|
37
32
|
self.instance_eval &block
|
38
|
-
|
33
|
+
|
39
34
|
if no_fields?
|
40
35
|
raise "At least one field is necessary for an index"
|
41
36
|
end
|
42
37
|
end
|
43
|
-
|
38
|
+
|
44
39
|
def define_source(&block)
|
45
40
|
if @explicit_source
|
46
41
|
@source = ThinkingSphinx::Source.new(@index)
|
@@ -48,10 +43,10 @@ module ThinkingSphinx
|
|
48
43
|
else
|
49
44
|
@explicit_source = true
|
50
45
|
end
|
51
|
-
|
46
|
+
|
52
47
|
self.instance_eval &block
|
53
48
|
end
|
54
|
-
|
49
|
+
|
55
50
|
# This is how you add fields - the strings Sphinx looks at - to your
|
56
51
|
# index. Technically, to use this method, you need to pass in some
|
57
52
|
# columns and options - but there's some neat method_missing stuff
|
@@ -63,26 +58,26 @@ module ThinkingSphinx
|
|
63
58
|
# field.
|
64
59
|
#
|
65
60
|
# Adding Single-Column Fields:
|
66
|
-
#
|
61
|
+
#
|
67
62
|
# You can use symbols or methods - and can chain methods together to
|
68
63
|
# get access down the associations tree.
|
69
|
-
#
|
64
|
+
#
|
70
65
|
# indexes :id, :as => :my_id
|
71
66
|
# indexes :name, :sortable => true
|
72
67
|
# indexes first_name, last_name, :sortable => true
|
73
68
|
# indexes users.posts.content, :as => :post_content
|
74
69
|
# indexes users(:id), :as => :user_ids
|
75
70
|
#
|
76
|
-
# Keep in mind that if any keywords for Ruby methods - such as id or
|
71
|
+
# Keep in mind that if any keywords for Ruby methods - such as id or
|
77
72
|
# name - clash with your column names, you need to use the symbol
|
78
73
|
# version (see the first, second and last examples above).
|
79
74
|
#
|
80
75
|
# If you specify multiple columns (example #2), a field will be created
|
81
76
|
# for each. Don't use the :as option in this case. If you want to merge
|
82
77
|
# those columns together, continue reading.
|
83
|
-
#
|
78
|
+
#
|
84
79
|
# Adding Multi-Column Fields:
|
85
|
-
#
|
80
|
+
#
|
86
81
|
# indexes [first_name, last_name], :as => :name
|
87
82
|
# indexes [location, parent.location], :as => :location
|
88
83
|
#
|
@@ -90,7 +85,7 @@ module ThinkingSphinx
|
|
90
85
|
# them in an Array, as shown by the above examples. There's no
|
91
86
|
# limitations on whether they're symbols or methods or what level of
|
92
87
|
# associations they come from.
|
93
|
-
#
|
88
|
+
#
|
94
89
|
# Adding SQL Fragment Fields
|
95
90
|
#
|
96
91
|
# You can also define a field using an SQL fragment, useful for when
|
@@ -102,37 +97,37 @@ module ThinkingSphinx
|
|
102
97
|
options = args.extract_options!
|
103
98
|
args.each do |columns|
|
104
99
|
field = Field.new(source, FauxColumn.coerce(columns), options)
|
105
|
-
|
100
|
+
|
106
101
|
add_sort_attribute field, options if field.sortable
|
107
102
|
add_facet_attribute field, options if field.faceted
|
108
103
|
end
|
109
104
|
end
|
110
|
-
|
105
|
+
|
111
106
|
# This is the method to add attributes to your index (hence why it is
|
112
107
|
# aliased as 'attribute'). The syntax is the same as #indexes, so use
|
113
108
|
# that as starting point, but keep in mind the following points.
|
114
|
-
#
|
109
|
+
#
|
115
110
|
# An attribute can have an alias (the :as option), but it is always
|
116
111
|
# sortable - so you don't need to explicitly request that. You _can_
|
117
112
|
# specify the data type of the attribute (the :type option), but the
|
118
113
|
# code's pretty good at figuring that out itself from peering into the
|
119
114
|
# database.
|
120
|
-
#
|
115
|
+
#
|
121
116
|
# Attributes are limited to the following types: integers, floats,
|
122
117
|
# datetimes (converted to timestamps), booleans, strings and MVAs
|
123
118
|
# (:multi). Don't forget that Sphinx converts string attributes to
|
124
119
|
# integers, which are useful for sorting, but that's about it.
|
125
|
-
#
|
120
|
+
#
|
126
121
|
# Collection of integers are known as multi-value attributes (MVAs).
|
127
122
|
# Generally these would be through a has_many relationship, like in this
|
128
123
|
# example:
|
129
|
-
#
|
124
|
+
#
|
130
125
|
# has posts(:id), :as => :post_ids
|
131
|
-
#
|
126
|
+
#
|
132
127
|
# This allows you to filter on any of the values tied to a specific
|
133
128
|
# record. Might be best to read through the Sphinx documentation to get
|
134
129
|
# a better idea of that though.
|
135
|
-
#
|
130
|
+
#
|
136
131
|
# Adding SQL Fragment Attributes
|
137
132
|
#
|
138
133
|
# You can also define an attribute using an SQL fragment, useful for
|
@@ -140,68 +135,68 @@ module ThinkingSphinx
|
|
140
135
|
# the type of the attribute though:
|
141
136
|
#
|
142
137
|
# has "age < 18", :as => :minor, :type => :boolean
|
143
|
-
#
|
138
|
+
#
|
144
139
|
# If you're creating attributes for latitude and longitude, don't
|
145
140
|
# forget that Sphinx expects these values to be in radians.
|
146
|
-
#
|
141
|
+
#
|
147
142
|
def has(*args)
|
148
143
|
options = args.extract_options!
|
149
144
|
args.each do |columns|
|
150
145
|
attribute = Attribute.new(source, FauxColumn.coerce(columns), options)
|
151
|
-
|
146
|
+
|
152
147
|
add_facet_attribute attribute, options if attribute.faceted
|
153
148
|
end
|
154
149
|
end
|
155
|
-
|
150
|
+
|
156
151
|
def facet(*args)
|
157
152
|
options = args.extract_options!
|
158
153
|
options[:facet] = true
|
159
|
-
|
154
|
+
|
160
155
|
args.each do |columns|
|
161
156
|
attribute = Attribute.new(source, FauxColumn.coerce(columns), options)
|
162
|
-
|
157
|
+
|
163
158
|
add_facet_attribute attribute, options
|
164
159
|
end
|
165
160
|
end
|
166
|
-
|
161
|
+
|
167
162
|
def join(*args)
|
168
163
|
args.each do |association|
|
169
164
|
Join.new(source, association)
|
170
165
|
end
|
171
166
|
end
|
172
|
-
|
167
|
+
|
173
168
|
# Use this method to add some manual SQL conditions for your index
|
174
169
|
# request. You can pass in as many strings as you like, they'll get
|
175
170
|
# joined together with ANDs later on.
|
176
|
-
#
|
171
|
+
#
|
177
172
|
# where "user_id = 10"
|
178
173
|
# where "parent_type = 'Article'", "created_at < NOW()"
|
179
|
-
#
|
174
|
+
#
|
180
175
|
def where(*args)
|
181
176
|
source.conditions += args
|
182
177
|
end
|
183
|
-
|
178
|
+
|
184
179
|
# Use this method to add some manual SQL strings to the GROUP BY
|
185
180
|
# clause. You can pass in as many strings as you'd like, they'll get
|
186
181
|
# joined together with commas later on.
|
187
|
-
#
|
182
|
+
#
|
188
183
|
# group_by "lat", "lng"
|
189
|
-
#
|
184
|
+
#
|
190
185
|
def group_by(*args)
|
191
186
|
source.groupings += args
|
192
187
|
end
|
193
|
-
|
188
|
+
|
194
189
|
# This is what to use to set properties on the index. Chief amongst
|
195
190
|
# those is the delta property - to allow automatic updates to your
|
196
191
|
# indexes as new models are added and edited - but also you can
|
197
192
|
# define search-related properties which will be the defaults for all
|
198
193
|
# searches on the model.
|
199
|
-
#
|
194
|
+
#
|
200
195
|
# set_property :delta => true
|
201
196
|
# set_property :field_weights => {"name" => 100}
|
202
197
|
# set_property :order => "name ASC"
|
203
198
|
# set_property :select => 'name'
|
204
|
-
#
|
199
|
+
#
|
205
200
|
# Also, the following two properties are particularly relevant for
|
206
201
|
# geo-location searching - latitude_attr and longitude_attr. If your
|
207
202
|
# attributes for these two values are named something other than
|
@@ -210,59 +205,59 @@ module ThinkingSphinx
|
|
210
205
|
# geo-related search.
|
211
206
|
#
|
212
207
|
# set_property :latitude_attr => "lt", :longitude_attr => "lg"
|
213
|
-
#
|
208
|
+
#
|
214
209
|
# Please don't forget to add a boolean field named 'delta' to your
|
215
210
|
# model's database table if enabling the delta index for it.
|
216
211
|
# Valid options for the delta property are:
|
217
|
-
#
|
212
|
+
#
|
218
213
|
# true
|
219
214
|
# false
|
220
215
|
# :default
|
221
216
|
# :delayed
|
222
217
|
# :datetime
|
223
|
-
#
|
224
|
-
# You can also extend ThinkingSphinx::Deltas::DefaultDelta to implement
|
218
|
+
#
|
219
|
+
# You can also extend ThinkingSphinx::Deltas::DefaultDelta to implement
|
225
220
|
# your own handling for delta indexing.
|
226
|
-
#
|
221
|
+
#
|
227
222
|
def set_property(*args)
|
228
223
|
options = args.extract_options!
|
229
224
|
options.each do |key, value|
|
230
225
|
set_single_property key, value
|
231
226
|
end
|
232
|
-
|
227
|
+
|
233
228
|
set_single_property args[0], args[1] if args.length == 2
|
234
229
|
end
|
235
230
|
alias_method :set_properties, :set_property
|
236
|
-
|
231
|
+
|
237
232
|
# Handles the generation of new columns for the field and attribute
|
238
233
|
# definitions.
|
239
|
-
#
|
234
|
+
#
|
240
235
|
def method_missing(method, *args)
|
241
236
|
FauxColumn.new(method, *args)
|
242
237
|
end
|
243
|
-
|
238
|
+
|
244
239
|
# A method to allow adding fields from associations which have names
|
245
240
|
# that clash with method names in the Builder class (ie: properties,
|
246
241
|
# fields, attributes).
|
247
|
-
#
|
242
|
+
#
|
248
243
|
# Example: indexes assoc(:properties).column
|
249
|
-
#
|
244
|
+
#
|
250
245
|
def assoc(assoc, *args)
|
251
246
|
FauxColumn.new(assoc, *args)
|
252
247
|
end
|
253
|
-
|
248
|
+
|
254
249
|
# Use this method to generate SQL for your attributes, conditions, etc.
|
255
250
|
# You can pass in as whatever ActiveRecord::Base.sanitize_sql accepts.
|
256
|
-
#
|
251
|
+
#
|
257
252
|
# where sanitize_sql(["active = ?", true])
|
258
253
|
# #=> WHERE active = 1
|
259
|
-
#
|
254
|
+
#
|
260
255
|
def sanitize_sql(*args)
|
261
256
|
@index.model.send(:sanitize_sql, *args)
|
262
257
|
end
|
263
|
-
|
258
|
+
|
264
259
|
private
|
265
|
-
|
260
|
+
|
266
261
|
def source
|
267
262
|
@source ||= begin
|
268
263
|
source = ThinkingSphinx::Source.new(@index)
|
@@ -270,7 +265,7 @@ module ThinkingSphinx
|
|
270
265
|
source
|
271
266
|
end
|
272
267
|
end
|
273
|
-
|
268
|
+
|
274
269
|
def set_single_property(key, value)
|
275
270
|
source_options = ThinkingSphinx::Configuration::SourceOptions
|
276
271
|
if source_options.include?(key.to_s)
|
@@ -279,19 +274,19 @@ module ThinkingSphinx
|
|
279
274
|
@index.local_options.merge! key => value
|
280
275
|
end
|
281
276
|
end
|
282
|
-
|
277
|
+
|
283
278
|
def add_sort_attribute(field, options)
|
284
279
|
add_internal_attribute field, options, "_sort"
|
285
280
|
end
|
286
|
-
|
281
|
+
|
287
282
|
def add_facet_attribute(property, options)
|
288
283
|
add_internal_attribute property, options, "_facet", true
|
289
284
|
@index.model.sphinx_facets << property.to_facet
|
290
285
|
end
|
291
|
-
|
286
|
+
|
292
287
|
def add_internal_attribute(property, options, suffix, crc = false)
|
293
288
|
return unless ThinkingSphinx::Facet.translate?(property)
|
294
|
-
|
289
|
+
|
295
290
|
Attribute.new(source,
|
296
291
|
property.columns.collect { |col| col.clone },
|
297
292
|
options.merge(
|
@@ -301,7 +296,7 @@ module ThinkingSphinx
|
|
301
296
|
).except(:facet)
|
302
297
|
)
|
303
298
|
end
|
304
|
-
|
299
|
+
|
305
300
|
def no_fields?
|
306
301
|
@index.sources.empty? || @index.sources.any? { |source|
|
307
302
|
source.fields.length == 0
|
@@ -3,7 +3,7 @@ require 'rails'
|
|
3
3
|
|
4
4
|
module ThinkingSphinx
|
5
5
|
class Railtie < Rails::Railtie
|
6
|
-
|
6
|
+
|
7
7
|
initializer 'thinking_sphinx.sphinx' do
|
8
8
|
ThinkingSphinx::AutoVersion.detect
|
9
9
|
end
|
@@ -26,16 +26,13 @@ module ThinkingSphinx
|
|
26
26
|
end
|
27
27
|
|
28
28
|
config.to_prepare do
|
29
|
-
I18n.backend.reload!
|
30
|
-
I18n.backend.available_locales
|
31
|
-
|
32
29
|
# ActiveRecord::Base.to_crc32s is dependant on the subclasses being loaded
|
33
30
|
# consistently. When the environment is reset, subclasses/descendants will
|
34
31
|
# be lost but our context will not reload them for us.
|
35
32
|
#
|
36
33
|
# We reset the context which causes the subclasses/descendants to be
|
37
34
|
# reloaded next time the context is called.
|
38
|
-
#
|
35
|
+
#
|
39
36
|
ThinkingSphinx.reset_context!
|
40
37
|
end
|
41
38
|
|
@@ -224,6 +224,10 @@ module ThinkingSphinx
|
|
224
224
|
def next_page?
|
225
225
|
!next_page.nil?
|
226
226
|
end
|
227
|
+
|
228
|
+
def last_page?
|
229
|
+
next_page.nil?
|
230
|
+
end
|
227
231
|
|
228
232
|
# The previous page number of the result set. If this is the first page,
|
229
233
|
# then nil is returned.
|
@@ -763,8 +767,8 @@ module ThinkingSphinx
|
|
763
767
|
filter_value(value.first).first..filter_value(value.last).first
|
764
768
|
when Array
|
765
769
|
value.collect { |v| filter_value(v) }.flatten
|
766
|
-
when Time
|
767
|
-
[value.to_i]
|
770
|
+
when Date, Time
|
771
|
+
[value.to_time.to_i]
|
768
772
|
when NilClass
|
769
773
|
0
|
770
774
|
else
|
@@ -33,12 +33,22 @@ namespace :thinking_sphinx do
|
|
33
33
|
|
34
34
|
Dir["#{config.searchd_file_path}/*.spl"].each { |file| File.delete(file) }
|
35
35
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
36
|
+
if ENV["NODETACH"] == "true"
|
37
|
+
unless pid = fork
|
38
|
+
config.controller.start(:nodetach => true)
|
39
|
+
end
|
40
|
+
Signal.trap('TERM') { Process.kill(:QUIT, pid) }
|
41
|
+
Signal.trap('INT') { Process.kill(:QUIT, pid) }
|
42
|
+
Process.wait(pid)
|
40
43
|
else
|
41
|
-
|
44
|
+
config.controller.start
|
45
|
+
|
46
|
+
if sphinx_running?
|
47
|
+
puts "Started successfully (pid #{sphinx_pid})."
|
48
|
+
else
|
49
|
+
puts "Failed to start searchd daemon. Check #{config.searchd_log_file}"
|
50
|
+
puts "Be sure to run thinking_sphinx:index before thinking_sphinx:start"
|
51
|
+
end
|
42
52
|
end
|
43
53
|
end
|
44
54
|
|
data/spec/sphinx_helper.rb
CHANGED
@@ -1,13 +1,6 @@
|
|
1
1
|
require 'active_record'
|
2
|
-
|
3
|
-
require
|
4
|
-
require "active_record/connection_adapters/mysql2_adapter"
|
5
|
-
|
6
|
-
begin
|
7
|
-
require "active_record/connection_adapters/#{prefix}postgresql_adapter"
|
8
|
-
rescue LoadError
|
9
|
-
# No postgres? no prob...
|
10
|
-
end
|
2
|
+
require 'active_record/connection_adapters/mysql2_adapter'
|
3
|
+
require 'active_record/connection_adapters/postgresql_adapter'
|
11
4
|
require 'yaml'
|
12
5
|
|
13
6
|
class SphinxHelper
|