thinking-sphinx 1.3.15 → 1.3.16

Sign up to get free protection for your applications and to get access to all the features.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.3.15
1
+ 1.3.16
@@ -63,17 +63,26 @@ module ThinkingSphinx
63
63
  # The collection of indexed models. Keep in mind that Rails lazily loads
64
64
  # its classes, so this may not actually be populated with _all_ the models
65
65
  # that have Sphinx indexes.
66
+ @@sphinx_mutex = Mutex.new
67
+ @@context = nil
68
+
66
69
  def self.context
67
- if Thread.current[:thinking_sphinx_context].nil?
68
- Thread.current[:thinking_sphinx_context] = ThinkingSphinx::Context.new
69
- Thread.current[:thinking_sphinx_context].prepare
70
+ if @@context.nil?
71
+ @@sphinx_mutex.synchronize do
72
+ if @@context.nil?
73
+ @@context = ThinkingSphinx::Context.new
74
+ @@context.prepare
75
+ end
76
+ end
70
77
  end
71
78
 
72
- Thread.current[:thinking_sphinx_context]
79
+ @@context
73
80
  end
74
-
81
+
75
82
  def self.reset_context!
76
- Thread.current[:thinking_sphinx_context] = nil
83
+ @@sphinx_mutex.synchronize do
84
+ @@context = nil
85
+ end
77
86
  end
78
87
 
79
88
  def self.unique_id_expression(offset = nil)
@@ -2,28 +2,50 @@ module ThinkingSphinx
2
2
  module ActiveRecord
3
3
  module HasManyAssociation
4
4
  def search(*args)
5
+ options = args.extract_options!
6
+ options[:with] ||= {}
7
+ options[:with].merge! default_filter
8
+
9
+ args << options
10
+ @reflection.klass.search(*args)
11
+ end
12
+
13
+ def method_missing(method, *args, &block)
14
+ if responds_to_scope(method)
15
+ @reflection.klass.
16
+ search(:with => default_filter).
17
+ send(method, *args, &block)
18
+ else
19
+ super
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def attribute_for_foreign_key
5
26
  foreign_key = @reflection.primary_key_name
6
27
  stack = [@reflection.options[:through]].compact
7
28
 
8
- attribute = nil
9
29
  @reflection.klass.define_indexes
10
30
  (@reflection.klass.sphinx_indexes || []).each do |index|
11
31
  attribute = index.attributes.detect { |attrib|
12
32
  attrib.columns.length == 1 &&
13
33
  attrib.columns.first.__name == foreign_key.to_sym
14
34
  }
15
- break if attribute
35
+ return attribute unless attribute.nil?
16
36
  end
17
37
 
18
- raise "Missing Attribute for Foreign Key #{foreign_key}" unless attribute
19
-
20
- options = args.extract_options!
21
- options[:with] ||= {}
22
- options[:with][attribute.unique_name] = @owner.id
23
-
24
- args << options
25
- @reflection.klass.search(*args)
38
+ raise "Missing Attribute for Foreign Key #{foreign_key}"
39
+ end
40
+
41
+ def default_filter
42
+ {attribute_for_foreign_key.unique_name => @owner.id}
43
+ end
44
+
45
+ def responds_to_scope(scope)
46
+ @reflection.klass.respond_to?(:sphinx_scopes) &&
47
+ @reflection.klass.sphinx_scopes.include?(scope)
26
48
  end
27
49
  end
28
50
  end
29
- end
51
+ end
@@ -33,6 +33,10 @@ module ThinkingSphinx
33
33
  "#{@model.quoted_table_name}.#{@model.connection.quote_column_name(column)}"
34
34
  end
35
35
 
36
+ def bigint_pattern
37
+ /bigint/i
38
+ end
39
+
36
40
  protected
37
41
 
38
42
  def connection
@@ -93,19 +93,18 @@ module ThinkingSphinx
93
93
 
94
94
  separator = all_ints? || all_datetimes? || @crc ? ',' : ' '
95
95
 
