jaikoo-thinking-sphinx 0.9.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 (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