sunspot 2.0.0 → 2.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (165) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +1 -0
  3. data/.rspec +2 -0
  4. data/Appraisals +7 -0
  5. data/Gemfile +0 -2
  6. data/History.txt +10 -0
  7. data/lib/sunspot.rb +55 -17
  8. data/lib/sunspot/adapters.rb +68 -18
  9. data/lib/sunspot/batcher.rb +1 -1
  10. data/lib/sunspot/configuration.rb +4 -2
  11. data/lib/sunspot/data_extractor.rb +36 -6
  12. data/lib/sunspot/dsl.rb +4 -3
  13. data/lib/sunspot/dsl/adjustable.rb +2 -2
  14. data/lib/sunspot/dsl/field_query.rb +69 -16
  15. data/lib/sunspot/dsl/field_stats.rb +25 -0
  16. data/lib/sunspot/dsl/fields.rb +28 -8
  17. data/lib/sunspot/dsl/fulltext.rb +9 -1
  18. data/lib/sunspot/dsl/group.rb +118 -0
  19. data/lib/sunspot/dsl/paginatable.rb +4 -1
  20. data/lib/sunspot/dsl/scope.rb +19 -10
  21. data/lib/sunspot/dsl/search.rb +1 -1
  22. data/lib/sunspot/dsl/spellcheckable.rb +14 -0
  23. data/lib/sunspot/dsl/standard_query.rb +63 -35
  24. data/lib/sunspot/field.rb +76 -4
  25. data/lib/sunspot/field_factory.rb +60 -11
  26. data/lib/sunspot/indexer.rb +70 -18
  27. data/lib/sunspot/query.rb +5 -4
  28. data/lib/sunspot/query/abstract_field_facet.rb +0 -2
  29. data/lib/sunspot/query/abstract_fulltext.rb +76 -0
  30. data/lib/sunspot/query/abstract_json_field_facet.rb +70 -0
  31. data/lib/sunspot/query/bbox.rb +5 -1
  32. data/lib/sunspot/query/common_query.rb +31 -6
  33. data/lib/sunspot/query/composite_fulltext.rb +58 -8
  34. data/lib/sunspot/query/date_field_json_facet.rb +25 -0
  35. data/lib/sunspot/query/dismax.rb +25 -71
  36. data/lib/sunspot/query/field_json_facet.rb +19 -0
  37. data/lib/sunspot/query/field_list.rb +15 -0
  38. data/lib/sunspot/query/field_stats.rb +61 -0
  39. data/lib/sunspot/query/function_query.rb +1 -2
  40. data/lib/sunspot/query/geo.rb +1 -1
  41. data/lib/sunspot/query/geofilt.rb +8 -3
  42. data/lib/sunspot/query/group.rb +46 -0
  43. data/lib/sunspot/query/group_query.rb +17 -0
  44. data/lib/sunspot/query/join.rb +88 -0
  45. data/lib/sunspot/query/more_like_this.rb +1 -1
  46. data/lib/sunspot/query/pagination.rb +12 -4
  47. data/lib/sunspot/query/range_json_facet.rb +28 -0
  48. data/lib/sunspot/query/restriction.rb +99 -13
  49. data/lib/sunspot/query/sort.rb +41 -0
  50. data/lib/sunspot/query/sort_composite.rb +7 -0
  51. data/lib/sunspot/query/spellcheck.rb +19 -0
  52. data/lib/sunspot/query/standard_query.rb +24 -2
  53. data/lib/sunspot/query/text_field_boost.rb +1 -3
  54. data/lib/sunspot/schema.rb +12 -3
  55. data/lib/sunspot/search.rb +4 -2
  56. data/lib/sunspot/search/abstract_search.rb +93 -43
  57. data/lib/sunspot/search/cursor_paginated_collection.rb +32 -0
  58. data/lib/sunspot/search/field_facet.rb +4 -4
  59. data/lib/sunspot/search/field_json_facet.rb +33 -0
  60. data/lib/sunspot/search/field_stats.rb +21 -0
  61. data/lib/sunspot/search/hit.rb +6 -1
  62. data/lib/sunspot/search/hit_enumerable.rb +4 -1
  63. data/lib/sunspot/search/json_facet_row.rb +40 -0
  64. data/lib/sunspot/search/json_facet_stats.rb +23 -0
  65. data/lib/sunspot/search/paginated_collection.rb +1 -0
  66. data/lib/sunspot/search/query_group.rb +74 -0
  67. data/lib/sunspot/search/standard_search.rb +70 -3
  68. data/lib/sunspot/search/stats_facet.rb +25 -0
  69. data/lib/sunspot/search/stats_json_row.rb +82 -0
  70. data/lib/sunspot/search/stats_row.rb +68 -0
  71. data/lib/sunspot/session.rb +62 -37
  72. data/lib/sunspot/session_proxy/class_sharding_session_proxy.rb +6 -4
  73. data/lib/sunspot/session_proxy/id_sharding_session_proxy.rb +16 -8
  74. data/lib/sunspot/session_proxy/master_slave_session_proxy.rb +2 -2
  75. data/lib/sunspot/session_proxy/retry_5xx_session_proxy.rb +1 -1
  76. data/lib/sunspot/session_proxy/sharding_session_proxy.rb +4 -2
  77. data/lib/sunspot/session_proxy/silent_fail_session_proxy.rb +1 -1
  78. data/lib/sunspot/session_proxy/thread_local_session_proxy.rb +6 -4
  79. data/lib/sunspot/setup.rb +42 -0
  80. data/lib/sunspot/type.rb +20 -0
  81. data/lib/sunspot/util.rb +78 -14
  82. data/lib/sunspot/version.rb +1 -1
  83. data/spec/api/adapters_spec.rb +40 -15
  84. data/spec/api/batcher_spec.rb +15 -15
  85. data/spec/api/binding_spec.rb +3 -3
  86. data/spec/api/class_set_spec.rb +6 -6
  87. data/spec/api/data_extractor_spec.rb +39 -0
  88. data/spec/api/hit_enumerable_spec.rb +32 -9
  89. data/spec/api/indexer/attributes_spec.rb +35 -30
  90. data/spec/api/indexer/batch_spec.rb +8 -7
  91. data/spec/api/indexer/dynamic_fields_spec.rb +8 -8
  92. data/spec/api/indexer/fixed_fields_spec.rb +16 -11
  93. data/spec/api/indexer/fulltext_spec.rb +8 -8
  94. data/spec/api/indexer/removal_spec.rb +24 -14
  95. data/spec/api/indexer_spec.rb +2 -2
  96. data/spec/api/query/advanced_manipulation_examples.rb +3 -3
  97. data/spec/api/query/connectives_examples.rb +26 -14
  98. data/spec/api/query/dsl_spec.rb +24 -6
  99. data/spec/api/query/dynamic_fields_examples.rb +18 -18
  100. data/spec/api/query/faceting_examples.rb +80 -61
  101. data/spec/api/query/fulltext_examples.rb +194 -40
  102. data/spec/api/query/function_spec.rb +116 -13
  103. data/spec/api/query/geo_examples.rb +8 -12
  104. data/spec/api/query/group_spec.rb +27 -5
  105. data/spec/api/query/highlighting_examples.rb +26 -26
  106. data/spec/api/query/join_spec.rb +19 -0
  107. data/spec/api/query/more_like_this_spec.rb +40 -27
  108. data/spec/api/query/ordering_pagination_examples.rb +37 -23
  109. data/spec/api/query/scope_examples.rb +39 -39
  110. data/spec/api/query/spatial_examples.rb +3 -3
  111. data/spec/api/query/spellcheck_examples.rb +20 -0
  112. data/spec/api/query/standard_spec.rb +3 -1
  113. data/spec/api/query/stats_examples.rb +66 -0
  114. data/spec/api/query/text_field_scoping_examples.rb +5 -5
  115. data/spec/api/query/types_spec.rb +4 -4
  116. data/spec/api/search/cursor_paginated_collection_spec.rb +35 -0
  117. data/spec/api/search/dynamic_fields_spec.rb +4 -4
  118. data/spec/api/search/faceting_spec.rb +55 -52
  119. data/spec/api/search/highlighting_spec.rb +7 -7
  120. data/spec/api/search/hits_spec.rb +43 -29
  121. data/spec/api/search/paginated_collection_spec.rb +19 -18
  122. data/spec/api/search/results_spec.rb +13 -13
  123. data/spec/api/search/search_spec.rb +3 -3
  124. data/spec/api/search/stats_spec.rb +94 -0
  125. data/spec/api/session_proxy/class_sharding_session_proxy_spec.rb +23 -16
  126. data/spec/api/session_proxy/id_sharding_session_proxy_spec.rb +16 -4
  127. data/spec/api/session_proxy/master_slave_session_proxy_spec.rb +10 -6
  128. data/spec/api/session_proxy/retry_5xx_session_proxy_spec.rb +11 -11
  129. data/spec/api/session_proxy/sharding_session_proxy_spec.rb +15 -14
  130. data/spec/api/session_proxy/silent_fail_session_proxy_spec.rb +3 -3
  131. data/spec/api/session_proxy/spec_helper.rb +1 -1
  132. data/spec/api/session_proxy/thread_local_session_proxy_spec.rb +40 -26
  133. data/spec/api/session_spec.rb +78 -38
  134. data/spec/api/sunspot_spec.rb +7 -4
  135. data/spec/helpers/integration_helper.rb +11 -1
  136. data/spec/helpers/query_helper.rb +1 -1
  137. data/spec/helpers/search_helper.rb +30 -0
  138. data/spec/integration/atomic_updates_spec.rb +58 -0
  139. data/spec/integration/dynamic_fields_spec.rb +31 -20
  140. data/spec/integration/faceting_spec.rb +252 -39
  141. data/spec/integration/field_grouping_spec.rb +47 -15
  142. data/spec/integration/field_lists_spec.rb +57 -0
  143. data/spec/integration/geospatial_spec.rb +34 -8
  144. data/spec/integration/highlighting_spec.rb +8 -8
  145. data/spec/integration/indexing_spec.rb +7 -6
  146. data/spec/integration/join_spec.rb +45 -0
  147. data/spec/integration/keyword_search_spec.rb +68 -38
  148. data/spec/integration/local_search_spec.rb +4 -4
  149. data/spec/integration/more_like_this_spec.rb +7 -7
  150. data/spec/integration/scoped_search_spec.rb +193 -74
  151. data/spec/integration/spellcheck_spec.rb +119 -0
  152. data/spec/integration/stats_spec.rb +88 -0
  153. data/spec/integration/stored_fields_spec.rb +1 -1
  154. data/spec/integration/test_pagination.rb +4 -4
  155. data/spec/integration/unicode_spec.rb +1 -1
  156. data/spec/mocks/adapters.rb +36 -0
  157. data/spec/mocks/connection.rb +5 -3
  158. data/spec/mocks/photo.rb +32 -1
  159. data/spec/mocks/post.rb +18 -3
  160. data/spec/spec_helper.rb +13 -8
  161. data/sunspot.gemspec +6 -4
  162. data/tasks/rdoc.rake +22 -14
  163. metadata +101 -44
  164. data/lib/sunspot/dsl/field_group.rb +0 -57
  165. data/lib/sunspot/query/field_group.rb +0 -37