96
- clause = @columns.collect { |column|
97
- part = column_with_prefix(column)
96
+ clause = columns_with_prefixes.collect { |column|
98
97
  case type
99
98
  when :string
100
- adapter.convert_nulls(part)
99
+ adapter.convert_nulls(column)
101
100
  when :datetime
102
- adapter.cast_to_datetime(part)
101
+ adapter.cast_to_datetime(column)
103
102
  when :multi
104
- part = adapter.cast_to_datetime(part) if is_many_datetimes?
105
- part = adapter.convert_nulls(part, '0') if is_many_ints?
106
- part
103
+ column = adapter.cast_to_datetime(column) if is_many_datetimes?
104
+ column = adapter.convert_nulls(column, '0') if is_many_ints?
105
+ column
107
106
  else
108
- part
107
+ column
109
108
  end
110
109
  }.join(', ')
111
110
 
@@ -123,7 +122,8 @@ module ThinkingSphinx
123
122
  :string => :sql_attr_str2ordinal,
124
123
  :float => :sql_attr_float,
125
124
  :boolean => :sql_attr_bool,
126
- :integer => :sql_attr_uint
125
+ :integer => :sql_attr_uint,
126
+ :bigint => :sql_attr_bigint
127
127
  }[type]
128
128
  end
129
129
 
@@ -301,19 +301,11 @@ WHERE #{@source.index.delta_object.clause(model, true)})
301
301
  is_many? && all_strings?
302
302
  end
303
303
 
304
- def type_from_database
305
- klass = @associations.values.flatten.first ?
306
- @associations.values.flatten.first.reflection.klass : @model
307
-
308
- column = klass.columns.detect { |col|
309
- @columns.collect { |c| c.__name.to_s }.include? col.name
310
- }
311
- column.nil? ? nil : column.type
312
- end
313
-
314
304
  def translated_type_from_database
315
305
  case type_from_db = type_from_database
316
- when :datetime, :string, :float, :boolean, :integer
306
+ when :integer
307
+ integer_type_from_db
308
+ when :datetime, :string, :float, :boolean
317
309
  type_from_db
318
310
  when :decimal
319
311
  :float
@@ -331,6 +323,32 @@ block:
331
323
  end
332
324
  end
333
325
 
326
+ def type_from_database
327
+ column = column_from_db
328
+ column.nil? ? nil : column.type
329
+ end
330
+
331
+ def integer_type_from_db
332
+ column = column_from_db
333
+ return nil if column.nil?
334
+
335
+ case column.sql_type
336
+ when adapter.bigint_pattern
337
+ :bigint
338
+ else
339
+ :integer
340
+ end
341
+ end
342
+
343
+ def column_from_db
344
+ klass = @associations.values.flatten.first ?
345
+ @associations.values.flatten.first.reflection.klass : @model
346
+
347
+ klass.columns.detect { |col|
348
+ @columns.collect { |c| c.__name.to_s }.include? col.name
349
+ }
350
+ end
351
+
334
352
  def all_of_type?(*column_types)
335
353
  @columns.all? { |col|
336
354
  klasses = @associations[col].empty? ? [@model] :
@@ -359,4 +377,4 @@ block:
359
377
  end
360
378
  end
361
379
  end
362
- end
380
+ end
@@ -1,7 +1,7 @@
1
1
  module ThinkingSphinx
2
2
  class AutoVersion
3
3
  def self.detect
4
- version = ThinkingSphinx::Configuration.instance.controller.sphinx_version
4
+ version = ThinkingSphinx::Configuration.instance.version
5
5
  case version
6
6
  when '0.9.8', '0.9.9'
7
7
  require "riddle/#{version}"
@@ -68,6 +68,7 @@ module ThinkingSphinx
68
68
  :app_root, :model_directories, :delayed_job_priority
69
69
 
70
70
  attr_accessor :source_options, :index_options
71
+ attr_accessor :version
71
72
 
72
73
  attr_reader :environment, :configuration, :controller
73
74
 
@@ -114,7 +115,9 @@ module ThinkingSphinx
114
115
  :charset_type => "utf-8"
115
116
  }
