pixeltrix-thinking-sphinx 1.1.5 → 1.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 (76) hide show
  1. data/README.textile +147 -0
  2. data/lib/thinking_sphinx/active_record/attribute_updates.rb +48 -0
  3. data/lib/thinking_sphinx/active_record/delta.rb +14 -1
  4. data/lib/thinking_sphinx/active_record/scopes.rb +37 -0
  5. data/lib/thinking_sphinx/active_record.rb +46 -12
  6. data/lib/thinking_sphinx/adapters/abstract_adapter.rb +9 -1
  7. data/lib/thinking_sphinx/adapters/mysql_adapter.rb +3 -2
  8. data/lib/thinking_sphinx/adapters/postgresql_adapter.rb +12 -5
  9. data/lib/thinking_sphinx/association.rb +20 -0
  10. data/lib/thinking_sphinx/attribute.rb +187 -116
  11. data/lib/thinking_sphinx/class_facet.rb +15 -0
  12. data/lib/thinking_sphinx/configuration.rb +46 -14
  13. data/lib/thinking_sphinx/core/string.rb +3 -10
  14. data/lib/thinking_sphinx/deltas/datetime_delta.rb +3 -3
  15. data/lib/thinking_sphinx/deltas/default_delta.rb +9 -6
  16. data/lib/thinking_sphinx/deltas/delayed_delta/delta_job.rb +1 -1
  17. data/lib/thinking_sphinx/deltas/delayed_delta.rb +4 -2
  18. data/lib/thinking_sphinx/deltas.rb +14 -6
  19. data/lib/thinking_sphinx/deploy/capistrano.rb +98 -0
  20. data/lib/thinking_sphinx/excerpter.rb +22 -0
  21. data/lib/thinking_sphinx/facet.rb +68 -18
  22. data/lib/thinking_sphinx/facet_search.rb +134 -0
  23. data/lib/thinking_sphinx/field.rb +7 -97
  24. data/lib/thinking_sphinx/index/builder.rb +255 -201
  25. data/lib/thinking_sphinx/index.rb +28 -343
  26. data/lib/thinking_sphinx/property.rb +160 -0
  27. data/lib/thinking_sphinx/rails_additions.rb +7 -4
  28. data/lib/thinking_sphinx/search.rb +593 -587
  29. data/lib/thinking_sphinx/search_methods.rb +421 -0
  30. data/lib/thinking_sphinx/source/internal_properties.rb +46 -0
  31. data/lib/thinking_sphinx/source/sql.rb +128 -0
  32. data/lib/thinking_sphinx/source.rb +150 -0
  33. data/lib/thinking_sphinx/tasks.rb +45 -11
  34. data/lib/thinking_sphinx.rb +88 -14
  35. data/rails/init.rb +14 -0
  36. data/spec/{unit → lib}/thinking_sphinx/active_record/delta_spec.rb +7 -7
  37. data/spec/{unit → lib}/thinking_sphinx/active_record/has_many_association_spec.rb +0 -0
  38. data/spec/lib/thinking_sphinx/active_record/scopes_spec.rb +92 -0
  39. data/spec/{unit → lib}/thinking_sphinx/active_record_spec.rb +115 -42
  40. data/spec/{unit → lib}/thinking_sphinx/association_spec.rb +4 -5
  41. data/spec/lib/thinking_sphinx/attribute_spec.rb +465 -0
  42. data/spec/{unit → lib}/thinking_sphinx/configuration_spec.rb +118 -7
  43. data/spec/{unit → lib}/thinking_sphinx/core/string_spec.rb +0 -0
  44. data/spec/lib/thinking_sphinx/excerpter_spec.rb +49 -0
  45. data/spec/lib/thinking_sphinx/facet_search_spec.rb +176 -0
  46. data/spec/lib/thinking_sphinx/facet_spec.rb +302 -0
  47. data/spec/{unit → lib}/thinking_sphinx/field_spec.rb +26 -17
  48. data/spec/lib/thinking_sphinx/index/builder_spec.rb +355 -0
  49. data/spec/{unit → lib}/thinking_sphinx/index/faux_column_spec.rb +0 -0
  50. data/spec/{unit → lib}/thinking_sphinx/index_spec.rb +3 -12
  51. data/spec/lib/thinking_sphinx/rails_additions_spec.rb +191 -0
  52. data/spec/lib/thinking_sphinx/search_methods_spec.rb +152 -0
  53. data/spec/lib/thinking_sphinx/search_spec.rb +887 -0
  54. data/spec/lib/thinking_sphinx/source_spec.rb +217 -0
  55. data/spec/{unit → lib}/thinking_sphinx_spec.rb +30 -8
  56. data/tasks/distribution.rb +20 -1
  57. data/tasks/testing.rb +7 -15
  58. data/vendor/after_commit/init.rb +3 -0
  59. data/vendor/after_commit/lib/after_commit/active_record.rb +27 -4
  60. data/vendor/after_commit/lib/after_commit/connection_adapters.rb +1 -1
  61. data/vendor/after_commit/lib/after_commit.rb +4 -1
  62. data/vendor/riddle/lib/riddle/client/message.rb +4 -3
  63. data/vendor/riddle/lib/riddle/client.rb +3 -0
  64. data/vendor/riddle/lib/riddle/configuration/section.rb +8 -2
  65. data/vendor/riddle/lib/riddle/controller.rb +1 -1
  66. data/vendor/riddle/lib/riddle.rb +1 -1
  67. metadata +75 -39
  68. data/README +0 -107
  69. data/lib/thinking_sphinx/active_record/search.rb +0 -57
  70. data/lib/thinking_sphinx/collection.rb +0 -142
  71. data/lib/thinking_sphinx/facet_collection.rb +0 -44
  72. data/spec/unit/thinking_sphinx/active_record/search_spec.rb +0 -107
  73. data/spec/unit/thinking_sphinx/attribute_spec.rb +0 -212
  74. data/spec/unit/thinking_sphinx/collection_spec.rb +0 -14
  75. data/spec/unit/thinking_sphinx/index/builder_spec.rb +0 -5
  76. data/spec/unit/thinking_sphinx/search_spec.rb +0 -59
