ts-sidekiq-delta 0.1.0

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 (40) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +16 -0
  3. data/.rspec +1 -0
  4. data/Gemfile +10 -0
  5. data/Guardfile +17 -0
  6. data/LICENSE +20 -0
  7. data/README.markdown +36 -0
  8. data/Rakefile +16 -0
  9. data/config/redis-cucumber.conf +13 -0
  10. data/cucumber.yml +2 -0
  11. data/features/sidekiq_deltas.feature +62 -0
  12. data/features/smart_indexing.feature +43 -0
  13. data/features/step_definitions/common_steps.rb +76 -0
  14. data/features/step_definitions/sidekiq_delta_steps.rb +33 -0
  15. data/features/step_definitions/smart_indexing_steps.rb +3 -0
  16. data/features/support/env.rb +31 -0
  17. data/features/thinking_sphinx/database.example.yml +3 -0
  18. data/features/thinking_sphinx/db/migrations/create_delayed_betas.rb +4 -0
  19. data/features/thinking_sphinx/models/delayed_beta.rb +6 -0
  20. data/lib/thinking_sphinx/deltas/sidekiq_delta.rb +64 -0
  21. data/lib/thinking_sphinx/deltas/sidekiq_delta/delta_job.rb +51 -0
  22. data/lib/thinking_sphinx/deltas/sidekiq_delta/flag_as_deleted_job.rb +20 -0
  23. data/lib/thinking_sphinx/deltas/sidekiq_delta/railtie.rb +5 -0
  24. data/lib/thinking_sphinx/deltas/sidekiq_delta/tasks.rb +38 -0
  25. data/lib/ts-sidekiq-delta.rb +2 -0
  26. data/spec/acceptance/sidekiq_deltas_spec.rb +50 -0
  27. data/spec/acceptance/spec_helper.rb +5 -0
  28. data/spec/acceptance/support/database_cleaner.rb +11 -0
  29. data/spec/acceptance/support/sphinx_controller.rb +39 -0
  30. data/spec/acceptance/support/sphinx_helpers.rb +39 -0
  31. data/spec/internal/.gitignore +2 -0
  32. data/spec/internal/app/indices/book_index.rb +3 -0
  33. data/spec/internal/app/models/book.rb +3 -0
  34. data/spec/internal/config/database.yml +5 -0
  35. data/spec/internal/db/schema.rb +24 -0
  36. data/spec/internal/log/.gitignore +1 -0
  37. data/spec/spec_helper.rb +17 -0
  38. data/tasks/rails.rake +1 -0
  39. data/ts-sidekiq-delta.gemspec +26 -0
  40. metadata +204 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 69d263abd6456994f89536fed3e8ab516e91d029