116
117
 
118
+ self.version = nil
117
119
  parse_config
120
+ self.version ||= @controller.sphinx_version
118
121
 
119
122
  self
120
123
  end
@@ -69,14 +69,12 @@ module ThinkingSphinx
69
69
  # multiple data values (has_many or has_and_belongs_to_many associations).
70
70
  #
71
71
  def to_select_sql
72
- clause = @columns.collect { |column|
73
- column_with_prefix(column)
74
- }.join(', ')
72
+ clause = columns_with_prefixes.join(', ')
75
73
 
76
74
  clause = adapter.concatenate(clause) if concat_ws?
77
75
  clause = adapter.group_concatenate(clause) if is_many?
78
76
 
79
- "#{adapter.cast_to_string clause } AS #{quote_column(unique_name)}"
77
+ "#{clause} AS #{quote_column(unique_name)}"
80
78
  end
81
79
  end
82
80
  end
@@ -136,10 +136,16 @@ module ThinkingSphinx
136
136
  assoc.has_column?(column.__name) ?
137
137
  "#{quote_with_table(assoc.join.aliased_table_name, column.__name)}" :
138
138
  nil
139
- }.compact.join(', ')
139
+ }.compact
140
140
  end
141
141
  end
142
142
 
143
+ def columns_with_prefixes
144
+ @columns.collect { |column|
145
+ column_with_prefix column
146
+ }.flatten
147
+ end
148
+
143
149
  # Gets a stack of associations for a specific path.
144
150
  #
145
151
  def association_stack(path, parent = nil)
@@ -159,4 +165,4 @@ module ThinkingSphinx
159
165
  assocs
160
166
  end
161
167
  end
162
- end
168
+ end
@@ -73,6 +73,8 @@ module ThinkingSphinx
73
73
  @array = []
74
74
  @options = args.extract_options!
75
75
  @args = args
76
+
77
+ populate if @options[:populate]
76
78
  end
77
79
 
78
80
  def to_a
@@ -129,8 +131,8 @@ module ThinkingSphinx
129
131
  # @param [Symbol] method The method name
130
132
  # @return [Boolean] true if either Search or Array responds to the method.
131
133
  #
132
- def respond_to?(method)
133
- super || @array.respond_to?(method)
134
+ def respond_to?(method, include_private = false)
135
+ super || @array.respond_to?(method, include_private)
134
136
  end
135
137
 
136
138
  # The current page number of the result set. Defaults to 1 if no page was
@@ -2,7 +2,8 @@ module ThinkingSphinx
2
2
  class Source
3
3
  module InternalProperties
4
4
  def add_internal_attributes_and_facets
5
- add_internal_attribute :sphinx_internal_id, :integer, @model.primary_key_for_sphinx.to_sym
5
+ add_internal_attribute :sphinx_internal_id, nil,
6
+ @model.primary_key_for_sphinx.to_sym
6
7
  add_internal_attribute :class_crc, :integer, crc_column, true
7
8
  add_internal_attribute :subclass_crcs, :multi, subclasses_to_s
8
9
  add_internal_attribute :sphinx_deleted, :integer, "0"
@@ -52,4 +52,20 @@ describe 'ThinkingSphinx::ActiveRecord::HasManyAssociation' do
52
52
  @person.friends.search "test"
53
53
  end
54
54
  end
55
- end
55
+
56
+ describe 'filtering sphinx scopes' do
57
+ before :each do
58
+ Friendship.stub!(:search => Friendship)
59
+
60
+ @person = Person.find(:first)
61
+ end
62
+
63
+ it "should add a filter for the attribute in a sphinx scope call" do
64
+ Friendship.should_receive(:search).with do |options|
65
+ options[:with][:person_id].should == @person.id
66
+ end
67
+
68
+ @person.friendships.reverse
69
+ end
70
+ end
71
+ end
@@ -47,47 +47,6 @@ describe ThinkingSphinx::Attribute do
47
47
  end
48
48
  end
