flying-sphinx 0.2.6 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.6
1
+ 0.3.0
data/lib/flying_sphinx.rb CHANGED
@@ -8,6 +8,8 @@ require 'riddle/0.9.9'
8
8
 
9
9
  require 'flying_sphinx/api'
10
10
  require 'flying_sphinx/configuration'
11
+ require 'flying_sphinx/delayed_delta'
12
+ require 'flying_sphinx/flag_as_deleted_job'
11
13
  require 'flying_sphinx/heroku_shared_adapter'
12
14
  require 'flying_sphinx/index_request'
13
15
  require 'flying_sphinx/tunnel'
@@ -0,0 +1,85 @@
1
+ class FlyingSphinx::DelayedDelta < ThinkingSphinx::Deltas::DefaultDelta
2
+ # Adds a job to the queue, if it doesn't already exist. This is to ensure
3
+ # multiple indexing requests for the same delta index don't get added, as the
4
+ # index only needs to be processed once.
5
+ #
6
+ # Because indexing jobs are all the same object, they all get serialised to
7
+ # the same YAML value.
8
+ #
9
+ # @param [Object] object The job, which must respond to the #perform method.
10
+ # @param [Integer] priority (0)
11
+ #
12
+ def self.enqueue(object, priority = 0)
13
+ return if duplicates_exist? object
14
+
15
+ ::Delayed::Job.enqueue(object, :priority => priority)
16
+ end
17
+
18
+ # Checks whether a given job already exists in the queue.
19
+ #
20
+ # @param [Object] object The job
21
+ # @return [Boolean] True if a duplicate of the job already exists in the queue
22
+ #
23
+ def self.duplicates_exist?(object)
24
+ ::Delayed::Job.count(
25
+ :conditions => {
26
+ :handler => object.to_yaml,
27
+ :locked_at => nil
28
+ }
29
+ ) > 0
30
+ end
31
+
32
+ # Adds a job to the queue for processing the given model's delta index. A job
33
+ # for hiding the instance in the core index is also created, if an instance is
34
+ # provided.
35
+ #
36
+ # Neither job will be queued if updates or deltas are disabled, or if the
37
+ # instance (when given) is not toggled to be in the delta index. The first two
38
+ # options are controlled via ThinkingSphinx.updates_enabled? and
39
+ # ThinkingSphinx.deltas_enabled?.
40
+ #
41
+ # @param [Class] model the ActiveRecord model to index.
42
+ # @param [ActiveRecord::Base] instance the instance of the given model that
43
+ # has changed. Optional.
44
+ # @return [Boolean] true
45
+ #
46
+ def index(model, instance = nil)
47
+ return true if skip? instance
48
+
49
+ self.class.enqueue(
50
+ FlyingSphinx::IndexRequest.new(model.delta_index_names),
51
+ delayed_job_priority
52
+ )
53
+
54
+ Delayed::Job.enqueue(
55
+ FlyingSphinx::FlagAsDeletedJob.new(
56
+ model.core_index_names, instance.sphinx_document_id
57
+ ),
58
+ :priority => delayed_job_priority
59
+ ) if instance
60
+
61
+ true
62
+ end
63
+
64
+ private
65
+
66
+ def config
67
+ @config ||= ThinkingSphinx::Configuration.instance
68
+ end
69
+
70
+ def delayed_job_priority
71
+ config.delayed_job_priority
72
+ end
73
+
74
+ # Checks whether jobs should be enqueued. Only true if updates and deltas are
75
+ # enabled, and the instance (if there is one) is toggled.
76
+ #
77
+ # @param [ActiveRecord::Base, NilClass] instance
78
+ # @return [Boolean]
79
+ #
80
+ def skip?(instance)
81
+ !ThinkingSphinx.updates_enabled? ||
82
+ !ThinkingSphinx.deltas_enabled? ||
83
+ (instance && !toggled(instance))
84
+ end
85
+ end
@@ -0,0 +1,45 @@
1
+ # A simple job for flagging a specified Sphinx document in a given index as
2
+ # 'deleted'.
3
+ #
4
+ class FlyingSphinx::FlagAsDeletedJob
5
+ attr_accessor :indices, :document_id
6
+
7
+ # Initialises the object with an index name and document id. Please note that
8
+ # the document id is Sphinx's unique identifier, and will almost certainly not
9
+ # be the model instance's primary key value.
10
+ #
11
+ # @param [String] index The index name
12
+ # @param [Integer] document_id The document id
13
+ #
14
+ def initialize(indices, document_id)
15
+ @indices, @document_id = indices, document_id
16
+ end
17
+
18
+ # Updates the sphinx_deleted attribute for the given document, setting the
19
+ # value to 1 (true). This is not a special attribute in Sphinx, but is used
20
+ # by Thinking Sphinx to ignore deleted values between full re-indexing. It's
21
+ # particularly useful in this situation to avoid old values in the core index
22
+ # and just use the new values in the delta index as a reference point.
23
+ #
24
+ # @return [Boolean] true
25
+ #
26
+ def perform
27
+ indices.each do |index|
28
+ client.update(
29
+ index,
30
+ ['sphinx_deleted'],
31
+ {@document_id => [1]}
32
+ ) if ThinkingSphinx.search_for_id(@document_id, index)
33
+ end
34
+ rescue Riddle::ConnectionError
35
+ # If it fails here, so be it.
36
+ ensure
37
+ true
38
+ end
39
+
40
+ private
41
+
42
+ def client
43
+ @client ||= ThinkingSphinx::Configuration.instance.client
44
+ end
45
+ end
@@ -1,26 +1,60 @@
1
1
  class FlyingSphinx::IndexRequest
