pg_search 0.2.2 → 0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -2
- data/.rspec +1 -0
- data/.travis.yml +2 -0
- data/CHANGELOG +8 -5
- data/Gemfile +10 -5
- data/README.rdoc +151 -44
- data/Rakefile +9 -24
- data/lib/pg_search.rb +45 -21
- data/lib/pg_search/configuration.rb +13 -1
- data/lib/pg_search/configuration/association.rb +5 -6
- data/lib/pg_search/configuration/column.rb +1 -2
- data/lib/pg_search/document.rb +21 -0
- data/lib/pg_search/features/tsearch.rb +19 -10
- data/lib/pg_search/multisearch.rb +46 -0
- data/lib/pg_search/multisearchable.rb +28 -0
- data/lib/pg_search/railtie.rb +0 -4
- data/lib/pg_search/scope.rb +1 -1
- data/lib/pg_search/scope_options.rb +13 -17
- data/lib/pg_search/tasks.rb +37 -0
- data/lib/pg_search/version.rb +1 -1
- data/pg_search.gemspec +3 -0
- data/spec/associations_spec.rb +209 -230
- data/spec/pg_search/document_spec.rb +49 -0
- data/spec/pg_search/multisearch_spec.rb +66 -0
- data/spec/pg_search/multisearchable_spec.rb +108 -0
- data/spec/pg_search_spec.rb +148 -42
- data/spec/spec_helper.rb +6 -2
- metadata +83 -31
- checksums.yaml +0 -7
- data/TODO +0 -10
- data/gemfiles/Gemfile.common +0 -9
- data/gemfiles/rails2/Gemfile +0 -4
- data/gemfiles/rails3/Gemfile +0 -4
@@ -0,0 +1,49 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe PgSearch::Document do
|
4
|
+
with_table "pg_search_documents", {}, &DOCUMENTS_SCHEMA
|
5
|
+
|
6
|
+
with_model :Searchable do
|
7
|
+
table
|
8
|
+
model do
|
9
|
+
include PgSearch
|
10
|
+
multisearchable({})
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
it { should be_an(ActiveRecord::Base) }
|
15
|
+
|
16
|
+
describe "callbacks" do
|
17
|
+
describe "before_validation" do
|
18
|
+
subject { document }
|
19
|
+
let(:document) { PgSearch::Document.new(:searchable => searchable) }
|
20
|
+
let(:searchable) { Searchable.new }
|
21
|
+
|
22
|
+
before do
|
23
|
+
# Redefine the options for multisearchable
|
24
|
+
Searchable.multisearchable(multisearchable_options)
|
25
|
+
end
|
26
|
+
|
27
|
+
context "when searching against a single column" do
|
28
|
+
let(:multisearchable_options) { {:against => :some_content} }
|
29
|
+
let(:text) { "foo bar" }
|
30
|
+
before do
|
31
|
+
searchable.stub!(:some_content => text)
|
32
|
+
document.valid?
|
33
|
+
end
|
34
|
+
|
35
|
+
its(:content) { should == text }
|
36
|
+
end
|
37
|
+
|
38
|
+
context "when searching against multiple columns" do
|
39
|
+
let(:multisearchable_options) { {:against => [:attr1, :attr2]} }
|
40
|
+
before do
|
41
|
+
searchable.stub!(:attr1 => "1", :attr2 => "2")
|
42
|
+
document.valid?
|
43
|
+
end
|
44
|
+
|
45
|
+
its(:content) { should == "1 2" }
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe PgSearch::Multisearch do
|
4
|
+
with_table "pg_search_documents", {}, &DOCUMENTS_SCHEMA
|
5
|
+
|
6
|
+
with_model :MultisearchableModel do
|
7
|
+
table do |t|
|
8
|
+
t.string :title
|
9
|
+
t.text :content
|
10
|
+
t.timestamps
|
11
|
+
end
|
12
|
+
model do
|
13
|
+
include PgSearch
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe ".rebuild" do
|
18
|
+
it "should fetch the proper columns from the model" do
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe ".rebuild_sql" do
|
23
|
+
context "with one attribute" do
|
24
|
+
it "should generate the proper SQL code" do
|
25
|
+
model = MultisearchableModel
|
26
|
+
connection = model.connection
|
27
|
+
|
28
|
+
model.multisearchable :against => :title
|
29
|
+
|
30
|
+
expected_sql = <<-SQL
|
31
|
+
INSERT INTO #{PgSearch::Document.quoted_table_name} (searchable_type, searchable_id, content)
|
32
|
+
SELECT #{connection.quote(model.name)} AS searchable_type,
|
33
|
+
#{model.quoted_table_name}.id AS searchable_id,
|
34
|
+
(
|
35
|
+
coalesce(#{model.quoted_table_name}.title, '')
|
36
|
+
) AS content
|
37
|
+
FROM #{model.quoted_table_name}
|
38
|
+
SQL
|
39
|
+
|
40
|
+
PgSearch::Multisearch.rebuild_sql(MultisearchableModel).should == expected_sql
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context "with multiple attributes" do
|
45
|
+
it "should generate the proper SQL code" do
|
46
|
+
model = MultisearchableModel
|
47
|
+
connection = model.connection
|
48
|
+
|
49
|
+
model.multisearchable :against => [:title, :content]
|
50
|
+
|
51
|
+
expected_sql = <<-SQL
|
52
|
+
INSERT INTO #{PgSearch::Document.quoted_table_name} (searchable_type, searchable_id, content)
|
53
|
+
SELECT #{connection.quote(model.name)} AS searchable_type,
|
54
|
+
#{model.quoted_table_name}.id AS searchable_id,
|
55
|
+
(
|
56
|
+
coalesce(#{model.quoted_table_name}.title, '') || ' ' || coalesce(#{model.quoted_table_name}.content, '')
|
57
|
+
) AS content
|
58
|
+
FROM #{model.quoted_table_name}
|
59
|
+
SQL
|
60
|
+
|
61
|
+
PgSearch::Multisearch.rebuild_sql(MultisearchableModel).should == expected_sql
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe PgSearch::Multisearchable do
|
4
|
+
with_table "pg_search_documents", {}, &DOCUMENTS_SCHEMA
|
5
|
+
|
6
|
+
before { PgSearch.stub(:multisearch_enabled?) { true } }
|
7
|
+
|
8
|
+
describe "a model that is multisearchable" do
|
9
|
+
subject { ModelThatIsMultisearchable }
|
10
|
+
|
11
|
+
with_model :ModelThatIsMultisearchable do
|
12
|
+
table do |t|
|
13
|
+
end
|
14
|
+
model do
|
15
|
+
include PgSearch
|
16
|
+
multisearchable
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "callbacks" do
|
21
|
+
describe "after_create" do
|
22
|
+
let(:record) { ModelThatIsMultisearchable.new }
|
23
|
+
|
24
|
+
describe "saving the record" do
|
25
|
+
subject do
|
26
|
+
lambda { record.save! }
|
27
|
+
end
|
28
|
+
|
29
|
+
context "with multisearch enabled" do
|
30
|
+
before { PgSearch.stub(:multisearch_enabled?) { true } }
|
31
|
+
it { should change(PgSearch::Document, :count).by(1) }
|
32
|
+
end
|
33
|
+
|
34
|
+
context "with multisearch disabled" do
|
35
|
+
before { PgSearch.stub(:multisearch_enabled?) { false } }
|
36
|
+
it { should_not change(PgSearch::Document, :count) }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe "the document" do
|
41
|
+
subject { document }
|
42
|
+
before { record.save! }
|
43
|
+
let(:document) { PgSearch::Document.last }
|
44
|
+
|
45
|
+
its(:searchable) { should == record }
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe "after_update" do
|
50
|
+
let!(:record) { ModelThatIsMultisearchable.create! }
|
51
|
+
|
52
|
+
context "when the document is present" do
|
53
|
+
describe "saving the record" do
|
54
|
+
subject do
|
55
|
+
lambda { record.save! }
|
56
|
+
end
|
57
|
+
|
58
|
+
context "with multisearch enabled" do
|
59
|
+
before { PgSearch.stub(:multisearch_enabled?) { true } }
|
60
|
+
|
61
|
+
before { record.pg_search_document.should_receive(:save) }
|
62
|
+
it { should_not change(PgSearch::Document, :count) }
|
63
|
+
end
|
64
|
+
|
65
|
+
context "with multisearch disabled" do
|
66
|
+
before { PgSearch.stub(:multisearch_enabled?) { false } }
|
67
|
+
|
68
|
+
before { record.pg_search_document.should_not_receive(:save) }
|
69
|
+
it { should_not change(PgSearch::Document, :count) }
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
context "when the document is missing" do
|
75
|
+
before { record.pg_search_document = nil }
|
76
|
+
|
77
|
+
describe "saving the record" do
|
78
|
+
subject do
|
79
|
+
lambda { record.save! }
|
80
|
+
end
|
81
|
+
|
82
|
+
context "with multisearch enabled" do
|
83
|
+
before { PgSearch.stub(:multisearch_enabled?) { true } }
|
84
|
+
it { should change(PgSearch::Document, :count).by(1) }
|
85
|
+
end
|
86
|
+
|
87
|
+
context "with multisearch disabled" do
|
88
|
+
before { PgSearch.stub(:multisearch_enabled?) { false } }
|
89
|
+
it { should_not change(PgSearch::Document, :count) }
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
describe "after_destroy" do
|
96
|
+
it "should remove its document" do
|
97
|
+
record = ModelThatIsMultisearchable.create!
|
98
|
+
document = record.pg_search_document
|
99
|
+
|
100
|
+
lambda { record.destroy }.should change(PgSearch::Document, :count).by(-1)
|
101
|
+
lambda {
|
102
|
+
PgSearch::Document.find(document.id)
|
103
|
+
}.should raise_error(ActiveRecord::RecordNotFound)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
data/spec/pg_search_spec.rb
CHANGED
@@ -369,6 +369,48 @@ describe "an ActiveRecord model which includes PgSearch" do
|
|
369
369
|
results.should =~ included
|
370
370
|
end
|
371
371
|
end
|
372
|
+
|
373
|
+
describe "ranking" do
|
374
|
+
before do
|
375
|
+
["Strip Down", "Down", "Down and Out", "Won't Let You Down"].each do |name|
|
376
|
+
ModelWithPgSearch.create! :content => name
|
377
|
+
end
|
378
|
+
end
|
379
|
+
|
380
|
+
context "with a normalization specified" do
|
381
|
+
before do
|
382
|
+
ModelWithPgSearch.class_eval do
|
383
|
+
pg_search_scope :search_content_with_normalization,
|
384
|
+
:against => :content,
|
385
|
+
:using => {
|
386
|
+
:tsearch => {:normalization => 2}
|
387
|
+
}
|
388
|
+
end
|
389
|
+
end
|
390
|
+
it "ranks the results for documents with less text higher" do
|
391
|
+
results = ModelWithPgSearch.search_content_with_normalization("down")
|
392
|
+
|
393
|
+
results.map(&:content).should == ["Down", "Strip Down", "Down and Out", "Won't Let You Down"]
|
394
|
+
results.first.rank.should be > results.last.rank
|
395
|
+
end
|
396
|
+
end
|
397
|
+
|
398
|
+
context "with no normalization" do
|
399
|
+
before do
|
400
|
+
ModelWithPgSearch.class_eval do
|
401
|
+
pg_search_scope :search_content_without_normalization,
|
402
|
+
:against => :content,
|
403
|
+
:using => :tsearch
|
404
|
+
end
|
405
|
+
end
|
406
|
+
it "ranks the results equally" do
|
407
|
+
results = ModelWithPgSearch.search_content_without_normalization("down")
|
408
|
+
|
409
|
+
results.map(&:content).should == ["Strip Down", "Down", "Down and Out", "Won't Let You Down"]
|
410
|
+
results.first.rank.should == results.last.rank
|
411
|
+
end
|
412
|
+
end
|
413
|
+
end
|
372
414
|
|
373
415
|
context "against columns ranked with arrays" do
|
374
416
|
before do
|
@@ -420,6 +462,33 @@ describe "an ActiveRecord model which includes PgSearch" do
|
|
420
462
|
results.should == [winner, loser]
|
421
463
|
end
|
422
464
|
end
|
465
|
+
|
466
|
+
context "searching any_word option" do
|
467
|
+
before do
|
468
|
+
ModelWithPgSearch.class_eval do
|
469
|
+
pg_search_scope :search_title_with_any_word,
|
470
|
+
:against => :title,
|
471
|
+
:using => {
|
472
|
+
:tsearch => {:any_word => true}
|
473
|
+
}
|
474
|
+
|
475
|
+
pg_search_scope :search_title_with_all_words,
|
476
|
+
:against => :title
|
477
|
+
end
|
478
|
+
end
|
479
|
+
|
480
|
+
it "returns all results containing any word in their title" do
|
481
|
+
numbers = %w(one two three four).map{|number| ModelWithPgSearch.create!(:title => number)}
|
482
|
+
|
483
|
+
results = ModelWithPgSearch.search_title_with_any_word("one two three four")
|
484
|
+
|
485
|
+
results.map(&:title).should == %w(one two three four)
|
486
|
+
|
487
|
+
results = ModelWithPgSearch.search_title_with_all_words("one two three four")
|
488
|
+
|
489
|
+
results.map(&:title).should == []
|
490
|
+
end
|
491
|
+
end
|
423
492
|
end
|
424
493
|
|
425
494
|
context "using dmetaphone" do
|
@@ -523,48 +592,6 @@ describe "an ActiveRecord model which includes PgSearch" do
|
|
523
592
|
end
|
524
593
|
end
|
525
594
|
|
526
|
-
context "using a tsvector column" do
|
527
|
-
with_model :ModelWithPgSearchUsingTsVectorColumn do
|
528
|
-
table do |t|
|
529
|
-
t.text 'content'
|
530
|
-
t.column 'content_tsvector', :tsvector
|
531
|
-
end
|
532
|
-
|
533
|
-
model { include PgSearch }
|
534
|
-
end
|
535
|
-
|
536
|
-
let!(:expected) { ModelWithPgSearchUsingTsVectorColumn.create!(:content => 'tiling is grouty') }
|
537
|
-
let!(:unexpected) { ModelWithPgSearchUsingTsVectorColumn.create!(:content => 'longcat is looooooooong') }
|
538
|
-
|
539
|
-
before do
|
540
|
-
ActiveRecord::Base.connection.execute <<-SQL
|
541
|
-
UPDATE #{ModelWithPgSearchUsingTsVectorColumn.table_name}
|
542
|
-
SET content_tsvector = to_tsvector('english'::regconfig, "#{ModelWithPgSearchUsingTsVectorColumn.table_name}"."content")
|
543
|
-
SQL
|
544
|
-
|
545
|
-
ModelWithPgSearchUsingTsVectorColumn.class_eval do
|
546
|
-
pg_search_scope :search_by_content_with_tsvector,
|
547
|
-
:against => :content,
|
548
|
-
:using => {
|
549
|
-
:tsearch => {
|
550
|
-
:tsvector_column => 'content_tsvector',
|
551
|
-
:dictionary => 'english'
|
552
|
-
}
|
553
|
-
}
|
554
|
-
end
|
555
|
-
end
|
556
|
-
|
557
|
-
if defined?(ActiveRecord::Relation)
|
558
|
-
it "should not use to_tsvector in the query" do
|
559
|
-
ModelWithPgSearchUsingTsVectorColumn.search_by_content_with_tsvector("tiles").to_sql.should_not =~ /to_tsvector/
|
560
|
-
end
|
561
|
-
end
|
562
|
-
|
563
|
-
it "should find the expected result" do
|
564
|
-
ModelWithPgSearchUsingTsVectorColumn.search_by_content_with_tsvector("tiles").map(&:id).should == [expected.id]
|
565
|
-
end
|
566
|
-
end
|
567
|
-
|
568
595
|
context "ignoring accents" do
|
569
596
|
before do
|
570
597
|
ModelWithPgSearch.class_eval do
|
@@ -645,4 +672,83 @@ describe "an ActiveRecord model which includes PgSearch" do
|
|
645
672
|
end
|
646
673
|
end
|
647
674
|
end
|
675
|
+
|
676
|
+
describe ".multisearchable" do
|
677
|
+
it "should include the Multisearchable module" do
|
678
|
+
ModelWithPgSearch.should_receive(:include).with(PgSearch::Multisearchable)
|
679
|
+
ModelWithPgSearch.multisearchable
|
680
|
+
end
|
681
|
+
|
682
|
+
it "should set pg_search_multisearchable_options on the class" do
|
683
|
+
options = double(:options)
|
684
|
+
ModelWithPgSearch.multisearchable(options)
|
685
|
+
ModelWithPgSearch.pg_search_multisearchable_options.should == options
|
686
|
+
end
|
687
|
+
end
|
688
|
+
|
689
|
+
describe ".multisearch" do
|
690
|
+
subject { PgSearch.multisearch(query) }
|
691
|
+
let(:query) { double(:query) }
|
692
|
+
let(:relation) { double(:relation) }
|
693
|
+
before do
|
694
|
+
PgSearch::Document.should_receive(:search).with(query).and_return(relation)
|
695
|
+
end
|
696
|
+
|
697
|
+
it { should == relation }
|
698
|
+
end
|
699
|
+
|
700
|
+
describe ".disable_multisearch" do
|
701
|
+
it "should temporarily disable multisearch" do
|
702
|
+
@multisearch_enabled_before = PgSearch.multisearch_enabled?
|
703
|
+
PgSearch.disable_multisearch do
|
704
|
+
@multisearch_enabled_inside = PgSearch.multisearch_enabled?
|
705
|
+
end
|
706
|
+
@multisearch_enabled_after = PgSearch.multisearch_enabled?
|
707
|
+
|
708
|
+
@multisearch_enabled_before.should be(true)
|
709
|
+
@multisearch_enabled_inside.should be(false)
|
710
|
+
@multisearch_enabled_after.should be(true)
|
711
|
+
end
|
712
|
+
|
713
|
+
it "should reenable multisearch after an error" do
|
714
|
+
@multisearch_enabled_before = PgSearch.multisearch_enabled?
|
715
|
+
begin
|
716
|
+
PgSearch.disable_multisearch do
|
717
|
+
@multisearch_enabled_inside = PgSearch.multisearch_enabled?
|
718
|
+
raise
|
719
|
+
end
|
720
|
+
rescue
|
721
|
+
end
|
722
|
+
|
723
|
+
@multisearch_enabled_after = PgSearch.multisearch_enabled?
|
724
|
+
|
725
|
+
@multisearch_enabled_before.should be(true)
|
726
|
+
@multisearch_enabled_inside.should be(false)
|
727
|
+
@multisearch_enabled_after.should be(true)
|
728
|
+
end
|
729
|
+
|
730
|
+
it "should not disable multisearch on other threads" do
|
731
|
+
values = Queue.new
|
732
|
+
sync = Queue.new
|
733
|
+
Thread.new do
|
734
|
+
values.push PgSearch.multisearch_enabled?
|
735
|
+
sync.pop # wait
|
736
|
+
values.push PgSearch.multisearch_enabled?
|
737
|
+
sync.pop # wait
|
738
|
+
values.push PgSearch.multisearch_enabled?
|
739
|
+
end
|
740
|
+
|
741
|
+
@multisearch_enabled_before = values.pop
|
742
|
+
PgSearch.disable_multisearch do
|
743
|
+
sync.push :go
|
744
|
+
@multisearch_enabled_inside = values.pop
|
745
|
+
end
|
746
|
+
sync.push :go
|
747
|
+
@multisearch_enabled_after = values.pop
|
748
|
+
|
749
|
+
@multisearch_enabled_before.should be(true)
|
750
|
+
@multisearch_enabled_inside.should be(true)
|
751
|
+
@multisearch_enabled_after.should be(true)
|
752
|
+
end
|
753
|
+
end
|
648
754
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -4,6 +4,7 @@ require "pg_search"
|
|
4
4
|
begin
|
5
5
|
ActiveRecord::Base.establish_connection(:adapter => 'postgresql',
|
6
6
|
:database => 'pg_search_test',
|
7
|
+
:username => ('postgres' if ENV["TRAVIS"]),
|
7
8
|
:min_messages => 'warning')
|
8
9
|
connection = ActiveRecord::Base.connection
|
9
10
|
connection.execute("SELECT 1")
|
@@ -45,8 +46,11 @@ RSpec.configure do |config|
|
|
45
46
|
config.extend WithModel
|
46
47
|
end
|
47
48
|
|
48
|
-
|
49
|
-
|
49
|
+
RSpec::Matchers::OperatorMatcher.register(ActiveRecord::Relation, '=~', RSpec::Matchers::MatchArray)
|
50
|
+
|
51
|
+
DOCUMENTS_SCHEMA = lambda do |t|
|
52
|
+
t.belongs_to :searchable, :polymorphic => true
|
53
|
+
t.text :content
|
50
54
|
end
|
51
55
|
|
52
56
|
require 'irb'
|