@@ -0,0 +1,465 @@
1
+ require 'spec/spec_helper'
2
+
3
+ describe ThinkingSphinx::Attribute do
4
+ before :each do
5
+ @index = ThinkingSphinx::Index.new(Person)
6
+ @source = ThinkingSphinx::Source.new(@index)
7
+
8
+ @index.delta_object = ThinkingSphinx::Deltas::DefaultDelta.new @index, @index.local_options
9
+ end
10
+
11
+ describe '#initialize' do
12
+ it 'raises if no columns are provided so that configuration errors are easier to track down' do
13
+ lambda {
14
+ ThinkingSphinx::Attribute.new(@source, [])
15
+ }.should raise_error(RuntimeError)
16
+ end
17
+
18
+ it 'raises if an element of the columns param is an integer - as happens when you use id instead of :id - so that configuration errors are easier to track down' do
19
+ lambda {
20
+ ThinkingSphinx::Attribute.new(@source, [1234])
21
+ }.should raise_error(RuntimeError)
22
+ end
23
+ end
24
+
25
+ describe "unique_name method" do
26
+ before :each do
27
+ @attribute = ThinkingSphinx::Attribute.new @source, [
28
+ Object.stub_instance(:__stack => [], :__name => "col_name")
29
+ ]
30
+ end
31
+
32
+ it "should use the alias if there is one" do
33
+ @attribute.alias = "alias"
34
+ @attribute.unique_name.should == "alias"
35
+ end
36
+
37
+ it "should use the alias if there's multiple columns" do
38
+ @attribute.columns << Object.stub_instance(:__stack => [], :__name => "col_name")
39
+ @attribute.unique_name.should be_nil
40
+
41
+ @attribute.alias = "alias"
42
+ @attribute.unique_name.should == "alias"
43
+ end
44
+
45
+ it "should use the column name if there's no alias and just one column" do
46
+ @attribute.unique_name.should == "col_name"
47
+ end
48
+ end
49
+
50
+ describe "column_with_prefix method" do
51
+ before :each do
52
+ @attribute = ThinkingSphinx::Attribute.new @source, [
53
+ ThinkingSphinx::Index::FauxColumn.new(:col_name)
54
+ ]
55
+ @attribute.columns.each { |col| @attribute.associations[col] = [] }
56
+ @attribute.model = Person
57
+
58
+ @first_join = Object.new
59
+ @first_join.stub!(:aliased_table_name => "tabular")
60
+ @second_join = Object.new
61
+ @second_join.stub!(:aliased_table_name => "data")
62
+
63
+ @first_assoc = ThinkingSphinx::Association.new nil, nil
64
+ @first_assoc.stub!(:join => @first_join, :has_column? => true)
65
+ @second_assoc = ThinkingSphinx::Association.new nil, nil
66
+ @second_assoc.stub!(:join => @second_join, :has_column? => true)
67
+ end
68
+
69
+ it "should return the column name if the column is a string" do
70
+ @attribute.columns = [ThinkingSphinx::Index::FauxColumn.new("string")]
71
+ @attribute.send(:column_with_prefix, @attribute.columns.first).should == "string"
72
+ end
73
+
74
+ it "should return the column with model's table prefix if there's no associations for the column" do
75
+ @attribute.send(:column_with_prefix, @attribute.columns.first).should == "`people`.`col_name`"
76
+ end
77
+
78
+ it "should return the column with its join table prefix if an association exists" do
79
+ column = @attribute.columns.first
80
+ @attribute.associations[column] = [@first_assoc]
81
+ @attribute.send(:column_with_prefix, column).should == "`tabular`.`col_name`"
82
+ end
83
+
84
+ it "should return multiple columns concatenated if more than one association exists" do
85
+ column = @attribute.columns.first
86
+ @attribute.associations[column] = [@first_assoc, @second_assoc]
87
+ @attribute.send(:column_with_prefix, column).should == "`tabular`.`col_name`, `data`.`col_name`"
88
+ end
89
+ end
90
+
91
+ describe '#to_select_sql' do
92
+ it "should convert a mixture of dates and datetimes to timestamps" do
93
+ attribute = ThinkingSphinx::Attribute.new(@source,
94
+ [ ThinkingSphinx::Index::FauxColumn.new(:created_at),
95
+ ThinkingSphinx::Index::FauxColumn.new(:created_on) ],
96
+ :as => :times
97
+ )
98
+ attribute.model = Friendship
99
+
100
+ attribute.to_select_sql.should == "CONCAT_WS(',', UNIX_TIMESTAMP(`friendships`.`created_at`), UNIX_TIMESTAMP(`friendships`.`created_on`)) AS `times`"
101
+ end
102
+ end
103
+
104
+ describe "is_many? method" do
105
+ before :each do
106
+ @assoc_a = Object.stub_instance(:is_many? => true)
107
+ @assoc_b = Object.stub_instance(:is_many? => true)
108
+ @assoc_c = Object.stub_instance(:is_many? => true)
109
+
110
+ @attribute = ThinkingSphinx::Attribute.new(
111
+ @source, [ThinkingSphinx::Index::FauxColumn.new(:col_name)]
112
+ )
113
+ @attribute.associations = {
114
+ :a => @assoc_a, :b => @assoc_b, :c => @assoc_c
115
+ }
116
+ end
117
+
118
+ it "should return true if all associations return true to is_many?" do
119
+ @attribute.send(:is_many?).should be_true
120
+ end
121
+
122
+ it "should return true if one association returns true to is_many?" do
123
+ @assoc_b.stub_method(:is_many? => false)
124
+ @assoc_c.stub_method(:is_many? => false)
125
+
126
+ @attribute.send(:is_many?).should be_true
127
+ end
128
+
129
+ it "should return false if all associations return false to is_many?" do
130
+ @assoc_a.stub_method(:is_many? => false)
131
+ @assoc_b.stub_method(:is_many? => false)
132
+ @assoc_c.stub_method(:is_many? => false)
133
+
134
+ @attribute.send(:is_many?).should be_false
135
+ end
136
+ end
137
+
138
+ describe "is_string? method" do
139
+ before :each do
140
+ @col_a = ThinkingSphinx::Index::FauxColumn.new("a")
141
+ @col_b = ThinkingSphinx::Index::FauxColumn.new("b")
142
+ @col_c = ThinkingSphinx::Index::FauxColumn.new("c")
143
+
144
+ @attribute = ThinkingSphinx::Attribute.new(
145
+ @source, [@col_a, @col_b, @col_c]
146
+ )
147
+ end
148
+
149
+ it "should return true if all columns return true to is_string?" do
150
+ @attribute.send(:is_string?).should be_true
151
+ end
152
+
153
+ it "should return false if one column returns true to is_string?" do
154
+ @col_a.send(:instance_variable_set, :@name, :a)
155
+ @attribute.send(:is_string?).should be_false
156
+ end
157
+
158
+ it "should return false if all columns return false to is_string?" do
159
+ @col_a.send(:instance_variable_set, :@name, :a)
160
+ @col_b.send(:instance_variable_set, :@name, :b)
161
+ @col_c.send(:instance_variable_set, :@name, :c)
162
+ @attribute.send(:is_string?).should be_false
163
+ end
164
+ end
165
+
166
+ describe "type method" do
167
+ before :each do
168
+ @column = ThinkingSphinx::Index::FauxColumn.new(:col_name)
169
+ @attribute = ThinkingSphinx::Attribute.new(@source, [@column])
170
+ @attribute.model = Person
171
+ @attribute.stub_method(:is_many? => false)
172
+ end
173
+
174
+ it "should return :multi if is_many? is true" do
175
+ @attribute.stub_method(:is_many? => true)
176
+ @attribute.send(:type).should == :multi
177
+ end
178
+
179
+ it "should return :string if there's more than one association" do
180
+ @attribute.associations = {:a => [:assoc], :b => [:assoc]}
181
+ @attribute.send(:type).should == :string
182
+ end
183
+
184
+ it "should return the column type from the database if not :multi or more than one association" do
185
+ @column.send(:instance_variable_set, :@name, "birthday")
186
+ @attribute.send(:type).should == :datetime
187
+
188
+ @attribute.send(:instance_variable_set, :@type, nil)
189
+ @column.send(:instance_variable_set, :@name, "first_name")
190
+ @attribute.send(:type).should == :string
191
+
192
+ @attribute.send(:instance_variable_set, :@type, nil)
193
+ @column.send(:instance_variable_set, :@name, "id")
194
+ @attribute.send(:type).should == :integer
195
+ end
196
+ end
197
+
198
+ describe "all_ints? method" do
199
+ it "should return true if all columns are integers" do
200
+ attribute = ThinkingSphinx::Attribute.new(@source,
201
+ [ ThinkingSphinx::Index::FauxColumn.new(:id),
202
+ ThinkingSphinx::Index::FauxColumn.new(:team_id) ]
203
+ )
204
+ attribute.model = Person
205
+ attribute.columns.each { |col| attribute.associations[col] = [] }
206
+
207
+ attribute.should be_all_ints
208
+ end
209
+
210
+ it "should return false if only some columns are integers" do
211
+ attribute = ThinkingSphinx::Attribute.new(@source,
212
+ [ ThinkingSphinx::Index::FauxColumn.new(:id),
213
+ ThinkingSphinx::Index::FauxColumn.new(:first_name) ]
214
+ )
215
+ attribute.model = Person
216
+ attribute.columns.each { |col| attribute.associations[col] = [] }
217
+
218
+ attribute.should_not be_all_ints
219
+ end
220
+
221
+ it "should return false if no columns are integers" do
222
+ attribute = ThinkingSphinx::Attribute.new(@source,
223
+ [ ThinkingSphinx::Index::FauxColumn.new(:first_name),
224
+ ThinkingSphinx::Index::FauxColumn.new(:last_name) ]
225
+ )
226
+ attribute.model = Person
227
+ attribute.columns.each { |col| attribute.associations[col] = [] }
228
+
229
+ attribute.should_not be_all_ints
230
+ end
231
+ end
232
+
233
+ describe "all_datetimes? method" do
234
+ it "should return true if all columns are datetimes" do
235
+ attribute = ThinkingSphinx::Attribute.new(@source,
236
+ [ ThinkingSphinx::Index::FauxColumn.new(:created_at),
237
+ ThinkingSphinx::Index::FauxColumn.new(:updated_at) ]
238
+ )
239
+ attribute.model = Friendship
240
+ attribute.columns.each { |col| attribute.associations[col] = [] }
241
+
242
+ attribute.should be_all_datetimes
243
+ end
244
+
245
+ it "should return false if only some columns are datetimes" do
246
+ attribute = ThinkingSphinx::Attribute.new(@source,
247
+ [ ThinkingSphinx::Index::FauxColumn.new(:id),
248
+ ThinkingSphinx::Index::FauxColumn.new(:created_at) ]
249
+ )
250
+ attribute.model = Friendship
251
+ attribute.columns.each { |col| attribute.associations[col] = [] }
252
+
253
+ attribute.should_not be_all_datetimes
254
+ end
255
+
256
+ it "should return true if all columns can be " do
257
+ attribute = ThinkingSphinx::Attribute.new(@source,
258
+ [ ThinkingSphinx::Index::FauxColumn.new(:created_at),
259
+ ThinkingSphinx::Index::FauxColumn.new(:created_on) ]
260
+ )
261
+ attribute.model = Friendship
262
+ attribute.columns.each { |col| attribute.associations[col] = [] }
263
+
264
+ attribute.should be_all_datetimes
265
+ end
266
+ end
267
+
268
+ describe "MVA with source query" do
269
+ before :each do
270
+ @attribute = ThinkingSphinx::Attribute.new(@source,
271
+ [ThinkingSphinx::Index::FauxColumn.new(:tags, :id)],
272
+ :as => :tag_ids, :source => :query
273
+ )
274
+ end
275
+
276
+ it "should use a query" do
277
+ @attribute.type_to_config.should == :sql_attr_multi
278
+
279
+ declaration, query = @attribute.config_value.split('; ')
280
+ declaration.should == "uint tag_ids from query"
281
+ query.should == "SELECT `tags`.`person_id` #{ThinkingSphinx.unique_id_expression} AS `id`, `tags`.`id` AS `tag_ids` FROM `tags`"
282
+ end
283
+ end
284
+
285
+ describe "MVA with source query for a delta source" do
286
+ before :each do
287
+ @attribute = ThinkingSphinx::Attribute.new(@source,
288
+ [ThinkingSphinx::Index::FauxColumn.new(:tags, :id)],
289
+ :as => :tag_ids, :source => :query
290
+ )
291
+ end
292
+
293
+ it "should use a query" do
294
+ @attribute.type_to_config.should == :sql_attr_multi
295
+
296
+ declaration, query = @attribute.config_value(nil, true).split('; ')
297
+ declaration.should == "uint tag_ids from query"
298
+ query.should == "SELECT `tags`.`person_id` #{ThinkingSphinx.unique_id_expression} AS `id`, `tags`.`id` AS `tag_ids` FROM `tags` WHERE `tags`.`person_id` IN (SELECT `id` FROM `people` WHERE `people`.`delta` = 1)"
299
+ end
300
+ end
301
+
302
+ describe "MVA via a HABTM association with a source query" do
303
+ before :each do
304
+ @attribute = ThinkingSphinx::Attribute.new(@source,
305
+ [ThinkingSphinx::Index::FauxColumn.new(:links, :id)],
306
+ :as => :link_ids, :source => :query
307
+ )
308
+ end
309
+
310
+ it "should use a ranged query" do
311
+ @attribute.type_to_config.should == :sql_attr_multi
312
+
313
+ declaration, query = @attribute.config_value.split('; ')
314
+ declaration.should == "uint link_ids from query"
315
+ query.should == "SELECT `links_people`.`person_id` #{ThinkingSphinx.unique_id_expression} AS `id`, `links_people`.`link_id` AS `link_ids` FROM `links_people`"
316
+ end
317
+ end
318
+
319
+ describe "MVA with ranged source query" do
320
+ before :each do
321
+ @attribute = ThinkingSphinx::Attribute.new(@source,
322
+ [ThinkingSphinx::Index::FauxColumn.new(:tags, :id)],
323
+ :as => :tag_ids, :source => :ranged_query
324
+ )
325
+ end
326
+
327
+ it "should use a ranged query" do
328
+ @attribute.type_to_config.should == :sql_attr_multi
329
+
330
+ declaration, query, range_query = @attribute.config_value.split('; ')
331
+ declaration.should == "uint tag_ids from ranged-query"
332
+ query.should == "SELECT `tags`.`person_id` #{ThinkingSphinx.unique_id_expression} AS `id`, `tags`.`id` AS `tag_ids` FROM `tags` WHERE `tags`.`person_id` >= $start AND `tags`.`person_id` <= $end"
333
+ range_query.should == "SELECT MIN(`tags`.`person_id`), MAX(`tags`.`person_id`) FROM `tags`"
334
+ end
335
+ end
336
+
337
+ describe "MVA with ranged source query for a delta source" do
338
+ before :each do
339
+ @attribute = ThinkingSphinx::Attribute.new(@source,
340
+ [ThinkingSphinx::Index::FauxColumn.new(:tags, :id)],
341
+ :as => :tag_ids, :source => :ranged_query
342
+ )
343
+ end
344
+
345
+ it "should use a ranged query" do
346
+ @attribute.type_to_config.should == :sql_attr_multi
347
+
348
+ declaration, query, range_query = @attribute.config_value(nil, true).split('; ')
349
+ declaration.should == "uint tag_ids from ranged-query"
350
+ query.should == "SELECT `tags`.`person_id` #{ThinkingSphinx.unique_id_expression} AS `id`, `tags`.`id` AS `tag_ids` FROM `tags` WHERE `tags`.`person_id` >= $start AND `tags`.`person_id` <= $end AND `tags`.`person_id` IN (SELECT `id` FROM `people` WHERE `people`.`delta` = 1)"
351
+ range_query.should == "SELECT MIN(`tags`.`person_id`), MAX(`tags`.`person_id`) FROM `tags`"
352
+ end
353
+ end
354
+
355
+ describe "MVA via a has-many :through with a ranged source query" do
356
+ before :each do
357
+ @attribute = ThinkingSphinx::Attribute.new(@source,
358
+ [ThinkingSphinx::Index::FauxColumn.new(:football_teams, :id)],
359
+ :as => :football_team_ids, :source => :ranged_query
360
+ )
361
+ end
362
+
363
+ it "should use a ranged query" do
364
+ @attribute.type_to_config.should == :sql_attr_multi
365
+
366
+ declaration, query, range_query = @attribute.config_value.split('; ')
367
+ declaration.should == "uint football_team_ids from ranged-query"
368
+ query.should == "SELECT `tags`.`person_id` #{ThinkingSphinx.unique_id_expression} AS `id`, `tags`.`football_team_id` AS `football_team_ids` FROM `tags` WHERE `tags`.`person_id` >= $start AND `tags`.`person_id` <= $end"
369
+ range_query.should == "SELECT MIN(`tags`.`person_id`), MAX(`tags`.`person_id`) FROM `tags`"
370
+ end
371
+ end
372
+
373
+ describe "MVA via a has-many :through using a foreign key with a ranged source query" do
374
+ before :each do
375
+ @attribute = ThinkingSphinx::Attribute.new(@source,
376
+ [ThinkingSphinx::Index::FauxColumn.new(:friends, :id)],
377
+ :as => :friend_ids, :source => :ranged_query
378
+ )
379
+ end
380
+
381
+ it "should use a ranged query" do
382
+ @attribute.type_to_config.should == :sql_attr_multi
383
+
384
+ declaration, query, range_query = @attribute.config_value.split('; ')
385
+ declaration.should == "uint friend_ids from ranged-query"
386
+ query.should == "SELECT `friendships`.`person_id` #{ThinkingSphinx.unique_id_expression} AS `id`, `friendships`.`friend_id` AS `friend_ids` FROM `friendships` WHERE `friendships`.`person_id` >= $start AND `friendships`.`person_id` <= $end"
387
+ range_query.should == "SELECT MIN(`friendships`.`person_id`), MAX(`friendships`.`person_id`) FROM `friendships`"
388
+ end
389
+ end
390
+
391
+ describe "MVA via a HABTM with a ranged source query" do
392
+ before :each do
393
+ @attribute = ThinkingSphinx::Attribute.new(@source,
394
+ [ThinkingSphinx::Index::FauxColumn.new(:links, :id)],
395
+ :as => :link_ids, :source => :ranged_query
396
+ )
397
+ end
398
+
399
+ it "should use a ranged query" do
400
+ @attribute.type_to_config.should == :sql_attr_multi
401
+
402
+ declaration, query, range_query = @attribute.config_value.split('; ')
403
+ declaration.should == "uint link_ids from ranged-query"
404
+ query.should == "SELECT `links_people`.`person_id` #{ThinkingSphinx.unique_id_expression} AS `id`, `links_people`.`link_id` AS `link_ids` FROM `links_people` WHERE `links_people`.`person_id` >= $start AND `links_people`.`person_id` <= $end"
405
+ range_query.should == "SELECT MIN(`links_people`.`person_id`), MAX(`links_people`.`person_id`) FROM `links_people`"
406
+ end
407
+ end
408
+
409
+ describe "MVA via two has-many associations with a ranged source query" do
410
+ before :each do
411
+ @index = ThinkingSphinx::Index.new(Alpha)
412
+ @source = ThinkingSphinx::Source.new(@index)
413
+ @attribute = ThinkingSphinx::Attribute.new(@source,
414
+ [ThinkingSphinx::Index::FauxColumn.new(:betas, :gammas, :value)],
415
+ :as => :gamma_values, :source => :ranged_query
416
+ )
417
+ end
418
+
419
+ it "should use a ranged query" do
420
+ @attribute.type_to_config.should == :sql_attr_multi
421
+
422
+ declaration, query, range_query = @attribute.config_value.split('; ')
423
+ declaration.should == "uint gamma_values from ranged-query"
424
+ query.should == "SELECT `betas`.`alpha_id` #{ThinkingSphinx.unique_id_expression} AS `id`, `gammas`.`value` AS `gamma_values` FROM `betas` LEFT OUTER JOIN `gammas` ON gammas.beta_id = betas.id WHERE `betas`.`alpha_id` >= $start AND `betas`.`alpha_id` <= $end"
425
+ range_query.should == "SELECT MIN(`betas`.`alpha_id`), MAX(`betas`.`alpha_id`) FROM `betas`"
426
+ end
427
+ end
428
+
429
+ describe "MVA via two has-many associations with a ranged source query for a delta source" do
430
+ before :each do
431
+ @index = ThinkingSphinx::Index.new(Alpha)
432
+ @source = ThinkingSphinx::Source.new(@index)
433
+ @attribute = ThinkingSphinx::Attribute.new(@source,
434
+ [ThinkingSphinx::Index::FauxColumn.new(:betas, :gammas, :value)],
435
+ :as => :gamma_values, :source => :ranged_query
436
+ )
437
+
438
+ @index.delta_object = ThinkingSphinx::Deltas::DefaultDelta.new @index, @index.local_options
439
+ end
440
+
441
+ it "should use a ranged query" do
442
+ @attribute.type_to_config.should == :sql_attr_multi
443
+
444
+ declaration, query, range_query = @attribute.config_value(nil, true).split('; ')
445
+ declaration.should == "uint gamma_values from ranged-query"
446
+ query.should == "SELECT `betas`.`alpha_id` #{ThinkingSphinx.unique_id_expression} AS `id`, `gammas`.`value` AS `gamma_values` FROM `betas` LEFT OUTER JOIN `gammas` ON gammas.beta_id = betas.id WHERE `betas`.`alpha_id` >= $start AND `betas`.`alpha_id` <= $end AND `betas`.`alpha_id` IN (SELECT `id` FROM `alphas` WHERE `alphas`.`delta` = 1)"
447
+ range_query.should == "SELECT MIN(`betas`.`alpha_id`), MAX(`betas`.`alpha_id`) FROM `betas`"
448
+ end
449
+ end
450
+
451
+ describe "with custom queries" do
452
+ before :each do
453
+ index = CricketTeam.sphinx_indexes.first
454
+ @statement = index.sources.first.to_riddle_for_core(0, 0).sql_attr_multi.last
455
+ end
456
+
457
+ it "should track the query type accordingly" do
458
+ @statement.should match(/uint tags from query/)
459
+ end
460
+
461
+ it "should include the SQL statement" do
462
+ @statement.should match(/SELECT cricket_team_id, id FROM tags/)
463
+ end
464
+ end
465
+ end
@@ -10,11 +10,11 @@ describe ThinkingSphinx::Configuration do
10
10
 
