sunspot_rails 1.2.1 → 1.3.0.rc6

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 (50) hide show
  1. data/.gitignore +7 -0
  2. data/.rspec +1 -0
  3. data/History.txt +15 -0
  4. data/README.rdoc +14 -7
  5. data/Rakefile +0 -6
  6. data/TESTING.md +32 -18
  7. data/dev_tasks/spec.rake +103 -18
  8. data/gemfiles/rails-2.3.14 +15 -0
  9. data/gemfiles/rails-3.0.10 +15 -0
  10. data/gemfiles/rails-3.1.1 +15 -0
  11. data/lib/sunspot/rails.rb +13 -6
  12. data/lib/sunspot/rails/configuration.rb +25 -4
  13. data/lib/sunspot/rails/log_subscriber.rb +33 -0
  14. data/lib/sunspot/rails/railtie.rb +10 -0
  15. data/lib/sunspot/rails/railties/controller_runtime.rb +36 -0
  16. data/lib/sunspot/rails/searchable.rb +88 -20
  17. data/lib/sunspot/rails/server.rb +10 -77
  18. data/lib/sunspot/rails/solr_instrumentation.rb +20 -0
  19. data/lib/sunspot/rails/solr_logging.rb +16 -17
  20. data/lib/sunspot/rails/stub_session_proxy.rb +56 -2
  21. data/lib/sunspot/rails/tasks.rb +56 -33
  22. data/lib/sunspot_rails.rb +5 -0
  23. data/spec/configuration_spec.rb +27 -5
  24. data/spec/model_lifecycle_spec.rb +1 -1
  25. data/spec/model_spec.rb +240 -1
  26. data/spec/rails_template/app/controllers/application_controller.rb +10 -0
  27. data/spec/rails_template/app/controllers/posts_controller.rb +6 -0
  28. data/spec/rails_template/app/models/author.rb +8 -0
  29. data/spec/rails_template/app/models/blog.rb +12 -0
  30. data/spec/rails_template/app/models/location.rb +2 -0
  31. data/spec/rails_template/app/models/photo_post.rb +2 -0
  32. data/spec/rails_template/app/models/post.rb +11 -0
  33. data/spec/rails_template/app/models/post_with_auto.rb +10 -0
  34. data/spec/rails_template/app/models/post_with_default_scope.rb +11 -0
  35. data/spec/rails_template/config/boot.rb +127 -0
  36. data/spec/rails_template/config/preinitializer.rb +22 -0
  37. data/spec/rails_template/config/routes.rb +9 -0
  38. data/spec/rails_template/config/sunspot.yml +22 -0
  39. data/spec/rails_template/db/schema.rb +27 -0
  40. data/spec/request_lifecycle_spec.rb +1 -1
  41. data/spec/searchable_spec.rb +12 -0
  42. data/spec/server_spec.rb +2 -6
  43. data/spec/session_spec.rb +35 -2
  44. data/spec/shared_examples/indexed_after_save.rb +8 -0
  45. data/spec/shared_examples/not_indexed_after_save.rb +8 -0
  46. data/spec/spec_helper.rb +4 -2
  47. data/spec/stub_session_proxy_spec.rb +2 -2
  48. data/sunspot_rails.gemspec +43 -0
  49. metadata +114 -44
  50. data/lib/sunspot/rails/version.rb +0 -5
@@ -55,18 +55,23 @@ module Sunspot
55
55
  def new_search(*types)
56
56
  Search.new
57
57
  end
58
+
59
+ def new_more_like_this(*args)
60
+ Search.new
61
+ end
58
62
 
59
63
  class Search
64
+
60
65
  def build
61
66
  self
62
67
  end
63
68
 
64
69
  def results
65
- []
70
+ PaginatedCollection.new
66
71
  end
67
72
 
68
73
  def hits(options = {})
69
- []
74
+ PaginatedCollection.new
70
75
  end
71
76
 
72
77
  def total
@@ -83,6 +88,55 @@ module Sunspot
83
88
  self
84
89
  end
85
90
  end
91
+
92
+
93
+ class PaginatedCollection < Array
94
+
95
+ def total_count
96
+ 0
97
+ end
98
+ alias :total_entries :total_count
99
+
100
+ def current_page
101
+ 1
102
+ end
103
+
104
+ def per_page
105
+ 30
106
+ end
107
+ alias :limit_value :per_page
108
+
109
+ def total_pages
110
+ 1
111
+ end
112
+ alias :num_pages :total_pages
113
+
114
+ def first_page?
115
+ true
116
+ end
117
+
118
+ def last_page?
119
+ true
120
+ end
121
+
122
+ def previous_page
123
+ nil
124
+ end
125
+
126
+ def next_page
127
+ nil
128
+ end
129
+
130
+ def out_of_bounds?
131
+ false
132
+ end
133
+
134
+ def offset
135
+ 0
136
+ end
137
+
138
+ end
139
+
86
140
  end
