ryansch-ts-resque-delta 1.1.5.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. data/.gitignore +16 -0
  2. data/.rspec +3 -0
  3. data/Gemfile +25 -0
  4. data/Guardfile +16 -0
  5. data/LICENSE +20 -0
  6. data/README.markdown +82 -0
  7. data/Rakefile +10 -0
  8. data/config/redis-cucumber.conf +13 -0
  9. data/features/resque_deltas.feature +62 -0
  10. data/features/smart_indexing.feature +42 -0
  11. data/features/step_definitions/common_steps.rb +76 -0
  12. data/features/step_definitions/resque_delta_steps.rb +33 -0
  13. data/features/step_definitions/smart_indexing_steps.rb +3 -0
  14. data/features/support/env.rb +32 -0
  15. data/features/thinking_sphinx/database.example.yml +3 -0
  16. data/features/thinking_sphinx/db/migrations/create_delayed_betas.rb +4 -0
  17. data/features/thinking_sphinx/models/delayed_beta.rb +6 -0
  18. data/lib/flying_sphinx/resque_delta.rb +38 -0
  19. data/lib/flying_sphinx/resque_delta/delta_job.rb +14 -0
  20. data/lib/flying_sphinx/resque_delta/flag_as_deleted_job.rb +7 -0
  21. data/lib/thinking_sphinx/deltas/resque_delta.rb +118 -0
  22. data/lib/thinking_sphinx/deltas/resque_delta/core_index.rb +98 -0
  23. data/lib/thinking_sphinx/deltas/resque_delta/delta_job.rb +90 -0
  24. data/lib/thinking_sphinx/deltas/resque_delta/flag_as_deleted_set.rb +56 -0
  25. data/lib/thinking_sphinx/deltas/resque_delta/index_utils.rb +47 -0
  26. data/lib/thinking_sphinx/deltas/resque_delta/railtie.rb +8 -0
  27. data/lib/thinking_sphinx/deltas/resque_delta/tasks.rb +38 -0
  28. data/lib/thinking_sphinx/deltas/resque_delta/version.rb +7 -0
  29. data/lib/ts-resque-delta.rb +2 -0
  30. data/spec/flying_sphinx/resque_delta/delta_job_spec.rb +32 -0
  31. data/spec/flying_sphinx/resque_delta/flag_as_deleted_job_spec.rb +23 -0
  32. data/spec/flying_sphinx/resque_delta_spec.rb +131 -0
  33. data/spec/spec_helper.rb +13 -0
  34. data/spec/thinking_sphinx/deltas/resque_delta/core_index_spec.rb +208 -0
  35. data/spec/thinking_sphinx/deltas/resque_delta/delta_job_spec.rb +172 -0
  36. data/spec/thinking_sphinx/deltas/resque_delta/flag_as_deleted_set_spec.rb +126 -0
  37. data/spec/thinking_sphinx/deltas/resque_delta/index_utils_spec.rb +67 -0
  38. data/spec/thinking_sphinx/deltas/resque_delta_spec.rb +191 -0
  39. data/tasks/rails.rake +1 -0
  40. data/ts-resque-delta.gemspec +40 -0
  41. metadata +393 -0