11
11
  it "should use the Merb environment value if set" do
12
12
  unless defined?(Merb)
13
- module Merb; end
13
+ module ::Merb; end
14
14
  end
15
-
15
+
16
16
  ThinkingSphinx::Configuration.stub_method(:defined? => true)
17
- Merb.stub_method(:environment => "merb_production")
17
+ Merb.stub!(:environment => "merb_production")
18
18
  ThinkingSphinx::Configuration.environment.should == "merb_production"
19
19
 
20
20
  Object.send(:remove_const, :Merb)
@@ -48,7 +48,9 @@ describe ThinkingSphinx::Configuration do
48
48
  "morphology" => "stem_ru",
49
49
  "charset_type" => "latin1",
50
50
  "charset_table" => "table",
51
- "ignore_chars" => "e"
51
+ "ignore_chars" => "e",
52
+ "searchd_binary_name" => "sphinx-searchd",
53
+ "indexer_binary_name" => "sphinx-indexer"
52
54
  }
53
55
  }
54
56
 
@@ -62,7 +64,7 @@ describe ThinkingSphinx::Configuration do
62
64
  config.send(:parse_config)
63
65
 
64
66
  %w(config_file searchd_log_file query_log_file pid_file searchd_file_path
65
- address port).each do |key|
67
+ address port searchd_binary_name indexer_binary_name).each do |key|
66
68
  config.send(key).should == @settings["development"][key]
