thinking-sphinx 1.5.0 → 2.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (104) hide show
  1. data/README.textile +15 -48
  2. data/VERSION +1 -0
  3. data/features/attribute_transformation.feature +7 -7
  4. data/features/attribute_updates.feature +16 -18
  5. data/features/deleting_instances.feature +13 -16
  6. data/features/excerpts.feature +0 -8
  7. data/features/facets.feature +19 -25
  8. data/features/handling_edits.feature +20 -25
  9. data/features/searching_across_models.feature +1 -1
  10. data/features/searching_by_index.feature +5 -6
  11. data/features/searching_by_model.feature +29 -29
  12. data/features/sphinx_scopes.feature +0 -26
  13. data/features/step_definitions/common_steps.rb +6 -18
  14. data/features/step_definitions/scope_steps.rb +0 -4
  15. data/features/step_definitions/search_steps.rb +4 -9
  16. data/features/support/env.rb +10 -3
  17. data/features/thinking_sphinx/db/fixtures/alphas.rb +10 -8
  18. data/features/thinking_sphinx/db/fixtures/cats.rb +1 -1
  19. data/features/thinking_sphinx/db/fixtures/dogs.rb +1 -1
  20. data/features/thinking_sphinx/db/fixtures/foxes.rb +1 -1
  21. data/features/thinking_sphinx/db/fixtures/people.rb +1 -1
  22. data/features/thinking_sphinx/db/fixtures/posts.rb +1 -5
  23. data/features/thinking_sphinx/db/migrations/create_posts.rb +0 -1
  24. data/features/thinking_sphinx/models/alpha.rb +0 -1
  25. data/features/thinking_sphinx/models/beta.rb +0 -5
  26. data/features/thinking_sphinx/models/developer.rb +1 -6
  27. data/features/thinking_sphinx/models/music.rb +1 -3
  28. data/features/thinking_sphinx/models/person.rb +1 -2
  29. data/features/thinking_sphinx/models/post.rb +0 -1
  30. data/lib/cucumber/thinking_sphinx/external_world.rb +4 -8
  31. data/lib/cucumber/thinking_sphinx/internal_world.rb +27 -36
  32. data/lib/thinking_sphinx.rb +60 -132
  33. data/lib/thinking_sphinx/active_record.rb +98 -124
  34. data/lib/thinking_sphinx/active_record/attribute_updates.rb +13 -17
  35. data/lib/thinking_sphinx/active_record/delta.rb +15 -21
  36. data/lib/thinking_sphinx/active_record/has_many_association.rb +23 -16
  37. data/lib/thinking_sphinx/active_record/scopes.rb +0 -18
  38. data/lib/thinking_sphinx/adapters/abstract_adapter.rb +15 -63
  39. data/lib/thinking_sphinx/adapters/mysql_adapter.rb +0 -4
  40. data/lib/thinking_sphinx/adapters/postgresql_adapter.rb +24 -65
  41. data/lib/thinking_sphinx/association.rb +11 -36
  42. data/lib/thinking_sphinx/attribute.rb +85 -92
  43. data/lib/thinking_sphinx/auto_version.rb +3 -21
  44. data/lib/thinking_sphinx/class_facet.rb +3 -8
  45. data/lib/thinking_sphinx/configuration.rb +58 -114
  46. data/lib/thinking_sphinx/context.rb +20 -22
  47. data/lib/thinking_sphinx/core/array.rb +13 -0
  48. data/lib/thinking_sphinx/deltas.rb +0 -2
  49. data/lib/thinking_sphinx/deltas/default_delta.rb +22 -18
  50. data/lib/thinking_sphinx/deploy/capistrano.rb +31 -30
  51. data/lib/thinking_sphinx/excerpter.rb +1 -2
  52. data/lib/thinking_sphinx/facet.rb +35 -45
  53. data/lib/thinking_sphinx/facet_search.rb +24 -58
  54. data/lib/thinking_sphinx/field.rb +0 -18
  55. data/lib/thinking_sphinx/index.rb +36 -38
  56. data/lib/thinking_sphinx/index/builder.rb +59 -74
  57. data/lib/thinking_sphinx/property.rb +45 -66
  58. data/lib/thinking_sphinx/railtie.rb +35 -0
  59. data/lib/thinking_sphinx/search.rb +250 -506
  60. data/lib/thinking_sphinx/source.rb +31 -50
  61. data/lib/thinking_sphinx/source/internal_properties.rb +3 -8
  62. data/lib/thinking_sphinx/source/sql.rb +31 -71
  63. data/lib/thinking_sphinx/tasks.rb +27 -48
  64. data/spec/thinking_sphinx/active_record/delta_spec.rb +41 -36
  65. data/spec/thinking_sphinx/active_record/has_many_association_spec.rb +0 -96
  66. data/spec/thinking_sphinx/active_record/scopes_spec.rb +29 -29
  67. data/spec/thinking_sphinx/active_record_spec.rb +169 -140
  68. data/spec/thinking_sphinx/association_spec.rb +2 -20
  69. data/spec/thinking_sphinx/attribute_spec.rb +97 -101
  70. data/spec/thinking_sphinx/auto_version_spec.rb +11 -75
  71. data/spec/thinking_sphinx/configuration_spec.rb +62 -63
  72. data/spec/thinking_sphinx/context_spec.rb +66 -66
  73. data/spec/thinking_sphinx/facet_search_spec.rb +99 -99
  74. data/spec/thinking_sphinx/facet_spec.rb +4 -30
  75. data/spec/thinking_sphinx/field_spec.rb +3 -17
  76. data/spec/thinking_sphinx/index/builder_spec.rb +132 -169
  77. data/spec/thinking_sphinx/index_spec.rb +39 -45
  78. data/spec/thinking_sphinx/search_methods_spec.rb +33 -37
  79. data/spec/thinking_sphinx/search_spec.rb +269 -491
  80. data/spec/thinking_sphinx/source_spec.rb +48 -62
  81. data/spec/thinking_sphinx_spec.rb +49 -49
  82. data/tasks/distribution.rb +46 -0
  83. data/tasks/testing.rb +74 -0
  84. metadata +123 -199
  85. data/features/field_sorting.feature +0 -18
  86. data/features/thinking_sphinx/db/.gitignore +0 -1
  87. data/features/thinking_sphinx/db/fixtures/post_keywords.txt +0 -1
  88. data/features/thinking_sphinx/models/andrew.rb +0 -17
  89. data/lib/thinking-sphinx.rb +0 -1
  90. data/lib/thinking_sphinx/active_record/has_many_association_with_scopes.rb +0 -21
  91. data/lib/thinking_sphinx/bundled_search.rb +0 -40
  92. data/lib/thinking_sphinx/connection.rb +0 -71
  93. data/lib/thinking_sphinx/deltas/delete_job.rb +0 -16
  94. data/lib/thinking_sphinx/deltas/index_job.rb +0 -17
  95. data/lib/thinking_sphinx/rails_additions.rb +0 -181
  96. data/spec/fixtures/data.sql +0 -32
  97. data/spec/fixtures/database.yml.default +0 -3
  98. data/spec/fixtures/models.rb +0 -161
  99. data/spec/fixtures/structure.sql +0 -146
  100. data/spec/spec_helper.rb +0 -54
  101. data/spec/sphinx_helper.rb +0 -67
  102. data/spec/thinking_sphinx/adapters/abstract_adapter_spec.rb +0 -163
  103. data/spec/thinking_sphinx/connection_spec.rb +0 -77
  104. data/spec/thinking_sphinx/rails_additions_spec.rb +0 -203