87
141
  end
88
142
  end
@@ -1,28 +1,4 @@
1
1
  namespace :sunspot do
2
- namespace :solr do
3
- desc 'Start the Solr instance'
4
- task :start => :environment do
5
- if RUBY_PLATFORM =~ /w(in)?32$/
6
- abort('This command does not work on Windows. Please use rake sunspot:solr:run to run Solr in the foreground.')
7
- end
8
- Sunspot::Rails::Server.new.start
9
- end
10
-
11
- desc 'Run the Solr instance in the foreground'
12
- task :run => :environment do
13
- Sunspot::Rails::Server.new.run
14
- end
15
-
16
- desc 'Stop the Solr instance'
17
- task :stop => :environment do
18
- if RUBY_PLATFORM =~ /w(in)?32$/
19
- abort('This command does not work on Windows.')
20
- end
21
- Sunspot::Rails::Server.new.stop
22
- end
23
-
24
- task :reindex => :"sunspot:reindex"
25
- end
26
2
 
27
3
  desc "Reindex all solr models that are located in your application's models directory."
28
4
  # This task depends on the standard Rails file naming \
@@ -38,23 +14,70 @@ namespace :sunspot do
38
14
  # $ rake sunspot:reindex[1000,Post] # reindex only the Post model in
39
15
  # # batchs of 1000
40
16
  # $ rake sunspot:reindex[,Post+Author] # reindex Post and Author model
41
- task :reindex, :batch_size, :models, :needs => :environment do |t, args|
42
- reindex_options = {:batch_commit => false}
17
+ task :reindex, [:batch_size, :models] => [:environment] do |t, args|
18
+ # Set up general options for reindexing
19
+ reindex_options = { :batch_commit => false }
20
+
43
21
  case args[:batch_size]
44
22
  when 'false'
45
23
  reindex_options[:batch_size] = nil
46
24
  when /^\d+$/
47
25
  reindex_options[:batch_size] = args[:batch_size].to_i if args[:batch_size].to_i > 0
48
26
  end
49
- unless args[:models]
50
- all_files = Dir.glob(Rails.root.join('app', 'models', '*.rb'))
51
- all_models = all_files.map { |path| File.basename(path, '.rb').camelize.constantize }
52
- sunspot_models = all_models.select { |m| m < ActiveRecord::Base and m.searchable? }
53
- else
54
- sunspot_models = args[:models].split('+').map{|m| m.constantize}
27
+
28
+ # Load all the application's models. Models which invoke 'searchable' will register themselves
29
+ # in Sunspot.searchable.
30
+ Dir.glob(Rails.root.join('app/models/**/*.rb')).each { |path| require path }
31
+
32
+ # By default, reindex all searchable models
33
+ sunspot_models = Sunspot.searchable
34
+
35
+ # Choose a specific subset of models, if requested
36
+ if args[:models]
37
+ model_names = args[:models].split('+')
38
+ sunspot_models = model_names.map{ |m| m.constantize }
39
+ end
40
+
41
+ # Set up progress_bar to, ah, report progress
42
+ begin
43
+ require 'progress_bar'
44
+ total_documents = sunspot_models.map { | m | m.count }.sum
45
+ reindex_options[:progress_bar] = ProgressBar.new(total_documents)
46
+ rescue LoadError => e
47
+ $stderr.puts "Skipping progress bar: for progress reporting, add gem 'progress_bar' to your Gemfile"
48
+ rescue Exception => e
49
+ $stderr.puts "Error using progress bar: #{e.message}"
55
50
  end
51
+
52
+ # Finally, invoke the class-level solr_reindex on each model
56
53
  sunspot_models.each do |model|
57
- model.solr_reindex reindex_options
54
+ model.solr_reindex(reindex_options)
55
+ end
56
+ end
57
+
58
+
59
+ unless defined?(Sunspot::Solr)
60
+ namespace :solr do
61
+ task :moved_to_sunspot_solr do
62
+ abort %(
63
+ Note: This task has been moved to the sunspot_solr gem. To install, start and
64
+ stop a local Solr instance, please add sunspot_solr to your Gemfile:
65
+
66
+ group :development do
67
+ gem 'sunspot_solr'
68
+ end
69
+
70
+ )
71
+ end
72
+
73
+ desc 'Start the Solr instance'
74
+ task :start => :moved_to_sunspot_solr
75
+ desc 'Run the Solr instance in the foreground'
76
+ task :run => :moved_to_sunspot_solr
77
+ desc 'Stop the Solr instance'
78
+ task :stop => :moved_to_sunspot_solr
79
+ # for backwards compatibility
80
+ task :reindex => :"sunspot:reindex"
58
81
  end