67
69
  end
68
70
  end
@@ -71,7 +73,16 @@ describe ThinkingSphinx::Configuration do
71
73
  FileUtils.rm "#{RAILS_ROOT}/config/sphinx.yml"
72
74
  end
73
75
  end
74
-
76
+
77
+ describe "block configuration" do
78
+ it "should let the user set-up a custom app_root" do
79
+ ThinkingSphinx::Configuration.configure do |config|
80
+ config.app_root = "/here/somewhere"
81
+ end
82
+ ThinkingSphinx::Configuration.instance.app_root.should == "/here/somewhere"
83
+ end
84
+ end
85
+
75
86
  describe "initialisation" do
76
87
  it "should have a default bin_path of nothing" do
77
88
  ThinkingSphinx::Configuration.instance.bin_path.should == ""
@@ -93,6 +104,81 @@ describe ThinkingSphinx::Configuration do
93
104
  end
94
105
  end
95
106
 
107
+ describe "index options" do
108
+ before :each do
109
+ @settings = {
110
+ "development" => {"disable_range" => true}
111
+ }
112
+
113
+ open("#{RAILS_ROOT}/config/sphinx.yml", "w") do |f|
114
+ f.write YAML.dump(@settings)
115
+ end
116
+
117
+ @config = ThinkingSphinx::Configuration.instance
118
+ @config.send(:parse_config)
119
+ end
120
+
121
+ it "should collect disable_range" do
122
+ @config.index_options[:disable_range].should be_true
123
+ end
124
+ end
125
+
126
+ describe "#load_models" do
127
+ before :each do
128
+ @config = ThinkingSphinx::Configuration.instance
129
+ @config.model_directories = ['']
130
+
131
+ @file_name = 'a.rb'
132
+ @model_name_lower = 'a'
133
+ @class_name = 'A'
134
+
135
+ @file_name.stub!(:gsub).and_return(@model_name_lower)
136
+ @model_name_lower.stub!(:camelize).and_return(@class_name)
137
+ Dir.stub(:[]).and_return([@file_name])
138
+ end
139
+
140
+ it "should load the files by guessing the file name" do
141
+ @class_name.should_receive(:constantize).and_return(true)
142
+
143
+ @config.load_models
144
+ end
145
+
146
+ it "should not raise errors if the model name is nil" do
147
+ @file_name.stub!(:gsub).and_return(nil)
148
+
149
+ lambda {
150
+ @config.load_models
151
+ }.should_not raise_error
152
+ end
153
+
154
+ it "should not raise errors if the file name does not represent a class name" do
155
+ @class_name.should_receive(:constantize).and_raise(NameError)
156
+
157
+ lambda {
158
+ @config.load_models
159
+ }.should_not raise_error
160
+ end
161
+
162
+ it "should retry if the first pass fails and contains a directory" do
163
+ @model_name_lower.stub!(:gsub!).and_return(true, nil)
164
+ @class_name.stub(:constantize).and_raise(LoadError)
165
+ @model_name_lower.should_receive(:camelize).twice
166
+
167
+ lambda {
168
+ @config.load_models
169
+ }.should_not raise_error
170
+ end
171
+
172
+ it "should catch database errors with a warning" do
173
+ @class_name.should_receive(:constantize).and_raise(Mysql::Error)
174
+ @config.should_receive(:puts).with('Warning: Error loading a.rb')
175
+
176
+ lambda {
177
+ @config.load_models
178
+ }.should_not raise_error
179
+ end
180
+ end
181
+
96
182
  it "should insert set index options into the configuration file" do
