sunspot 2.2.0 → 2.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. checksums.yaml +7 -0
  2. data/lib/sunspot/dsl/field_query.rb +11 -9
  3. data/lib/sunspot/dsl/fulltext.rb +9 -1
  4. data/lib/sunspot/dsl/group.rb +108 -0
  5. data/lib/sunspot/dsl/search.rb +1 -1
  6. data/lib/sunspot/dsl.rb +1 -1
  7. data/lib/sunspot/field_factory.rb +28 -15
  8. data/lib/sunspot/indexer.rb +63 -17
  9. data/lib/sunspot/query/abstract_fulltext.rb +9 -2
  10. data/lib/sunspot/query/dismax.rb +11 -4
  11. data/lib/sunspot/query/{field_group.rb → group.rb} +16 -6
  12. data/lib/sunspot/query/group_query.rb +17 -0
  13. data/lib/sunspot/query/restriction.rb +45 -1
  14. data/lib/sunspot/query.rb +1 -1
  15. data/lib/sunspot/schema.rb +2 -1
  16. data/lib/sunspot/search/abstract_search.rb +7 -3
  17. data/lib/sunspot/search/query_group.rb +74 -0
  18. data/lib/sunspot/search/standard_search.rb +1 -1
  19. data/lib/sunspot/search.rb +1 -1
  20. data/lib/sunspot/session.rb +16 -0
  21. data/lib/sunspot/session_proxy/master_slave_session_proxy.rb +2 -2
  22. data/lib/sunspot/session_proxy/retry_5xx_session_proxy.rb +1 -1
  23. data/lib/sunspot/session_proxy/sharding_session_proxy.rb +4 -2
  24. data/lib/sunspot/session_proxy/silent_fail_session_proxy.rb +1 -1
  25. data/lib/sunspot/session_proxy/thread_local_session_proxy.rb +3 -1
  26. data/lib/sunspot/type.rb +20 -0
  27. data/lib/sunspot/version.rb +1 -1
  28. data/lib/sunspot.rb +36 -2
  29. data/spec/api/indexer/attributes_spec.rb +5 -0
  30. data/spec/api/query/function_spec.rb +103 -0
  31. data/spec/api/query/group_spec.rb +23 -1
  32. data/spec/api/session_proxy/sharding_session_proxy_spec.rb +1 -1
  33. data/spec/helpers/integration_helper.rb +9 -0
  34. data/spec/integration/atomic_updates_spec.rb +44 -0
  35. data/spec/integration/field_grouping_spec.rb +13 -0
  36. data/spec/integration/scoped_search_spec.rb +51 -0
  37. data/spec/mocks/post.rb +3 -1
  38. metadata +11 -15
  39. data/lib/sunspot/dsl/field_group.rb +0 -57
