friendlyfashion-thinking-sphinx 2.0.13

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 (175) hide show
  1. data/HISTORY +244 -0
  2. data/LICENCE +20 -0
  3. data/README.textile +235 -0
  4. data/features/abstract_inheritance.feature +10 -0
  5. data/features/alternate_primary_key.feature +27 -0
  6. data/features/attribute_transformation.feature +22 -0
  7. data/features/attribute_updates.feature +77 -0
  8. data/features/deleting_instances.feature +67 -0
  9. data/features/direct_attributes.feature +11 -0
  10. data/features/excerpts.feature +21 -0
  11. data/features/extensible_delta_indexing.feature +9 -0
  12. data/features/facets.feature +88 -0
  13. data/features/facets_across_model.feature +29 -0
  14. data/features/field_sorting.feature +18 -0
  15. data/features/handling_edits.feature +94 -0
  16. data/features/retry_stale_indexes.feature +24 -0
  17. data/features/searching_across_models.feature +20 -0
  18. data/features/searching_by_index.feature +40 -0
  19. data/features/searching_by_model.feature +175 -0
  20. data/features/searching_with_find_arguments.feature +56 -0
  21. data/features/sphinx_detection.feature +25 -0
  22. data/features/sphinx_scopes.feature +68 -0
  23. data/features/step_definitions/alpha_steps.rb +16 -0
  24. data/features/step_definitions/beta_steps.rb +7 -0
  25. data/features/step_definitions/common_steps.rb +201 -0
  26. data/features/step_definitions/extensible_delta_indexing_steps.rb +7 -0
  27. data/features/step_definitions/facet_steps.rb +96 -0
  28. data/features/step_definitions/find_arguments_steps.rb +36 -0
  29. data/features/step_definitions/gamma_steps.rb +15 -0
  30. data/features/step_definitions/scope_steps.rb +19 -0
  31. data/features/step_definitions/search_steps.rb +94 -0
  32. data/features/step_definitions/sphinx_steps.rb +35 -0
  33. data/features/sti_searching.feature +19 -0
  34. data/features/support/env.rb +27 -0
  35. data/features/support/lib/generic_delta_handler.rb +8 -0
  36. data/features/thinking_sphinx/database.example.yml +3 -0
  37. data/features/thinking_sphinx/db/.gitignore +1 -0
  38. data/features/thinking_sphinx/db/fixtures/alphas.rb +8 -0
  39. data/features/thinking_sphinx/db/fixtures/authors.rb +1 -0
  40. data/features/thinking_sphinx/db/fixtures/betas.rb +11 -0
  41. data/features/thinking_sphinx/db/fixtures/boxes.rb +9 -0
  42. data/features/thinking_sphinx/db/fixtures/categories.rb +1 -0
  43. data/features/thinking_sphinx/db/fixtures/cats.rb +3 -0
  44. data/features/thinking_sphinx/db/fixtures/comments.rb +24 -0
  45. data/features/thinking_sphinx/db/fixtures/developers.rb +31 -0
  46. data/features/thinking_sphinx/db/fixtures/dogs.rb +3 -0
  47. data/features/thinking_sphinx/db/fixtures/extensible_betas.rb +10 -0
  48. data/features/thinking_sphinx/db/fixtures/foxes.rb +3 -0
  49. data/features/thinking_sphinx/db/fixtures/gammas.rb +10 -0
  50. data/features/thinking_sphinx/db/fixtures/music.rb +4 -0
  51. data/features/thinking_sphinx/db/fixtures/people.rb +1001 -0
  52. data/features/thinking_sphinx/db/fixtures/post_keywords.txt +1 -0
  53. data/features/thinking_sphinx/db/fixtures/posts.rb +10 -0
  54. data/features/thinking_sphinx/db/fixtures/robots.rb +8 -0
  55. data/features/thinking_sphinx/db/fixtures/tags.rb +27 -0
  56. data/features/thinking_sphinx/db/migrations/create_alphas.rb +8 -0
  57. data/features/thinking_sphinx/db/migrations/create_animals.rb +5 -0
  58. data/features/thinking_sphinx/db/migrations/create_authors.rb +3 -0
  59. data/features/thinking_sphinx/db/migrations/create_authors_posts.rb +6 -0
  60. data/features/thinking_sphinx/db/migrations/create_betas.rb +5 -0
  61. data/features/thinking_sphinx/db/migrations/create_boxes.rb +5 -0
  62. data/features/thinking_sphinx/db/migrations/create_categories.rb +3 -0
  63. data/features/thinking_sphinx/db/migrations/create_comments.rb +10 -0
  64. data/features/thinking_sphinx/db/migrations/create_developers.rb +7 -0
  65. data/features/thinking_sphinx/db/migrations/create_extensible_betas.rb +5 -0
  66. data/features/thinking_sphinx/db/migrations/create_gammas.rb +3 -0
  67. data/features/thinking_sphinx/db/migrations/create_genres.rb +3 -0
  68. data/features/thinking_sphinx/db/migrations/create_music.rb +6 -0
  69. data/features/thinking_sphinx/db/migrations/create_people.rb +13 -0
  70. data/features/thinking_sphinx/db/migrations/create_posts.rb +6 -0
  71. data/features/thinking_sphinx/db/migrations/create_robots.rb +4 -0
  72. data/features/thinking_sphinx/db/migrations/create_taggings.rb +5 -0
  73. data/features/thinking_sphinx/db/migrations/create_tags.rb +4 -0
  74. data/features/thinking_sphinx/models/alpha.rb +23 -0
  75. data/features/thinking_sphinx/models/andrew.rb +17 -0
  76. data/features/thinking_sphinx/models/animal.rb +5 -0
  77. data/features/thinking_sphinx/models/author.rb +3 -0
  78. data/features/thinking_sphinx/models/beta.rb +13 -0
  79. data/features/thinking_sphinx/models/box.rb +8 -0
  80. data/features/thinking_sphinx/models/cat.rb +3 -0
  81. data/features/thinking_sphinx/models/category.rb +4 -0
  82. data/features/thinking_sphinx/models/comment.rb +10 -0
  83. data/features/thinking_sphinx/models/developer.rb +21 -0
  84. data/features/thinking_sphinx/models/dog.rb +3 -0
  85. data/features/thinking_sphinx/models/extensible_beta.rb +9 -0
  86. data/features/thinking_sphinx/models/fox.rb +5 -0
  87. data/features/thinking_sphinx/models/gamma.rb +5 -0
  88. data/features/thinking_sphinx/models/genre.rb +3 -0
  89. data/features/thinking_sphinx/models/medium.rb +5 -0
  90. data/features/thinking_sphinx/models/music.rb +10 -0
  91. data/features/thinking_sphinx/models/person.rb +24 -0
  92. data/features/thinking_sphinx/models/post.rb +22 -0
  93. data/features/thinking_sphinx/models/robot.rb +12 -0
  94. data/features/thinking_sphinx/models/tag.rb +3 -0
  95. data/features/thinking_sphinx/models/tagging.rb +4 -0
  96. data/lib/cucumber/thinking_sphinx/external_world.rb +12 -0
  97. data/lib/cucumber/thinking_sphinx/internal_world.rb +137 -0
  98. data/lib/cucumber/thinking_sphinx/sql_logger.rb +28 -0
  99. data/lib/thinking-sphinx.rb +1 -0
  100. data/lib/thinking_sphinx/action_controller.rb +31 -0
  101. data/lib/thinking_sphinx/active_record/attribute_updates.rb +53 -0
  102. data/lib/thinking_sphinx/active_record/collection_proxy.rb +47 -0
  103. data/lib/thinking_sphinx/active_record/collection_proxy_with_scopes.rb +27 -0
  104. data/lib/thinking_sphinx/active_record/delta.rb +67 -0
  105. data/lib/thinking_sphinx/active_record/has_many_association.rb +44 -0
  106. data/lib/thinking_sphinx/active_record/has_many_association_with_scopes.rb +21 -0
  107. data/lib/thinking_sphinx/active_record/log_subscriber.rb +61 -0
  108. data/lib/thinking_sphinx/active_record/scopes.rb +110 -0
  109. data/lib/thinking_sphinx/active_record.rb +386 -0
  110. data/lib/thinking_sphinx/adapters/abstract_adapter.rb +87 -0
  111. data/lib/thinking_sphinx/adapters/mysql_adapter.rb +62 -0
  112. data/lib/thinking_sphinx/adapters/postgresql_adapter.rb +188 -0
  113. data/lib/thinking_sphinx/association.rb +230 -0
  114. data/lib/thinking_sphinx/attribute.rb +405 -0
  115. data/lib/thinking_sphinx/auto_version.rb +40 -0
  116. data/lib/thinking_sphinx/bundled_search.rb +44 -0
  117. data/lib/thinking_sphinx/class_facet.rb +20 -0
  118. data/lib/thinking_sphinx/configuration.rb +375 -0
  119. data/lib/thinking_sphinx/context.rb +76 -0
  120. data/lib/thinking_sphinx/core/string.rb +15 -0
  121. data/lib/thinking_sphinx/deltas/default_delta.rb +62 -0
  122. data/lib/thinking_sphinx/deltas.rb +28 -0
  123. data/lib/thinking_sphinx/deploy/capistrano.rb +99 -0
  124. data/lib/thinking_sphinx/excerpter.rb +23 -0
  125. data/lib/thinking_sphinx/facet.rb +135 -0
  126. data/lib/thinking_sphinx/facet_search.rb +170 -0
  127. data/lib/thinking_sphinx/field.rb +98 -0
  128. data/lib/thinking_sphinx/index/builder.rb +315 -0
  129. data/lib/thinking_sphinx/index/faux_column.rb +118 -0
  130. data/lib/thinking_sphinx/index.rb +159 -0
  131. data/lib/thinking_sphinx/join.rb +37 -0
  132. data/lib/thinking_sphinx/property.rb +187 -0
  133. data/lib/thinking_sphinx/railtie.rb +43 -0
  134. data/lib/thinking_sphinx/search.rb +1061 -0
  135. data/lib/thinking_sphinx/search_methods.rb +439 -0
  136. data/lib/thinking_sphinx/sinatra.rb +7 -0
  137. data/lib/thinking_sphinx/source/internal_properties.rb +51 -0
  138. data/lib/thinking_sphinx/source/sql.rb +174 -0
  139. data/lib/thinking_sphinx/source.rb +194 -0
  140. data/lib/thinking_sphinx/tasks.rb +142 -0
  141. data/lib/thinking_sphinx/test.rb +55 -0
  142. data/lib/thinking_sphinx/version.rb +3 -0
  143. data/lib/thinking_sphinx.rb +297 -0
  144. data/spec/fixtures/data.sql +32 -0
  145. data/spec/fixtures/database.yml.default +3 -0
  146. data/spec/fixtures/models.rb +164 -0
  147. data/spec/fixtures/structure.sql +146 -0
  148. data/spec/spec_helper.rb +61 -0
  149. data/spec/sphinx_helper.rb +60 -0
  150. data/spec/support/rails.rb +25 -0
  151. data/spec/thinking_sphinx/active_record/delta_spec.rb +122 -0
  152. data/spec/thinking_sphinx/active_record/has_many_association_spec.rb +173 -0
  153. data/spec/thinking_sphinx/active_record/scopes_spec.rb +176 -0
  154. data/spec/thinking_sphinx/active_record_spec.rb +573 -0
  155. data/spec/thinking_sphinx/adapters/abstract_adapter_spec.rb +145 -0
  156. data/spec/thinking_sphinx/association_spec.rb +250 -0
  157. data/spec/thinking_sphinx/attribute_spec.rb +552 -0
  158. data/spec/thinking_sphinx/auto_version_spec.rb +103 -0
  159. data/spec/thinking_sphinx/configuration_spec.rb +326 -0
  160. data/spec/thinking_sphinx/context_spec.rb +126 -0
  161. data/spec/thinking_sphinx/core/array_spec.rb +9 -0
  162. data/spec/thinking_sphinx/core/string_spec.rb +9 -0
  163. data/spec/thinking_sphinx/excerpter_spec.rb +49 -0
  164. data/spec/thinking_sphinx/facet_search_spec.rb +176 -0
  165. data/spec/thinking_sphinx/facet_spec.rb +359 -0
  166. data/spec/thinking_sphinx/field_spec.rb +127 -0
  167. data/spec/thinking_sphinx/index/builder_spec.rb +532 -0
  168. data/spec/thinking_sphinx/index/faux_column_spec.rb +36 -0
  169. data/spec/thinking_sphinx/index_spec.rb +189 -0
  170. data/spec/thinking_sphinx/search_methods_spec.rb +156 -0
  171. data/spec/thinking_sphinx/search_spec.rb +1455 -0
  172. data/spec/thinking_sphinx/source_spec.rb +267 -0
  173. data/spec/thinking_sphinx/test_spec.rb +20 -0
  174. data/spec/thinking_sphinx_spec.rb +204 -0
  175. metadata +524 -0
