thinking-sphinx 3.0.0.pre → 3.0.0.rc

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.
Files changed (61) hide show
  1. data/Gemfile +4 -1
  2. data/HISTORY +16 -0
  3. data/README.textile +41 -23
  4. data/lib/thinking_sphinx.rb +9 -0
  5. data/lib/thinking_sphinx/active_record.rb +1 -0
  6. data/lib/thinking_sphinx/active_record/attribute/sphinx_presenter.rb +20 -3
  7. data/lib/thinking_sphinx/active_record/base.rb +15 -2
  8. data/lib/thinking_sphinx/active_record/callbacks/delete_callbacks.rb +2 -2
  9. data/lib/thinking_sphinx/active_record/callbacks/update_callbacks.rb +1 -1
  10. data/lib/thinking_sphinx/active_record/field.rb +5 -0
  11. data/lib/thinking_sphinx/active_record/index.rb +10 -2
  12. data/lib/thinking_sphinx/active_record/interpreter.rb +9 -0
  13. data/lib/thinking_sphinx/active_record/property.rb +4 -0
  14. data/lib/thinking_sphinx/active_record/property_query.rb +112 -0
  15. data/lib/thinking_sphinx/active_record/sql_source.rb +10 -8
  16. data/lib/thinking_sphinx/active_record/sql_source/template.rb +3 -1
  17. data/lib/thinking_sphinx/configuration.rb +21 -24
  18. data/lib/thinking_sphinx/connection.rb +71 -0
  19. data/lib/thinking_sphinx/core.rb +1 -0
  20. data/lib/thinking_sphinx/core/field.rb +9 -0
  21. data/lib/thinking_sphinx/core/index.rb +8 -3
  22. data/lib/thinking_sphinx/deltas.rb +3 -0
  23. data/lib/thinking_sphinx/deltas/default_delta.rb +1 -1
  24. data/lib/thinking_sphinx/excerpter.rb +1 -1
  25. data/lib/thinking_sphinx/frameworks.rb +9 -0
  26. data/lib/thinking_sphinx/frameworks/plain.rb +8 -0
  27. data/lib/thinking_sphinx/frameworks/rails.rb +9 -0
  28. data/lib/thinking_sphinx/index.rb +5 -1
  29. data/lib/thinking_sphinx/real_time/callbacks/real_time_callbacks.rb +2 -2
  30. data/lib/thinking_sphinx/real_time/field.rb +2 -0
  31. data/lib/thinking_sphinx/real_time/property.rb +2 -0
  32. data/lib/thinking_sphinx/scopes.rb +6 -0
  33. data/lib/thinking_sphinx/search.rb +4 -0
  34. data/lib/thinking_sphinx/search/batch_inquirer.rb +1 -9
  35. data/lib/thinking_sphinx/search/merger.rb +3 -1
  36. data/lib/thinking_sphinx/sinatra.rb +5 -0
  37. data/lib/thinking_sphinx/test.rb +2 -2
  38. data/spec/acceptance/index_options_spec.rb +87 -0
  39. data/spec/acceptance/searching_within_a_model_spec.rb +7 -0
  40. data/spec/acceptance/specifying_sql_spec.rb +277 -0
  41. data/spec/acceptance/support/sphinx_controller.rb +4 -3
  42. data/spec/fixtures/database.yml +4 -0
  43. data/spec/internal/app/indices/admin_person_index.rb +3 -0
  44. data/spec/internal/app/models/admin/person.rb +3 -0
  45. data/spec/internal/app/models/book.rb +2 -0
  46. data/spec/internal/app/models/genre.rb +3 -0
  47. data/spec/internal/db/schema.rb +14 -0
  48. data/spec/thinking_sphinx/active_record/base_spec.rb +20 -14
  49. data/spec/thinking_sphinx/active_record/callbacks/delete_callbacks_spec.rb +6 -5
  50. data/spec/thinking_sphinx/active_record/callbacks/update_callbacks_spec.rb +6 -5
  51. data/spec/thinking_sphinx/active_record/index_spec.rb +1 -1
  52. data/spec/thinking_sphinx/active_record/sql_source_spec.rb +35 -27
  53. data/spec/thinking_sphinx/configuration_spec.rb +8 -45
  54. data/spec/thinking_sphinx/deltas/default_delta_spec.rb +4 -5
  55. data/spec/thinking_sphinx/deltas_spec.rb +6 -0
  56. data/spec/thinking_sphinx/excerpter_spec.rb +1 -2
  57. data/spec/thinking_sphinx/index_spec.rb +23 -10
  58. data/spec/thinking_sphinx/real_time/callbacks/real_time_callbacks_spec.rb +4 -4
  59. data/spec/thinking_sphinx/scopes_spec.rb +7 -0
  60. data/thinking-sphinx.gemspec +2 -3
  61. metadata +66 -26
