outoftime-sunspot 0.7.3 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/sunspot/type.rb CHANGED
@@ -98,19 +98,27 @@ module Sunspot
98
98
  # UTC before indexing, and facets of Time fields always return times in UTC.
99
99
  #
100
100
  module TimeType
101
+
101
102
  class <<self
102
- def indexed_name(name)
103
+ def indexed_name(name) #:nodoc:
103
104
  "#{name}_d"
104
105
  end
105
106
 
106
- def to_indexed(value)
107
+ def to_indexed(value) #:nodoc:
107
108
  if value
108
- time = value.respond_to?(:to_time) ? value.to_time : Time.parse(value.to_s)
109
+ time =
110
+ if value.respond_to?(:utc)
111
+ value
112
+ elsif %w(year mon mday).each { |method| value.respond_to?(method) }
113
+ Time.gm(value.year, value.mon, value.mday)
114
+ else
115
+ Time.parse(value.to_s)
116
+ end
109
117
  time.utc.xmlschema
110
118
  end
111
119
  end
112
120
 
113
- def cast(string)
121
+ def cast(string) #:nodoc:
114
122
  Time.xmlschema(string)
115
123
  end
116
124
  end
@@ -122,17 +130,17 @@ module Sunspot
122
130
  #
123
131
  module BooleanType
124
132
  class <<self
125
- def indexed_name(name)
133
+ def indexed_name(name) #:nodoc:
126
134
  "#{name}_b"
127
135
  end
128
136
 
129
- def to_indexed(value)
137
+ def to_indexed(value) #:nodoc:
130
138
  unless value.nil?
131
139
  value ? 'true' : 'false'
132
140
  end
133
141
  end
134
142
 
135
- def cast(string)
143
+ def cast(string) #:nodoc:
136
144
  case string
137
145
  when 'true'
138
146
  true
data/lib/sunspot/util.rb CHANGED
@@ -70,6 +70,23 @@ module Sunspot
70
70
  end
71
71
  end
72
72
 
73
+ #
74
+ # Evaluate the given proc in the context of the given object if the
75
+ # block's arity is non-positive, or by passing the given object as an
76
+ # argument if it is negative.
77
+ #
78
+ # ==== Parameters
79
+ #
80
+ # object<Object>:: Object to pass to the proc
81
+ #
82
+ def instance_eval_or_call(object, &block)
83
+ if block.arity > 0
84
+ block.call(object)
85
+ else
86
+ object.instance_eval(&block)
87
+ end
88
+ end
89
+
73
90
  #
74
91
  # Perform a deep merge of hashes, returning the result as a new hash.
75
92
  # See #deep_merge_into for rules used to merge the hashes
data/lib/sunspot.rb CHANGED
@@ -2,7 +2,8 @@ gem 'solr-ruby'
2
2
  require 'solr'
3
3
  require File.join(File.dirname(__FILE__), 'light_config')
4
4
 
5
- %w(adapters restriction configuration setup field facets indexer query search facet facet_row session type util dsl).each do |filename|
5
+ %w(adapters configuration setup field data_extractor indexer
6
+ query search facet facet_row session type util dsl).each do |filename|
6
7
  require File.join(File.dirname(__FILE__), 'sunspot', filename)
7
8
  end
8
9
 
@@ -93,6 +94,41 @@ module Sunspot
93
94
  # internally, the fields will have different names so there is no danger
94
95
  # of conflict.
95
96
  #
