sunspot 2.0.0 → 2.5.0
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.
- checksums.yaml +7 -0
- data/.gitignore +1 -0
- data/.rspec +2 -0
- data/Appraisals +7 -0
- data/Gemfile +0 -2
- data/History.txt +10 -0
- data/lib/sunspot.rb +55 -17
- data/lib/sunspot/adapters.rb +68 -18
- data/lib/sunspot/batcher.rb +1 -1
- data/lib/sunspot/configuration.rb +4 -2
- data/lib/sunspot/data_extractor.rb +36 -6
- data/lib/sunspot/dsl.rb +4 -3
- data/lib/sunspot/dsl/adjustable.rb +2 -2
- data/lib/sunspot/dsl/field_query.rb +69 -16
- data/lib/sunspot/dsl/field_stats.rb +25 -0
- data/lib/sunspot/dsl/fields.rb +28 -8
- data/lib/sunspot/dsl/fulltext.rb +9 -1
- data/lib/sunspot/dsl/group.rb +118 -0
- data/lib/sunspot/dsl/paginatable.rb +4 -1
- data/lib/sunspot/dsl/scope.rb +19 -10
- data/lib/sunspot/dsl/search.rb +1 -1
- data/lib/sunspot/dsl/spellcheckable.rb +14 -0
- data/lib/sunspot/dsl/standard_query.rb +63 -35
- data/lib/sunspot/field.rb +76 -4
- data/lib/sunspot/field_factory.rb +60 -11
- data/lib/sunspot/indexer.rb +70 -18
- data/lib/sunspot/query.rb +5 -4
- data/lib/sunspot/query/abstract_field_facet.rb +0 -2
- data/lib/sunspot/query/abstract_fulltext.rb +76 -0
- data/lib/sunspot/query/abstract_json_field_facet.rb +70 -0
- data/lib/sunspot/query/bbox.rb +5 -1
- data/lib/sunspot/query/common_query.rb +31 -6
- data/lib/sunspot/query/composite_fulltext.rb +58 -8
- data/lib/sunspot/query/date_field_json_facet.rb +25 -0
- data/lib/sunspot/query/dismax.rb +25 -71
- data/lib/sunspot/query/field_json_facet.rb +19 -0
- data/lib/sunspot/query/field_list.rb +15 -0
- data/lib/sunspot/query/field_stats.rb +61 -0
- data/lib/sunspot/query/function_query.rb +1 -2
- data/lib/sunspot/query/geo.rb +1 -1
- data/lib/sunspot/query/geofilt.rb +8 -3
- data/lib/sunspot/query/group.rb +46 -0
- data/lib/sunspot/query/group_query.rb +17 -0
- data/lib/sunspot/query/join.rb +88 -0
- data/lib/sunspot/query/more_like_this.rb +1 -1
- data/lib/sunspot/query/pagination.rb +12 -4
- data/lib/sunspot/query/range_json_facet.rb +28 -0
- data/lib/sunspot/query/restriction.rb +99 -13
- data/lib/sunspot/query/sort.rb +41 -0
- data/lib/sunspot/query/sort_composite.rb +7 -0
- data/lib/sunspot/query/spellcheck.rb +19 -0
- data/lib/sunspot/query/standard_query.rb +24 -2
- data/lib/sunspot/query/text_field_boost.rb +1 -3
- data/lib/sunspot/schema.rb +12 -3
- data/lib/sunspot/search.rb +4 -2
- data/lib/sunspot/search/abstract_search.rb +93 -43
- data/lib/sunspot/search/cursor_paginated_collection.rb +32 -0
- data/lib/sunspot/search/field_facet.rb +4 -4
- data/lib/sunspot/search/field_json_facet.rb +33 -0
- data/lib/sunspot/search/field_stats.rb +21 -0
- data/lib/sunspot/search/hit.rb +6 -1
- data/lib/sunspot/search/hit_enumerable.rb +4 -1
- data/lib/sunspot/search/json_facet_row.rb +40 -0
- data/lib/sunspot/search/json_facet_stats.rb +23 -0
- data/lib/sunspot/search/paginated_collection.rb +1 -0
- data/lib/sunspot/search/query_group.rb +74 -0
- data/lib/sunspot/search/standard_search.rb +70 -3
- data/lib/sunspot/search/stats_facet.rb +25 -0
- data/lib/sunspot/search/stats_json_row.rb +82 -0
- data/lib/sunspot/search/stats_row.rb +68 -0
- data/lib/sunspot/session.rb +62 -37
- data/lib/sunspot/session_proxy/class_sharding_session_proxy.rb +6 -4
- data/lib/sunspot/session_proxy/id_sharding_session_proxy.rb +16 -8
- data/lib/sunspot/session_proxy/master_slave_session_proxy.rb +2 -2
- data/lib/sunspot/session_proxy/retry_5xx_session_proxy.rb +1 -1
- data/lib/sunspot/session_proxy/sharding_session_proxy.rb +4 -2
- data/lib/sunspot/session_proxy/silent_fail_session_proxy.rb +1 -1
- data/lib/sunspot/session_proxy/thread_local_session_proxy.rb +6 -4
- data/lib/sunspot/setup.rb +42 -0
- data/lib/sunspot/type.rb +20 -0
- data/lib/sunspot/util.rb +78 -14
- data/lib/sunspot/version.rb +1 -1
- data/spec/api/adapters_spec.rb +40 -15
- data/spec/api/batcher_spec.rb +15 -15
- data/spec/api/binding_spec.rb +3 -3
- data/spec/api/class_set_spec.rb +6 -6
- data/spec/api/data_extractor_spec.rb +39 -0
- data/spec/api/hit_enumerable_spec.rb +32 -9
- data/spec/api/indexer/attributes_spec.rb +35 -30
- data/spec/api/indexer/batch_spec.rb +8 -7
- data/spec/api/indexer/dynamic_fields_spec.rb +8 -8
- data/spec/api/indexer/fixed_fields_spec.rb +16 -11
- data/spec/api/indexer/fulltext_spec.rb +8 -8
- data/spec/api/indexer/removal_spec.rb +24 -14
- data/spec/api/indexer_spec.rb +2 -2
- data/spec/api/query/advanced_manipulation_examples.rb +3 -3
- data/spec/api/query/connectives_examples.rb +26 -14
- data/spec/api/query/dsl_spec.rb +24 -6
- data/spec/api/query/dynamic_fields_examples.rb +18 -18
- data/spec/api/query/faceting_examples.rb +80 -61
- data/spec/api/query/fulltext_examples.rb +194 -40
- data/spec/api/query/function_spec.rb +116 -13
- data/spec/api/query/geo_examples.rb +8 -12
- data/spec/api/query/group_spec.rb +27 -5
- data/spec/api/query/highlighting_examples.rb +26 -26
- data/spec/api/query/join_spec.rb +19 -0
- data/spec/api/query/more_like_this_spec.rb +40 -27
- data/spec/api/query/ordering_pagination_examples.rb +37 -23
- data/spec/api/query/scope_examples.rb +39 -39
- data/spec/api/query/spatial_examples.rb +3 -3
- data/spec/api/query/spellcheck_examples.rb +20 -0
- data/spec/api/query/standard_spec.rb +3 -1
- data/spec/api/query/stats_examples.rb +66 -0
- data/spec/api/query/text_field_scoping_examples.rb +5 -5
- data/spec/api/query/types_spec.rb +4 -4
- data/spec/api/search/cursor_paginated_collection_spec.rb +35 -0
- data/spec/api/search/dynamic_fields_spec.rb +4 -4
- data/spec/api/search/faceting_spec.rb +55 -52
- data/spec/api/search/highlighting_spec.rb +7 -7
- data/spec/api/search/hits_spec.rb +43 -29
- data/spec/api/search/paginated_collection_spec.rb +19 -18
- data/spec/api/search/results_spec.rb +13 -13
- data/spec/api/search/search_spec.rb +3 -3
- data/spec/api/search/stats_spec.rb +94 -0
- data/spec/api/session_proxy/class_sharding_session_proxy_spec.rb +23 -16
- data/spec/api/session_proxy/id_sharding_session_proxy_spec.rb +16 -4
- data/spec/api/session_proxy/master_slave_session_proxy_spec.rb +10 -6
- data/spec/api/session_proxy/retry_5xx_session_proxy_spec.rb +11 -11
- data/spec/api/session_proxy/sharding_session_proxy_spec.rb +15 -14
- data/spec/api/session_proxy/silent_fail_session_proxy_spec.rb +3 -3
- data/spec/api/session_proxy/spec_helper.rb +1 -1
- data/spec/api/session_proxy/thread_local_session_proxy_spec.rb +40 -26
- data/spec/api/session_spec.rb +78 -38
- data/spec/api/sunspot_spec.rb +7 -4
- data/spec/helpers/integration_helper.rb +11 -1
- data/spec/helpers/query_helper.rb +1 -1
- data/spec/helpers/search_helper.rb +30 -0
- data/spec/integration/atomic_updates_spec.rb +58 -0
- data/spec/integration/dynamic_fields_spec.rb +31 -20
- data/spec/integration/faceting_spec.rb +252 -39
- data/spec/integration/field_grouping_spec.rb +47 -15
- data/spec/integration/field_lists_spec.rb +57 -0
- data/spec/integration/geospatial_spec.rb +34 -8
- data/spec/integration/highlighting_spec.rb +8 -8
- data/spec/integration/indexing_spec.rb +7 -6
- data/spec/integration/join_spec.rb +45 -0
- data/spec/integration/keyword_search_spec.rb +68 -38
- data/spec/integration/local_search_spec.rb +4 -4
- data/spec/integration/more_like_this_spec.rb +7 -7
- data/spec/integration/scoped_search_spec.rb +193 -74
- data/spec/integration/spellcheck_spec.rb +119 -0
- data/spec/integration/stats_spec.rb +88 -0
- data/spec/integration/stored_fields_spec.rb +1 -1
- data/spec/integration/test_pagination.rb +4 -4
- data/spec/integration/unicode_spec.rb +1 -1
- data/spec/mocks/adapters.rb +36 -0
- data/spec/mocks/connection.rb +5 -3
- data/spec/mocks/photo.rb +32 -1
- data/spec/mocks/post.rb +18 -3
- data/spec/spec_helper.rb +13 -8
- data/sunspot.gemspec +6 -4
- data/tasks/rdoc.rake +22 -14
- metadata +101 -44
- data/lib/sunspot/dsl/field_group.rb +0 -57
- data/lib/sunspot/query/field_group.rb +0 -37
|
@@ -19,8 +19,8 @@ describe "field grouping" do
|
|
|
19
19
|
group :title
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
-
search.group(:title).groups.
|
|
23
|
-
search.group(:title).groups.
|
|
22
|
+
expect(search.group(:title).groups).to include { |g| g.value == "Title1" }
|
|
23
|
+
expect(search.group(:title).groups).to include { |g| g.value == "Title2" }
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
it "returns the number of matches unique groups" do
|
|
@@ -28,7 +28,7 @@ describe "field grouping" do
|
|
|
28
28
|
group :title
|
|
29
29
|
end
|
|
30
30
|
|
|
31
|
-
search.group(:title).total.
|
|
31
|
+
expect(search.group(:title).total).to eq(2)
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
it "provides access to the number of matches before grouping" do
|
|
@@ -36,7 +36,7 @@ describe "field grouping" do
|
|
|
36
36
|
group :title
|
|
37
37
|
end
|
|
38
38
|
|
|
39
|
-
search.group(:title).matches.
|
|
39
|
+
expect(search.group(:title).matches).to eq(@posts.length)
|
|
40
40
|
end
|
|
41
41
|
|
|
42
42
|
it "allows grouping by multiple fields" do
|
|
@@ -44,8 +44,8 @@ describe "field grouping" do
|
|
|
44
44
|
group :title, :sort_title
|
|
45
45
|
end
|
|
46
46
|
|
|
47
|
-
search.group(:title).groups.
|
|
48
|
-
search.group(:sort_title).groups.
|
|
47
|
+
expect(search.group(:title).groups).not_to be_empty
|
|
48
|
+
expect(search.group(:sort_title).groups).not_to be_empty
|
|
49
49
|
end
|
|
50
50
|
|
|
51
51
|
it "allows specification of the number of documents per group" do
|
|
@@ -56,7 +56,7 @@ describe "field grouping" do
|
|
|
56
56
|
end
|
|
57
57
|
|
|
58
58
|
title1_group = search.group(:title).groups.detect { |g| g.value == "Title1" }
|
|
59
|
-
title1_group.hits.length.
|
|
59
|
+
expect(title1_group.hits.length).to eq(2)
|
|
60
60
|
end
|
|
61
61
|
|
|
62
62
|
it "allows specification of the sort within groups" do
|
|
@@ -69,7 +69,20 @@ describe "field grouping" do
|
|
|
69
69
|
highest_ranked_post = @posts.sort_by { |p| -p.ratings_average }.first
|
|
70
70
|
|
|
71
71
|
title1_group = search.group(:title).groups.detect { |g| g.value == "Title1" }
|
|
72
|
-
title1_group.hits.first.primary_key.to_i.
|
|
72
|
+
expect(title1_group.hits.first.primary_key.to_i).to eq(highest_ranked_post.id)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
it "allows specification of an ordering function within groups" do
|
|
76
|
+
search = Sunspot.search(Post) do
|
|
77
|
+
group :title do
|
|
78
|
+
order_by_function(:product, :average_rating, -2, :asc)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
highest_ranked_post = @posts.sort_by { |p| -p.ratings_average }.first
|
|
83
|
+
|
|
84
|
+
title1_group = search.group(:title).groups.detect { |g| g.value == "Title1" }
|
|
85
|
+
expect(title1_group.hits.first.primary_key.to_i).to eq(highest_ranked_post.id)
|
|
73
86
|
end
|
|
74
87
|
|
|
75
88
|
it "allows pagination within groups" do
|
|
@@ -78,8 +91,27 @@ describe "field grouping" do
|
|
|
78
91
|
paginate :per_page => 1, :page => 2
|
|
79
92
|
end
|
|
80
93
|
|
|
81
|
-
search.group(:title).groups.length.
|
|
82
|
-
search.group(:title).groups.first.results.
|
|
94
|
+
expect(search.group(:title).groups.length).to eql(1)
|
|
95
|
+
expect(search.group(:title).groups.first.results).to eq([ @posts.last ])
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
context "returns a not paginated collection" do
|
|
99
|
+
subject do
|
|
100
|
+
search = Sunspot.search(Post) do
|
|
101
|
+
group :title do
|
|
102
|
+
ngroups false
|
|
103
|
+
end
|
|
104
|
+
paginate :per_page => 1, :page => 2
|
|
105
|
+
|
|
106
|
+
end
|
|
107
|
+
search.group(:title).groups
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
it { expect(subject.per_page).to eql(1) }
|
|
111
|
+
it { expect(subject.total_pages).to eql(0) }
|
|
112
|
+
it { expect(subject.current_page).to eql(2) }
|
|
113
|
+
it { expect(subject.first_page?).to be(false) }
|
|
114
|
+
it { expect(subject.last_page?).to be(true) }
|
|
83
115
|
end
|
|
84
116
|
|
|
85
117
|
context "returns a paginated collection" do
|
|
@@ -91,10 +123,10 @@ describe "field grouping" do
|
|
|
91
123
|
search.group(:title).groups
|
|
92
124
|
end
|
|
93
125
|
|
|
94
|
-
it { subject.per_page.
|
|
95
|
-
it { subject.total_pages.
|
|
96
|
-
it { subject.current_page.
|
|
97
|
-
it { subject.first_page
|
|
98
|
-
it { subject.last_page
|
|
126
|
+
it { expect(subject.per_page).to eql(1) }
|
|
127
|
+
it { expect(subject.total_pages).to eql(2) }
|
|
128
|
+
it { expect(subject.current_page).to eql(2) }
|
|
129
|
+
it { expect(subject.first_page?).to be(false) }
|
|
130
|
+
it { expect(subject.last_page?).to be(true) }
|
|
99
131
|
end
|
|
100
132
|
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
require File.expand_path('../spec_helper', File.dirname(__FILE__))
|
|
2
|
+
|
|
3
|
+
describe 'fields lists' do
|
|
4
|
+
before :all do
|
|
5
|
+
Sunspot.remove_all
|
|
6
|
+
@post = Post.new(title: 'A Title', body: 'A Body', featured: true, tags: ['tag'])
|
|
7
|
+
Sunspot.index!(@post)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
let(:stored_field_names) do
|
|
11
|
+
(Sunspot::Setup.for(Post).fields + Sunspot::Setup.for(Post).all_text_fields)
|
|
12
|
+
.select { |f| f.stored? }
|
|
13
|
+
.map { |f| f.name }
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it 'loads all stored fields by dafault' do
|
|
17
|
+
hit = Sunspot.search(Post).hits.first
|
|
18
|
+
|
|
19
|
+
stored_field_names.each do |field|
|
|
20
|
+
expect(hit.stored(field)).not_to be_nil
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it 'loads only filtered fields' do
|
|
25
|
+
hit = Sunspot.search(Post) { field_list(:title) }.hits.first
|
|
26
|
+
|
|
27
|
+
expect(hit.stored(:title)).to eq(@post.title)
|
|
28
|
+
|
|
29
|
+
(stored_field_names - [:title]).each do |field|
|
|
30
|
+
expect(hit.stored(field)).to be_nil
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it 'does not raise Sunspot::UnrecognizedFieldError when listing existing text fields' do
|
|
35
|
+
expect do
|
|
36
|
+
Sunspot.search(Post) {
|
|
37
|
+
field_list(:body)
|
|
38
|
+
}
|
|
39
|
+
end.to_not raise_error
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it 'does raise Sunspot::UnrecognizedFieldError when listing a non-existent text fields' do
|
|
43
|
+
expect do
|
|
44
|
+
Sunspot.search(Post) {
|
|
45
|
+
field_list(:bogus_body)
|
|
46
|
+
}
|
|
47
|
+
end.to raise_error(Sunspot::UnrecognizedFieldError)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
it 'does not load any stored fields' do
|
|
51
|
+
hit = Sunspot.search(Post) { without_stored_fields }.hits.first
|
|
52
|
+
|
|
53
|
+
stored_field_names.each do |field|
|
|
54
|
+
expect(hit.stored(field)).to be_nil
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -15,7 +15,7 @@ describe "geospatial search" do
|
|
|
15
15
|
with(:coordinates_new).in_radius(32, -68, 1)
|
|
16
16
|
}.results
|
|
17
17
|
|
|
18
|
-
results.
|
|
18
|
+
expect(results).to include(@post)
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
it "filters out posts not in the radius" do
|
|
@@ -23,18 +23,44 @@ describe "geospatial search" do
|
|
|
23
23
|
with(:coordinates_new).in_radius(33, -68, 1)
|
|
24
24
|
}.results
|
|
25
25
|
|
|
26
|
-
results.
|
|
26
|
+
expect(results).not_to include(@post)
|
|
27
27
|
end
|
|
28
28
|
|
|
29
|
-
it "
|
|
29
|
+
it "filters out posts in the radius" do
|
|
30
|
+
results = Sunspot.search(Post) {
|
|
31
|
+
without(:coordinates_new).in_radius(32, -68, 1)
|
|
32
|
+
}.results
|
|
33
|
+
|
|
34
|
+
expect(results).not_to include(@post)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it "allows conjunction queries with radius" do
|
|
38
|
+
post = Post.new(:title => "Howdy",
|
|
39
|
+
:coordinates => Sunspot::Util::Coordinates.new(35, -68))
|
|
40
|
+
|
|
41
|
+
Sunspot.index!(post)
|
|
42
|
+
|
|
30
43
|
results = Sunspot.search(Post) {
|
|
31
44
|
any_of do
|
|
32
45
|
with(:coordinates_new).in_radius(32, -68, 1)
|
|
33
46
|
with(:coordinates_new).in_radius(35, 68, 1)
|
|
47
|
+
without(:coordinates_new).in_radius(35, -68, 1)
|
|
48
|
+
end
|
|
49
|
+
}.results
|
|
50
|
+
|
|
51
|
+
expect(results).to include(@post)
|
|
52
|
+
expect(results).not_to include(post)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
it "allows conjunction queries with bounding box" do
|
|
56
|
+
results = Sunspot.search(Post) {
|
|
57
|
+
any_of do
|
|
58
|
+
with(:coordinates_new).in_bounding_box([31, -69], [33, -67])
|
|
59
|
+
with(:coordinates_new).in_bounding_box([35, 68], [36, 69])
|
|
34
60
|
end
|
|
35
61
|
}.results
|
|
36
62
|
|
|
37
|
-
results.
|
|
63
|
+
expect(results).to include(@post)
|
|
38
64
|
end
|
|
39
65
|
end
|
|
40
66
|
|
|
@@ -52,7 +78,7 @@ describe "geospatial search" do
|
|
|
52
78
|
with(:coordinates_new).in_bounding_box [31, -69], [33, -67]
|
|
53
79
|
}.results
|
|
54
80
|
|
|
55
|
-
results.
|
|
81
|
+
expect(results).to include(@post)
|
|
56
82
|
end
|
|
57
83
|
|
|
58
84
|
it "filters out posts not in the bounding box" do
|
|
@@ -60,7 +86,7 @@ describe "geospatial search" do
|
|
|
60
86
|
with(:coordinates_new).in_bounding_box [20, -70], [21, -69]
|
|
61
87
|
}.results
|
|
62
88
|
|
|
63
|
-
results.
|
|
89
|
+
expect(results).not_to include(@post)
|
|
64
90
|
end
|
|
65
91
|
end
|
|
66
92
|
|
|
@@ -82,7 +108,7 @@ describe "geospatial search" do
|
|
|
82
108
|
order_by_geodist(:coordinates_new, 32, -68)
|
|
83
109
|
}.results
|
|
84
110
|
|
|
85
|
-
results.
|
|
111
|
+
expect(results).to eq(@posts.reverse)
|
|
86
112
|
end
|
|
87
113
|
|
|
88
114
|
it "orders posts by distance descending" do
|
|
@@ -90,7 +116,7 @@ describe "geospatial search" do
|
|
|
90
116
|
order_by_geodist(:coordinates_new, 32, -68, :desc)
|
|
91
117
|
}.results
|
|
92
118
|
|
|
93
|
-
results.
|
|
119
|
+
expect(results).to eq(@posts)
|
|
94
120
|
end
|
|
95
121
|
end
|
|
96
122
|
end
|
|
@@ -11,22 +11,22 @@ describe 'keyword highlighting' do
|
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
it 'should include highlights in the results' do
|
|
14
|
-
@search_result.hits.first.highlights.length.
|
|
14
|
+
expect(@search_result.hits.first.highlights.length).to eq(1)
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
it 'should return formatted highlight fragments' do
|
|
18
|
-
@search_result.hits.first.highlights(:body).first.format.
|
|
18
|
+
expect(@search_result.hits.first.highlights(:body).first.format).to eq('And the <em>fox</em> laughed')
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
it 'should be empty for non-keyword searches' do
|
|
22
22
|
search_result = Sunspot.search(Post){ with :blog_id, 1 }
|
|
23
|
-
search_result.hits.first.highlights.
|
|
23
|
+
expect(search_result.hits.first.highlights).to be_empty
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
it "should process multiple keyword request on different fields with highlights correctly" do
|
|
27
27
|
search_results = nil
|
|
28
|
-
|
|
29
|
-
search_results = Sunspot.search(Post) do
|
|
28
|
+
expect do
|
|
29
|
+
search_results = Sunspot.search(Post) do
|
|
30
30
|
keywords 'Lorem ipsum', :fields => [:body] do
|
|
31
31
|
highlight :body
|
|
32
32
|
end
|
|
@@ -34,9 +34,9 @@ describe 'keyword highlighting' do
|
|
|
34
34
|
highlight :title
|
|
35
35
|
end
|
|
36
36
|
end
|
|
37
|
-
end.
|
|
38
|
-
search_results.results.length.
|
|
39
|
-
search_results.results.first.
|
|
37
|
+
end.to_not raise_error
|
|
38
|
+
expect(search_results.results.length).to eq(1)
|
|
39
|
+
expect(search_results.results.first).to eq(@posts.last)
|
|
40
40
|
# this one might be a Solr bug, therefore not related to Sunspot itself
|
|
41
41
|
# search_results.hits.first.highlights.should_not be_empty
|
|
42
42
|
end
|
|
@@ -2,33 +2,34 @@ require File.expand_path('../spec_helper', File.dirname(__FILE__))
|
|
|
2
2
|
|
|
3
3
|
describe 'indexing' do
|
|
4
4
|
it 'should index non-multivalued field with newlines' do
|
|
5
|
-
|
|
5
|
+
expect do
|
|
6
6
|
Sunspot.index!(Post.new(:title => "A\nTitle"))
|
|
7
|
-
end.
|
|
7
|
+
end.not_to raise_error
|
|
8
8
|
end
|
|
9
9
|
|
|
10
10
|
it 'should correctly remove by model instance' do
|
|
11
11
|
post = Post.new(:title => 'test post')
|
|
12
12
|
Sunspot.index!(post)
|
|
13
13
|
Sunspot.remove!(post)
|
|
14
|
-
Sunspot.search(Post) { with(:title, 'test post') }.results.
|
|
14
|
+
expect(Sunspot.search(Post) { with(:title, 'test post') }.results).to be_empty
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
it 'should correctly delete by ID' do
|
|
18
18
|
post = Post.new(:title => 'test post')
|
|
19
19
|
Sunspot.index!(post)
|
|
20
20
|
Sunspot.remove_by_id!(Post, post.id)
|
|
21
|
-
Sunspot.search(Post) { with(:title, 'test post') }.results.
|
|
21
|
+
expect(Sunspot.search(Post) { with(:title, 'test post') }.results).to be_empty
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
it 'removes documents by query' do
|
|
25
25
|
Sunspot.remove_all!
|
|
26
26
|
posts = [Post.new(:title => 'birds'), Post.new(:title => 'monkeys')]
|
|
27
27
|
Sunspot.index!(posts)
|
|
28
|
-
|
|
28
|
+
|
|
29
|
+
Sunspot.remove!(Post) do
|
|
29
30
|
with(:title, 'birds')
|
|
30
31
|
end
|
|
31
|
-
Sunspot.search(Post).
|
|
32
|
+
expect(Sunspot.search(Post).results.size).to eq(1)
|
|
32
33
|
end
|
|
33
34
|
|
|
34
35
|
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
require File.expand_path('../spec_helper', File.dirname(__FILE__))
|
|
2
|
+
|
|
3
|
+
describe "searching by joined fields" do
|
|
4
|
+
before :each do
|
|
5
|
+
Sunspot.remove_all!
|
|
6
|
+
|
|
7
|
+
@container1 = PhotoContainer.new(:id => 1)
|
|
8
|
+
@container2 = PhotoContainer.new(:id => 2).tap { |c| allow(c).to receive(:id).and_return(2) }
|
|
9
|
+
@container3 = PhotoContainer.new(:id => 3).tap { |c| allow(c).to receive(:id).and_return(3) }
|
|
10
|
+
|
|
11
|
+
@picture = Picture.new(:photo_container_id => @container1.id, :description => "one")
|
|
12
|
+
@photo1 = Photo.new(:photo_container_id => @container1.id, :description => "two")
|
|
13
|
+
@photo2 = Photo.new(:photo_container_id => @container2.id, :description => "three")
|
|
14
|
+
|
|
15
|
+
Sunspot.index!(@container1, @container2, @photo1, @photo2, @picture)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
it "matches by joined fields" do
|
|
19
|
+
{
|
|
20
|
+
"one" => [],
|
|
21
|
+
"two" => [@container1],
|
|
22
|
+
"three" => [@container2]
|
|
23
|
+
}.each do |key, res|
|
|
24
|
+
results = Sunspot.search(PhotoContainer) {
|
|
25
|
+
fulltext(key, :fields => [:photo_description])
|
|
26
|
+
}.results
|
|
27
|
+
|
|
28
|
+
expect(results).to eq res
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it "doesn't match by joined fields with the same name from other collections" do
|
|
33
|
+
{
|
|
34
|
+
"one" => [@container1],
|
|
35
|
+
"two" => [],
|
|
36
|
+
"three" => []
|
|
37
|
+
}.each do |key, res|
|
|
38
|
+
results = Sunspot.search(PhotoContainer) {
|
|
39
|
+
fulltext(key, :fields => [:picture_description])
|
|
40
|
+
}.results
|
|
41
|
+
|
|
42
|
+
expect(results).to eq res
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -16,36 +16,64 @@ describe 'keyword search' do
|
|
|
16
16
|
Sunspot.index!(@comment)
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
+
context 'edismax' do
|
|
20
|
+
it 'matches with wildcards' do
|
|
21
|
+
results = Sunspot.search(Post) { keywords '*oas*' }.results
|
|
22
|
+
[0,2].each { |i| expect(results).to include(@posts[i])}
|
|
23
|
+
[1].each { |i| expect(results).not_to include(@posts[i])}
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
it 'matches multiple keywords on different fields with wildcards using subqueries' do
|
|
27
|
+
results = Sunspot.search(Post) do
|
|
28
|
+
keywords 'insuffic*',:fields=>[:title]
|
|
29
|
+
keywords 'win*',:fields=>[:body]
|
|
30
|
+
end.results
|
|
31
|
+
[0].each {|i| expect(results).to include(@posts[i])}
|
|
32
|
+
[1,2].each {|i| expect(results).not_to include(@posts[i])}
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it 'matches with proximity' do
|
|
36
|
+
results = Sunspot.search(Post) { keywords '"wind buffer"~4' }.results
|
|
37
|
+
[0,1].each {|i| expect(results).not_to include(@posts[i])}
|
|
38
|
+
[2].each {|i| expect(results).to include(@posts[i])}
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
it 'does not match if not within proximity' do
|
|
42
|
+
results = Sunspot.search(Post) { keywords '"wind buffer"~1' }.results
|
|
43
|
+
expect(results).to eq([])
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
19
47
|
it 'matches a single keyword out of a single field' do
|
|
20
48
|
results = Sunspot.search(Post) { keywords 'toast' }.results
|
|
21
|
-
[0, 2].each { |i| results.
|
|
22
|
-
[1].each { |i| results.
|
|
49
|
+
[0, 2].each { |i| expect(results).to include(@posts[i]) }
|
|
50
|
+
[1].each { |i| expect(results).not_to include(@posts[i]) }
|
|
23
51
|
end
|
|
24
52
|
|
|
25
53
|
it 'matches multiple words out of a single field' do
|
|
26
54
|
results = Sunspot.search(Post) { keywords 'elects toast' }.results
|
|
27
|
-
results.
|
|
55
|
+
expect(results).to eq([@posts[0]])
|
|
28
56
|
end
|
|
29
57
|
|
|
30
58
|
it 'matches multiple words in multiple fields' do
|
|
31
59
|
results = Sunspot.search(Post) { keywords 'toast wind' }.results
|
|
32
|
-
[0, 2].each { |i| results.
|
|
33
|
-
[1].each { |i| results.
|
|
60
|
+
[0, 2].each { |i| expect(results).to include(@posts[i]) }
|
|
61
|
+
[1].each { |i| expect(results).not_to include(@posts[i]) }
|
|
34
62
|
end
|
|
35
63
|
|
|
36
64
|
it 'matches multiple types' do
|
|
37
65
|
results = Sunspot.search(Post, Namespaced::Comment) do
|
|
38
66
|
keywords 'toast'
|
|
39
67
|
end.results
|
|
40
|
-
[@posts[0], @posts[2], @comment].each { |obj| results.
|
|
41
|
-
results.
|
|
68
|
+
[@posts[0], @posts[2], @comment].each { |obj| expect(results).to include(obj) }
|
|
69
|
+
expect(results).not_to include(@posts[1])
|
|
42
70
|
end
|
|
43
71
|
|
|
44
72
|
it 'matches keywords from only the fields specified' do
|
|
45
73
|
results = Sunspot.search(Post) do
|
|
46
74
|
keywords 'moron', :fields => [:title]
|
|
47
75
|
end.results
|
|
48
|
-
results.
|
|
76
|
+
expect(results).to eq([@posts[1]])
|
|
49
77
|
end
|
|
50
78
|
|
|
51
79
|
it 'matches multiple keywords on different fields using subqueries' do
|
|
@@ -53,13 +81,13 @@ describe 'keyword search' do
|
|
|
53
81
|
keywords 'moron', :fields => [:title]
|
|
54
82
|
keywords 'wind', :fields => [:body]
|
|
55
83
|
end
|
|
56
|
-
search.results.
|
|
84
|
+
expect(search.results).to eq([])
|
|
57
85
|
|
|
58
86
|
search = Sunspot.search(Post) do
|
|
59
87
|
keywords 'moron', :fields => [:title]
|
|
60
88
|
keywords 'buffer', :fields => [:body]
|
|
61
89
|
end
|
|
62
|
-
search.results.
|
|
90
|
+
expect(search.results).to eq([@posts[1]])
|
|
63
91
|
end
|
|
64
92
|
|
|
65
93
|
it 'matches multiple keywords with escaped characters' do
|
|
@@ -67,7 +95,7 @@ describe 'keyword search' do
|
|
|
67
95
|
keywords 'spirit', :fields => [:title]
|
|
68
96
|
keywords 'host\'s', :fields => [:body]
|
|
69
97
|
end
|
|
70
|
-
search.results.
|
|
98
|
+
expect(search.results).to eq([@posts[2]])
|
|
71
99
|
end
|
|
72
100
|
|
|
73
101
|
it 'matches multiple keywords with phrase-based search' do
|
|
@@ -76,7 +104,7 @@ describe 'keyword search' do
|
|
|
76
104
|
keywords '"interpret the buffer"', :fields => [:body]
|
|
77
105
|
keywords '"does the"', :fields => [:body]
|
|
78
106
|
end
|
|
79
|
-
search.results.
|
|
107
|
+
expect(search.results).to eq([@posts[2]])
|
|
80
108
|
end
|
|
81
109
|
|
|
82
110
|
it 'matches multiple keywords different options' do
|
|
@@ -84,7 +112,7 @@ describe 'keyword search' do
|
|
|
84
112
|
keywords 'insufficient nonexistent', :fields => [:title], :minimum_match => 1
|
|
85
113
|
keywords 'wind does', :fields => [:body], :minimum_match => 2
|
|
86
114
|
end
|
|
87
|
-
search.results.
|
|
115
|
+
expect(search.results).to eq([@posts[0]])
|
|
88
116
|
end
|
|
89
117
|
end
|
|
90
118
|
|
|
@@ -97,9 +125,10 @@ describe 'keyword search' do
|
|
|
97
125
|
|
|
98
126
|
it 'should assign a higher score to the result matching the higher-boosted field' do
|
|
99
127
|
search = Sunspot.search(Post) { keywords 'rhinoceros' }
|
|
100
|
-
search.hits.map { |hit| hit.primary_key }.
|
|
128
|
+
expect(search.hits.map { |hit| hit.primary_key }).to eq(
|
|
101
129
|
@posts.map { |post| post.id.to_s }
|
|
102
|
-
|
|
130
|
+
)
|
|
131
|
+
expect(search.hits.first.score).to be > search.hits.last.score
|
|
103
132
|
end
|
|
104
133
|
end
|
|
105
134
|
|
|
@@ -114,9 +143,10 @@ describe 'keyword search' do
|
|
|
114
143
|
|
|
115
144
|
it 'should assign a higher score to the higher-boosted document' do
|
|
116
145
|
search = Sunspot.search(Post) { keywords 'test' }
|
|
117
|
-
search.hits.map { |hit| hit.primary_key }.
|
|
146
|
+
expect(search.hits.map { |hit| hit.primary_key }).to eq(
|
|
118
147
|
@posts.map { |post| post.id.to_s }
|
|
119
|
-
|
|
148
|
+
)
|
|
149
|
+
expect(search.hits.first.score).to be > search.hits.last.score
|
|
120
150
|
end
|
|
121
151
|
end
|
|
122
152
|
|
|
@@ -136,8 +166,8 @@ describe 'keyword search' do
|
|
|
136
166
|
phrase_fields :body => 2.0
|
|
137
167
|
end
|
|
138
168
|
end.hits
|
|
139
|
-
hits.first.instance.
|
|
140
|
-
hits.first.score.
|
|
169
|
+
expect(hits.first.instance).to eq(@comments.first)
|
|
170
|
+
expect(hits.first.score).to be > hits.last.score
|
|
141
171
|
end
|
|
142
172
|
|
|
143
173
|
it 'assigns a higher score to documents in which the search terms appear in a boosted field' do
|
|
@@ -146,8 +176,8 @@ describe 'keyword search' do
|
|
|
146
176
|
fields :body => 2.0, :author_name => 0.75
|
|
147
177
|
end
|
|
148
178
|
end.hits
|
|
149
|
-
hits.first.instance.
|
|
150
|
-
hits.first.score.
|
|
179
|
+
expect(hits.first.instance).to eq(@comments.first)
|
|
180
|
+
expect(hits.first.score).to be > hits.last.score
|
|
151
181
|
end
|
|
152
182
|
|
|
153
183
|
it 'assigns a higher score to documents in which the search terms appear in a higher boosted phrase field' do
|
|
@@ -156,8 +186,8 @@ describe 'keyword search' do
|
|
|
156
186
|
phrase_fields :body => 2.0, :author_name => 0.75
|
|
157
187
|
end
|
|
158
188
|
end.hits
|
|
159
|
-
hits.first.instance.
|
|
160
|
-
hits.first.score.
|
|
189
|
+
expect(hits.first.instance).to eq(@comments.first)
|
|
190
|
+
expect(hits.first.score).to be > hits.last.score
|
|
161
191
|
end
|
|
162
192
|
end
|
|
163
193
|
|
|
@@ -182,8 +212,8 @@ describe 'keyword search' do
|
|
|
182
212
|
end
|
|
183
213
|
query.without(@posts[1])
|
|
184
214
|
end
|
|
185
|
-
search.results.
|
|
186
|
-
search.hits[0].score.
|
|
215
|
+
expect(search.results).to eq([@posts[0], @posts[2]])
|
|
216
|
+
expect(search.hits[0].score).to be > search.hits[1].score
|
|
187
217
|
end
|
|
188
218
|
|
|
189
219
|
it 'should assign scores in order of multiple boost query match' do
|
|
@@ -193,9 +223,9 @@ describe 'keyword search' do
|
|
|
193
223
|
boost(1.5) { with(:average_rating).greater_than(3.0) }
|
|
194
224
|
end
|
|
195
225
|
end
|
|
196
|
-
search.results.
|
|
197
|
-
search.hits[0].score.
|
|
198
|
-
search.hits[1].score.
|
|
226
|
+
expect(search.results).to eq(@posts)
|
|
227
|
+
expect(search.hits[0].score).to be > search.hits[1].score
|
|
228
|
+
expect(search.hits[1].score).to be > search.hits[2].score
|
|
199
229
|
end
|
|
200
230
|
end
|
|
201
231
|
|
|
@@ -213,11 +243,11 @@ describe 'keyword search' do
|
|
|
213
243
|
end
|
|
214
244
|
|
|
215
245
|
it 'should match documents that contain the minimum_match number of search terms' do
|
|
216
|
-
@search.results.
|
|
246
|
+
expect(@search.results).to include(@posts[0])
|
|
217
247
|
end
|
|
218
248
|
|
|
219
249
|
it 'should not match documents that do not contain the minimum_match number of search terms' do
|
|
220
|
-
@search.results.
|
|
250
|
+
expect(@search.results).not_to include(@posts[1])
|
|
221
251
|
end
|
|
222
252
|
end
|
|
223
253
|
|
|
@@ -236,15 +266,15 @@ describe 'keyword search' do
|
|
|
236
266
|
end
|
|
237
267
|
|
|
238
268
|
it 'should match exact phrase' do
|
|
239
|
-
@search.results.
|
|
269
|
+
expect(@search.results).to include(@posts[0])
|
|
240
270
|
end
|
|
241
271
|
|
|
242
272
|
it 'should match phrase divided by query phrase slop terms' do
|
|
243
|
-
@search.results.
|
|
273
|
+
expect(@search.results).to include(@posts[1])
|
|
244
274
|
end
|
|
245
275
|
|
|
246
276
|
it 'should not match phrase divided by more than query phrase slop terms' do
|
|
247
|
-
@search.results.
|
|
277
|
+
expect(@search.results).not_to include(@posts[2])
|
|
248
278
|
end
|
|
249
279
|
end
|
|
250
280
|
|
|
@@ -270,15 +300,15 @@ describe 'keyword search' do
|
|
|
270
300
|
end
|
|
271
301
|
|
|
272
302
|
it 'should give phrase field boost to exact match' do
|
|
273
|
-
@sorted_hits[0].score.
|
|
303
|
+
expect(@sorted_hits[0].score).to be > @sorted_hits[1].score
|
|
274
304
|
end
|
|
275
305
|
|
|
276
306
|
it 'should give phrase field boost to match within slop' do
|
|
277
|
-
@sorted_hits[2].score.
|
|
307
|
+
expect(@sorted_hits[2].score).to be > @sorted_hits[3].score
|
|
278
308
|
end
|
|
279
309
|
|
|
280
310
|
it 'should not give phrase field boost to match beyond slop' do
|
|
281
|
-
@sorted_hits[4].score.
|
|
311
|
+
expect(@sorted_hits[4].score).to eq(@sorted_hits[5].score)
|
|
282
312
|
end
|
|
283
313
|
end
|
|
284
314
|
|
|
@@ -288,8 +318,8 @@ describe 'keyword search' do
|
|
|
288
318
|
end
|
|
289
319
|
|
|
290
320
|
after :each do
|
|
291
|
-
@search.results.
|
|
292
|
-
@search.hits.first.score.
|
|
321
|
+
expect(@search.results).to eq(@posts)
|
|
322
|
+
expect(@search.hits.first.score).to be > @search.hits.last.score
|
|
293
323
|
end
|
|
294
324
|
|
|
295
325
|
it 'boosts via function query with float' do
|