ts-sidekiq-delta 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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