@@ -0,0 +1,74 @@
1
+ require 'sunspot/search/paginated_collection'
2
+
3
+ module Sunspot
4
+ module Search
5
+ class QueryGroup
6
+ def initialize(queries, search) #:nodoc:
7
+ @queries, @search = queries, search
8
+ end
9
+
10
+ def groups
11
+ @groups ||=
12
+ if solr_response_for_first
13
+ groups = @queries.map { |query| Group.new(query.label, doclist_for(query), @search) }
14
+
15
+ paginate_collection(groups)
16
+ end
17
+ end
18
+
19
+ def matches
20
+ if solr_response_for_first
21
+ solr_response_for_first['matches'].to_i
22
+ end
23
+ end
24
+
25
+ def total
26
+ @queries.count
27
+ end
28
+
29
+ #
30
+ # It populates all grouped hits at once.
31
+ # Useful for eager loading fall grouped results at once.
32
+ #
33
+ def populate_all_hits
34
+ # Init a 2 dimension Hash that contains an array per key
35
+ id_hit_hash = Hash.new { |hash, key| hash[key] = Hash.new{ |h, k| h[k] = [] } }
36
+ groups.each do |g|
37
+ # Take all hits to being populated later on
38
+ g.hits.each do |hit|
39
+ id_hit_hash[hit.class_name][hit.primary_key] |= [hit]
40
+ end
41
+ end
42
+ # Go for each class and load the results' objects into each of the hits
43
+ id_hit_hash.each_pair do |class_name, many_hits|
44
+ ids = many_hits.keys
45
+ data_accessor = @search.data_accessor_for(Util.full_const_get(class_name))
46
+ hits_for_class = id_hit_hash[class_name]
47
+ data_accessor.load_all(ids).each do |result|
48
+ hits = hits_for_class.delete(Adapters::InstanceAdapter.adapt(result).id.to_s)
49
+ hits.each{ |hit| hit.result = result }
50
+ end
51
+ hits_for_class.values.each { |hits| hits.each{|hit| hit.result = nil } }
52
+ end
53
+ end
54
+
55
+ private
56
+
57
+ def doclist_for(query)
58
+ solr_response_for(query.to_boolean_phrase)['doclist']
59
+ end
60
+
61
+ def solr_response_for(query_string)
62
+ @search.group_response[query_string]
63
+ end
64
+
65
+ def solr_response_for_first
66
+ solr_response_for(@queries[0].to_boolean_phrase)
67
+ end
68
+
69
+ def paginate_collection(collection)
70
+ PaginatedCollection.new(collection, @search.query.page, @search.query.per_page, total)
71
+ end
72
+ end
73
+ end
74
+ end
@@ -70,7 +70,7 @@ module Sunspot
70
70
  # If no query was given, or all terms are present in the index,
71
71
  # return Solr's suggested collation.
72
72
  if terms.length == 0
73
- collation = solr_spellcheck['suggestions'][-1]
73
+ collation = solr_spellcheck['collations'][-1]
74
74
  end
75
75
 
76
76
  collation
@@ -1,6 +1,6 @@
1
1
  %w(abstract_search standard_search more_like_this_search query_facet field_facet
2
2
  date_facet range_facet facet_row hit highlight field_group group hit_enumerable
3
- stats_row field_stats stats_facet).each do |file|
3
+ stats_row field_stats stats_facet query_group).each do |file|
4
4
  require File.join(File.dirname(__FILE__), 'search', file)
5
5
  end
6
6
 
@@ -99,6 +99,22 @@ module Sunspot
99
99
  commit
100
100
  end
101
101
 
102
+ #
103
+ # See Sunspot.atomic_update
104
+ #
105
+ def atomic_update(clazz, updates = {})
106
+ @adds += updates.keys.length
107
+ indexer.add_atomic_update(clazz, updates)
108
+ end
109
+
110
+ #
111
+ # See Sunspot.atomic_update!
112
+ #
113
+ def atomic_update!(clazz, updates = {})
114
+ atomic_update(clazz, updates)
115
+ commit
116
+ end
117
+
102
118
  #
103
119
  # See Sunspot.commit
104
120
  #
@@ -19,8 +19,8 @@ module Sunspot
19
19
 
20
20
  delegate :batch, :commit, :commit_if_delete_dirty, :commit_if_dirty,
21
21
  :config, :delete_dirty?, :dirty?, :index, :index!, :optimize, :remove,
22
- :remove!, :remove_all, :remove_all!, :remove_by_id,
23
- :remove_by_id!, :to => :master_session
22
+ :remove!, :remove_all, :remove_all!, :remove_by_id, :remove_by_id!,
23
+ :atomic_update, :atomic_update!, :to => :master_session
24
24
  delegate :new_search, :search, :new_more_like_this, :more_like_this, :to => :slave_session
25
25
 
26
26
  def initialize(master_session, slave_session)
@@ -59,7 +59,7 @@ module Sunspot
59
59
 
60
60
  delegate :batch, :commit, :commit_if_dirty, :commit_if_delete_dirty,
61
61
  :dirty?, :index!, :index, :optimize, :remove!, :remove, :remove_all!,
62
- :remove_all, :remove_by_id!, :remove_by_id,
62
+ :remove_all, :remove_by_id!, :remove_by_id, :atomic_update, :atomic_update!,
63
63
  :to => :retry_handler