@@ -1,126 +1,126 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe ThinkingSphinx::Context do
4
- let(:ts_context) { ThinkingSphinx::Context.new }
5
-
4
+ before :each do
5
+ @context = ThinkingSphinx::Context.new
6
+ end
7
+
6
8
  describe '#prepare' do
7
- let(:config) { ThinkingSphinx::Configuration.instance }
8
- let(:file_name) { 'a.rb' }
9
- let(:model_name_lower) { 'a' }
10
- let(:class_name) { 'A' }
11
-
12
9
  before :each do
13
- config.model_directories = ['']
10
+ @config = ThinkingSphinx::Configuration.instance
11
+ @config.model_directories = ['']
14
12
 
15
- file_name.stub!(:gsub).and_return(model_name_lower)
16
- model_name_lower.stub!(:camelize).and_return(class_name)
17
- Dir.stub(:[]).and_return([file_name])
13
+ @file_name = 'a.rb'
14
+ @model_name_lower = 'a'
15
+ @class_name = 'A'
16
+
17
+ @file_name.stub!(:gsub).and_return(@model_name_lower)
18
+ @model_name_lower.stub!(:camelize).and_return(@class_name)
19
+ Dir.stub(:[]).and_return([@file_name])
18
20
  end
19
21
 
20
22
  it "should load the files by guessing the file name" do