97
+ # ===== Dynamic Fields
98
+ #
99
+ # For use cases which have highly dynamic data models (for instance, an
100
+ # open set of key-value pairs attached to a model), it may be useful to
101
+ # defer definition of fields until indexing time. Sunspot exposes dynamic
102
+ # fields, which define a data accessor (either attribute or virtual, see
103
+ # above), which accepts a hash of field names to values. Note that the field
104
+ # names in the hash are internally scoped to the base name of the dynamic
105
+ # field, so any time they are referred to, they are referred to using both
106
+ # the base name and the dynamic (runtime-specified) name.
107
+ #
108
+ # Dynamic fields are speficied in the setup block using the type name
109
+ # prefixed by +dynamic_+. For example:
110
+ #
111
+ # Sunspot.setup(Post) do
112
+ # dynamic_string :custom_values do
113
+ # key_value_pairs.inject({}) do |hash, key_value_pair|
114
+ # hash[key_value_pair.key.to_sym] = key_value_pair.value
115
+ # end
116
+ # end
117
+ # end
118
+ #
119
+ # If you later wanted to facet all of the values for the key "cuisine",
120
+ # you could issue:
121
+ #
122
+ # Sunspot.search(Post) do
123
+ # dynamic :custom_values do
124
+ # facet :cuisine
125
+ # end
126
+ # end
127
+ #
128
+ # In the documentation, +:custom_values+ is referred to as the "base name" -
129
+ # that is, the one specified statically - and +:cuisine+ is referred to as
130
+ # the dynamic name, which is the part that is specified at indexing time.
131
+ #
96
132
  def setup(clazz, &block)
97
133
  Setup.setup(clazz, &block)
98
134
  end
@@ -146,6 +182,29 @@ module Sunspot
146
182
  session.commit
147
183
  end
148
184
 
185
+ #
186
+ # Create a new Search instance, but do not execute it immediately. Generally
187
+ # you will want to use the #search method to execute searches using the
188
+ # DSL; however, if you are building searches dynamically (using the Builder
189
+ # pattern, for instance), it may be easier to access the Query API directly.
190
+ #
191
+ # ==== Parameters
192
+ #
193
+ # types<Class>...::
194
+ # Zero, one, or more types to search for. If no types are passed, all
195
+ # configured types will be searched for.
196
+ #
197
+ # ==== Returns
198
+ #
199
+ # Sunspot::Search::
200
+ # Search object, not yet executed. Query parameters can be added manually;
201
+ # then #execute! should be called.
202
+ #
203
+ def new_search(*types)
204
+ session.new_search(*types)
205
+ end
206
+
207
+
149
208
  # Search for objects in the index.
150
209
  #
151
210
  # ==== Parameters
@@ -154,6 +213,18 @@ module Sunspot
154
213
  # Zero, one, or more types to search for. If no types are passed, all
155
214
  # configured types will be searched.
156
215
  #
216
+ # ==== Options (last argument, optional)
217
+ #
218
+ # :keywords<String>:: Fulltext search string
219
+ # :conditions<Hash>::
220
+ # Hash of key-value pairs to be used as restrictions. Keys are field
221
+ # names. Scalar values are used as equality restrictions; arrays are used
222
+ # as "any of" restrictions; and Ranges are used as range restrictions.
223
+ # :order<String>:: order field and direction (e.g., 'updated_at desc')
224
+ # :page<Integer>:: Page to start on for pagination
225
+ # :per_page<Integer>::
226
+ # Number of results to use per page. Ignored if :page is not specified.
227
+ #
157
228
  # ==== Returns
158
229
  #
159
230
  # Sunspot::Search:: Object containing results, facets, count, etc.
@@ -201,8 +272,18 @@ module Sunspot
201
272
  # order_by :published_at, :desc
202
273
  # paginate 2, 15
203
274
  # end
275
+ #
276
+ # If the block passed to #search takes an argument, that argument will
277
+ # present the DSL, and the block will be evaluated in the calling context.
278
+ # This will come in handy for building searches using instance data or
279
+ # methods, e.g.:
280
+ #
281
+ # Sunspot.search(Post) do |query|
282
+ # query.with(:blog_id, @current_blog.id)
283
+ # end
204
284
  #
205
- # See Sunspot::DSL::Query for the full API presented inside the block.
285
+ # See Sunspot::DSL::Scope and Sunspot::DSL::Query for the full API presented
286
+ # inside the block.
206
287
  #