64
64
 
65
65
  end
@@ -27,9 +27,11 @@ module Sunspot
27
27
  # * remove_by_id!
28
28
  # * remove_all with an argument
29
29
  # * remove_all! with an argument
30
+ # * atomic_update with arguments
31
+ # * atomic_update! with arguments
30
32
  #
31
33
  class ShardingSessionProxy < AbstractSessionProxy
32
- not_supported :batch, :config, :remove_by_id, :remove_by_id!
34
+ not_supported :batch, :config, :remove_by_id, :remove_by_id!, :atomic_update, :atomic_update!
33
35
 
34
36
  #
35
37
  # +search_session+ is the session that should be used for searching.
@@ -70,7 +72,7 @@ module Sunspot
70
72
  using_sharded_session(objects) { |session, group| session.index!(group) }
71
73
  end
72
74
 
73
- #
75
+ #
74
76
  # See Sunspot.remove
75
77
  #
76
78
  def remove(*objects)
@@ -22,7 +22,7 @@ module Sunspot
22
22
  SUPPORTED_METHODS = [
23
23
  :batch, :commit, :commit_if_dirty, :commit_if_delete_dirty, :dirty?,
24
24
  :index!, :index, :optimize, :remove!, :remove, :remove_all!, :remove_all,
25
- :remove_by_id!, :remove_by_id
25
+ :remove_by_id!, :remove_by_id, :atomic_update, :atomic_update!
26
26
  ]
27
27
 
28
28
  SUPPORTED_METHODS.each do |method|
@@ -17,7 +17,9 @@ module Sunspot
17
17
  attr_reader :config
18
18
  @@next_id = 0
19
19
 
20
- delegate :batch, :commit, :commit_if_delete_dirty, :commit_if_dirty, :delete_dirty?, :dirty?, :index, :index!, :new_search, :optimize, :remove, :remove!, :remove_all, :remove_all!, :remove_by_id, :remove_by_id!, :search, :more_like_this, :new_more_like_this, :to => :session
20
+ delegate :batch, :commit, :commit_if_delete_dirty, :commit_if_dirty, :delete_dirty?, :dirty?, :index, :index!,
21
+ :new_search, :optimize, :remove, :remove!, :remove_all, :remove_all!, :remove_by_id, :remove_by_id!,
22
+ :search, :more_like_this, :new_more_like_this, :atomic_update, :atomic_update!, :to => :session
21
23
 
22
24
  #
23
25
  # Optionally pass an existing Sunspot::Configuration object. If none is
data/lib/sunspot/type.rb CHANGED
@@ -375,6 +375,26 @@ module Sunspot
375
375
  end
376
376
  end
377
377
 
378
+ class DateRangeType < DateType
379
+ def indexed_name(name)
380
+ "#{name}_dr"
381
+ end
382
+
383
+ def to_indexed(value)
384
+ if value.respond_to?(:first) && value.respond_to?(:last)
385
+ "[#{super value.first} TO #{super value.last}]"
386
+ else
387
+ super value
388
+ end
389
+ end
390
+
391
+ def cast(value)
392
+ return super unless m = value.match(/^\[(?<start>.+) TO (?<end>.+)\]$/)
393
+ Range.new super(m[:start]), super(m[:end])
394
+ end
395
+ end
396
+ register DateRangeType, Range
397
+
378
398
  class ClassType < AbstractType
379
399
  def indexed_name(name) #:nodoc:
380
400
  'class_name'
@@ -1,3 +1,3 @@
1
1
  module Sunspot
2
- VERSION = '2.2.0'
2
+ VERSION = '2.2.1'
3
3
  end
data/lib/sunspot.rb CHANGED
@@ -196,6 +196,40 @@ module Sunspot
196
196
  session.index!(*objects)
197
197
  end
198
198
 
