ebeigarts-thinking-sphinx 1.1.22 → 1.2.10

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/README.textile +14 -0
  2. data/VERSION.yml +4 -0
  3. data/lib/thinking_sphinx.rb +60 -64
  4. data/lib/thinking_sphinx/active_record.rb +35 -7
  5. data/lib/thinking_sphinx/active_record/scopes.rb +39 -0
  6. data/lib/thinking_sphinx/adapters/postgresql_adapter.rb +3 -2
  7. data/lib/thinking_sphinx/attribute.rb +62 -22
  8. data/lib/thinking_sphinx/configuration.rb +21 -1
  9. data/lib/thinking_sphinx/core/array.rb +7 -0
  10. data/lib/thinking_sphinx/deltas/delayed_delta.rb +3 -0
  11. data/lib/thinking_sphinx/deploy/capistrano.rb +26 -8
  12. data/lib/thinking_sphinx/excerpter.rb +22 -0
  13. data/lib/thinking_sphinx/facet.rb +8 -2
  14. data/lib/thinking_sphinx/facet_search.rb +134 -0
  15. data/lib/thinking_sphinx/index.rb +2 -2
  16. data/lib/thinking_sphinx/index/builder.rb +0 -1
  17. data/lib/thinking_sphinx/property.rb +2 -0
  18. data/lib/thinking_sphinx/rails_additions.rb +14 -0
  19. data/lib/thinking_sphinx/search.rb +633 -671
  20. data/lib/thinking_sphinx/search_methods.rb +421 -0
  21. data/lib/thinking_sphinx/source.rb +5 -5
  22. data/lib/thinking_sphinx/source/internal_properties.rb +1 -1
  23. data/lib/thinking_sphinx/source/sql.rb +10 -8
  24. data/lib/thinking_sphinx/tasks.rb +14 -9
  25. data/spec/{unit → lib}/thinking_sphinx/active_record/delta_spec.rb +1 -1
  26. data/spec/{unit → lib}/thinking_sphinx/active_record/has_many_association_spec.rb +0 -0
  27. data/spec/lib/thinking_sphinx/active_record/scopes_spec.rb +96 -0
  28. data/spec/{unit → lib}/thinking_sphinx/active_record_spec.rb +44 -5
  29. data/spec/{unit → lib}/thinking_sphinx/association_spec.rb +0 -0
  30. data/spec/{unit → lib}/thinking_sphinx/attribute_spec.rb +110 -3
  31. data/spec/{unit → lib}/thinking_sphinx/configuration_spec.rb +87 -41
  32. data/spec/lib/thinking_sphinx/core/array_spec.rb +9 -0
  33. data/spec/{unit → lib}/thinking_sphinx/core/string_spec.rb +0 -0
  34. data/spec/lib/thinking_sphinx/excerpter_spec.rb +49 -0
  35. data/spec/lib/thinking_sphinx/facet_search_spec.rb +176 -0
  36. data/spec/{unit → lib}/thinking_sphinx/facet_spec.rb +34 -15
  37. data/spec/{unit → lib}/thinking_sphinx/field_spec.rb +0 -0
  38. data/spec/{unit → lib}/thinking_sphinx/index/builder_spec.rb +100 -0
  39. data/spec/{unit → lib}/thinking_sphinx/index/faux_column_spec.rb +0 -0
  40. data/spec/{unit → lib}/thinking_sphinx/index_spec.rb +0 -0
  41. data/spec/{unit → lib}/thinking_sphinx/rails_additions_spec.rb +12 -0
  42. data/spec/lib/thinking_sphinx/search_methods_spec.rb +152 -0
  43. data/spec/lib/thinking_sphinx/search_spec.rb +1066 -0
  44. data/spec/{unit → lib}/thinking_sphinx/source_spec.rb +10 -0
  45. data/spec/{unit → lib}/thinking_sphinx_spec.rb +10 -0
  46. data/tasks/distribution.rb +20 -38
  47. data/tasks/testing.rb +3 -1
  48. data/vendor/riddle/lib/riddle.rb +1 -1
  49. data/vendor/riddle/lib/riddle/client.rb +3 -0
  50. data/vendor/riddle/lib/riddle/client/message.rb +4 -3
  51. data/vendor/riddle/lib/riddle/configuration/section.rb +1 -1
  52. data/vendor/riddle/lib/riddle/controller.rb +17 -7
  53. metadata +63 -83
  54. data/lib/thinking_sphinx/active_record/search.rb +0 -57
  55. data/lib/thinking_sphinx/collection.rb +0 -148
  56. data/lib/thinking_sphinx/facet_collection.rb +0 -59
  57. data/lib/thinking_sphinx/search/facets.rb +0 -104
  58. data/spec/unit/thinking_sphinx/active_record/search_spec.rb +0 -107
  59. data/spec/unit/thinking_sphinx/collection_spec.rb +0 -15
  60. data/spec/unit/thinking_sphinx/facet_collection_spec.rb +0 -64
  61. data/spec/unit/thinking_sphinx/search_spec.rb +0 -228
