jaikoo-thinking-sphinx 0.9.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. data/LICENCE +20 -0
  2. data/README +76 -0
  3. data/lib/thinking_sphinx.rb +112 -0
  4. data/lib/thinking_sphinx/active_record.rb +153 -0
  5. data/lib/thinking_sphinx/active_record/delta.rb +80 -0
  6. data/lib/thinking_sphinx/active_record/has_many_association.rb +29 -0
  7. data/lib/thinking_sphinx/active_record/search.rb +50 -0
  8. data/lib/thinking_sphinx/adapters/abstract_adapter.rb +27 -0
  9. data/lib/thinking_sphinx/adapters/mysql_adapter.rb +9 -0
  10. data/lib/thinking_sphinx/adapters/postgresql_adapter.rb +84 -0
  11. data/lib/thinking_sphinx/association.rb +144 -0
  12. data/lib/thinking_sphinx/attribute.rb +284 -0
  13. data/lib/thinking_sphinx/collection.rb +105 -0
  14. data/lib/thinking_sphinx/configuration.rb +314 -0
  15. data/lib/thinking_sphinx/field.rb +206 -0
  16. data/lib/thinking_sphinx/index.rb +432 -0
  17. data/lib/thinking_sphinx/index/builder.rb +220 -0
  18. data/lib/thinking_sphinx/index/faux_column.rb +110 -0
  19. data/lib/thinking_sphinx/rails_additions.rb +68 -0
  20. data/lib/thinking_sphinx/search.rb +436 -0
  21. data/spec/unit/thinking_sphinx/active_record/delta_spec.rb +132 -0
  22. data/spec/unit/thinking_sphinx/active_record/has_many_association_spec.rb +53 -0
  23. data/spec/unit/thinking_sphinx/active_record/search_spec.rb +107 -0
  24. data/spec/unit/thinking_sphinx/active_record_spec.rb +295 -0
  25. data/spec/unit/thinking_sphinx/association_spec.rb +247 -0
  26. data/spec/unit/thinking_sphinx/attribute_spec.rb +360 -0
  27. data/spec/unit/thinking_sphinx/collection_spec.rb +71 -0
  28. data/spec/unit/thinking_sphinx/configuration_spec.rb +512 -0
  29. data/spec/unit/thinking_sphinx/field_spec.rb +224 -0
  30. data/spec/unit/thinking_sphinx/index/builder_spec.rb +34 -0
  31. data/spec/unit/thinking_sphinx/index/faux_column_spec.rb +68 -0
  32. data/spec/unit/thinking_sphinx/index_spec.rb +317 -0
  33. data/spec/unit/thinking_sphinx/search_spec.rb +203 -0
  34. data/spec/unit/thinking_sphinx_spec.rb +129 -0
  35. data/tasks/thinking_sphinx_tasks.rake +1 -0
  36. data/tasks/thinking_sphinx_tasks.rb +100 -0
  37. metadata +103 -0