199
+ # Atomic update object properties on the singleton session.
200
+ #
201
+ # ==== Parameters
202
+ #
203
+ # clazz<Class>:: the class of the objects to be updated
204
+ # updates<Hash>:: hash of updates where keys are model ids
205
+ # and values are hash with property name/values to be updated
206
+ #
207
+ # ==== Example
208
+ #
209
+ # post1, post2 = new Array(2) { Post.create }
210
+ # Sunspot.atomic_update(Post, post1.id => {title: 'New Title'}, post2.id => {description: 'new description'})
211
+ #
212
+ # Note that indexed objects won't be reflected in search until a commit is
213
+ # sent - see Sunspot.index! and Sunspot.commit
214
+ #
215
+ def atomic_update(clazz, updates = {})
216
+ session.atomic_update(clazz, updates)
217
+ end
218
+
219
+ # Atomic update object properties on the singleton session.
220
+ #
221
+ # See: Sunspot.atomic_update and Sunspot.commit
222
+ #
223
+ # ==== Parameters
224
+ #
225
+ # clazz<Class>:: the class of the objects to be updated
226
+ # updates<Hash>:: hash of updates where keys are model ids
227
+ # and values are hash with property name/values to be updated
228
+ #
229
+ def atomic_update!(clazz, updates = {})
230
+ session.atomic_update!(clazz, updates)
231
+ end
232
+
199
233
  # Commits (soft or hard) the singleton session
200
234
  #
201
235
  # When documents are added to or removed from Solr, the changes are
@@ -490,9 +524,9 @@ module Sunspot
490
524
  #
491
525
  # Sunspot.batch do
492
526
  # post = Post.new
493
- # Sunspot.add(post)
527
+ # Sunspot.index(post)
494
528
  # comment = Comment.new
495
- # Sunspot.add(comment)
529
+ # Sunspot.index(comment)
496
530
  # end
497
531
  #
498
532
  # Sunspot will send both the post and the comment in a single request.
@@ -71,6 +71,11 @@ describe 'indexing attribute fields', :type => :indexer do
71
71
  connection.should have_add_with(:expire_date_d => '2009-07-13T00:00:00Z')
72
72
  end
73
73
 
74
+ it 'should correctly index a date range field' do
75
+ session.index(post(:featured_for => Date.new(2009, 07, 13)..Date.new(2009, 12, 25)))
76
+ connection.should have_add_with(:featured_for_dr => '[2009-07-13T00:00:00Z TO 2009-12-25T00:00:00Z]')
77
+ end
78
+
74
79
  it 'should correctly index a boolean field' do
75
80
  session.index(post(:featured => true))
76
81
  connection.should have_add_with(:featured_bs => 'true')
@@ -102,5 +102,108 @@ describe 'function query' do
102
102
  end
103
103
  end.should raise_error(Sunspot::UnrecognizedFieldError)
104
104
  end