@@ -4,32 +4,32 @@ describe ThinkingSphinx::Configuration do
4
4
  describe "environment class method" do
5
5
  before :each do
6
6
  ThinkingSphinx::Configuration.send(:class_variable_set, :@@environment, nil)
7
-
7
+
8
8
  ENV["RAILS_ENV"] = nil
9
9
  end
10
-
10
+
11
11
  it "should use the Merb environment value if set" do
12
12
  unless defined?(Merb)
13
13
  module ::Merb; end
14
14
  end
15
-
15
+
16
16
  ThinkingSphinx::Configuration.stub_method(:defined? => true)
17
17
  Merb.stub!(:environment => "merb_production")
18
18
  ThinkingSphinx::Configuration.environment.should == "merb_production"
19
-
19
+
20
20
  Object.send(:remove_const, :Merb)
21
21
  end
22
-
22
+
23
23
  it "should use the Rails environment value if set" do
24
24
  ENV["RAILS_ENV"] = "rails_production"
25
25
  ThinkingSphinx::Configuration.environment.should == "rails_production"
26
26
  end
27
-
27
+
28
28
  it "should default to development" do
29
29
  ThinkingSphinx::Configuration.environment.should == "development"
30
30
  end
31
31
  end
32
-
32
+
33
33
  describe "parse_config method" do
34
34
  before :each do
35
35
  @settings = {
@@ -53,22 +53,22 @@ describe ThinkingSphinx::Configuration do
53
53
  "indexer_binary_name" => "sphinx-indexer"
54
54
  }
55
55
  }
56
-
56
+
57
57
  open("#{RAILS_ROOT}/config/sphinx.yml", "w") do |f|
58
58
  f.write YAML.dump(@settings)
59
59
  end
60
60
  end
61
-
61
+
62
62
  it "should use the accessors to set the configuration values" do
63
63
  config = ThinkingSphinx::Configuration.instance
64
64
  config.send(:parse_config)
65
-
65
+
66
66
  %w(config_file searchd_log_file query_log_file pid_file searchd_file_path
67
67
  address port searchd_binary_name indexer_binary_name).each do |key|
68
68
  config.send(key).should == @settings["development"][key]
69
69
  end
70
70
  end
71
-
71
+
72
72
  after :each do
73
73
  FileUtils.rm "#{RAILS_ROOT}/config/sphinx.yml"
74
74
  end
@@ -82,141 +82,187 @@ describe ThinkingSphinx::Configuration do
82
82
  ThinkingSphinx::Configuration.instance.app_root.should == "/here/somewhere"
83
83
  end
84
84
  end
85
-
85
+
86
86
  describe "initialisation" do
87
87
  it "should have a default bin_path of nothing" do
88
88
  ThinkingSphinx::Configuration.instance.bin_path.should == ""
89
89
  end
90
-
90
+
91
91
  it "should append a / to bin_path if one is supplied" do
92
92
  @settings = {
93
93
  "development" => {
94
94
  "bin_path" => "path/to/somewhere"
95
95
  }
96
96
  }
