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.
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������