@@ -0,0 +1,71 @@
1
+ require 'spec/spec_helper'
2
+
3
+ describe ThinkingSphinx::Collection do
4
+ before :all do
5
+ @sphinx.setup_sphinx
6
+ @sphinx.start
7
+ end
8
+
9
+ after :all do
10
+ @sphinx.stop
11
+ end
12
+
13
+ it "should return items paired to their attribute values" do
14
+ results = Person.search ""
15
+ results.should_not be_empty
16
+ results.each_with_sphinx_internal_id do |result, id|
17
+ result.id.should == id
18
+ end
19
+ end
20
+
21
+ it "should return items paired with their weighting" do
22
+ results = Person.search "Ellie Ford", :match_mode => :any
23
+ results.should_not be_empty
24
+ results.each_with_weighting do |result, weight|
25
+ result.should be_kind_of(Person)
26
+ weight.should be_kind_of(Integer)
27
+ end
28
+ end
29
+
30
+ it "should return items paired with their count if grouping" do
31
+ results = Person.search :group_function => :attr, :group_by => "birthday"
32
+ results.should_not be_empty
33
+ results.each_with_count do |result, count|
34
+ result.should be_kind_of(Person)
35
+ count.should be_kind_of(Integer)
36
+ end
37
+ end
38
+
39
+ it "should return items paired with their count and group value" do
40
+ results = Person.search :group_function => :attr, :group_by => "birthday"
41
+ results.should_not be_empty
42
+ results.each_with_group_and_count do |result, group, count|
43
+ result.should be_kind_of(Person)
44
+ # sometimes the grouping value will be nil/null
45
+ group.should be_kind_of(Integer) unless group.nil?
46
+ count.should be_kind_of(Integer)
47
+ end
48
+ end
49
+
50
+ it "should return ids" do
51
+ results = Person.search_for_ids "Ellie"
52
+ results.should_not be_empty
53
+ results.each do |result|
54
+ result.should be_kind_of(Integer)
55
+ end
56
+ end
57
+
58
+ it "should return ids paired with weighting" do
59
+ results = Person.search_for_ids "Ellie Ford", :match_mode => :any
60
+ results.should_not be_empty
61
+ results.each_with_weighting do |result, weight|
62
+ result.should be_kind_of(Integer)
63
+ weight.should be_kind_of(Integer)
64
+ end
65
+ end
66
+
67
+ it "should sort the objects the same as the result set" do
68
+ Person.search_for_ids("Ellie", :order => "sphinx_internal_id DESC").should ==
69
+ Person.search("Ellie", :order => "sphinx_internal_id DESC").collect(&:id)
70
+ end
71
+ end
@@ -0,0 +1,512 @@
1
+ require 'spec/spec_helper'
2
+
3
+ describe ThinkingSphinx::Configuration do
4
+ describe "environment class method" do
5
+ before :each do
6
+ ThinkingSphinx::Configuration.send(:class_variable_set, :@@environment, nil)
7
+
8
+ ENV["RAILS_ENV"] = nil
9
+ end
10
+
11
+ it "should use the Merb environment value if set" do
12
+ unless defined?(Merb)
13
+ module Merb; end
14
+ end
15
+
16
+ ThinkingSphinx::Configuration.stub_method(:defined? => true)
17
+ Merb.stub_method(:environment => "merb_production")
18
+ ThinkingSphinx::Configuration.environment.should == "merb_production"
19
+
20
+ Object.send(:remove_const, :Merb)
21
+ end
22
+
23
+ it "should use the Rails environment value if set" do
24
+ ENV["RAILS_ENV"] = "rails_production"
25
+ ThinkingSphinx::Configuration.environment.should == "rails_production"
26
+ end
27
+
28
+ it "should default to development" do
29
+ ThinkingSphinx::Configuration.environment.should == "development"
30
+ end
31
+ end
32
+
33
+ describe "environment instance method" do
34
+ it "should return the class method" do
35
+ ThinkingSphinx::Configuration.stub_method(:environment => "spec")
36
+ ThinkingSphinx::Configuration.instance.environment.should == "spec"
37
+ ThinkingSphinx::Configuration.should have_received(:environment)
38
+ end
39
+ end
40
+
41
+ describe "build method" do
42
+ before :each do
43
+ @config = ThinkingSphinx::Configuration.instance
44
+
45
+ @config.stub_methods(
46
+ :load_models => "",
47
+ :core_index_for_model => "",
48
+ :delta_index_for_model => "",
49
+ :distributed_index_for_model => ""
50
+ )
51
+
52
+ ThinkingSphinx.stub_method :indexed_models => ["Person", "Friendship"]
53
+ YAML.stub_method(:load => {
54
+ :development => {
55
+ "option" => "value"
56
+ }
57
+ })
58
+
59
+ @adapter = ThinkingSphinx::MysqlAdapter.stub_instance(
60
+ :setup => true
61
+ )
62
+
63
+ @person_index_a = ThinkingSphinx::Index.stub_instance(
64
+ :to_config => "", :adapter => :mysql, :delta? => false,
65
+ :name => "person", :model => Person, :adapter_object => @adapter
66
+ )
67
+ @person_index_b = ThinkingSphinx::Index.stub_instance(
68
+ :to_config => "", :adapter => :mysql, :delta? => false,
69
+ :name => "person", :model => Person, :adapter_object => @adapter
70
+ )
71
+ @friendship_index_a = ThinkingSphinx::Index.stub_instance(
72
+ :to_config => "", :adapter => :mysql, :delta? => false,
73
+ :name => "friendship", :model => Friendship, :adapter_object => @adapter
74
+ )
75
+
76
+ Person.stub_method(:sphinx_indexes => [@person_index_a, @person_index_b])
77
+ Friendship.stub_method(:sphinx_indexes => [@friendship_index_a])
78
+
79
+ FileUtils.mkdir_p "#{@config.app_root}/config"
80
+ FileUtils.touch "#{@config.app_root}/config/database.yml"
81
+ end
82
+
83
+ after :each do
84
+ ThinkingSphinx.unstub_method :indexed_models
85
+ YAML.unstub_method :load
86
+
87
+ Person.unstub_method :sphinx_indexes
88
+ Friendship.unstub_method :sphinx_indexes
89
+ #
90
+ # FileUtils.rm_rf "#{@config.app_root}/config"
91
+ end
92
+
93
+ it "should load the models" do
94
+ @config.build
95
+
96
+ @config.should have_received(:load_models)
97
+ end
98
+
99
+ it "should load in the database YAML configuration" do
100
+ @config.build
101
+
102
+ YAML.should have_received(:load)
103
+ end
104
+
105
+ it "should use the configuration port" do
106
+ @config.build
107
+
108
+ file = open(@config.config_file) { |f| f.read }
109
+ file.should match(/port\s+= #{@config.port}/)
110
+ end
111
+
112
+ it "should use the configuration's log file locations" do
113
+ @config.build
114
+
115
+ file = open(@config.config_file) { |f| f.read }
116
+ file.should match(/log\s+= #{@config.searchd_log_file}/)
117
+ file.should match(/query_log\s+= #{@config.query_log_file}/)
118
+ end
119
+
120
+ it "should use the configuration's pid file location" do
121
+ @config.build
122
+
123
+ file = open(@config.config_file) { |f| f.read }
124
+ file.should match(/pid_file\s+= #{@config.pid_file}/)
125
+ end
126
+
127
+ it "should request configuration for each index for each model" do
128
+ @config.build
129
+
130
+ @person_index_a.should have_received(:to_config).with(
131
+ Person, 0, {:option => "value"}, 0
132
+ )
133
+ @person_index_b.should have_received(:to_config).with(
134
+ Person, 1, {:option => "value"}, 0
135
+ )
136
+ @friendship_index_a.should have_received(:to_config).with(
137
+ Friendship, 0, {:option => "value"}, 1
138
+ )
139
+ end
140
+
141
+ it "should call core_index_for_model for each model" do
142
+ @config.build
143
+
144
+ @config.should have_received(:core_index_for_model).with(
145
+ Person, "source = person_0_core\nsource = person_1_core"
146
+ )
147
+ @config.should have_received(:core_index_for_model).with(
148
+ Friendship, "source = friendship_0_core"
149
+ )
150
+ end
151
+
152
+ it "should call delta_index_for_model for each model if any index has a delta" do
153
+ @person_index_b.stub_method(:delta? => true)
154
+
155
+ @config.build
156
+
157
+ @config.should have_received(:delta_index_for_model).with(
158
+ Person, "source = person_1_delta"
159
+ )
160
+ end
161
+
162
+ it "should not call delta_index_for_model for each model if no indexes have deltas" do
163
+ @config.build
164
+
165
+ @config.should_not have_received(:delta_index_for_model)
166
+ end
167
+
168
+ it "should call distributed_index_for_model for each model" do
169
+ @config.build
170
+
171
+ @config.should have_received(:distributed_index_for_model).with(Person)
172
+ @config.should have_received(:distributed_index_for_model).with(Friendship)
173
+ end
174
+ end
175
+
176
+ describe "load_models method" do
177
+ it "should have some specs"
178
+ end
179
+
180
+ describe "parse_config method" do
181
+ before :each do
182
+ @settings = {
183
+ "development" => {
184
+ "config_file" => "tmp/config/development.sphinx.conf",
185
+ "searchd_log_file" => "searchd_log_file.log",
186
+ "query_log_file" => "query_log_file.log",
187
+ "pid_file" => "pid_file.pid",
188
+ "searchd_file_path" => "searchd/file/path",
189
+ "address" => "127.0.0.1",
190
+ "port" => 3333,
191
+ "min_prefix_len" => 2,
192
+ "min_infix_len" => 3,
193
+ "mem_limit" => "128M",
194
+ "max_matches" => 1001,
195
+ "morphology" => "stem_ru",
196
+ "charset_type" => "latin1",
197
+ "charset_table" => "table",
198
+ "ignore_chars" => "e"
199
+ }
200
+ }
201
+
202
+ open("#{RAILS_ROOT}/config/sphinx.yml", "w") do |f|
203
+ f.write YAML.dump(@settings)
204
+ end
205
+ end
206
+
207
+ it "should use the accessors to set the configuration values" do
208
+ config = ThinkingSphinx::Configuration.instance
209
+ config.send(:parse_config)
210
+
211
+ %w(config_file searchd_log_file query_log_file pid_file searchd_file_path
212
+ address port).each do |key|
213
+ config.send(key).should == @settings["development"][key]
214
+ end
215
+ end
216
+
217
+ after :each do
218
+ FileUtils.rm "#{RAILS_ROOT}/config/sphinx.yml"
219
+ end
220
+ end
221
+
222
+ describe "core_index_for_model method" do
223
+ before :each do
224
+ @config = ThinkingSphinx::Configuration.instance
225
+ @model = Person
226
+ end
227
+
228
+ it "should take its name from the model, with _core appended" do
229
+ @config.send(:core_index_for_model, @model, "my sources").should match(
230
+ /index person_core/
231
+ )
232
+ end
233
+
234
+ it "should set the path to follow the name" do
235
+ @config.searchd_file_path = "/my/file/path"
236
+ @config.send(:core_index_for_model, @model, "my sources").should match(
237
+ /path = \/my\/file\/path\/person_core/
238
+ )
239
+ end
240
+
241
+ it "should include the charset type setting" do
242
+ @config.index_options[:charset_type] = "specchars"
243
+ @config.send(:core_index_for_model, @model, "my sources").should match(
244
+ /charset_type = specchars/
245
+ )
246
+ end
247
+
248
+ it "should include the morphology setting if it isn't blank" do
249
+ @config.index_options[:morphology] = "morph"
250
+ @config.send(:core_index_for_model, @model, "my sources").should match(
251
+ /morphology\s+= morph/
252
+ )
253
+ end
254
+
255
+ it "should not include the morphology setting if it is blank" do
256
+ @config.index_options[:morphology] = nil
257
+ @config.send(:core_index_for_model, @model, "my sources").should_not match(
258
+ /morphology\s+=/
259
+ )
260
+
261
+ @config.index_options[:morphology] = ""
262
+ @config.send(:core_index_for_model, @model, "my sources").should_not match(
263
+ /morphology\s+=/
264
+ )
265
+ end
266
+
267
+ it "should include the charset_table value if it isn't nil" do
268
+ @config.index_options[:charset_table] = "table_chars"
269
+ @config.send(:core_index_for_model, @model, "my sources").should match(
270
+ /charset_table\s+= table_chars/
271
+ )
272
+ end
273
+
274
+ it "should not set the charset_table value if it is nil" do
275
+ @config.index_options[:charset_table] = nil
276
+ @config.send(:core_index_for_model, @model, "my sources").should_not match(
277
+ /charset_table\s+=/
278
+ )
279
+ end
280
+
281
+ it "should set the ignore_chars value if it isn't nil" do
282
+ @config.index_options[:ignore_chars] = "ignorable"
283
+ @config.send(:core_index_for_model, @model, "my sources").should match(
284
+ /ignore_chars\s+= ignorable/
285
+ )
286
+ end
287
+
288
+ it "should not set the ignore_chars value if it is nil" do
289
+ @config.index_options[:ignore_chars] = nil
290
+ @config.send(:core_index_for_model, @model, "my sources").should_not match(
291
+ /ignore_chars\s+=/
292
+ )
293
+ end
294
+
295
+ it "should set prefix_fields if any fields are flagged explicitly" do
296
+ @model.sphinx_indexes.first.stub_methods(
297
+ :prefix_fields => [
298
+ ThinkingSphinx::Field.stub_instance(:unique_name => "a"),
299
+ ThinkingSphinx::Field.stub_instance(:unique_name => "b"),
300
+ ThinkingSphinx::Field.stub_instance(:unique_name => "c")
301
+ ],
302
+ :infix_fields => [
303
+ ThinkingSphinx::Field.stub_instance(:unique_name => "d"),
304
+ ThinkingSphinx::Field.stub_instance(:unique_name => "e"),
305
+ ThinkingSphinx::Field.stub_instance(:unique_name => "f")
306
+ ]
307
+ )
308
+
309
+ @config.send(:core_index_for_model, @model, "my sources").should match(
310
+ /prefix_fields\s+= a, b, c/
311
+ )
312
+ end
313
+
314
+ it "shouldn't set prefix_fields if none are flagged explicitly" do
315
+ @config.send(:core_index_for_model, @model, "my sources").should_not match(
316
+ /prefix_fields\s+=/
317
+ )
318
+ end
319
+
320
+ it "should set infix_fields if any fields are flagged explicitly" do
321
+ @model.sphinx_indexes.first.stub_methods(
322
+ :prefix_fields => [
323
+ ThinkingSphinx::Field.stub_instance(:unique_name => "a"),
324
+ ThinkingSphinx::Field.stub_instance(:unique_name => "b"),
325
+ ThinkingSphinx::Field.stub_instance(:unique_name => "c")
326
+ ],
327
+ :infix_fields => [
328
+ ThinkingSphinx::Field.stub_instance(:unique_name => "d"),
329
+ ThinkingSphinx::Field.stub_instance(:unique_name => "e"),
330
+ ThinkingSphinx::Field.stub_instance(:unique_name => "f")
331
+ ]
332
+ )
333
+
334
+ @config.send(:core_index_for_model, @model, "my sources").should match(
335
+ /infix_fields\s+= d, e, f/
336
+ )
337
+ end
338
+
339
+ it "shouldn't set infix_fields if none are flagged explicitly" do
340
+ @config.send(:core_index_for_model, @model, "my sources").should_not match(
341
+ /infix_fields\s+=/
342
+ )
343
+ end
344
+
345
+ it "should include html_strip if value is set" do
346
+ @config.index_options[:html_strip] = 1
347
+ text = @config.send(:core_index_for_model, @model, "my sources")
348
+ text.should match(/html_strip\s+= 1/)
349
+ end
350
+
351
+ it "shouldn't include html_strip if value is not set" do
352
+ @config.index_options.delete :html_strip
353
+ text = @config.send(:core_index_for_model, @model, "my sources")
354
+ text.should_not match(/html_strip/)
355
+ end
356
+
357
+ it "should include html_remove_elements if values are set" do
358
+ @config.index_options[:html_remove_elements] = 'script'
359
+ text = @config.send(:core_index_for_model, @model, "my sources")
360
+ text.should match(/html_remove_elements\s+= script/)
361
+ end
362
+
363
+ it "shouldn't include html_remove_elements if no values are set" do
364
+ @config.index_options.delete :html_remove_elements
365
+ text = @config.send(:core_index_for_model, @model, "my sources")
366
+ text.should_not match(/html_remove_elements/)
367
+ end
368
+ end
369
+
370
+ describe "delta_index_for_model method" do
371
+ before :each do
372
+ @config = ThinkingSphinx::Configuration.instance
373
+ @model = Person
374
+ end
375
+
376
+ it "should take its name from the model, with _delta appended" do
377
+ @config.send(:delta_index_for_model, @model, "delta_sources").should match(
378
+ /index person_delta/
379
+ )
380
+ end
381
+
382
+ it "should inherit from the equivalent core index" do
383
+ @config.send(:delta_index_for_model, @model, "delta_sources").should match(
384
+ /index person_delta : person_core/
385
+ )
386
+ end
387
+
388
+ it "should set the path to follow the name" do
389
+ @config.searchd_file_path = "/my/file/path"
390
+ @config.send(:delta_index_for_model, @model, "delta_sources").should match(
391
+ /path = \/my\/file\/path\/person_delta/
392
+ )
393
+ end
394
+ end
395
+
396
+ describe "distributed_index_for_model method" do
397
+ before :each do
398
+ @config = ThinkingSphinx::Configuration.instance
399
+ @model = Person
400
+ end
401
+
402
+ it "should take its name from the model" do
403
+ @config.send(:distributed_index_for_model, @model).should match(
404
+ /index person/
405
+ )
406
+ end
407
+
408
+ it "should have a type of distributed" do
409
+ @config.send(:distributed_index_for_model, @model).should match(
410
+ /type = distributed/
411
+ )
412
+ end
413
+
414
+ it "should include the core as a local source" do
415
+ @config.send(:distributed_index_for_model, @model).should match(
416
+ /local = person_core/
417
+ )
418
+ end
419
+
420
+ it "should only include the delta as a local source if an index is flagged to be delta" do
421
+ @config.send(:distributed_index_for_model, @model).should_not match(
422
+ /local = person_delta/
423
+ )
424
+
425
+ @model.sphinx_indexes.first.stub_method(:delta? => true)
426
+ @config.send(:distributed_index_for_model, @model).should match(
427
+ /local = person_delta/
428
+ )
429
+ end
430
+
431
+ it "should handle namespaced models correctly" do
432
+ Person.stub_method(:name => "Namespaced::Model")
433
+
434
+ @config.send(:distributed_index_for_model, @model).should match(
435
+ /index namespaced_model/
436
+ )
437
+ end
438
+ end
439
+
440
+ describe "initialisation" do
441
+ it "should have a default bin_path of nothing" do
442
+ ThinkingSphinx::Configuration.instance.bin_path.should == ""
443
+ end
444
+
445
+ it "should append a / to bin_path if one is supplied" do
446
+ @settings = {
447
+ "development" => {
448
+ "bin_path" => "path/to/somewhere"
449
+ }
450
+ }
451
+
452
+ open("#{RAILS_ROOT}/config/sphinx.yml", "w") do |f|
453
+ f.write YAML.dump(@settings)
454
+ end
455
+
456
+ ThinkingSphinx::Configuration.instance.send(:parse_config)
457
+ ThinkingSphinx::Configuration.instance.bin_path.should match(/\/$/)
458
+ end
459
+ end
460
+
461
+ it "should insert set searchd options into the configuration file" do
462
+ config = ThinkingSphinx::Configuration.instance
463
+ ThinkingSphinx::Configuration::SearchdOptions.each do |option|
464
+ config.searchd_options[option.to_sym] = "something"
465
+ config.build
466
+
467
+ file = open(config.config_file) { |f| f.read }
468
+ file.should match(/#{option}\s+= something/)
469
+
470
+ config.searchd_options[option.to_sym] = nil
471
+ end
472
+ end
473
+
474
+ it "should insert set indexer options into the configuration file" do
475
+ config = ThinkingSphinx::Configuration.instance
476
+ ThinkingSphinx::Configuration::IndexerOptions.each do |option|
477
+ config.indexer_options[option.to_sym] = "something"
478
+ config.build
479
+
480
+ file = open(config.config_file) { |f| f.read }
481
+ file.should match(/#{option}\s+= something/)
482
+
483
+ config.indexer_options[option.to_sym] = nil
484
+ end
485
+ end
486
+
487
+ it "should insert set index options into the configuration file" do
488
+ config = ThinkingSphinx::Configuration.instance
489
+ ThinkingSphinx::Configuration::IndexOptions.each do |option|
490
+ config.index_options[option.to_sym] = "something"
491
+ config.build
492
+
493
+ file = open(config.config_file) { |f| f.read }
494
+ file.should match(/#{option}\s+= something/)
495
+
496
+ config.index_options[option.to_sym] = nil
497
+ end
498
+ end
499
+
500
+ it "should insert set source options into the configuration file" do
501
+ config = ThinkingSphinx::Configuration.instance
502
+ ThinkingSphinx::Configuration::SourceOptions.each do |option|
503
+ config.source_options[option.to_sym] = "something"
504
+ config.build
505
+
506
+ file = open(config.config_file) { |f| f.read }
507
+ file.should match(/#{option}\s+= something/)
508
+
509
+ config.source_options[option.to_sym] = nil
510
+ end
511
+ end
512
+ end