97
-
97
+
98
98
  open("#{RAILS_ROOT}/config/sphinx.yml", "w") do |f|
99
99
  f.write YAML.dump(@settings)
100
100
  end
101
-
101
+
102
102
  ThinkingSphinx::Configuration.instance.send(:parse_config)
103
103
  ThinkingSphinx::Configuration.instance.bin_path.should match(/\/$/)
104
+
105
+ FileUtils.rm "#{RAILS_ROOT}/config/sphinx.yml"
104
106
  end
105
107
  end
106
-
108
+
107
109
  describe "index options" do
108
110
  before :each do
109
111
  @settings = {
110
112
  "development" => {"disable_range" => true}
111
113
  }
112
-
114
+
113
115
  open("#{RAILS_ROOT}/config/sphinx.yml", "w") do |f|
114
116
  f.write YAML.dump(@settings)
115
117
  end
116
-
118
+
117
119
  @config = ThinkingSphinx::Configuration.instance
118
120
  @config.send(:parse_config)
119
121
  end
120
-
122
+
121
123
  it "should collect disable_range" do
122
124
  @config.index_options[:disable_range].should be_true
123
125
  end
126
+
127
+ after :each do
128
+ FileUtils.rm "#{RAILS_ROOT}/config/sphinx.yml"
129
+ end
124
130
  end
125
-
131
+
126
132
  describe "#load_models" do
127
133
  before :each do
128
134
  @config = ThinkingSphinx::Configuration.instance
129
135
  @config.model_directories = ['']
130
-
136
+
131
137
  @file_name = 'a.rb'
132
138
  @model_name_lower = 'a'
133
139
  @class_name = 'A'
134
-
140
+
135
141
  @file_name.stub!(:gsub).and_return(@model_name_lower)
136
142
  @model_name_lower.stub!(:camelize).and_return(@class_name)
137
143
  Dir.stub(:[]).and_return([@file_name])
138
144
  end
139
-
145
+
140
146
  it "should load the files by guessing the file name" do
141
147
  @class_name.should_receive(:constantize).and_return(true)
142
-
148
+
143
149
  @config.load_models
144
150
  end
145
-
151
+
146
152
  it "should not raise errors if the model name is nil" do
147
153
  @file_name.stub!(:gsub).and_return(nil)
148
-
154
+
149
155
  lambda {
150
156
  @config.load_models
151
157
  }.should_not raise_error
152
158
  end
153
-
159
+
154
160
  it "should not raise errors if the file name does not represent a class name" do
155
161
  @class_name.should_receive(:constantize).and_raise(NameError)
156
-
162
+
157
163
  lambda {
158
164
  @config.load_models
159
165
  }.should_not raise_error
160
166
  end
161
-
167
+
162
168
  it "should retry if the first pass fails and contains a directory" do
163
169
  @model_name_lower.stub!(:gsub!).and_return(true, nil)
164
170
  @class_name.stub(:constantize).and_raise(LoadError)
165
171
  @model_name_lower.should_receive(:camelize).twice
166
-
172
+
167
173
  lambda {
168
174
  @config.load_models
169
175
  }.should_not raise_error
170
176
  end
171
-
177
+
172
178
  it "should catch database errors with a warning" do
173
179
  @class_name.should_receive(:constantize).and_raise(Mysql::Error)
174
180
  @config.should_receive(:puts).with('Warning: Error loading a.rb')
175
-
181
+
176
182
  lambda {
177
183
  @config.load_models
178
184
  }.should_not raise_error
179
185
  end
180
186
  end
181
-
187
+
182
188
  it "should insert set index options into the configuration file" do
183
189
  config = ThinkingSphinx::Configuration.instance
184
190
  ThinkingSphinx::Configuration::IndexOptions.each do |option|
185
191
  config.index_options[option.to_sym] = "something"
186
192
  config.build
187
-
193
+
188
194
  file = open(config.config_file) { |f| f.read }