2
- attr_reader :configuration, :index_id
2
+ attr_reader :index_id, :indices
3
3
 
4
- def initialize(configuration)
5
- @configuration = configuration
4
+ # Remove all Delta jobs from the queue. If the
5
+ # delayed_jobs table does not exist, this method will do nothing.
6
+ #
7
+ def self.cancel_jobs
8
+ return unless ::Delayed::Job.table_exists?
6
9
 
10
+ ::Delayed::Job.delete_all "handler LIKE '--- !ruby/object:FlyingSphinx::%'"
11
+ end
12
+
13
+ def initialize(indices = [])
14
+ @indices = indices
15
+ end
16
+
17
+ # Shows index name in Delayed::Job#name.
18
+ #
19
+ def display_name
20
+ "#{self.class.name} for #{indices.join(', ')}"
21
+ end
22
+
23
+ def update_and_index
7
24
  update_sphinx_configuration
8
-
9
- FlyingSphinx::Tunnel.connect(configuration) do
10
- begin_request unless request_begun?
11
-
12
- !request_complete?
13
- end
25
+ index
26
+ end
27
+
28
+ # Runs Sphinx's indexer tool to process the index. Currently assumes Sphinx is
29
+ # running.
30
+ #
31
+ # @return [Boolean] true
32
+ #
33
+ def perform
34
+ index
35
+ true
14
36
  end
15
37
 
16
38
  private
17
39
 
40
+ def configuration
41
+ @configuration ||= FlyingSphinx::Configuration.new
42
+ end
43
+
18
44
  def update_sphinx_configuration
19
45
  api.put '/app', :configuration => configuration.sphinx_configuration
20
46
  end
21
47
 
48
+ def index
49
+ FlyingSphinx::Tunnel.connect(configuration) do
50
+ begin_request unless request_begun?
51
+
52
+ !request_complete?
53
+ end
54
+ end
55
+
22
56
  def begin_request
23
- @index_id = api.post('/app/indices')
57
+ @index_id = api.post('/app/indices', :indices => indices.join(','))
24
58
  @request_begun = true
25
59
  end
26
60
 
@@ -1,7 +1,8 @@
1
1
  namespace :fs do
2
2
  task :index => :environment do
3
3
  puts "Starting Index Request"
4
- FlyingSphinx::IndexRequest.new FlyingSphinx::Configuration.new
4
+ FlyingSphinx::IndexRequest.cancel_jobs
5
+ FlyingSphinx::IndexRequest.new.update_and_index
5
6
  puts "Index Request has completed"
6
7
  end
7
8
 
