Sphincter 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data.tar.gz.sig +0 -0
- data/History.txt +11 -1
- data/README.txt +35 -14
- data/Rakefile +2 -2
- data/lib/sphincter.rb +6 -1
- data/lib/sphincter/configure.rb +172 -53
- data/lib/sphincter/search.rb +27 -4
- data/lib/sphincter/tasks.rb +51 -1
- data/test/sphincter_test_case.rb +43 -13
- data/test/test_sphincter_association_searcher.rb +3 -3
- data/test/test_sphincter_configure.rb +114 -46
- data/test/test_sphincter_search.rb +5 -5
- metadata +25 -3
- metadata.gz.sig +3 -0
data.tar.gz.sig
ADDED
Binary file
|
data/History.txt
CHANGED
@@ -1,5 +1,15 @@
|
|
1
|
+
== 1.1.0 / 2007-08-13
|
2
|
+
|
3
|
+
* 2 major enhancements:
|
4
|
+
* Fields across relationships may be included via add_index.
|
5
|
+
* Sphincter now automatically configures Dmytro Shteflyuk's sphinx API. Run
|
6
|
+
`rake sphincter:setup_sphinx` and check in vendor/plugins/sphinx.
|
7
|
+
* 1 bug fix:
|
8
|
+
* `rake sphincter:index` task didn't correctly run reindex. Bug submitted
|
9
|
+
by Lee O'Mara.
|
10
|
+
|
1
11
|
== 1.0.0 / 2007-07-26
|
2
12
|
|
3
|
-
* 1 major enhancement
|
13
|
+
* 1 major enhancement:
|
4
14
|
* Birthday!
|
5
15
|
|
data/README.txt
CHANGED
@@ -4,6 +4,10 @@ Eric Hodel <drbrain@segment7.net>
|
|
4
4
|
|
5
5
|
http://seattlerb.org/Sphincter
|
6
6
|
|
7
|
+
File bugs:
|
8
|
+
|
9
|
+
http://rubyforge.org/tracker/?func=add&group_id=1513&atid=5921
|
10
|
+
|
7
11
|
Sphincter was named by David Yeu.
|
8
12
|
|
9
13
|
== DESCRIPTION:
|
@@ -41,22 +45,24 @@ For complete documentation:
|
|
41
45
|
|
42
46
|
Download and install Sphinx from http://www.sphinxsearch.com/downloads.html
|
43
47
|
|
44
|
-
Download Sphinx Ruby API from http://rubyforge.org/frs/?group_id=2604&release_id=11049
|
45
|
-
|
46
|
-
Unpack Sphinx Ruby API into vendor/plugins/.
|
47
|
-
|
48
48
|
Install Sphincter:
|
49
49
|
|
50
50
|
$ gem install Sphincter
|
51
51
|
|
52
|
+
Load Sphincter tasks in Rakefile:
|
53
|
+
|
54
|
+
require 'sphincter/tasks'
|
55
|
+
|
56
|
+
Setup the Dmytro Shteflyuk's Sphinx client:
|
57
|
+
|
58
|
+
$ rake sphincter:setup_sphinx
|
59
|
+
|
60
|
+
Add vendor/plugins/sphinx to your SCM system.
|
61
|
+
|
52
62
|
Load Sphincter in config/environment.rb:
|
53
63
|
|
54
64
|
require 'sphincter'
|
55
65
|
|
56
|
-
By default, Sphincter will run searchd on the same port for all
|
57
|
-
environments. See Sphincter::Configure for how to configure different
|
58
|
-
environments to use different ports.
|
59
|
-
|
60
66
|
Add indexes to models:
|
61
67
|
|
62
68
|
class Post < ActiveRecord::Base
|
@@ -74,12 +80,25 @@ Add searching UI:
|
|
74
80
|
end
|
75
81
|
end
|
76
82
|
|
83
|
+
<ol>
|
84
|
+
<% @results.records.each do |post| -%>
|
85
|
+
<li>
|
86
|
+
<div><%= link_to post.title, post_path(post) %></div>
|
87
|
+
<div><%= truncate post.body, 250 %></div>
|
88
|
+
</li>
|
89
|
+
<% end -%>
|
90
|
+
</ol>
|
91
|
+
|
77
92
|
Start searchd:
|
78
93
|
|
79
94
|
$ rake sphincter:start_searchd
|
80
95
|
|
81
96
|
Then test it out in your browser.
|
82
97
|
|
98
|
+
NOTE: By default, Sphincter will run searchd on the same port for all
|
99
|
+
environments. See Sphincter::Configure for how to configure different
|
100
|
+
environments to use different ports.
|
101
|
+
|
83
102
|
== TESTING QUICK-START:
|
84
103
|
|
85
104
|
See Sphinx::SearchStub.
|
@@ -92,33 +111,35 @@ Example ActiveRecord model:
|
|
92
111
|
|
93
112
|
class Post < ActiveRecord::Base
|
94
113
|
belongs_to :blog
|
114
|
+
belongs_to :user
|
95
115
|
|
96
116
|
# published is a boolean and title and body are string or text fields
|
117
|
+
# user.name is automatically fetched via the user association
|
97
118
|
add_index :fields => %w[title body published]
|
98
119
|
end
|
99
|
-
|
120
|
+
|
100
121
|
Simple search:
|
101
122
|
|
102
123
|
Post.search 'words'
|
103
|
-
|
124
|
+
|
104
125
|
Only search published posts:
|
105
126
|
|
106
127
|
Post.search 'words', :conditions => { :published => 1 }
|
107
|
-
|
128
|
+
|
108
129
|
Only search posts created in the last week:
|
109
130
|
|
110
131
|
now = Time.now
|
111
132
|
ago = now - 1.weeks
|
112
133
|
Post.search 'words', :between => { :created_on => [ago, now] }
|
113
|
-
|
134
|
+
|
114
135
|
Pagination (defaults to ten records/page):
|
115
136
|
|
116
137
|
Post.search 'words', :page => 2
|
117
|
-
|
138
|
+
|
118
139
|
Pagination with custom page size:
|
119
140
|
|
120
141
|
Post.search 'words', :page => 2, :per_page => 20
|
121
|
-
|
142
|
+
|
122
143
|
Pagination with custom page size (better):
|
123
144
|
|
124
145
|
Add to config/sphincter.yml:
|
data/Rakefile
CHANGED
@@ -9,8 +9,8 @@ Hoe.new('Sphincter', Sphincter::VERSION) do |p|
|
|
9
9
|
p.rubyforge_name = 'seattlerb'
|
10
10
|
p.author = 'Eric Hodel'
|
11
11
|
p.email = 'drbrain@segment7.net'
|
12
|
-
p.summary = p.paragraphs_of('README.txt',
|
13
|
-
p.description = p.paragraphs_of('README.txt',
|
12
|
+
p.summary = p.paragraphs_of('README.txt', 7).first
|
13
|
+
p.description = p.paragraphs_of('README.txt', 8).first
|
14
14
|
p.url = p.paragraphs_of('README.txt', 2).first
|
15
15
|
p.changes = p.paragraphs_of('History.txt', 0..1).join("\n\n")
|
16
16
|
|
data/lib/sphincter.rb
CHANGED
data/lib/sphincter/configure.rb
CHANGED
@@ -50,6 +50,175 @@ require 'sphincter'
|
|
50
50
|
|
51
51
|
module Sphincter::Configure
|
52
52
|
|
53
|
+
##
|
54
|
+
# A class for building sphinx.conf source/index sections.
|
55
|
+
|
56
|
+
class Index
|
57
|
+
|
58
|
+
attr_reader :source_conf
|
59
|
+
|
60
|
+
attr_reader :name
|
61
|
+
|
62
|
+
##
|
63
|
+
# Creates a new Index for +klass+ and +options+.
|
64
|
+
|
65
|
+
def initialize(klass, options)
|
66
|
+
@fields = []
|
67
|
+
@where = []
|
68
|
+
@group = false
|
69
|
+
|
70
|
+
@source_conf = {}
|
71
|
+
@source_conf['sql_date_column'] = []
|
72
|
+
@source_conf['sql_group_column'] = %w[sphincter_index_id]
|
73
|
+
|
74
|
+
@klass = klass
|
75
|
+
@table = @klass.table_name
|
76
|
+
@conn = @klass.connection
|
77
|
+
@tables = @table.dup
|
78
|
+
|
79
|
+
defaults = {
|
80
|
+
:conditions => [],
|
81
|
+
:fields => [],
|
82
|
+
:name => @table,
|
83
|
+
}
|
84
|
+
|
85
|
+
@options = defaults.merge options
|
86
|
+
|
87
|
+
@name = @options[:name] || @table
|
88
|
+
end
|
89
|
+
|
90
|
+
##
|
91
|
+
# Adds plain field +field+ to the index from class +klass+ using
|
92
|
+
# +as_table+ as the table name.
|
93
|
+
|
94
|
+
def add_field(field, klass = @klass, as_table = nil)
|
95
|
+
table = klass.table_name
|
96
|
+
quoted_field = @conn.quote_column_name field
|
97
|
+
|
98
|
+
column_type = klass.columns_hash[field].type
|
99
|
+
expr = case column_type
|
100
|
+
when :date, :datetime, :time, :timestamp then
|
101
|
+
@source_conf['sql_date_column'] << field
|
102
|
+
"UNIX_TIMESTAMP(#{table}.#{quoted_field})"
|
103
|
+
when :boolean, :integer then
|
104
|
+
@source_conf['sql_group_column'] << field
|
105
|
+
"#{table}.#{quoted_field}"
|
106
|
+
when :string, :text then
|
107
|
+
"#{table}.#{quoted_field}"
|
108
|
+
else
|
109
|
+
raise Sphincter::Error, "unknown column type #{column_type}"
|
110
|
+
end
|
111
|
+
|
112
|
+
as_name = [as_table, field].compact.join '_'
|
113
|
+
as_name = @conn.quote_column_name as_name
|
114
|
+
|
115
|
+
"#{expr} AS #{as_name}"
|
116
|
+
end
|
117
|
+
|
118
|
+
##
|
119
|
+
# Includes field +as_field+ from association +as_name+ in the index.
|
120
|
+
|
121
|
+
def add_include(as_name, as_field)
|
122
|
+
as_assoc = @klass.reflect_on_all_associations.find do |assoc|
|
123
|
+
assoc.name == as_name.intern
|
124
|
+
end
|
125
|
+
|
126
|
+
if as_assoc.nil? then
|
127
|
+
raise Sphincter::Error,
|
128
|
+
"could not find association \"#{as_name}\" in #{@klass.name}"
|
129
|
+
end
|
130
|
+
|
131
|
+
as_klass = as_assoc.class_name.constantize
|
132
|
+
as_table = as_klass.table_name
|
133
|
+
|
134
|
+
as_klass_key = @conn.quote_column_name as_klass.primary_key.to_s
|
135
|
+
as_assoc_key = @conn.quote_column_name as_assoc.primary_key_name.to_s
|
136
|
+
|
137
|
+
case as_assoc.macro
|
138
|
+
when :belongs_to then
|
139
|
+
@fields << add_field(as_field, as_klass, as_table)
|
140
|
+
@tables << " LEFT JOIN #{as_table} ON" \
|
141
|
+
" #{@table}.#{as_assoc_key} = #{as_table}.#{as_klass_key}"
|
142
|
+
|
143
|
+
when :has_many then
|
144
|
+
if as_assoc.options.include? :through then
|
145
|
+
raise Sphincter::Error,
|
146
|
+
"unsupported macro has_many :through for \"#{as_name}\" " \
|
147
|
+
"in #{klass.name}.add_index"
|
148
|
+
end
|
149
|
+
|
150
|
+
as_pkey = @conn.quote_column_name as_klass.primary_key.to_s
|
151
|
+
as_fkey = @conn.quote_column_name as_assoc.primary_key_name.to_s
|
152
|
+
|
153
|
+
as_name = [as_table, as_field].compact.join '_'
|
154
|
+
as_name = @conn.quote_column_name as_name
|
155
|
+
|
156
|
+
field = @conn.quote_column_name as_field
|
157
|
+
|
158
|
+
@fields << "GROUP_CONCAT(#{as_table}.#{field} SEPARATOR ' ') AS #{as_name}"
|
159
|
+
|
160
|
+
if as_assoc.options.include? :as then
|
161
|
+
poly_name = as_assoc.options[:as]
|
162
|
+
id_col = @conn.quote_column_name "#{poly_name}_id"
|
163
|
+
type_col = @conn.quote_column_name "#{poly_name}_type"
|
164
|
+
|
165
|
+
@tables << " LEFT JOIN #{as_table} ON"\
|
166
|
+
" #{@table}.#{as_klass_key} = #{as_table}.#{id_col} AND" \
|
167
|
+
" #{@conn.quote @klass.name} = #{as_table}.#{type_col}"
|
168
|
+
else
|
169
|
+
@tables << " LEFT JOIN #{as_table} ON" \
|
170
|
+
" #{@table}.#{as_klass_key} = #{as_table}.#{as_assoc_key}"
|
171
|
+
end
|
172
|
+
|
173
|
+
@group = true
|
174
|
+
else
|
175
|
+
raise Sphincter::Error,
|
176
|
+
"unsupported macro #{as_assoc.macro} for \"#{as_name}\" " \
|
177
|
+
"in #{klass.name}.add_index"
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
def configure
|
182
|
+
conn = @klass.connection
|
183
|
+
pk = conn.quote_column_name @klass.primary_key
|
184
|
+
index_id = @options[:index_id]
|
185
|
+
|
186
|
+
index_count = Sphincter::Configure.index_count
|
187
|
+
|
188
|
+
@fields << "(#{@table}.#{pk} * #{index_count} + #{index_id}) AS #{pk}"
|
189
|
+
@fields << "#{index_id} AS sphincter_index_id"
|
190
|
+
@fields << "'#{@klass.name}' AS sphincter_klass"
|
191
|
+
|
192
|
+
@options[:fields].each do |field|
|
193
|
+
case field
|
194
|
+
when /\./ then add_include(*field.split('.', 2))
|
195
|
+
else @fields << add_field(field)
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
@fields = @fields.join ', '
|
200
|
+
|
201
|
+
@where << "#{@table}.#{pk} >= $start"
|
202
|
+
@where << "#{@table}.#{pk} <= $end"
|
203
|
+
@where.push(*@options[:conditions])
|
204
|
+
@where = @where.compact.join ' AND '
|
205
|
+
|
206
|
+
query = "SELECT #{@fields} FROM #{@tables} WHERE #{@where}"
|
207
|
+
query << " GROUP BY #{@table}.#{pk}" if @group
|
208
|
+
|
209
|
+
@source_conf['sql_query'] = query
|
210
|
+
@source_conf['sql_query_info'] =
|
211
|
+
"SELECT * FROM #{@table} " \
|
212
|
+
"WHERE #{@table}.#{pk} = (($id - #{index_id}) / #{index_count})"
|
213
|
+
@source_conf['sql_query_range'] =
|
214
|
+
"SELECT MIN(#{pk}), MAX(#{pk}) FROM #{@table}"
|
215
|
+
@source_conf['strip_html'] = @options[:strip_html] ? 1 : 0
|
216
|
+
|
217
|
+
@source_conf
|
218
|
+
end
|
219
|
+
|
220
|
+
end
|
221
|
+
|
53
222
|
@env_conf = nil
|
54
223
|
@index_count = nil
|
55
224
|
|
@@ -80,7 +249,6 @@ module Sphincter::Configure
|
|
80
249
|
|
81
250
|
'mysql' => {
|
82
251
|
'sql_query_pre' => [
|
83
|
-
'SET SESSION group_concat_max_len = 65535',
|
84
252
|
'SET NAMES utf8',
|
85
253
|
],
|
86
254
|
},
|
@@ -179,42 +347,10 @@ module Sphincter::Configure
|
|
179
347
|
|
180
348
|
indexes.each do |klass, model_indexes|
|
181
349
|
model_indexes.each do |options|
|
182
|
-
|
183
|
-
|
184
|
-
pk = conn.quote_column_name klass.primary_key
|
185
|
-
index_id = options[:index_id]
|
186
|
-
|
187
|
-
source_conf = {}
|
188
|
-
source_conf['sql_date_column'] = []
|
189
|
-
source_conf['sql_group_column'] = %w[sphincter_index_id]
|
190
|
-
|
191
|
-
fields = []
|
192
|
-
fields << "(#{table}.#{pk} * #{index_count} + #{index_id}) AS #{pk}"
|
193
|
-
fields << "#{index_id} AS sphincter_index_id"
|
194
|
-
fields << "#{conn.quote table} AS klass"
|
195
|
-
|
196
|
-
options[:fields].each do |field|
|
197
|
-
fields << get_sources_field(source_conf, klass, field)
|
198
|
-
end
|
199
|
-
|
200
|
-
fields = fields.join ', '
|
201
|
-
|
202
|
-
where = []
|
203
|
-
where << "#{table}.#{pk} >= $start AND #{table}.#{pk} <= $end"
|
204
|
-
where << options[:conditions]
|
205
|
-
where = where.compact.join ' AND '
|
206
|
-
|
207
|
-
source_conf['sql_query'] =
|
208
|
-
"SELECT #{fields} FROM #{table} WHERE #{where}"
|
209
|
-
source_conf['sql_query_info'] =
|
210
|
-
"SELECT * FROM #{table} " \
|
211
|
-
"WHERE #{table}.#{pk} = (($id - #{index_id}) / #{index_count})"
|
212
|
-
source_conf['sql_query_range'] =
|
213
|
-
"SELECT MIN(#{pk}), MAX(#{pk}) FROM #{table}"
|
214
|
-
source_conf['strip_html'] = options[:strip_html] ? 1 : 0
|
350
|
+
index = Index.new klass, options
|
351
|
+
index.configure
|
215
352
|
|
216
|
-
|
217
|
-
sources[name] = source_conf
|
353
|
+
sources[index.name] = index.source_conf
|
218
354
|
end
|
219
355
|
end
|
220
356
|
|
@@ -227,23 +363,6 @@ module Sphincter::Configure
|
|
227
363
|
# get_sources_field only understands :datetime, :boolean, :integer, :string
|
228
364
|
# and :text column types.
|
229
365
|
|
230
|
-
def self.get_sources_field(source_conf, klass, field)
|
231
|
-
conn = klass.connection
|
232
|
-
table = klass.table_name
|
233
|
-
|
234
|
-
quoted_field = conn.quote_column_name field
|
235
|
-
case klass.columns_hash[field].type
|
236
|
-
when :date, :datetime, :time, :timestamp then
|
237
|
-
source_conf['sql_date_column'] << field
|
238
|
-
"UNIX_TIMESTAMP(#{table}.#{quoted_field}) AS #{quoted_field}"
|
239
|
-
when :boolean, :integer then
|
240
|
-
source_conf['sql_group_column'] << field
|
241
|
-
"#{table}.#{quoted_field} AS #{quoted_field}"
|
242
|
-
when :string, :text then
|
243
|
-
"#{table}.#{quoted_field} AS #{quoted_field}"
|
244
|
-
end
|
245
|
-
end
|
246
|
-
|
247
366
|
##
|
248
367
|
# Retrieves the database configuration for ActiveRecord::Base and adapts it
|
249
368
|
# for a sphinx.conf file.
|
data/lib/sphincter/search.rb
CHANGED
@@ -44,10 +44,33 @@ module Sphincter::Search
|
|
44
44
|
# Options are:
|
45
45
|
#
|
46
46
|
# :name:: Name of index. Defaults to ActiveRecord::Base::table_name.
|
47
|
-
# :fields::
|
48
|
-
# automatically added.
|
49
|
-
#
|
50
|
-
#
|
47
|
+
# :fields:: Array of fields to index. Foreign key columns for belongs_to
|
48
|
+
# associations are automatically added. Fields from associations
|
49
|
+
# may be included by using "association.field".
|
50
|
+
# :conditions:: Array of SQL conditions that will be ANDed together to
|
51
|
+
# predicate inclusion in the search index.
|
52
|
+
#
|
53
|
+
# Example:
|
54
|
+
#
|
55
|
+
# class Post < ActiveRecord::Base
|
56
|
+
# belongs_to :user
|
57
|
+
# belongs_to :blog
|
58
|
+
# has_many :comments
|
59
|
+
#
|
60
|
+
# add_index :fields => %w[title body user.name, comments.body],
|
61
|
+
# :conditions => ['published = 1']
|
62
|
+
# end
|
63
|
+
#
|
64
|
+
# When including fields from associations, MySQL's GROUP_CONCAT() function
|
65
|
+
# is used. By default this will create a string up to 1024 characters long.
|
66
|
+
# A larger string can be used by changing the value of MySQL's
|
67
|
+
# group_concat_max_len variable. To do this, add the following to your
|
68
|
+
# sphincter.RAILS_ENV.yml files:
|
69
|
+
#
|
70
|
+
# mysql:
|
71
|
+
# sql_query_pre:
|
72
|
+
# - SET NAMES utf8
|
73
|
+
# - SET SESSION group_concat_max_len = VALUE
|
51
74
|
|
52
75
|
def add_index(options = {})
|
53
76
|
options[:fields] ||= []
|
data/lib/sphincter/tasks.rb
CHANGED
@@ -21,7 +21,7 @@ namespace :sphincter do
|
|
21
21
|
|
22
22
|
indexes_found = Dir[File.join(sphinx_dir, '*.spd')].length
|
23
23
|
|
24
|
-
Rake::Task['sphincter
|
24
|
+
Rake::Task['sphincter:reindex'].invoke if indexes_found > indexes_defined
|
25
25
|
end
|
26
26
|
|
27
27
|
desc 'Runs the sphinx indexer'
|
@@ -45,6 +45,56 @@ namespace :sphincter do
|
|
45
45
|
desc 'Restarts the searchd sphinx daemon'
|
46
46
|
task :restart_searchd => %w[sphincter:stop_searchd sphincter:start_searchd]
|
47
47
|
|
48
|
+
desc 'Sets up the sphinx client'
|
49
|
+
task :setup_sphinx do
|
50
|
+
require 'fileutils'
|
51
|
+
require 'tmpdir'
|
52
|
+
require 'open-uri'
|
53
|
+
|
54
|
+
verbose = Rake.application.options.trace
|
55
|
+
|
56
|
+
begin
|
57
|
+
tmpdir = File.join Dir.tmpdir, "Sphincter_setup_#{$$}"
|
58
|
+
|
59
|
+
mkdir tmpdir, :verbose => true
|
60
|
+
|
61
|
+
chdir tmpdir
|
62
|
+
|
63
|
+
src = open "http://rubyforge.org/frs/download.php/19571/sphinx-0.3.0.zip"
|
64
|
+
File.open('sphinx-0.3.0.zip', 'wb') { |dst| dst.write src.read }
|
65
|
+
|
66
|
+
quiet = verbose ? '' : ' -q'
|
67
|
+
sh "unzip#{quiet} sphinx-0.3.0.zip" or
|
68
|
+
raise "couldn't unzip sphinx-0.3.0.zip"
|
69
|
+
|
70
|
+
File.open 'sphinx.patch', 'wb' do |patch|
|
71
|
+
patch.puts <<-EOF
|
72
|
+
--- sphinx/lib/client.rb.orig 2007-04-05 06:38:14.000000000 -0700
|
73
|
+
+++ sphinx/lib/client.rb 2007-07-29 20:23:18.000000000 -0700
|
74
|
+
@@ -398,6 +398,7 @@
|
75
|
+
\r
|
76
|
+
result['matches'][doc] ||= {}\r
|
77
|
+
result['matches'][doc]['weight'] = weight\r
|
78
|
+
+ result['matches'][doc]['index'] = count\r
|
79
|
+
attrs_names_in_order.each do |attr|\r
|
80
|
+
val = response[p, 4].unpack('N*').first; p += 4\r
|
81
|
+
result['matches'][doc]['attrs'] ||= {}\r
|
82
|
+
EOF
|
83
|
+
end
|
84
|
+
|
85
|
+
quiet = verbose ? ' --verbose' : ''
|
86
|
+
sh "patch#{quiet} -p0 sphinx/lib/client.rb sphinx.patch" or
|
87
|
+
raise "couldn't patch sphinx"
|
88
|
+
|
89
|
+
sphinx_plugin_dir = File.join RAILS_ROOT, 'vendor', 'plugins', 'sphinx'
|
90
|
+
rm_rf sphinx_plugin_dir, :verbose => true
|
91
|
+
|
92
|
+
mv 'sphinx', sphinx_plugin_dir, :verbose => true
|
93
|
+
ensure
|
94
|
+
rm_rf tmpdir, :verbose => true
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
48
98
|
desc 'Starts the searchd sphinx daemon'
|
49
99
|
task :start_searchd => :index do
|
50
100
|
unless Sphincter::Configure.searchd_running? then
|
data/test/sphincter_test_case.rb
CHANGED
@@ -5,7 +5,14 @@ require 'tmpdir'
|
|
5
5
|
$TESTING = true
|
6
6
|
|
7
7
|
class String
|
8
|
-
def constantize
|
8
|
+
def constantize
|
9
|
+
case self
|
10
|
+
when /belongs_to/i then SphincterTestCase::BelongsTo
|
11
|
+
when /many/i then SphincterTestCase::HasMany
|
12
|
+
when /poly/i then SphincterTestCase::Poly
|
13
|
+
else raise "missing klass for #{self} in #constantize"
|
14
|
+
end
|
15
|
+
end
|
9
16
|
end
|
10
17
|
|
11
18
|
require 'sphincter'
|
@@ -87,29 +94,30 @@ class SphincterTestCase < Test::Unit::TestCase
|
|
87
94
|
end
|
88
95
|
|
89
96
|
class Connection
|
90
|
-
def quote(name) "
|
97
|
+
def quote(name) "'#{name}'" end
|
91
98
|
def quote_column_name(name) "`#{name}`" end
|
92
99
|
end
|
93
100
|
|
94
101
|
class Reflection
|
95
102
|
attr_accessor :klass
|
96
|
-
attr_reader :macro, :options
|
103
|
+
attr_reader :macro, :options, :name
|
97
104
|
|
98
|
-
def initialize(macro, name)
|
105
|
+
def initialize(macro, name, options = {})
|
99
106
|
@klass = Model
|
100
107
|
@macro = macro
|
101
|
-
@name = name
|
102
|
-
@options =
|
108
|
+
@name = name.intern
|
109
|
+
@options = options
|
103
110
|
end
|
104
111
|
|
105
|
-
def class_name()
|
112
|
+
def class_name() @name.to_s.sub(/s$/, '').capitalize end
|
106
113
|
def primary_key_name() "#{@name}_id" end
|
107
114
|
end
|
108
115
|
|
109
116
|
class Model
|
110
117
|
|
111
|
-
@reflections = [Reflection.new(:belongs_to, '
|
112
|
-
Reflection.new(:has_many, '
|
118
|
+
@reflections = [Reflection.new(:belongs_to, 'belongs_to'),
|
119
|
+
Reflection.new(:has_many, 'manys'),
|
120
|
+
Reflection.new(:has_many, 'polys', :as => :polyable)]
|
113
121
|
|
114
122
|
class << self; attr_accessor :reflections; end
|
115
123
|
|
@@ -120,6 +128,7 @@ class SphincterTestCase < Test::Unit::TestCase
|
|
120
128
|
'boolean' => Column.new(:boolean),
|
121
129
|
'date' => Column.new(:date),
|
122
130
|
'datetime' => Column.new(:datetime),
|
131
|
+
'float' => Column.new(:float),
|
123
132
|
'integer' => Column.new(:integer),
|
124
133
|
'string' => Column.new(:string),
|
125
134
|
'text' => Column.new(:text),
|
@@ -130,6 +139,8 @@ class SphincterTestCase < Test::Unit::TestCase
|
|
130
139
|
|
131
140
|
def self.find(ids) ids end
|
132
141
|
|
142
|
+
def self.name() 'Model' end
|
143
|
+
|
133
144
|
def self.primary_key() 'id' end
|
134
145
|
|
135
146
|
def self.reflect_on_all_associations
|
@@ -140,13 +151,32 @@ class SphincterTestCase < Test::Unit::TestCase
|
|
140
151
|
|
141
152
|
end
|
142
153
|
|
143
|
-
class
|
144
|
-
@reflections = [Reflection.new(:belongs_to, '
|
145
|
-
Reflection.new(:has_many, '
|
154
|
+
class BelongsTo < Model
|
155
|
+
@reflections = [Reflection.new(:belongs_to, 'something'),
|
156
|
+
Reflection.new(:has_many, 'models')]
|
157
|
+
|
158
|
+
def self.table_name() 'belongs_tos' end
|
146
159
|
|
147
160
|
def id() 42 end
|
148
161
|
end
|
149
162
|
|
163
|
+
class HasMany < Model
|
164
|
+
@reflections = [Reflection.new(:belongs_to, 'models')]
|
165
|
+
|
166
|
+
def self.table_name() 'has_manys' end
|
167
|
+
|
168
|
+
def id() 84 end
|
169
|
+
end
|
170
|
+
|
171
|
+
class Poly < Model
|
172
|
+
@reflections = [Reflection.new(:belongs_to, 'polyable',
|
173
|
+
:polymorphic => true)]
|
174
|
+
|
175
|
+
def self.table_name() 'polys' end
|
176
|
+
|
177
|
+
def id() 126 end
|
178
|
+
end
|
179
|
+
|
150
180
|
class Model
|
151
181
|
extend Sphincter::Search
|
152
182
|
end
|
@@ -172,7 +202,7 @@ class SphincterTestCase < Test::Unit::TestCase
|
|
172
202
|
Sphincter::Configure.instance_variable_set '@index_count', nil if
|
173
203
|
Sphincter::Configure.instance_variables.include? '@index_count'
|
174
204
|
|
175
|
-
|
205
|
+
BelongsTo.reflections.last.options.delete :extend
|
176
206
|
end
|
177
207
|
|
178
208
|
def teardown
|
@@ -9,7 +9,7 @@ class TestSphincterAssociationSearcher < SphincterTestCase
|
|
9
9
|
attr_accessor :reflection
|
10
10
|
|
11
11
|
def initialize
|
12
|
-
@reflection = SphincterTestCase::
|
12
|
+
@reflection = SphincterTestCase::BelongsTo.reflections.last
|
13
13
|
klass = Object.new
|
14
14
|
def klass.search_args() @search_args end
|
15
15
|
def klass.search(*args) @search_args = args; :searched end
|
@@ -21,7 +21,7 @@ class TestSphincterAssociationSearcher < SphincterTestCase
|
|
21
21
|
end
|
22
22
|
|
23
23
|
def proxy_owner()
|
24
|
-
SphincterTestCase::
|
24
|
+
SphincterTestCase::BelongsTo.new
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
@@ -31,7 +31,7 @@ class TestSphincterAssociationSearcher < SphincterTestCase
|
|
31
31
|
results = proxy.search 'words'
|
32
32
|
|
33
33
|
assert_equal :searched, results
|
34
|
-
assert_equal ['words', { :conditions => { '
|
34
|
+
assert_equal ['words', { :conditions => { 'models_id' => 42 } } ],
|
35
35
|
proxy.proxy_reflection.klass.search_args
|
36
36
|
end
|
37
37
|
|
@@ -6,7 +6,6 @@ class TestSphincterConfigure < SphincterTestCase
|
|
6
6
|
DEFAULT_GET_CONF_EXPECTED = {
|
7
7
|
"mysql" => {
|
8
8
|
"sql_query_pre" => [
|
9
|
-
"SET SESSION group_concat_max_len = 65535",
|
10
9
|
"SET NAMES utf8",
|
11
10
|
]
|
12
11
|
},
|
@@ -129,7 +128,9 @@ searchd
|
|
129
128
|
end
|
130
129
|
|
131
130
|
def test_self_get_sources
|
132
|
-
Sphincter::Search.indexes[Model] << {
|
131
|
+
Sphincter::Search.indexes[Model] << {
|
132
|
+
:fields => %w[text belongs_to.string]
|
133
|
+
}
|
133
134
|
|
134
135
|
expected = {
|
135
136
|
"models" => {
|
@@ -142,56 +143,18 @@ searchd
|
|
142
143
|
"sql_query" =>
|
143
144
|
"SELECT (models.`id` * 1 + 0) AS `id`, " \
|
144
145
|
"0 AS sphincter_index_id, " \
|
145
|
-
"
|
146
|
-
"models.`text` AS `text
|
147
|
-
|
148
|
-
|
146
|
+
"'Model' AS sphincter_klass, "\
|
147
|
+
"models.`text` AS `text`, " \
|
148
|
+
"belongs_tos.`string` AS `belongs_tos_string` "\
|
149
|
+
"FROM models LEFT JOIN belongs_tos ON " \
|
150
|
+
"models.`belongs_to_id` = belongs_tos.`id` " \
|
151
|
+
"WHERE models.`id` >= $start AND models.`id` <= $end"
|
149
152
|
}
|
150
153
|
}
|
151
154
|
|
152
155
|
assert_equal expected, Sphincter::Configure.get_sources
|
153
156
|
end
|
154
157
|
|
155
|
-
def test_self_get_sources_field
|
156
|
-
source_conf = { 'sql_group_column' => [], 'sql_date_column' => [] }
|
157
|
-
klass = Model
|
158
|
-
|
159
|
-
fields = []
|
160
|
-
fields << Sphincter::Configure.get_sources_field(source_conf, klass,
|
161
|
-
'date')
|
162
|
-
fields << Sphincter::Configure.get_sources_field(source_conf, klass,
|
163
|
-
'datetime')
|
164
|
-
fields << Sphincter::Configure.get_sources_field(source_conf, klass,
|
165
|
-
'boolean')
|
166
|
-
fields << Sphincter::Configure.get_sources_field(source_conf, klass,
|
167
|
-
'integer')
|
168
|
-
fields << Sphincter::Configure.get_sources_field(source_conf, klass,
|
169
|
-
'string')
|
170
|
-
fields << Sphincter::Configure.get_sources_field(source_conf, klass,
|
171
|
-
'time')
|
172
|
-
fields << Sphincter::Configure.get_sources_field(source_conf, klass,
|
173
|
-
'timestamp')
|
174
|
-
fields << Sphincter::Configure.get_sources_field(source_conf, klass,
|
175
|
-
'text')
|
176
|
-
|
177
|
-
expected_fields = [
|
178
|
-
"UNIX_TIMESTAMP(models.`date`) AS `date`",
|
179
|
-
"UNIX_TIMESTAMP(models.`datetime`) AS `datetime`",
|
180
|
-
"models.`boolean` AS `boolean`",
|
181
|
-
"models.`integer` AS `integer`",
|
182
|
-
"models.`string` AS `string`",
|
183
|
-
"UNIX_TIMESTAMP(models.`time`) AS `time`",
|
184
|
-
"UNIX_TIMESTAMP(models.`timestamp`) AS `timestamp`",
|
185
|
-
"models.`text` AS `text`"
|
186
|
-
]
|
187
|
-
|
188
|
-
assert_equal expected_fields, fields
|
189
|
-
|
190
|
-
assert_equal %w[boolean integer], source_conf['sql_group_column']
|
191
|
-
assert_equal %w[date datetime time timestamp],
|
192
|
-
source_conf['sql_date_column']
|
193
|
-
end
|
194
|
-
|
195
158
|
def test_self_get_db_conf
|
196
159
|
expected = {
|
197
160
|
'type' => 'mysql',
|
@@ -316,3 +279,108 @@ index source_2
|
|
316
279
|
|
317
280
|
end
|
318
281
|
|
282
|
+
class Sphincter::Configure::Index
|
283
|
+
attr_reader :fields, :where, :tables, :group
|
284
|
+
end
|
285
|
+
|
286
|
+
class TestSphincterConfigureIndex < SphincterTestCase
|
287
|
+
|
288
|
+
def setup
|
289
|
+
super
|
290
|
+
|
291
|
+
@index = Sphincter::Configure::Index.new Model, {}
|
292
|
+
end
|
293
|
+
|
294
|
+
def test_self_add_field
|
295
|
+
fields = []
|
296
|
+
fields << @index.add_field('date')
|
297
|
+
fields << @index.add_field('datetime')
|
298
|
+
fields << @index.add_field('boolean')
|
299
|
+
fields << @index.add_field('integer')
|
300
|
+
fields << @index.add_field('string')
|
301
|
+
fields << @index.add_field('time')
|
302
|
+
fields << @index.add_field('timestamp')
|
303
|
+
fields << @index.add_field('text')
|
304
|
+
|
305
|
+
expected_fields = [
|
306
|
+
"UNIX_TIMESTAMP(models.`date`) AS `date`",
|
307
|
+
"UNIX_TIMESTAMP(models.`datetime`) AS `datetime`",
|
308
|
+
"models.`boolean` AS `boolean`",
|
309
|
+
"models.`integer` AS `integer`",
|
310
|
+
"models.`string` AS `string`",
|
311
|
+
"UNIX_TIMESTAMP(models.`time`) AS `time`",
|
312
|
+
"UNIX_TIMESTAMP(models.`timestamp`) AS `timestamp`",
|
313
|
+
"models.`text` AS `text`"
|
314
|
+
]
|
315
|
+
|
316
|
+
assert_equal expected_fields, fields
|
317
|
+
|
318
|
+
assert_equal %w[sphincter_index_id boolean integer],
|
319
|
+
@index.source_conf['sql_group_column']
|
320
|
+
assert_equal %w[date datetime time timestamp],
|
321
|
+
@index.source_conf['sql_date_column']
|
322
|
+
end
|
323
|
+
|
324
|
+
def test_self_add_field_unknown
|
325
|
+
e = assert_raise Sphincter::Error do
|
326
|
+
@index.add_field 'float'
|
327
|
+
end
|
328
|
+
|
329
|
+
assert_equal 'unknown column type float', e.message
|
330
|
+
end
|
331
|
+
|
332
|
+
def test_add_include_belongs_to
|
333
|
+
@index.add_include 'belongs_to', 'string'
|
334
|
+
|
335
|
+
assert_equal ["belongs_tos.`string` AS `belongs_tos_string`"], @index.fields
|
336
|
+
|
337
|
+
tables = "models LEFT JOIN belongs_tos ON " \
|
338
|
+
"models.`belongs_to_id` = belongs_tos.`id`"
|
339
|
+
assert_equal tables, @index.tables
|
340
|
+
|
341
|
+
assert_equal [], @index.where
|
342
|
+
|
343
|
+
assert_equal false, @index.group
|
344
|
+
end
|
345
|
+
|
346
|
+
def test_add_include_has_many
|
347
|
+
@index.add_include 'manys', 'string'
|
348
|
+
|
349
|
+
fields = [
|
350
|
+
"GROUP_CONCAT(has_manys.`string` SEPARATOR ' ') AS `has_manys_string`"
|
351
|
+
]
|
352
|
+
assert_equal fields, @index.fields
|
353
|
+
|
354
|
+
tables = "models LEFT JOIN has_manys ON models.`id` = has_manys.`manys_id`"
|
355
|
+
assert_equal tables, @index.tables
|
356
|
+
|
357
|
+
assert_equal [], @index.where
|
358
|
+
assert_equal true, @index.group
|
359
|
+
end
|
360
|
+
|
361
|
+
def test_add_include_has_many_polymorphic
|
362
|
+
@index.add_include 'polys', 'string'
|
363
|
+
|
364
|
+
fields = ["GROUP_CONCAT(polys.`string` SEPARATOR ' ') AS `polys_string`"]
|
365
|
+
assert_equal fields, @index.fields
|
366
|
+
|
367
|
+
tables = "models LEFT JOIN polys ON " \
|
368
|
+
"models.`id` = polys.`polyable_id` AND " \
|
369
|
+
"'Model' = polys.`polyable_type`"
|
370
|
+
assert_equal tables, @index.tables
|
371
|
+
|
372
|
+
assert_equal [], @index.where
|
373
|
+
assert_equal true, @index.group
|
374
|
+
end
|
375
|
+
|
376
|
+
def test_add_include_nonexistent_association
|
377
|
+
e = assert_raise Sphincter::Error do
|
378
|
+
@index.add_include 'nonexistent', 'string'
|
379
|
+
end
|
380
|
+
|
381
|
+
assert_equal "could not find association \"nonexistent\" in Model",
|
382
|
+
e.message
|
383
|
+
end
|
384
|
+
|
385
|
+
end
|
386
|
+
|
@@ -12,15 +12,15 @@ class TestSphincterSearch < SphincterTestCase
|
|
12
12
|
def test_add_index
|
13
13
|
Model.add_index :fields => %w[text]
|
14
14
|
|
15
|
-
assert_equal [{ :fields => %w[text
|
15
|
+
assert_equal [{ :fields => %w[text belongs_to_id] }],
|
16
16
|
Sphincter::Search.indexes[Model]
|
17
17
|
|
18
|
-
|
19
|
-
|
18
|
+
belongs_to_belongs_to = BelongsTo.reflections.first
|
19
|
+
belongs_to_has_many = BelongsTo.reflections.last
|
20
20
|
|
21
|
-
assert_equal({},
|
21
|
+
assert_equal({}, belongs_to_belongs_to.options, 'BelongsTo belongs_to')
|
22
22
|
assert_equal({ :extend => [Sphincter::AssociationSearcher] },
|
23
|
-
|
23
|
+
belongs_to_has_many.options, 'BelongsTo has_many')
|
24
24
|
end
|
25
25
|
|
26
26
|
def test_sphincter_convert_values
|
metadata
CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.4.3
|
|
3
3
|
specification_version: 1
|
4
4
|
name: Sphincter
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 1.
|
7
|
-
date: 2007-
|
6
|
+
version: 1.1.0
|
7
|
+
date: 2007-08-13 00:00:00 -07:00
|
8
8
|
summary: Sphincter is an ActiveRecord extension for full-text searching with Sphinx.
|
9
9
|
require_paths:
|
10
10
|
- lib
|
@@ -31,6 +31,28 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
31
31
|
platform: ruby
|
32
32
|
signing_key:
|
33
33
|
cert_chain:
|
34
|
+
- |
|
35
|
+
-----BEGIN CERTIFICATE-----
|
36
|
+
MIIDNjCCAh6gAwIBAgIBADANBgkqhkiG9w0BAQUFADBBMRAwDgYDVQQDDAdkcmJy
|
37
|
+
YWluMRgwFgYKCZImiZPyLGQBGRYIc2VnbWVudDcxEzARBgoJkiaJk/IsZAEZFgNu
|
38
|
+
ZXQwHhcNMDcwODE0MDAwMjIzWhcNMDgwODEzMDAwMjIzWjBBMRAwDgYDVQQDDAdk
|
39
|
+
cmJyYWluMRgwFgYKCZImiZPyLGQBGRYIc2VnbWVudDcxEzARBgoJkiaJk/IsZAEZ
|
40
|
+
FgNuZXQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDVZUNKmnp8LrkM
|
41
|
+
bQd5ZrcTV1R7woai4clBLUjH3DL47r+f6d5dz+gJUegZ3RKWdSvOfaRmXFkr2+nv
|
42
|
+
vc6uzcxk9w1uN5Z3w+BCeKtsUR8EtUhH8b26HDNGDeuoTX1gEgm4DacBh1/Ib+SQ
|
43
|
+
PxLVkFnWiCekGvL7jzecw6UwADn49Ag4NxIvpN0ttsYCQFMDuqzdISjurbb3dZ2z
|
44
|
+
OsaDqdW29c3Jed816kVhOzRZ2EC4BExPtEN6xZCwab6f9tzJT+Atea7PRFLm/T7t
|
45
|
+
QFBPGC2XjPUXJxTyz+8PEDDb2PXeZwPSDIysq1tzB55A3rE1a5pLvnBfek5KjC25
|
46
|
+
0wuuKuxlAgMBAAGjOTA3MAkGA1UdEwQCMAAwCwYDVR0PBAQDAgSwMB0GA1UdDgQW
|
47
|
+
BBStuxfp/gfzqL+3k7tFe8gVU9zpvDANBgkqhkiG9w0BAQUFAAOCAQEAvnmUelUN
|
48
|
+
s9f/VasT9mZV4tIP3sKi0uqyq9i7vtDgCNFw0BAKNxa6ybO1CrBBnjDMa4hvhrW8
|
49
|
+
qCLkx7BFHGV/eWR3pdwcLAS8cLuEib75nuG1lbG2yIvGSYTyQ/oxbmuUAZxpavK2
|
50
|
+
101OludXvBC9hpA4Qz3UhJYBdtT8TuztiFGLzhCJusjUD7I6Y+TrrTPkBGceVVyY
|
51
|
+
hx8aJk+44+jvzgsTi4MyrRo4lAGsQxFa1f1IBuEgqNPdML31yGO0QKof+IqPiVNo
|
52
|
+
HsCQoSWkgfQE0DHTgx+hWkF2d10+54I4aM9tIROeGACQemcj0IRf3v7Au8I+6PWl
|
53
|
+
3E1oHz01aNcUFA==
|
54
|
+
-----END CERTIFICATE-----
|
55
|
+
|
34
56
|
post_install_message:
|
35
57
|
authors:
|
36
58
|
- Eric Hodel
|
@@ -96,5 +118,5 @@ dependencies:
|
|
96
118
|
requirements:
|
97
119
|
- - ">="
|
98
120
|
- !ruby/object:Gem::Version
|
99
|
-
version: 1.
|
121
|
+
version: 1.3.0
|
100
122
|
version:
|
metadata.gz.sig
ADDED