21
- class_name.should_receive(:constantize).and_return(true)
23
+ @class_name.should_receive(:constantize).and_return(true)
22
24
 
23
- ts_context.prepare
25
+ @context.prepare
24
26
  end
25
27
 
26
28
  it "should not raise errors if the model name is nil" do
27
- file_name.stub!(:gsub).and_return(nil)
29
+ @file_name.stub!(:gsub).and_return(nil)
28
30
 
29
31
  lambda {
30
- ts_context.prepare
32
+ @context.prepare
31
33
  }.should_not raise_error
32
34
  end
33
35
 
34
- it "should report name errors but not raise them" do
35
- class_name.stub(:constantize).and_raise(NameError)
36
- STDERR.stub!(:puts => '')
37
- STDERR.should_receive(:puts).with('Warning: Error loading a.rb:')
36
+ it "should not raise errors if the file name does not represent a class name" do
37
+ @class_name.should_receive(:constantize).and_raise(NameError)
38
38
 
39
39
  lambda {
40
- ts_context.prepare
40
+ @context.prepare
41
41
  }.should_not raise_error
42
42
  end
43
43
 
44
- it "should retry if the first load fails and contains a directory" do
45
- model_name_lower.should_receive(:gsub!).twice.and_return(true, nil)
46
- class_name.stub(:constantize).and_raise(LoadError)
44
+ it "should retry if the first pass fails and contains a directory" do
45
+ @model_name_lower.stub!(:gsub!).and_return(true, nil)
46
+ @class_name.stub(:constantize).and_raise(LoadError)
47
+ @model_name_lower.should_receive(:camelize).twice
47
48
 
48
49
  lambda {
49
- ts_context.prepare
50
+ @context.prepare
50
51
  }.should_not raise_error
51
52
  end
52
53
 
53
54
  it "should catch database errors with a warning" do
54
- class_name.should_receive(:constantize).and_raise(Mysql2::Error)
55
- STDERR.stub!(:puts => '')
56
- STDERR.should_receive(:puts).with('Warning: Error loading a.rb:')
55
+ @class_name.should_receive(:constantize).and_raise(Mysql::Error)
56
+ STDERR.should_receive(:puts).with('Warning: Error loading a.rb')
57
57
 
58
58
  lambda {
59
- ts_context.prepare
59
+ @context.prepare
60
60
  }.should_not raise_error
61
- end unless RUBY_PLATFORM == 'java'
62
-
61
+ end
62
+
63
63
  it "should not load models if they're explicitly set in the configuration" do
64
- config.indexed_models = ['Alpha', 'Beta']
65
- ts_context.prepare
66
-
67
- ts_context.indexed_models.should == ['Alpha', 'Beta']
64
+ @config.indexed_models = ['Alpha', 'Beta']
65
+ @context.prepare
66
+
67
+ @context.indexed_models.should == ['Alpha', 'Beta']
68
68
  end
69
69
  end
70
-
70
+
71
71
  describe '#define_indexes' do
72
72
  it "should call define_indexes on all known indexed models" do
73
- ts_context.stub!(:indexed_models => ['Alpha', 'Beta'])
73
+ @context.stub!(:indexed_models => ['Alpha', 'Beta'])
74
74
  Alpha.should_receive(:define_indexes)
75
75
  Beta.should_receive(:define_indexes)
76
-
77
- ts_context.define_indexes
76
+
77
+ @context.define_indexes
78
78
  end
79
79
  end