207
288
  def search(*types, &block)
208
289
  session.search(*types, &block)
@@ -157,6 +157,79 @@ describe 'Search' do
157
157
  end
158
158
  end
159
159
 
160
+ it 'should restrict by dynamic string field with equality restriction' do
161
+ connection.should_receive(:query).with('(type:Post)', hash_including(:filter_queries => ['custom_string\:test_s:string']))
162
+ session.search Post do
163
+ dynamic :custom_string do
164
+ with :test, 'string'
165
+ end
166
+ end
167
+ end
168
+
169
+ it 'should restrict by dynamic integer field with less than restriction' do
170
+ connection.should_receive(:query).with(anything, hash_including(:filter_queries => ['custom_integer\:test_i:[* TO 1]']))
171
+ session.search Post do
172
+ dynamic :custom_integer do
173
+ with(:test).less_than(1)
174
+ end
175
+ end
176
+ end
177
+
178
+ it 'should restrict by dynamic float field with between restriction' do
179
+ connection.should_receive(:query).with(anything, hash_including(:filter_queries => ['custom_float\:test_fm:[2\.2 TO 3\.3]']))
180
+ session.search Post do
181
+ dynamic :custom_float do
182
+ with(:test).between(2.2..3.3)
183
+ end
184
+ end
185
+ end
186
+
187
+ it 'should restrict by dynamic time field with any of restriction' do
188
+ connection.should_receive(:query).with(anything, hash_including(:filter_queries => ['custom_time\:test_d:(2009\-02\-10T14\:00\:00Z OR 2009\-02\-13T18\:00\:00Z)']))
189
+ session.search Post do
190
+ dynamic :custom_time do
191
+ with(:test).any_of([Time.parse('2009-02-10 14:00:00 UTC'),
192
+ Time.parse('2009-02-13 18:00:00 UTC')])
193
+ end
194
+ end
195
+ end
196
+
197
+ it 'should restrict by dynamic boolean field with equality restriction' do
198
+ connection.should_receive(:query).with(anything, hash_including(:filter_queries => ['custom_boolean\:test_b:false']))
199
+ session.search Post do
200
+ dynamic :custom_boolean do
201
+ with :test, false
202
+ end
203
+ end
204
+ end
205
+
206
+ it 'should negate a dynamic field restriction' do
207
+ connection.should_receive(:query).with(anything, hash_including(:filter_queries => ['-custom_string\:test_s:foo']))
208
+ session.search Post do
209
+ dynamic :custom_string do
210
+ without :test, 'foo'
211
+ end
212
+ end
213
+ end
214
+
215
+ it 'should throw an UnrecognizedFieldError if an unknown dynamic field is searched by' do
216
+ lambda do
217
+ session.search Post do
218
+ dynamic(:bogus) { with :some, 'value' }
219
+ end
220
+ end.should raise_error(Sunspot::UnrecognizedFieldError)
221
+ end
222
+
223
+ it 'should throw a NoMethodError if pagination is attempted in a dynamic query' do
224
+ lambda do
225
+ session.search Post do
226
+ dynamic :custom_string do
227
+ paginate 3, 10
228
+ end
229
+ end
230
+ end.should raise_error(NoMethodError)
231
+ end
232
+
160
233
  it 'should paginate using default per_page when page not provided' do
161
234
  connection.should_receive(:query).with('(type:Post)', hash_including(:rows => 30))
162
235
  session.search Post
@@ -186,7 +259,7 @@ describe 'Search' do
186
259
  end
187
260
  end
188
261
 
189
- it 'should order by multiple columns' do
262
+ it 'should order by multiple fields' do
190
263
  connection.should_receive(:query).with('(type:Post)', hash_including(:sort => [{ :average_rating_f => :descending },
191
264
  { :sort_title_s => :ascending }])).twice
192
265
  session.search Post, :order => ['average_rating desc', 'sort_title asc']
@@ -196,6 +269,34 @@ describe 'Search' do
196
269
  end
