sunspot_rails 1.2.1 → 1.3.0.rc6

Sign up to get free protection for your applications and to get access to all the features.
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