@@ -7,8 +7,8 @@ describe Sunspot do
7
7
  Sunspot.setup(User) do
8
8
  text :name
9
9
  end
10
- Sunspot.searchable.should_not be_empty
11
- Sunspot.searchable.should include(User)
10
+ expect(Sunspot.searchable).not_to be_empty
11
+ expect(Sunspot.searchable).to include(User)
12
12
  end
13
13
  end
14
14
 
@@ -16,14 +16,17 @@ describe Sunspot do
16
16
  it "should reset current session" do
17
17
  old_session = Sunspot.send(:session)
18
18
  Sunspot.reset!(true)
19
- Sunspot.send(:session).should_not == old_session
19
+ expect(Sunspot.send(:session)).not_to eq(old_session)
20
20
  end
21
21
 
22
22
  it "should keep keep configuration if specified" do
23
23
  Sunspot.config.solr.url = "http://localhost:9999/path/solr"
24
24
  config_before_reset = Sunspot.config
25
25
  Sunspot.reset!(true)
26
- Sunspot.config.should == config_before_reset
26
+ expect(Sunspot.config).to eq(config_before_reset)
27
+
28
+ # Restore sunspot config after test
29
+ Sunspot.reset!(false)
27
30
  end
28
31
  end
29
32
  end
@@ -1,8 +1,18 @@
1
1
  module IntegrationHelper