197
270
  end
198
271
 
272
+ it 'should order by a dynamic field' do
273
+ connection.should_receive(:query).with(anything, hash_including(:sort => [{ :"custom_integer:test_i" => :descending }]))
274
+ session.search Post do
275
+ dynamic :custom_integer do
276
+ order_by :test, :desc
277
+ end
278
+ end
279
+ end
280
+
281
+ it 'should order by a dynamic field and static field, with given precedence' do
282
+ connection.should_receive(:query).with(anything, hash_including(:sort => [{ :"custom_integer:test_i" => :descending },
283
+ { :sort_title_s => :ascending}]))
284
+ session.search Post do
285
+ dynamic :custom_integer do
286
+ order_by :test, :desc
287
+ end
288
+ order_by :sort_title, :asc
289
+ end
290
+ end
291
+
292
+ it 'should throw an ArgumentError if a bogus order direction is given' do
293
+ lambda do
294
+ session.search Post do
295
+ order_by :sort_title, :sideways
296
+ end
297
+ end.should raise_error(ArgumentError)
298
+ end
299
+
199
300
  it 'should request single field facet' do
200
301
  connection.should_receive(:query).with('(type:Post)', hash_including(:facets => { :fields => %w(category_ids_im) }))
201
302
  session.search Post do
@@ -203,13 +304,22 @@ describe 'Search' do
203
304
  end
204
305
  end
205
306
 
206
- it 'should request multiple field facet' do
307
+ it 'should request multiple field facets' do
207
308
  connection.should_receive(:query).with('(type:Post)', hash_including(:facets => { :fields => %w(category_ids_im blog_id_i) }))
208
309
  session.search Post do
209
310
  facet :category_ids, :blog_id
210
311
  end
211
312
  end
212
313
 
314
+ it 'should allow faceting by dynamic string field' do
315
+ connection.should_receive(:query).with(anything, hash_including(:facets => { :fields => %w(custom_string:test_s) }))
316
+ session.search Post do
317
+ dynamic :custom_string do
318
+ facet :test
319
+ end
320
+ end
321
+ end
322
+
213
323
  it 'should build search for multiple types' do
214
324
  connection.should_receive(:query).with('(type:(Post OR Comment))', hash_including)
215
325
  session.search(Post, Comment)
@@ -245,6 +355,14 @@ describe 'Search' do
245
355
  session.search Post, Comment, :conditions => { :blog_id => 1 }
246
356
  end
247
357
 
358
+ it 'should allow building search using block argument rather than instance_eval' do
359
+ connection.should_receive(:query).with('(type:Post)', hash_including(:filter_queries => ['blog_id_i:1']))
360
+ @blog_id = 1
361
+ session.search Post do |query|
362
+ query.with(:blog_id, @blog_id)
363
+ end
364
+ end
365
+
248
366
  it 'should raise Sunspot::UnrecognizedFieldError for nonexistant fields in block scope' do
249
367
  lambda do
250
368
  session.search Post do
@@ -115,6 +115,44 @@ describe 'indexer' do
115
115
  end
116
116
  end
117
117
 
