sunspot_rbg 1.3.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.
Files changed (222) hide show
  1. data/.gitignore +12 -0
  2. data/Gemfile +4 -0
  3. data/History.txt +222 -0
  4. data/LICENSE +18 -0
  5. data/Rakefile +17 -0
  6. data/TODO +13 -0
  7. data/VERSION.yml +4 -0
  8. data/bin/sunspot-installer +19 -0
  9. data/bin/sunspot-solr +74 -0
  10. data/installer/config/schema.yml +95 -0
  11. data/lib/light_config.rb +40 -0
  12. data/lib/sunspot/adapters.rb +265 -0
  13. data/lib/sunspot/composite_setup.rb +202 -0
  14. data/lib/sunspot/configuration.rb +46 -0
  15. data/lib/sunspot/data_extractor.rb +50 -0
  16. data/lib/sunspot/dsl/adjustable.rb +47 -0
  17. data/lib/sunspot/dsl/field_query.rb +279 -0
  18. data/lib/sunspot/dsl/fields.rb +103 -0
  19. data/lib/sunspot/dsl/fulltext.rb +243 -0
  20. data/lib/sunspot/dsl/function.rb +14 -0
  21. data/lib/sunspot/dsl/functional.rb +44 -0
  22. data/lib/sunspot/dsl/more_like_this_query.rb +56 -0
  23. data/lib/sunspot/dsl/paginatable.rb +28 -0
  24. data/lib/sunspot/dsl/query_facet.rb +36 -0
  25. data/lib/sunspot/dsl/restriction.rb +25 -0
  26. data/lib/sunspot/dsl/restriction_with_near.rb +121 -0
  27. data/lib/sunspot/dsl/scope.rb +217 -0
  28. data/lib/sunspot/dsl/search.rb +30 -0
  29. data/lib/sunspot/dsl/standard_query.rb +121 -0
  30. data/lib/sunspot/dsl.rb +5 -0
  31. data/lib/sunspot/field.rb +193 -0
  32. data/lib/sunspot/field_factory.rb +129 -0
  33. data/lib/sunspot/indexer.rb +131 -0
  34. data/lib/sunspot/installer/library_installer.rb +45 -0
  35. data/lib/sunspot/installer/schema_builder.rb +219 -0
  36. data/lib/sunspot/installer/solrconfig_updater.rb +76 -0
  37. data/lib/sunspot/installer/task_helper.rb +18 -0
  38. data/lib/sunspot/installer.rb +31 -0
  39. data/lib/sunspot/query/abstract_field_facet.rb +52 -0
  40. data/lib/sunspot/query/boost_query.rb +24 -0
  41. data/lib/sunspot/query/common_query.rb +85 -0
  42. data/lib/sunspot/query/composite_fulltext.rb +36 -0
  43. data/lib/sunspot/query/connective.rb +206 -0
  44. data/lib/sunspot/query/date_field_facet.rb +14 -0
  45. data/lib/sunspot/query/dismax.rb +128 -0
  46. data/lib/sunspot/query/field_facet.rb +41 -0
  47. data/lib/sunspot/query/filter.rb +38 -0
  48. data/lib/sunspot/query/function_query.rb +52 -0
  49. data/lib/sunspot/query/geo.rb +53 -0
  50. data/lib/sunspot/query/highlighting.rb +55 -0
  51. data/lib/sunspot/query/more_like_this.rb +61 -0
  52. data/lib/sunspot/query/more_like_this_query.rb +12 -0
  53. data/lib/sunspot/query/pagination.rb +38 -0
  54. data/lib/sunspot/query/query_facet.rb +16 -0
  55. data/lib/sunspot/query/restriction.rb +262 -0
  56. data/lib/sunspot/query/scope.rb +9 -0
  57. data/lib/sunspot/query/sort.rb +95 -0
  58. data/lib/sunspot/query/sort_composite.rb +33 -0
  59. data/lib/sunspot/query/standard_query.rb +16 -0
  60. data/lib/sunspot/query/text_field_boost.rb +17 -0
  61. data/lib/sunspot/query.rb +11 -0
  62. data/lib/sunspot/schema.rb +151 -0
  63. data/lib/sunspot/search/abstract_search.rb +293 -0
  64. data/lib/sunspot/search/date_facet.rb +35 -0
  65. data/lib/sunspot/search/facet_row.rb +27 -0
  66. data/lib/sunspot/search/field_facet.rb +88 -0
  67. data/lib/sunspot/search/highlight.rb +38 -0
  68. data/lib/sunspot/search/hit.rb +136 -0
  69. data/lib/sunspot/search/more_like_this_search.rb +31 -0
  70. data/lib/sunspot/search/paginated_collection.rb +55 -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/server.rb +152 -0
  75. data/lib/sunspot/session.rb +260 -0
  76. data/lib/sunspot/session_proxy/abstract_session_proxy.rb +29 -0
  77. data/lib/sunspot/session_proxy/class_sharding_session_proxy.rb +66 -0
  78. data/lib/sunspot/session_proxy/id_sharding_session_proxy.rb +89 -0
  79. data/lib/sunspot/session_proxy/master_slave_session_proxy.rb +43 -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 +87 -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 +372 -0
  87. data/lib/sunspot/util.rb +243 -0
  88. data/lib/sunspot/version.rb +3 -0
  89. data/lib/sunspot.rb +569 -0
  90. data/lib/sunspot_rbg.rb +7 -0
  91. data/log/.gitignore +1 -0
  92. data/pkg/.gitignore +1 -0
  93. data/script/console +10 -0
  94. data/solr/README.txt +42 -0
  95. data/solr/etc/jetty.xml +218 -0
  96. data/solr/etc/webdefault.xml +379 -0
  97. data/solr/lib/jetty-6.1.3.jar +0 -0
  98. data/solr/lib/jetty-util-6.1.3.jar +0 -0
  99. data/solr/lib/jsp-2.1/ant-1.6.5.jar +0 -0
  100. data/solr/lib/jsp-2.1/core-3.1.1.jar +0 -0
  101. data/solr/lib/jsp-2.1/jsp-2.1.jar +0 -0
  102. data/solr/lib/jsp-2.1/jsp-api-2.1.jar +0 -0
  103. data/solr/lib/servlet-api-2.5-6.1.3.jar +0 -0
  104. data/solr/logs/.gitignore +1 -0
  105. data/solr/solr/.gitignore +1 -0
  106. data/solr/solr/README.txt +54 -0
  107. data/solr/solr/conf/admin-extra.html +31 -0
  108. data/solr/solr/conf/elevate.xml +36 -0
  109. data/solr/solr/conf/mapping-ISOLatin1Accent.txt +246 -0
  110. data/solr/solr/conf/protwords.txt +21 -0
  111. data/solr/solr/conf/schema.xml +238 -0
  112. data/solr/solr/conf/scripts.conf +24 -0
  113. data/solr/solr/conf/solrconfig.xml +934 -0
  114. data/solr/solr/conf/spellings.txt +2 -0
  115. data/solr/solr/conf/stopwords.txt +58 -0
  116. data/solr/solr/conf/synonyms.txt +31 -0
  117. data/solr/solr/conf/xslt/example.xsl +132 -0
  118. data/solr/solr/conf/xslt/example_atom.xsl +67 -0
  119. data/solr/solr/conf/xslt/example_rss.xsl +66 -0
  120. data/solr/solr/conf/xslt/luke.xsl +337 -0
  121. data/solr/start.jar +0 -0
  122. data/solr/webapps/solr.war +0 -0
  123. data/solr-1.3/etc/jetty.xml +212 -0
  124. data/solr-1.3/etc/webdefault.xml +379 -0
  125. data/solr-1.3/lib/jetty-6.1.3.jar +0 -0
  126. data/solr-1.3/lib/jetty-util-6.1.3.jar +0 -0
  127. data/solr-1.3/lib/jsp-2.1/ant-1.6.5.jar +0 -0
  128. data/solr-1.3/lib/jsp-2.1/core-3.1.1.jar +0 -0
  129. data/solr-1.3/lib/jsp-2.1/jsp-2.1.jar +0 -0
  130. data/solr-1.3/lib/jsp-2.1/jsp-api-2.1.jar +0 -0
  131. data/solr-1.3/lib/servlet-api-2.5-6.1.3.jar +0 -0
  132. data/solr-1.3/solr/conf/elevate.xml +36 -0
  133. data/solr-1.3/solr/conf/protwords.txt +21 -0
  134. data/solr-1.3/solr/conf/schema.xml +64 -0
  135. data/solr-1.3/solr/conf/solrconfig.xml +725 -0
  136. data/solr-1.3/solr/conf/stopwords.txt +57 -0
  137. data/solr-1.3/solr/conf/synonyms.txt +31 -0
  138. data/solr-1.3/solr/lib/geoapi-nogenerics-2.1-M2.jar +0 -0
  139. data/solr-1.3/solr/lib/gt2-referencing-2.3.1.jar +0 -0
  140. data/solr-1.3/solr/lib/jsr108-0.01.jar +0 -0
  141. data/solr-1.3/solr/lib/locallucene.jar +0 -0
  142. data/solr-1.3/solr/lib/localsolr.jar +0 -0
  143. data/solr-1.3/start.jar +0 -0
  144. data/solr-1.3/webapps/solr.war +0 -0
  145. data/spec/api/adapters_spec.rb +33 -0
  146. data/spec/api/binding_spec.rb +50 -0
  147. data/spec/api/indexer/attributes_spec.rb +149 -0
  148. data/spec/api/indexer/batch_spec.rb +46 -0
  149. data/spec/api/indexer/dynamic_fields_spec.rb +42 -0
  150. data/spec/api/indexer/fixed_fields_spec.rb +57 -0
  151. data/spec/api/indexer/fulltext_spec.rb +43 -0
  152. data/spec/api/indexer/removal_spec.rb +53 -0
  153. data/spec/api/indexer/spec_helper.rb +1 -0
  154. data/spec/api/indexer_spec.rb +14 -0
  155. data/spec/api/query/advanced_manipulation_examples.rb +35 -0
  156. data/spec/api/query/connectives_examples.rb +189 -0
  157. data/spec/api/query/dsl_spec.rb +18 -0
  158. data/spec/api/query/dynamic_fields_examples.rb +165 -0
  159. data/spec/api/query/faceting_examples.rb +397 -0
  160. data/spec/api/query/fulltext_examples.rb +313 -0
  161. data/spec/api/query/function_spec.rb +70 -0
  162. data/spec/api/query/geo_examples.rb +68 -0
  163. data/spec/api/query/highlighting_examples.rb +223 -0
  164. data/spec/api/query/more_like_this_spec.rb +140 -0
  165. data/spec/api/query/ordering_pagination_examples.rb +95 -0
  166. data/spec/api/query/scope_examples.rb +275 -0
  167. data/spec/api/query/spec_helper.rb +1 -0
  168. data/spec/api/query/standard_spec.rb +28 -0
  169. data/spec/api/query/text_field_scoping_examples.rb +30 -0
  170. data/spec/api/query/types_spec.rb +20 -0
  171. data/spec/api/search/dynamic_fields_spec.rb +33 -0
  172. data/spec/api/search/faceting_spec.rb +360 -0
  173. data/spec/api/search/highlighting_spec.rb +69 -0
  174. data/spec/api/search/hits_spec.rb +120 -0
  175. data/spec/api/search/paginated_collection_spec.rb +26 -0
  176. data/spec/api/search/results_spec.rb +66 -0
  177. data/spec/api/search/search_spec.rb +23 -0
  178. data/spec/api/search/spec_helper.rb +1 -0
  179. data/spec/api/server_spec.rb +91 -0
  180. data/spec/api/session_proxy/class_sharding_session_proxy_spec.rb +85 -0
  181. data/spec/api/session_proxy/id_sharding_session_proxy_spec.rb +30 -0
  182. data/spec/api/session_proxy/master_slave_session_proxy_spec.rb +41 -0
  183. data/spec/api/session_proxy/sharding_session_proxy_spec.rb +77 -0
  184. data/spec/api/session_proxy/silent_fail_session_proxy_spec.rb +24 -0
  185. data/spec/api/session_proxy/spec_helper.rb +9 -0
  186. data/spec/api/session_proxy/thread_local_session_proxy_spec.rb +50 -0
  187. data/spec/api/session_spec.rb +220 -0
  188. data/spec/api/spec_helper.rb +3 -0
  189. data/spec/api/sunspot_spec.rb +18 -0
  190. data/spec/ext.rb +11 -0
  191. data/spec/helpers/indexer_helper.rb +29 -0
  192. data/spec/helpers/query_helper.rb +38 -0
  193. data/spec/helpers/search_helper.rb +80 -0
  194. data/spec/integration/dynamic_fields_spec.rb +55 -0
  195. data/spec/integration/faceting_spec.rb +238 -0
  196. data/spec/integration/highlighting_spec.rb +22 -0
  197. data/spec/integration/indexing_spec.rb +33 -0
  198. data/spec/integration/keyword_search_spec.rb +317 -0
  199. data/spec/integration/local_search_spec.rb +64 -0
  200. data/spec/integration/more_like_this_spec.rb +43 -0
  201. data/spec/integration/scoped_search_spec.rb +354 -0
  202. data/spec/integration/spec_helper.rb +7 -0
  203. data/spec/integration/stored_fields_spec.rb +10 -0
  204. data/spec/integration/test_pagination.rb +32 -0
  205. data/spec/mocks/adapters.rb +32 -0
  206. data/spec/mocks/blog.rb +3 -0
  207. data/spec/mocks/comment.rb +21 -0
  208. data/spec/mocks/connection.rb +126 -0
  209. data/spec/mocks/mock_adapter.rb +30 -0
  210. data/spec/mocks/mock_class_sharding_session_proxy.rb +24 -0
  211. data/spec/mocks/mock_record.rb +52 -0
  212. data/spec/mocks/mock_sharding_session_proxy.rb +15 -0
  213. data/spec/mocks/photo.rb +11 -0
  214. data/spec/mocks/post.rb +85 -0
  215. data/spec/mocks/super_class.rb +2 -0
  216. data/spec/mocks/user.rb +13 -0
  217. data/spec/spec_helper.rb +30 -0
  218. data/sunspot.gemspec +40 -0
  219. data/tasks/rdoc.rake +27 -0
  220. data/tasks/schema.rake +19 -0
  221. data/tasks/todo.rake +4 -0
  222. metadata +457 -0