2
2
  def self.included(base)
3
3
  base.before(:all) do
4
- Sunspot.config.solr.url = ENV['SOLR_URL'] || 'http://localhost:8983/solr'
4
+ Sunspot.config.solr.url = ENV['SOLR_URL'] || 'http://localhost:8983/solr/default'
5
+ Sunspot.config.solr.update_format = ENV['UPDATE_FORMAT'].to_sym if ENV['UPDATE_FORMAT']
5
6
  Sunspot.reset!(true)
6
7
  end
7
8
  end
9
+
10
+ def featured_for_posts(method, param, negated = false)
11
+ with_method = negated ? :without : :with
12
+ param = date_ranges[param] if param.is_a? String
13
+
14
+ Sunspot.search(Post) do
15
+ send(with_method, :featured_for).send(method, param)
16
+ end.results
17
+ end
8
18
  end
@@ -11,7 +11,7 @@ module QueryHelper
11
11
  def subqueries(param)
12
12
  q = connection.searches.last[:q]
13
13
  subqueries = []
14
- subqueries = q.scan(%r(_query_:"\{!dismax (.*?)\}(.*?)"))
14
+ subqueries = q.scan(%r(_query_:"\{!edismax (.*?)\}(.*?)"))
15
15
  subqueries.map do |subquery|
16
16
  params = {}
17
17
  subquery[0].scan(%r((\S+?)='(.+?)')) do |key, value|
@@ -54,6 +54,28 @@ module SearchHelper
54
54
  }
55
55
  end
56
56
 
57
+ def stub_stats(name, values)
58
+ connection.response = {
59
+ 'stats' => {
60
+ 'stats_fields' => {
61
+ name.to_s => { :facets => {} }.merge(values)
62
+ }
63
+ }
64
+ }
65
+ end
66
+
67
+ def stub_stats_facets(name, facets)
68
+ connection.response = {
69
+ 'stats' => {
70
+ 'stats_fields' => {
71
+ name.to_s => {
72
+ 'facets' => facets
73
+ }
74
+ }
75
+ }
76
+ }
77
+ end
78
+
57
79
  def stub_query_facet(values)
58
80
  connection.response = { 'facet_counts' => { 'facet_queries' => values } }
59
81
  end
@@ -65,4 +87,12 @@ module SearchHelper
65
87
  def facet_counts(result, field_name)
66
88
  result.facet(field_name).rows.map { |row| row.count }
67
89
  end
90
+
91
+ def stats_facet_values(result, field_name, facet_name)
92
+ result.stats(field_name).facet(facet_name).rows.map(&:value)
93
+ end
94
+
95
+ def stats_facet_stats(result, field_name, facet_name, value)
96
+ result.stats(field_name).facet(facet_name).rows.find { |r| r.value == value }
97
+ end
68
98
  end
