Sphincter 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Binary file
@@ -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', 5).first
13
- p.description = p.paragraphs_of('README.txt', 6).first
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
 
@@ -92,7 +92,12 @@ module Sphincter
92
92
  ##
93
93
  # This is the version of Sphincter you are using.
94
94
 
95
- VERSION = '1.0.0'
95
+ VERSION = '1.1.0'
96
+
97
+ ##
98
+ # Sphincter error base class.
99
+
100
+ class Error < RuntimeError; end
96
101
 
97
102
  end
98
103
 
@@ -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
- conn = klass.connection
183
- table = klass.table_name
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
- name = options[:name] || table
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.
@@ -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:: Fields to index. Columns from belongs_to associations are
48
- # automatically added.
49
- # :conditions:: Hash of SQL conditions that predicate inclusion in the
50
- # search index.
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] ||= []
@@ -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::reindex'].invoke if indexes_found > indexes_defined
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
@@ -5,7 +5,14 @@ require 'tmpdir'
5
5
  $TESTING = true
6
6
 
7
7
  class String
8
- def constantize() SphincterTestCase::Other end
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) "`#{name}`" end
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() "SphincterTestCase::#{@name.capitalize}" end
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, 'other'),
112
- Reflection.new(:has_many, 'other')]
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 Other < Model
144
- @reflections = [Reflection.new(:belongs_to, 'model'),
145
- Reflection.new(:has_many, 'model')]
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
- Other.reflections.last.options.delete :extend
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::Reflection.new :has_many, 'other'
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::Other.new
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 => { 'other_id' => 42 } } ],
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] << { :fields => %w[text] }
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
- "`models` AS klass, "\
146
- "models.`text` AS `text` " \
147
- "FROM models WHERE models.`id` >= $start AND " \
148
- "models.`id` <= $end"
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 other_id] }],
15
+ assert_equal [{ :fields => %w[text belongs_to_id] }],
16
16
  Sphincter::Search.indexes[Model]
17
17
 
18
- other_belongs_to = Other.reflections.first
19
- other_has_many = Other.reflections.last
18
+ belongs_to_belongs_to = BelongsTo.reflections.first
19
+ belongs_to_has_many = BelongsTo.reflections.last
20
20
 
21
- assert_equal({}, other_belongs_to.options, 'Other belongs_to')
21
+ assert_equal({}, belongs_to_belongs_to.options, 'BelongsTo belongs_to')
22
22
  assert_equal({ :extend => [Sphincter::AssociationSearcher] },
23
- other_has_many.options, 'Other has_many')
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.0.0
7
- date: 2007-07-30 00:00:00 -07:00
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.2.2
121
+ version: 1.3.0
100
122
  version:
@@ -0,0 +1,3 @@
1
+ �eg�>Mu�nUEo�p�����.ɵ(_>��:���;qe����������Pm
2
+ A�a���=m� mǨ��F�՗|��7��i˜Kg�a�z ��ߩ�#'R�qF~�bA��4���_��+҉PS��G^
3
+ �ڝ��� ���j!��zƠ��/�Y^��O�aTI������