49
49
 
50
- describe '#column_with_prefix' do
51
- before :each do
52
- @attribute = ThinkingSphinx::Attribute.new @source, [
53
- ThinkingSphinx::Index::FauxColumn.new(:col_name)
54
- ]
55
- @attribute.columns.each { |col| @attribute.associations[col] = [] }
56
- @attribute.model = Person
57
-
58
- @first_join = Object.new
59
- @first_join.stub!(:aliased_table_name => "tabular")
60
- @second_join = Object.new
61
- @second_join.stub!(:aliased_table_name => "data")
62
-
63
- @first_assoc = ThinkingSphinx::Association.new nil, nil
64
- @first_assoc.stub!(:join => @first_join, :has_column? => true)
65
- @second_assoc = ThinkingSphinx::Association.new nil, nil
66
- @second_assoc.stub!(:join => @second_join, :has_column? => true)
67
- end
68
-
69
- it "should return the column name if the column is a string" do
70
- @attribute.columns = [ThinkingSphinx::Index::FauxColumn.new("string")]
71
- @attribute.send(:column_with_prefix, @attribute.columns.first).should == "string"
72
- end
73
-
74
- it "should return the column with model's table prefix if there's no associations for the column" do
75
- @attribute.send(:column_with_prefix, @attribute.columns.first).should == "`people`.`col_name`"
76
- end
77
-
78
- it "should return the column with its join table prefix if an association exists" do
79
- column = @attribute.columns.first
80
- @attribute.associations[column] = [@first_assoc]
81
- @attribute.send(:column_with_prefix, column).should == "`tabular`.`col_name`"
82
- end
83
-
84
- it "should return multiple columns concatenated if more than one association exists" do
85
- column = @attribute.columns.first
86
- @attribute.associations[column] = [@first_assoc, @second_assoc]
87
- @attribute.send(:column_with_prefix, column).should == "`tabular`.`col_name`, `data`.`col_name`"
88
- end
89
- end
90
-
91
50
  describe '#to_select_sql' do
92
51
  it "should convert a mixture of dates and datetimes to timestamps" do