118
+ describe 'dynamic fields' do
119
+ it 'should index string data' do
120
+ post(:custom_string => { :test => 'string' })
121
+ connection.should_receive(:add).with(hash_including(:"custom_string:test_s" => 'string'))
122
+ session.index(post)
123
+ end
124
+
125
+ it 'should index integer data with virtual accessor' do
126
+ post(:category_ids => [1, 2])
127
+ connection.should_receive(:add).with(hash_including(:"custom_integer:1_i" => '1', :"custom_integer:2_i" => '1'))
128
+ session.index(post)
129
+ end
130
+
131
+ it 'should index float data' do
132
+ post(:custom_fl => { :test => 1.5 })
133
+ connection.should_receive(:add).with(hash_including(:"custom_float:test_fm" => '1.5'))
134
+ session.index(post)
135
+ end
136
+
137
+ it 'should index time data' do
138
+ post(:custom_time => { :test => Time.parse('2009-05-18 18:05:00 -0400') })
139
+ connection.should_receive(:add).with(hash_including(:"custom_time:test_d" => '2009-05-18T22:05:00Z'))
140
+ session.index(post)
141
+ end
142
+
143
+ it 'should index boolean data' do
144
+ post(:custom_boolean => { :test => false })
145
+ connection.should_receive(:add).with(hash_including(:"custom_boolean:test_b" => 'false'))
146
+ session.index(post)
147
+ end
148
+
149
+ it 'should index multiple values for a field' do
150
+ post(:custom_fl => { :test => [1.0, 2.1, 3.2] })
151
+ connection.should_receive(:add).with(hash_including(:"custom_float:test_fm" => %w(1.0 2.1 3.2)))
152
+ session.index(post)
153
+ end
154
+ end
155
+
118
156
  it 'should throw a NoMethodError only if a nonexistent type is defined' do
119
157
  lambda { Sunspot.setup(Post) { string :author_name }}.should_not raise_error
120
158
  lambda { Sunspot.setup(Post) { bogus :journey }}.should raise_error(NoMethodError)
@@ -139,6 +177,12 @@ describe 'indexer' do
139
177
  lambda { session.index(User.new) }.should raise_error(Sunspot::NoAdapterError)
140
178
  end
141
179
 
180
+ it 'should throw an ArgumentError if a non-word character is included in the field name' do
181
+ lambda do
182
+ Sunspot.setup(Post) { string :"bad name" }
183
+ end.should raise_error(ArgumentError)
184
+ end
185
+
142
186
  private
143
187
 
144
188
  def config