@@ -0,0 +1,58 @@
1
+ require File.expand_path('../spec_helper', File.dirname(__FILE__))
2
+
3
+ describe 'atomic updates' do
4
+ before :all do
5
+ Sunspot.remove_all
6
+ end
7
+
8
+ def validate_hit(hit, values = {})
9
+ values.each do |field, value|
10
+ stored = hit.stored(field)
11
+ expect(stored).to eq(value), "expected #{value.inspect}, but got #{stored.inspect} for field '#{field}'"
12
+ end
13
+ end
14
+
15
+ def find_indexed_post(id)
16
+ hit = Sunspot.search(Post).hits.find{ |h| h.primary_key.to_i == id }
17
+ expect(hit).not_to be_nil
18
+ hit
19
+ end
20
+
21
+ it 'should update single record fields one by one' do
22
+ post = Post.new(title: 'A Title', featured: true)
23
+ Sunspot.index!(post)
24
+
25
+ validate_hit(find_indexed_post(post.id), title: post.title, featured: post.featured)
26
+
27
+ Sunspot.atomic_update!(Post, post.id => {title: 'A New Title'})
28
+ validate_hit(find_indexed_post(post.id), title: 'A New Title', featured: true)
29
+
30
+ Sunspot.atomic_update!(Post, post.id => {featured: false})
31
+ validate_hit(find_indexed_post(post.id), title: 'A New Title', featured: false)
32
+ end
33
+
34
+ it 'should update fields for multiple records' do
35
+ post1 = Post.new(title: 'A First Title', featured: true)
36
+ post2 = Post.new(title: 'A Second Title', featured: false)
37
+ Sunspot.index!(post1, post2)
38
+
39
+ validate_hit(find_indexed_post(post1.id), title: post1.title, featured: post1.featured)
40
+ validate_hit(find_indexed_post(post2.id), title: post2.title, featured: post2.featured)
41
+
42
+ Sunspot.atomic_update!(Post, post1.id => {title: 'A New Title'}, post2.id => {featured: true})
43
+ validate_hit(find_indexed_post(post1.id), title: 'A New Title', featured: true)
44
+ validate_hit(find_indexed_post(post2.id), title: 'A Second Title', featured: true)
45
+ end
46
+
47
+ it 'should clear field value properly' do
48
+ post = Post.new(title: 'A Title', tags: %w(tag1 tag2), featured: true)
49
+ Sunspot.index!(post)
50
+ validate_hit(find_indexed_post(post.id), title: post.title, tag_list: post.tags, featured: true)
51
+
52
+ Sunspot.atomic_update!(Post, post.id => {tag_list: []})
53
+ validate_hit(find_indexed_post(post.id), title: post.title, tag_list: nil, featured: true)
54
+
55
+ Sunspot.atomic_update!(Post, post.id => {featured: nil})
56
+ validate_hit(find_indexed_post(post.id), title: post.title, tag_list: nil, featured: nil)
57
+ end
58
+ end
@@ -1,57 +1,68 @@
1
1
  require File.expand_path('../spec_helper', File.dirname(__FILE__))
2
2
 
3
- describe 'dynamic fields' do
3
+ shared_examples 'dynamic fields' do
4
4
  before :each do
5
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' })
6
+ @posts = Post.new(field_name => { :cuisine => 'Pizza' }),
7
+ Post.new(field_name => { :cuisine => 'Greek' }),
8
+ Post.new(field_name => { :cuisine => 'Greek' })
9
9
  Sunspot.index!(@posts)
10
10
  end
11
11
 
12
12
  it 'should search for dynamic string field' do
13
- Sunspot.search(Post) do
14
- dynamic(:custom_string) do
13
+ expect(Sunspot.search(Post) do
14
+ dynamic(field_name) do
15
15
  with(:cuisine, 'Pizza')
16
16
  end
17
- end.results.should == [@posts.first]
17
+ end.results).to eq([@posts.first])
18
18
  end
19
19
 
20
20
  describe 'faceting' do
21
21
  before :each do
22
22
  @search = Sunspot.search(Post) do
23
- dynamic :custom_string do
23
+ dynamic field_name do
24
24
  facet :cuisine
25
25
  end
26
26
  end
27
27
  end
28
28
 
29
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
30
+ row = @search.dynamic_facet(field_name, :cuisine).rows[0]
31
+ expect(row.value).to eq('Greek')
32
+ expect(row.count).to eq(2)
33
33
  end
34
34
 
35
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
36
+ row = @search.dynamic_facet(field_name, :cuisine).rows[1]
37
+ expect(row.value).to eq('Pizza')
38
+ expect(row.count).to eq(1)
39
39
  end
40
40
  end
41
41
 
42
42
  it 'should order by dynamic string field ascending' do
43
- Sunspot.search(Post) do
44
- dynamic :custom_string do
43
+ expect(Sunspot.search(Post) do
44
+ dynamic field_name do
45
45
  order_by :cuisine, :asc
46
46
  end
47
- end.results.last.should == @posts.first
47
+ end.results.last).to eq(@posts.first)
48
48
  end
49
49
 
50
50
  it 'should order by dynamic string field descending' do
51
- Sunspot.search(Post) do
52
- dynamic :custom_string do
51
+ expect(Sunspot.search(Post) do
52
+ dynamic field_name do
53
53
  order_by :cuisine, :desc
54
54
  end
55
- end.results.first.should == @posts.first
55
+ end.results.first).to eq(@posts.first)
56
+ end
57
+ end
58
+
59
+ describe "default separator" do
60
+ it_behaves_like "dynamic fields" do
61
+ let(:field_name) { :custom_string }
62
+ end
63
+ end
64
+ describe "custom separator" do
65
+ it_behaves_like "dynamic fields" do
66
+ let(:field_name) { :custom_underscored_string }
56
67
  end