4
+ data.tar.gz: d8068a4e447382b7b8c95f08702ba8b7988a13f3
5
+ SHA512:
6
+ metadata.gz: 7ad79d13705125cdbbc5b7d171c7de7a3de5411189e36845adf9b5db7a4c9a9187c8eb0d91fcf32db56d6e5df377a8762541def04441c43b4c4f490a916653d5
7
+ data.tar.gz: 31e37ee5b4ae26931e4b0d18eeb275a5ea251c4dac16e6ee9deaebc0332250e3e73fa79431f4a1e254010c44e659341e7c6a8fb5e8341c3db3b9610e624aa5fe
@@ -0,0 +1,16 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ *~
6
+ .DS_Store
7
+ *.tmproj
8
+ tmtags
9
+ *~
10
+ \#*
11
+ .\#*
12
+ *.swp
13
+ coverage
14
+ rdoc
15
+ features/thinking_sphinx/database.yml
16
+ tmp
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source :rubygems
2
+
3
+ gemspec
4
+
5
+ gem 'thinking-sphinx',
6
+ :git => 'git://github.com/pat/thinking-sphinx.git',
7
+ :branch => 'edge'
8
+ gem 'combustion',
9
+ :git => 'git://github.com/pat/combustion.git',
10
+ :ref => '45f50e64c3'
@@ -0,0 +1,17 @@
1
+ guard 'bundler' do
2
+ watch('Gemfile')
3
+ watch(/^.+\.gemspec/)
4
+ end
5
+
6
+ guard 'rspec', :version => 2, :cli => "-c --format progress", :all_on_start => false do
7
+ watch(%r{^spec/.+_spec\.rb$})
8
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
9
+ watch('spec/spec_helper.rb') { "spec" }
10
+ end
11
+
12
+ guard 'cucumber', :all_on_start => false do
13
+ watch(%r{^features/.+\.feature$})
14
+ watch(%r{^features/support/.+$}) { 'features' }
15
+ watch(%r{^features/step_definitions/(.+)_steps\.rb$}) { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'features' }
16
+ end
17
+
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Pat Allan
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,36 @@
1
+ # Delayed Deltas for Thinking Sphinx with Sidekiq
2
+
3
+ This code was heavily based on Aaron Gibralter's [ts-resque-delta](https://github.com/agibralter/ts-resque-delta), and was initially adapted for Sidekiq by [Danny Hawkins](https://github.com/danhawkins). This release is maintained by [Pat Allan](https://github.com/pat).
4
+
5
+ This version of `ts-sidekiq-delta` works only with [Thinking Sphinx](https://github.com/pat/thinking-sphinx) v3 or newer. v1/v2 releases are not supported, and almost certainly will never be. It does work with the Flying Sphinx service, provided you're using 1.0.0 or newer of the `flying-sphinx` gem.
6
+
7
+ ## Installation
8
+
9
+ Get it into your Gemfile - and don't forget the version constraint!
10
+
11
+ gem 'ts-sidekiq-delta', '~> 0.1.0'
12
+
13
+ ## Usage
14
+
15
+ In your index definitions, you'll want to include the delta setting as an initial option:
16
+
17
+ ThinkingSphinx::Index.define(:article,
18
+ :with => :active_record,
19
+ :delta => ThinkingSphinx::Deltas::SidekiqDelta
20
+ ) do
21
+ # fields and attributes and such
22
+ end
23
+
24
+ If you've never used delta indexes before, you'll want to add the boolean
25
+ column named `:delta` to each model's table and a corresponding database index:
26
+
27
+ def change
28
+ add_column :articles, :delta, :boolean, :default => true, :null => false
29
+ add_index :articles, :delta
30
+ end
31
+
32
+ From here on in, just use Thinking Sphinx and Sidekiq as you normally would, and you'll find your Sphinx indices are updated quite promptly by Sidekiq.
33
+
34
+ ## Licence
35
+
36
+ Copyright (c) 2013, ts-sidekiq-delta was originally developed by Danny Hawkins, is currently maintained by Pat Allan, and is released under the open MIT Licence.
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+ require 'rspec/core/rake_task'
4
+ require 'cucumber'
5
+ require 'cucumber/rake/task'
6
+
7
+ RSpec::Core::RakeTask.new(:spec) do |t|
8
+ t.rspec_opts = ["-c", "--format progress"]
9
+ end
10
+
11
+ Cucumber::Rake::Task.new(:features) do |t|
12
+ end
13
+
14
+ task :all_tests => [:spec, :features]
15
+
16
+ task :default => :all_tests
@@ -0,0 +1,13 @@
1
+ daemonize yes
2
+ pidfile tmp/redis-cucumber.pid
3
+ port 6398
4
+ timeout 300
5
+ save 900 1
6
+ save 300 10
7
+ save 60 10000
8
+ dbfilename tmp/redis-cucumber-dump.rdb
9
+ dir .
10
+ loglevel debug
11
+ logfile stdout
12
+ databases 16
13
+ glueoutputbuf yes
@@ -0,0 +1,2 @@
1
+ ---
2
+ default: --color --format progress --strict
@@ -0,0 +1,62 @@
1
+ Feature: Resque Delta Indexing
2
+ In order to have delta indexing on frequently-updated sites
3
+ Developers
4
+ Should be able to use Resque to handle delta indices to lower system load
5
+
6
+ Background:
7
+ Given Sphinx is running
8
+ And I am searching on delayed betas
9
+ And I have data and it has been indexed
10
+
11
+ Scenario: Delta Index should not fire automatically
12
+ When I search for one
13
+ Then I should get 1 result
14
+
15
+ When I change the name of delayed beta one to eleven
16
+ And I wait for Sphinx to catch up
17
+ And I search for one
18
+ Then I should get 1 result
19
+
20
+ When I search for eleven
21
+ Then I should get 0 results
22
+
23
+ Scenario: Delta Index should fire when jobs are run
24
+ When I search for one
25
+ Then I should get 1 result
26
+
27
+ When I change the name of delayed beta two to twelve
28
+ And I wait for Sphinx to catch up
29
+ And I search for twelve
30
+ Then I should get 0 results
31
+
32
+ When I run the delayed jobs
33
+ And I wait for Sphinx to catch up
34
+ And I search for twelve
35
+ Then I should get 1 result
36
+
37
+ When I search for two
38
+ Then I should get 0 results
39
+
40
+ Scenario: ensuring that duplicate jobs are deleted
41
+ When I change the name of delayed beta two to fifty
42
+ And I change the name of delayed beta five to twelve
43
+ And I change the name of delayed beta one to fifteen
44
+ And I change the name of delayed beta six to twenty
45
+ And I run one delayed job
46
+ Then there should be no more DeltaJobs on the Resque queue
47
+
48
+ When I run the delayed jobs
49
+ And I wait for Sphinx to catch up
50
+ And I search for fifty
51
+ Then I should get 1 result
52
+
53
+ When I search for two
54
+ Then I should get 0 results
55
+
56
+ Scenario: canceling jobs
57
+ When I change the name of delayed beta two to fifty
58
+ And I cancel the jobs
59
+ And I run the delayed jobs
60
+ And I wait for Sphinx to catch up
61
+ And I search for fifty
62
+ Then I should get 0 results
@@ -0,0 +1,43 @@
1
+ Feature: Smart Indexing
2
+ In order to have core indexing that works well with resque delta indexing
3
+ Developers
4
+ Should be able to use smart index to update core indices
5
+
6
+ Background:
7
+ Given Sphinx is running
8
+ And I am searching on delayed betas
9
+ And I have data
10
+
11
+ Scenario: Smart indexing should update core indices
12
+ When I run the smart indexer
13
+ And I wait for Sphinx to catch up
14
+ And I search for one
15
+ Then I should get 1 result
16
+
17
+ Scenario: Smart indexing should reset the delta index
18
+ Given I have indexed
19
+ When I change the name of delayed beta one to eleven
20
+ And I run the delayed jobs
21
+ And I wait for Sphinx to catch up
22
+
23
+ When I change the name of delayed beta eleven to one
24
+ And I run the smart indexer
25
+ And I run the delayed jobs
26
+ And I wait for Sphinx to catch up
27
+
28
+ When I search for eleven
29
+ Then I should get 0 results
30
+
31
+ Scenario: Delta Index running after smart indexing should not hide records
32
+ When I run the smart indexer
33
+ And I run the delayed jobs
34
+ And I wait for Sphinx to catch up
35
+
36
+ When I search for two
37
+ Then I should get 1 result
38
+
39
+ Scenario: Smart index should remove existing delta jobs
40
+ When I run the smart indexer
41
+ And I run one delayed job
42
+ And I wait for Sphinx to catch up
43
+ Then there should be no more DeltaJobs on the Resque queue
@@ -0,0 +1,76 @@
1
+ Before do
2
+ $queries_executed = []
3
+ ThinkingSphinx::Deltas::SidekiqDelta.clear!
4
+ @model = nil
5
+ @method = :search
6
+ @query = ""
7
+ @conditions = {}
8
+ @with = {}
9
+ @without = {}
10
+ @with_all = {}
11
+ @options = {}
12
+ @results = nil
13
+ end
14
+
15
+ Given "Sphinx is running" do
16
+ ThinkingSphinx::Configuration.instance.controller.should be_running
17
+ end
18
+
19
+ Given /^I am searching on (.+)$/ do |model|
20
+ @model = model.gsub(/\s/, '_').singularize.camelize.constantize
21
+ end
22
+
23
+ Given "I have data" do
24
+ DelayedBeta.create(:name => "one")
25
+ DelayedBeta.create(:name => "two")
26
+ DelayedBeta.create(:name => "three")
27
+ DelayedBeta.create(:name => "four")
28
+ DelayedBeta.create(:name => "five")
29
+ DelayedBeta.create(:name => "six")
30
+ DelayedBeta.create(:name => "seven")
31
+ DelayedBeta.create(:name => "eight")
32
+ DelayedBeta.create(:name => "nine")
33
+ DelayedBeta.create(:name => "ten")
34
+ end
35
+
36
+ Given "I have indexed" do
37
+ ThinkingSphinx::Deltas::SidekiqDelta.clear!
38
+ ThinkingSphinx::Configuration.instance.controller.index
39
+ sleep(1.5)
40
+ end
41
+
42
+ Given "I have data and it has been indexed" do
43
+ step "I have data"
44
+ step "I have indexed"
45
+ end
46
+
47
+ When "I wait for Sphinx to catch up" do
48
+ sleep(0.5)
49
+ end
50
+
51
+ When /^I search for (\w+)$/ do |query|
52
+ @results = nil
53
+ @query = query
54
+ end
55
+
56
+ Then /^I should get (\d+) results?$/ do |count|
57
+ results.length.should == count.to_i
58
+ end
59
+
60
+ Then /^I debug$/ do
61
+ debugger
62
+ 0
63
+ end
64
+
65
+ def results
66
+ @results ||= (@model || ThinkingSphinx).send(
67
+ @method,
68
+ @query,
69
+ @options.merge(
70
+ :conditions => @conditions,
71
+ :with => @with,
72
+ :without => @without,
73
+ :with_all => @with_all
74
+ )
75
+ )
76
+ end
@@ -0,0 +1,33 @@
1
+ When /^I run the delayed jobs$/ do
2
+ unless @resque_worker
3
+ @resque_worker = Resque::Worker.new("ts_delta")
4
+ @resque_worker.register_worker
5
+ end
6
+ while job = @resque_worker.reserve
7
+ @resque_worker.perform(job)
8
+ end
9
+ end
10
+
11
+ When /^I run one delayed job$/ do
12
+ unless @resque_worker
13
+ @resque_worker = Resque::Worker.new("ts_delta")
14
+ @resque_worker.register_worker
15
+ end
16
+ job = @resque_worker.reserve
17
+ @resque_worker.perform(job)
18
+ end
19
+
20
+ When /^I cancel the jobs$/ do
21
+ ThinkingSphinx::Deltas::SidekiqDelta.clear!
22
+ end
23
+
24
+ When /^I change the name of delayed beta (\w+) to (\w+)$/ do |current, replacement|
25
+ DelayedBeta.find_by_name(current).update_attributes(:name => replacement)
26
+ end
27
+
28
+ Then /^there should be no more DeltaJobs on the Resque queue$/ do
29
+ job_classes = Resque.redis.lrange("queue:ts_delta", 0, -1).collect do |j|
30
+ Resque.decode(j)["class"]
31
+ end
32
+ job_classes.should_not include("ThinkingSphinx::Deltas::SidekiqDelta::DeltaJob")
33
+ end
@@ -0,0 +1,3 @@
1
+ When /^I run the smart indexer$/ do
2
+ ThinkingSphinx::Deltas::SidekiqDelta::CoreIndex.new.smart_index(:verbose => false)
3
+ end
@@ -0,0 +1,31 @@
1
+ require 'cucumber'
2
+ require 'rspec/expectations'
3
+ require 'fileutils'
4
+ require 'active_record'
5
+ require 'mock_redis'
6
+
7
+ PROJECT_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..', '..'))
8
+
9
+ $:.unshift(File.join(PROJECT_ROOT, 'lib'))
10
+ $:.unshift(File.dirname(__FILE__))
11
+
12
+ require 'cucumber/thinking_sphinx/internal_world'
13
+
14
+ ActiveRecord::Base.default_timezone = :utc
15
+
16
+ world = Cucumber::ThinkingSphinx::InternalWorld.new
17
+ world.configure_database
18
+
19
+ require 'thinking_sphinx'
20
+ require 'thinking_sphinx/deltas/resque_delta'
21
+
22
+ world.setup
23
+
24
+ Resque.redis = MockRedis.new
25
+ Before do
26
+ Sidekiq.redis{|r| r.flushall}
27
+ end
28
+
29
+ require 'database_cleaner'
30
+ require 'database_cleaner/cucumber'
31
+ DatabaseCleaner.strategy = :truncation
@@ -0,0 +1,3 @@
1
+ username: root
2
+ host: localhost
3
+ password:
@@ -0,0 +1,4 @@
1
+ ActiveRecord::Base.connection.create_table :delayed_betas, :force => true do |t|
2
+ t.column :name, :string, :null => false
3
+ t.column :delta, :boolean, :null => false, :default => false
4
+ end
@@ -0,0 +1,6 @@
1
+ class DelayedBeta < ActiveRecord::Base
2
+ define_index do
3
+ indexes :name, :sortable => true
4
+ set_property :delta => ThinkingSphinx::Deltas::SidekiqDelta
5
+ end
6
+ end
@@ -0,0 +1,64 @@
1
+ require 'sidekiq'
2
+ require 'thinking_sphinx'
3
+
4
+ class ThinkingSphinx::Deltas::SidekiqDelta < ThinkingSphinx::Deltas::DefaultDelta
5
+ JOB_TYPES = []
6
+ JOB_PREFIX = 'ts-delta'
7
+
8
+ # LTRIM + LPOP deletes all items from the Resque queue without loading it
9
+ # into client memory (unlike Resque.dequeue).
10
+ # WARNING: This will clear ALL jobs in any queue used by a ResqueDelta job.
11
+ # If you're sharing a queue with other jobs they'll be deleted!
12
+ def self.clear_thinking_sphinx_queues
13
+ JOB_TYPES.collect { |job|
14
+ job.sidekiq_options['queue']
15
+ }.uniq.each do |queue|
16
+ Sidekiq.redis { |redis| redis.srem "queues", queue }
17
+ Sidekiq.redis { |redis| redis.del "queue:#{queue}" }
18
+ end
19
+ end
20
+
21
+ # Clear both the resque queues and any other state maintained in redis
22
+ def self.clear!
23
+ self.clear_thinking_sphinx_queues
24
+
25
+ FlagAsDeletedSet.clear_all!
26
+ end
27
+
28
+ # Use simplistic locking. We're assuming that the user won't run more than one
29
+ # `rake ts:si` or `rake ts:in` task at a time.
30
+ def self.lock(index_name)
31
+ Sidekiq.redis {|redis|
32
+ redis.set("#{JOB_PREFIX}:index:#{index_name}:locked", 'true')
33
+ }
34
+ end
35
+
36
+ def self.unlock(index_name)
37
+ Sidekiq.redis {|redis|
38
+ redis.del("#{JOB_PREFIX}:index:#{index_name}:locked")
39
+ }
40
+ end
41
+
42
+ def self.locked?(index_name)
43
+ Sidekiq.redis {|redis|
44
+ redis.get("#{JOB_PREFIX}:index:#{index_name}:locked") == 'true'
45
+ }
46
+ end
47
+
48
+ def delete(index, instance)
49
+ return if self.class.locked?(index.reference)
50
+
51
+ ThinkingSphinx::Deltas::SidekiqDelta::FlagAsDeletedJob.perform_async(
52
+ index.name, index.document_id_for_key(instance.id)
53
+ )
54
+ end
55
+
56
+ def index(index)
57
+ return if self.class.locked?(index.reference)
58
+
59
+ ThinkingSphinx::Deltas::SidekiqDelta::DeltaJob.perform_async(index.name)
60
+ end
61
+ end
62
+
63
+ require 'thinking_sphinx/deltas/sidekiq_delta/delta_job'
64
+ require 'thinking_sphinx/deltas/sidekiq_delta/flag_as_deleted_job'
@@ -0,0 +1,51 @@
1
+ # A simple job class that processes a given index.
2
+ #
3
+ class ThinkingSphinx::Deltas::SidekiqDelta::DeltaJob
4
+ include Sidekiq::Worker
5
+
6
+ sidekiq_options unique: true, retry: true, queue: 'ts_delta'
7
+
8
+ # Runs Sphinx's indexer tool to process the index.
9
+ #
10
+ # @param [String] index the name of the Sphinx index
11
+ #
12
+ def perform(index)
13
+ config = ThinkingSphinx::Configuration.instance
14
+ config.controller.index index, :verbose => !config.settings['quiet_deltas']
15
+ end
16
+
17
+ # Try again later if lock is in use.
18
+ def self.lock_failed(*arguments)
19
+ self.class.perform_async(*arguments)
20
+ end
21
+
22
+ # This allows us to have a concurrency safe version of ts-delayed-delta's
23
+ # duplicates_exist:
24
+ #
25
+ # http://github.com/freelancing-god/ts-delayed-delta/blob/master/lib/thinkin
26
+ # g_sphinx/deltas/delayed_delta/job.rb#L47
27
+ #
28
+ # The name of this method ensures that it runs within around_perform_lock.
29
+ #
30
+ # We've leveraged resque-lock-timeout to ensure that only one DeltaJob is
31
+ # running at a time. Now, this around filter essentially ensures that only
32
+ # one DeltaJob of each index type can sit at the queue at once. If the queue
33
+ # has more than one, lrem will clear the rest off.
34
+ #
35
+ def self.around_perform_lock1(*arguments)
36
+ # Remove all other instances of this job (with the same args) from the
37
+ # queue. Uses LREM (http://code.google.com/p/redis/wiki/LremCommand) which
38
+ # takes the form: "LREM key count value" and if count == 0 removes all
39
+ # instances of value from the list.
40
+ redis_job_value = {:class => self.to_s, :args => arguments}.to_json
41
+
42
+ Sidekiq.redis {|redis|
43
+ redis.lrem("queue:#{@queue}", 0, redis_job_value)
44
+ }
45
+
46
+ yield
47
+ end
48
+ end
49
+
50
+ ThinkingSphinx::Deltas::SidekiqDelta::JOB_TYPES <<
51
+ ThinkingSphinx::Deltas::SidekiqDelta::DeltaJob
@@ -0,0 +1,20 @@
1
+ class ThinkingSphinx::Deltas::SidekiqDelta::FlagAsDeletedJob
2
+ include Sidekiq::Worker
3
+
4
+ # Runs Sphinx's indexer tool to process the index. Currently assumes Sphinx
5
+ # is running.
6
+ sidekiq_options unique: true, retry: true, queue: 'ts_delta'
7
+
8
+ def perform(index, document_id)
9
+ ThinkingSphinx::Connection.pool.take do |connection|
10
+ connection.query(
11
+ Riddle::Query.update(index, document_id, :sphinx_deleted => true)
12
+ )
13
+ end
14
+ rescue Mysql2::Error => error
15
+ # This isn't vital, so don't raise the error
16
+ end
17
+ end
18
+
19
+ ThinkingSphinx::Deltas::SidekiqDelta::JOB_TYPES <<
20
+ ThinkingSphinx::Deltas::SidekiqDelta::FlagAsDeletedJob
@@ -0,0 +1,5 @@
1
+ class ThinkingSphinx::Deltas::SidekiqDelta::Railtie < Rails::Railtie
2
+ rake_tasks do
3
+ load File.expand_path('../tasks.rb', __FILE__)
4
+ end
5
+ end
@@ -0,0 +1,38 @@
1
+ require 'thinking_sphinx/deltas/sidekiq_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::SidekiqDelta::CoreIndex.new.lock_deltas
7
+ end
8
+
9
+ desc 'Unlock all delta indices.'
10
+ task :unlock_deltas do
11
+ ThinkingSphinx::Deltas::SidekiqDelta::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::SidekiqDelta::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?('ts: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['ts:index'].enhance ['thinking_sphinx:lock_deltas'] do
33
+ # Rake::Task['thinking_sphinx:unlock_deltas'].invoke
34
+ # end
35
+
36
+ # Rake::Task['ts:reindex'].enhance ['thinking_sphinx:lock_deltas'] do
37
+ # Rake::Task['thinking_sphinx:unlock_deltas'].invoke
38
+ # end
@@ -0,0 +1,2 @@
1
+ require 'thinking_sphinx/deltas/sidekiq_delta'
2
+ require 'thinking_sphinx/deltas/sidekiq_delta/railtie' if defined?(Rails::Railtie)
@@ -0,0 +1,50 @@
1
+ require 'acceptance/spec_helper'
2
+
3
+ describe 'SQL delta indexing', :live => true do
4
+ before :each do
5
+ Sidekiq.redis = { url: "resque://localhost:6379/", namespace: 'test' }
6
+ end
7
+
8
+ it "automatically indexes new records" do
9
+ guards = Book.create(
10
+ :title => 'Guards! Guards!', :author => 'Terry Pratchett'
11
+ )
12
+ index
13
+
14
+ Book.search('Terry Pratchett').to_a.should == [guards]
15
+
16
+ men = Book.create(
17
+ :title => 'Men At Arms', :author => 'Terry Pratchett'
18
+ )
19
+ work
20
+ sleep 0.25
21
+
22
+ Book.search('Terry Pratchett').to_a.should == [guards, men]
23
+ end
24
+
25
+ it "automatically indexes updated records" do
26
+ book = Book.create :title => 'Night Watch', :author => 'Harry Pritchett'
27
+ index
28
+
29
+ Book.search('Harry').to_a.should == [book]
30
+
31
+ book.reload.update_attributes(:author => 'Terry Pratchett')
32
+ work
33
+ sleep 0.25
34
+
35
+ Book.search('Terry').to_a.should == [book]
36
+ end
37
+
38
+ it "does not match on old values" do
39
+ book = Book.create :title => 'Night Watch', :author => 'Harry Pritchett'
40
+ index
41
+
42
+ Book.search('Harry').to_a.should == [book]
43
+
44
+ book.reload.update_attributes(:author => 'Terry Pratchett')
45
+ work
46
+ sleep 0.25
47
+
48
+ Book.search('Harry').should be_empty
49
+ end
50
+ end
@@ -0,0 +1,5 @@
1
+ require 'spec_helper'
2
+ require 'sidekiq/processor'
3
+
4
+ root = File.expand_path File.dirname(__FILE__)
5
+ Dir["#{root}/support/**/*.rb"].each { |file| require file }
@@ -0,0 +1,11 @@
1
+ RSpec.configure do |config|
2
+ config.before(:suite) do
3
+ DatabaseCleaner.strategy = :truncation
4
+ end
5
+
6
+ config.after(:each) do
7
+ if example.example_group_instance.class.metadata[:live]
8
+ DatabaseCleaner.clean
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,39 @@
1
+ class SphinxController
2
+ def initialize
3
+ config.searchd.mysql41 = 9307
4
+ end
5
+
6
+ def setup
7
+ FileUtils.mkdir_p config.indices_location
8
+ config.render_to_file && index
9
+
10
+ ThinkingSphinx::Configuration.reset
11
+ ActiveSupport::Dependencies.clear
12
+
13
+ config.index_paths.each do |path|
14
+ Dir["#{path}/**/*.rb"].each { |file| $LOADED_FEATURES.delete file }
15
+ end
16
+
17
+ config.searchd.mysql41 = 9307
18
+ config.settings['quiet_deltas'] = true
19
+ config.settings['attribute_updates'] = true
20
+ end
21
+
22
+ def start
23
+ config.controller.start
24
+ end
25
+
26
+ def stop
27
+ config.controller.stop
28
+ end
29
+
30
+ def index(*indices)
31
+ config.controller.index *indices
32
+ end
33
+
34
+ private
35
+
36
+ def config
37
+ ThinkingSphinx::Configuration.instance
38
+ end
39
+ end
@@ -0,0 +1,39 @@
1
+ module SphinxHelpers
2
+ include Sidekiq::Util
3
+
4
+ def sphinx
5
+ @sphinx ||= SphinxController.new
6
+ end
7
+
8
+ def index(*indices)
9
+ yield if block_given?
10
+
11
+ ThinkingSphinx::Deltas::SidekiqDelta.clear_thinking_sphinx_queues
12
+ sphinx.index *indices
13
+ sleep 0.25
14
+ end
15
+
16
+ def work
17
+ client = Redis.connect(:url => "resque://localhost:6379/")
18
+ namespace = Redis::Namespace.new('test', :redis => client)
19
+
20
+ Sidekiq::Client.registered_queues.each do |queue_name|
21
+ while message = namespace.lpop("queue:#{queue_name}")
22
+ message = JSON.parse(message)
23
+ message['class'].constantize.new.perform(*message['args'])
24
+ end
25
+ end
26
+ end
27
+ end
28
+
29
+ RSpec.configure do |config|
30
+ config.include SphinxHelpers
31
+
32
+ config.before :all do |group|
33
+ sphinx.setup && sphinx.start if group.class.metadata[:live]
34
+ end
35
+
36
+ config.after :all do |group|
37
+ sphinx.stop if group.class.metadata[:live]
38
+ end
39
+ end
@@ -0,0 +1,2 @@
1
+ config/*.sphinx.conf
2
+ db/sphinx/*
@@ -0,0 +1,3 @@
1
+ ThinkingSphinx::Index.define :book, :with => :active_record, :delta => ThinkingSphinx::Deltas::SidekiqDelta do
2
+ indexes title, author
3
+ end
@@ -0,0 +1,3 @@
1
+ class Book < ActiveRecord::Base
2
+ #
3
+ end
@@ -0,0 +1,5 @@
1
+ test:
2
+ adapter: <%= ENV['DATABASE'] || 'mysql2' %>
3
+ database: thinking_sphinx
4
+ username: <%= ENV['DATABASE'] == 'postgresql' ? ENV['USER'] : 'root' %>
5
+ min_messages: warning
@@ -0,0 +1,24 @@
1
+ ActiveRecord::Schema.define do
2
+ create_table(:books, :force => true) do |t|
3
+ t.string :title
4
+ t.string :author
5
+ t.integer :year
6
+ t.string :blurb_file
7
+ t.boolean :delta, :default => true, :null => false
8
+ t.timestamps
9
+ end
10
+
11
+ create_table(:delayed_jobs, :force => true) do |t|
12
+ t.column :priority, :integer, :default => 0
13
+ t.column :attempts, :integer, :default => 0
14
+ t.column :handler, :text
15
+ t.column :last_error, :text
16
+ t.column :run_at, :datetime
17
+ t.column :locked_at, :datetime
18
+ t.column :failed_at, :datetime
19
+ t.column :locked_by, :string
20
+ t.column :queue, :string
21
+ t.column :created_at, :datetime
22
+ t.column :updated_at, :datetime
23
+ end
24
+ end
@@ -0,0 +1 @@
1
+ *.log
@@ -0,0 +1,17 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+
4
+ Bundler.require :default, :development
5
+
6
+ require 'thinking_sphinx/railtie'
7
+
8
+ Combustion.initialize! :active_record
9
+
10
+ root = File.expand_path File.dirname(__FILE__)
11
+ Dir["#{root}/support/**/*.rb"].each { |file| require file }
12
+
13
+ RSpec.configure do |config|
14
+ # enable filtering for examples
15
+ config.filter_run :wip => nil
16
+ config.run_all_when_everything_filtered = true
17
+ end
@@ -0,0 +1 @@
1
+ require File.join(File.dirname(__FILE__), '/../lib/thinking_sphinx/deltas/resque_delta/tasks')
@@ -0,0 +1,26 @@
1
+ # -*- encoding: utf-8 -*-
2
+ Gem::Specification.new do |s|
3
+ s.name = 'ts-sidekiq-delta'
4
+ s.version = '0.1.0'
5
+ s.platform = Gem::Platform::RUBY
6
+ s.authors = ['Pat Allan', 'Aaron Gibralter', 'Danny Hawkins']
7
+ s.email = ['pat@freelancing-gods.com', 'danny.hawkins@gmail.com']
8
+ s.homepage = 'https://github.com/pat/ts-sidekiq-delta'
9
+ s.summary = %q{Thinking Sphinx - Sidekiq Deltas}
10
+ s.description = %q{Manage delta indexes via Sidekiq for Thinking Sphinx}
11
+ s.license = 'MIT'
12
+
13
+ s.files = `git ls-files`.split("\n")
14
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
15
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
16
+ s.require_paths = ["lib"]
17
+
18
+ s.add_dependency 'thinking-sphinx', '>= 3.0.0'
19
+ s.add_dependency 'sidekiq', '>= 2.5.4'
20
+
21
+ s.add_development_dependency 'activerecord', '>= 3.1.0'
22
+ s.add_development_dependency 'database_cleaner', '>= 0.5.2'
23
+ s.add_development_dependency 'mysql2', '>= 0.3.12b4'
24
+ s.add_development_dependency 'rake', '>= 0.8.7'
25
+ s.add_development_dependency 'rspec', '>= 2.11.0'
26
+ end
metadata ADDED
@@ -0,0 +1,204 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ts-sidekiq-delta
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Pat Allan
8
+ - Aaron Gibralter
9
+ - Danny Hawkins
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2013-12-02 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: thinking-sphinx
17
+ requirement: !ruby/object:Gem::Requirement
18
+ requirements:
19
+ - - '>='
20
+ - !ruby/object:Gem::Version
21
+ version: 3.0.0
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - '>='
27
+ - !ruby/object:Gem::Version
28
+ version: 3.0.0
29
+ - !ruby/object:Gem::Dependency
30
+ name: sidekiq
31
+ requirement: !ruby/object:Gem::Requirement
32
+ requirements:
33
+ - - '>='
34
+ - !ruby/object:Gem::Version
35
+ version: 2.5.4
36
+ type: :runtime
37
+ prerelease: false
38
+ version_requirements: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - '>='
41
+ - !ruby/object:Gem::Version
42
+ version: 2.5.4
43
+ - !ruby/object:Gem::Dependency
44
+ name: activerecord
45
+ requirement: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - '>='
48
+ - !ruby/object:Gem::Version
49
+ version: 3.1.0
50
+ type: :development
51
+ prerelease: false
52
+ version_requirements: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - '>='
55
+ - !ruby/object:Gem::Version
56
+ version: 3.1.0
57
+ - !ruby/object:Gem::Dependency
58
+ name: database_cleaner
59
+ requirement: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - '>='
62
+ - !ruby/object:Gem::Version
63
+ version: 0.5.2
64
+ type: :development
65
+ prerelease: false
66
+ version_requirements: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - '>='
69
+ - !ruby/object:Gem::Version
70
+ version: 0.5.2
71
+ - !ruby/object:Gem::Dependency
72
+ name: mysql2
73
+ requirement: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - '>='
76
+ - !ruby/object:Gem::Version
77
+ version: 0.3.12b4
78
+ type: :development
79
+ prerelease: false
80
+ version_requirements: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - '>='
83
+ - !ruby/object:Gem::Version
84
+ version: 0.3.12b4
85
+ - !ruby/object:Gem::Dependency
86
+ name: rake
87
+ requirement: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - '>='
90
+ - !ruby/object:Gem::Version
91
+ version: 0.8.7
92
+ type: :development
93
+ prerelease: false
94
+ version_requirements: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - '>='
97
+ - !ruby/object:Gem::Version
98
+ version: 0.8.7
99
+ - !ruby/object:Gem::Dependency
100
+ name: rspec
101
+ requirement: !ruby/object:Gem::Requirement
102
+ requirements:
103
+ - - '>='
104
+ - !ruby/object:Gem::Version
105
+ version: 2.11.0
106
+ type: :development
107
+ prerelease: false
108
+ version_requirements: !ruby/object:Gem::Requirement
109
+ requirements:
110
+ - - '>='
111
+ - !ruby/object:Gem::Version
112
+ version: 2.11.0
113
+ description: Manage delta indexes via Sidekiq for Thinking Sphinx
114
+ email:
115
+ - pat@freelancing-gods.com
116
+ - danny.hawkins@gmail.com
117
+ executables: []
118
+ extensions: []
119
+ extra_rdoc_files: []
120
+ files:
121
+ - .gitignore
122
+ - .rspec
123
+ - Gemfile
124
+ - Guardfile
125
+ - LICENSE
126
+ - README.markdown
127
+ - Rakefile
128
+ - config/redis-cucumber.conf
129
+ - cucumber.yml
130
+ - features/sidekiq_deltas.feature
131
+ - features/smart_indexing.feature
132
+ - features/step_definitions/common_steps.rb
133
+ - features/step_definitions/sidekiq_delta_steps.rb
134
+ - features/step_definitions/smart_indexing_steps.rb
135
+ - features/support/env.rb
136
+ - features/thinking_sphinx/database.example.yml
137
+ - features/thinking_sphinx/db/migrations/create_delayed_betas.rb
138
+ - features/thinking_sphinx/models/delayed_beta.rb
139
+ - lib/thinking_sphinx/deltas/sidekiq_delta.rb
140
+ - lib/thinking_sphinx/deltas/sidekiq_delta/delta_job.rb
141
+ - lib/thinking_sphinx/deltas/sidekiq_delta/flag_as_deleted_job.rb
142
+ - lib/thinking_sphinx/deltas/sidekiq_delta/railtie.rb
143
+ - lib/thinking_sphinx/deltas/sidekiq_delta/tasks.rb
144
+ - lib/ts-sidekiq-delta.rb
145
+ - spec/acceptance/sidekiq_deltas_spec.rb
146
+ - spec/acceptance/spec_helper.rb
147
+ - spec/acceptance/support/database_cleaner.rb
148
+ - spec/acceptance/support/sphinx_controller.rb
149
+ - spec/acceptance/support/sphinx_helpers.rb
150
+ - spec/internal/.gitignore
151
+ - spec/internal/app/indices/book_index.rb
152
+ - spec/internal/app/models/book.rb
153
+ - spec/internal/config/database.yml
154
+ - spec/internal/db/schema.rb
155
+ - spec/internal/log/.gitignore
156
+ - spec/spec_helper.rb
157
+ - tasks/rails.rake
158
+ - ts-sidekiq-delta.gemspec
159
+ homepage: https://github.com/pat/ts-sidekiq-delta
160
+ licenses:
161
+ - MIT
162
+ metadata: {}
163
+ post_install_message:
164
+ rdoc_options: []
165
+ require_paths:
166
+ - lib
167
+ required_ruby_version: !ruby/object:Gem::Requirement
168
+ requirements:
169
+ - - '>='
170
+ - !ruby/object:Gem::Version
171
+ version: '0'
172
+ required_rubygems_version: !ruby/object:Gem::Requirement
173
+ requirements:
174
+ - - '>='
175
+ - !ruby/object:Gem::Version
176
+ version: '0'
177
+ requirements: []
178
+ rubyforge_project:
179
+ rubygems_version: 2.1.11
180
+ signing_key:
181
+ specification_version: 4
182
+ summary: Thinking Sphinx - Sidekiq Deltas
183
+ test_files:
184
+ - features/sidekiq_deltas.feature
185
+ - features/smart_indexing.feature
186
+ - features/step_definitions/common_steps.rb
187
+ - features/step_definitions/sidekiq_delta_steps.rb
188
+ - features/step_definitions/smart_indexing_steps.rb
189
+ - features/support/env.rb
190
+ - features/thinking_sphinx/database.example.yml
191
+ - features/thinking_sphinx/db/migrations/create_delayed_betas.rb
192
+ - features/thinking_sphinx/models/delayed_beta.rb
193
+ - spec/acceptance/sidekiq_deltas_spec.rb
194
+ - spec/acceptance/spec_helper.rb
195
+ - spec/acceptance/support/database_cleaner.rb
196
+ - spec/acceptance/support/sphinx_controller.rb
197
+ - spec/acceptance/support/sphinx_helpers.rb
198
+ - spec/internal/.gitignore
199
+ - spec/internal/app/indices/book_index.rb
200
+ - spec/internal/app/models/book.rb
201
+ - spec/internal/config/database.yml
202
+ - spec/internal/db/schema.rb
203
+ - spec/internal/log/.gitignore
204
+ - spec/spec_helper.rb