105
+
106
+ it "should send query to solr with multiplicative boost function" do
107
+ session.search Post do
108
+ keywords('pizza') do
109
+ multiplicative_boost(function { :average_rating })
110
+ end
111
+ end
112
+ connection.should have_last_search_including(:boost, 'average_rating_ft')
113
+ end
114
+
115
+ it "should send query to solr with multiplicative boost function and boost amount" do
116
+ session.search Post do
117
+ keywords('pizza') do
118
+ multiplicative_boost(function { :average_rating }^5)
119
+ end
120
+ end
121
+ connection.should have_last_search_including(:boost, 'average_rating_ft^5')
122
+ end
123
+
124
+ it "should handle multiplicative boost function with constant float" do
125
+ session.search Post do
126
+ keywords('pizza') do
127
+ multiplicative_boost(function { 10.5 })
128
+ end
129
+ end
130
+ connection.should have_last_search_including(:boost, '10.5')
131
+ end
132
+
133
+ it "should handle multiplicative boost function with constant float and boost amount" do
134
+ session.search Post do
135
+ keywords('pizza') do
136
+ multiplicative_boost(function { 10.5 }^5)
137
+ end
138
+ end
139
+ connection.should have_last_search_including(:boost, '10.5^5')
140
+ end
141
+
142
+ it "should handle multiplicative boost function with time literal" do
143
+ session.search Post do
144
+ keywords('pizza') do
145
+ multiplicative_boost(function { Time.parse('2010-03-25 14:13:00 EDT') })
146
+ end
147
+ end
148
+ connection.should have_last_search_including(:boost, '2010-03-25T18:13:00Z')
149
+ end
150
+
151
+ it "should handle arbitrary functions in a function query block" do
152
+ session.search Post do
153
+ keywords('pizza') do
154
+ multiplicative_boost(function { product(:average_rating, 10) })
155
+ end
156
+ end
157
+ connection.should have_last_search_including(:boost, 'product(average_rating_ft,10)')
158
+ end
159
+
160
+ it "should handle the sub function in a multiplicative boost function query block" do
161
+ session.search Post do
162
+ keywords('pizza') do
163
+ multiplicative_boost(function { sub(:average_rating, 10) })
164
+ end
165
+ end
166
+ connection.should have_last_search_including(:boost, 'sub(average_rating_ft,10)')
167
+ end
168
+
169
+ it "should handle boost amounts on multiplicative boost function query block" do
170
+ session.search Post do
171
+ keywords('pizza') do
172
+ multiplicative_boost(function { sub(:average_rating, 10)^5 })
173
+ end
174
+ end
175
+ connection.should have_last_search_including(:boost, 'sub(average_rating_ft,10)^5')
176
+ end
177
+
178
+ it "should handle nested functions in a multiplicative boost function query block" do
179
+ session.search Post do
180
+ keywords('pizza') do
181
+ multiplicative_boost(function { product(:average_rating, sum(:average_rating, 20)) })
182
+ end
183
+ end
184
+ connection.should have_last_search_including(:boost, 'product(average_rating_ft,sum(average_rating_ft,20))')
185
+ end
186
+
187
+ # TODO SOLR 1.5
188
+ it "should raise ArgumentError if string literal passed to multiplicative boost" do
189
+ lambda do
190
+ session.search Post do
191
+ keywords('pizza') do
192
+ multiplicative_boost(function { "hello world" })
193
+ end
194
+ end
195
+ end.should raise_error(ArgumentError)
196
+ end
197
+
198
+ it "should raise UnrecognizedFieldError if bogus field name passed to multiplicative boost" do
199
+ lambda do
200
+ session.search Post do
201
+ keywords('pizza') do
202
+ multiplicative_boost(function { :bogus })
203
+ end
204
+ end
205
+ end.should raise_error(Sunspot::UnrecognizedFieldError)
206
+ end
207
+
105
208
  end
106
209
 
@@ -1,6 +1,6 @@
1
1
  require File.expand_path('spec_helper', File.dirname(__FILE__))
2
2
 
3
- describe "field grouping" do
3
+ describe "grouping" do
4
4
  it "sends grouping parameters to solr" do
5
5
  session.search Post do
6
6
  group :title
@@ -29,4 +29,26 @@ describe "field grouping" do
29
29
 
30
30
  connection.should have_last_search_including(:"group.sort", "average_rating_ft asc")
31
31
  end
32
+
33
+ it "sends grouping field parameters to solr" do
34
+ session.search Post do
35
+ group do
36
+ field :title
37
+ end
38
+ end
39
+
40
+ connection.should have_last_search_including(:"group.field", "title_ss")
41
+ end
42
+
43
+ it "sends grouping query parameters to solr" do
44
+ session.search Post do
45
+ group do
46
+ query 'category 1' do
47
+ with(:category_ids, 1)
48
+ end
49
+ end
50
+ end
51
+
52
+ connection.should have_last_search_including(:"group.query", "category_ids_im:1")
53
+ end
32
54
  end
@@ -15,7 +15,7 @@ describe Sunspot::SessionProxy::ShardingSessionProxy do
15
15
  end
16
16
  end
17
17
 
18
- [:remove_by_id, :remove_by_id!].each do |method|
18
+ [:remove_by_id, :remove_by_id!, :atomic_update, :atomic_update!].each do |method|
19
19
  it "should raise NotSupportedError when #{method} called" do