59
82
  end
60
83
 
data/lib/sunspot_rails.rb CHANGED
@@ -1,3 +1,8 @@
1
+ # This needs to be loaded before sunspot/search/paginated_collection
2
+ # or #to_json gets defined in Object breaking delegation to Array via
3
+ # method_missing
4
+ require 'active_support/core_ext/object/to_json' if Rails::VERSION::MAJOR == 3
5
+
1
6
  require 'sunspot/rails'
2
7
 
3
8
  if Rails::VERSION::MAJOR == 3
@@ -1,4 +1,4 @@
1
- require File.dirname(__FILE__) + '/spec_helper'
1
+ require File.expand_path('spec_helper', File.dirname(__FILE__))
2
2
 
3
3
  describe Sunspot::Rails::Configuration, "default values without a sunspot.yml" do
4
4
  before(:each) do
@@ -55,9 +55,9 @@ describe Sunspot::Rails::Configuration, "default values without a sunspot.yml" d
55
55
  @config.data_path.should == '/some/path/solr/data/test'
56
56
  end
57
57
 
58
- it "should handle the 'pid_path' property when not set" do
58
+ it "should handle the 'pid_dir' property when not set" do
59
59
  Rails.should_receive(:root).at_least(1).and_return('/some/path')
60
- @config.pid_path.should == '/some/path/solr/pids/test'
60
+ @config.pid_dir.should == '/some/path/solr/pids/test'
61
61
  end
62
62
 
63
63
  it "should handle the 'auto_commit_after_request' propery when not set" do
@@ -67,6 +67,14 @@ describe Sunspot::Rails::Configuration, "default values without a sunspot.yml" d
67
67
  it "should handle the 'auto_commit_after_delete_request' propery when not set" do
68
68
  @config.auto_commit_after_delete_request?.should == false
69
69
  end
70
+
71
+ it "should handle the 'bind_address' property when not set" do
72
+ @config.bind_address.should be_nil
73
+ end
74
+
75
+ it "should handle the 'disabled' property when not set" do
76
+ @config.disabled?.should be_false
77
+ end
70
78
  end
71
79
 
72
80
  describe Sunspot::Rails::Configuration, "user provided sunspot.yml" do
@@ -99,8 +107,8 @@ describe Sunspot::Rails::Configuration, "user provided sunspot.yml" do
99
107
  @config.data_path.should == '/my_superior_path/data'
100
108
  end
101
109
 
102
- it "should handle the 'pid_path' property when set" do
103
- @config.pid_path.should == '/my_superior_path/pids'
110
+ it "should handle the 'pid_dir' property when set" do
111
+ @config.pid_dir.should == '/my_superior_path/pids'
104
112
  end
105
113
 
106
114
  it "should handle the 'solr_home' property when set" do
@@ -114,8 +122,22 @@ describe Sunspot::Rails::Configuration, "user provided sunspot.yml" do
114
122
  it "should handle the 'auto_commit_after_delete_request' propery when set" do
115
123
  @config.auto_commit_after_delete_request?.should == true
116
124
  end
125
+
126
+ it "should handle the 'bind_address' property when set" do
127
+ @config.bind_address.should == "127.0.0.1"
128
+ end
117
129
  end
118
130
 
131
+ describe Sunspot::Rails::Configuration, "with disabled: true in sunspot.yml" do
132
+ before(:each) do
133
+ ::Rails.stub!(:env => 'config_disabled_test')
134
+ @config = Sunspot::Rails::Configuration.new
135
+ end
136
+
137
+ it "should handle the 'disabled' property when set" do
138
+ @config.disabled?.should be_true
139
+ end
140
+ end
119
141
 
120
142
  describe Sunspot::Rails::Configuration, "with ENV['SOLR_URL'] overriding sunspot.yml" do
121
143
  before(:all) do
@@ -1,4 +1,4 @@
1
- require File.dirname(__FILE__) + '/spec_helper'
1
+ require File.expand_path('spec_helper', File.dirname(__FILE__))
2
2
 
3
3
  describe 'searchable with lifecycle' do
4
4
  describe 'on create' do