@@ -0,0 +1,129 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe Sunspot::Query do
4
+ before :each do
5
+ @config ||= Sunspot::Configuration.build
6
+ @connection ||= mock('connection')
7
+ @session ||= Sunspot::Session.new(@config, @connection)
8
+ @search = @session.new_search(Post)
9
+ end
10
+
11
+ after :each do
12
+ @search.execute!
13
+ end
14
+
15
+ it 'should perform keyword search' do
16
+ @search.query.keywords = 'keyword search'
17
+ @connection.should_receive(:query).with('(keyword search) AND (type:Post)', hash_including)
18
+ end
19
+
20
+ it 'should add equality restriction' do
21
+ @search.query.add_restriction(:title, :equal_to, 'My Pet Post')
22
+ @connection.should_receive(:query).with('(type:Post)', hash_including(:filter_queries => ['title_s:My\ Pet\ Post']))
23
+ end
24
+
25
+ it 'should add less than restriction' do
26
+ @search.query.add_restriction(:average_rating, :less_than, 3.0)
27
+ @connection.should_receive(:query).with('(type:Post)', hash_including(:filter_queries => ['average_rating_f:[* TO 3\.0]']))
28
+ end
29
+
30
+ it 'should add greater than restriction' do
31
+ @search.query.add_restriction(:average_rating, :greater_than, 3.0)
32
+ @connection.should_receive(:query).with('(type:Post)', hash_including(:filter_queries => ['average_rating_f:[3\.0 TO *]']))
33
+ end
34
+
35
+ it 'should add between restriction' do
36
+ @search.query.add_restriction(:average_rating, :between, 2.0..4.0)
37
+ @connection.should_receive(:query).with('(type:Post)', hash_including(:filter_queries => ['average_rating_f:[2\.0 TO 4\.0]']))
38
+ end
39
+
40
+ it 'should add any restriction' do
41
+ @search.query.add_restriction(:category_ids, :any_of, [2, 7, 12])
42
+ @connection.should_receive(:query).with('(type:Post)', hash_including(:filter_queries => ['category_ids_im:(2 OR 7 OR 12)']))
43
+ end
44
+
45
+ it 'should add all restriction' do
46
+ @search.query.add_restriction(:category_ids, :all_of, [2, 7, 12])
47
+ @connection.should_receive(:query).with('(type:Post)', hash_including(:filter_queries => ['category_ids_im:(2 AND 7 AND 12)']))
48
+ end
49
+
50
+ it 'should negate restriction' do
51
+ @search.query.add_negated_restriction(:title, :equal_to, 'Bad Post')
52
+ @connection.should_receive(:query).with('(type:Post)', hash_including(:filter_queries => ['-title_s:Bad\ Post']))
53
+ end
54
+
55
+ it 'should exclude instance' do
56
+ post = Post.new
57
+ @search.query.exclude_instance(post)
58
+ @connection.should_receive(:query).with('(type:Post)', hash_including(:filter_queries => ["-id:Post\\ #{post.id}"]))
59
+ end
60
+
61
+ it 'should paginate using default per-page' do
62
+ @search.query.paginate(2)
63
+ @connection.should_receive(:query).with('(type:Post)', hash_including(:rows => 30, :start => 30))
64
+ end
65
+
66
+ it 'should paginate using provided per-page' do
67
+ @search.query.paginate(4, 15)
68
+ @connection.should_receive(:query).with('(type:Post)', hash_including(:rows => 15, :start => 45))
69
+ end
70
+
71
+ it 'should order ascending by default' do
72
+ @search.query.order_by(:average_rating)
73
+ @connection.should_receive(:query).with('(type:Post)', hash_including(:sort => [{ :average_rating_f => :ascending }]))
74
+ end
75
+
76
+ it 'should order descending if specified' do
77
+ @search.query.order_by(:average_rating, :desc)
78
+ @connection.should_receive(:query).with('(type:Post)', hash_including(:sort => [{ :average_rating_f => :descending }]))
79
+ end
80
+
81
+ it 'should request a field facet' do
82
+ @search.query.add_field_facet(:category_ids)
83
+ @connection.should_receive(:query).with('(type:Post)', hash_including(:facets => { :fields => %w(category_ids_im) }))
84
+ end
85
+
86
+ it 'should restrict by dynamic string field with equality restriction' do
87
+ @search.query.dynamic_query(:custom_string).add_restriction(:test, :equal_to, 'string')
88
+ @connection.should_receive(:query).with('(type:Post)', hash_including(:filter_queries => ['custom_string\:test_s:string']))
89
+ end
90
+
91
+ it 'should restrict by dynamic integer field with less than restriction' do
92
+ @search.query.dynamic_query(:custom_integer).add_restriction(:test, :less_than, 1)
93
+ @connection.should_receive(:query).with(anything, hash_including(:filter_queries => ['custom_integer\:test_i:[* TO 1]']))
94
+ end
95
+
96
+ it 'should restrict by dynamic float field with between restriction' do
97
+ @search.query.dynamic_query(:custom_float).add_restriction(:test, :between, 2.2..3.3)
98
+ @connection.should_receive(:query).with(anything, hash_including(:filter_queries => ['custom_float\:test_fm:[2\.2 TO 3\.3]']))
99
+ end
100
+
101
+ it 'should restrict by dynamic time field with any of restriction' do
102
+ @search.query.dynamic_query(:custom_time).add_restriction(:test, :any_of,
103
+ [Time.parse('2009-02-10 14:00:00 UTC'),
104
+ Time.parse('2009-02-13 18:00:00 UTC')])
105
+ @connection.should_receive(:query).with(anything, hash_including(:filter_queries => ['custom_time\:test_d:(2009\-02\-10T14\:00\:00Z OR 2009\-02\-13T18\:00\:00Z)']))
106
+ end
107
+
108
+ it 'should restrict by dynamic boolean field with equality restriction' do
109
+ @search.query.dynamic_query(:custom_boolean).add_restriction(:test, :equal_to, false)
110
+ @connection.should_receive(:query).with(anything, hash_including(:filter_queries => ['custom_boolean\:test_b:false']))
111
+ end
112
+
113
+ it 'should negate a dynamic field restriction' do
114
+ @search.query.dynamic_query(:custom_string).add_negated_restriction(:test, :equal_to, 'foo')
115
+ @connection.should_receive(:query).with(anything, hash_including(:filter_queries => ['-custom_string\:test_s:foo']))
116
+ end
117
+
118
+ it 'should order by a dynamic field' do
119
+ @search.query.dynamic_query(:custom_integer).order_by(:test, :desc)
120
+ @connection.should_receive(:query).with(anything, hash_including(:sort => [{ :"custom_integer:test_i" => :descending }]))
121
+ end
122
+
123
+ it 'should order by a dynamic field and static field, with given precedence' do
124
+ @search.query.dynamic_query(:custom_integer).order_by(:test, :desc)
125
+ @search.query.order_by(:sort_title, :asc)
126
+ @connection.should_receive(:query).with(anything, hash_including(:sort => [{ :"custom_integer:test_i" => :descending },
127
+ { :sort_title_s => :ascending}]))
128
+ end
129
+ end
@@ -96,6 +96,12 @@ describe 'retrieving search' do
96
96
  facet_values(result, :featured).should == [true, false]