@@ -13,31 +13,37 @@ describe ThinkingSphinx::ActiveRecord::Base do
13
13
  def self.name; 'SubModel'; end
14
14
  end
15
15
  }
16
- let(:search) { double('search', :options => {})}
17
16
 
18
17
  describe '.search' do
19
- before :each do
20
- ThinkingSphinx.stub :search => search
21
- end
22
-
23
18
  it "returns a new search object" do
24
- model.search.should == search
19
+ model.search.should be_a(ThinkingSphinx::Search)
25
20
  end
26
21
 
27
- it "passes through arguments to the search object initializer" do
28
- ThinkingSphinx.should_receive(:search).with('pancakes', anything)
29
-
30
- model.search 'pancakes'
22
+ it "passes through arguments to the search object" do
23
+ model.search('pancakes').query.should == 'pancakes'
31
24
  end
32
25
 
33
26
  it "scopes the search to a given model" do
34
27
  model.search('pancakes').options[:classes].should == [model]
35
28
  end
36
-
29
+
37
30
  it "merges the :classes option with the model" do
38
- search_options = {:classes=>[sub_model]}
39
- search.stub :options => search_options
40
- model.search('pancakes', search_options).options[:classes].should == [sub_model, model]
31
+ model.search('pancakes', :classes => [sub_model]).
32
+ options[:classes].should == [sub_model, model]
33
+ end
34
+
35
+ it "applies the default scope if there is one" do
36
+ model.stub :default_sphinx_scope => :default,
37
+ :sphinx_scopes => {:default => Proc.new { {:order => :created_at} }}
38
+
39
+ model.search.options[:order].should == :created_at
40
+ end
41
+
42
+ it "does not apply a default scope if one is not set" do
43
+ model.stub :default_sphinx_scope => nil,
44
+ :default => {:order => :created_at}
45
+
46
+ model.search.options[:order].should be_nil
41
47
  end
42
48
  end
43
49
 
@@ -31,9 +31,9 @@ describe ThinkingSphinx::ActiveRecord::Callbacks::DeleteCallbacks do
31
31
  end
32
32
 
33
33
  describe '#after_destroy' do
34
- let(:config) { double('config', :connection => connection,
35
- :indices_for_references => [index], :preload_indices => true) }
36
- let(:connection) { double('connection', :query => nil) }
34
+ let(:config) { double('config', :indices_for_references => [index],
35
+ :preload_indices => true) }
36
+ let(:connection) { double('connection', :execute => nil) }
37
37
  let(:index) {
38
38
  double('index', :name => 'foo_core', :document_id_for_key => 14)
39
39
  }
@@ -41,11 +41,12 @@ describe ThinkingSphinx::ActiveRecord::Callbacks::DeleteCallbacks do
41
41
 
42
42
  before :each do
43
43
  ThinkingSphinx::Configuration.stub :instance => config
44
+ ThinkingSphinx::Connection.stub :new => connection
44
45
  Riddle::Query.stub :update => 'UPDATE STATEMENT'
45
46
  end
46
47
 
47
48
  it "updates the deleted flag to false" do
48
- connection.should_receive(:query).with('UPDATE STATEMENT')
49
+ connection.should_receive(:execute).with('UPDATE STATEMENT')
49
50
 
50
51
  callbacks.after_destroy
51
52
  end
@@ -72,7 +73,7 @@ describe ThinkingSphinx::ActiveRecord::Callbacks::DeleteCallbacks do
72
73
  end
73
74
 
74
75
  it "doesn't care about Sphinx errors" do