57
68
  end
@@ -27,14 +27,14 @@ describe 'search faceting' do
27
27
 
28
28
  it "should return value #{value1.inspect} with count 2" do
29
29
  row = @search.facet(field).rows[0]
30
- row.value.should == value1
31
- row.count.should == 2
30
+ expect(row.value).to eq(value1)
31
+ expect(row.count).to eq(2)
32
32
  end
33
33
 
34
34
  it "should return value #{value2.inspect} with count 1" do
35
35
  row = @search.facet(field).rows[1]
36
- row.value.should == value2
37
- row.count.should == 1
36
+ expect(row.value).to eq(value2)
37
+ expect(row.count).to eq(1)
38
38
  end
39
39
  end
40
40
  end
@@ -66,7 +66,7 @@ describe 'search faceting' do
66
66
  search = Sunspot.search(Post) do
67
67
  facet :title, :limit => 3
68
68
  end
69
- search.facet(:title).should have(3).rows
69
+ expect(search.facet(:title).rows.size).to eq(3)
70
70
  end
71
71
 
72
72
  it 'should not return zeros by default' do
@@ -74,7 +74,7 @@ describe 'search faceting' do
74
74
  with :blog_id, 1
75
75
  facet :title
76
76
  end
77
- search.facet(:title).rows.map { |row| row.value }.should_not include('zero')
77
+ expect(search.facet(:title).rows.map { |row| row.value }).not_to include('zero')
78
78
  end
79
79
 
80
80
  it 'should return zeros when specified' do
@@ -82,14 +82,14 @@ describe 'search faceting' do
82
82
  with :blog_id, 1
83
83
  facet :title, :zeros => true
84
84
  end
85
- search.facet(:title).rows.map { |row| row.value }.should include('zero')
85
+ expect(search.facet(:title).rows.map { |row| row.value }).to include('zero')
86
86
  end
87
87
 
88
88
  it 'should return facet rows from an offset' do
89
89
  search = Sunspot.search(Post) do
90
90
  facet :title, :offset => 3
91
91
  end
92
- search.facet(:title).rows.map { |row| row.value }.should == %w(one zero)
92
+ expect(search.facet(:title).rows.map { |row| row.value }).to eq(%w(one zero))
93
93
  end
94
94
 
95
95
  it 'should return a specified minimum count' do
@@ -97,7 +97,7 @@ describe 'search faceting' do
97
97
  with :blog_id, 1
98
98
  facet :title, :minimum_count => 2
99
99
  end
100
- search.facet(:title).rows.map { |row| row.value }.should == %w(four three two)
100
+ expect(search.facet(:title).rows.map { |row| row.value }).to eq(%w(four three two))
101
101
  end
102
102
 
103
103
  it 'should order facets lexically' do
@@ -105,7 +105,7 @@ describe 'search faceting' do
105
105
  with :blog_id, 1
106
106
  facet :title, :sort => :index
107
107
  end
108
- search.facet(:title).rows.map { |row| row.value }.should == %w(four one three two)
108
+ expect(search.facet(:title).rows.map { |row| row.value }).to eq(%w(four one three two))
109
109
  end
110
110
 
111
111
  it 'should order facets by count' do
@@ -113,7 +113,7 @@ describe 'search faceting' do
113
113
  with :blog_id, 1
114
114
  facet :title, :sort => :count
115
115
  end
116
- search.facet(:title).rows.map { |row| row.value }.should == %w(four three two one)
116
+ expect(search.facet(:title).rows.map { |row| row.value }).to eq(%w(four three two one))
117
117
  end
118
118
 
119
119
  it 'should limit facet values by prefix' do
@@ -121,7 +121,7 @@ describe 'search faceting' do
121
121
  with :blog_id, 1
122
122
  facet :title, :prefix => 't'
123
123
  end
124
- search.facet(:title).rows.map { |row| row.value }.sort.should == %w(three two)
124
+ expect(search.facet(:title).rows.map { |row| row.value }.sort).to eq(%w(three two))
125
125
  end
126
126
 
127
127
  it 'should return :all facet' do
@@ -129,8 +129,8 @@ describe 'search faceting' do
129
129
  with :blog_id, 1
130
130
  facet :title, :extra => :any
131
131
  end
132
- search.facet(:title).rows.first.value.should == :any
133
- search.facet(:title).rows.first.count.should == 10
132
+ expect(search.facet(:title).rows.first.value).to eq(:any)
133
+ expect(search.facet(:title).rows.first.count).to eq(10)
134
134
  end
135
135
 
136
136
  it 'should return :none facet' do
@@ -138,8 +138,8 @@ describe 'search faceting' do
138
138
  with :blog_id, 1
139
139
  facet :title, :extra => :none
140
140
  end
141
- search.facet(:title).rows.first.value.should == :none
142
- search.facet(:title).rows.first.count.should == 1
141
+ expect(search.facet(:title).rows.first.value).to eq(:none)
142
+ expect(search.facet(:title).rows.first.count).to eq(1)
143
143
  end
144
144
 