97
97
  end
98
98
 
99
+ it 'should return dynamic string facet' do
100
+ stub_facet(:"custom_string:test_s", 'two' => 2, 'one' => 1)
101
+ result = session.search(Post) { dynamic(:custom_string) { facet(:test) }}
102
+ result.dynamic_facet(:custom_string, :test).rows.map { |row| row.value }.should == ['two', 'one']
103
+ end
104
+
99
105
  private
100
106
 
101
107
  def stub_results(*results)
@@ -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
data/spec/mocks/post.rb CHANGED
@@ -25,8 +25,24 @@ class Post < BaseClass
25
25
  ids.map { |id| get(id) }.sort_by { |post| post.id } # this is so that results are not ordered by coincidence
26
26
  end
27
27
 
28
+ def custom_string
29
+ @custom_string ||= {}
30
+ end
31
+
32
+ def custom_fl
33
+ @custom_fl ||= {}
34
+ end
35
+
36
+ def custom_time
37
+ @custom_time ||= {}
38
+ end
39
+
40
+ def custom_boolean
41
+ @custom_boolean ||= {}
42
+ end
43
+
28
44
  private
29
- attr_writer :category_ids
45
+ attr_writer :category_ids, :custom_string, :custom_fl, :custom_time, :custom_boolean
30
46
  end
31
47
 
32
48
  Sunspot.setup(Post) do
@@ -43,4 +59,14 @@ Sunspot.setup(Post) do
43
59
  integer :primary_category_id do |post|
44
60
  post.category_ids.first
45
61
  end
62
+
63
+ dynamic_string :custom_string
64
+ dynamic_float :custom_float, :multiple => true, :using => :custom_fl
65
+ dynamic_integer :custom_integer do
66
+ category_ids.inject({}) do |hash, category_id|
67
+ hash.merge(category_id => 1)
68
+ end
69
+ end
70
+ dynamic_time :custom_time
71
+ dynamic_boolean :custom_boolean
46
72
  end
data/spec/spec_helper.rb CHANGED
@@ -1,9 +1,16 @@
1
1
  require 'rubygems'
2
- gem 'ruby-debug', '~>0.10'
3
2
  gem 'mislav-will_paginate', '~> 2.3'
4
3
  gem 'rspec', '~> 1.1'
5
-
6
- require 'ruby-debug'
4
+ begin
5
+ gem 'ruby-debug', '~>0.10'
6
+ require 'ruby-debug'
7
+ rescue Gem::LoadError
8
+ module Kernel
9
+ def debugger
10
+ raise("debugger is not available")
11
+ end
12
+ end
13
+ end
7
14
  require 'spec'
8
15
  require 'will_paginate'
9
16
  require 'will_paginate/collection'