ebeigarts-thinking-sphinx 1.1.21

Sign up to get free protection for your applications and to get access to all the features.
Files changed (91) hide show
  1. data/LICENCE +20 -0
  2. data/README.textile +143 -0
  3. data/lib/thinking_sphinx.rb +217 -0
  4. data/lib/thinking_sphinx/active_record.rb +278 -0
  5. data/lib/thinking_sphinx/active_record/attribute_updates.rb +48 -0
  6. data/lib/thinking_sphinx/active_record/delta.rb +87 -0
  7. data/lib/thinking_sphinx/active_record/has_many_association.rb +29 -0
  8. data/lib/thinking_sphinx/active_record/search.rb +57 -0
  9. data/lib/thinking_sphinx/adapters/abstract_adapter.rb +53 -0
  10. data/lib/thinking_sphinx/adapters/mysql_adapter.rb +54 -0
  11. data/lib/thinking_sphinx/adapters/postgresql_adapter.rb +135 -0
  12. data/lib/thinking_sphinx/association.rb +164 -0
  13. data/lib/thinking_sphinx/attribute.rb +269 -0
  14. data/lib/thinking_sphinx/class_facet.rb +15 -0
  15. data/lib/thinking_sphinx/collection.rb +148 -0
  16. data/lib/thinking_sphinx/configuration.rb +275 -0
  17. data/lib/thinking_sphinx/core/string.rb +15 -0
  18. data/lib/thinking_sphinx/deltas.rb +30 -0
  19. data/lib/thinking_sphinx/deltas/datetime_delta.rb +50 -0
  20. data/lib/thinking_sphinx/deltas/default_delta.rb +68 -0
  21. data/lib/thinking_sphinx/deltas/delayed_delta.rb +27 -0
  22. data/lib/thinking_sphinx/deltas/delayed_delta/delta_job.rb +24 -0
  23. data/lib/thinking_sphinx/deltas/delayed_delta/flag_as_deleted_job.rb +27 -0
  24. data/lib/thinking_sphinx/deltas/delayed_delta/job.rb +26 -0
  25. data/lib/thinking_sphinx/deploy/capistrano.rb +82 -0
  26. data/lib/thinking_sphinx/facet.rb +108 -0
  27. data/lib/thinking_sphinx/facet_collection.rb +59 -0
  28. data/lib/thinking_sphinx/field.rb +82 -0
  29. data/lib/thinking_sphinx/index.rb +99 -0
  30. data/lib/thinking_sphinx/index/builder.rb +287 -0
  31. data/lib/thinking_sphinx/index/faux_column.rb +110 -0
  32. data/lib/thinking_sphinx/property.rb +160 -0
  33. data/lib/thinking_sphinx/rails_additions.rb +136 -0
  34. data/lib/thinking_sphinx/search.rb +727 -0
  35. data/lib/thinking_sphinx/search/facets.rb +104 -0
  36. data/lib/thinking_sphinx/source.rb +175 -0
  37. data/lib/thinking_sphinx/source/internal_properties.rb +46 -0
  38. data/lib/thinking_sphinx/source/sql.rb +126 -0
  39. data/lib/thinking_sphinx/tasks.rb +245 -0
  40. data/rails/init.rb +14 -0
  41. data/spec/unit/thinking_sphinx/active_record/delta_spec.rb +136 -0
  42. data/spec/unit/thinking_sphinx/active_record/has_many_association_spec.rb +53 -0
  43. data/spec/unit/thinking_sphinx/active_record/search_spec.rb +107 -0
  44. data/spec/unit/thinking_sphinx/active_record_spec.rb +329 -0
  45. data/spec/unit/thinking_sphinx/association_spec.rb +246 -0
  46. data/spec/unit/thinking_sphinx/attribute_spec.rb +338 -0
  47. data/spec/unit/thinking_sphinx/collection_spec.rb +15 -0
  48. data/spec/unit/thinking_sphinx/configuration_spec.rb +222 -0
  49. data/spec/unit/thinking_sphinx/core/string_spec.rb +9 -0
  50. data/spec/unit/thinking_sphinx/facet_collection_spec.rb +64 -0
  51. data/spec/unit/thinking_sphinx/facet_spec.rb +302 -0
  52. data/spec/unit/thinking_sphinx/field_spec.rb +154 -0
  53. data/spec/unit/thinking_sphinx/index/builder_spec.rb +355 -0
  54. data/spec/unit/thinking_sphinx/index/faux_column_spec.rb +30 -0
  55. data/spec/unit/thinking_sphinx/index_spec.rb +45 -0
  56. data/spec/unit/thinking_sphinx/rails_additions_spec.rb +191 -0
  57. data/spec/unit/thinking_sphinx/search_spec.rb +228 -0
  58. data/spec/unit/thinking_sphinx/source_spec.rb +217 -0
  59. data/spec/unit/thinking_sphinx_spec.rb +151 -0
  60. data/tasks/distribution.rb +67 -0
  61. data/tasks/rails.rake +1 -0
  62. data/tasks/testing.rb +100 -0
  63. data/vendor/after_commit/LICENSE +20 -0
  64. data/vendor/after_commit/README +16 -0
  65. data/vendor/after_commit/Rakefile +22 -0
  66. data/vendor/after_commit/init.rb +8 -0
  67. data/vendor/after_commit/lib/after_commit.rb +45 -0
  68. data/vendor/after_commit/lib/after_commit/active_record.rb +114 -0
  69. data/vendor/after_commit/lib/after_commit/connection_adapters.rb +103 -0
  70. data/vendor/after_commit/test/after_commit_test.rb +53 -0
  71. data/vendor/delayed_job/lib/delayed/job.rb +251 -0
  72. data/vendor/delayed_job/lib/delayed/message_sending.rb +7 -0
  73. data/vendor/delayed_job/lib/delayed/performable_method.rb +55 -0
  74. data/vendor/delayed_job/lib/delayed/worker.rb +54 -0
  75. data/vendor/riddle/lib/riddle.rb +30 -0
  76. data/vendor/riddle/lib/riddle/client.rb +619 -0
  77. data/vendor/riddle/lib/riddle/client/filter.rb +53 -0
  78. data/vendor/riddle/lib/riddle/client/message.rb +65 -0
  79. data/vendor/riddle/lib/riddle/client/response.rb +84 -0
  80. data/vendor/riddle/lib/riddle/configuration.rb +33 -0
  81. data/vendor/riddle/lib/riddle/configuration/distributed_index.rb +48 -0
  82. data/vendor/riddle/lib/riddle/configuration/index.rb +142 -0
  83. data/vendor/riddle/lib/riddle/configuration/indexer.rb +19 -0
  84. data/vendor/riddle/lib/riddle/configuration/remote_index.rb +17 -0
  85. data/vendor/riddle/lib/riddle/configuration/searchd.rb +25 -0
  86. data/vendor/riddle/lib/riddle/configuration/section.rb +43 -0
  87. data/vendor/riddle/lib/riddle/configuration/source.rb +23 -0
  88. data/vendor/riddle/lib/riddle/configuration/sql_source.rb +34 -0
  89. data/vendor/riddle/lib/riddle/configuration/xml_source.rb +28 -0
  90. data/vendor/riddle/lib/riddle/controller.rb +44 -0
  91. metadata +191 -0
