gojee-sunspot 2.0.3 → 2.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (178) hide show
  1. data/.gitignore +12 -0
  2. data/Gemfile +5 -0
  3. data/History.txt +252 -0
  4. data/LICENSE +18 -0
  5. data/Rakefile +13 -0
  6. data/TODO +13 -0
  7. data/lib/light_config.rb +40 -0
  8. data/lib/sunspot/adapters.rb +265 -0
  9. data/lib/sunspot/batcher.rb +62 -0
  10. data/lib/sunspot/class_set.rb +23 -0
  11. data/lib/sunspot/composite_setup.rb +202 -0
  12. data/lib/sunspot/configuration.rb +53 -0
  13. data/lib/sunspot/data_extractor.rb +50 -0
  14. data/lib/sunspot/dsl/adjustable.rb +47 -0
  15. data/lib/sunspot/dsl/field_group.rb +57 -0
  16. data/lib/sunspot/dsl/field_query.rb +327 -0
  17. data/lib/sunspot/dsl/fields.rb +103 -0
  18. data/lib/sunspot/dsl/fulltext.rb +243 -0
  19. data/lib/sunspot/dsl/function.rb +27 -0
  20. data/lib/sunspot/dsl/functional.rb +44 -0
  21. data/lib/sunspot/dsl/more_like_this_query.rb +56 -0
  22. data/lib/sunspot/dsl/paginatable.rb +32 -0
  23. data/lib/sunspot/dsl/query_facet.rb +36 -0
  24. data/lib/sunspot/dsl/restriction.rb +25 -0
  25. data/lib/sunspot/dsl/restriction_with_near.rb +160 -0
  26. data/lib/sunspot/dsl/scope.rb +217 -0
  27. data/lib/sunspot/dsl/search.rb +30 -0
  28. data/lib/sunspot/dsl/standard_query.rb +123 -0
  29. data/lib/sunspot/dsl.rb +5 -0
  30. data/lib/sunspot/field.rb +193 -0
  31. data/lib/sunspot/field_factory.rb +129 -0
  32. data/lib/sunspot/indexer.rb +136 -0
  33. data/lib/sunspot/query/abstract_field_facet.rb +52 -0
  34. data/lib/sunspot/query/bbox.rb +15 -0
  35. data/lib/sunspot/query/boost_query.rb +24 -0
  36. data/lib/sunspot/query/common_query.rb +96 -0
  37. data/lib/sunspot/query/composite_fulltext.rb +36 -0
  38. data/lib/sunspot/query/connective.rb +206 -0
  39. data/lib/sunspot/query/date_field_facet.rb +14 -0
  40. data/lib/sunspot/query/dismax.rb +132 -0
  41. data/lib/sunspot/query/field_facet.rb +41 -0
  42. data/lib/sunspot/query/field_group.rb +36 -0
  43. data/lib/sunspot/query/filter.rb +38 -0
  44. data/lib/sunspot/query/function_query.rb +52 -0
  45. data/lib/sunspot/query/geo.rb +53 -0
  46. data/lib/sunspot/query/geofilt.rb +16 -0
  47. data/lib/sunspot/query/highlighting.rb +62 -0
  48. data/lib/sunspot/query/more_like_this.rb +61 -0
  49. data/lib/sunspot/query/more_like_this_query.rb +12 -0
  50. data/lib/sunspot/query/pagination.rb +42 -0
  51. data/lib/sunspot/query/query_facet.rb +16 -0
  52. data/lib/sunspot/query/restriction.rb +262 -0
  53. data/lib/sunspot/query/scope.rb +9 -0
  54. data/lib/sunspot/query/sort.rb +109 -0
  55. data/lib/sunspot/query/sort_composite.rb +34 -0
  56. data/lib/sunspot/query/standard_query.rb +16 -0
  57. data/lib/sunspot/query/text_field_boost.rb +17 -0
  58. data/lib/sunspot/query.rb +11 -0
  59. data/lib/sunspot/schema.rb +151 -0
  60. data/lib/sunspot/search/abstract_search.rb +281 -0
  61. data/lib/sunspot/search/date_facet.rb +35 -0
  62. data/lib/sunspot/search/facet_row.rb +27 -0
  63. data/lib/sunspot/search/field_facet.rb +88 -0
  64. data/lib/sunspot/search/field_group.rb +32 -0
  65. data/lib/sunspot/search/group.rb +50 -0
  66. data/lib/sunspot/search/highlight.rb +38 -0
  67. data/lib/sunspot/search/hit.rb +150 -0
  68. data/lib/sunspot/search/hit_enumerable.rb +72 -0
  69. data/lib/sunspot/search/more_like_this_search.rb +31 -0
  70. data/lib/sunspot/search/paginated_collection.rb +57 -0
  71. data/lib/sunspot/search/query_facet.rb +67 -0
  72. data/lib/sunspot/search/standard_search.rb +21 -0
  73. data/lib/sunspot/search.rb +9 -0
  74. data/lib/sunspot/session.rb +262 -0
  75. data/lib/sunspot/session_proxy/abstract_session_proxy.rb +29 -0
  76. data/lib/sunspot/session_proxy/class_sharding_session_proxy.rb +66 -0
  77. data/lib/sunspot/session_proxy/id_sharding_session_proxy.rb +89 -0
  78. data/lib/sunspot/session_proxy/master_slave_session_proxy.rb +43 -0
  79. data/lib/sunspot/session_proxy/multicore_session_proxy.rb +67 -0
  80. data/lib/sunspot/session_proxy/sharding_session_proxy.rb +222 -0
  81. data/lib/sunspot/session_proxy/silent_fail_session_proxy.rb +42 -0
  82. data/lib/sunspot/session_proxy/thread_local_session_proxy.rb +37 -0
  83. data/lib/sunspot/session_proxy.rb +95 -0
  84. data/lib/sunspot/setup.rb +350 -0
  85. data/lib/sunspot/text_field_setup.rb +29 -0
  86. data/lib/sunspot/type.rb +393 -0
  87. data/lib/sunspot/util.rb +252 -0
  88. data/lib/sunspot/version.rb +3 -0
  89. data/lib/sunspot.rb +579 -0
  90. data/log/.gitignore +1 -0
  91. data/pkg/.gitignore +1 -0
  92. data/script/console +10 -0
  93. data/spec/api/adapters_spec.rb +33 -0
  94. data/spec/api/batcher_spec.rb +112 -0
  95. data/spec/api/binding_spec.rb +50 -0
  96. data/spec/api/class_set_spec.rb +24 -0
  97. data/spec/api/hit_enumerable_spec.rb +47 -0
  98. data/spec/api/indexer/attributes_spec.rb +149 -0
  99. data/spec/api/indexer/batch_spec.rb +72 -0
  100. data/spec/api/indexer/dynamic_fields_spec.rb +42 -0
  101. data/spec/api/indexer/fixed_fields_spec.rb +57 -0
  102. data/spec/api/indexer/fulltext_spec.rb +43 -0
  103. data/spec/api/indexer/removal_spec.rb +53 -0
  104. data/spec/api/indexer/spec_helper.rb +1 -0
  105. data/spec/api/indexer_spec.rb +14 -0
  106. data/spec/api/query/advanced_manipulation_examples.rb +35 -0
  107. data/spec/api/query/connectives_examples.rb +189 -0
  108. data/spec/api/query/dsl_spec.rb +18 -0
  109. data/spec/api/query/dynamic_fields_examples.rb +165 -0
  110. data/spec/api/query/faceting_examples.rb +397 -0
  111. data/spec/api/query/fulltext_examples.rb +313 -0
  112. data/spec/api/query/function_spec.rb +79 -0
  113. data/spec/api/query/geo_examples.rb +68 -0
  114. data/spec/api/query/group_spec.rb +32 -0
  115. data/spec/api/query/highlighting_examples.rb +245 -0
  116. data/spec/api/query/more_like_this_spec.rb +140 -0
  117. data/spec/api/query/ordering_pagination_examples.rb +116 -0
  118. data/spec/api/query/scope_examples.rb +275 -0
  119. data/spec/api/query/spatial_examples.rb +27 -0
  120. data/spec/api/query/spec_helper.rb +1 -0
  121. data/spec/api/query/standard_spec.rb +29 -0
  122. data/spec/api/query/text_field_scoping_examples.rb +30 -0
  123. data/spec/api/query/types_spec.rb +20 -0
  124. data/spec/api/search/dynamic_fields_spec.rb +33 -0
  125. data/spec/api/search/faceting_spec.rb +360 -0
  126. data/spec/api/search/highlighting_spec.rb +69 -0
  127. data/spec/api/search/hits_spec.rb +131 -0
  128. data/spec/api/search/paginated_collection_spec.rb +36 -0
  129. data/spec/api/search/results_spec.rb +72 -0
  130. data/spec/api/search/search_spec.rb +23 -0
  131. data/spec/api/search/spec_helper.rb +1 -0
  132. data/spec/api/session_proxy/class_sharding_session_proxy_spec.rb +85 -0
  133. data/spec/api/session_proxy/id_sharding_session_proxy_spec.rb +30 -0
  134. data/spec/api/session_proxy/master_slave_session_proxy_spec.rb +41 -0
  135. data/spec/api/session_proxy/sharding_session_proxy_spec.rb +77 -0
  136. data/spec/api/session_proxy/silent_fail_session_proxy_spec.rb +24 -0
  137. data/spec/api/session_proxy/spec_helper.rb +9 -0
  138. data/spec/api/session_proxy/thread_local_session_proxy_spec.rb +39 -0
  139. data/spec/api/session_spec.rb +232 -0
  140. data/spec/api/spec_helper.rb +3 -0
  141. data/spec/api/sunspot_spec.rb +29 -0
  142. data/spec/ext.rb +11 -0
  143. data/spec/helpers/indexer_helper.rb +17 -0
  144. data/spec/helpers/integration_helper.rb +8 -0
  145. data/spec/helpers/mock_session_helper.rb +13 -0
  146. data/spec/helpers/query_helper.rb +26 -0
  147. data/spec/helpers/search_helper.rb +68 -0
  148. data/spec/integration/dynamic_fields_spec.rb +57 -0
  149. data/spec/integration/faceting_spec.rb +251 -0
  150. data/spec/integration/field_grouping_spec.rb +66 -0
  151. data/spec/integration/geospatial_spec.rb +85 -0
  152. data/spec/integration/highlighting_spec.rb +44 -0
  153. data/spec/integration/indexing_spec.rb +55 -0
  154. data/spec/integration/keyword_search_spec.rb +317 -0
  155. data/spec/integration/local_search_spec.rb +64 -0
  156. data/spec/integration/more_like_this_spec.rb +43 -0
  157. data/spec/integration/scoped_search_spec.rb +354 -0
  158. data/spec/integration/stored_fields_spec.rb +12 -0
  159. data/spec/integration/test_pagination.rb +43 -0
  160. data/spec/integration/unicode_spec.rb +15 -0
  161. data/spec/mocks/adapters.rb +32 -0
  162. data/spec/mocks/blog.rb +3 -0
  163. data/spec/mocks/comment.rb +21 -0
  164. data/spec/mocks/connection.rb +126 -0
  165. data/spec/mocks/mock_adapter.rb +30 -0
  166. data/spec/mocks/mock_class_sharding_session_proxy.rb +24 -0
  167. data/spec/mocks/mock_record.rb +52 -0
  168. data/spec/mocks/mock_sharding_session_proxy.rb +15 -0
  169. data/spec/mocks/photo.rb +11 -0
  170. data/spec/mocks/post.rb +86 -0
  171. data/spec/mocks/super_class.rb +2 -0
  172. data/spec/mocks/user.rb +13 -0
  173. data/spec/spec_helper.rb +40 -0
  174. data/sunspot.gemspec +42 -0
  175. data/tasks/rdoc.rake +27 -0
  176. data/tasks/schema.rake +19 -0
  177. data/tasks/todo.rake +4 -0
  178. metadata +261 -3