189
195
  file.should match(/#{option}\s+= something/)
190
-
196
+
191
197
  config.index_options[option.to_sym] = nil
192
198
  end
193
199
  end
194
-
200
+
195
201
  it "should insert set source options into the configuration file" do
196
202
  config = ThinkingSphinx::Configuration.instance
197
203
  ThinkingSphinx::Configuration::SourceOptions.each do |option|
198
204
  config.source_options[option.to_sym] = "something"
199
205
  config.build
200
-
206
+
201
207
  file = open(config.config_file) { |f| f.read }
202
208
  file.should match(/#{option}\s+= something/)
203
-
209
+
204
210
  config.source_options[option.to_sym] = nil
205
211
  end
206
212
  end
207
-
213
+
208
214
  it "should set any explicit prefixed or infixed fields" do
209
215
  file = open(ThinkingSphinx::Configuration.instance.config_file) { |f|
210
216
  f.read
211
217
  }
212
218
  file.should match(/prefix_fields\s+= city/)
213
- file.should match(/infix_fields\s+= state/)
219
+ file.should match(/infix_fields\s+= state/)
214
220
  end
215
-
221
+
216
222
  it "should not have prefix fields in indexes where nothing is set" do
217
223
  file = open(ThinkingSphinx::Configuration.instance.config_file) { |f|
218
224
  f.read
219
225
  }
220
226
  file.should_not match(/index alpha_core\s+\{\s+[^\}]*prefix_fields\s+=[^\}]*\}/m)
221
227
  end
228
+
229
+ describe '#client' do
230
+ before :each do
231
+ @config = ThinkingSphinx::Configuration.instance
232
+ @config.address = 'domain.url'
233
+ @config.port = 3333
234
+ @config.configuration.searchd.max_matches = 100
235
+ end
236
+
237
+ it "should return an instance of Riddle::Client" do
238
+ @config.client.should be_a(Riddle::Client)
239
+ end
240
+
241
+ it "should use the configuration address" do
242
+ @config.client.server.should == 'domain.url'
243
+ end
244
+
245
+ it "should use the configuration port" do
246
+ @config.client.port.should == 3333
247
+ end
248
+
249
+ it "should use the configuration max matches" do
250
+ @config.client.max_matches.should == 100
251
+ end
252
+ end
253
+
254
+ describe '#models_by_crc' do
255
+ before :each do
256
+ @config = ThinkingSphinx::Configuration.instance
257
+ end
258
+
259
+ it "should return a hash" do
260
+ @config.models_by_crc.should be_a(Hash)
261
+ end
262
+
263
+ it "should pair class names to their crc codes" do
264
+ @config.models_by_crc[Person.to_crc32].should == 'Person'
265
+ @config.models_by_crc[Alpha.to_crc32].should == 'Alpha'
266
+ end
267
+ end
222
268
  end