@@ -0,0 +1,245 @@
1
+ require 'fileutils'
2
+
3
+ namespace :thinking_sphinx do
4
+ task :app_env do
5
+ if defined?(RAILS_ROOT)
6
+ Rake::Task[:environment].invoke
7
+ Rails.configuration.cache_classes = false
8
+ end
9
+
10
+ Rake::Task[:merb_env].invoke if defined?(Merb)
11
+ end
12
+
13
+ desc "Output the current Thinking Sphinx version"
14
+ task :version => :app_env do
15
+ puts "Thinking Sphinx v" + ThinkingSphinx::Version::String
16
+ end
17
+
18
+ desc "Stop if running, then start a Sphinx searchd daemon using Thinking Sphinx's settings"
19
+ task :running_start => :app_env do
20
+ Rake::Task["thinking_sphinx:stop"].invoke if sphinx_running?
21
+ Rake::Task["thinking_sphinx:start"].invoke
22
+ end
23
+
24
+ desc "Start a Sphinx searchd daemon using Thinking Sphinx's settings"
25
+ task :start => :app_env do
26
+ config = ThinkingSphinx::Configuration.instance
27
+
28
+ FileUtils.mkdir_p config.searchd_file_path
29
+ raise RuntimeError, "searchd is already running." if sphinx_running?
30
+
31
+ Dir["#{config.searchd_file_path}/*.spl"].each { |file| File.delete(file) }
32
+
33
+ system! "#{config.bin_path}#{config.searchd_binary_name} --pidfile --config #{config.config_file}"
34
+
35
+ sleep(2)
36
+
37
+ if sphinx_running?
38
+ puts "Started successfully (pid #{sphinx_pid})."
39
+ else
40
+ puts "Failed to start searchd daemon. Check #{config.searchd_log_file}"
41
+ end
42
+ end
43
+
44
+ desc "Stop Sphinx using Thinking Sphinx's settings"
45
+ task :stop => :app_env do
46
+ raise RuntimeError, "searchd is not running." unless sphinx_running?
47
+ config = ThinkingSphinx::Configuration.instance
48
+ pid = sphinx_pid
49
+ system! "#{config.bin_path}#{config.searchd_binary_name} --stop --config #{config.config_file}"
50
+ puts "Stopped search daemon (pid #{pid})."
51
+ end
52
+
53
+ desc "Restart Sphinx"
54
+ task :restart => [:app_env, :stop, :start]
55
+
56
+ desc "Generate the Sphinx configuration file using Thinking Sphinx's settings"
57
+ task :configure => :app_env do
58
+ config = ThinkingSphinx::Configuration.instance
59
+ puts "Generating Configuration to #{config.config_file}"
60
+ config.build
61
+ end
62
+
63
+ desc "Index data for Sphinx using Thinking Sphinx's settings"
64
+ task :index => :app_env do
65
+ ThinkingSphinx::Deltas::Job.cancel_thinking_sphinx_jobs
66
+
67
+ config = ThinkingSphinx::Configuration.instance
68
+ unless ENV["INDEX_ONLY"] == "true"
69
+ puts "Generating Configuration to #{config.config_file}"
70
+ config.build
71
+ end
72
+
73
+ FileUtils.mkdir_p config.searchd_file_path
74
+ cmd = "#{config.bin_path}#{config.indexer_binary_name} --config #{config.config_file} --all"
75
+ cmd << " --rotate" if sphinx_running?
76
+
77
+ system! cmd
78
+ end
79
+
80
+ desc "Stop Sphinx (if it's running), rebuild the indexes, and start Sphinx"
81
+ task :rebuild => :app_env do
82
+ Rake::Task["thinking_sphinx:stop"].invoke if sphinx_running?
83
+ Rake::Task["thinking_sphinx:index"].invoke
84
+ Rake::Task["thinking_sphinx:start"].invoke
85
+ end
86
+
87
+ namespace :index do
88
+ task :delta => :app_env do
89
+ ThinkingSphinx.indexed_models.select { |model|
90
+ model.constantize.sphinx_indexes.any? { |index| index.delta? }
91
+ }.each do |model|
92
+ model.constantize.sphinx_indexes.select { |index|
93
+ index.delta? && index.delta_object.respond_to?(:delayed_index)
94
+ }.each { |index|
95
+ index.delta_object.delayed_index(index.model)
96
+ }
97
+ end
98
+ end
99
+ end
100
+
101
+ desc "Process stored delta index requests"
102
+ task :delayed_delta => :app_env do
103
+ require 'delayed/worker'
104
+
105
+ Delayed::Worker.new(
106
+ :min_priority => ENV['MIN_PRIORITY'],
107
+ :max_priority => ENV['MAX_PRIORITY']
108
+ ).start
109
+ end
110
+
111
+ # http://www.sphinxsearch.com/docs/current.html#xmlpipe2
112
+ desc "Streams XML data to STDOUT"
113
+ task :xml => :app_env do
114
+ source_name = ENV["NAME"]
115
+ # STDERR.puts "Source name: #{source_name}"
116
+ source_name =~ /^(.+)_(delta|core)_(\d+)$/
117
+ sphinx_name = $1
118
+ index_name = "#{$1}_#{$2}"
119
+ offset = ENV["OFFSET"]
120
+ model_name = ThinkingSphinx.indexed_models.detect { |s| s.constantize.send(:sphinx_name) == sphinx_name }
121
+ # STDERR.puts "Model name: #{model_name}"
122
+ model_klass = model_name.constantize
123
+
124
+ # Do not use xml type in rake task
125
+ ThinkingSphinx::Configuration.instance.type = "sql"
126
+
127
+ # Get TS index configuration
128
+ index_configuration = model_klass.to_riddle(offset).detect { |c| c.name == index_name }
129
+ source = index_configuration.sources.first
130
+
131
+ # get $start, $end
132
+ # STDERR.puts source.sql_query_range
133
+ range_start, range_end = model_klass.connection.select_rows(source.sql_query_range).first.collect(&:to_i)
134
+ query = source.sql_query.gsub('$start', range_start.to_s).gsub('$end', range_end.to_s)
135
+
136
+ # fetch sql and generate xml
137
+ puts %{<?xml version="1.0" encoding="utf-8"?>}
138
+ puts %{<sphinx:docset>}
139
+
140
+ # results = model_klass.connection.select_all(query)
141
+ # STDERR.puts "fetching #{start_id}.. "
142
+ # STDERR.puts query
143
+ model_klass.sphinx_database_adapter.select_each(query) do |values|
144
+ id = values.delete "id"
145
+ puts %{<sphinx:document id="#{id.to_i}">}
146
+ values.each do |k, v|
147
+ if source.sql_attr_bool.include?(k.to_sym)
148
+ v = ['Y', 'T', '1', 1, true].include?(v) ? 1 : 0
149
+ elsif source.sql_attr_uint.include?(k.to_sym) ||
150
+ source.sql_attr_timestamp.include?(k.to_sym)
151
+ v = v.to_i
152
+ elsif source.sql_attr_float.include?(k.to_sym)
153
+ v = v.to_f
154
+ else
155
+ v = value_to_string(v)
156
+ v = %{<![CDATA[[#{v}]]>}
157
+ end
158
+ puts %{<#{k}>#{v}</#{k}>}
159
+ end
160
+ # Add MVA attributes (:ranged_query, :query)
161
+ internal_id = values["sphinx_internal_id"]
162
+ source.sql_attr_multi.each do |params|
163
+ # STDERR.puts "MVA_ATTRS: #{params.inspect}"
164
+ mva_definition, mva_query, mva_query_range = params.split(";")
165
+ k = mva_definition.split(" ")[1]
166
+ if mva_query
167
+ mva_query = mva_query.gsub('$start', internal_id.to_s).gsub('$end', internal_id.to_s)
168
+ printf %{<#{k}>}
169
+ not_first = false
170
+ model_klass.sphinx_database_adapter.select_each(mva_query) do |values|
171
+ if not_first
172
+ printf ","
173
+ else
174
+ not_first = true
175
+ end
176
+ printf values.values.first.to_s
177
+ end
178
+ puts %{</#{k}>}
179
+ end
180
+ end
181
+ puts %{</sphinx:document>}
182
+ end
183
+
184
+ puts %{</sphinx:docset>}
185
+ end
186
+
187
+ # try to remove invalid xml characters
188
+ def value_to_string(v)
189
+ v.is_a?(String) ? v.gsub("\000", '') : v
190
+ end
191
+ end
192
+
193
+ namespace :ts do
194
+ desc "Output the current Thinking Sphinx version"
195
+ task :version => "thinking_sphinx:version"
196
+ desc "Stop if running, then start a Sphinx searchd daemon using Thinking Sphinx's settings"
197
+ task :run => "thinking_sphinx:running_start"
198
+ desc "Start a Sphinx searchd daemon using Thinking Sphinx's settings"
199
+ task :start => "thinking_sphinx:start"
200
+ desc "Stop Sphinx using Thinking Sphinx's settings"
201
+ task :stop => "thinking_sphinx:stop"
202
+ desc "Index data for Sphinx using Thinking Sphinx's settings"
203
+ task :in => "thinking_sphinx:index"
204
+ namespace :in do
205
+ desc "Index Thinking Sphinx datetime delta indexes"
206
+ task :delta => "thinking_sphinx:index:delta"
207
+ end
208
+ task :index => "thinking_sphinx:index"
209
+ desc "Restart Sphinx"
210
+ task :restart => "thinking_sphinx:restart"
211
+ desc "Generate the Sphinx configuration file using Thinking Sphinx's settings"
212
+ task :conf => "thinking_sphinx:configure"
213
+ desc "Generate the Sphinx configuration file using Thinking Sphinx's settings"
214
+ task :config => "thinking_sphinx:configure"
215
+ desc "Stop Sphinx (if it's running), rebuild the indexes, and start Sphinx"
216
+ task :rebuild => "thinking_sphinx:rebuild"
217
+ desc "Process stored delta index requests"
218
+ task :dd => "thinking_sphinx:delayed_delta"
219
+ desc "Streams XML data to STDOUT"
220
+ task :xml => "thinking_sphinx:xml"
221
+ end
222
+
223
+ def sphinx_pid
224
+ ThinkingSphinx.sphinx_pid
225
+ end
226
+
227
+ def sphinx_running?
228
+ ThinkingSphinx.sphinx_running?
229
+ end
230
+
231
+ # a fail-fast, hopefully helpful version of system
232
+ def system!(cmd)
233
+ unless system(cmd)
234
+ raise <<-SYSTEM_CALL_FAILED
235
+ The following command failed:
236
+ #{cmd}
237
+
238
+ This could be caused by a PATH issue in the environment of cron/passenger/etc. Your current PATH:
239
+ #{ENV['PATH']}
240
+ You can set the path to your indexer and searchd binaries using the bin_path property in config/sphinx.yml:
241
+ production:
242
+ bin_path: '/usr/local/bin'
243
+ SYSTEM_CALL_FAILED
244
+ end
245
+ end
data/rails/init.rb ADDED
@@ -0,0 +1,14 @@
1
+ require 'thinking_sphinx'
2
+ require 'action_controller/dispatcher'
3
+
4
+ ActionController::Dispatcher.to_prepare :thinking_sphinx do
5
+ # Force internationalisation to be loaded.
6
+ if Rails::VERSION::STRING.to_f > 2.2
7
+ I18n.backend.reload!
8
+ I18n.backend.available_locales
9
+ elsif Rails::VERSION::STRING.to_f > 2.1
10
+ I18n.backend.load_translations(*I18n.load_path)
11
+ end
12
+
13
+ ThinkingSphinx::Configuration.instance.load_models
14
+ end
@@ -0,0 +1,136 @@
1
+ require 'spec/spec_helper'
2
+
3
+ describe "ThinkingSphinx::ActiveRecord::Delta" do
4
+ it "should call the toggle_delta method after a save" do
5
+ @beta = Beta.new(:name => 'beta')
6
+ @beta.stub_method(:toggle_delta => true)
7
+
8
+ @beta.save
9
+
10
+ @beta.should have_received(:toggle_delta)
11
+ end
12
+
13
+ it "should call the toggle_delta method after a save!" do
14
+ @beta = Beta.new(:name => 'beta')
15
+ @beta.stub_method(:toggle_delta => true)
16
+
17
+ @beta.save!
18
+
19
+ @beta.should have_received(:toggle_delta)
20
+ end
21
+
22
+ describe "suspended_delta method" do
23
+ before :each do
24
+ ThinkingSphinx.stub_method(:deltas_enabled? => true)
25
+ Person.sphinx_indexes.first.delta_object.stub_method(:` => "")
26
+ end
27
+
28
+ it "should execute the argument block with deltas disabled" do
29
+ ThinkingSphinx.should_receive(:deltas_enabled=).once.with(false)
30
+ ThinkingSphinx.should_receive(:deltas_enabled=).once.with(true)
31
+ lambda { Person.suspended_delta { raise 'i was called' } }.should(
32
+ raise_error(Exception)
33
+ )
34
+ end
35
+
36
+ it "should restore deltas_enabled to its original setting" do
37
+ ThinkingSphinx.stub_method(:deltas_enabled? => false)
38
+ ThinkingSphinx.should_receive(:deltas_enabled=).twice.with(false)
39
+ Person.suspended_delta { 'no-op' }
40
+ end
41
+
42
+ it "should restore deltas_enabled to its original setting even if there was an exception" do
43
+ ThinkingSphinx.stub_method(:deltas_enabled? => false)
44
+ ThinkingSphinx.should_receive(:deltas_enabled=).twice.with(false)
45
+ lambda { Person.suspended_delta { raise 'bad error' } }.should(
46
+ raise_error(Exception)
47
+ )
48
+ end
49
+
50
+ it "should reindex by default after the code block is run" do
51
+ Person.should_receive(:index_delta)
52
+ Person.suspended_delta { 'no-op' }
53
+ end
54
+
55
+ it "should not reindex after the code block if false is passed in" do
56
+ Person.should_not_receive(:index_delta)
57
+ Person.suspended_delta(false) { 'no-op' }
58
+ end
59
+ end
60
+
61
+ describe "toggle_delta method" do
62
+ it "should set the delta value to true" do
63
+ @person = Person.new
64
+
65
+ @person.delta.should be_false
66
+ @person.send(:toggle_delta)
67
+ @person.delta.should be_true
68
+ end
69
+ end
70
+
71
+ describe "index_delta method" do
72
+ before :each do
73
+ ThinkingSphinx::Configuration.stub_method(:environment => "spec")
74
+ ThinkingSphinx.stub_method(:deltas_enabled? => true, :sphinx_running? => true)
75
+ Person.delta_object.stub_methods(:` => "", :toggled => true)
76
+
77
+ @person = Person.new
78
+ @person.stub_method(
79
+ :in_both_indexes? => false,
80
+ :sphinx_document_id => 1
81
+ )
82
+
83
+ @client = Riddle::Client.new
84
+ @client.stub!(:update => true)
85
+ Riddle::Client.stub_method(:new => @client)
86
+ end
87
+
88
+ it "shouldn't index if delta indexing is disabled" do
89
+ ThinkingSphinx.stub_method(:deltas_enabled? => false)
90
+
91
+ @person.send(:index_delta)
92
+
93
+ Person.sphinx_indexes.first.delta_object.should_not have_received(:`)
94
+ @client.should_not have_received(:update)
95
+ end
96
+
97
+ it "shouldn't index if index updating is disabled" do
98
+ ThinkingSphinx.stub_method(:updates_enabled? => false)
99
+
100
+ @person.send(:index_delta)
101
+
102
+ Person.sphinx_indexes.first.delta_object.should_not have_received(:`)
103
+ end
104
+
105
+ it "shouldn't index if the environment is 'test'" do
106
+ ThinkingSphinx.unstub_method(:deltas_enabled?)
107
+ ThinkingSphinx.deltas_enabled = nil
108
+ ThinkingSphinx::Configuration.stub_method(:environment => "test")
109
+
110
+ @person.send(:index_delta)
111
+
112
+ Person.sphinx_indexes.first.delta_object.should_not have_received(:`)
113
+ end
114
+
115
+ it "should call indexer for the delta index" do
116
+ @person.send(:index_delta)
117
+
118
+ Person.sphinx_indexes.first.delta_object.should have_received(:`).with(
119
+ "#{ThinkingSphinx::Configuration.instance.bin_path}indexer --config #{ThinkingSphinx::Configuration.instance.config_file} --rotate person_delta"
120
+ )
121
+ end
122
+
123
+ it "shouldn't update the deleted attribute if not in the index" do
124
+ @client.should_not_receive(:update)
125
+
126
+ @person.send(:index_delta)
127
+ end
128
+
129
+ it "should update the deleted attribute if in the core index" do
130
+ @person.stub_method(:in_both_indexes? => true)
131
+ @client.should_receive(:update)
132
+
133
+ @person.send(:index_delta)
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,53 @@
1
+ require 'spec/spec_helper'
2
+
3
+ describe 'ThinkingSphinx::ActiveRecord::HasManyAssociation' do
4
+ describe "search method" do
5
+ before :each do
6
+ Friendship.stub_method(:search => true)
7
+
8
+ @person = Person.find(:first)
9
+ @index = Friendship.sphinx_indexes.first
10
+ end
11
+
12
+ it "should raise an error if the required attribute doesn't exist" do
13
+ @index.stub_method(:attributes => [])
14
+
15
+ lambda { @person.friendships.search "test" }.should raise_error(RuntimeError)
16
+
17
+ @index.unstub_method(:attributes)
18
+ end
19
+
20
+ it "should add a filter for the attribute into a normal search call" do
21
+ @person.friendships.search "test"
22
+
23
+ Friendship.should have_received(:search).with(
24
+ "test", :with => {:person_id => @person.id}
25
+ )
26
+ end
27
+ end
28
+
29
+ describe "search method for has_many :through" do
30
+ before :each do
31
+ Person.stub_method(:search => true)
32
+
33
+ @person = Person.find(:first)
34
+ @index = Person.sphinx_indexes.first
35
+ end
36
+
37
+ it "should raise an error if the required attribute doesn't exist" do
38
+ @index.stub_method(:attributes => [])
39
+
40
+ lambda { @person.friends.search "test" }.should raise_error(RuntimeError)
41
+
42
+ @index.unstub_method(:attributes)
43
+ end
44
+
45
+ it "should add a filter for the attribute into a normal search call" do
46
+ @person.friends.search "test"
47
+
48
+ Person.should have_received(:search).with(
49
+ "test", :with => {:friendly_ids => @person.id}
50
+ )
51
+ end
52
+ end
53
+ end