80
-
80
+
81
81
  describe '#add_indexed_model' do
82
82
  before :each do
83
- ts_context.indexed_models.clear
83
+ @context.indexed_models.clear
84
84
  end
85
-
85
+
86
86
  it "should add the model to the collection" do
87
- ts_context.add_indexed_model 'Alpha'
88
-
89
- ts_context.indexed_models.should == ['Alpha']
87
+ @context.add_indexed_model 'Alpha'
88
+
89
+ @context.indexed_models.should == ['Alpha']
90
90
  end
91
-
91
+
92
92
  it "should not duplicate models in the collection" do
93
- ts_context.add_indexed_model 'Alpha'
94
- ts_context.add_indexed_model 'Alpha'
95
-
96
- ts_context.indexed_models.should == ['Alpha']
93
+ @context.add_indexed_model 'Alpha'
94
+ @context.add_indexed_model 'Alpha'
95
+
96
+ @context.indexed_models.should == ['Alpha']
97
97
  end
98
-
98
+
99
99
  it "should keep the collection in alphabetical order" do
100
- ts_context.add_indexed_model 'Beta'
101
- ts_context.add_indexed_model 'Alpha'
102
-
103
- ts_context.indexed_models.should == ['Alpha', 'Beta']
100
+ @context.add_indexed_model 'Beta'
101
+ @context.add_indexed_model 'Alpha'
102
+
103
+ @context.indexed_models.should == ['Alpha', 'Beta']
104
104
  end
105
-
105
+
106
106
  it "should translate classes to their names" do
107
- ts_context.add_indexed_model Alpha
108
-
109
- ts_context.indexed_models.should == ['Alpha']
107
+ @context.add_indexed_model Alpha
108
+
109
+ @context.indexed_models.should == ['Alpha']
110
110
  end
111
111
  end
112
-
112
+
113
113
  describe '#superclass_indexed_models' do
114
114
  it "should return indexed model names" do
115
- ts_context.stub!(:indexed_models => ['Alpha', 'Beta'])
116
-
117
- ts_context.superclass_indexed_models.should == ['Alpha', 'Beta']
115
+ @context.stub!(:indexed_models => ['Alpha', 'Beta'])
116
+
117
+ @context.superclass_indexed_models.should == ['Alpha', 'Beta']
118
118
  end
119
-
119
+
120
120
  it "should not include classes which have indexed superclasses" do
121
- ts_context.stub!(:indexed_models => ['Parent', 'Person'])
122
-
123
- ts_context.superclass_indexed_models.should == ['Person']
121
+ @context.stub!(:indexed_models => ['Parent', 'Person'])
122
+
123
+ @context.superclass_indexed_models.should == ['Person']
124
124
  end
125
125
  end
126
126
  end
@@ -1,82 +1,136 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe ThinkingSphinx::FacetSearch do
4
- let(:search) { stub('search', :append_to => nil, :empty? => true) }
5
- let(:config) { ThinkingSphinx::Configuration.instance }
6
- let(:client) { stub('client', :run => []) }
7
-
8
- before :each do
9
- ThinkingSphinx::Connection.stub!(:take).and_yield(client)
10
- end
11
-
12
4
  describe 'populate' do
13
- before :each do
14
- config.configuration.searchd.max_matches = 10_000
5
+ it "should make separate Sphinx queries for each facet" do
6
+ ThinkingSphinx.should_receive(:search).with(
7
+ hash_including(:group_by => 'city_facet')
8
+ ).and_return([])
9
+ ThinkingSphinx.should_receive(:search).with(
10
+ hash_including(:group_by => 'state_facet')
11
+ ).and_return([])
12
+ ThinkingSphinx.should_receive(:search).with(
13
+ hash_including(:group_by => 'birthday')
14
+ ).and_return([])
15
+
16
+ ThinkingSphinx::FacetSearch.new(:classes => [Person])
15
17
  end
16
-
18
+
17
19
  it "should request all shared facets in a multi-model request by default" do