@@ -0,0 +1,47 @@
1
+ module ThinkingSphinx::Deltas::ResqueDelta::IndexUtils
2
+ extend self
3
+
4
+ # Public: Return a list of index prefixes (i.e. without "_core"/"_delta").
5
+ #
6
+ # Examples
7
+ #
8
+ # sphinx_indices
9
+ # # => ['foo', 'bar']
10
+ #
11
+ # Returns an Array of index prefixes.
12
+ def index_prefixes
13
+ @prefixes ||= indices.reject { |i| i =~ /_(core|delta)$/ }
14
+ end
15
+
16
+ def core_indices
17
+ @core_indices ||= indices.select { |i| i =~ /_core$/ }
18
+ end
19
+
20
+ def delta_indices
21
+ @delta_indices ||= indices.select { |i| i =~ /_delta$/ }
22
+ end
23
+
24
+ def reload!
25
+ @ts_config = @indices = @prefixes = @core_indices = @delta_indices = nil
26
+ end
27
+
28
+ def delta_to_core(delta_name)
29
+ delta_name.sub(/_delta$/, '_core')
30
+ end
31
+
32
+ def core_to_delta(core_name)
33
+ core_name.sub(/_core$/, '_delta')
34
+ end
35
+
36
+ def ts_config
37
+ @ts_config ||= ThinkingSphinx::Configuration.instance.tap do |config|
38
+ config.generate
39
+ end
40
+ end
41
+
42
+ private
43
+ def indices
44
+ @indices ||= ts_config.configuration.indices.collect { |i| i.name }
45
+ end
46
+
47
+ end
@@ -0,0 +1,8 @@
1
+ require 'rails'
2
+
3
+ class ThinkingSphinx::Deltas::ResqueDelta::Railtie < Rails::Railtie
4
+
5
+ rake_tasks do
6
+ load File.expand_path('../tasks.rb', __FILE__)
7
+ end
8
+ end
@@ -0,0 +1,38 @@
1
+ require 'thinking_sphinx/deltas/resque_delta'
2
+
3
+ namespace :thinking_sphinx do
4
+ desc 'Lock all delta indices (Resque will not run indexer or place new jobs on the :ts_delta queue).'
5
+ task :lock_deltas do
6
+ ThinkingSphinx::Deltas::ResqueDelta::CoreIndex.new.lock_deltas
7
+ end
8
+
9
+ desc 'Unlock all delta indices.'
10
+ task :unlock_deltas do
11
+ ThinkingSphinx::Deltas::ResqueDelta::CoreIndex.new.unlock_deltas
12
+ end
13
+
14
+ desc 'Like `rake thinking_sphinx:index`, but locks one index at a time.'
15
+ task :smart_index => :app_env do
16
+ ret = ThinkingSphinx::Deltas::ResqueDelta::CoreIndex.new.smart_index
17
+
18
+ abort("Indexing failed.") if ret != true
19
+ end
20
+ end
21
+
22
+ namespace :ts do
23
+ desc 'Like `rake thinking_sphinx:index`, but locks one index at a time.'
24
+ task :si => 'thinking_sphinx:smart_index'
25
+ end
26
+
27
+ unless Rake::Task.task_defined?('thinking_sphinx:index')
28
+ require 'thinking_sphinx/tasks'
29
+ end
30
+
31
+ # Ensure that indexing does not conflict with ts-resque-delta delta jobs.
32
+ Rake::Task['thinking_sphinx:index'].enhance ['thinking_sphinx:lock_deltas'] do
33
+ Rake::Task['thinking_sphinx:unlock_deltas'].invoke
34
+ end
35
+
36
+ Rake::Task['thinking_sphinx:reindex'].enhance ['thinking_sphinx:lock_deltas'] do
37
+ Rake::Task['thinking_sphinx:unlock_deltas'].invoke
38
+ end
@@ -0,0 +1,7 @@
1
+ module ThinkingSphinx
2
+ module Deltas
3
+ class ResqueDeltaInfo
4
+ VERSION = "1.1.5.1"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,2 @@
1
+ require 'thinking_sphinx/deltas/resque_delta'
2
+ require 'thinking_sphinx/deltas/resque_delta/railtie' if defined?(Rails::Railtie)
@@ -0,0 +1,32 @@
1
+ require 'spec_helper'
2
+
3
+ describe FlyingSphinx::ResqueDelta::DeltaJob do
4
+ describe '@queue' do
5
+ it "uses the fs_delta queue" do
6
+ FlyingSphinx::ResqueDelta::DeltaJob.instance_variable_get(:@queue).
7
+ should == :fs_delta
8
+ end
9
+ end
10
+
11
+ describe '.perform' do
12
+ it "doesn't create an index request when skipping" do
13
+ FlyingSphinx::ResqueDelta::DeltaJob.stub!(:skip? => true)
14
+
15
+ FlyingSphinx::IndexRequest.should_not_receive(:new)
16
+
17
+ FlyingSphinx::ResqueDelta::DeltaJob.perform ['foo_delta']
18
+ end
19
+
20
+ it "performs an index request when not skipping" do
21
+ request = double('index request', :perform => true)
22
+ FlyingSphinx::ResqueDelta::DeltaJob.stub!(:skip? => false)
23
+
24
+ FlyingSphinx::IndexRequest.should_receive(:new).
25
+ with(['foo_delta']).
26
+ and_return(request)
27
+ request.should_receive(:perform)
28
+
29
+ FlyingSphinx::ResqueDelta::DeltaJob.perform ['foo_delta']
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,23 @@
1
+ require 'spec_helper'
2
+
3
+ describe FlyingSphinx::ResqueDelta::FlagAsDeletedJob do
4
+ describe '@queue' do
5
+ it "uses the fs_delta queue" do
6
+ FlyingSphinx::ResqueDelta::FlagAsDeletedJob.
7
+ instance_variable_get(:@queue).should == :fs_delta
8
+ end
9
+ end
10
+
11
+ describe '.perform' do
12
+ it "performs a flag-as-deleted job" do
13
+ job = double('flag as deleted job', :perform => true)
14
+
15
+ FlyingSphinx::FlagAsDeletedJob.should_receive(:new).
16
+ with(['foo_core'], 5).
17
+ and_return(job)
18
+ job.should_receive(:perform)
19
+
20
+ FlyingSphinx::ResqueDelta::FlagAsDeletedJob.perform ['foo_core'], 5
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,131 @@
1
+ require 'spec_helper'
2
+
3
+ describe FlyingSphinx::ResqueDelta do
4
+ describe '.job_types' do
5
+ it "contains just the Flying Sphinx delta and delete jobs" do
6
+ FlyingSphinx::ResqueDelta.job_types.should == [
7
+ FlyingSphinx::ResqueDelta::DeltaJob,
8
+ FlyingSphinx::ResqueDelta::FlagAsDeletedJob
9
+ ]
10
+ end
11
+ end
12
+
13
+ describe '.job_prefix' do
14
+ it "is fs-delta" do
15
+ FlyingSphinx::ResqueDelta.job_prefix.should == 'fs-delta'
16
+ end
17
+ end
18
+
19
+ describe '#index' do
20
+ before :each do
21
+ ThinkingSphinx.updates_enabled = true
22
+ ThinkingSphinx.deltas_enabled = true
23
+
24
+ Resque.stub(:enqueue => true)
25
+
26
+ @delayed_delta = FlyingSphinx::ResqueDelta.new(
27
+ stub('instance'), {}
28
+ )
29
+ @delayed_delta.stub(:toggled).and_return(true)
30
+
31
+ FlyingSphinx::ResqueDelta.stub(:lock)
32
+ FlyingSphinx::ResqueDelta.stub(:unlock)
33
+ FlyingSphinx::ResqueDelta.stub(:locked?).and_return(false)
34
+
35
+ @model = stub('foo')
36
+ @model.stub(:core_index_names => ['foo_core'])
37
+ @model.stub(:delta_index_names => ['foo_delta'])
38
+
39
+ @instance = stub('instance')
40
+ @instance.stub(:sphinx_document_id => 42)
41
+ end
42
+
43
+ context 'updates disabled' do
44
+ before :each do
45
+ ThinkingSphinx.updates_enabled = false
46
+ end
47
+
48
+ it "should not enqueue a delta job" do
49
+ Resque.should_not_receive(:enqueue)
50
+ @delayed_delta.index(@model)
51
+ end
52
+
53
+ it "should not enqueue a flag as deleted job" do
54
+ Resque.should_not_receive(:enqueue)
55
+ @delayed_delta.index(@model)
56
+ end
57
+ end
58
+
59
+ context 'deltas disabled' do
60
+ before :each do
61
+ ThinkingSphinx.deltas_enabled = false
62
+ end
63
+
64
+ it "should not enqueue a delta job" do
65
+ Resque.should_not_receive(:enqueue)
66
+ @delayed_delta.index(@model)
67
+ end
68
+
69
+ it "should not enqueue a flag as deleted job" do
70
+ Resque.should_not_receive(:enqueue)
71
+ @delayed_delta.index(@model)
72
+ end
73
+ end
74
+
75
+ context "instance isn't toggled" do
76
+ before :each do
77
+ @delayed_delta.stub(:toggled => false)
78
+ end
79
+
80
+ it "should not enqueue a delta job" do
81
+ Resque.should_not_receive(:enqueue)
82
+ @delayed_delta.index(@model, @instance)
83
+ end
84
+
85
+ it "should not enqueue a flag as deleted job" do
86
+ Resque.should_not_receive(:enqueue)
87
+ @delayed_delta.index(@model, @instance)
88
+ end
89
+ end
90
+
91
+ it "should enqueue a delta job" do
92
+ Resque.should_receive(:enqueue).at_least(:once).with(
93
+ FlyingSphinx::ResqueDelta::DeltaJob,
94
+ ['foo_delta']
95
+ )
96
+ @delayed_delta.index(@model)
97
+ end
98
+
99
+ it "should enqueue a flag-as-deleted job" do
100
+ Resque.should_receive(:enqueue).at_least(:once).with(
101
+ FlyingSphinx::ResqueDelta::FlagAsDeletedJob,
102
+ ['foo_core'],
103
+ 42
104
+ )
105
+ @delayed_delta.index(@model, @instance)
106
+ end
107
+
108
+ context "delta index is locked" do
109
+ before :each do
110
+ FlyingSphinx::ResqueDelta.stub(:locked?).and_return(true)
111
+ end
112
+
113
+ it "should not enqueue a delta job" do
114
+ Resque.should_not_receive(:enqueue).with(
115
+ FlyingSphinx::ResqueDelta::DeltaJob,
116
+ ['foo_delta']
117
+ )
118
+ @delayed_delta.index(@model, @instance)
119
+ end
120
+
121
+ it "should enqueue a flag-as-deleted job" do
122
+ Resque.should_receive(:enqueue).at_least(:once).with(
123
+ FlyingSphinx::ResqueDelta::FlagAsDeletedJob,
124
+ ['foo_core'],
125
+ 42
126
+ )
127
+ @delayed_delta.index(@model, @instance)
128
+ end
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,13 @@
1
+ require 'thinking_sphinx'
2
+ require 'thinking_sphinx/deltas/resque_delta'
3
+ require 'flying_sphinx'
4
+ require 'flying_sphinx/resque_delta'
5
+
6
+ require 'mock_redis'
7
+ require 'fakefs/spec_helpers'
8
+
9
+ RSpec.configure do |c|
10
+ c.filter_run :focus => true
11
+ c.run_all_when_everything_filtered = true
12
+ c.treat_symbols_as_metadata_keys_with_true_values = true
13
+ end
@@ -0,0 +1,208 @@
1
+ require 'spec_helper'
2
+
3
+ describe ThinkingSphinx::Deltas::ResqueDelta::CoreIndex do
4
+ let(:indices) { %w[foo bar] }
5
+ let(:config) { double('config') }
6
+
7
+ describe '#lock_delta' do
8
+ it 'should lock the delta' do
9
+ ThinkingSphinx::Deltas::ResqueDelta.should_receive(:lock)
10
+
11
+ subject.lock_delta('foo')
12
+ end
13
+
14
+ it 'should lock the delta for the given index' do
15
+ ThinkingSphinx::Deltas::ResqueDelta.should_receive(:lock).with('foo_delta')
16
+
17
+ subject.lock_delta('foo')
18
+ end
19
+ end
20
+
21
+ describe '#unlock_delta' do
22
+ it 'should unlock the delta' do
23
+ ThinkingSphinx::Deltas::ResqueDelta.should_receive(:unlock)
24
+
25
+ subject.unlock_delta('foo')
26
+ end
27
+
28
+ it 'should unlock the delta for the given index' do
29
+ ThinkingSphinx::Deltas::ResqueDelta.should_receive(:unlock).with('foo_delta')
30
+
31
+ subject.unlock_delta('foo')
32
+ end
33
+ end
34
+
35
+ describe '#lock_deltas' do
36
+ it 'should lock all delta indices' do
37
+ subject.stub(:sphinx_indices => indices)
38
+
39
+ indices.each {|index| subject.should_receive(:lock_delta).once.with(index) }
40
+
41
+ subject.lock_deltas
42
+ end
43
+ end
44
+
45
+ describe '#unlock_deltas' do
46
+ it 'should unlock all delta indices' do
47
+ subject.stub(:sphinx_indices => indices)
48
+
49
+ indices.each {|index| subject.should_receive(:unlock_delta).once.with(index) }
50
+
51
+ subject.unlock_deltas
52
+ end
53
+ end
54
+
55
+ describe '#with_delta_index_lock' do
56
+ before :each do
57
+ subject.stub(:lock_delta)
58
+ subject.stub(:unlock_delta)
59
+
60
+ subject.stub(:block_called)
61
+
62
+ @block_called = false
63
+ @block = lambda { @block_called = true; subject.block_called }
64
+ end
65
+
66
+ it 'should yield' do
67
+ subject.with_delta_index_lock('foo', &@block)
68
+ @block_called.should be_true
69
+ end
70
+
71
+ it 'should lock before yielding' do
72
+ subject.should_receive(:lock_delta).with('foo').ordered
73
+ subject.should_receive(:block_called).ordered
74
+
75
+ subject.with_delta_index_lock('foo', &@block)
76
+ end
77
+
78
+ it 'should unlock after yielding' do
79
+ subject.should_receive(:block_called).ordered
80
+ subject.should_receive(:unlock_delta).with('foo').ordered
81
+
82
+ subject.with_delta_index_lock('foo', &@block)
83
+ end
84
+ end
85
+
86
+ describe '#smart_index' do
87
+ include FakeFS::SpecHelpers
88
+
89
+ let(:test_path) { '/tmp/ts-resque-delta/foo' }
90
+
91
+ before :each do
92
+ subject.stub(:ts_config => config)
93
+ config.stub(:config_file => 'foo_config')
94
+ config.stub(:build)
95
+ config.stub(:searchd_file_path => test_path)
96
+ config.stub_chain(:controller, :index) do
97
+ # Set $? to 0
98
+ `/usr/bin/true`
99
+ end
100
+
101
+ # Silence Generating config message
102
+ subject.stub(:puts)
103
+
104
+ subject.stub(:index_prefixes => indices)
105
+ subject.stub(:lock_delta)
106
+ subject.stub(:unlock_delta)
107
+
108
+ ThinkingSphinx::Deltas::ResqueDelta.stub(:prepare_for_core_index)
109
+ Resque.stub(:enqueue)
110
+ end
111
+
112
+ it 'should not generate sphinx configuration if INDEX_ONLY is true' do
113
+ ENV.stub(:[]).with('INDEX_ONLY').and_return('true')
114
+ config.should_not_receive(:build)
115
+
116
+ subject.smart_index
117
+ end
118
+
119
+ it 'should generate sphinx configuration if INDEX_ONLY is not true' do
120
+ ENV.stub(:[]).with('INDEX_ONLY').and_return(nil)
121
+ config.should_receive(:build).once
122
+
123
+ subject.smart_index
124
+ end
125
+
126
+ it 'should create the sphinx file directory' do
127
+ subject.smart_index
128
+
129
+ File.directory?(test_path).should be_true
130
+ end
131
+
132
+ it 'should index all core indices' do
133
+ indices.each do |index|
134
+ config.controller.should_receive(:index).with("#{index}_core", anything)
135
+ end
136
+
137
+ subject.smart_index
138
+ end
139
+
140
+ context "with delta lock" do
141
+ before :each do
142
+ subject.stub(:with_delta_index_lock).and_yield
143
+ end
144
+
145
+ it 'should index' do
146
+ config.controller.should_receive(:index)
147
+ subject.smart_index
148
+ end
149
+
150
+ it 'should signal resque delta to prepare for the core index' do
151
+ ThinkingSphinx::Deltas::ResqueDelta.should_receive(:prepare_for_core_index)
152
+ subject.smart_index
153
+ end
154
+ end
155
+
156
+ context "without delta lock" do
157
+ before :each do
158
+ subject.stub(:with_delta_index_lock)
159
+ end
160
+
161
+ it 'should not index without the delta lock' do
162
+ config.controller.should_not_receive(:index)
163
+ subject.smart_index
164
+ end
165
+
166
+ it 'should not signal resque delta to prepare for the core index' do
167
+ ThinkingSphinx::Deltas::ResqueDelta.should_not_receive(:prepare_for_core_index)
168
+ subject.smart_index
169
+ end
170
+ end
171
+
172
+ it 'should create a delta job after the delta is unlocked' do
173
+ # Create a dummy method on subject that's called when Resque.enqueue is called so we can enforce order.
174
+ subject.stub(:resque_called)
175
+ Resque.stub(:enqueue) { subject.resque_called }
176
+
177
+ Resque.should_receive(:enqueue)
178
+
179
+ subject.should_receive(:with_delta_index_lock).ordered.exactly(indices.size)
180
+ subject.should_receive(:resque_called).ordered.exactly(indices.size)
181
+
182
+ subject.smart_index
183
+ end
184
+
185
+ context 'with an error' do
186
+ before :each do
187
+ config.stub_chain(:controller, :index) do
188
+ # Set $? to 1
189
+ `/usr/bin/false`
190
+ end
191
+ end
192
+
193
+ it 'should stop processing indexes after an error' do
194
+ config.controller.should_receive(:index).once
195
+
196
+ subject.smart_index
197
+ end
198
+
199
+ it 'should return false on failure' do
200
+ subject.smart_index.should be_false
201
+ end
202
+ end
203
+
204
+ it 'should return true on success' do
205
+ subject.smart_index.should be_true
206
+ end
207
+ end
208
+ end