ctt-background-jobs 0.8.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 (39) hide show
  1. data/.gitignore +18 -0
  2. data/.rspec +1 -0
  3. data/.ruby-gemset +1 -0
  4. data/.ruby-version +1 -0
  5. data/CTT-Background-Jobs.gemspec +37 -0
  6. data/Gemfile +6 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +25 -0
  9. data/Rakefile +29 -0
  10. data/lib/background_jobs/configuration.rb +27 -0
  11. data/lib/background_jobs/job.rb +17 -0
  12. data/lib/background_jobs/job_factory.rb +9 -0
  13. data/lib/background_jobs/job_id_generator.rb +15 -0
  14. data/lib/background_jobs/job_notifier.rb +31 -0
  15. data/lib/background_jobs/job_queue_factory.rb +36 -0
  16. data/lib/background_jobs/job_registry.rb +65 -0
  17. data/lib/background_jobs/queue_service.rb +17 -0
  18. data/lib/background_jobs/strategies/direct_call/job_queue.rb +11 -0
  19. data/lib/background_jobs/strategies/sidekiq/job_adapter.rb +21 -0
  20. data/lib/background_jobs/strategies/sidekiq/job_attributes_adapter.rb +141 -0
  21. data/lib/background_jobs/strategies/sidekiq/job_queue.rb +42 -0
  22. data/lib/background_jobs/strategies/sidekiq/setup.rb +31 -0
  23. data/lib/version.rb +3 -0
  24. data/spec/fast_helper.rb +11 -0
  25. data/spec/integration/sidekiq_spec.rb +34 -0
  26. data/spec/lib/background_jobs/configuration_spec.rb +13 -0
  27. data/spec/lib/background_jobs/job_factory_fast_spec.rb +24 -0
  28. data/spec/lib/background_jobs/job_fast_spec.rb +38 -0
  29. data/spec/lib/background_jobs/job_id_generator_fast_spec.rb +23 -0
  30. data/spec/lib/background_jobs/job_notififer_spec.rb +9 -0
  31. data/spec/lib/background_jobs/job_queue_factory_fast_spec.rb +58 -0
  32. data/spec/lib/background_jobs/queue_service_fast_spec.rb +36 -0
  33. data/spec/lib/background_jobs/strategies/direct_call/job_queue_spec.rb +27 -0
  34. data/spec/lib/background_jobs/strategies/sidekiq/job_adapter_spec.rb +30 -0
  35. data/spec/lib/background_jobs/strategies/sidekiq/job_attributes_adapter_spec.rb +129 -0
  36. data/spec/lib/background_jobs/strategies/sidekiq/job_queue_spec.rb +63 -0
  37. data/spec/lib/background_jobs/torquebox_jobs_queue_fast_spec.rb +31 -0
  38. data/spec/support/job_queue.rb +7 -0
  39. metadata +281 -0