20
20
  lambda { @proxy.send(method, Post, 1) }.should raise_error(Sunspot::SessionProxy::NotSupportedError)
21
21
  end
@@ -5,4 +5,13 @@ module IntegrationHelper
5
5
  Sunspot.reset!(true)
6
6
  end
7
7
  end
8
+
9
+ def featured_for_posts(method, param, negated = false)
10
+ with_method = negated ? :without : :with
11
+ param = date_ranges[param] if param.is_a? String
12
+
13
+ Sunspot.search(Post) do
14
+ send(with_method, :featured_for).send(method, param)
15
+ end.results
16
+ end
8
17
  end
@@ -0,0 +1,44 @@
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, title, featured)
9
+ hit.stored(:title).should == title
10
+ hit.stored(:featured).should == featured
11
+ end
12
+
13
+ def find_indexed_post(id)
14
+ hit = Sunspot.search(Post).hits.find{ |h| h.primary_key.to_i == id }
15
+ hit.should_not be_nil
16
+ hit
17
+ end
18
+
19
+ it 'should update single record fields one by one' do
20
+ post = Post.new(title: 'A Title', featured: true)
21
+ Sunspot.index!(post)
22
+
23
+ validate_hit(find_indexed_post(post.id), post.title, post.featured)
24
+
25
+ Sunspot.atomic_update!(Post, post.id => {title: 'A New Title'})
26
+ validate_hit(find_indexed_post(post.id), 'A New Title', true)
27
+
28
+ Sunspot.atomic_update!(Post, post.id => {featured: false})
29
+ validate_hit(find_indexed_post(post.id), 'A New Title', false)
30
+ end
31
+
32
+ it 'should update fields for multiple records' do
33
+ post1 = Post.new(title: 'A First Title', featured: true)
34
+ post2 = Post.new(title: 'A Second Title', featured: false)
35
+ Sunspot.index!(post1, post2)
36
+
37
+ validate_hit(find_indexed_post(post1.id), post1.title, post1.featured)
38
+ validate_hit(find_indexed_post(post2.id), post2.title, post2.featured)
39
+
40
+ Sunspot.atomic_update!(Post, post1.id => {title: 'A New Title'}, post2.id => {featured: true})
41
+ validate_hit(find_indexed_post(post1.id), 'A New Title', true)
42
+ validate_hit(find_indexed_post(post2.id), 'A Second Title', true)
43
+ end
44
+ end
@@ -72,6 +72,19 @@ describe "field grouping" do
72
72
  title1_group.hits.first.primary_key.to_i.should == highest_ranked_post.id
73
73
  end
74
74
 
75
+ it "allows specification of an ordering function within groups" do
76
+ search = Sunspot.search(Post) do
77
+ group :title do
78
+ order_by_function(:product, :average_rating, -2, :asc)
79
+ end
80
+ end
81
+
82
+ highest_ranked_post = @posts.sort_by { |p| -p.ratings_average }.first
83
+
84
+ title1_group = search.group(:title).groups.detect { |g| g.value == "Title1" }
85
+ title1_group.hits.first.primary_key.to_i.should == highest_ranked_post.id
86
+ end
87
+
75
88
  it "allows pagination within groups" do
76
89
  search = Sunspot.search(Post) do
77
90
  group :title
@@ -127,6 +127,57 @@ describe 'scoped_search' do
127
127
  test_field_type 'Trie Time', :created_at, :created_at, Photo, *(['1970-01-01 00:00:00 UTC', '1983-07-08 04:00:00 UTC', '1983-07-08 02:00:00 -0500',
128
128
  '2005-11-05 10:00:00 UTC', Time.now.to_s].map { |t| Time.parse(t) })
129
129
 