145
145
  it 'gives correct facet count when group == true and truncate == true' do
@@ -152,10 +152,140 @@ describe 'search faceting' do
152
152
  end
153
153
 
154
154
  # Should be 5 instead of 11
155
- search.facet(:title).rows.first.count.should == 5
155
+ expect(search.facet(:title).rows.first.count).to eq(5)
156
156
  end
157
157
  end
158
158
 
159
+ context 'json facet options' do
160
+ before :all do
161
+ Sunspot.remove_all
162
+ facet_values = %w(zero one two three four)
163
+ facet_values.each_with_index do |value, i|
164
+ i.times { Sunspot.index(Post.new(:title => value, :blog_id => 1)) }
165
+ end
166
+ Sunspot.index(Post.new(:blog_id => 1))
167
+ Sunspot.index(Post.new(:title => 'zero', :blog_id => 2))
168
+ Sunspot.commit
169
+ end
170
+
171
+ it 'should return indexed elements' do
172
+ search = Sunspot.search(Post) do
173
+ json_facet(:title)
174
+ end
175
+ expect(search.facet(:title).rows.size).to eq(5)
176
+ end
177
+
178
+ it 'should limit the number of facet rows' do
179
+ search = Sunspot.search(Post) do
180
+ json_facet :title, :limit => 3
181
+ end
182
+ expect(search.facet(:title).rows.size).to eq(3)
183
+ end
184
+
185
+ it 'should not return zeros by default' do
186
+ search = Sunspot.search(Post) do
187
+ with :blog_id, 1
188
+ json_facet :title
189
+ end
190
+ expect(search.facet(:title).rows.map { |row| row.value }).not_to include('zero')
191
+ end
192
+
193
+ it 'should return a specified minimum count' do
194
+ search = Sunspot.search(Post) do
195
+ with :blog_id, 1
196
+ json_facet :title, :minimum_count => 2
197
+ end
198
+ expect(search.facet(:title).rows.map { |row| row.value }).to eq(%w(four three two))
199
+ end
200
+
201
+ it 'should order facets lexically' do
202
+ search = Sunspot.search(Post) do
203
+ with :blog_id, 1
204
+ json_facet :title, :sort => :index
205
+ end
206
+ expect(search.facet(:title).rows.map { |row| row.value }).to eq(%w(four one three two))
207
+ end
208
+
209
+ it 'should order facets by count' do
210
+ search = Sunspot.search(Post) do
211
+ with :blog_id, 1
212
+ json_facet :title, :sort => :count
213
+ end
214
+ expect(search.facet(:title).rows.map { |row| row.value }).to eq(%w(four three two one))
215
+ end
216
+
217
+ it 'should limit facet values by prefix' do
218
+ search = Sunspot.search(Post) do
219
+ with :blog_id, 1
220
+ json_facet :title, :prefix => 't'
221
+ end
222
+ expect(search.facet(:title).rows.map { |row| row.value }.sort).to eq(%w(three two))
223
+ end
224
+
225
+ end
226
+
227
+ context 'nested json facet' do
228
+ before :all do
229
+ Sunspot.remove_all
230
+ facet_values = %w(zero one two three four)
231
+ nested_facet_values = %w(alfa bravo charlie delta)
232
+
233
+ facet_values.each do |value|
234
+ nested_facet_values.each do |v2|
235
+ Sunspot.index(Post.new(:title => value, :author_name => v2, :blog_id => 1))
236
+ end
237
+ end
238
+
239
+ 0.upto(9) { |i| Sunspot.index(Post.new(:title => 'zero', :author_name => "another#{i}", :blog_id => 1)) }
240
+
241
+ Sunspot.commit
242
+ end
243
+
244
+ it 'should get nested' do
245
+ search = Sunspot.search(Post) do
246
+ json_facet(:title, nested: { field: :author_name } )
247
+ end
248
+ expect(search.facet(:title).rows.first.nested.size).to eq(4)
249
+ end
250
+
251
+ it 'without limit take the first 10' do
252
+ search = Sunspot.search(Post) do
253
+ json_facet(:title, nested: { field: :author_name } )
254
+ end
255
+ expect(search.facet(:title).rows.last.nested.size).to eq(10)
256
+ end
257
+
258
+ it 'without limit' do
259
+ search = Sunspot.search(Post) do
260
+ json_facet(:title, nested: { field: :author_name, limit: -1 } )
261
+ end
262
+ expect(search.facet(:title).rows.last.nested.size).to eq(14)
263
+ end
264
+
265
+ it 'works with distinct' do
266
+ search = Sunspot.search(Post) do
267
+ json_facet(:title, nested: { field: :author_name, distinct: { strategy: :unique } } )
268
+ end
269
+ expect(search.facet(:title).rows.first.nested.map(&:count).uniq.size).to eq(1)
270
+ end
271
+
272
+ it 'should limit the nested facet' do
273
+ search = Sunspot.search(Post) do
274
+ json_facet(:title, nested: { field: :author_name, limit: 2 } )
275
+ end
276
+ expect(search.facet(:title).rows.first.nested.size).to eq(2)
277
+ end
278
+
279
+ it 'should work nested of nested' do
280
+ search = Sunspot.search(Post) do
281
+ json_facet(:title, nested: { field: :author_name, nested: { field: :title } } )
282
+ end
283
+ expect(search.facet(:title).rows.first.nested.first.nested.size).to eq(1)
284
+ expect(search.facet(:title).rows.first.nested.first.nested.first.nested).to eq(nil)
285
+ end
286
+
287
+ end
288
+
159
289
  context 'prefix escaping' do