@@ -0,0 +1,29 @@
1
+ module IndexerHelper
2
+ def config
3
+ Sunspot::Configuration.build
4
+ end
5
+
6
+ def connection
7
+ @connection ||= Mock::Connection.new
8
+ end
9
+
10
+ def session
11
+ @session ||= Sunspot::Session.new(config, connection)
12
+ end
13
+
14
+ def post(attrs = {})
15
+ @post ||= Post.new(attrs)
16
+ end
17
+
18
+ def last_add
19
+ @connection.adds.last
20
+ end
21
+
22
+ def value_in_last_document_for(field_name)
23
+ @connection.adds.last.last.field_by_name(field_name).value
24
+ end
25
+
26
+ def values_in_last_document_for(field_name)
27
+ @connection.adds.last.last.fields_by_name(field_name).map { |field| field.value }
28
+ end
29
+ end
@@ -0,0 +1,38 @@
1
+ module QueryHelper
2
+ def config
3
+ @config ||= Sunspot::Configuration.build
4
+ end
5
+
6
+ def connection
7
+ @connection ||= Mock::Connection.new
8
+ end
9
+
10
+ def session
11
+ @session ||= Sunspot::Session.new(config, connection)
12
+ end
13
+
14
+ def get_filter_tag(boolean_query)
15
+ connection.searches.last[:fq].each do |fq|
16
+ if match = fq.match(/^\{!tag=(.+)\}#{Regexp.escape(boolean_query)}$/)
17
+ return match[1]
18
+ end
19
+ end
20
+ nil
21
+ end
22
+
23
+ def subqueries(param)
24
+ q = connection.searches.last[:q]
25
+ subqueries = []
26
+ subqueries = q.scan(%r(_query_:"\{!dismax (.*?)\}(.*?)"))
27
+ subqueries.map do |subquery|
28
+ params = {}
29
+ subquery[0].scan(%r((\S+?)='(.+?)')) do |key, value|
30
+ params[key.to_sym] = value
31
+ end
32
+ unless subquery[1].empty?
33
+ params[:v] = subquery[1]
34
+ end
35
+ params
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,80 @@
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
+
69
+ def config
70
+ @config ||= Sunspot::Configuration.build
71
+ end
72
+
73
+ def connection
74
+ @connection ||= Mock::Connection.new
75
+ end
76
+
77
+ def session
78
+ @session ||= Sunspot::Session.new(config, connection)
79
+ end
80
+ end
@@ -0,0 +1,55 @@
1
+ describe 'dynamic fields' do
2
+ before :each do
3
+ Sunspot.remove_all
4
+ @posts = Post.new(:custom_string => { :cuisine => 'Pizza' }),
5
+ Post.new(:custom_string => { :cuisine => 'Greek' }),
6
+ Post.new(:custom_string => { :cuisine => 'Greek' })
7
+ Sunspot.index!(@posts)
8
+ end
9
+
10
+ it 'should search for dynamic string field' do
11
+ Sunspot.search(Post) do
12
+ dynamic(:custom_string) do
13
+ with(:cuisine, 'Pizza')
14
+ end
15
+ end.results.should == [@posts.first]
16
+ end
17
+
18
+ describe 'faceting' do
19
+ before :each do
20
+ @search = Sunspot.search(Post) do
21
+ dynamic :custom_string do
22
+ facet :cuisine
23
+ end
24
+ end
25
+ end
26
+
27
+ it 'should return value "value" with count 2' do
28
+ row = @search.dynamic_facet(:custom_string, :cuisine).rows[0]
29
+ row.value.should == 'Greek'
30
+ row.count.should == 2
31
+ end
32
+
33
+ it 'should return value "other" with count 1' do
34
+ row = @search.dynamic_facet(:custom_string, :cuisine).rows[1]
35
+ row.value.should == 'Pizza'
36
+ row.count.should == 1
37
+ end
38
+ end
39
+
40
+ it 'should order by dynamic string field ascending' do
41
+ Sunspot.search(Post) do
42
+ dynamic :custom_string do
43
+ order_by :cuisine, :asc
44
+ end
45
+ end.results.last.should == @posts.first
46
+ end
47
+
48
+ it 'should order by dynamic string field descending' do
49
+ Sunspot.search(Post) do
50
+ dynamic :custom_string do
51
+ order_by :cuisine, :desc
52
+ end
53
+ end.results.first.should == @posts.first
54
+ end
55
+ 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,22 @@
1
+ describe 'keyword highlighting' do
2
+ before :all do
3
+ @posts = []
4
+ @posts << Post.new(:body => 'And the fox laughed')
5
+ @posts << Post.new(:body => 'Lorem ipsum dolor sit amet, consectetur adipiscing elit', :blog_id => 1)
6
+ Sunspot.index!(*@posts)
7
+ @search_result = Sunspot.search(Post) { keywords 'fox', :highlight => true }
8
+ end
9
+
10
+ it 'should include highlights in the results' do
11
+ @search_result.hits.first.highlights.length.should == 1
12
+ end
13
+
14
+ it 'should return formatted highlight fragments' do
15
+ @search_result.hits.first.highlights(:body).first.format.should == 'And the <em>fox</em> laughed'
16
+ end
17
+
18
+ it 'should be empty for non-keyword searches' do
19
+ search_result = Sunspot.search(Post){ with :blog_id, 1 }
20
+ search_result.hits.first.highlights.should be_empty
21
+ end
22
+ 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