sunspot 0.9.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (101) hide show
  1. data/History.txt +83 -0
  2. data/LICENSE +18 -0
  3. data/README.rdoc +154 -0
  4. data/Rakefile +9 -0
  5. data/TODO +9 -0
  6. data/VERSION.yml +4 -0
  7. data/bin/sunspot-configure-solr +46 -0
  8. data/bin/sunspot-solr +62 -0
  9. data/lib/light_config.rb +40 -0
  10. data/lib/sunspot.rb +469 -0
  11. data/lib/sunspot/adapters.rb +265 -0
  12. data/lib/sunspot/composite_setup.rb +186 -0
  13. data/lib/sunspot/configuration.rb +38 -0
  14. data/lib/sunspot/data_extractor.rb +47 -0
  15. data/lib/sunspot/dsl.rb +3 -0
  16. data/lib/sunspot/dsl/field_query.rb +72 -0
  17. data/lib/sunspot/dsl/fields.rb +86 -0
  18. data/lib/sunspot/dsl/query.rb +59 -0
  19. data/lib/sunspot/dsl/query_facet.rb +31 -0
  20. data/lib/sunspot/dsl/restriction.rb +25 -0
  21. data/lib/sunspot/dsl/scope.rb +193 -0
  22. data/lib/sunspot/dsl/search.rb +30 -0
  23. data/lib/sunspot/facet.rb +16 -0
  24. data/lib/sunspot/facet_data.rb +120 -0
  25. data/lib/sunspot/facet_row.rb +10 -0
  26. data/lib/sunspot/field.rb +157 -0
  27. data/lib/sunspot/field_factory.rb +126 -0
  28. data/lib/sunspot/indexer.rb +123 -0
  29. data/lib/sunspot/instantiated_facet.rb +42 -0
  30. data/lib/sunspot/instantiated_facet_row.rb +22 -0
  31. data/lib/sunspot/query.rb +191 -0
  32. data/lib/sunspot/query/base_query.rb +90 -0
  33. data/lib/sunspot/query/connective.rb +126 -0
  34. data/lib/sunspot/query/dynamic_query.rb +69 -0
  35. data/lib/sunspot/query/field_facet.rb +151 -0
  36. data/lib/sunspot/query/field_query.rb +63 -0
  37. data/lib/sunspot/query/pagination.rb +39 -0
  38. data/lib/sunspot/query/query_facet.rb +73 -0
  39. data/lib/sunspot/query/query_facet_row.rb +19 -0
  40. data/lib/sunspot/query/query_field_facet.rb +13 -0
  41. data/lib/sunspot/query/restriction.rb +233 -0
  42. data/lib/sunspot/query/scope.rb +165 -0
  43. data/lib/sunspot/query/sort.rb +36 -0
  44. data/lib/sunspot/query/sort_composite.rb +33 -0
  45. data/lib/sunspot/schema.rb +165 -0
  46. data/lib/sunspot/search.rb +219 -0
  47. data/lib/sunspot/search/hit.rb +66 -0
  48. data/lib/sunspot/session.rb +201 -0
  49. data/lib/sunspot/setup.rb +271 -0
  50. data/lib/sunspot/type.rb +200 -0
  51. data/lib/sunspot/util.rb +164 -0
  52. data/solr/etc/jetty.xml +212 -0
  53. data/solr/etc/webdefault.xml +379 -0
  54. data/solr/lib/jetty-6.1.3.jar +0 -0
  55. data/solr/lib/jetty-util-6.1.3.jar +0 -0
  56. data/solr/lib/jsp-2.1/ant-1.6.5.jar +0 -0
  57. data/solr/lib/jsp-2.1/core-3.1.1.jar +0 -0
  58. data/solr/lib/jsp-2.1/jsp-2.1.jar +0 -0
  59. data/solr/lib/jsp-2.1/jsp-api-2.1.jar +0 -0
  60. data/solr/lib/servlet-api-2.5-6.1.3.jar +0 -0
  61. data/solr/solr/conf/elevate.xml +36 -0
  62. data/solr/solr/conf/protwords.txt +21 -0
  63. data/solr/solr/conf/schema.xml +50 -0
  64. data/solr/solr/conf/solrconfig.xml +696 -0
  65. data/solr/solr/conf/stopwords.txt +57 -0
  66. data/solr/solr/conf/synonyms.txt +31 -0
  67. data/solr/start.jar +0 -0
  68. data/solr/webapps/solr.war +0 -0
  69. data/spec/api/adapters_spec.rb +33 -0
  70. data/spec/api/build_search_spec.rb +1039 -0
  71. data/spec/api/indexer_spec.rb +311 -0
  72. data/spec/api/query_spec.rb +153 -0
  73. data/spec/api/search_retrieval_spec.rb +362 -0
  74. data/spec/api/session_spec.rb +157 -0
  75. data/spec/api/spec_helper.rb +1 -0
  76. data/spec/api/sunspot_spec.rb +18 -0
  77. data/spec/integration/dynamic_fields_spec.rb +55 -0
  78. data/spec/integration/faceting_spec.rb +169 -0
  79. data/spec/integration/keyword_search_spec.rb +83 -0
  80. data/spec/integration/scoped_search_spec.rb +289 -0
  81. data/spec/integration/spec_helper.rb +1 -0
  82. data/spec/integration/stored_fields_spec.rb +10 -0
  83. data/spec/integration/test_pagination.rb +32 -0
  84. data/spec/mocks/adapters.rb +32 -0
  85. data/spec/mocks/blog.rb +3 -0
  86. data/spec/mocks/comment.rb +19 -0
  87. data/spec/mocks/connection.rb +84 -0
  88. data/spec/mocks/mock_adapter.rb +30 -0
  89. data/spec/mocks/mock_record.rb +48 -0
  90. data/spec/mocks/photo.rb +8 -0
  91. data/spec/mocks/post.rb +73 -0
  92. data/spec/mocks/user.rb +8 -0
  93. data/spec/spec_helper.rb +47 -0
  94. data/tasks/gemspec.rake +25 -0
  95. data/tasks/rcov.rake +28 -0
  96. data/tasks/rdoc.rake +22 -0
  97. data/tasks/schema.rake +19 -0
  98. data/tasks/spec.rake +24 -0
  99. data/tasks/todo.rake +4 -0
  100. data/templates/schema.xml.haml +24 -0
  101. metadata +246 -0
