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,172 @@
1
+ require 'spec_helper'
2
+
3
+ describe ThinkingSphinx::Deltas::ResqueDelta::DeltaJob do
4
+ subject do
5
+ ThinkingSphinx::Deltas::ResqueDelta::DeltaJob.tap do |s|
6
+ s.stub(:` => true)
7
+ s.stub(:puts => nil)
8
+ end
9
+ end
10
+
11
+ describe '.perform' do
12
+ before :each do
13
+ ThinkingSphinx.suppress_delta_output = false
14
+ ThinkingSphinx::Deltas::ResqueDelta.stub(:locked?).and_return(false)
15
+ end
16
+
17
+ it "should output the delta indexing by default" do
18
+ subject.should_receive(:puts)
19
+ subject.perform('foo_delta')
20
+ end
21
+
22
+ it "should not output the delta indexing if requested" do
23
+ ThinkingSphinx.suppress_delta_output = true
24
+ subject.should_not_receive(:puts)
25
+ subject.perform('foo_delta')
26
+ end
27
+
28
+ it "should process just the requested index" do
29
+ subject.should_receive(:`) do |c|
30
+ c.should match(/foo_delta/)
31
+ c.should_not match(/--all/)
32
+ end
33
+ subject.perform('foo_delta')
34
+ end
35
+
36
+ context 'when an index is locked' do
37
+ before do
38
+ ThinkingSphinx::Deltas::ResqueDelta.stub(:locked?) do |index_name|
39
+ index_name == 'foo_delta' ? true : false
40
+ end
41
+ end
42
+
43
+ it "should not start the indexer" do
44
+ subject.should_not_receive(:`)
45
+ subject.perform('foo_delta')
46
+ end
47
+
48
+ it "should start the indexer for unlocked indexes" do
49
+ subject.should_receive(:`)
50
+ subject.perform('bar_delta')
51
+ end
52
+ end
53
+
54
+ context 'with flag as deleted document ids' do
55
+ let(:client) { stub('client', :update => true) }
56
+ let(:document_ids) { [1, 2, 3] }
57
+ let(:bundled_search) { double('bundled_search', :search_for_ids => []) }
58
+
59
+ before :each do
60
+ ThinkingSphinx.updates_enabled = true
61
+
62
+ ThinkingSphinx::Configuration.instance.stub(:client => client)
63
+ ThinkingSphinx.stub(:sphinx_running? => true)
64
+
65
+ ThinkingSphinx::Deltas::ResqueDelta::FlagAsDeletedSet.stub(:processing_members => document_ids)
66
+ ThinkingSphinx::Search.stub_chain(:bundle_searches, :map => document_ids)
67
+ end
68
+
69
+ it 'should get the processing set of flag as deleted document ids' do
70
+ ThinkingSphinx::Deltas::ResqueDelta::FlagAsDeletedSet.should_receive(:processing_members).with('foo_core')
71
+ subject.perform('foo_delta')
72
+ end
73
+
74
+ it "should not update if Sphinx isn't running" do
75
+ ThinkingSphinx.stub(:sphinx_running? => false)
76
+ client.should_not_receive(:update)
77
+ subject.perform('foo_delta')
78
+ end
79
+
80
+ it "should validate the document ids with sphinx" do
81
+ ThinkingSphinx::Search.stub(:bundle_searches).tap do |s|
82
+ document_ids.inject(s) do |s, id|
83
+ s.and_yield(bundled_search, id)
84
+ end
85
+ end
86
+
87
+ document_ids.each do |id|
88
+ bundled_search.should_receive(:search_for_ids).with([], :index => 'foo_core', :id_range => id..id)
89
+ end
90
+
91
+ subject.perform('foo_delta')
92
+ end
93
+
94
+ context "with invalid ids" do
95
+ before :each do
96
+ ThinkingSphinx::Search.stub_chain(:bundle_searches, :map => document_ids.reject {|x| x == 2} )
97
+ end
98
+
99
+ it "should not update documents that aren't in the index" do
100
+ client.should_receive(:update) do |index, attributes, values|
101
+ values.should_not include(2)
102
+ end
103
+ subject.perform('foo_delta')
104
+ end
105
+
106
+ it "should update documents that are in the index" do
107
+ client.should_receive(:update) do |index, attributes, values|
108
+ values.keys.should eql(document_ids.reject{|x| x == 2})
109
+ end
110
+ subject.perform('foo_delta')
111
+ end
112
+ end
113
+
114
+ it "should update the specified index" do
115
+ client.should_receive(:update) do |index, attributes, values|
116
+ index.should == 'foo_core'
117
+ end
118
+ subject.perform('foo_delta')
119
+ end
120
+
121
+ it "should update the sphinx_deleted attribute" do
122
+ client.should_receive(:update) do |index, attributes, values|
123
+ attributes.should == ['sphinx_deleted']
124
+ end
125
+ subject.perform('foo_delta')
126
+ end
127
+
128
+ it "should set sphinx_deleted for valid documents to true" do
129
+ client.should_receive(:update) do |index, attributes, values|
130
+ document_ids.each {|id| values[id].should == [1] }
131
+ end
132
+ subject.perform('foo_delta')
133
+ end
134
+ end
135
+ end
136
+
137
+ describe '.around_perform_lock1' do
138
+ before :each do
139
+ Resque.stub(:encode => 'DeltaJobsAreAwesome')
140
+ Resque.stub_chain(:redis, :lrem)
141
+ ThinkingSphinx::Deltas::ResqueDelta::FlagAsDeletedSet.stub(:get_subset_for_processing)
142
+ ThinkingSphinx::Deltas::ResqueDelta::FlagAsDeletedSet.stub(:clear_processing)
143
+ end
144
+
145
+ it 'should clear all other delta jobs' do
146
+ Resque.redis.should_receive(:lrem).with("queue:#{subject.instance_variable_get(:@queue)}", 0, 'DeltaJobsAreAwesome')
147
+
148
+ subject.around_perform_lock1('foo_delta') {}
149
+ end
150
+
151
+ it 'should set up the processing set of document ids' do
152
+ ThinkingSphinx::Deltas::ResqueDelta::FlagAsDeletedSet.should_receive(:get_subset_for_processing).with('foo_core')
153
+
154
+ subject.around_perform_lock1('foo_delta') {}
155
+ end
156
+
157
+ it 'should clear the processing set when finished' do
158
+ ThinkingSphinx::Deltas::ResqueDelta::FlagAsDeletedSet.should_receive(:clear_processing).with('foo_core')
159
+
160
+ subject.around_perform_lock1('foo_delta') {}
161
+ end
162
+ end
163
+
164
+ describe '.lock_failed' do
165
+ it 'should enqueue the delta job again' do
166
+ Resque.stub(:enqueue => true)
167
+ Resque.should_receive(:enqueue)
168
+
169
+ subject.lock_failed('foo_delta')
170
+ end
171
+ end
172
+ end
@@ -0,0 +1,126 @@
1
+ require 'spec_helper'
2
+
3
+ describe ThinkingSphinx::Deltas::ResqueDelta::FlagAsDeletedSet do
4
+ describe '.add' do
5
+ before :each do
6
+ Resque.stub_chain(:redis, :sadd => true)
7
+ end
8
+
9
+ it 'should add the document id to the correct set' do
10
+ Resque.redis.should_receive(:sadd).once.with(subject.set_name('foo_core'), 42)
11
+ subject.add('foo_core', 42)
12
+ end
13
+ end
14
+
15
+ describe '.clear!' do
16
+ before :each do
17
+ Resque.stub_chain(:redis, :del)
18
+ ThinkingSphinx::Deltas::ResqueDelta::DeltaJob.stub(:around_perform_lock)
19
+ end
20
+
21
+ it 'should delete all items in the set' do
22
+ Resque.redis.should_receive(:del).once.with(subject.set_name('foo_core'))
23
+ subject.clear!('foo_core')
24
+ end
25
+
26
+ context "with DeltaJob integration" do
27
+ before :each do
28
+ ThinkingSphinx::Deltas::ResqueDelta::DeltaJob.stub(:around_perform_lock).and_yield
29
+ end
30
+
31
+ it 'should acquire the DeltaJob lock' do
32
+ ThinkingSphinx::Deltas::ResqueDelta::DeltaJob.should_receive(:around_perform_lock).once.with('foo_delta')
33
+ subject.clear!('foo_core')
34
+ end
35
+
36
+ it 'should delete all items in the processing set' do
37
+ Resque.redis.should_receive(:del).once.with(subject.processing_name('foo_core'))
38
+ subject.clear!('foo_core')
39
+ end
40
+ end
41
+ end
42
+
43
+ describe '.clear_all!' do
44
+ let(:core_indices) { %w[foo_core bar_core] }
45
+
46
+ it 'should clear each index' do
47
+ ThinkingSphinx::Deltas::ResqueDelta::IndexUtils.stub_chain(:core_indices, :each).tap do |s|
48
+ core_indices.inject(s) do |s, index|
49
+ s.and_yield(index)
50
+ end
51
+ end
52
+
53
+ core_indices.each do |index|
54
+ subject.should_receive(:clear!).with(index)
55
+ end
56
+
57
+ subject.clear_all!
58
+ end
59
+ end
60
+
61
+ describe '.get_subset_for_processing' do
62
+ let(:mock_redis) do
63
+ Resque.redis = mr = MockRedis.new
64
+ subject.add 'foo_core', 42
65
+ subject.add 'foo_core', 52
66
+ subject.add 'foo_core', 100
67
+ mr
68
+ end
69
+
70
+ before :each do
71
+ Resque.redis = mock_redis.clone
72
+ end
73
+
74
+ it 'should move all members from the flag as deleted set to the processing set' do
75
+ subject.get_subset_for_processing('foo_core')
76
+
77
+ Resque.redis.scard(subject.set_name('foo_core')).should eql(0)
78
+ Resque.redis.scard(subject.processing_name('foo_core')).should eql(3)
79
+ end
80
+
81
+ it 'should remove the temp set' do
82
+ subject.get_subset_for_processing('foo_core')
83
+
84
+ Resque.redis.scard(subject.temp_name('foo_core')).should eql(0)
85
+ end
86
+
87
+ it 'should preserve existing members of the processing set' do
88
+ Resque.redis.sadd(subject.processing_name('foo_core'), 1)
89
+
90
+ subject.get_subset_for_processing('foo_core')
91
+
92
+ Resque.redis.smembers(subject.processing_name('foo_core')).should == %w[1 100 52 42]
93
+ end
94
+ end
95
+
96
+ describe '.processing_members' do
97
+ let(:document_ids) { %w[1, 2, 3] }
98
+
99
+ before :each do
100
+ Resque.stub_chain(:redis, :smembers => document_ids)
101
+ end
102
+
103
+ it 'should get the members of the correct set' do
104
+ Resque.redis.should_receive(:smembers).once.with(subject.processing_name('foo_core'))
105
+ subject.processing_members('foo_core')
106
+ end
107
+
108
+ it 'should return a list of integers' do
109
+ subject.processing_members('foo_core').each do |id|
110
+ id.class.should == Fixnum
111
+ end
112
+ end
113
+ end
114
+
115
+ describe '.clear_processing' do
116
+ before :each do
117
+ Resque.stub_chain(:redis, :del)
118
+ end
119
+
120
+ it 'should delete the processing set' do
121
+ Resque.redis.should_receive(:del).once.with(subject.processing_name('foo_core'))
122
+
123
+ subject.clear_processing('foo_core')
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,67 @@
1
+ require 'spec_helper'
2
+
3
+ describe ThinkingSphinx::Deltas::ResqueDelta::IndexUtils do
4
+ let(:indices) { %w[foo_core foo_delta foo bar_core bar_delta bar] }
5
+ let(:config) { double('config') }
6
+
7
+ before :each do
8
+ ThinkingSphinx::Configuration.stub(:instance => config)
9
+ config.stub(:generate)
10
+ config.stub_chain(:configuration, :indices, :collect => indices)
11
+
12
+ subject.reload!
13
+ end
14
+
15
+ describe '.index_prefixes' do
16
+ it 'should use a cached value if one exists' do
17
+ indices = []
18
+ subject.instance_variable_set(:@prefixes, indices)
19
+
20
+ subject.index_prefixes.should be(indices)
21
+ end
22
+
23
+ it 'should return a list of only index prefixes' do
24
+ subject.index_prefixes.should =~ %w[foo bar]
25
+ end
26
+ end
27
+
28
+ describe '.core_indices' do
29
+ it 'should use a cached value if one exists' do
30
+ indices = []
31
+ subject.instance_variable_set(:@core_indices, indices)
32
+
33
+ subject.core_indices.should be(indices)
34
+ end
35
+
36
+ it 'should return a list of only core indices' do
37
+ subject.core_indices.should =~ %w[foo_core bar_core]
38
+ end
39
+ end
40
+
41
+ describe '.delta_indices' do
42
+ it 'should use a cached value if one exists' do
43
+ indices = []
44
+ subject.instance_variable_set(:@delta_indices, indices)
45
+
46
+ subject.delta_indices.should be(indices)
47
+ end
48
+
49
+ it 'should return a list of only delta indices' do
50
+ subject.delta_indices.should =~ %w[foo_delta bar_delta]
51
+ end
52
+ end
53
+
54
+ describe '.ts_config' do
55
+ it 'should use a cached value if one exists' do
56
+ subject.instance_variable_set(:@ts_config, config)
57
+
58
+ subject.ts_config.should be(config)
59
+ end
60
+
61
+ it 'should generate the config when fetching the Configuration instance' do
62
+ config.should_receive(:generate)
63
+
64
+ subject.ts_config
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,191 @@
1
+ require 'spec_helper'
2
+
3
+ describe ThinkingSphinx::Deltas::ResqueDelta do
4
+ before :each do
5
+ ThinkingSphinx.updates_enabled = true
6
+ ThinkingSphinx.deltas_enabled = true
7
+
8
+ Resque.redis = MockRedis.new
9
+ end
10
+
11
+ describe '#index' do
12
+ def flag_as_deleted_document_in_set?
13
+ Resque.redis.sismember(ThinkingSphinx::Deltas::ResqueDelta::FlagAsDeletedSet.set_name('foo_core'), 42)
14
+ end
15
+
16
+ subject do
17
+ ThinkingSphinx::Deltas::ResqueDelta.new(
18
+ stub('instance'), {}
19
+ ).tap do |s|
20
+ s.stub(:toggled).and_return(true)
21
+ s.stub(:lock)
22
+ s.stub(:unlock)
23
+ s.stub(:locked?).and_return(false)
24
+ end
25
+ end
26
+
27
+ let(:model) do
28
+ stub('foo').tap do |m|
29
+ m.stub(:name => 'foo')
30
+ m.stub(:source_of_sphinx_index => m)
31
+ m.stub(:core_index_names => ['foo_core'])
32
+ m.stub(:delta_index_names => ['foo_delta'])
33
+ end
34
+ end
35
+
36
+ let(:instance) do
37
+ stub('instance').tap do |i|
38
+ i.stub(:sphinx_document_id => 42)
39
+ end
40
+ end
41
+
42
+ before :each do
43
+ Resque.stub(:enqueue => true)
44
+ end
45
+
46
+ context 'updates disabled' do
47
+ before :each do
48
+ ThinkingSphinx.updates_enabled = false
49
+ end
50
+
51
+ it "should not enqueue a delta job" do
52
+ Resque.should_not_receive(:enqueue)
53
+ subject.index(model)
54
+ end
55
+
56
+ it "should not add a flag as deleted document to the set" do
57
+ subject.index(model, instance)
58
+ flag_as_deleted_document_in_set?.should be_false
59
+ end
60
+ end
61
+
62
+ context 'deltas disabled' do
63
+ before :each do
64
+ ThinkingSphinx.deltas_enabled = false
65
+ end
66
+
67
+ it "should not enqueue a delta job" do
68
+ Resque.should_not_receive(:enqueue)
69
+ subject.index(model)
70
+ end
71
+
72
+ it "should not add a flag as deleted document to the set" do
73
+ subject.index(model, instance)
74
+ flag_as_deleted_document_in_set?.should be_false
75
+ end
76
+ end
77
+
78
+ context "instance isn't toggled" do
79
+ before :each do
80
+ subject.stub(:toggled => false)
81
+ end
82
+
83
+ it "should not enqueue a delta job" do
84
+ Resque.should_not_receive(:enqueue)
85
+ subject.index(model, instance)
86
+ end
87
+
88
+ it "should not add a flag as deleted document to the set" do
89
+ subject.index(model, instance)
90
+ flag_as_deleted_document_in_set?.should be_false
91
+ end
92
+ end
93
+
94
+ it "should enqueue a delta job" do
95
+ Resque.should_receive(:enqueue).once.with(
96
+ ThinkingSphinx::Deltas::ResqueDelta::DeltaJob,
97
+ 'foo_delta'
98
+ )
99
+ subject.index(model)
100
+ end
101
+
102
+ it "should add the flag as deleted document id to the set" do
103
+ subject.index(model, instance)
104
+ flag_as_deleted_document_in_set?.should be_true
105
+ end
106
+
107
+ context "delta index is locked" do
108
+ before :each do
109
+ ThinkingSphinx::Deltas::ResqueDelta.stub(:locked?).and_return(true)
110
+ end
111
+
112
+ it "should not enqueue a delta job" do
113
+ Resque.should_not_receive(:enqueue)
114
+ subject.index(model, instance)
115
+ end
116
+
117
+ it "should add the flag as deleted document id to the set" do
118
+ subject.index(model, instance)
119
+ flag_as_deleted_document_in_set?.should be_true
120
+ end
121
+ end
122
+ end
123
+
124
+ describe '.clear_thinking_sphinx_queues' do
125
+ subject { ThinkingSphinx::Deltas::ResqueDelta.clear_thinking_sphinx_queues }
126
+
127
+ before :all do
128
+ class RandomJob
129
+ @queue = 'ts_delta'
130
+ end
131
+ end
132
+
133
+ before :each do
134
+ Resque.enqueue(ThinkingSphinx::Deltas::ResqueDelta::DeltaJob, 'foo_delta')
135
+ Resque.enqueue(ThinkingSphinx::Deltas::ResqueDelta::DeltaJob, 'bar_delta')
136
+ Resque.enqueue(RandomJob, '1234')
137
+ end
138
+
139
+ it 'should remove all jobs' do
140
+ subject
141
+ Resque.size('ts_delta').should eq(0)
142
+ end
143
+ end
144
+
145
+ describe '.lock' do
146
+ it 'should set the lock key in redis' do
147
+ ThinkingSphinx::Deltas::ResqueDelta.lock('foo')
148
+ Resque.redis.get("#{ThinkingSphinx::Deltas::ResqueDelta.job_prefix}:index:foo:locked").should eql('true')
149
+ end
150
+ end
151
+
152
+ describe '.unlock' do
153
+ it 'should unset the lock key in redis' do
154
+ Resque.redis.set("#{ThinkingSphinx::Deltas::ResqueDelta.job_prefix}:index:foo:locked", 'true')
155
+ ThinkingSphinx::Deltas::ResqueDelta.unlock('foo')
156
+ Resque.redis.get("#{ThinkingSphinx::Deltas::ResqueDelta.job_prefix}:index:foo:locked").should be_nil
157
+ end
158
+ end
159
+
160
+ describe '.locked?' do
161
+ subject { ThinkingSphinx::Deltas::ResqueDelta.locked?('foo') }
162
+
163
+ context "when lock key in redis is true" do
164
+ before { Resque.redis.set("#{ThinkingSphinx::Deltas::ResqueDelta.job_prefix}:index:foo:locked", 'true') }
165
+ it { should be_true }
166
+ end
167
+
168
+ context "when lock key in redis is nil" do
169
+ it { should be_false }
170
+ end
171
+ end
172
+
173
+ describe '.prepare_for_core_index' do
174
+ subject { ThinkingSphinx::Deltas::ResqueDelta.prepare_for_core_index('foo') }
175
+
176
+ before :each do
177
+ Resque.stub(:dequeue)
178
+ ThinkingSphinx::Deltas::ResqueDelta::FlagAsDeletedSet.stub(:clear!)
179
+ end
180
+
181
+ it "should call FlagAsDeletedSet.clear!" do
182
+ ThinkingSphinx::Deltas::ResqueDelta::FlagAsDeletedSet.should_receive(:clear!).with('foo_core')
183
+ subject
184
+ end
185
+
186
+ it "should clear delta jobs" do
187
+ Resque.should_receive(:dequeue).with(ThinkingSphinx::Deltas::ResqueDelta::DeltaJob, 'foo_delta')
188
+ subject
189
+ end
190
+ end
191
+ end