@@ -0,0 +1,146 @@
1
+ require 'spec_helper'
2
+
3
+ describe FlyingSphinx::DelayedDelta do
4
+ describe '.enqueue' do
5
+ before :each do
6
+ Delayed::Job.stub!(:count => 0)
7
+ end
8
+
9
+ it "should enqueue if there's no existing jobs for the same index" do
10
+ Delayed::Job.should_receive(:enqueue)
11
+
12
+ FlyingSphinx::DelayedDelta.enqueue(stub('object'))
13
+ end
14
+
15
+ it "should not enqueue the job if there's an existing job already" do
16
+ Delayed::Job.stub!(:count => 1)
17
+ Delayed::Job.should_not_receive(:enqueue)
18
+
19
+ FlyingSphinx::DelayedDelta.enqueue(stub('object'))
20
+ end
21
+ end
22
+
23
+ describe '#index' do
24
+ let(:config) { ThinkingSphinx::Configuration.instance }
25
+ let(:delayed_delta) { FlyingSphinx::DelayedDelta.new stub('instance'), {} }
26
+ let(:model) {
27
+ stub 'foo',
28
+ :name => 'foo',
29
+ :core_index_names => ['foo_core'],
30
+ :delta_index_names => ['foo_delta']
31
+ }
32
+ let(:instance) { stub('instance', :sphinx_document_id => 42) }
33
+
34
+ before :each do
35
+ ThinkingSphinx.updates_enabled = true
36
+ ThinkingSphinx.deltas_enabled = true
37
+
38
+ config.delayed_job_priority = 2
39
+
40
+ FlyingSphinx::DelayedDelta.stub!(:enqueue => true)
41
+ Delayed::Job.stub!(:enqueue => true, :inspect => 'Delayed::Job')
42
+
43
+ delayed_delta.stub!(:toggled => 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
+ FlyingSphinx::DelayedDelta.should_not_receive(:enqueue)
53
+
54
+ delayed_delta.index model
55
+ end
56
+
57
+ it "should not enqueue a flag as deleted job" do
58
+ Delayed::Job.should_not_receive(:enqueue)
59
+
60
+ delayed_delta.index model
61
+ end
62
+ end
63
+
64
+ context 'deltas disabled' do
65
+ before :each do
66
+ ThinkingSphinx.deltas_enabled = false
67
+ end
68
+
69
+ it "should not enqueue a delta job" do
70
+ FlyingSphinx::DelayedDelta.should_not_receive(:enqueue)
71
+
72
+ delayed_delta.index model
73
+ end
74
+
75
+ it "should not enqueue a flag as deleted job" do
76
+ Delayed::Job.should_not_receive(:enqueue)
77
+
78
+ delayed_delta.index model
79
+ end
80
+ end
81
+
82
+ context "instance isn't toggled" do
83
+ before :each do
84
+ delayed_delta.stub!(:toggled => false)
85
+ end
86
+
87
+ it "should not enqueue a delta job" do
88
+ FlyingSphinx::DelayedDelta.should_not_receive(:enqueue)
89
+
90
+ delayed_delta.index model, instance
91
+ end
92
+
93
+ it "should not enqueue a flag as deleted job" do
94
+ Delayed::Job.should_not_receive(:enqueue)
95
+
96
+ delayed_delta.index model, instance
97
+ end
98
+ end
99
+
100
+ it "should enqueue a delta job for the appropriate indexes" do
101
+ FlyingSphinx::DelayedDelta.should_receive(:enqueue) do |job, priority|
102
+ job.indices.should == ['foo_delta']
103
+ end
104
+
105
+ delayed_delta.index model
106
+ end
107
+
108
+ it "should use the defined priority for the delta job" do
109
+ FlyingSphinx::DelayedDelta.should_receive(:enqueue) do |job, priority|
110
+ priority.should == 2
111
+ end
112
+
113
+ delayed_delta.index model
114
+ end
115
+
116
+ it "should enqueue a flag-as-deleted job for the appropriate indexes" do
117
+ Delayed::Job.should_receive(:enqueue) do |job, options|
118
+ job.indices.should == ['foo_core']
119
+ end
120
+
121
+ delayed_delta.index model, instance
122
+ end
123
+
124
+ it "should enqueue a flag-as-deleted job for the appropriate id" do
125
+ Delayed::Job.should_receive(:enqueue) do |job, options|
126
+ job.document_id.should == 42
127
+ end
128
+
129
+ delayed_delta.index model, instance
130
+ end
131
+
132
+ it "should use the defined priority for the flag-as-deleted job" do
133
+ Delayed::Job.should_receive(:enqueue) do |job, options|
134
+ options[:priority].should == 2
135
+ end
136
+
137
+ delayed_delta.index model, instance
138
+ end
139
+
140
+ it "should not enqueue a flag-as-deleted job if no instance is provided" do
141
+ Delayed::Job.should_not_receive(:enqueue)
142
+
143
+ delayed_delta.index model
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,69 @@
1
+ require 'spec_helper'
2
+
3
+ describe FlyingSphinx::FlagAsDeletedJob do
4
+ describe '#perform' do
5
+ let(:config) { ThinkingSphinx::Configuration.instance }
6
+ let(:client) { stub('client', :update => true) }
7
+ let(:job) { FlyingSphinx::FlagAsDeletedJob.new(['foo_core'], 12) }
8
+
9
+ before :each do
10
+ config.stub!(:client => client)
11
+ ThinkingSphinx.stub!(:search_for_id => true)
12
+ end
13
+
14
+ it "should not update if the document isn't in the index" do
15
+ ThinkingSphinx.stub!(:search_for_id => false)
16
+ client.should_not_receive(:update)
17
+
18
+ job.perform
19
+ end
20
+
21
+ it "should update the specified index" do
22
+ client.should_receive(:update) do |index, attributes, values|
23
+ index.should == 'foo_core'
24
+ end
25
+
26
+ job.perform
27
+ end
28
+
29
+ it "should update all specified indexes" do
30
+ job.indices = ['foo_core', 'bar_core']
31
+ client.should_receive(:update).with('foo_core', anything, anything)
32
+ client.should_receive(:update).with('bar_core', anything, anything)
33
+
34
+ job.perform
35
+ end
36
+
37
+ it "should update the sphinx_deleted attribute" do
38
+ client.should_receive(:update) do |index, attributes, values|
39
+ attributes.should == ['sphinx_deleted']
40
+ end
41
+
42
+ job.perform
43
+ end
44
+
45
+ it "should set sphinx_deleted for the given document to true" do
46
+ client.should_receive(:update) do |index, attributes, values|
47
+ values[12].should == [1]
48
+ end
49
+
50
+ job.perform
51
+ end
52
+
53
+ it "should check for the existence of the document in the specified index" do
54
+ ThinkingSphinx.should_receive(:search_for_id) do |id, index|
55
+ index.should == 'foo_core'
56
+ end
57
+
58
+ job.perform
59
+ end
60
+
61
+ it "should check for the existence of the given document id" do
62
+ ThinkingSphinx.should_receive(:search_for_id) do |id, index|
63
+ id.should == 12
64
+ end
65
+
66
+ job.perform
67
+ end
68
+ end
69
+ end
@@ -2,28 +2,106 @@ require 'spec_helper'
2
2
 
3
3
  describe FlyingSphinx::IndexRequest do
4
4
  let(:api) { FlyingSphinx::API.new 'foo', 'bar' }
5
- let(:configuration) { stub(:configuration, :api => api) }
5
+ let(:configuration) {
6
+ stub(:configuration, :api => api, :sphinx_configuration => 'foo {}')
7
+ }
6
8
 
7
9
  before :each do
10
+ FlyingSphinx::Configuration.stub!(:new => configuration)
8
11
  FlyingSphinx::Tunnel.stub(:connect) { |config, block| block.call }
9
12
  end
10
13
 
11
- describe '#initialize' do
14
+ describe '.cancel_jobs' do
15
+ before :each do
16
+ Delayed::Job.stub!(:delete_all => true)
17
+ end
18
+
19
+ it "should not delete any rows if the delayed_jobs table does not exist" do
20
+ Delayed::Job.stub!(:table_exists? => false)
21
+ Delayed::Job.should_not_receive(:delete_all)
22
+
23
+ FlyingSphinx::IndexRequest.cancel_jobs
24
+ end
25
+
26
+ it "should delete rows if the delayed_jobs table does exist" do
27
+ Delayed::Job.stub!(:table_exists? => true)
28
+ Delayed::Job.should_receive(:delete_all)
29
+
30
+ FlyingSphinx::IndexRequest.cancel_jobs
31
+ end
32
+
33
+ it "should delete only Thinking Sphinx jobs" do
34
+ Delayed::Job.stub!(:table_exists? => true)
35
+ Delayed::Job.should_receive(:delete_all) do |sql|
36
+ sql.should match(/handler LIKE '--- !ruby\/object:FlyingSphinx::\%'/)
37
+ end
38
+
39
+ FlyingSphinx::IndexRequest.cancel_jobs
40
+ end
41
+ end
42
+
43
+ describe '#update_and_index' do
44
+ let(:index_request) { FlyingSphinx::IndexRequest.new }
45
+ let(:conf_params) { {:configuration => 'foo {}'} }
46
+ let(:index_params) { {:indices => ''} }
47
+
12
48
  it "makes a new request" do
13
- api.should_receive(:post).with('/app/indices').and_return(42)
49
+ api.should_receive(:put).with('/app', conf_params).and_return('ok')
50
+ api.should_receive(:post).
51
+ with('/app/indices', index_params).and_return(42)
14
52
  api.should_receive(:get).with('/app/indices/42').and_return('PENDING')
15
53
 
16
54
  begin
17
- Timeout::timeout(0.2) { FlyingSphinx::IndexRequest.new(configuration) }
55
+ Timeout::timeout(0.2) {
56
+ index_request.update_and_index
57
+ }
18
58
  rescue Timeout::Error
19
59
  end
20
60
  end
21
61
 
22
62
  it "should finish when the index request has been completed" do
23
- api.should_receive(:post).with('/app/indices').and_return(42)
63
+ api.should_receive(:put).with('/app', conf_params).and_return('ok')
64
+ api.should_receive(:post).
65
+ with('/app/indices', index_params).and_return(42)
24
66
  api.should_receive(:get).with('/app/indices/42').and_return('FINISHED')
25
67
 
26
- FlyingSphinx::IndexRequest.new(configuration)
68
+ index_request.update_and_index
69
+ end
70
+ end
71
+
72
+ describe '#perform' do
73
+ let(:index_request) { FlyingSphinx::IndexRequest.new ['foo_delta'] }
74
+ let(:index_params) { {:indices => 'foo_delta'} }
75
+
76
+ it "makes a new request" do
77
+ api.should_receive(:post).
78
+ with('/app/indices', index_params).and_return(42)
79
+ api.should_receive(:get).with('/app/indices/42').and_return('PENDING')
80
+
81
+ begin
82
+ Timeout::timeout(0.2) {
83
+ index_request.perform
84
+ }
85
+ rescue Timeout::Error
86
+ end
87
+ end
88
+
89
+ it "should finish when the index request has been completed" do
90
+ api.should_receive(:post).
91
+ with('/app/indices', index_params).and_return(42)
92
+ api.should_receive(:get).with('/app/indices/42').and_return('FINISHED')
93
+
94
+ index_request.perform
95
+ end
96
+ end
97
+
98
+ describe "#display_name" do
99
+ let(:index_request) {
100
+ FlyingSphinx::IndexRequest.new ['foo_core', 'bar_core']
101
+ }
102
+
103
+ it "should display class name with all indexes" do
104
+ index_request.display_name.should == "FlyingSphinx::IndexRequest for foo_core, bar_core"
27
105
  end
28
106
  end
29
107
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: flying-sphinx
3
3
  version: !ruby/object:Gem::Version
4
- hash: 27
4
+ hash: 19
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
- - 2
9
- - 6
10
- version: 0.2.6
8
+ - 3
9
+ - 0
10
+ version: 0.3.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Pat Allan
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-01-01 00:00:00 +11:00
18
+ date: 2011-01-02 00:00:00 +11:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -160,6 +160,22 @@ dependencies:
160
160
  - 2
161
161
  version: 1.2.2
162
162
  requirement: *id009
163
+ - !ruby/object:Gem::Dependency
164
+ type: :development
165
+ prerelease: false
166
+ name: delayed_job
167
+ version_requirements: &id010 !ruby/object:Gem::Requirement
168
+ none: false
169
+ requirements:
170
+ - - "="
171
+ - !ruby/object:Gem::Version
172
+ hash: 15
173
+ segments:
174
+ - 2
175
+ - 1
176
+ - 2
177
+ version: 2.1.2
178
+ requirement: *id010
163
179
  description: Hooks Thinking Sphinx into the Flying Sphinx service
164
180
  email: pat@freelancing-gods.com
165
181
  executables: []
@@ -177,6 +193,8 @@ files:
177
193
  - lib/flying_sphinx.rb
178
194
  - lib/flying_sphinx/api.rb
179
195
  - lib/flying_sphinx/configuration.rb
196
+ - lib/flying_sphinx/delayed_delta.rb
197
+ - lib/flying_sphinx/flag_as_deleted_job.rb
180
198
  - lib/flying_sphinx/heroku_shared_adapter.rb
181
199
  - lib/flying_sphinx/index_request.rb
182
200
  - lib/flying_sphinx/rails.rb
@@ -184,6 +202,8 @@ files:
184
202
  - lib/flying_sphinx/tasks.rb
185
203
  - lib/flying_sphinx/tunnel.rb
186
204
  - spec/flying_sphinx/configuration_spec.rb
205
+ - spec/flying_sphinx/delayed_delta_spec.rb
206
+ - spec/flying_sphinx/flag_as_deleted_job_spec.rb
187
207
  - spec/flying_sphinx/index_request_spec.rb
188
208
  has_rdoc: true
189
209
  homepage: http://flying-sphinx.com
@@ -221,4 +241,6 @@ specification_version: 3
221
241
  summary: Sphinx in the Cloud
222
242
  test_files:
223
243
  - spec/flying_sphinx/configuration_spec.rb
244
+ - spec/flying_sphinx/delayed_delta_spec.rb
245
+ - spec/flying_sphinx/flag_as_deleted_job_spec.rb
224
246
  - spec/flying_sphinx/index_request_spec.rb