93
52
  attribute = ThinkingSphinx::Attribute.new(@source,
@@ -99,6 +58,16 @@ describe ThinkingSphinx::Attribute do
99
58
 
100
59
  attribute.to_select_sql.should == "CONCAT_WS(',', UNIX_TIMESTAMP(`friendships`.`created_at`), UNIX_TIMESTAMP(`friendships`.`created_on`)) AS `times`"
101
60
  end
61
+
62
+ it "should handle columns which don't exist for polymorphic joins" do
63
+ attribute = ThinkingSphinx::Attribute.new(@source,
64
+ [ ThinkingSphinx::Index::FauxColumn.new(:team, :name),
65
+ ThinkingSphinx::Index::FauxColumn.new(:team, :league) ],
66
+ :as => :team
67
+ )
68
+
69
+ attribute.to_select_sql.should == "CONCAT_WS(' ', IFNULL(`cricket_teams`.`name`, ''), IFNULL(`football_teams`.`name`, ''), IFNULL(`football_teams`.`league`, '')) AS `team`"
70
+ end
102
71
  end
103
72
 
104
73
  describe '#is_many?' do
@@ -183,15 +152,15 @@ describe ThinkingSphinx::Attribute do
183
152
 
184
153
  it "should return the column type from the database if not :multi or more than one association" do
185
154
  @column.send(:instance_variable_set, :@name, "birthday")
186
- @attribute.send(:type).should == :datetime
155
+ @attribute.type.should == :datetime
187
156
 
188
157
  @attribute.send(:instance_variable_set, :@type, nil)
189
158
  @column.send(:instance_variable_set, :@name, "first_name")
190
- @attribute.send(:type).should == :string
159
+ @attribute.type.should == :string
191
160
 
192
161
  @attribute.send(:instance_variable_set, :@type, nil)
193
162
  @column.send(:instance_variable_set, :@name, "id")
194
- @attribute.send(:type).should == :integer
163
+ @attribute.type.should == :integer
195
164
  end
196
165
 
197
166
  it "should return :multi if the columns return multiple datetimes" do
@@ -200,6 +169,15 @@ describe ThinkingSphinx::Attribute do
200
169
 
201
170
  @attribute.type.should == :multi
202
171
  end
172
+
173
+ it "should return :bigint for 64bit integers" do
174
+ Person.columns.detect { |col|
175
+ col.name == 'id'
176
+ }.stub!(:sql_type => 'BIGINT(20)')
177
+ @column.send(:instance_variable_set, :@name, 'id')
178
+
179
+ @attribute.type.should == :bigint
180
+ end
203
181
  end
204
182
 
205
183
  describe '#all_ints?' do
@@ -3,14 +3,14 @@ require 'spec/spec_helper'
3
3
  describe ThinkingSphinx::AutoVersion do
4
4
  describe '.detect' do
5
5
  before :each do
6
- @controller = ThinkingSphinx::Configuration.instance.controller
6
+ @config = ThinkingSphinx::Configuration.instance
7
7
  end
8
8
 
9
9
  it "should require 0.9.8 if that is the detected version" do
10
10
  ThinkingSphinx::AutoVersion.should_receive(:require).
11
11
  with('riddle/0.9.8')
12
12
 
13
- @controller.stub!(:sphinx_version => '0.9.8')
13
+ @config.stub!(:version => '0.9.8')
14
14
  ThinkingSphinx::AutoVersion.detect
15
15
  end
16
16
 
@@ -18,21 +18,21 @@ describe ThinkingSphinx::AutoVersion do
18
18
  ThinkingSphinx::AutoVersion.should_receive(:require).
19
19
  with('riddle/0.9.9')
20
20
 
21
- @controller.stub!(:sphinx_version => '0.9.9')
21
+ @config.stub!(:version => '0.9.9')
22
22
  ThinkingSphinx::AutoVersion.detect
23
23
  end
24
24
 
25
25
  it "should output a warning if the detected version is something else" do
26
26
  STDERR.should_receive(:puts)
27
27
 
28
- @controller.stub!(:sphinx_version => '0.9.7')
28
+ @config.stub!(:version => '0.9.7')
29
29
  ThinkingSphinx::AutoVersion.detect
30
30
  end
31
31
 
32
32
  it "should output a warning if the version cannot be determined" do
33
33
  STDERR.should_receive(:puts)
34
34
 
35
- @controller.stub!(:sphinx_version => nil)
35
+ @config.stub!(:version => nil)
36
36
  ThinkingSphinx::AutoVersion.detect
37
37
  end
38
38
  end
@@ -37,6 +37,34 @@ describe ThinkingSphinx::Configuration do
37
37
  ThinkingSphinx::Configuration.environment.should == "development"
38
38
  end
39
39
  end
40
+
41
+ describe '#version' do
42
+ before :each do
43
+ @config = ThinkingSphinx::Configuration.instance
44
+ @config.reset
45
+ end
46
+
47
+ it "should use the given version from sphinx.yml if there is one" do
48
+ open("#{RAILS_ROOT}/config/sphinx.yml", "w") do |f|
49
+ f.write YAML.dump({'development' => {'version' => '0.9.7'}})
50
+ end
51
+ @config.reset
52
+
53
+ @config.version.should == '0.9.7'
54
+
55
+ FileUtils.rm "#{RAILS_ROOT}/config/sphinx.yml"
56
+ end
57
+
58
+ it "should detect the version from Riddle otherwise" do
59
+ controller = @config.controller
60
+ controller.stub!(:sphinx_version => '0.9.6')
61
+
62
+ Riddle::Controller.stub!(:new => controller)
63
+ @config.reset
64
+
65
+ @config.version.should == '0.9.6'
66
+ end
67
+ end
40
68
 
41
69
  describe "parse_config method" do
42
70
  before :each do
@@ -77,47 +77,6 @@ describe ThinkingSphinx::Field do
77
77
  end
78
78
  end
79
79
 
80
- describe "column_with_prefix method" do
81
- before :each do
82
- @field = ThinkingSphinx::Field.new @source, [
83
- ThinkingSphinx::Index::FauxColumn.new(:col_name)
84
- ]
85
- @field.columns.each { |col| @field.associations[col] = [] }
86
- @field.model = Person
87
-
88
- @first_join = Object.new
89
- @first_join.stub!(:aliased_table_name => "tabular")
90
- @second_join = Object.new
91
- @second_join.stub!(:aliased_table_name => "data")
92
-
93
- @first_assoc = ThinkingSphinx::Association.new nil, nil
94
- @first_assoc.stub!(:join => @first_join, :has_column? => true)
95
- @second_assoc = ThinkingSphinx::Association.new nil, nil
96
- @second_assoc.stub!(:join => @second_join, :has_column? => true)
97
- end
98
-
99
- it "should return the column name if the column is a string" do
100
- @field.columns = [ThinkingSphinx::Index::FauxColumn.new("string")]
101
- @field.send(:column_with_prefix, @field.columns.first).should == "string"
102
- end
103
-
104
- it "should return the column with model's table prefix if there's no associations for the column" do
105
- @field.send(:column_with_prefix, @field.columns.first).should == "`people`.`col_name`"
106
- end
107
-
108
- it "should return the column with its join table prefix if an association exists" do
109
- column = @field.columns.first
110
- @field.associations[column] = [@first_assoc]
111
- @field.send(:column_with_prefix, column).should == "`tabular`.`col_name`"
112
- end
113
-
114
- it "should return multiple columns concatenated if more than one association exists" do
115
- column = @field.columns.first
116
- @field.associations[column] = [@first_assoc, @second_assoc]
117
- @field.send(:column_with_prefix, column).should == "`tabular`.`col_name`, `data`.`col_name`"
118
- end
119
- end
120
-
121
80
  describe "is_many? method" do
122
81
  before :each do
123
82
  @assoc_a = stub('assoc', :is_many? => true)
@@ -43,7 +43,12 @@ describe ThinkingSphinx::Search do
43
43
 
44
44
  it "should be true once the client request has been made" do
45
45
  @search.first
46
- @search.populated?.should be_true
46
+ @search.should be_populated
47
+ end
48
+
49
+ it "should be populated if :populate is set to true" do
50
+ search = ThinkingSphinx::Search.new(:populate => true)
51
+ search.should be_populated
47
52
  end
48
53
  end
49
54
 
@@ -28,8 +28,8 @@ Jeweler::Tasks.new do |gem|
28
28
  ]