18
- ThinkingSphinx.stub!(:search => search)
19
- if Riddle.loaded_version.to_i < 2
20
- ThinkingSphinx::FacetSearch.new.facet_names.should == ['class_crc']
21
- else
22
- ThinkingSphinx::FacetSearch.new.facet_names.should == ['sphinx_internal_class']
23
- end
20
+ ThinkingSphinx.stub!(:search => [])
21
+ ThinkingSphinx::FacetSearch.new.facet_names.should == ['class_crc']
24
22
  end
25
-
23
+
26
24
  it "should request all facets in a multi-model request if specified" do
27
- ThinkingSphinx.stub!(:search => search)
28
- names = ThinkingSphinx::FacetSearch.new(:all_facets => true).facet_names
25
+ ThinkingSphinx.stub!(:search => [])
26
+ ThinkingSphinx::FacetSearch.new(
27
+ :all_facets => true
28
+ ).facet_names.should == [
29
+ 'class_crc', 'city_facet', 'state_facet', 'birthday'
30
+ ]
31
+ end
32
+
33
+ describe ':facets option' do
34
+ it "should limit facets to the requested set" do
35
+ ThinkingSphinx.should_receive(:search).once.and_return([])
36
+
37
+ ThinkingSphinx::FacetSearch.new(
38
+ :classes => [Person], :facets => :state
39
+ )
40
+ end
41
+ end
42
+
43
+ describe "empty result set for attributes" do
44
+ before :each do
45
+ ThinkingSphinx.stub!(:search => [])
46
+ @facets = ThinkingSphinx::FacetSearch.new(
47
+ :classes => [Person], :facets => :state
48
+ )
49
+ end
50
+
51
+ it "should add key as attribute" do
52
+ @facets.should have_key(:state)
53
+ end
29
54
 
30
- if Riddle.loaded_version.to_i < 2
31
- names.should == ['class_crc', 'city_facet', 'state_facet', 'birthday']
32
- else
33
- names.should == ['sphinx_internal_class', 'city_facet', 'state_facet', 'birthday']
55
+ it "should return an empty hash for the facet results" do
56
+ @facets[:state].should be_empty
34
57
  end
35
58
  end
36
59
 
60
+ describe "non-empty result set" do
61
+ before :each do
62
+ @person = Person.find(:first)
63
+ @people = [@person]
64
+ @people.stub!(:each_with_groupby_and_count).
65
+ and_yield(@person, @person.city.to_crc32, 1)
66
+ ThinkingSphinx.stub!(:search => @people)
67
+
68
+ @facets = ThinkingSphinx::FacetSearch.new(
69
+ :classes => [Person], :facets => :city
70
+ )
71
+ end
72
+
73
+ it "should return a hash" do
74
+ @facets.should be_a_kind_of(Hash)
75
+ end
76
+
77
+ it "should add key as attribute" do
78
+ @facets.keys.should include(:city)
79
+ end
80
+
81
+ it "should return a hash" do
82
+ @facets[:city].should == {@person.city => 1}
83
+ end
84
+ end
85
+
86
+ before :each do
87
+ @config = ThinkingSphinx::Configuration.instance
88
+ @config.configuration.searchd.max_matches = 10_000
89
+ end
90
+
37
91
  it "should use the system-set max_matches for limit on facet calls" do
38
92
  ThinkingSphinx.should_receive(:search) do |options|
39
93
  options[:max_matches].should == 10_000
40
94
  options[:limit].should == 10_000
41
- search
95
+ []
42
96
  end
43
-
97
+
44
98
  ThinkingSphinx::FacetSearch.new
45
99
  end
46
-
100
+
47
101
  it "should use the default max-matches if there is no explicit setting" do
48
- config.configuration.searchd.max_matches = nil
102
+ @config.configuration.searchd.max_matches = nil
49
103
  ThinkingSphinx.should_receive(:search) do |options|
50
104
  options[:max_matches].should == 1000
51
105
  options[:limit].should == 1000
52
- search
106
+ []
53
107
  end
54
-
108
+
55
109
  ThinkingSphinx::FacetSearch.new
56
110
  end
57
-
111
+
58
112
  it "should ignore user-provided max_matches and limit on facet calls" do
59
113
  ThinkingSphinx.should_receive(:search) do |options|
60
114
  options[:max_matches].should == 10_000