@@ -0,0 +1,68 @@
1
+ module SearchHelper
2
+ def stub_nil_results
3
+ connection.response = { 'response' => nil }
4
+ end
5
+
6
+ def stub_full_results(*results)
7
+ count =
8
+ if results.last.is_a?(Integer) then results.pop
9
+ else results.length
10
+ end
11
+ docs = results.map do |result|
12
+ instance = result.delete('instance')
13
+ result.merge('id' => "#{instance.class.name} #{instance.id}")
14
+ end
15
+ response = {
16
+ 'response' => {
17
+ 'docs' => docs,
18
+ 'numFound' => count
19
+ }
20
+ }
21
+ connection.response = response
22
+ response
23
+ end
24
+
25
+ def stub_results(*results)
26
+ stub_full_results(
27
+ *results.map do |result|
28
+ if result.is_a?(Integer)
29
+ result
30
+ else
31
+ { 'instance' => result }
32
+ end
33
+ end
34
+ )
35
+ end
36
+
37
+ def stub_facet(name, values)
38
+ connection.response = {
39
+ 'facet_counts' => {
40
+ 'facet_fields' => {
41
+ name.to_s => values.to_a.sort_by { |value, count| -count }.flatten
42
+ }
43
+ }
44
+ }
45
+ end
46
+
47
+ def stub_date_facet(name, gap, values)
48
+ connection.response = {
49
+ 'facet_counts' => {
50
+ 'facet_dates' => {
51
+ name.to_s => { 'gap' => "+#{gap}SECONDS" }.merge(values)
52
+ }
53
+ }
54
+ }
55
+ end
56
+
57
+ def stub_query_facet(values)
58
+ connection.response = { 'facet_counts' => { 'facet_queries' => values } }
59
+ end
60
+
61
+ def facet_values(result, field_name)
62
+ result.facet(field_name).rows.map { |row| row.value }
63
+ end
64
+
65
+ def facet_counts(result, field_name)
66
+ result.facet(field_name).rows.map { |row| row.count }
67
+ end
68
+ end
@@ -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,251 @@
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
+
138
+ it 'gives correct facet count when group == true and truncate == true' do
139
+ search = Sunspot.search(Post) do
140
+ group :title do
141
+ truncate
142
+ end
143
+
144
+ facet :title, :extra => :any
145
+ end
146
+
147
+ # Should be 5 instead of 11
148
+ search.facet(:title).rows.first.count.should == 5
149
+ end
150
+ end
151
+
152
+ context 'multiselect faceting' do
153
+ before do
154
+ Sunspot.remove_all
155
+ Sunspot.index!(
156
+ Post.new(:blog_id => 1, :category_ids => [1]),
157
+ Post.new(:blog_id => 1, :category_ids => [2]),
158
+ Post.new(:blog_id => 3, :category_ids => [3])
159
+ )
160
+ end
161
+
162
+ it 'should exclude filter from faceting' do
163
+ search = Sunspot.search(Post) do
164
+ with(:blog_id, 1)
165
+ category_filter = with(:category_ids, 1)
166
+ facet(:category_ids, :exclude => category_filter)
167
+ end
168
+ search.facet(:category_ids).rows.map { |row| row.value }.to_set.should == Set[1, 2]
169
+ end
170
+
171
+ it 'should use facet keys to facet more than once with different exclusions' do
172
+ search = Sunspot.search(Post) do
173
+ with(:blog_id, 1)
174
+ category_filter = with(:category_ids, 1)
175
+ facet(:category_ids)
176
+ facet(:category_ids, :exclude => category_filter, :name => :all_category_ids)
177
+ end
178
+ search.facet(:category_ids).rows.map { |row| row.value }.should == [1]
179
+ search.facet(:all_category_ids).rows.map { |row| row.value }.to_set.should == Set[1, 2]
180
+ end
181
+ end
182
+
183
+ context 'date facets' do
184
+ before :all do
185
+ Sunspot.remove_all
186
+ time = Time.utc(2009, 7, 8)
187
+ Sunspot.index!(
188
+ (0..2).map { |i| Post.new(:published_at => time + i*60*60*16) }
189
+ )
190
+ end
191
+
192
+ it 'should return time ranges' do
193
+ time = Time.utc(2009, 7, 8)
194
+ search = Sunspot.search(Post) do
195
+ facet :published_at, :time_range => time..(time + 60*60*24*2), :sort => :count
196
+ end
197
+ search.facet(:published_at).rows.first.value.should == (time..(time + 60*60*24))
198
+ search.facet(:published_at).rows.first.count.should == 2
199
+ search.facet(:published_at).rows.last.value.should == ((time + 60*60*24)..(time + 60*60*24*2))
200
+ search.facet(:published_at).rows.last.count.should == 1
201
+ end
202
+ end
203
+
204
+ context 'class facets' do
205
+ before :all do
206
+ Sunspot.remove_all
207
+ Sunspot.index!(Post.new, Post.new, Namespaced::Comment.new)
208
+ end
209
+
210
+ it 'should return classes' do
211
+ search = Sunspot.search(Post, Namespaced::Comment) do
212
+ facet(:class, :sort => :count)
213
+ end
214
+ search.facet(:class).rows.first.value.should == Post
215
+ search.facet(:class).rows.first.count.should == 2
216
+ search.facet(:class).rows.last.value.should == Namespaced::Comment
217
+ search.facet(:class).rows.last.count.should == 1
218
+ end
219
+ end
220
+
221
+ context 'query facets' do
222
+ before :all do
223
+ Sunspot.remove_all
224
+ Sunspot.index!(
225
+ [1.1, 1.2, 3.2, 3.4, 3.9, 4.1].map do |rating|
226
+ Post.new(:ratings_average => rating)
227
+ end
228
+ )
229
+ end
230
+
231
+ it 'should return specified facets' do
232
+ search = Sunspot.search(Post) do
233
+ facet :rating_range, :sort => :count do
234
+ for rating in [1.0, 2.0, 3.0, 4.0]
235
+ range = rating..(rating + 1.0)
236
+ row range do
237
+ with :average_rating, rating..(rating + 1.0)
238
+ end
239
+ end
240
+ end
241
+ end
242
+ facet = search.facet(:rating_range)
243
+ facet.rows[0].value.should == (3.0..4.0)
244
+ facet.rows[0].count.should == 3
245
+ facet.rows[1].value.should == (1.0..2.0)
246
+ facet.rows[1].count.should == 2
247
+ facet.rows[2].value.should == (4.0..5.0)
248
+ facet.rows[2].count.should == 1
249
+ end
250
+ end
251
+ end
@@ -0,0 +1,66 @@
1
+ require File.expand_path("../spec_helper", File.dirname(__FILE__))
2
+ require File.expand_path("../helpers/search_helper", File.dirname(__FILE__))
3
+
4
+ describe "field grouping" do
5
+ before :each do
6
+ Sunspot.remove_all
7
+
8
+ @posts = [
9
+ Post.new(:title => "Title1", :ratings_average => 4),
10
+ Post.new(:title => "Title1", :ratings_average => 5),
11
+ Post.new(:title => "Title2", :ratings_average => 3)
12
+ ]
13
+
14
+ Sunspot.index!(*@posts)
15
+ end
16
+
17
+ it "allows grouping by a field" do
18
+ search = Sunspot.search(Post) do
19
+ group :title
20
+ end
21
+
22
+ search.group(:title).groups.should include { |g| g.value == "Title1" }
23
+ search.group(:title).groups.should include { |g| g.value == "Title2" }
24
+ end
25
+
26
+ it "provides access to the number of matches before grouping" do
27
+ search = Sunspot.search(Post) do
28
+ group :title
29
+ end
30
+
31
+ search.group(:title).matches.should == @posts.length
32
+ end
33
+
34
+ it "allows grouping by multiple fields" do
35
+ search = Sunspot.search(Post) do
36
+ group :title, :sort_title
37
+ end
38
+
39
+ search.group(:title).groups.should_not be_empty
40
+ search.group(:sort_title).groups.should_not be_empty
41
+ end
42
+
43
+ it "allows specification of the number of documents per group" do
44
+ search = Sunspot.search(Post) do
45
+ group :title do
46
+ limit 2
47
+ end
48
+ end
49
+
50
+ title1_group = search.group(:title).groups.detect { |g| g.value == "Title1" }
51
+ title1_group.hits.length.should == 2
52
+ end
53
+
54
+ it "allows specification of the sort within groups" do
55
+ search = Sunspot.search(Post) do
56
+ group :title do
57
+ order_by(:average_rating, :desc)
58
+ end
59
+ end
60
+
61
+ highest_ranked_post = @posts.sort_by { |p| -p.ratings_average }.first
62
+
63
+ title1_group = search.group(:title).groups.detect { |g| g.value == "Title1" }
64
+ title1_group.hits.first.primary_key.to_i.should == highest_ranked_post.id
65
+ end
66
+ end
@@ -0,0 +1,85 @@
1
+ require File.expand_path('../spec_helper', File.dirname(__FILE__))
2
+
3
+ describe "geospatial search" do
4
+ describe "filtering by radius" do
5
+ before :all do
6
+ Sunspot.remove_all
7
+
8
+ @post = Post.new(:title => "Howdy",
9
+ :coordinates => Sunspot::Util::Coordinates.new(32, -68))
10
+ Sunspot.index!(@post)
11
+ end
12
+
13
+ it "matches posts within the radius" do
14
+ results = Sunspot.search(Post) {
15
+ with(:coordinates_new).in_radius(32, -68, 1)
16
+ }.results
17
+
18
+ results.should include(@post)
19
+ end
20
+
21
+ it "filters out posts not in the radius" do
22
+ results = Sunspot.search(Post) {
23
+ with(:coordinates_new).in_radius(33, -68, 1)
24
+ }.results
25
+
26
+ results.should_not include(@post)
27
+ end
28
+ end
29
+
30
+ describe "filtering by bounding box" do
31
+ before :all do
32
+ Sunspot.remove_all
33
+
34
+ @post = Post.new(:title => "Howdy",
35
+ :coordinates => Sunspot::Util::Coordinates.new(32, -68))
36
+ Sunspot.index!(@post)
37
+ end
38
+
39
+ it "matches post within the bounding box" do
40
+ results = Sunspot.search(Post) {
41
+ with(:coordinates_new).in_bounding_box [31, -69], [33, -67]
42
+ }.results
43
+
44
+ results.should include(@post)
45
+ end
46
+
47
+ it "filters out posts not in the bounding box" do
48
+ results = Sunspot.search(Post) {
49
+ with(:coordinates_new).in_bounding_box [20, -70], [21, -69]
50
+ }.results
51
+
52
+ results.should_not include(@post)
53
+ end
54
+ end
55
+
56
+ describe "ordering by geodist" do
57
+ before :all do
58
+ Sunspot.remove_all
59
+
60
+ @posts = [
61
+ Post.new(:title => "Howdy", :coordinates => Sunspot::Util::Coordinates.new(34, -68)),
62
+ Post.new(:title => "Howdy", :coordinates => Sunspot::Util::Coordinates.new(33, -68)),
63
+ Post.new(:title => "Howdy", :coordinates => Sunspot::Util::Coordinates.new(32, -68))
64
+ ]
65
+
66
+ Sunspot.index!(@posts)
67
+ end
68
+
69
+ it "orders posts by distance ascending" do
70
+ results = Sunspot.search(Post) {
71
+ order_by_geodist(:coordinates_new, 32, -68)
72
+ }.results
73
+
74
+ results.should == @posts.reverse
75
+ end
76
+
77
+ it "orders posts by distance descending" do
78
+ results = Sunspot.search(Post) {
79
+ order_by_geodist(:coordinates_new, 32, -68, :desc)
80
+ }.results
81
+
82
+ results.should == @posts
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,44 @@
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
+ @posts << Post.new(:body => 'Lorem ipsum dolor sit amet', :title => 'consectetur adipiscing elit', :blog_id => 1)
9
+ Sunspot.index!(*@posts)
10
+ @search_result = Sunspot.search(Post) { keywords 'fox', :highlight => true }
11
+ end
12
+
13
+ it 'should include highlights in the results' do
14
+ @search_result.hits.first.highlights.length.should == 1
15
+ end
16
+
17
+ it 'should return formatted highlight fragments' do
18
+ @search_result.hits.first.highlights(:body).first.format.should == 'And the <em>fox</em> laughed'
19
+ end
20
+
21
+ it 'should be empty for non-keyword searches' do
22
+ search_result = Sunspot.search(Post){ with :blog_id, 1 }
23
+ search_result.hits.first.highlights.should be_empty
24
+ end
25
+
26
+ it "should process multiple keyword request on different fields with highlights correctly" do
27
+ search_results = nil
28
+ lambda do
29
+ search_results = Sunspot.search(Post) do
30
+ keywords 'Lorem ipsum', :fields => [:body] do
31
+ highlight :body
32
+ end
33
+ keywords 'consectetur', :fields => [:title] do
34
+ highlight :title
35
+ end
36
+ end
37
+ end.should_not raise_error(RSolr::Error::Http)
38
+ search_results.results.length.should eq(1)
39
+ search_results.results.first.should eq(@posts.last)
40
+ # this one might be a Solr bug, therefore not related to Sunspot itself
41
+ # search_results.hits.first.highlights.should_not be_empty
42
+ end
43
+
44
+ end
@@ -0,0 +1,55 @@
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
+
34
+
35
+ describe "in batches" do
36
+ let(:post_1) { Post.new :title => 'A tittle' }
37
+ let(:post_2) { Post.new :title => 'Another title' }
38
+
39
+ describe "nested" do
40
+ let(:a_nested_batch) do
41
+ Sunspot.batch do
42
+ Sunspot.index post_1
43
+
44
+ Sunspot.batch do
45
+ Sunspot.index post_2
46
+ end
47
+ end
48
+ end
49
+
50
+ it "does not fail" do
51
+ expect { a_nested_batch }.to_not raise_error
52
+ end
53
+ end
54
+ end
55
+ end