data/spec/model_spec.rb CHANGED
@@ -1,4 +1,4 @@
1
- require File.dirname(__FILE__) + '/spec_helper'
1
+ require File.expand_path('spec_helper', File.dirname(__FILE__))
2
2
 
3
3
  describe 'ActiveRecord mixin' do
4
4
  describe 'index()' do
@@ -297,6 +297,22 @@ describe 'ActiveRecord mixin' do
297
297
  Post.should_receive(:all).with(:include => :author).and_return([])
298
298
  Post.reindex(:batch_size => nil, :include => :author)
299
299
  end
300
+
301
+ describe ':if constraints' do
302
+ before do
303
+ Post.sunspot_options[:if] = proc { |model| model.id != @posts.first.id }
304
+ end
305
+
306
+ after do
307
+ Post.sunspot_options[:if] = nil
308
+ end
309
+
310
+ it 'should only index those models where :if constraints pass' do
311
+ Post.reindex(:batch_size => nil)
312
+
313
+ Post.search.results.should_not include(@posts.first)
314
+ end
315
+ end
300
316
 
301
317
  end
302
318
 
@@ -310,6 +326,22 @@ describe 'ActiveRecord mixin' do
310
326
  Sunspot.should_receive(:commit).once
311
327
  Post.reindex(:batch_commit => false)
312
328
  end
329
+
330
+ describe ':if constraints' do
331
+ before do
332
+ Post.sunspot_options[:if] = proc { |model| model.id != @posts.first.id }
333
+ end
334
+
335
+ after do
336
+ Post.sunspot_options[:if] = nil
337
+ end
338
+
339
+ it 'should only index those models where :if constraints pass' do
340
+ Post.reindex(:batch_size => 50)
341
+
342
+ Post.search.results.should_not include(@posts.first)
343
+ end
344
+ end
313
345
  end
314
346
  end
315
347
 
@@ -353,4 +385,211 @@ describe 'ActiveRecord mixin' do
353
385
  @posts.first.more_like_this_ids.to_set.should == [@posts[3], @posts[1]].map { |post| post.id }.to_set
354
386
  end
355
387
  end