@@ -0,0 +1 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'spec_helper')
@@ -0,0 +1,18 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe Sunspot do
4
+ describe "reset!" do
5
+ it "should reset current session" do
6
+ old_session = Sunspot.send(:session)
7
+ Sunspot.reset!(true)
8
+ Sunspot.send(:session).should_not == old_session
9
+ end
10
+
11
+ it "should keep keep configuration if specified" do
12
+ Sunspot.config.solr.url = "http://localhost:9999/path/solr"
13
+ config_before_reset = Sunspot.config
14
+ Sunspot.reset!(true)
15
+ Sunspot.config.should == config_before_reset
16
+ end
17
+ end
18
+ 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,169 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe 'search faceting' do
4
+ def self.test_field_type(name, attribute, field, value1, value2)
5
+ context "with field of type #{name}" do
6
+ before :all do
7
+ Sunspot.remove_all
8
+ 2.times do
9
+ Sunspot.index(Post.new(attribute => value1))
10
+ end
11
+ Sunspot.index(Post.new(attribute => value2))
12
+ Sunspot.commit
13
+ end
14
+
15
+ before :each do
16
+ @search = Sunspot.search(Post) do
17
+ facet(field)
18
+ end
19
+ end
20
+
21
+ it "should return value #{value1.inspect} with count 2" do
22
+ row = @search.facet(field).rows[0]
23
+ row.value.should == value1
24
+ row.count.should == 2
25
+ end
26
+
27
+ it "should return value #{value2.inspect} with count 1" do
28
+ row = @search.facet(field).rows[1]
29
+ row.value.should == value2
30
+ row.count.should == 1
31
+ end
32
+ end
33
+ end
34
+
35
+ test_field_type('String', :title, :title, 'Title 1', 'Title 2')
36
+ test_field_type('Integer', :blog_id, :blog_id, 3, 4)
37
+ test_field_type('Float', :ratings_average, :average_rating, 2.2, 1.1)
38
+ test_field_type('Time', :published_at, :published_at, Time.mktime(2008, 02, 17, 17, 45, 04),
39
+ Time.mktime(2008, 07, 02, 03, 56, 22))
40
+ test_field_type('Boolean', :featured, :featured, true, false)
41
+
42
+ context 'facet options' do
43
+ before :all do
44
+ Sunspot.remove_all
45
+ facet_values = %w(zero one two three four)
46
+ facet_values.each_with_index do |value, i|
47
+ i.times { Sunspot.index(Post.new(:title => value, :blog_id => 1)) }
48
+ end
49
+ Sunspot.index(Post.new(:title => 'zero', :blog_id => 2))
50
+ Sunspot.commit
51
+ end
52
+
53
+ it 'should limit the number of facet rows' do
54
+ search = Sunspot.search(Post) do
55
+ facet :title, :limit => 3
56
+ end
57
+ search.facet(:title).should have(3).rows
58
+ end
59
+
60
+ it 'should not return zeros by default' do
61
+ search = Sunspot.search(Post) do
62
+ with :blog_id, 1
63
+ facet :title
64
+ end
65
+ search.facet(:title).rows.map { |row| row.value }.should_not include('zero')
66
+ end
67
+
68
+ it 'should return zeros when specified' do
69
+ search = Sunspot.search(Post) do
70
+ with :blog_id, 1
71
+ facet :title, :zeros => true
72
+ end
73
+ search.facet(:title).rows.map { |row| row.value }.should include('zero')
74
+ end
75
+
76
+ it 'should return a specified minimum count' do
77
+ search = Sunspot.search(Post) do
78
+ with :blog_id, 1
79
+ facet :title, :minimum_count => 2
80
+ end
81
+ search.facet(:title).rows.map { |row| row.value }.should == %w(four three two)
82
+ end
83
+
84
+ it 'should order facets lexically' do
85
+ search = Sunspot.search(Post) do
86
+ with :blog_id, 1
87
+ facet :title, :sort => :index
88
+ end
89
+ search.facet(:title).rows.map { |row| row.value }.should == %w(four one three two)
90
+ end
91
+
92
+ it 'should order facets by count' do
93
+ search = Sunspot.search(Post) do
94
+ with :blog_id, 1
95
+ facet :title, :sort => :count
96
+ end
97
+ search.facet(:title).rows.map { |row| row.value }.should == %w(four three two one)
98
+ end
99
+ end
100
+
101
+ context 'date facets' do
102
+ before :all do
103
+ Sunspot.remove_all
104
+ time = Time.utc(2009, 7, 8)
105
+ Sunspot.index!(
106
+ (0..2).map { |i| Post.new(:published_at => time + i*60*60*16) }
107
+ )
108
+ end
109
+
110
+ it 'should return time ranges' do
111
+ time = Time.utc(2009, 7, 8)
112
+ search = Sunspot.search(Post) do
113
+ facet :published_at, :time_range => time..(time + 60*60*24*2), :sort => :count
114
+ end
115
+ search.facet(:published_at).rows.first.value.should == (time..(time + 60*60*24))
116
+ search.facet(:published_at).rows.first.count.should == 2
117
+ search.facet(:published_at).rows.last.value.should == ((time + 60*60*24)..(time + 60*60*24*2))
118
+ search.facet(:published_at).rows.last.count.should == 1
119
+ end
120
+ end
121
+
122
+ context 'class facets' do
123
+ before :all do
124
+ Sunspot.remove_all
125
+ Sunspot.index!(Post.new, Post.new, Namespaced::Comment.new)
126
+ end
127
+
128
+ it 'should return classes' do
129
+ search = Sunspot.search(Post, Namespaced::Comment) do
130
+ facet(:class, :sort => :count)
131
+ end
132
+ search.facet(:class).rows.first.value.should == Post
133
+ search.facet(:class).rows.first.count.should == 2
134
+ search.facet(:class).rows.last.value.should == Namespaced::Comment
135
+ search.facet(:class).rows.last.count.should == 1
136
+ end
137
+ end
138
+
139
+ context 'query facets' do
140
+ before :all do
141
+ Sunspot.remove_all
142
+ Sunspot.index!(
143
+ [1.1, 1.2, 3.2, 3.4, 3.9, 4.1].map do |rating|
144
+ Post.new(:ratings_average => rating)
145
+ end
146
+ )
147
+ end
148
+
149
+ it 'should return specified facets' do
150
+ search = Sunspot.search(Post) do
151
+ facet :rating_range do
152
+ for rating in [1.0, 2.0, 3.0, 4.0]
153
+ range = rating..(rating + 1.0)
154
+ row range do
155
+ with :average_rating, rating..(rating + 1.0)
156
+ end
157
+ end
158
+ end
159
+ end
160
+ facet = search.facet(:rating_range)
161
+ facet.rows[0].value.should == (3.0..4.0)
162
+ facet.rows[0].count.should == 3
163
+ facet.rows[1].value.should == (1.0..2.0)
164
+ facet.rows[1].count.should == 2
165
+ facet.rows[2].value.should == (4.0..5.0)
166
+ facet.rows[2].count.should == 1
167
+ end
168
+ end
169
+ end
@@ -0,0 +1,83 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
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 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
+ end
51
+
52
+ describe 'with field boost' do
53
+ before :all do
54
+ Sunspot.remove_all
55
+ @posts = [:title, :body].map { |field| Post.new(field => 'rhinoceros') }
56
+ Sunspot.index!(*@posts)
57
+ end
58
+
59
+ it 'should assign a higher score to the result matching the higher-boosted field' do
60
+ search = Sunspot.search(Post) { keywords 'rhinoceros' }
61
+ search.hits.map { |hit| hit.primary_key }.should ==
62
+ @posts.map { |post| post.id.to_s }
63
+ search.hits.first.score.should > search.hits.last.score
64
+ end
65
+ end
66
+
67
+ describe 'with document boost' do
68
+ before :all do
69
+ Sunspot.remove_all
70
+ @posts = [4.0, 2.0].map do |rating|
71
+ Post.new(:title => 'Test', :ratings_average => rating)
72
+ end
73
+ Sunspot.index!(*@posts)
74
+ end
75
+
76
+ it 'should assign a higher score to the higher-boosted document' do
77
+ search = Sunspot.search(Post) { keywords 'test' }
78
+ search.hits.map { |hit| hit.primary_key }.should ==
79
+ @posts.map { |post| post.id.to_s }
80
+ search.hits.first.score.should > search.hits.last.score
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,289 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe 'scoped_search' do
4
+ def self.test_field_type(name, attribute, field, *values)
5
+ raise(ArgumentError, 'Please supply five values') unless values.length == 5
6
+
7
+ context "with field of type #{name}" do
8
+ before :all do
9
+ Sunspot.remove_all
10
+ @posts = values.map do |value|
11
+ post = Post.new(attribute => value)
12
+ Sunspot.index(post)
13
+ post
14
+ end
15
+ Sunspot.commit
16
+ end
17
+
18
+ it 'should filter by exact match' do
19
+ Sunspot.search(Post) { with(field, values[2]) }.results.should == [@posts[2]]
20
+ end
21
+
22
+ it 'should reject by inexact match' do
23
+ results = Sunspot.search(Post) { without(field, values[2]) }.results
24
+ [0, 1, 3, 4].each { |i| results.should include(@posts[i]) }
25
+ results.should_not include(@posts[2])
26
+ end
27
+
28
+ it 'should filter by less than' do
29
+ results = Sunspot.search(Post) { with(field).less_than values[2] }.results
30
+ (0..2).each { |i| results.should include(@posts[i]) }
31
+ (3..4).each { |i| results.should_not include(@posts[i]) }
32
+ end
33
+
34
+ it 'should reject by less than' do
35
+ results = Sunspot.search(Post) { without(field).less_than values[2] }.results
36
+ (0..2).each { |i| results.should_not include(@posts[i]) }
37
+ (3..4).each { |i| results.should include(@posts[i]) }
38
+ end
39
+
40
+ it 'should filter by greater than' do
41
+ results = Sunspot.search(Post) { with(field).greater_than values[2] }.results
42
+ (2..4).each { |i| results.should include(@posts[i]) }
43
+ (0..1).each { |i| results.should_not include(@posts[i]) }
44
+ end
45
+
46
+ it 'should reject by greater than' do
47
+ results = Sunspot.search(Post) { without(field).greater_than values[2] }.results
48
+ (2..4).each { |i| results.should_not include(@posts[i]) }
49
+ (0..1).each { |i| results.should include(@posts[i]) }
50
+ end
51
+
52
+ it 'should filter by between' do
53
+ results = Sunspot.search(Post) { with(field).between(values[1]..values[3]) }.results
54
+ (1..3).each { |i| results.should include(@posts[i]) }
55
+ [0, 4].each { |i| results.should_not include(@posts[i]) }
56
+ end
57
+
58
+ it 'should reject by between' do
59
+ results = Sunspot.search(Post) { without(field).between(values[1]..values[3]) }.results
60
+ (1..3).each { |i| results.should_not include(@posts[i]) }
61
+ [0, 4].each { |i| results.should include(@posts[i]) }
62
+ end
63
+
64
+ it 'should filter by any of' do
65
+ results = Sunspot.search(Post) { with(field).any_of(values.values_at(1, 3)) }.results
66
+ [1, 3].each { |i| results.should include(@posts[i]) }
67
+ [0, 2, 4].each { |i| results.should_not include(@posts[i]) }
68
+ end
69
+
70
+ it 'should reject by any of' do
71
+ results = Sunspot.search(Post) { without(field).any_of(values.values_at(1, 3)) }.results
72
+ [1, 3].each { |i| results.should_not include(@posts[i]) }
73
+ [0, 2, 4].each { |i| results.should include(@posts[i]) }
74
+ end
75
+
76
+ it 'should order by field ascending' do
77
+ results = Sunspot.search(Post) { order_by field, :asc }.results
78
+ results.should == @posts
79
+ end
80
+
81
+ it 'should order by field descending' do
82
+ results = Sunspot.search(Post) { order_by field, :desc }.results
83
+ results.should == @posts.reverse
84
+ end
85
+ end
86
+ end
87
+
88
+ test_field_type 'String', :title, :title, 'apple', 'banana', 'cherry', 'date', 'eggplant'
89
+ test_field_type 'Integer', :blog_id, :blog_id, -2, 0, 3, 12, 20
90
+ test_field_type 'Float', :ratings_average, :average_rating, -2.5, 0.0, 3.2, 3.5, 16.0
91
+ test_field_type 'Time', :published_at, :published_at, *(['1970-01-01 00:00:00 UTC', '1983-07-08 04:00:00 UTC', '1983-07-08 02:00:00 -0500',
92
+ '2005-11-05 10:00:00 UTC', Time.now.to_s].map { |t| Time.parse(t) })
93
+
94
+ describe 'Boolean field type' do
95
+ before :all do
96
+ Sunspot.remove_all
97
+ @posts = [Post.new(:featured => true), Post.new(:featured => false), Post.new]
98
+ Sunspot.index!(@posts)
99
+ end
100
+
101
+ it 'should filter by exact match for true' do
102
+ Sunspot.search(Post) { with(:featured, true) }.results.should == [@posts[0]]
103
+ end
104
+
105
+ it 'should filter for exact match for false' do
106
+ Sunspot.search(Post) { with(:featured, false) }.results.should == [@posts[1]]
107
+ end
108
+ end
109
+
110
+ describe 'passing nil value to equal' do
111
+ before :all do
112
+ Sunspot.remove_all
113
+ @posts = [Post.new(:title => 'apple'), Post.new]
114
+ Sunspot.index!(@posts)
115
+ end
116
+
117
+ it 'should filter results without value for field' do
118
+ Sunspot.search(Post) { with(:title, nil) }.results.should == [@posts[1]]
119
+ end
120
+
121
+ it 'should exclude results without value for field' do
122
+ Sunspot.search(Post) { without(:title, nil) }.results.should == [@posts[0]]
123
+ end
124
+ end
125
+
126
+ describe 'exclusion by identity' do
127
+ before do
128
+ @posts = (1..5).map do |i|
129
+ post = Post.new
130
+ Sunspot.index(post)
131
+ post
132
+ end
133
+ Sunspot.commit
134
+ end
135
+
136
+ it 'should not return excluded object' do
137
+ excluded_post = @posts.shift
138
+ Sunspot.search(Post) { without(excluded_post) }.results.should_not include(excluded_post)
139
+ end
140
+
141
+ it 'should return objects not excluded' do
142
+ excluded_post = @posts.shift
143
+ for included_post in @posts
144
+ Sunspot.search(Post) { without(excluded_post) }.results.should include(included_post)
145
+ end
146
+ end
147
+
148
+ it 'should not return excluded objects' do
149
+ excluded_posts = [@posts.shift, @posts.shift]
150
+ for excluded_post in excluded_posts
151
+ Sunspot.search(Post) { without(excluded_posts) }.results.should_not include(excluded_post)
152
+ end
153
+ end
154
+ end
155
+
156
+ describe 'connectives' do
157
+ before :each do
158
+ Sunspot.remove_all
159
+ end
160
+
161
+ it 'should return results that match any restriction in a disjunction' do
162
+ posts = (1..3).map { |i| Post.new(:blog_id => i)}
163
+ Sunspot.index!(posts)
164
+ Sunspot.search(Post) do
165
+ any_of do
166
+ with(:blog_id, 1)
167
+ with(:blog_id, 2)
168
+ end
169
+ end.results.should == posts[0..1]
170
+ end
171
+
172
+ it 'should return results that match a nested conjunction in a disjunction' do
173
+ posts = [
174
+ Post.new(:title => 'No', :blog_id => 1),
175
+ Post.new(:title => 'Yes', :blog_id => 2),
176
+ Post.new(:title => 'Yes', :blog_id => 3),
177
+ Post.new(:title => 'No', :blog_id => 2)
178
+ ]
179
+ Sunspot.index!(posts)
180
+ Sunspot.search(Post) do
181
+ any_of do
182
+ with(:blog_id, 1)
183
+ all_of do
184
+ with(:blog_id, 2)
185
+ with(:title, 'Yes')
186
+ end
187
+ end
188
+ end.results.should == posts[0..1]
189
+ end
190
+
191
+ it 'should return results that match a conjunction with a negated restriction' do
192
+ posts = [
193
+ Post.new(:title => 'No', :blog_id => 1),
194
+ Post.new(:title => 'Yes', :blog_id => 2),
195
+ Post.new(:title => 'No', :blog_id => 2)
196
+ ]
197
+ Sunspot.index!(posts)
198
+ search = Sunspot.search(Post) do
199
+ any_of do
200
+ with(:blog_id, 1)
201
+ without(:title, 'No')
202
+ end
203
+ end
204
+ search.results.should == posts[0..1]
205
+ end
206
+
207
+ it 'should return results that match a conjunction with a disjunction with a conjunction with a negated restriction' do
208
+ posts = [
209
+ Post.new(:title => 'Yes', :ratings_average => 2.0),
210
+ Post.new(:blog_id => 1, :category_ids => [4], :ratings_average => 2.0),
211
+ Post.new(:blog_id => 1),
212
+ Post.new(:blog_id => 2),
213
+ Post.new(:blog_id => 1, :ratings_average => 2.0)
214
+ ]
215
+ Sunspot.index!(posts)
216
+ search = Sunspot.search(Post) do
217
+ any_of do
218
+ with(:title, 'Yes')
219
+ all_of do
220
+ with(:blog_id, 1)
221
+ any_of do
222
+ with(:category_ids, 4)
223
+ without(:average_rating, 2.0)
224
+ end
225
+ end
226
+ end
227
+ end
228
+ search.results.should == posts[0..2]
229
+ end
230
+
231
+ it 'should return results that match a disjunction with a negated restriction and a nested disjunction in a conjunction with a negated restriction' do
232
+ posts = [
233
+ Post.new,
234
+ Post.new(:title => 'Yes', :blog_id => 1, :category_ids => [4], :ratings_average => 2.0),
235
+ Post.new(:title => 'Yes', :blog_id => 1),
236
+ Post.new(:title => 'Yes'),
237
+ Post.new(:title => 'Yes', :category_ids => [4], :ratings_average => 2.0),
238
+ Post.new(:title => 'Yes', :blog_id => 1, :ratings_average => 2.0)
239
+ ]
240
+ Sunspot.index!(posts)
241
+ search = Sunspot.search(Post) do
242
+ any_of do
243
+ without(:title, 'Yes')
244
+ all_of do
245
+ with(:blog_id, 1)
246
+ any_of do
247
+ with(:category_ids, 4)
248
+ without(:average_rating, 2.0)
249
+ end
250
+ end
251
+ end
252
+ end
253
+ search.results.should == posts[0..2]
254
+ end
255
+ end
256
+
257
+ describe 'multiple column ordering' do
258
+ before do
259
+ Sunspot.remove_all
260
+ @posts = [
261
+ Post.new(:ratings_average => 2, :title => 'banana'),
262
+ Post.new(:ratings_average => 2, :title => 'eggplant'),
263
+ Post.new(:ratings_average => 1, :title => 'apple')
264
+ ].each { |post| Sunspot.index(post) }
265
+ Sunspot.commit
266
+ end
267
+
268
+ it 'should order with precedence given' do
269
+ search = Sunspot.search(Post) do
270
+ order_by :average_rating, :desc
271
+ order_by :sort_title, :asc
272
+ end
273
+ search.results.should == @posts
274
+ end
275
+ end
276
+
277
+ describe 'ordering by random' do
278
+ it 'should order randomly (run this test again if it fails)' do
279
+ Sunspot.remove_all
280
+ Sunspot.index!(Array.new(100) { Post.new })
281
+ result_sets = Array.new(2) do
282
+ Sunspot.search(Post) { order_by_random }.results.map do |result|
283
+ result.id
284
+ end
285
+ end
286
+ result_sets[0].should_not == result_sets[1]
287
+ end
288
+ end
289
+ end