75
- connection.stub(:query).and_raise(Mysql2::Error.new(''))
76
+ connection.stub(:execute).and_raise(Mysql2::Error.new(''))
76
77
 
77
78
  lambda { callbacks.after_destroy }.should_not raise_error
78
79
  end
@@ -15,10 +15,10 @@ describe ThinkingSphinx::ActiveRecord::Callbacks::UpdateCallbacks do
15
15
  ThinkingSphinx::ActiveRecord::Callbacks::UpdateCallbacks.new instance }
16
16
  let(:instance) { double('instance', :class => klass, :id => 2) }
17
17
  let(:klass) { double(:name => 'Article') }
18
- let(:configuration) { double('configuration', :connection => connection,
18
+ let(:configuration) { double('configuration',
19
19
  :settings => {'attribute_updates' => true},
20
20
  :indices_for_references => [index]) }
21
- let(:connection) { double('connection', :query => '') }
21
+ let(:connection) { double('connection', :execute => '') }
22
22
  let(:index) { double('index', :name => 'article_core',
23
23
  :sources => [source], :document_id_for_key => 3) }
24
24
  let(:source) { double('source', :attributes => []) }
@@ -26,6 +26,7 @@ describe ThinkingSphinx::ActiveRecord::Callbacks::UpdateCallbacks do
26
26
  before :each do
27
27
  stub_const 'ThinkingSphinx::Configuration',
28
28
  double(:instance => configuration)
29
+ stub_const 'ThinkingSphinx::Connection', double(:new => connection)
29
30
  stub_const 'Riddle::Query', double(:update => 'SphinxQL')
30
31
 
31
32
  source.attributes.replace([
@@ -42,7 +43,7 @@ describe ThinkingSphinx::ActiveRecord::Callbacks::UpdateCallbacks do
42
43
  it "does not send any updates to Sphinx if updates are disabled" do
43
44
  configuration.settings['attribute_updates'] = false
44
45
 
45
- connection.should_not_receive(:query)
46
+ connection.should_not_receive(:execute)
46
47
 
47
48
  callbacks.after_update
48
49
  end
@@ -55,13 +56,13 @@ describe ThinkingSphinx::ActiveRecord::Callbacks::UpdateCallbacks do
55
56
  end
56
57
 
57
58
  it "sends the update query through to Sphinx" do
58
- connection.should_receive(:query).with('SphinxQL')
59
+ connection.should_receive(:execute).with('SphinxQL')
59
60
 
60
61
  callbacks.after_update
61
62
  end
62
63
 
63
64
  it "doesn't care if the update fails at Sphinx's end" do
64
- connection.stub(:query).and_raise(Mysql2::Error.new(''))
65
+ connection.stub(:execute).and_raise(Mysql2::Error.new(''))
65
66
 
66
67
  lambda { callbacks.after_update }.should_not raise_error
67
68
  end
@@ -196,7 +196,7 @@ describe ThinkingSphinx::ActiveRecord::Index do
196
196
 
197
197
  describe '#render' do
198
198
  it "interprets the provided definition" do
199
- index.should_receive(:interpret_definition!)
199
+ index.should_receive(:interpret_definition!).at_least(:once)
200
200
 
201
201
  begin
202
202
  index.render
@@ -7,7 +7,8 @@ describe ThinkingSphinx::ActiveRecord::SQLSource do
7
7
  double('connection', :instance_variable_get => db_config) }
8
8
  let(:db_config) { {:host => 'localhost', :user => 'root',
9
9
  :database => 'default'} }
10
- let(:source) { ThinkingSphinx::ActiveRecord::SQLSource.new(model) }
10
+ let(:source) { ThinkingSphinx::ActiveRecord::SQLSource.new(model,
11
+ :position => 3) }
11
12
  let(:adapter) { double('adapter') }
12
13
 
13
14
  before :each do
@@ -86,15 +87,16 @@ describe ThinkingSphinx::ActiveRecord::SQLSource do
86
87
  end
87
88
 
88
89
  it "uses the inheritance column if it exists for the sphinx class field" do
90
+ adapter.stub :quoted_table_name => '"users"', :quote => '"type"'
89
91
  adapter.stub(:convert_nulls) { |clause, default|
90
92
  "ifnull(#{clause}, #{default})"
91
93
  }
92
- model.stub :column_names => ['type']
94
+ model.stub :column_names => ['type'], :sti_name => 'User'
93
95
 
94
96
  source.fields.detect { |field|
95
97
  field.name == 'sphinx_internal_class'
96
98
  }.columns.first.__name.
97
- should == "ifnull(type, 'User')"
99
+ should == "ifnull(\"users\".\"type\", 'User')"
98
100
  end
99
101
 
100
102
  it "marks the internal class field as a facet" do
@@ -105,29 +107,15 @@ describe ThinkingSphinx::ActiveRecord::SQLSource do
105
107
  end
106
108
 
107
109
  describe '#name' do
108
- it "defaults to the model name downcased with the core suffix" do
109
- source.name.should == 'user_core'
110
+ it "defaults to the model name downcased with the given position" do
111
+ source.name.should == 'user_3'
110
112
  end
111
113
 
112
- it "changes the suffix to delta if set to true" do
114
+ it "allows for custom names, but adds the position suffix" do
113
115
  source = ThinkingSphinx::ActiveRecord::SQLSource.new model,
114
- :delta? => true
115
-
116
- source.name.should == 'user_delta'
117
- end
118
-
119
- it "allows for custom names, but adds the core suffix" do
120
- source = ThinkingSphinx::ActiveRecord::SQLSource.new model,
121
- :name => 'people'
122
-
123
- source.name.should == 'people_core'
124
- end
125
-
126
- it "allows for custom names and adds the delta suffix if a delta source" do
127
- source = ThinkingSphinx::ActiveRecord::SQLSource.new model,
128
- :name => 'people', :delta? => true
116
+ :name => 'people', :position => 2
129
117
 
130
- source.name.should == 'people_delta'
118
+ source.name.should == 'people_2'
131
119
  end
132
120
  end
133
121
 
@@ -258,8 +246,8 @@ describe ThinkingSphinx::ActiveRecord::SQLSource do
258
246
  end
259
247
 
260
248
  it "adds fields with attributes to sql_field_string" do
261
- source.fields << double('field',
262
- :name => 'title', :with_attribute? => true, :file? => false)
249
+ source.fields << double('field', :name => 'title', :source_type => nil,
250
+ :with_attribute? => true, :file? => false, :wordcount? => false)
263
251
 
264
252
  source.render
265
253
 
@@ -267,15 +255,35 @@ describe ThinkingSphinx::ActiveRecord::SQLSource do
267
255
  end
268
256
 
269
257
  it "adds any joined or file fields" do
270
- source.fields << double('field',
271
- :name => 'title', :file? => true, :with_attribute? => false)
258
+ source.fields << double('field', :name => 'title', :file? => true,
259
+ :with_attribute? => false, :wordcount? => false, :source_type => nil)
272
260
 
273
261
  source.render
274
262
 
275
263
  source.sql_file_field.should include('title')
276
264
  end
277
265
 
278
- it "adds any joined fields"
266
+ it "adds wordcounted fields to sql_field_str2wordcount" do
267
+ source.fields << double('field', :name => 'title', :source_type => nil,
268
+ :with_attribute? => false, :file? => false, :wordcount? => true)
269
+
270
+ source.render
271
+
272
+ source.sql_field_str2wordcount.should include('title')
273
+ end
274
+
275
+ it "adds any joined fields" do
276
+ ThinkingSphinx::ActiveRecord::PropertyQuery.stub(
277
+ :new => double(:to_s => 'query for title')
278
+ )
279
+ source.fields << double('field', :name => 'title',
280
+ :source_type => :query, :with_attribute? => false, :file? => false,
281
+ :wordcount? => false)
282
+
283
+ source.render
284
+
285
+ source.sql_joined_field.should include('query for title')
286
+ end
279
287
 
280
288
  it "adds integer attributes to sql_attr_uint" do
281
289
  source.attributes << double('attribute')
@@ -26,46 +26,7 @@ describe ThinkingSphinx::Configuration do
26
26
  describe '#configuration_file' do
27
27
  it "uses the Rails environment in the configuration file name" do
28
28
  config.configuration_file.
29
- should == Rails.root.join('config', 'test.sphinx.conf')
30
- end
31
- end
32
-
33
- describe '#connection' do
34
- let(:connection) { double('connection') }
35
-
36
- before :each do
37
- Mysql2::Client.stub :new => connection
38
- end
39
-
40
- it "connects using the searchd address and port" do
41
- config.searchd.stub :address => '127.0.0.1', :mysql41 => 121
42
-
43
- Mysql2::Client.should_receive(:new).with(
44
- :host => '127.0.0.1',
45
- :port => 121,
46
- :flags => Mysql2::Client::MULTI_STATEMENTS
47
- ).and_return(connection)
48
-
49
- config.connection
50
- end
51
-
52
- it "returns the connection" do
53
- config.connection.should == connection
54
- end
55
-
56
- it "respects any connection options in the settings" do
57
- write_configuration(
58
- 'connection_options' => {:username => 'pat', :port => 9312}
59
- )
60
-
61
- Mysql2::Client.should_receive(:new).with(
62
- :host => '127.0.0.1',
63
- :port => 9312,
64
- :flags => Mysql2::Client::MULTI_STATEMENTS,
65
- :username => 'pat'
66
- ).and_return(connection)
67
-
68
- config.connection
29
+ should == File.join(Rails.root, 'config', 'test.sphinx.conf')
69
30
  end
70
31
  end
71
32
 
@@ -97,7 +58,7 @@ describe ThinkingSphinx::Configuration do
97
58
 
98
59
  describe '#index_paths' do
99
60
  it "uses app/indices in the Rails app by default" do
100
- config.index_paths.should == [Rails.root.join('app', 'indices')]
61
+ config.index_paths.should == [File.join(Rails.root, 'app', 'indices')]
101
62
  end
102
63
  end
103
64
 
@@ -113,23 +74,25 @@ describe ThinkingSphinx::Configuration do
113
74
 
114
75
  describe '#indices_location' do
115
76
  it "stores index files in db/sphinx/ENVIRONMENT" do
116
- config.indices_location.should == Rails.root.join('db', 'sphinx', 'test')
77
+ config.indices_location.
78
+ should == File.join(Rails.root, 'db', 'sphinx', 'test')
117
79
  end
118
80
  end
119
81
 
120
82
  describe '#initialize' do
121
83
  it "sets the daemon pid file within log for the Rails app" do
122
84
  config.searchd.pid_file.
123
- should == Rails.root.join('log', 'test.sphinx.pid')
85
+ should == File.join(Rails.root, 'log', 'test.sphinx.pid')
124
86
  end
125
87
 
126
88
  it "sets the daemon log within log for the Rails app" do
127
- config.searchd.log.should == Rails.root.join('log', 'test.searchd.log')
89
+ config.searchd.log.
90
+ should == File.join(Rails.root, 'log', 'test.searchd.log')
128
91
  end
129
92
 
130
93
  it "sets the query log within log for the Rails app" do
131
94
  config.searchd.query_log.
132
- should == Rails.root.join('log', 'test.searchd.query.log')
95
+ should == File.join(Rails.root, 'log', 'test.searchd.query.log')
133
96
  end
134
97
  end
135
98
 
@@ -19,19 +19,18 @@ describe ThinkingSphinx::Deltas::DefaultDelta do
19
19
  end
20
20
 
21
21
  describe '#delete' do
22
- let(:config) { double('config', :connection => connection) }
23
- let(:connection) { double('connection', :query => nil) }
22
+ let(:connection) { double('connection', :execute => nil) }
24
23
  let(:index) { double('index', :name => 'foo_core',
25
24
  :document_id_for_key => 14) }
26
25
  let(:instance) { double('instance', :id => 7) }
27
26
 
28
27
  before :each do
29
- ThinkingSphinx::Configuration.stub :instance => config
28
+ ThinkingSphinx::Connection.stub :new => connection
30
29
  Riddle::Query.stub :update => 'UPDATE STATEMENT'
31
30
  end
32
31
 
33
32
  it "updates the deleted flag to false" do
34
- connection.should_receive(:query).with('UPDATE STATEMENT')
33
+ connection.should_receive(:execute).with('UPDATE STATEMENT')
35
34
 
36
35
  delta.delete index, instance
37
36
  end
@@ -58,7 +57,7 @@ describe ThinkingSphinx::Deltas::DefaultDelta do
58
57
  end
59
58
 
60
59
  it "doesn't care about Sphinx errors" do
61
- connection.stub(:query).and_raise(Mysql2::Error.new(''))
60
+ connection.stub(:execute).and_raise(Mysql2::Error.new(''))
62
61
 
63
62
  lambda { delta.delete index, instance }.should_not raise_error
64
63
  end
@@ -11,6 +11,12 @@ describe ThinkingSphinx::Deltas do
11
11
  klass = Class.new
12
12
  ThinkingSphinx::Deltas.processor_for(klass).should == klass
13
13
  end
14
+
15
+ it "instantiates a class from the name as a string" do
16
+ ThinkingSphinx::Deltas.
17
+ processor_for('ThinkingSphinx::Deltas::DefaultDelta').
18
+ should == ThinkingSphinx::Deltas::DefaultDelta
19
+ end
14
20
  end
15
21
 
16
22
  describe '.suspend' do
@@ -2,13 +2,12 @@ require 'spec_helper'
2
2
 
3
3
  describe ThinkingSphinx::Excerpter do
4
4
  let(:excerpter) { ThinkingSphinx::Excerpter.new('index', 'all words') }
5
- let(:config) { double('config', :connection => connection) }
6
5
  let(:connection) {
7
6
  double('connection', :query => [{'snippet' => 'some highlighted words'}])
8
7
  }
9
8
 
10
9
  before :each do
11
- ThinkingSphinx::Configuration.stub :instance => config
10
+ ThinkingSphinx::Connection.stub :new => connection
12
11
  Riddle::Query.stub :snippets => 'CALL SNIPPETS'
13
12
  end
14
13
 
@@ -1,14 +1,15 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe ThinkingSphinx::Index do
4
+ let(:configuration) { Struct.new(:indices, :settings).new([], {}) }
5
+
6
+ before :each do
7
+ ThinkingSphinx::Configuration.stub :instance => configuration
8
+ end
9
+
4
10
  describe '.define' do
5
11
  let(:index) { double('index', :definition_block= => nil) }
6
- let(:config) { Struct.new(:indices).new([]) }
7
-
8
- before :each do
9
- ThinkingSphinx::Configuration.stub :instance => config
10
- end
11
-
12
+
12
13
  context 'with ActiveRecord' do
13
14
  before :each do
14
15
  ThinkingSphinx::ActiveRecord::Index.stub :new => index
@@ -29,7 +30,7 @@ describe ThinkingSphinx::Index do
29
30
  it "adds the index to the collection of indices" do
30
31
  ThinkingSphinx::Index.define(:user, :with => :active_record)
31
32
 
32
- config.indices.should include(index)
33
+ configuration.indices.should include(index)
33
34
  end
34
35
 
35
36
  it "sets the block in the index" do
@@ -73,8 +74,8 @@ describe ThinkingSphinx::Index do
73
74
  :with => :active_record,
74
75
  :delta => true
75
76
 
76
- config.indices.should include(index)
77
- config.indices.should include(delta_index)
77
+ configuration.indices.should include(index)
78
+ configuration.indices.should include(delta_index)
78
79
  end
79
80
 
80
81
  it "sets the block in the index" do
@@ -110,7 +111,7 @@ describe ThinkingSphinx::Index do
110
111
  it "adds the index to the collection of indices" do
111
112
  ThinkingSphinx::Index.define(:user, :with => :real_time)
112
113
 
113
- config.indices.should include(index)
114
+ configuration.indices.should include(index)
114
115
  end
115
116
 
116
117
  it "sets the block in the index" do
@@ -122,4 +123,16 @@ describe ThinkingSphinx::Index do
122
123
  end
123
124
  end
124
125
  end
126
+
127
+ describe '#initialize' do
128
+ it "is fine with no defaults from settings" do
129
+ ThinkingSphinx::Index.new(:user, {}).options.should == {}
130
+ end
131
+
132
+ it "respects defaults from settings" do
133
+ configuration.settings['index_options'] = {'delta' => true}
134
+
135
+ ThinkingSphinx::Index.new(:user, {}).options.should == {:delta => true}
136
+ end
137
+ end
125
138
  end