thinking-sphinx 1.5.0 → 2.0.0.rc1

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 (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