130
+ describe 'Date range field type' do
131
+ let(:january) { Date.new(2015,1,1)..Date.new(2015,1,31) }
132
+ let(:february) { Date.new(2015,2,1)..Date.new(2015,2,28) }
133
+ let(:date_ranges) do
134
+ {
135
+ 'December and January' => Date.new(2014,12,25)..Date.new(2015,1,10),
136
+ 'January only' => Date.new(2015,1,5)..Date.new(2015,1,20),
137
+ 'January and February' => Date.new(2015,1,25)..Date.new(2015,2,10),
138
+ 'February only' => Date.new(2015,2,5)..Date.new(2015,2,20),
139
+ 'December to February' => Date.new(2014,12,25)..Date.new(2015,2,10),
140
+ 'January to March' => Date.new(2015,1,25)..Date.new(2015,3,10),
141
+ 'December to March' => Date.new(2014,12,25)..Date.new(2015,3,20)
142
+ }
143
+ end
144
+
145
+ before :all do
146
+ Sunspot.remove_all
147
+ @posts = [Post.new(featured_for: january), Post.new(featured_for: february), Post.new]
148
+ Sunspot.index!(@posts)
149
+ end
150
+
151
+ it 'should filter by Contains' do
152
+ featured_for_posts(:containing, Date.new(2015,1,15) ).should == [@posts[0]]
153
+ featured_for_posts(:containing, 'December and January').should be_empty
154
+ featured_for_posts(:containing, 'January only').should == [@posts[0]]
155
+ featured_for_posts(:containing, 'January only', negated = true).should == @posts[1..-1]
156
+ end
157
+
158
+ it 'should filter by Intersects' do
159
+ featured_for_posts(:intersecting, Date.new(2015,1,15) ).should == [@posts[0]]
160
+ featured_for_posts(:intersecting, 'January only').should == [@posts[0]]
161
+ featured_for_posts(:intersecting, 'January and February').should == @posts[0..1]
162
+ featured_for_posts(:intersecting, 'January and February', negated = true).should == [@posts[2]]
163
+ featured_for_posts(:intersecting, 'February only').should == [@posts[1]]
164
+ featured_for_posts(:intersecting, 'February only', negated = true).should == [@posts[0], @posts[2]]
165
+ end
166
+
167
+ it 'should filter by Within' do
168
+ featured_for_posts(:within, Date.new(2015,1,15) ).should be_empty
169
+ (date_ranges.keys - date_ranges.keys.grep(/ to /)).each do |key|
170
+ featured_for_posts(:within, key).should be_empty
171
+ end
172
+ featured_for_posts(:within, 'December to February').should == [@posts[0]]
173
+ featured_for_posts(:within, 'December to February', negated = true).should == @posts[1..-1]
174
+ featured_for_posts(:within, 'January to March').should == [@posts[1]]
175
+ featured_for_posts(:within, 'January to March', negated = true).should == [@posts[0], @posts[2]]
176
+ featured_for_posts(:within, 'December to March').should == @posts[0..1]
177
+ featured_for_posts(:within, 'December to March', negated = true).should == [@posts[2]]
178
+ end
179
+ end
180
+
130
181
  describe 'Boolean field type' do
131
182
  before :all do
132
183
  Sunspot.remove_all
data/spec/mocks/post.rb CHANGED
@@ -3,7 +3,8 @@ require File.join(File.dirname(__FILE__), 'super_class')
3
3
 
4
4
  class Post < SuperClass
5
5
  attr_accessor :title, :body, :blog_id, :published_at, :ratings_average,
6
- :author_name, :featured, :expire_date, :coordinates, :tags
6
+ :author_name, :featured, :expire_date, :coordinates, :tags,
7
+ :featured_for
7
8
  alias_method :featured?, :featured
8
9
 
9
10
  def category_ids
@@ -43,6 +44,7 @@ Sunspot.setup(Post) do
43
44
  float :average_rating, :using => :ratings_average, :trie => true
44
45
  time :published_at, :trie => true
45
46
  date :expire_date
47
+ date_range :featured_for
46
48
  boolean :featured, :using => :featured?, :stored => true
47
49
  string :sort_title do
48
50
  title.downcase.sub(/^(a|an|the)\W+/, '') if title