97
183
  config = ThinkingSphinx::Configuration.instance
98
184
  ThinkingSphinx::Configuration::IndexOptions.each do |option|
@@ -133,4 +219,29 @@ describe ThinkingSphinx::Configuration do
133
219
  }
134
220
  file.should_not match(/index alpha_core\s+\{\s+[^\}]*prefix_fields\s+=[^\}]*\}/m)
135
221
  end
136
- end
222
+
223
+ describe '#client' do
224
+ before :each do
225
+ @config = ThinkingSphinx::Configuration.instance
226
+ @config.address = 'domain.url'
227
+ @config.port = 3333
228
+ @config.configuration.searchd.max_matches = 100
229
+ end
230
+
231
+ it "should return an instance of Riddle::Client" do
232
+ @config.client.should be_a(Riddle::Client)
233
+ end
234
+
235
+ it "should use the configuration address" do
236
+ @config.client.server.should == 'domain.url'
237
+ end
238
+
239
+ it "should use the configuration port" do
240
+ @config.client.port.should == 3333
241
+ end
242
+
243
+ it "should use the configuration max matches" do
244
+ @config.client.max_matches.should == 100
245
+ end
246
+ end
247
+ end
@@ -0,0 +1,49 @@
1
+ require 'spec/spec_helper'
2
+
3
+ describe ThinkingSphinx::Excerpter do
4
+ before :each do
5
+ @alpha = Alpha.find(:first)
6
+ @search = mock 'search', :excerpt_for => 'excerpted value'
7
+ @excerpter = ThinkingSphinx::Excerpter.new(@search, @alpha)
8
+ end
9
+
10
+ it "should not respond to id" do
11
+ @excerpter.should_not respond_to(:id)
12
+ end
13
+
14
+ describe '#method_missing' do
15
+ it "should return the excerpt from Sphinx" do
16
+ @excerpter.name.should == 'excerpted value'
17
+ end
18
+
19
+ it "should send through the instance class to excerpt_for" do
20
+ @search.should_receive(:excerpt_for) do |string, model|
21
+ model.should == Alpha
22
+ end
23
+
24
+ @excerpter.name
25
+ end
26
+
27
+ it "should use attribute methods for excerpts calls" do
28
+ @search.should_receive(:excerpt_for) do |string, model|
29
+ string.should == 'one'
30
+ end
31
+
32
+ @excerpter.name
33
+ end
34
+
35
+ it "should use instance methods for excerpts calls" do
36
+ @search.should_receive(:excerpt_for) do |string, model|
37
+ string.should == 'ONE'
38
+ end
39
+
40
+ @excerpter.big_name
41
+ end
42
+
43
+ it "should still raise an exception if no column or method exists" do
44
+ lambda {
45
+ @excerpter.foo
46
+ }.should raise_error(NoMethodError)
47
+ end
48
+ end
49
+ end