pduey-sunspot 1.2.1.1
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.
- data/.gitignore +12 -0
- data/Gemfile +5 -0
- data/History.txt +225 -0
- data/LICENSE +18 -0
- data/Rakefile +15 -0
- data/TODO +13 -0
- data/VERSION.yml +4 -0
- data/bin/sunspot-installer +19 -0
- data/installer/config/schema.yml +95 -0
- data/lib/light_config.rb +40 -0
- data/lib/sunspot.rb +568 -0
- data/lib/sunspot/adapters.rb +265 -0
- data/lib/sunspot/composite_setup.rb +202 -0
- data/lib/sunspot/configuration.rb +46 -0
- data/lib/sunspot/data_extractor.rb +50 -0
- data/lib/sunspot/dsl.rb +5 -0
- data/lib/sunspot/dsl/adjustable.rb +47 -0
- data/lib/sunspot/dsl/field_query.rb +279 -0
- data/lib/sunspot/dsl/fields.rb +103 -0
- data/lib/sunspot/dsl/fulltext.rb +243 -0
- data/lib/sunspot/dsl/function.rb +14 -0
- data/lib/sunspot/dsl/functional.rb +44 -0
- data/lib/sunspot/dsl/more_like_this_query.rb +56 -0
- data/lib/sunspot/dsl/paginatable.rb +28 -0
- data/lib/sunspot/dsl/query_facet.rb +36 -0
- data/lib/sunspot/dsl/restriction.rb +25 -0
- data/lib/sunspot/dsl/restriction_with_near.rb +121 -0
- data/lib/sunspot/dsl/scope.rb +217 -0
- data/lib/sunspot/dsl/search.rb +30 -0
- data/lib/sunspot/dsl/standard_query.rb +121 -0
- data/lib/sunspot/field.rb +193 -0
- data/lib/sunspot/field_factory.rb +129 -0
- data/lib/sunspot/indexer.rb +131 -0
- data/lib/sunspot/installer.rb +31 -0
- data/lib/sunspot/installer/library_installer.rb +45 -0
- data/lib/sunspot/installer/schema_builder.rb +219 -0
- data/lib/sunspot/installer/solrconfig_updater.rb +76 -0
- data/lib/sunspot/installer/task_helper.rb +18 -0
- data/lib/sunspot/java.rb +8 -0
- data/lib/sunspot/query.rb +11 -0
- data/lib/sunspot/query/abstract_field_facet.rb +52 -0
- data/lib/sunspot/query/boost_query.rb +24 -0
- data/lib/sunspot/query/common_query.rb +85 -0
- data/lib/sunspot/query/composite_fulltext.rb +36 -0
- data/lib/sunspot/query/connective.rb +206 -0
- data/lib/sunspot/query/date_field_facet.rb +14 -0
- data/lib/sunspot/query/dismax.rb +128 -0
- data/lib/sunspot/query/field_facet.rb +41 -0
- data/lib/sunspot/query/filter.rb +38 -0
- data/lib/sunspot/query/function_query.rb +52 -0
- data/lib/sunspot/query/geo.rb +53 -0
- data/lib/sunspot/query/highlighting.rb +55 -0
- data/lib/sunspot/query/more_like_this.rb +61 -0
- data/lib/sunspot/query/more_like_this_query.rb +12 -0
- data/lib/sunspot/query/pagination.rb +38 -0
- data/lib/sunspot/query/query_facet.rb +16 -0
- data/lib/sunspot/query/restriction.rb +262 -0
- data/lib/sunspot/query/scope.rb +9 -0
- data/lib/sunspot/query/sort.rb +95 -0
- data/lib/sunspot/query/sort_composite.rb +33 -0
- data/lib/sunspot/query/standard_query.rb +16 -0
- data/lib/sunspot/query/text_field_boost.rb +17 -0
- data/lib/sunspot/schema.rb +151 -0
- data/lib/sunspot/search.rb +9 -0
- data/lib/sunspot/search/abstract_search.rb +335 -0
- data/lib/sunspot/search/date_facet.rb +35 -0
- data/lib/sunspot/search/facet_row.rb +27 -0
- data/lib/sunspot/search/field_facet.rb +88 -0
- data/lib/sunspot/search/highlight.rb +38 -0
- data/lib/sunspot/search/hit.rb +150 -0
- data/lib/sunspot/search/more_like_this_search.rb +31 -0
- data/lib/sunspot/search/paginated_collection.rb +55 -0
- data/lib/sunspot/search/query_facet.rb +67 -0
- data/lib/sunspot/search/standard_search.rb +21 -0
- data/lib/sunspot/session.rb +260 -0
- data/lib/sunspot/session_proxy.rb +87 -0
- data/lib/sunspot/session_proxy/abstract_session_proxy.rb +29 -0
- data/lib/sunspot/session_proxy/class_sharding_session_proxy.rb +66 -0
- data/lib/sunspot/session_proxy/id_sharding_session_proxy.rb +89 -0
- data/lib/sunspot/session_proxy/master_slave_session_proxy.rb +43 -0
- data/lib/sunspot/session_proxy/sharding_session_proxy.rb +222 -0
- data/lib/sunspot/session_proxy/silent_fail_session_proxy.rb +42 -0
- data/lib/sunspot/session_proxy/thread_local_session_proxy.rb +37 -0
- data/lib/sunspot/setup.rb +350 -0
- data/lib/sunspot/text_field_setup.rb +29 -0
- data/lib/sunspot/type.rb +372 -0
- data/lib/sunspot/util.rb +243 -0
- data/lib/sunspot/version.rb +3 -0
- data/pduey-sunspot.gemspec +38 -0
- data/script/console +10 -0
- data/spec/api/adapters_spec.rb +33 -0
- data/spec/api/binding_spec.rb +50 -0
- data/spec/api/indexer/attributes_spec.rb +149 -0
- data/spec/api/indexer/batch_spec.rb +46 -0
- data/spec/api/indexer/dynamic_fields_spec.rb +42 -0
- data/spec/api/indexer/fixed_fields_spec.rb +57 -0
- data/spec/api/indexer/fulltext_spec.rb +43 -0
- data/spec/api/indexer/removal_spec.rb +53 -0
- data/spec/api/indexer/spec_helper.rb +1 -0
- data/spec/api/indexer_spec.rb +14 -0
- data/spec/api/query/advanced_manipulation_examples.rb +35 -0
- data/spec/api/query/connectives_examples.rb +189 -0
- data/spec/api/query/dsl_spec.rb +18 -0
- data/spec/api/query/dynamic_fields_examples.rb +165 -0
- data/spec/api/query/faceting_examples.rb +397 -0
- data/spec/api/query/fulltext_examples.rb +313 -0
- data/spec/api/query/function_spec.rb +70 -0
- data/spec/api/query/geo_examples.rb +68 -0
- data/spec/api/query/highlighting_examples.rb +223 -0
- data/spec/api/query/more_like_this_spec.rb +140 -0
- data/spec/api/query/ordering_pagination_examples.rb +95 -0
- data/spec/api/query/scope_examples.rb +275 -0
- data/spec/api/query/spec_helper.rb +1 -0
- data/spec/api/query/standard_spec.rb +28 -0
- data/spec/api/query/text_field_scoping_examples.rb +30 -0
- data/spec/api/query/types_spec.rb +20 -0
- data/spec/api/search/dynamic_fields_spec.rb +33 -0
- data/spec/api/search/faceting_spec.rb +360 -0
- data/spec/api/search/highlighting_spec.rb +69 -0
- data/spec/api/search/hits_spec.rb +131 -0
- data/spec/api/search/paginated_collection_spec.rb +26 -0
- data/spec/api/search/results_spec.rb +66 -0
- data/spec/api/search/search_spec.rb +23 -0
- data/spec/api/search/spec_helper.rb +1 -0
- data/spec/api/session_proxy/class_sharding_session_proxy_spec.rb +85 -0
- data/spec/api/session_proxy/id_sharding_session_proxy_spec.rb +30 -0
- data/spec/api/session_proxy/master_slave_session_proxy_spec.rb +41 -0
- data/spec/api/session_proxy/sharding_session_proxy_spec.rb +77 -0
- data/spec/api/session_proxy/silent_fail_session_proxy_spec.rb +24 -0
- data/spec/api/session_proxy/spec_helper.rb +9 -0
- data/spec/api/session_proxy/thread_local_session_proxy_spec.rb +39 -0
- data/spec/api/session_spec.rb +220 -0
- data/spec/api/spec_helper.rb +3 -0
- data/spec/api/sunspot_spec.rb +18 -0
- data/spec/ext.rb +11 -0
- data/spec/helpers/indexer_helper.rb +29 -0
- data/spec/helpers/query_helper.rb +38 -0
- data/spec/helpers/search_helper.rb +80 -0
- data/spec/integration/dynamic_fields_spec.rb +57 -0
- data/spec/integration/faceting_spec.rb +238 -0
- data/spec/integration/highlighting_spec.rb +24 -0
- data/spec/integration/indexing_spec.rb +33 -0
- data/spec/integration/keyword_search_spec.rb +317 -0
- data/spec/integration/local_search_spec.rb +64 -0
- data/spec/integration/more_like_this_spec.rb +43 -0
- data/spec/integration/scoped_search_spec.rb +354 -0
- data/spec/integration/spec_helper.rb +7 -0
- data/spec/integration/stored_fields_spec.rb +12 -0
- data/spec/integration/test_pagination.rb +32 -0
- data/spec/mocks/adapters.rb +32 -0
- data/spec/mocks/blog.rb +3 -0
- data/spec/mocks/comment.rb +21 -0
- data/spec/mocks/connection.rb +126 -0
- data/spec/mocks/mock_adapter.rb +30 -0
- data/spec/mocks/mock_class_sharding_session_proxy.rb +24 -0
- data/spec/mocks/mock_record.rb +52 -0
- data/spec/mocks/mock_sharding_session_proxy.rb +15 -0
- data/spec/mocks/photo.rb +11 -0
- data/spec/mocks/post.rb +85 -0
- data/spec/mocks/super_class.rb +2 -0
- data/spec/mocks/user.rb +13 -0
- data/spec/spec_helper.rb +28 -0
- data/tasks/rdoc.rake +27 -0
- data/tasks/schema.rake +19 -0
- data/tasks/todo.rake +4 -0
- metadata +369 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
require File.expand_path('spec_helper', File.dirname(__FILE__))
|
|
2
|
+
|
|
3
|
+
describe 'dynamic fields' do
|
|
4
|
+
before :each do
|
|
5
|
+
Sunspot.remove_all
|
|
6
|
+
@posts = Post.new(:custom_string => { :cuisine => 'Pizza' }),
|
|
7
|
+
Post.new(:custom_string => { :cuisine => 'Greek' }),
|
|
8
|
+
Post.new(:custom_string => { :cuisine => 'Greek' })
|
|
9
|
+
Sunspot.index!(@posts)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
it 'should search for dynamic string field' do
|
|
13
|
+
Sunspot.search(Post) do
|
|
14
|
+
dynamic(:custom_string) do
|
|
15
|
+
with(:cuisine, 'Pizza')
|
|
16
|
+
end
|
|
17
|
+
end.results.should == [@posts.first]
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
describe 'faceting' do
|
|
21
|
+
before :each do
|
|
22
|
+
@search = Sunspot.search(Post) do
|
|
23
|
+
dynamic :custom_string do
|
|
24
|
+
facet :cuisine
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it 'should return value "value" with count 2' do
|
|
30
|
+
row = @search.dynamic_facet(:custom_string, :cuisine).rows[0]
|
|
31
|
+
row.value.should == 'Greek'
|
|
32
|
+
row.count.should == 2
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it 'should return value "other" with count 1' do
|
|
36
|
+
row = @search.dynamic_facet(:custom_string, :cuisine).rows[1]
|
|
37
|
+
row.value.should == 'Pizza'
|
|
38
|
+
row.count.should == 1
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it 'should order by dynamic string field ascending' do
|
|
43
|
+
Sunspot.search(Post) do
|
|
44
|
+
dynamic :custom_string do
|
|
45
|
+
order_by :cuisine, :asc
|
|
46
|
+
end
|
|
47
|
+
end.results.last.should == @posts.first
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
it 'should order by dynamic string field descending' do
|
|
51
|
+
Sunspot.search(Post) do
|
|
52
|
+
dynamic :custom_string do
|
|
53
|
+
order_by :cuisine, :desc
|
|
54
|
+
end
|
|
55
|
+
end.results.first.should == @posts.first
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
require File.expand_path('spec_helper', File.dirname(__FILE__))
|
|
2
|
+
|
|
3
|
+
describe 'search faceting' do
|
|
4
|
+
def self.test_field_type(name, attribute, field, *args)
|
|
5
|
+
clazz, value1, value2 =
|
|
6
|
+
if args.length == 2
|
|
7
|
+
[Post, args.first, args.last]
|
|
8
|
+
else
|
|
9
|
+
args
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
context "with field of type #{name}" do
|
|
13
|
+
before :all do
|
|
14
|
+
Sunspot.remove_all
|
|
15
|
+
2.times do
|
|
16
|
+
Sunspot.index(clazz.new(attribute => value1))
|
|
17
|
+
end
|
|
18
|
+
Sunspot.index(clazz.new(attribute => value2))
|
|
19
|
+
Sunspot.commit
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
before :each do
|
|
23
|
+
@search = Sunspot.search(clazz) do
|
|
24
|
+
facet(field)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it "should return value #{value1.inspect} with count 2" do
|
|
29
|
+
row = @search.facet(field).rows[0]
|
|
30
|
+
row.value.should == value1
|
|
31
|
+
row.count.should == 2
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it "should return value #{value2.inspect} with count 1" do
|
|
35
|
+
row = @search.facet(field).rows[1]
|
|
36
|
+
row.value.should == value2
|
|
37
|
+
row.count.should == 1
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
test_field_type('String', :title, :title, 'Title 1', 'Title 2')
|
|
43
|
+
test_field_type('Integer', :blog_id, :blog_id, 3, 4)
|
|
44
|
+
test_field_type('Float', :ratings_average, :average_rating, 2.2, 1.1)
|
|
45
|
+
test_field_type('Time', :published_at, :published_at, Time.mktime(2008, 02, 17, 17, 45, 04),
|
|
46
|
+
Time.mktime(2008, 07, 02, 03, 56, 22))
|
|
47
|
+
test_field_type('Trie Integer', :size, :size, Photo, 3, 4)
|
|
48
|
+
test_field_type('Float', :average_rating, :average_rating, Photo, 2.2, 1.1)
|
|
49
|
+
test_field_type('Time', :created_at, :created_at, Photo, Time.mktime(2008, 02, 17, 17, 45, 04),
|
|
50
|
+
Time.mktime(2008, 07, 02, 03, 56, 22))
|
|
51
|
+
test_field_type('Boolean', :featured, :featured, true, false)
|
|
52
|
+
|
|
53
|
+
context 'facet options' do
|
|
54
|
+
before :all do
|
|
55
|
+
Sunspot.remove_all
|
|
56
|
+
facet_values = %w(zero one two three four)
|
|
57
|
+
facet_values.each_with_index do |value, i|
|
|
58
|
+
i.times { Sunspot.index(Post.new(:title => value, :blog_id => 1)) }
|
|
59
|
+
end
|
|
60
|
+
Sunspot.index(Post.new(:blog_id => 1))
|
|
61
|
+
Sunspot.index(Post.new(:title => 'zero', :blog_id => 2))
|
|
62
|
+
Sunspot.commit
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it 'should limit the number of facet rows' do
|
|
66
|
+
search = Sunspot.search(Post) do
|
|
67
|
+
facet :title, :limit => 3
|
|
68
|
+
end
|
|
69
|
+
search.facet(:title).should have(3).rows
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
it 'should not return zeros by default' do
|
|
73
|
+
search = Sunspot.search(Post) do
|
|
74
|
+
with :blog_id, 1
|
|
75
|
+
facet :title
|
|
76
|
+
end
|
|
77
|
+
search.facet(:title).rows.map { |row| row.value }.should_not include('zero')
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
it 'should return zeros when specified' do
|
|
81
|
+
search = Sunspot.search(Post) do
|
|
82
|
+
with :blog_id, 1
|
|
83
|
+
facet :title, :zeros => true
|
|
84
|
+
end
|
|
85
|
+
search.facet(:title).rows.map { |row| row.value }.should include('zero')
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
it 'should return a specified minimum count' do
|
|
89
|
+
search = Sunspot.search(Post) do
|
|
90
|
+
with :blog_id, 1
|
|
91
|
+
facet :title, :minimum_count => 2
|
|
92
|
+
end
|
|
93
|
+
search.facet(:title).rows.map { |row| row.value }.should == %w(four three two)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
it 'should order facets lexically' do
|
|
97
|
+
search = Sunspot.search(Post) do
|
|
98
|
+
with :blog_id, 1
|
|
99
|
+
facet :title, :sort => :index
|
|
100
|
+
end
|
|
101
|
+
search.facet(:title).rows.map { |row| row.value }.should == %w(four one three two)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
it 'should order facets by count' do
|
|
105
|
+
search = Sunspot.search(Post) do
|
|
106
|
+
with :blog_id, 1
|
|
107
|
+
facet :title, :sort => :count
|
|
108
|
+
end
|
|
109
|
+
search.facet(:title).rows.map { |row| row.value }.should == %w(four three two one)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
it 'should limit facet values by prefix' do
|
|
113
|
+
search = Sunspot.search(Post) do
|
|
114
|
+
with :blog_id, 1
|
|
115
|
+
facet :title, :prefix => 't'
|
|
116
|
+
end
|
|
117
|
+
search.facet(:title).rows.map { |row| row.value }.sort.should == %w(three two)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
it 'should return :all facet' do
|
|
121
|
+
search = Sunspot.search(Post) do
|
|
122
|
+
with :blog_id, 1
|
|
123
|
+
facet :title, :extra => :any
|
|
124
|
+
end
|
|
125
|
+
search.facet(:title).rows.first.value.should == :any
|
|
126
|
+
search.facet(:title).rows.first.count.should == 10
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
it 'should return :none facet' do
|
|
130
|
+
search = Sunspot.search(Post) do
|
|
131
|
+
with :blog_id, 1
|
|
132
|
+
facet :title, :extra => :none
|
|
133
|
+
end
|
|
134
|
+
search.facet(:title).rows.first.value.should == :none
|
|
135
|
+
search.facet(:title).rows.first.count.should == 1
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
context 'multiselect faceting' do
|
|
140
|
+
before do
|
|
141
|
+
Sunspot.remove_all
|
|
142
|
+
Sunspot.index!(
|
|
143
|
+
Post.new(:blog_id => 1, :category_ids => [1]),
|
|
144
|
+
Post.new(:blog_id => 1, :category_ids => [2]),
|
|
145
|
+
Post.new(:blog_id => 3, :category_ids => [3])
|
|
146
|
+
)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
it 'should exclude filter from faceting' do
|
|
150
|
+
search = Sunspot.search(Post) do
|
|
151
|
+
with(:blog_id, 1)
|
|
152
|
+
category_filter = with(:category_ids, 1)
|
|
153
|
+
facet(:category_ids, :exclude => category_filter)
|
|
154
|
+
end
|
|
155
|
+
search.facet(:category_ids).rows.map { |row| row.value }.to_set.should == Set[1, 2]
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
it 'should use facet keys to facet more than once with different exclusions' do
|
|
159
|
+
search = Sunspot.search(Post) do
|
|
160
|
+
with(:blog_id, 1)
|
|
161
|
+
category_filter = with(:category_ids, 1)
|
|
162
|
+
facet(:category_ids)
|
|
163
|
+
facet(:category_ids, :exclude => category_filter, :name => :all_category_ids)
|
|
164
|
+
end
|
|
165
|
+
search.facet(:category_ids).rows.map { |row| row.value }.should == [1]
|
|
166
|
+
search.facet(:all_category_ids).rows.map { |row| row.value }.to_set.should == Set[1, 2]
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
context 'date facets' do
|
|
171
|
+
before :all do
|
|
172
|
+
Sunspot.remove_all
|
|
173
|
+
time = Time.utc(2009, 7, 8)
|
|
174
|
+
Sunspot.index!(
|
|
175
|
+
(0..2).map { |i| Post.new(:published_at => time + i*60*60*16) }
|
|
176
|
+
)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
it 'should return time ranges' do
|
|
180
|
+
time = Time.utc(2009, 7, 8)
|
|
181
|
+
search = Sunspot.search(Post) do
|
|
182
|
+
facet :published_at, :time_range => time..(time + 60*60*24*2), :sort => :count
|
|
183
|
+
end
|
|
184
|
+
search.facet(:published_at).rows.first.value.should == (time..(time + 60*60*24))
|
|
185
|
+
search.facet(:published_at).rows.first.count.should == 2
|
|
186
|
+
search.facet(:published_at).rows.last.value.should == ((time + 60*60*24)..(time + 60*60*24*2))
|
|
187
|
+
search.facet(:published_at).rows.last.count.should == 1
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
context 'class facets' do
|
|
192
|
+
before :all do
|
|
193
|
+
Sunspot.remove_all
|
|
194
|
+
Sunspot.index!(Post.new, Post.new, Namespaced::Comment.new)
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
it 'should return classes' do
|
|
198
|
+
search = Sunspot.search(Post, Namespaced::Comment) do
|
|
199
|
+
facet(:class, :sort => :count)
|
|
200
|
+
end
|
|
201
|
+
search.facet(:class).rows.first.value.should == Post
|
|
202
|
+
search.facet(:class).rows.first.count.should == 2
|
|
203
|
+
search.facet(:class).rows.last.value.should == Namespaced::Comment
|
|
204
|
+
search.facet(:class).rows.last.count.should == 1
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
context 'query facets' do
|
|
209
|
+
before :all do
|
|
210
|
+
Sunspot.remove_all
|
|
211
|
+
Sunspot.index!(
|
|
212
|
+
[1.1, 1.2, 3.2, 3.4, 3.9, 4.1].map do |rating|
|
|
213
|
+
Post.new(:ratings_average => rating)
|
|
214
|
+
end
|
|
215
|
+
)
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
it 'should return specified facets' do
|
|
219
|
+
search = Sunspot.search(Post) do
|
|
220
|
+
facet :rating_range, :sort => :count do
|
|
221
|
+
for rating in [1.0, 2.0, 3.0, 4.0]
|
|
222
|
+
range = rating..(rating + 1.0)
|
|
223
|
+
row range do
|
|
224
|
+
with :average_rating, rating..(rating + 1.0)
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
facet = search.facet(:rating_range)
|
|
230
|
+
facet.rows[0].value.should == (3.0..4.0)
|
|
231
|
+
facet.rows[0].count.should == 3
|
|
232
|
+
facet.rows[1].value.should == (1.0..2.0)
|
|
233
|
+
facet.rows[1].count.should == 2
|
|
234
|
+
facet.rows[2].value.should == (4.0..5.0)
|
|
235
|
+
facet.rows[2].count.should == 1
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
require File.expand_path('spec_helper', File.dirname(__FILE__))
|
|
2
|
+
|
|
3
|
+
describe 'keyword highlighting' do
|
|
4
|
+
before :all do
|
|
5
|
+
@posts = []
|
|
6
|
+
@posts << Post.new(:body => 'And the fox laughed')
|
|
7
|
+
@posts << Post.new(:body => 'Lorem ipsum dolor sit amet, consectetur adipiscing elit', :blog_id => 1)
|
|
8
|
+
Sunspot.index!(*@posts)
|
|
9
|
+
@search_result = Sunspot.search(Post) { keywords 'fox', :highlight => true }
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
it 'should include highlights in the results' do
|
|
13
|
+
@search_result.hits.first.highlights.length.should == 1
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it 'should return formatted highlight fragments' do
|
|
17
|
+
@search_result.hits.first.highlights(:body).first.format.should == 'And the <em>fox</em> laughed'
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it 'should be empty for non-keyword searches' do
|
|
21
|
+
search_result = Sunspot.search(Post){ with :blog_id, 1 }
|
|
22
|
+
search_result.hits.first.highlights.should be_empty
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
require File.expand_path('spec_helper', File.dirname(__FILE__))
|
|
2
|
+
|
|
3
|
+
describe 'indexing' do
|
|
4
|
+
it 'should index non-multivalued field with newlines' do
|
|
5
|
+
lambda do
|
|
6
|
+
Sunspot.index!(Post.new(:title => "A\nTitle"))
|
|
7
|
+
end.should_not raise_error
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
it 'should correctly remove by model instance' do
|
|
11
|
+
post = Post.new(:title => 'test post')
|
|
12
|
+
Sunspot.index!(post)
|
|
13
|
+
Sunspot.remove!(post)
|
|
14
|
+
Sunspot.search(Post) { with(:title, 'test post') }.results.should be_empty
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it 'should correctly delete by ID' do
|
|
18
|
+
post = Post.new(:title => 'test post')
|
|
19
|
+
Sunspot.index!(post)
|
|
20
|
+
Sunspot.remove_by_id!(Post, post.id)
|
|
21
|
+
Sunspot.search(Post) { with(:title, 'test post') }.results.should be_empty
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it 'removes documents by query' do
|
|
25
|
+
Sunspot.remove_all!
|
|
26
|
+
posts = [Post.new(:title => 'birds'), Post.new(:title => 'monkeys')]
|
|
27
|
+
Sunspot.index!(posts)
|
|
28
|
+
Sunspot.remove! do
|
|
29
|
+
with(:title, 'birds')
|
|
30
|
+
end
|
|
31
|
+
Sunspot.search(Post).should have(2).results
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
require File.expand_path('spec_helper', File.dirname(__FILE__))
|
|
2
|
+
|
|
3
|
+
describe 'keyword search' do
|
|
4
|
+
describe 'generally' do
|
|
5
|
+
before :all do
|
|
6
|
+
Sunspot.remove_all
|
|
7
|
+
@posts = []
|
|
8
|
+
@posts << Post.new(:title => 'The toast elects the insufficient spirit',
|
|
9
|
+
:body => 'Does the wind write?')
|
|
10
|
+
@posts << Post.new(:title => 'A nail abbreviates the recovering insight outside the moron',
|
|
11
|
+
:body => 'The interpreted strain scans the buffer around the upper temper')
|
|
12
|
+
@posts << Post.new(:title => 'The toast abbreviates the recovering spirit',
|
|
13
|
+
:body => 'Does the host\'s wind interpret the buffer, moron?')
|
|
14
|
+
Sunspot.index!(*@posts)
|
|
15
|
+
@comment = Namespaced::Comment.new(:body => 'Hey there where ya goin, not exactly knowin, who says you have to call just one place toast.')
|
|
16
|
+
Sunspot.index!(@comment)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it 'matches a single keyword out of a single field' do
|
|
20
|
+
results = Sunspot.search(Post) { keywords 'toast' }.results
|
|
21
|
+
[0, 2].each { |i| results.should include(@posts[i]) }
|
|
22
|
+
[1].each { |i| results.should_not include(@posts[i]) }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it 'matches multiple words out of a single field' do
|
|
26
|
+
results = Sunspot.search(Post) { keywords 'elects toast' }.results
|
|
27
|
+
results.should == [@posts[0]]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it 'matches multiple words in multiple fields' do
|
|
31
|
+
results = Sunspot.search(Post) { keywords 'toast wind' }.results
|
|
32
|
+
[0, 2].each { |i| results.should include(@posts[i]) }
|
|
33
|
+
[1].each { |i| results.should_not include(@posts[i]) }
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it 'matches multiple types' do
|
|
37
|
+
results = Sunspot.search(Post, Namespaced::Comment) do
|
|
38
|
+
keywords 'toast'
|
|
39
|
+
end.results
|
|
40
|
+
[@posts[0], @posts[2], @comment].each { |obj| results.should include(obj) }
|
|
41
|
+
results.should_not include(@posts[1])
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
it 'matches keywords from only the fields specified' do
|
|
45
|
+
results = Sunspot.search(Post) do
|
|
46
|
+
keywords 'moron', :fields => [:title]
|
|
47
|
+
end.results
|
|
48
|
+
results.should == [@posts[1]]
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
it 'matches multiple keywords on different fields using subqueries' do
|
|
52
|
+
search = Sunspot.search(Post) do
|
|
53
|
+
keywords 'moron', :fields => [:title]
|
|
54
|
+
keywords 'wind', :fields => [:body]
|
|
55
|
+
end
|
|
56
|
+
search.results.should == []
|
|
57
|
+
|
|
58
|
+
search = Sunspot.search(Post) do
|
|
59
|
+
keywords 'moron', :fields => [:title]
|
|
60
|
+
keywords 'buffer', :fields => [:body]
|
|
61
|
+
end
|
|
62
|
+
search.results.should == [@posts[1]]
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it 'matches multiple keywords with escaped characters' do
|
|
66
|
+
search = Sunspot.search(Post) do
|
|
67
|
+
keywords 'spirit', :fields => [:title]
|
|
68
|
+
keywords 'host\'s', :fields => [:body]
|
|
69
|
+
end
|
|
70
|
+
search.results.should == [@posts[2]]
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
it 'matches multiple keywords with phrase-based search' do
|
|
74
|
+
search = Sunspot.search(Post) do
|
|
75
|
+
keywords 'spirit', :fields => [:title]
|
|
76
|
+
keywords '"interpret the buffer"', :fields => [:body]
|
|
77
|
+
keywords '"does the"', :fields => [:body]
|
|
78
|
+
end
|
|
79
|
+
search.results.should == [@posts[2]]
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
it 'matches multiple keywords different options' do
|
|
83
|
+
search = Sunspot.search(Post) do
|
|
84
|
+
keywords 'insufficient nonexistent', :fields => [:title], :minimum_match => 1
|
|
85
|
+
keywords 'wind does', :fields => [:body], :minimum_match => 2
|
|
86
|
+
end
|
|
87
|
+
search.results.should == [@posts[0]]
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
describe 'with field boost' do
|
|
92
|
+
before :all do
|
|
93
|
+
Sunspot.remove_all
|
|
94
|
+
@posts = [:title, :body].map { |field| Post.new(field => 'rhinoceros') }
|
|
95
|
+
Sunspot.index!(*@posts)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
it 'should assign a higher score to the result matching the higher-boosted field' do
|
|
99
|
+
search = Sunspot.search(Post) { keywords 'rhinoceros' }
|
|
100
|
+
search.hits.map { |hit| hit.primary_key }.should ==
|
|
101
|
+
@posts.map { |post| post.id.to_s }
|
|
102
|
+
search.hits.first.score.should > search.hits.last.score
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
describe 'with document boost' do
|
|
107
|
+
before :all do
|
|
108
|
+
Sunspot.remove_all
|
|
109
|
+
@posts = [4.0, 2.0].map do |rating|
|
|
110
|
+
Post.new(:title => 'Test', :ratings_average => rating)
|
|
111
|
+
end
|
|
112
|
+
Sunspot.index!(*@posts)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
it 'should assign a higher score to the higher-boosted document' do
|
|
116
|
+
search = Sunspot.search(Post) { keywords 'test' }
|
|
117
|
+
search.hits.map { |hit| hit.primary_key }.should ==
|
|
118
|
+
@posts.map { |post| post.id.to_s }
|
|
119
|
+
search.hits.first.score.should > search.hits.last.score
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
describe 'with search-time boost' do
|
|
124
|
+
before :each do
|
|
125
|
+
Sunspot.remove_all
|
|
126
|
+
@comments = [
|
|
127
|
+
Namespaced::Comment.new(:body => 'test text'),
|
|
128
|
+
Namespaced::Comment.new(:author_name => 'test text')
|
|
129
|
+
]
|
|
130
|
+
Sunspot.index!(@comments)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
it 'assigns a higher score to documents in which all words appear in the phrase field' do
|
|
134
|
+
hits = Sunspot.search(Namespaced::Comment) do
|
|
135
|
+
keywords 'test text' do
|
|
136
|
+
phrase_fields :body => 2.0
|
|
137
|
+
end
|
|
138
|
+
end.hits
|
|
139
|
+
hits.first.instance.should == @comments.first
|
|
140
|
+
hits.first.score.should > hits.last.score
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
it 'assigns a higher score to documents in which the search terms appear in a boosted field' do
|
|
144
|
+
hits = Sunspot.search(Namespaced::Comment) do
|
|
145
|
+
keywords 'test' do
|
|
146
|
+
fields :body => 2.0, :author_name => 0.75
|
|
147
|
+
end
|
|
148
|
+
end.hits
|
|
149
|
+
hits.first.instance.should == @comments.first
|
|
150
|
+
hits.first.score.should > hits.last.score
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
it 'assigns a higher score to documents in which the search terms appear in a higher boosted phrase field' do
|
|
154
|
+
hits = Sunspot.search(Namespaced::Comment) do
|
|
155
|
+
keywords 'test text' do
|
|
156
|
+
phrase_fields :body => 2.0, :author_name => 0.75
|
|
157
|
+
end
|
|
158
|
+
end.hits
|
|
159
|
+
hits.first.instance.should == @comments.first
|
|
160
|
+
hits.first.score.should > hits.last.score
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
describe 'boost query' do
|
|
165
|
+
before :all do
|
|
166
|
+
Sunspot.remove_all
|
|
167
|
+
Sunspot.index!(
|
|
168
|
+
@posts = [
|
|
169
|
+
Post.new(:title => 'Rhino', :featured => true),
|
|
170
|
+
Post.new(:title => 'Rhino', :ratings_average => 3.3),
|
|
171
|
+
Post.new(:title => 'Rhino')
|
|
172
|
+
]
|
|
173
|
+
)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
it 'should assign a higher score to the document matching the boost query' do
|
|
177
|
+
search = Sunspot.search(Post) do |query|
|
|
178
|
+
query.keywords('rhino') do
|
|
179
|
+
boost(2.0) do
|
|
180
|
+
with(:featured, true)
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
query.without(@posts[1])
|
|
184
|
+
end
|
|
185
|
+
search.results.should == [@posts[0], @posts[2]]
|
|
186
|
+
search.hits[0].score.should > search.hits[1].score
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
it 'should assign scores in order of multiple boost query match' do
|
|
190
|
+
search = Sunspot.search(Post) do
|
|
191
|
+
keywords 'rhino' do
|
|
192
|
+
boost(2.0) { with(:featured, true) }
|
|
193
|
+
boost(1.5) { with(:average_rating).greater_than(3.0) }
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
search.results.should == @posts
|
|
197
|
+
search.hits[0].score.should > search.hits[1].score
|
|
198
|
+
search.hits[1].score.should > search.hits[2].score
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
describe 'minimum match' do
|
|
203
|
+
before do
|
|
204
|
+
Sunspot.remove_all
|
|
205
|
+
@posts = [
|
|
206
|
+
Post.new(:title => 'Pepperoni Sausage Anchovies'),
|
|
207
|
+
Post.new(:title => 'Pepperoni Tomatoes Mushrooms')
|
|
208
|
+
]
|
|
209
|
+
Sunspot.index!(@posts)
|
|
210
|
+
@search = Sunspot.search(Post) do
|
|
211
|
+
keywords 'pepperoni sausage extra cheese', :minimum_match => 2
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
it 'should match documents that contain the minimum_match number of search terms' do
|
|
216
|
+
@search.results.should include(@posts[0])
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
it 'should not match documents that do not contain the minimum_match number of search terms' do
|
|
220
|
+
@search.results.should_not include(@posts[1])
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
describe 'query phrase slop' do
|
|
225
|
+
before do
|
|
226
|
+
Sunspot.remove_all
|
|
227
|
+
@posts = [
|
|
228
|
+
Post.new(:title => 'One four'),
|
|
229
|
+
Post.new(:title => 'One three four'),
|
|
230
|
+
Post.new(:title => 'One two three four')
|
|
231
|
+
]
|
|
232
|
+
Sunspot.index!(@posts)
|
|
233
|
+
@search = Sunspot.search(Post) do
|
|
234
|
+
keywords '"one four"', :query_phrase_slop => 1
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
it 'should match exact phrase' do
|
|
239
|
+
@search.results.should include(@posts[0])
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
it 'should match phrase divided by query phrase slop terms' do
|
|
243
|
+
@search.results.should include(@posts[1])
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
it 'should not match phrase divided by more than query phrase slop terms' do
|
|
247
|
+
@search.results.should_not include(@posts[2])
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
describe 'phrase field slop' do
|
|
252
|
+
before do
|
|
253
|
+
Sunspot.remove_all
|
|
254
|
+
@comments = [
|
|
255
|
+
Namespaced::Comment.new(:author_name => 'one four'),
|
|
256
|
+
Namespaced::Comment.new(:body => 'one four'),
|
|
257
|
+
Namespaced::Comment.new(:author_name => 'one three four'),
|
|
258
|
+
Namespaced::Comment.new(:body => 'one three four'),
|
|
259
|
+
Namespaced::Comment.new(:author_name => 'one two three four'),
|
|
260
|
+
Namespaced::Comment.new(:body => 'one two three four')
|
|
261
|
+
]
|
|
262
|
+
Sunspot.index!(@comments)
|
|
263
|
+
@search = Sunspot.search(Namespaced::Comment) do
|
|
264
|
+
keywords 'one four' do
|
|
265
|
+
phrase_fields :author_name => 3.0
|
|
266
|
+
phrase_slop 1
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
@sorted_hits = @search.hits.sort_by { |hit| @comments.index(hit.instance) }
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
it 'should give phrase field boost to exact match' do
|
|
273
|
+
@sorted_hits[0].score.should > @sorted_hits[1].score
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
it 'should give phrase field boost to match within slop' do
|
|
277
|
+
@sorted_hits[2].score.should > @sorted_hits[3].score
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
it 'should not give phrase field boost to match beyond slop' do
|
|
281
|
+
@sorted_hits[4].score.should == @sorted_hits[5].score
|
|
282
|
+
end
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
describe 'with function queries' do
|
|
286
|
+
before :each do
|
|
287
|
+
Sunspot.remove_all
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
after :each do
|
|
291
|
+
@search.results.should == @posts
|
|
292
|
+
@search.hits.first.score.should > @search.hits.last.score
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
it 'boosts via function query with float' do
|
|
296
|
+
@posts = [Post.new(:title => 'test', :ratings_average => 4.0),
|
|
297
|
+
Post.new(:title => 'test', :ratings_average => 2.0)]
|
|
298
|
+
Sunspot.index!(@posts)
|
|
299
|
+
@search = Sunspot.search(Post) do
|
|
300
|
+
keywords('test') do
|
|
301
|
+
boost function { :average_rating }
|
|
302
|
+
end
|
|
303
|
+
end
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
it 'boosts via function query with date' do
|
|
307
|
+
@posts = [Post.new(:title => 'test', :published_at => Time.now),
|
|
308
|
+
Post.new(:title => 'test', :published_at => Time.now - 60*60*24*31*6)] # roughly six months ago
|
|
309
|
+
Sunspot.index!(@posts)
|
|
310
|
+
@search = Sunspot.search(Post) do
|
|
311
|
+
keywords('test') do
|
|
312
|
+
boost function { recip(ms(Time.now, :published_at), 3.16e-11, 1, 1) }
|
|
313
|
+
end
|
|
314
|
+
end
|
|
315
|
+
end
|
|
316
|
+
end
|
|
317
|
+
end
|