388
+
389
+ describe ':if constraint' do
390
+ subject do
391
+ PostWithAuto.new(:title => 'Post123')
392
+ end
393
+
394
+ after do
395
+ subject.class.sunspot_options[:if] = nil
396
+ end
397
+
398
+ context 'Symbol' do
399
+ context 'constraint returns true' do
400
+ # searchable :if => :returns_true
401
+ before do
402
+ subject.should_receive(:returns_true).and_return(true)
403
+ subject.class.sunspot_options[:if] = :returns_true
404
+ end
405
+
406
+ it_should_behave_like 'indexed after save'
407
+ end
408
+
409
+ context 'constraint returns false' do
410
+ # searchable :if => :returns_false
411
+ before do
412
+ subject.should_receive(:returns_false).and_return(false)
413
+ subject.class.sunspot_options[:if] = :returns_false
414
+ end
415
+
416
+ it_should_behave_like 'not indexed after save'
417
+ end
418
+ end
419
+
420
+ context 'String' do
421
+ context 'constraint returns true' do
422
+ # searchable :if => 'returns_true'
423
+ before do
424
+ subject.should_receive(:returns_true).and_return(true)
425
+ subject.class.sunspot_options[:if] = 'returns_true'
426
+ end
427
+
428
+ it_should_behave_like 'indexed after save'
429
+ end
430
+
431
+ context 'constraint returns false' do
432
+ # searchable :if => 'returns_false'
433
+ before do
434
+ subject.should_receive(:returns_false).and_return(false)
435
+ subject.class.sunspot_options[:if] = 'returns_false'
436
+ end
437
+
438
+ it_should_behave_like 'not indexed after save'
439
+ end
440
+ end
441
+
442
+ context 'Proc' do
443
+ context 'constraint returns true' do
444
+ # searchable :if => proc { true }
445
+ before do
446
+ subject.class.sunspot_options[:if] = proc { true }
447
+ end
448
+
449
+ it_should_behave_like 'indexed after save'
450
+ end
451
+
452
+ context 'constraint returns false' do
453
+ # searchable :if => proc { false }
454
+ before do
455
+ subject.class.sunspot_options[:if] = proc { false }
456
+ end
457
+
458
+ it_should_behave_like 'not indexed after save'
459
+ end
460
+ end
461
+
462
+ context 'Array' do
463
+ context 'all constraints returns true' do
464
+ # searchable :if => [:returns_true_1, :returns_true_2]
465
+ before do
466
+ subject.should_receive(:returns_true_1).and_return(true)
467
+ subject.should_receive(:returns_true_2).and_return(true)
468
+ subject.class.sunspot_options[:if] = [:returns_true_1, 'returns_true_2']
469
+ end
470
+
471
+ it_should_behave_like 'indexed after save'
472
+ end
473
+
474
+ context 'one constraint returns false' do
475
+ # searchable :if => [:returns_true, :returns_false]
476
+ before do
477
+ subject.should_receive(:returns_true).and_return(true)
478
+ subject.should_receive(:returns_false).and_return(false)
479
+ subject.class.sunspot_options[:if] = [:returns_true, 'returns_false']
480
+ end
481
+
482
+ it_should_behave_like 'not indexed after save'
483
+ end
484
+ end
485
+
486
+ it 'removes the model from the index if the constraint does not match' do
487
+ subject.save!
488
+ Sunspot.commit
489
+ subject.class.search.results.should include(subject)
490
+
491
+ subject.class.sunspot_options[:if] = proc { false }
492
+ subject.save!
493
+ Sunspot.commit
494
+ subject.class.search.results.should_not include(subject)
495
+ end
496
+ end
497
+
498
+ describe ':unless constraint' do
499
+ subject do
500
+ PostWithAuto.new(:title => 'Post123')
501
+ end
502
+
503
+ after do
504
+ subject.class.sunspot_options[:unless] = nil
505
+ end
506
+
507
+ context 'Symbol' do
508
+ context 'constraint returns true' do
509
+ # searchable :unless => :returns_true
510
+ before do
511
+ subject.should_receive(:returns_true).and_return(true)
512
+ subject.class.sunspot_options[:unless] = :returns_true
513
+ end
514
+
515
+ it_should_behave_like 'not indexed after save'
516
+ end
517
+
518
+ context 'constraint returns false' do
519
+ # searchable :unless => :returns_false
520
+ before do
521
+ subject.should_receive(:returns_false).and_return(false)
522
+ subject.class.sunspot_options[:unless] = :returns_false
523
+ end
524
+
525
+ it_should_behave_like 'indexed after save'
526
+ end
527
+ end
528
+
529
+ context 'String' do
530
+ context 'constraint returns true' do
531
+ # searchable :unless => 'returns_true'
532
+ before do
533
+ subject.should_receive(:returns_true).and_return(true)
534
+ subject.class.sunspot_options[:unless] = 'returns_true'
535
+ end
536
+
537
+ it_should_behave_like 'not indexed after save'
538
+ end
539
+
540
+ context 'constraint returns false' do
541
+ # searchable :unless => 'returns_false'
542
+ before do
543
+ subject.should_receive(:returns_false).and_return(false)
544
+ subject.class.sunspot_options[:unless] = 'returns_false'
545
+ end
546
+
547
+ it_should_behave_like 'indexed after save'
548
+ end
549
+ end
550
+
551
+ context 'Proc' do
552
+ context 'constraint returns true' do
553
+ # searchable :unless => proc { true }
554
+ before do
555
+ subject.class.sunspot_options[:unless] = proc { |model| model == subject } # true
556
+ end
557
+
558
+ it_should_behave_like 'not indexed after save'
559
+ end
560
+
561
+ context 'constraint returns false' do
562
+ # searchable :unless => proc { false }
563
+ before do
564
+ subject.class.sunspot_options[:unless] = proc { false }
565
+ end
566
+
567
+ it_should_behave_like 'indexed after save'
568
+ end
569
+ end
570
+
571
+ context 'Array' do
572
+ context 'all constraints returns true' do
573
+ # searchable :unless => [:returns_true_1, :returns_true_2]
574
+ before do
575
+ subject.should_receive(:returns_true_1).and_return(true)
576
+ subject.should_receive(:returns_true_2).and_return(true)
577
+ subject.class.sunspot_options[:unless] = [:returns_true_1, 'returns_true_2']
578
+ end
579
+
580
+ it_should_behave_like 'not indexed after save'
581
+ end
582
+
583
+ context 'one constraint returns false' do
584
+ # searchable :unless => [:returns_true, :returns_false]
585
+ before do
586
+ subject.should_receive(:returns_true).and_return(true)
587
+ subject.should_receive(:returns_false).and_return(false)
588
+ subject.class.sunspot_options[:unless] = [:returns_true, 'returns_false']
589
+ end
590
+
591
+ it_should_behave_like 'indexed after save'
592
+ end
593
+ end
594
+ end
356
595
  end