Sphincter 1.0.0 → 1.1.0
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.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