160
290
  before do
161
291
  Sunspot.remove_all
@@ -170,7 +300,7 @@ describe 'search faceting' do
170
300
  with :blog_id, 1
171
301
  facet :title, :prefix => 'title '
172
302
  end
173
- search.facet(:title).rows.map { |row| row.value }.sort.should == ["title with spaces 1", "title with spaces 2"]
303
+ expect(search.facet(:title).rows.map { |row| row.value }.sort).to eq(["title with spaces 1", "title with spaces 2"])
174
304
  end
175
305
 
176
306
  it 'should limit facet values by a prefix with slashes' do
@@ -178,7 +308,7 @@ describe 'search faceting' do
178
308
  with :blog_id, 1
179
309
  facet :title, :prefix => 'title/'
180
310
  end
181
- search.facet(:title).rows.map { |row| row.value }.sort.should == ["title/with/slashes/1", "title/with/slashes/2"]
311
+ expect(search.facet(:title).rows.map { |row| row.value }.sort).to eq(["title/with/slashes/1", "title/with/slashes/2"])
182
312
  end
183
313
  end
184
314
 
@@ -199,7 +329,7 @@ describe 'search faceting' do
199
329
  category_filter = with(:category_ids, 1)
200
330
  facet(:category_ids, :exclude => category_filter)
201
331
  end
202
- search.facet(:category_ids).rows.map { |row| row.value }.to_set.should == Set[1, 2]
332
+ expect(search.facet(:category_ids).rows.map { |row| row.value }.to_set).to eq(Set[1, 2])
203
333
  end
204
334
 
205
335
  it 'should use facet keys to facet more than once with different exclusions' do
@@ -209,8 +339,8 @@ describe 'search faceting' do
209
339
  facet(:category_ids)
210
340
  facet(:category_ids, :exclude => category_filter, :name => :all_category_ids)
211
341
  end
212
- search.facet(:category_ids).rows.map { |row| row.value }.should == [1]
213
- search.facet(:all_category_ids).rows.map { |row| row.value }.to_set.should == Set[1, 2]
342
+ expect(search.facet(:category_ids).rows.map { |row| row.value }).to eq([1])
343
+ expect(search.facet(:all_category_ids).rows.map { |row| row.value }.to_set).to eq(Set[1, 2])
214
344
  end
215
345
  end
216
346
 
@@ -228,7 +358,7 @@ describe 'search faceting' do
228
358
  end
229
359
  end
230
360
  end
231
- search.facet(:category_ids).rows.map { |row| [row.value, row.count] }.to_set.should == Set[[:category_1, 1], [:category_2, 1]]
361
+ expect(search.facet(:category_ids).rows.map { |row| [row.value, row.count] }.to_set).to eq(Set[[:category_1, 1], [:category_2, 1]])
232
362
  end
233
363
 
234
364
  it 'should use facet keys to facet more than once with different exclusions' do
@@ -253,12 +383,63 @@ describe 'search faceting' do
253
383
  end
254
384
  end
255
385
  end
256
- search.facet(:category_ids).rows.map { |row| [row.value, row.count] }.to_set.should == Set[[:category_1, 1]]
257
- search.facet(:all_category_ids).rows.map { |row| [row.value, row.count] }.to_set.should == Set[[:category_1, 1], [:category_2, 1]]
386
+ expect(search.facet(:category_ids).rows.map { |row| [row.value, row.count] }.to_set).to eq(Set[[:category_1, 1]])
387
+ expect(search.facet(:all_category_ids).rows.map { |row| [row.value, row.count] }.to_set).to eq(Set[[:category_1, 1], [:category_2, 1]])
258
388
  end
259
389
  end
260
390
  end
261
391
 
392
+ context 'distinct field facets' do
393
+ before :all do
394
+ Sunspot.remove_all
395
+
396
+ Sunspot.index!(
397
+ (0..5).map { |i| Post.new(:blog_id => i, :title => 'title') }
398
+ )
399
+
400
+ 0.upto(3) { |i| Sunspot.index(Post.new(:blog_id => i, :title => 'title')) }
401
+
402
+ Sunspot.index!(Post.new(:blog_id => 4, :title => 'other title'))
403
+ Sunspot.index!(Post.new(:blog_id => 5, :title => 'other title'))
404
+
405
+ Sunspot.index!(Post.new(:blog_id => 40, :title => 'title'))
406
+ Sunspot.index!(Post.new(:blog_id => 40, :title => 'title'))
407
+
408
+ Sunspot.index!(Post.new(:blog_id => 40, :title => 'other title'))
409
+ Sunspot.index!(Post.new(:blog_id => 40, :title => 'other title'))
410
+ end
411
+
412
+ it 'should return unique indexed elements for a field' do
413
+ search = Sunspot.search(Post) do
414
+ json_facet(:blog_id, distinct: { strategy: :unique })
415
+ end
416
+
417
+ expect(search.facet(:blog_id).rows.size).to eq(7)
418
+ expect(search.facet(:blog_id).rows.map(&:count).uniq.size).to eq(1)
419
+ end
420
+
421
+ it 'should return unique indexed elements for a field and facet on a field' do
422
+ search = Sunspot.search(Post) do
423
+ json_facet(:blog_id, distinct: { group_by: :title, strategy: :unique })
424
+ end
425
+
426
+ expect(search.facet(:blog_id).rows.size).to eq(2)
427
+ expect(search.facet(:blog_id).rows[0].count).to eq(3)
428
+ expect(search.facet(:blog_id).rows[1].count).to eq(7)
429
+ end
430
+
431
+ it 'should return unique indexed elements for a field and facet on a field with hll' do
432
+ search = Sunspot.search(Post) do
433
+ json_facet(:blog_id, distinct: { group_by: :title, strategy: :hll })
434
+ end
435
+
436
+ expect(search.facet(:blog_id).rows.size).to eq(2)
437
+ expect(search.facet(:blog_id).rows[0].count).to eq(3)
438
+ expect(search.facet(:blog_id).rows[1].count).to eq(7)
439
+ end
440
+
441
+ end
442
+
262
443
  context 'date facets' do