@@ -0,0 +1,42 @@
1
+ require 'sidekiq'
2
+ require 'background_jobs/job_registry'
3
+ require 'background_jobs/strategies/sidekiq/job_adapter'
4
+ require 'background_jobs/strategies/sidekiq/job_attributes_adapter'
5
+
6
+ module BackgroundJobs
7
+ module Sidekiq
8
+ class JobQueue
9
+
10
+ def enqueue(job_type, job_id, attributes, options = {})
11
+ priority = options[:priority] || 1
12
+ unique = options[:unique] || false
13
+ at = (Time.now + options[:in]).to_i if options[:in]
14
+
15
+ return if unique && is_scheduled?(job_type)
16
+
17
+ queue = BackgroundJobs.job_registry.get_type(job_type)
18
+ attributes.unshift job_id
19
+ attributes.unshift job_type
20
+
21
+ args = {
22
+ 'queue' => queue.to_s, # queue by priority?
23
+ 'class' => JobAdapter,
24
+ 'args' => JobAttributesAdapter.new(attributes).encode
25
+ }
26
+
27
+ args['at'] = at if at
28
+
29
+ ::Sidekiq::Client.push(args)
30
+ end
31
+
32
+ private
33
+
34
+ def is_scheduled?(job_name)
35
+ entries = ::Sidekiq::ScheduledSet.new.entries
36
+ job_names = entries.map(&:item).map {|item| item['args'].first }
37
+
38
+ job_names.include?(job_name.to_s)
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,31 @@
1
+ require 'sidekiq'
2
+ require 'background_jobs/job_registry'
3
+
4
+ module BackgroundJobs
5
+ module Sidekiq
6
+ def self.setup(options)
7
+ ::Sidekiq.configure_server do |config|
8
+ config.redis = options[:redis] if options[:redis]
9
+ end
10
+
11
+ BackgroundJobs.job_registry.register_callback = method(:register_job_callback)
12
+ end
13
+
14
+ def self.register_job_callback(job_metadata)
15
+ options = job_metadata.options
16
+
17
+ BackgroundJobs::QueueService.enqueue(
18
+ job_metadata.name, [], :in => options[:schedule], :unique => true
19
+ ) if options[:schedule]
20
+ end
21
+
22
+ private
23
+
24
+ def self.is_scheduled?(job_name)
25
+ entries = ::Sidekiq::ScheduledSet.new.entries
26
+ job_names = entries.map(&:item).map {|item| item['args'].first }
27
+
28
+ job_names.include?(job_name.to_s)
29
+ end
30
+ end
31
+ end
data/lib/version.rb ADDED
@@ -0,0 +1,3 @@
1
+ module BackgroundJobs
2
+ VERSION = '0.8.0'
3
+ end
@@ -0,0 +1,11 @@
1
+ # This file is copied to spec/ when you run 'rails generate rspec:install'
2
+ ENV["RAILS_ENV"] ||= 'test'
3
+
4
+ require 'rspec'
5
+ require 'shoulda'
6
+ require 'require_relative'
7
+ require 'timecop'
8
+
9
+ RSpec.configure do |config|
10
+ config.mock_with :mocha
11
+ end
@@ -0,0 +1,34 @@
1
+ require 'fast_helper'
2
+ require 'background_jobs/configuration'
3
+ require 'background_jobs/queue_service'
4
+ require 'background_jobs/strategies/sidekiq/setup'
5
+
6
+ describe 'Sidekiq' do
7
+
8
+ let(:sidekiq_fake_job) { Class.new }
9
+
10
+ let(:clock) { stub('clock') }
11
+ let(:clock_schedule) { stub('clock_schedule') }
12
+
13
+ before do
14
+ BackgroundJobs.configure do |config|
15
+ config.strategy = 'sidekiq'
16
+ end
17
+ end
18
+
19
+ context 'with schedule' do
20
+ let(:scheduled_set) { stub('scheduled_set') }
21
+
22
+ it 'should schedule the job if schedule is set' do
23
+ BackgroundJobs::QueueService.expects(:enqueue).with(:fake_job, [], :in => 60*10, :unique => true)
24
+
25
+ BackgroundJobs.register_job :fake_job, :core, sidekiq_fake_job, :schedule => 60*10
26
+ end
27
+ end
28
+
29
+ it 'should not schedule the job if schedule not set' do
30
+ BackgroundJobs::QueueService.expects(:enqueue).never
31
+
32
+ BackgroundJobs.register_job :fake_job, :core, sidekiq_fake_job
33
+ end
34
+ end
@@ -0,0 +1,13 @@
1
+ require 'fast_helper'
2
+ require 'background_jobs/configuration'
3
+
4
+ module BackgroundJobs
5
+ describe Configuration do
6
+ describe '#strategy' do
7
+ context 'with env configuration set' do
8
+ before { BackgroundJobs.configure {|c| c.strategy = 'foo' } }
9
+ it { BackgroundJobs.config.strategy.should == 'foo' }
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,24 @@
1
+ require 'fast_helper'
2
+ require 'background_jobs/job_factory'
3
+
4
+ module BackgroundJobs
5
+ describe JobFactory do
6
+ before do
7
+ BackgroundJobs.register_job :foo, :bar, Imitate.mock('Foo')
8
+ end
9
+
10
+ describe '.build' do
11
+ context 'for existing job' do
12
+ it 'should create job instance' do
13
+ JobFactory.build(:foo).should be_instance_of Foo
14
+ end
15
+ end
16
+
17
+ context 'for not-existing job' do
18
+ it 'should throw an exception' do
19
+ expect { JobFactory.build(:not_existing_job) }.to raise_error("Unknown job 'not_existing_job'!")
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,38 @@
1
+ require 'fast_helper'
2
+ require 'background_jobs/job'
3
+
4
+ describe BackgroundJobs::Job do
5
+ before do
6
+ Imitate.mock 'Airbrake' do
7
+ def self.notify(param); end
8
+ end
9
+ end
10
+
11
+ let(:job) { BackgroundJobs::Job.new }
12
+
13
+ subject { job }
14
+
15
+ describe '#perform' do
16
+ it { expect { subject.perform }.to raise_error NotImplementedError }
17
+ end
18
+
19
+ describe '#id=' do
20
+ before { job.id = 123 }
21
+
22
+ it { subject.instance_variable_get(:@id).should eq 123 }
23
+ end
24
+
25
+ describe '#execute' do
26
+ it { expect { subject.execute }.to raise_error NotImplementedError }
27
+ end
28
+
29
+ describe 'error' do
30
+ let(:exception) { stub('exception') }
31
+
32
+ before do
33
+ Airbrake.expects(:notify).with(exception)
34
+ end
35
+
36
+ it { job.error('whatever', exception) }
37
+ end
38
+ end
@@ -0,0 +1,23 @@
1
+ require 'fast_helper'
2
+ require 'background_jobs/job_id_generator'
3
+ require 'imitate'
4
+
5
+ describe BackgroundJobs::JobIdGenerator do
6
+ let(:generator) do
7
+ Imitate.mock('Generator') do
8
+ include BackgroundJobs::JobIdGenerator
9
+ end
10
+ end
11
+
12
+ describe '.generate' do
13
+ let(:result1) { generator.generate_job_id }
14
+ let(:result2) { generator.generate_job_id }
15
+ let(:result3) { generator.generate_job_id }
16
+
17
+ subject { result1 }
18
+
19
+ it { should_not == result2 }
20
+ it { should_not == result3 }
21
+ it(:result2) { should_not == result3 }
22
+ end
23
+ end
@@ -0,0 +1,9 @@
1
+ require 'fast_helper'
2
+ require 'background_jobs/job_notifier'
3
+
4
+ describe BackgroundJobs::JobNotifier do
5
+
6
+ let(:job) { stub('job').tap {|j| j.extend BackgroundJobs::JobNotifier } }
7
+
8
+ it { should be }
9
+ end
@@ -0,0 +1,58 @@
1
+ require 'fast_helper'
2
+ require 'background_jobs/job_queue_factory'
3
+ require 'background_jobs/strategies/sidekiq/job_queue'
4
+ require 'background_jobs/strategies/direct_call/job_queue'
5
+
6
+ module BackgroundJobs
7
+ describe JobQueueFactory do
8
+ describe '.build' do
9
+ subject { JobQueueFactory.build }
10
+
11
+ context 'sidekiq' do
12
+ before { configure_strategy('sidekiq', options) }
13
+
14
+ let(:sidekiq_job_queue) { stub('sidekiq_job_queue') }
15
+
16
+ let(:options) { stub('options') }
17
+
18
+ it {
19
+ Sidekiq::JobQueue.expects(:new).returns(sidekiq_job_queue)
20
+ JobQueueFactory.build.should == sidekiq_job_queue
21
+ }
22
+
23
+ after { clear_job_queue_strategy }
24
+ end
25
+
26
+ context 'direct_call' do
27
+ before { configure_strategy('direct_call') }
28
+
29
+ it { should be_instance_of DirectCall::JobQueue }
30
+
31
+ after { clear_job_queue_strategy }
32
+ end
33
+
34
+ context 'with no strategy configured' do
35
+ before { clear_job_queue_strategy }
36
+
37
+ it "will blow up" do
38
+ expect {
39
+ JobQueueFactory.build
40
+ }.to raise_exception UnknownBackgroundJobsQueueStrategy
41
+ end
42
+
43
+ after { configure_strategy('delayed_job') }
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ def clear_job_queue_strategy
50
+ configure_strategy nil
51
+ end
52
+
53
+ def configure_strategy(name, options = {})
54
+ BackgroundJobs.configure do |config|
55
+ config.strategy = name
56
+ config.strategy_options = options
57
+ end
58
+ end
@@ -0,0 +1,36 @@
1
+ require 'fast_helper'
2
+ require 'background_jobs/queue_service'
3
+
4
+ module BackgroundJobs
5
+ describe QueueService do
6
+ let(:job_type) { :fix_all_bugs_while_we_sleep }
7
+
8
+ let(:priority) { 1 }
9
+
10
+ let(:data) { [1, 2, 3, 'foo', {1 => :uno}] }
11
+
12
+ let(:job_id) { 'job_id' }
13
+
14
+ let(:expected_queue_params) { data }
15
+
16
+ let(:strategy) { stub('strategy') }
17
+
18
+ describe '.enqueue' do
19
+ let(:delay) { stub('delay') }
20
+
21
+ before do
22
+ QueueService.stubs(:generate_job_id).returns(job_id)
23
+
24
+ JobQueueFactory.expects(:build).returns(strategy)
25
+
26
+ strategy.expects(:enqueue).with(
27
+ job_type, job_id, expected_queue_params, :priority => priority, :in => delay
28
+ )
29
+ end
30
+
31
+ subject { QueueService.enqueue(job_type, data, :priority => priority, :in => delay) }
32
+
33
+ it { should == job_id }
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,27 @@
1
+ require 'fast_helper'
2
+ require 'background_jobs/strategies/direct_call/job_queue'
3
+
4
+ module BackgroundJobs
5
+ module DirectCall
6
+ describe JobQueue.new do
7
+ let(:type) { :foo }
8
+
9
+ let(:priority) { 1 }
10
+
11
+ let(:attrs) { [10..10, :foo => :bar] }
12
+
13
+ let(:job) { stub('job') }
14
+
15
+ let(:job_id) { 123 }
16
+
17
+ describe '.enqueue' do
18
+ it 'should create a job and execute it' do
19
+ JobFactory.expects(:build).with(type, attrs).returns(job)
20
+ job.expects(:execute)
21
+
22
+ JobQueue.new.enqueue(type, job_id, attrs)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,30 @@
1
+ require 'fast_helper'
2
+ require 'background_jobs/strategies/sidekiq/job_queue'
3
+
4
+ module BackgroundJobs
5
+ module Sidekiq
6
+ describe JobAdapter.new do
7
+ let(:job) { stub('job', :execute => true) }
8
+
9
+ describe '#peform' do
10
+ subject { JobAdapter.new.perform('foo', '1', :bar, :baz, 'range:[10,1000]') }
11
+
12
+ before { JobFactory.stubs(:build).returns(job) }
13
+
14
+ it 'should call job factory with appropriate job type and attributes' do
15
+ JobFactory.expects(:build).with(:foo, [:bar, :baz, 10..1000]).returns(job)
16
+ job.expects(:id=).with(1)
17
+
18
+ subject
19
+ end
20
+
21
+ it 'should call job perform with no attributes' do
22
+ job.expects(:execute)
23
+ job.expects(:id=).with(1)
24
+
25
+ subject
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,129 @@
1
+ require 'fast_helper'
2
+ require 'background_jobs/strategies/sidekiq/job_attributes_adapter'
3
+
4
+ module BackgroundJobs
5
+ module Sidekiq
6
+ describe JobAttributesAdapter do
7
+ context 'date' do
8
+ let(:attributes) { [Date.parse('1st Jan 2011')] }
9
+ let(:attributes_encoded) { ['date:2011-01-01'] }
10
+
11
+ it 'does decode to string with "date:"" prefix' do
12
+ JobAttributesAdapter.new(attributes_encoded).decode.should eq attributes
13
+ end
14
+
15
+ it 'does encode to string with "date:"" prefix' do
16
+ JobAttributesAdapter.new(attributes).encode.should eq attributes_encoded
17
+ end
18
+ end
19
+
20
+ context 'date range' do
21
+ let(:attributes) { [Date.parse('1st Jan 2011')..Date.parse('1st Feb 2011')] }
22
+ let(:attributes_encoded) { ['range:[date:2011-01-01,date:2011-02-01]'] }
23
+
24
+ it 'does decode to string with "range:" prefix and encode its boundary dates' do
25
+ JobAttributesAdapter.new(attributes_encoded).decode.should eq attributes
26
+ end
27
+
28
+ it 'does encode to string with "range:" prefix and encode its boundary dates' do
29
+ JobAttributesAdapter.new(attributes).encode.should eq attributes_encoded
30
+ end
31
+ end
32
+
33
+ context 'integers range' do
34
+ let(:attributes) { [1..10] }
35
+ let(:attributes_encoded) { ['range:[1,10]'] }
36
+
37
+ it 'does decode to string with prefix range:' do
38
+ JobAttributesAdapter.new(attributes_encoded).decode.should eq attributes
39
+ end
40
+
41
+ it 'does encode to string with prefix range:' do
42
+ JobAttributesAdapter.new(attributes).encode.should eq attributes_encoded
43
+ end
44
+ end
45
+
46
+ context 'integer' do
47
+ let(:attributes) { [12345] }
48
+ let(:attributes_encoded) { [12345] }
49
+
50
+ it 'is left decodehout any tranformations' do
51
+ JobAttributesAdapter.new(attributes_encoded).decode.should eq attributes
52
+ end
53
+
54
+ it 'does encode to string with prefix range:' do
55
+ JobAttributesAdapter.new(attributes).encode.should eq attributes_encoded
56
+ end
57
+ end
58
+
59
+ context 'hash' do
60
+ let(:attributes) { [{:foo => 'bar'}] }
61
+ let(:attributes_encoded) { [{'foo' => 'bar'}] }
62
+
63
+ it 'is left decodehout any tranformations' do
64
+ JobAttributesAdapter.new(attributes_encoded).decode.should eq attributes
65
+ end
66
+
67
+ it 'does encode to string with prefix range:' do
68
+ JobAttributesAdapter.new(attributes).encode.should eq attributes_encoded
69
+ end
70
+ end
71
+
72
+ context 'integer, hash and range' do
73
+ let(:attributes) { [12345, {:foo => 'bar'}, 10..1000] }
74
+ let(:attributes_encoded) { [12345, {'foo'=>"bar"}, "range:[10,1000]"] }
75
+
76
+ it 'does decode all together appropriately to their type' do
77
+ JobAttributesAdapter.new(attributes_encoded).decode.should eq attributes
78
+ end
79
+
80
+ it 'does encode to string with prefix range:' do
81
+ JobAttributesAdapter.new(attributes).encode.should eq attributes_encoded
82
+ end
83
+ end
84
+
85
+ context 'unknown group id bug' do
86
+ let(:attributes) {
87
+ [
88
+ "624ea403ad028e03045ba34c923bce0b2a812099bcd815bb1ddf2d92d8aaa560",
89
+ {
90
+ :group_id => 627,
91
+ :date_range => [Date.parse("2016-12-31")],
92
+ :week_schedule => {
93
+ :monday => 0,
94
+ :tuesday => 1,
95
+ :wednesday => 2,
96
+ :thursday => 3,
97
+ :friday => 2,
98
+ :saturday => 0,
99
+ :sunday => 0
100
+ }
101
+ }
102
+ ]
103
+ }
104
+
105
+ let(:attributes_encoded) {
106
+ [
107
+ "624ea403ad028e03045ba34c923bce0b2a812099bcd815bb1ddf2d92d8aaa560",
108
+ {
109
+ "group_id"=>627,
110
+ "date_range"=>["date:2016-12-31"],
111
+ "week_schedule"=>{"monday"=>0, "tuesday"=>1, "wednesday"=>2, "thursday"=>3, "friday"=>2, "saturday"=>0, "sunday"=>0}
112
+ }
113
+ ]
114
+ }
115
+
116
+ it 'does encode well' do
117
+ JobAttributesAdapter.new(attributes).encode.should eq attributes_encoded
118
+ end
119
+
120
+ it 'does decode well' do
121
+ decoded = JobAttributesAdapter.new(attributes_encoded).decode
122
+
123
+ decoded.should eq attributes
124
+ decoded.last[:group_id].should == 627
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end