@@ -0,0 +1,9 @@
1
+ require 'spec/spec_helper'
2
+
3
+ describe Array do
4
+ describe '.===' do
5
+ it "should return true if an instance of ThinkingSphinx::Search" do
6
+ Array.should === ThinkingSphinx::Search.new
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,49 @@
1
+ require 'spec/spec_helper'
2
+
3
+ describe ThinkingSphinx::Excerpter do
4
+ before :each do
5
+ @alpha = Alpha.find(:first)
6
+ @search = mock 'search', :excerpt_for => 'excerpted value'
7
+ @excerpter = ThinkingSphinx::Excerpter.new(@search, @alpha)
8
+ end
9
+
10
+ it "should not respond to id" do
11
+ @excerpter.should_not respond_to(:id)
12
+ end
13
+
14
+ describe '#method_missing' do
15
+ it "should return the excerpt from Sphinx" do
16
+ @excerpter.name.should == 'excerpted value'
17
+ end
18
+
19
+ it "should send through the instance class to excerpt_for" do
20
+ @search.should_receive(:excerpt_for) do |string, model|
21
+ model.should == Alpha
22
+ end
23
+
24
+ @excerpter.name
25
+ end
26
+
27
+ it "should use attribute methods for excerpts calls" do
28
+ @search.should_receive(:excerpt_for) do |string, model|
29
+ string.should == 'one'
30
+ end
31
+
32
+ @excerpter.name
33
+ end
34
+
35
+ it "should use instance methods for excerpts calls" do
36
+ @search.should_receive(:excerpt_for) do |string, model|
37
+ string.should == 'ONE'
38
+ end
39
+
40
+ @excerpter.big_name
41
+ end
42
+
43
+ it "should still raise an exception if no column or method exists" do
44
+ lambda {
45
+ @excerpter.foo
46
+ }.should raise_error(NoMethodError)
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,176 @@
1
+ require 'spec/spec_helper'
2
+
3
+ describe ThinkingSphinx::FacetSearch do
4
+ describe 'populate' do
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])
17
+ end
18
+
19
+ it "should request all shared facets in a multi-model request by default" do
20
+ ThinkingSphinx.stub!(:search => [])
21
+ ThinkingSphinx::FacetSearch.new.facet_names.should == ['class_crc']
22
+ end
23
+
24
+ it "should request all facets in a multi-model request if specified" do
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
54
+
55
+ it "should return an empty hash for the facet results" do
56
+ @facets[:state].should be_empty
57
+ end
58
+ end
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
+
91
+ it "should use the system-set max_matches for limit on facet calls" do
92
+ ThinkingSphinx.should_receive(:search) do |options|
93
+ options[:max_matches].should == 10_000
94
+ options[:limit].should == 10_000
95
+ []
96
+ end
97
+
98
+ ThinkingSphinx::FacetSearch.new
99
+ end
100
+
101
+ it "should use the default max-matches if there is no explicit setting" do
102
+ @config.configuration.searchd.max_matches = nil
103
+ ThinkingSphinx.should_receive(:search) do |options|
104
+ options[:max_matches].should == 1000
105
+ options[:limit].should == 1000
106
+ []
107
+ end
108
+
109
+ ThinkingSphinx::FacetSearch.new
110
+ end
111
+
112
+ it "should ignore user-provided max_matches and limit on facet calls" do
113
+ ThinkingSphinx.should_receive(:search) do |options|
114
+ options[:max_matches].should == 10_000
115
+ options[:limit].should == 10_000
116
+ []
117
+ end
118
+
119
+ ThinkingSphinx::FacetSearch.new(
120
+ :max_matches => 500,
121
+ :limit => 200
122
+ )
123
+ end
124
+
125
+ it "should not use an explicit :page" do
126
+ ThinkingSphinx.should_receive(:search) do |options|
127
+ options[:page].should == 1
128
+ []
129
+ end
130
+
131
+ ThinkingSphinx::FacetSearch.new(:page => 3)
132
+ end
133
+
134
+ describe "conflicting facets" do
135
+ before :each do
136
+ @index = ThinkingSphinx::Index::Builder.generate(Alpha) do
137
+ indexes :name
138
+ has :value, :as => :city, :facet => true
139
+ end
140
+ end
141
+
142
+ after :each do
143
+ Alpha.sphinx_facets.delete_at(-1)
144
+ end
145
+
146
+ it "should raise an error if searching with facets of same name but different type" do
147
+ lambda {
148
+ facets = ThinkingSphinx.facets :all_facets => true
149
+ }.should raise_error
150
+ end
151
+ end
152
+ end
153
+
154
+ describe "#for" do
155
+ before do
156
+ @person = Person.find(:first)
157
+ @people = [@person]
158
+ @people.stub!(:each_with_groupby_and_count).
159
+ and_yield(@person, @person.city.to_crc32, 1)
160
+ ThinkingSphinx.stub!(:search => @people)
161
+
162
+ @facets = ThinkingSphinx::FacetSearch.new(
163
+ :classes => [Person], :facets => :city
164
+ )
165
+ end
166
+
167
+ it "should return the search results for the attribute and key pair" do
168
+ ThinkingSphinx.should_receive(:search) do |options|
169
+ options[:with].should have_key('city_facet')
170
+ options[:with]['city_facet'].should == @person.city.to_crc32
171
+ end
172
+
173
+ @facets.for(:city => @person.city)
174
+ end
175
+ end
176
+ end