@@ -0,0 +1,326 @@
1
+ require 'spec_helper'
2
+
3
+ describe ThinkingSphinx::Configuration do
4
+ describe "environment class method" do
5
+ before :each do
6
+ ThinkingSphinx::Configuration.reset_environment
7
+
8
+ ENV["RAILS_ENV"] = nil
9
+ end
10
+
11
+ it "should use Rails.env if set" do
12
+ was = Rails.env
13
+ Rails.env = 'global_rails'
14
+
15
+ ThinkingSphinx::Configuration.environment.should == 'global_rails'
16
+
17
+ Rails.env = was
18
+ end
19
+
20
+ it "should use the Rails environment value if set" do
21
+ Rails.stub!(:env => "rails_production")
22
+ ThinkingSphinx::Configuration.environment.should == "rails_production"
23
+ end
24
+
25
+ it "should default to development" do
26
+ ThinkingSphinx::Configuration.environment.should == "development"
27
+ end
28
+ end
29
+
30
+ describe '#version' do
31
+ before :each do
32
+ @config = ThinkingSphinx::Configuration.instance
33
+ @config.reset
34
+ end
35
+
36
+ it "should use the given version from sphinx.yml if there is one" do
37
+ open("#{Rails.root}/config/sphinx.yml", "w") do |f|
38
+ f.write YAML.dump({'development' => {'version' => '0.9.7'}})
39
+ end
40
+ @config.reset
41
+
42
+ @config.version.should == '0.9.7'
43
+
44
+ FileUtils.rm "#{Rails.root}/config/sphinx.yml"
45
+ end
46
+
47
+ it "should detect the version from Riddle otherwise" do
48
+ controller = @config.controller
49
+ controller.stub!(:sphinx_version => '0.9.6')
50
+
51
+ Riddle::Controller.stub!(:new => controller)
52
+ @config.reset
53
+
54
+ @config.version.should == '0.9.6'
55
+ end
56
+ end
57
+
58
+ describe "parse_config method" do
59
+ before :each do
60
+ @settings = {
61
+ "development" => {
62
+ "config_file" => "tmp/config/development.sphinx.conf",
63
+ "searchd_log_file" => "searchd_log_file.log",
64
+ "query_log_file" => "query_log_file.log",
65
+ "pid_file" => "pid_file.pid",
66
+ "searchd_file_path" => "searchd/file/path",
67
+ "address" => "127.0.0.1",
68
+ "port" => 3333,
69
+ "min_prefix_len" => 2,
70
+ "min_infix_len" => 3,
71
+ "mem_limit" => "128M",
72
+ "max_matches" => 1001,
73
+ "morphology" => "stem_ru",
74
+ "charset_type" => "latin1",
75
+ "charset_table" => "table",
76
+ "ignore_chars" => "e",
77
+ "searchd_binary_name" => "sphinx-searchd",
78
+ "indexer_binary_name" => "sphinx-indexer",
79
+ "index_exact_words" => true,
80
+ "indexed_models" => ['Alpha', 'Beta']
81
+ }
82
+ }
83
+
84
+ open("#{Rails.root}/config/sphinx.yml", "w") do |f|
85
+ f.write YAML.dump(@settings)
86
+ end
87
+ end
88
+
89
+ it "should use the accessors to set the configuration values" do
90
+ config = ThinkingSphinx::Configuration.instance
91
+ config.send(:parse_config)
92
+
93
+ %w(config_file searchd_log_file query_log_file pid_file searchd_file_path
94
+ address port searchd_binary_name indexer_binary_name).each do |key|
95
+ config.send(key).should == @settings["development"][key]
96
+ end
97
+ end
98
+
99
+ after :each do
100
+ FileUtils.rm "#{Rails.root}/config/sphinx.yml"
101
+ end
102
+ end
103
+
104
+ describe "block configuration" do
105
+ it "should let the user set-up a custom app_root" do
106
+ ThinkingSphinx::Configuration.configure do |config|
107
+ config.app_root = "/here/somewhere"
108
+ end
109
+ ThinkingSphinx::Configuration.instance.app_root.should == "/here/somewhere"
110
+
111
+ ThinkingSphinx::Configuration.instance.reset
112
+ end
113
+ end
114
+
115
+ describe "initialisation" do
116
+ it "should have a default bin_path of nothing" do
117
+ ThinkingSphinx::Configuration.instance.bin_path.should == ""
118
+ end
119
+
120
+ it "should append a / to bin_path if one is supplied" do
121
+ @settings = {
122
+ "development" => {
123
+ "bin_path" => "path/to/somewhere"
124
+ }
125
+ }
126
+
127
+ open("#{Rails.root}/config/sphinx.yml", "w") do |f|
128
+ f.write YAML.dump(@settings)
129
+ end
130
+
131
+ ThinkingSphinx::Configuration.instance.send(:parse_config)
132
+ ThinkingSphinx::Configuration.instance.bin_path.should match(/\/$/)
133
+
134
+ FileUtils.rm "#{Rails.root}/config/sphinx.yml"
135
+ end
136
+ end
137
+
138
+ describe "index options" do
139
+ before :each do
140
+ @settings = {
141
+ "development" => {"disable_range" => true}
142
+ }
143
+
144
+ open("#{Rails.root}/config/sphinx.yml", "w") do |f|
145
+ f.write YAML.dump(@settings)
146
+ end
147
+
148
+ @config = ThinkingSphinx::Configuration.instance
149
+ @config.send(:parse_config)
150
+ end
151
+
152
+ it "should collect disable_range" do
153
+ @config.index_options[:disable_range].should be_true
154
+ end
155
+
156
+ after :each do
157
+ FileUtils.rm "#{Rails.root}/config/sphinx.yml"
158
+ end
159
+ end
160
+
161
+ it "should insert set index options into the configuration file" do
162
+ config = ThinkingSphinx::Configuration.instance
163
+
164
+ ThinkingSphinx::Configuration::IndexOptions.each do |option|
165
+ config.reset
166
+ config.index_options[option.to_sym] = "something"
167
+ config.build
168
+
169
+ file = open(config.config_file) { |f| f.read }
170
+ file.should match(/#{option}\s+= something/)
171
+
172
+ config.index_options[option.to_sym] = nil
173
+ end
174
+ end
175
+
176
+ it "should insert set source options into the configuration file" do
177
+ config = ThinkingSphinx::Configuration.instance
178
+ config.reset
179
+
180
+ config.source_options[:sql_query_pre] = ["something"]
181
+ ThinkingSphinx::Configuration::SourceOptions.each do |option|
182
+ config.source_options[option.to_sym] ||= "something"
183
+ config.build
184
+
185
+ file = open(config.config_file) { |f| f.read }
186
+ file.should match(/#{option}\s+= something/)
187
+
188
+ config.source_options.delete option.to_sym
189
+ end
190
+
191
+ config.source_options[:sql_query_pre] = []
192
+ end
193
+
194
+ it "should not blow away delta or utf options if sql pre is specified in config" do
195
+ config = ThinkingSphinx::Configuration.instance
196
+ config.reset
197
+
198
+ config.source_options[:sql_query_pre] = ["a pre query"]
199
+ config.build
200
+ file = open(config.config_file) { |f| f.read }
201
+
202
+ file.should match(/sql_query_pre = a pre query\n\s*sql_query_pre = UPDATE `\w+` SET `delta` = 0 WHERE `delta` = 1/im)
203
+ file.should match(/sql_query_pre = a pre query\n\s*sql_query_pre = \n/im)
204
+
205
+ config.source_options[:sql_query_pre] = []
206
+ end
207
+
208
+ it "should set any explicit prefixed or infixed fields" do
209
+ ThinkingSphinx::Configuration.instance.build
210
+
211
+ file = open(ThinkingSphinx::Configuration.instance.config_file) { |f|
212
+ f.read
213
+ }
214
+ file.should match(/prefix_fields\s+= city/)
215
+ file.should match(/infix_fields\s+= state/)
216
+ end
217
+
218
+ it "should not have prefix fields in indexes where nothing is set" do
219
+ ThinkingSphinx::Configuration.instance.build
220
+
221
+ file = open(ThinkingSphinx::Configuration.instance.config_file) { |f|
222
+ f.read
223
+ }
224
+ file.should_not match(/index alpha_core\s+\{\s+[^\}]*prefix_fields\s+=[^\}]*\}/m)
225
+ end
226
+
227
+ describe '#generate' do
228
+ let(:config) { ThinkingSphinx::Configuration.instance }
229
+
230
+ it "should set all sphinx_internal_id attributes to bigints if one is" do
231
+ config.reset
232
+ config.generate
233
+
234
+ config.configuration.indices.each do |index|
235
+ next if index.is_a? Riddle::Configuration::DistributedIndex
236
+
237
+ index.sources.each do |source|
238
+ source.sql_attr_bigint.should include(:sphinx_internal_id)
239
+ end
240
+ end
241
+ end
242
+ end
243
+
244
+ describe '#client' do
245
+ before :each do
246
+ @config = ThinkingSphinx::Configuration.instance
247
+ @config.address = 'domain.url'
248
+ @config.port = 3333
249
+ @config.configuration.searchd.max_matches = 100
250
+ @config.timeout = 1
251
+ end
252
+
253
+ it "should return an instance of Riddle::Client" do
254
+ @config.client.should be_a(Riddle::Client)
255
+ end
256
+
257
+ it "should use the configuration address" do
258
+ @config.client.server.should == 'domain.url'
259
+ end
260
+
261
+ it "should use the configuration port" do
262
+ @config.client.port.should == 3333
263
+ end
264
+
265
+ it "should use the configuration max matches" do
266
+ @config.client.max_matches.should == 100
267
+ end
268
+
269
+ it "should use the configuration timeout" do
270
+ @config.client.timeout.should == 1
271
+ end
272
+
273
+ describe 'when shuffle is enabled' do
274
+ let(:client) { double('client', :max_matches= => nil, :timeout= => nil) }
275
+
276
+ before :each do
277
+ @config.shuffle = true
278
+ end
279
+
280
+ it "should shuffle client servers" do
281
+ @config.address = ['1.1.1.1', '2.2.2.2']
282
+ @config.address.stub!(:shuffle => ['2.2.2.2', '1.1.1.1'])
283
+
284
+ Riddle::Client.should_receive(:new) do |addresses, port, key|
285
+ addresses.should == ['2.2.2.2', '1.1.1.1']
286
+ client
287
+ end
288
+ @config.client
289
+ end
290
+ end
291
+
292
+ describe 'when shuffle is disabled' do
293
+ let(:client) { double('client', :max_matches= => nil, :timeout= => nil) }
294
+
295
+ before :each do
296
+ @config.shuffle = false
297
+ end
298
+
299
+ it "should not shuffle client servers" do
300
+ @config.address = ['1.1.1.1', '2.2.2.2.', '3.3.3.3', '4.4.4.4', '5.5.5.5']
301
+
302
+ @config.address.should_not_receive(:shuffle)
303
+ Riddle::Client.should_receive(:new) do |addresses, port, key|
304
+ addresses.should == ['1.1.1.1', '2.2.2.2.', '3.3.3.3', '4.4.4.4', '5.5.5.5']
305
+ client
306
+ end
307
+ @config.client
308
+ end
309
+ end
310
+ end
311
+
312
+ describe '#models_by_crc' do
313
+ before :each do
314
+ @config = ThinkingSphinx::Configuration.instance
315
+ end
316
+
317
+ it "should return a hash" do
318
+ @config.models_by_crc.should be_a(Hash)
319
+ end
320
+
321
+ it "should pair class names to their crc codes" do
322
+ @config.models_by_crc[Person.to_crc32].should == 'Person'
323
+ @config.models_by_crc[Alpha.to_crc32].should == 'Alpha'
324
+ end
325
+ end
326
+ end
@@ -0,0 +1,126 @@
1
+ require 'spec_helper'
2
+
3
+ describe ThinkingSphinx::Context do
4
+ let(:ts_context) { ThinkingSphinx::Context.new }
5
+
6
+ describe '#prepare' do
7
+ let(:config) { ThinkingSphinx::Configuration.instance }
8
+ let(:file_name) { 'a.rb' }
9
+ let(:model_name_lower) { 'a' }
10
+ let(:class_name) { 'A' }
11
+
12
+ before :each do
13
+ config.model_directories = ['']
14
+
15
+ file_name.stub!(:gsub).and_return(model_name_lower)
16
+ model_name_lower.stub!(:camelize).and_return(class_name)
17
+ Dir.stub(:[]).and_return([file_name])
18
+ end
19
+
20
+ it "should load the files by guessing the file name" do
21
+ class_name.should_receive(:constantize).and_return(true)
22
+
23
+ ts_context.prepare
24
+ end
25
+
26
+ it "should not raise errors if the model name is nil" do
27
+ file_name.stub!(:gsub).and_return(nil)
28
+
29
+ lambda {
30
+ ts_context.prepare
31
+ }.should_not raise_error
32
+ end
33
+
34
+ it "should report name errors but not raise them" do
35
+ class_name.stub(:constantize).and_raise(NameError)
36
+ STDERR.stub!(:puts => '')
37
+ STDERR.should_receive(:puts).with('Warning: Error loading a.rb:')
38
+
39
+ lambda {
40
+ ts_context.prepare
41
+ }.should_not raise_error
42
+ end
43
+
44
+ it "should retry if the first load fails and contains a directory" do
45
+ model_name_lower.should_receive(:gsub!).twice.and_return(true, nil)
46
+ class_name.stub(:constantize).and_raise(LoadError)
47
+
48
+ lambda {
49
+ ts_context.prepare
50
+ }.should_not raise_error
51
+ end
52
+
53
+ it "should catch database errors with a warning" do
54
+ class_name.should_receive(:constantize).and_raise(Mysql2::Error)
55
+ STDERR.stub!(:puts => '')
56
+ STDERR.should_receive(:puts).with('Warning: Error loading a.rb:')
57
+
58
+ lambda {
59
+ ts_context.prepare
60
+ }.should_not raise_error
61
+ end unless RUBY_PLATFORM == 'java'
62
+
63
+ it "should not load models if they're explicitly set in the configuration" do
64
+ config.indexed_models = ['Alpha', 'Beta']
65
+ ts_context.prepare
66
+
67
+ ts_context.indexed_models.should == ['Alpha', 'Beta']
68
+ end
69
+ end
70
+
71
+ describe '#define_indexes' do
72
+ it "should call define_indexes on all known indexed models" do
73
+ ts_context.stub!(:indexed_models => ['Alpha', 'Beta'])
74
+ Alpha.should_receive(:define_indexes)
75
+ Beta.should_receive(:define_indexes)
76
+
77
+ ts_context.define_indexes
78
+ end
79
+ end
80
+
81
+ describe '#add_indexed_model' do
82
+ before :each do
83
+ ts_context.indexed_models.clear
84
+ end
85
+
86
+ it "should add the model to the collection" do
87
+ ts_context.add_indexed_model 'Alpha'
88
+
89
+ ts_context.indexed_models.should == ['Alpha']
90
+ end
91
+
92
+ it "should not duplicate models in the collection" do
93
+ ts_context.add_indexed_model 'Alpha'
94
+ ts_context.add_indexed_model 'Alpha'
95
+
96
+ ts_context.indexed_models.should == ['Alpha']
97
+ end
98
+
99
+ it "should keep the collection in alphabetical order" do
100
+ ts_context.add_indexed_model 'Beta'
101
+ ts_context.add_indexed_model 'Alpha'
102
+
103
+ ts_context.indexed_models.should == ['Alpha', 'Beta']
104
+ end
105
+
106
+ it "should translate classes to their names" do
107
+ ts_context.add_indexed_model Alpha
108
+
109
+ ts_context.indexed_models.should == ['Alpha']
110
+ end
111
+ end
112
+
113
+ describe '#superclass_indexed_models' do
114
+ it "should return indexed model names" do
115
+ ts_context.stub!(:indexed_models => ['Alpha', 'Beta'])
116
+
117
+ ts_context.superclass_indexed_models.should == ['Alpha', 'Beta']
118
+ end
119
+
120
+ it "should not include classes which have indexed superclasses" do
121
+ ts_context.stub!(:indexed_models => ['Parent', 'Person'])
122
+
123
+ ts_context.superclass_indexed_models.should == ['Person']
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,9 @@
1
+ require 'spec_helper'
2
+
3
+ describe Array do
4
+ describe '.===' do
5
+ it "should return true if an instance of ThinkingSphinx::Search" do
6
+ Array.should === ThinkingSphinx::Search.new
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ require 'spec_helper'
2
+
3
+ describe String do
4
+ describe "to_crc32 instance method" do
5
+ it "should return an integer" do
6
+ 'to_crc32'.to_crc32.should be_a_kind_of(Integer)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,49 @@
1
+ require '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
@@ -0,0 +1,176 @@
1
+ require 'spec_helper'
2
+
3
+ describe ThinkingSphinx::FacetSearch do
4
+ let(:search) { stub('search', :append_to => nil, :empty? => true) }
5
+ let(:config) { ThinkingSphinx::Configuration.instance }
6
+ let(:client) { stub('client', :run => []) }
7
+
8
+ before :each do
9
+ config.stub!(:client => client)
10
+ end
11
+
12
+ describe 'populate' do
13
+ before :each do
14
+ config.configuration.searchd.max_matches = 10_000
15
+ end
16
+
17
+ it "should request all shared facets in a multi-model request by default" do
18
+ ThinkingSphinx.stub!(:search => search)
19
+ if Riddle.loaded_version.to_i < 2
20
+ ThinkingSphinx::FacetSearch.new.facet_names.should == ['class_crc']
21
+ else
22
+ ThinkingSphinx::FacetSearch.new.facet_names.should == ['sphinx_internal_class']
23
+ end
24
+ end
25
+
26
+ it "should request all facets in a multi-model request if specified" do
27
+ ThinkingSphinx.stub!(:search => search)
28
+ names = ThinkingSphinx::FacetSearch.new(:all_facets => true).facet_names
29
+
30
+ if Riddle.loaded_version.to_i < 2
31
+ names.should == ['class_crc', 'city_facet', 'state_facet', 'birthday']
32
+ else
33
+ names.should == ['sphinx_internal_class', 'city_facet', 'state_facet', 'birthday']
34
+ end
35
+ end
36
+
37
+ it "should use the system-set max_matches for limit on facet calls" do
38
+ ThinkingSphinx.should_receive(:search) do |options|
39
+ options[:max_matches].should == 10_000
40
+ options[:limit].should == 10_000
41
+ search
42
+ end
43
+
44
+ ThinkingSphinx::FacetSearch.new
45
+ end
46
+
47
+ it "should use the default max-matches if there is no explicit setting" do
48
+ config.configuration.searchd.max_matches = nil
49
+ ThinkingSphinx.should_receive(:search) do |options|
50
+ options[:max_matches].should == 1000
51
+ options[:limit].should == 1000
52
+ search
53
+ end
54
+
55
+ ThinkingSphinx::FacetSearch.new
56
+ end
57
+
58
+ it "should ignore user-provided max_matches and limit on facet calls" do
59
+ ThinkingSphinx.should_receive(:search) do |options|
60
+ options[:max_matches].should == 10_000
61
+ options[:limit].should == 10_000
62
+ search
63
+ end
64
+
65
+ ThinkingSphinx::FacetSearch.new(
66
+ :max_matches => 500,
67
+ :limit => 200
68
+ )
69
+ end
70
+
71
+ it "should not use an explicit :page" do
72
+ ThinkingSphinx.should_receive(:search) do |options|
73
+ options[:page].should == 1
74
+ search
75
+ end
76
+
77
+ ThinkingSphinx::FacetSearch.new(:page => 3)
78
+ end
79
+
80
+ describe "conflicting facets" do
81
+ before :each do
82
+ @index = ThinkingSphinx::Index::Builder.generate(Alpha) do
83
+ indexes :name
84
+ has :value, :as => :city, :facet => true
85
+ end
86
+ end
87
+
88
+ after :each do
89
+ Alpha.sphinx_facets.delete_at(-1)
90
+ end
91
+
92
+ it "should raise an error if searching with facets of same name but different type" do
93
+ lambda {
94
+ facets = ThinkingSphinx.facets :all_facets => true
95
+ }.should raise_error
96
+ end
97
+ end
98
+
99
+ describe ':facets option' do
100
+ it "should limit facets to the requested set" do
101
+ ThinkingSphinx.should_receive(:search).once.and_return(search)
102
+
103
+ ThinkingSphinx::FacetSearch.new(
104
+ :classes => [Person], :facets => :state
105
+ )
106
+ end
107
+ end
108
+
109
+ describe "empty result set for attributes" do
110
+ before :each do
111
+ ThinkingSphinx.stub!(:search => search)
112
+ @facets = ThinkingSphinx::FacetSearch.new(
113
+ :classes => [Person], :facets => :state
114
+ )
115
+ end
116
+
117
+ it "should add key as attribute" do
118
+ @facets.should have_key(:state)
119
+ end
120
+
121
+ it "should return an empty hash for the facet results" do
122
+ @facets[:state].should be_empty
123
+ end
124
+ end
125
+
126
+ describe "non-empty result set" do
127
+ before :each do
128
+ @person = Person.find(:first)
129
+ @people = [@person]
130
+ search.stub!(:empty? => false)
131
+ search.stub!(:each_with_match).
132
+ and_yield(@person, {:attributes => {'@groupby' => @person.city.to_crc32, '@count' => 1}})
133
+ ThinkingSphinx::Search.stub!(:bundle_searches => [search])
134
+
135
+ @facets = ThinkingSphinx::FacetSearch.new(
136
+ :classes => [Person], :facets => :city
137
+ )
138
+ end
139
+
140
+ it "should return a hash" do
141
+ @facets.should be_a_kind_of(Hash)
142
+ end
143
+
144
+ it "should add key as attribute" do
145
+ @facets.keys.should include(:city)
146
+ end
147
+
148
+ it "should return a hash" do
149
+ @facets[:city].should == {@person.city => 1}
150
+ end
151
+ end
152
+ end
153
+
154
+ describe "#for" do
155
+ before do
156
+ @person = Person.find(:first)
157
+ @people = [@person]
158
+ search.stub!(:each_with_match).
159
+ and_yield(@person, {:attributes => {'@groupby' => @person.city.to_crc32, '@count' => 1}})
160
+ ThinkingSphinx::Search.stub!(:bundle_searches => [search])
161
+
162
+ @facets = ThinkingSphinx::FacetSearch.new(
163
+ :classes => [Person], :facets => :city
164
+ )
165
+ end
166
+
167
+ it "should return the search results for the attribute and key pair" do
168
+ ThinkingSphinx.should_receive(:search) do |options|
169
+ options[:with].should have_key('city_facet')
170
+ options[:with]['city_facet'].should == @person.city.to_crc32
171
+ end
172
+
173
+ @facets.for(:city => @person.city)
174
+ end
175
+ end
176
+ end