29
29
 
30
30
  gem.add_dependency 'activerecord', '>= 1.15.6'
31
- gem.add_dependency 'riddle', '>= 1.0.9'
32
- gem.add_dependency 'after_commit', '>= 1.0.5'
31
+ gem.add_dependency 'riddle', '>= 1.0.10'
32
+ gem.add_dependency 'after_commit', '>= 1.0.6'
33
33
 
34
34
  gem.post_install_message = <<-MESSAGE
35
35
  If you're upgrading, you should read this:
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: thinking-sphinx
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.15
4
+ version: 1.3.16
5
5
  platform: ruby
6
6
  authors:
7
7
  - Pat Allan
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2010-01-29 00:00:00 +08:00
12
+ date: 2010-02-16 00:00:00 +11:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -30,7 +30,7 @@ dependencies:
30
30
  requirements:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: 1.0.9
33
+ version: 1.0.10
34
34
  version:
35
35
  - !ruby/object:Gem::Dependency
36
36
  name: after_commit
@@ -40,7 +40,7 @@ dependencies:
40
40
  requirements:
41
41
  - - ">="
42
42
  - !ruby/object:Gem::Version
43
- version: 1.0.5
43
+ version: 1.0.6
44
44
  version:
45
45
  description: A concise and easy-to-use Ruby library that connects ActiveRecord to the Sphinx search daemon, managing configuration, indexing and searching.
46
46
  email: pat@freelancing-gods.com