61
115
  options[:limit].should == 10_000
62
- search
116
+ []
63
117
  end
64
-
118
+
65
119
  ThinkingSphinx::FacetSearch.new(
66
120
  :max_matches => 500,
67
121
  :limit => 200
68
122
  )
69
123
  end
70
-
124
+
71
125
  it "should not use an explicit :page" do
72
126
  ThinkingSphinx.should_receive(:search) do |options|
73
127
  options[:page].should == 1
74
- search
128
+ []
75
129
  end
76
-
130
+
77
131
  ThinkingSphinx::FacetSearch.new(:page => 3)
78
132
  end
79
-
133
+
80
134
  describe "conflicting facets" do
81
135
  before :each do
82
136
  @index = ThinkingSphinx::Index::Builder.generate(Alpha) do
@@ -84,81 +138,27 @@ describe ThinkingSphinx::FacetSearch do
84
138
  has :value, :as => :city, :facet => true
85
139
  end
86
140
  end
87
-
141
+
88
142
  after :each do
89
143
  Alpha.sphinx_facets.delete_at(-1)
90
144
  end
91
-
145
+
92
146
  it "should raise an error if searching with facets of same name but different type" do
93
147
  lambda {
94
148
  facets = ThinkingSphinx.facets :all_facets => true
95
149
  }.should raise_error
96
150
  end
97
151
  end
98
-
99
- describe ':facets option' do
100
- it "should limit facets to the requested set" do
101
- ThinkingSphinx.should_receive(:search).once.and_return(search)
102
-
103
- ThinkingSphinx::FacetSearch.new(
104
- :classes => [Person], :facets => :state
105
- )
106
- end
107
- end
108
-
109
- describe "empty result set for attributes" do
110
- before :each do
111
- ThinkingSphinx.stub!(:search => search)
112
- @facets = ThinkingSphinx::FacetSearch.new(
113
- :classes => [Person], :facets => :state
114
- )
115
- end
116
-
117
- it "should add key as attribute" do
118
- @facets.should have_key(:state)
119
- end
120
-
121
- it "should return an empty hash for the facet results" do
122
- @facets[:state].should be_empty
123
- end
124
- end
125
-
126
- describe "non-empty result set" do
127
- before :each do
128
- @person = Person.find(:first)
129
- @people = [@person]
130
- search.stub!(:empty? => false)
131
- search.stub!(:each_with_match).
132
- and_yield(@person, {:attributes => {'@groupby' => @person.city.to_crc32, '@count' => 1}})
133
- ThinkingSphinx::Search.stub!(:bundle_searches => [search])
134
-
135
- @facets = ThinkingSphinx::FacetSearch.new(
136
- :classes => [Person], :facets => :city
137
- )
138
- end
139
-
140
- it "should return a hash" do
141
- @facets.should be_a_kind_of(Hash)
142
- end
143
-
144
- it "should add key as attribute" do
145
- @facets.keys.should include(:city)
146
- end
147
-
148
- it "should return a hash" do
149
- @facets[:city].should == {@person.city => 1}
150
- end
151
- end
152
152
  end
153
-
153
+
154
154
  describe "#for" do
155
155
  before do
156
156
  @person = Person.find(:first)
157
157
  @people = [@person]
158
- search.stub!(:each_with_match).
159
- and_yield(@person, {:attributes => {'@groupby' => @person.city.to_crc32, '@count' => 1}})
160
- ThinkingSphinx::Search.stub!(:bundle_searches => [search])
161
-
158
+ @people.stub!(:each_with_groupby_and_count).
159
+ and_yield(@person, @person.city.to_crc32, 1)
160
+ ThinkingSphinx.stub!(:search => @people)
161
+
162
162
  @facets = ThinkingSphinx::FacetSearch.new(
163
163
  :classes => [Person], :facets => :city
164
164
  )
@@ -169,7 +169,7 @@ describe ThinkingSphinx::FacetSearch do
169
169
  options[:with].should have_key('city_facet')
170
170
  options[:with]['city_facet'].should == @person.city.to_crc32
171
171
  end
172
-
172
+
173
173
  @facets.for(:city => @person.city)
174
174
  end
175
175
  end