263
444
  before :all do
264
445
  Sunspot.remove_all
@@ -273,11 +454,43 @@ describe 'search faceting' do
273
454
  search = Sunspot.search(Post) do
274
455
  facet :published_at, :time_range => time..(time + 60*60*24*2), :sort => :count
275
456
  end
276
- search.facet(:published_at).rows.first.value.should == (time..(time + 60*60*24))
277
- search.facet(:published_at).rows.first.count.should == 2
278
- search.facet(:published_at).rows.last.value.should == ((time + 60*60*24)..(time + 60*60*24*2))
279
- search.facet(:published_at).rows.last.count.should == 1
457
+ expect(search.facet(:published_at).rows.first.value).to eq(time..(time + 60*60*24))
458
+ expect(search.facet(:published_at).rows.first.count).to eq(2)
459
+ expect(search.facet(:published_at).rows.last.value).to eq((time + 60*60*24)..(time + 60*60*24*2))
460
+ expect(search.facet(:published_at).rows.last.count).to eq(1)
280
461
  end
462
+
463
+ it 'json facet should return time ranges' do
464
+ days_diff = 15
465
+ time_from = Time.utc(2009, 7, 8)
466
+ time_to = Time.utc(2009, 7, 8 + days_diff)
467
+ search = Sunspot.search(Post) do
468
+ json_facet(
469
+ :published_at,
470
+ :time_range => time_from..time_to
471
+ )
472
+ end
473
+
474
+ expect(search.facet(:published_at).rows.size).to eq(days_diff)
475
+ expect(search.facet(:published_at).rows[0].count).to eq(2)
476
+ expect(search.facet(:published_at).rows[1].count).to eq(1)
477
+ end
478
+
479
+ it 'json facet should return time ranges with custom gap' do
480
+ days_diff = 10
481
+ time_from = Time.utc(2009, 7, 8)
482
+ time_to = Time.utc(2009, 7, 8 + days_diff)
483
+ search = Sunspot.search(Post) do
484
+ json_facet(
485
+ :published_at,
486
+ :time_range => time_from..time_to,
487
+ gap: 60*60*24*2
488
+ )
489
+ end
490
+ expect(search.facet(:published_at).rows.size).to eq(days_diff / 2)
491
+ expect(search.facet(:published_at).rows[0].count).to eq(3)
492
+ end
493
+
281
494
  end
282
495
 
283
496
  context 'class facets' do
@@ -290,10 +503,10 @@ describe 'search faceting' do
290
503
  search = Sunspot.search(Post, Namespaced::Comment) do
291
504
  facet(:class, :sort => :count)
292
505
  end
293
- search.facet(:class).rows.first.value.should == Post
294
- search.facet(:class).rows.first.count.should == 2
295
- search.facet(:class).rows.last.value.should == Namespaced::Comment
296
- search.facet(:class).rows.last.count.should == 1
506
+ expect(search.facet(:class).rows.first.value).to eq(Post)
507
+ expect(search.facet(:class).rows.first.count).to eq(2)
508
+ expect(search.facet(:class).rows.last.value).to eq(Namespaced::Comment)
509
+ expect(search.facet(:class).rows.last.count).to eq(1)
297
510
  end
298
511
  end
299
512
 
@@ -319,12 +532,12 @@ describe 'search faceting' do
319
532
  end
320
533
  end
321
534
  facet = search.facet(:rating_range)
322
- facet.rows[0].value.should == (3.0..4.0)
323
- facet.rows[0].count.should == 3
324
- facet.rows[1].value.should == (1.0..2.0)
325
- facet.rows[1].count.should == 2
326
- facet.rows[2].value.should == (4.0..5.0)
327
- facet.rows[2].count.should == 1
535
+ expect(facet.rows[0].value).to eq(3.0..4.0)
536
+ expect(facet.rows[0].count).to eq(3)
537
+ expect(facet.rows[1].value).to eq(1.0..2.0)
538
+ expect(facet.rows[1].count).to eq(2)
539
+ expect(facet.rows[2].value).to eq(4.0..5.0)
540
+ expect(facet.rows[2].count).to eq(1)
328
541
  end
329
542
  end
330
543
  end