canvas-jobs 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +7 -0
  2. data/db/migrate/20101216224513_create_delayed_jobs.rb +40 -0
  3. data/db/migrate/20110208031356_add_delayed_jobs_tag.rb +14 -0
  4. data/db/migrate/20110426161613_add_delayed_jobs_max_attempts.rb +13 -0
  5. data/db/migrate/20110516225834_add_delayed_jobs_strand.rb +14 -0
  6. data/db/migrate/20110531144916_cleanup_delayed_jobs_indexes.rb +26 -0
  7. data/db/migrate/20110610213249_optimize_delayed_jobs.rb +40 -0
  8. data/db/migrate/20110831210257_add_delayed_jobs_next_in_strand.rb +52 -0
  9. data/db/migrate/20120510004759_delayed_jobs_delete_trigger_lock_for_update.rb +31 -0
  10. data/db/migrate/20120531150712_drop_psql_jobs_pop_fn.rb +15 -0
  11. data/db/migrate/20120607164022_delayed_jobs_use_advisory_locks.rb +80 -0
  12. data/db/migrate/20120607181141_index_jobs_on_locked_by.rb +15 -0
  13. data/db/migrate/20120608191051_add_jobs_run_at_index.rb +15 -0
  14. data/db/migrate/20120927184213_change_delayed_jobs_handler_to_text.rb +13 -0
  15. data/db/migrate/20140505215131_add_failed_jobs_original_job_id.rb +13 -0
  16. data/db/migrate/20140505215510_copy_failed_jobs_original_id.rb +13 -0
  17. data/db/migrate/20140505223637_drop_failed_jobs_original_id.rb +13 -0
  18. data/db/migrate/20140512213941_add_source_to_jobs.rb +15 -0
  19. data/lib/canvas-jobs.rb +1 -0
  20. data/lib/delayed/backend/active_record.rb +297 -0
  21. data/lib/delayed/backend/base.rb +317 -0
  22. data/lib/delayed/backend/redis/bulk_update.lua +40 -0
  23. data/lib/delayed/backend/redis/destroy_job.lua +2 -0
  24. data/lib/delayed/backend/redis/enqueue.lua +29 -0
  25. data/lib/delayed/backend/redis/fail_job.lua +5 -0
  26. data/lib/delayed/backend/redis/find_available.lua +3 -0
  27. data/lib/delayed/backend/redis/functions.rb +57 -0
  28. data/lib/delayed/backend/redis/get_and_lock_next_available.lua +17 -0
  29. data/lib/delayed/backend/redis/includes/jobs_common.lua +203 -0
  30. data/lib/delayed/backend/redis/job.rb +481 -0
  31. data/lib/delayed/backend/redis/set_running.lua +5 -0
  32. data/lib/delayed/backend/redis/tickle_strand.lua +2 -0
  33. data/lib/delayed/batch.rb +56 -0
  34. data/lib/delayed/engine.rb +4 -0
  35. data/lib/delayed/job_tracking.rb +31 -0
  36. data/lib/delayed/lifecycle.rb +83 -0
  37. data/lib/delayed/message_sending.rb +130 -0
  38. data/lib/delayed/performable_method.rb +42 -0
  39. data/lib/delayed/periodic.rb +81 -0
  40. data/lib/delayed/pool.rb +335 -0
  41. data/lib/delayed/settings.rb +32 -0
  42. data/lib/delayed/version.rb +3 -0
  43. data/lib/delayed/worker.rb +213 -0
  44. data/lib/delayed/yaml_extensions.rb +63 -0
  45. data/lib/delayed_job.rb +40 -0
  46. data/spec/active_record_job_spec.rb +61 -0
  47. data/spec/gemfiles/32.gemfile +6 -0
  48. data/spec/gemfiles/40.gemfile +6 -0
  49. data/spec/gemfiles/41.gemfile +6 -0
  50. data/spec/gemfiles/42.gemfile +6 -0
  51. data/spec/migrate/20140924140513_add_story_table.rb +7 -0
  52. data/spec/redis_job_spec.rb +77 -0
  53. data/spec/sample_jobs.rb +26 -0
  54. data/spec/shared/delayed_batch.rb +85 -0
  55. data/spec/shared/delayed_method.rb +419 -0
  56. data/spec/shared/performable_method.rb +52 -0
  57. data/spec/shared/shared_backend.rb +836 -0
  58. data/spec/shared/worker.rb +291 -0
  59. data/spec/shared_jobs_specs.rb +13 -0
  60. data/spec/spec_helper.rb +91 -0
  61. metadata +329 -0
@@ -0,0 +1,63 @@
1
+ # New definitions for YAML to aid in serialization and deserialization of delayed jobs.
2
+
3
+ require 'yaml'
4
+ require 'syck'
5
+ # this code needs to be updated to work with the new Psych YAML engine in ruby 1.9.x
6
+ # for now we force Syck
7
+ YAML::ENGINE.yamler = 'syck' if defined?(YAML::ENGINE) && YAML::ENGINE.yamler != 'syck'
8
+
9
+ # First, tell YAML how to load a Module. This depends on Rails .constantize and autoloading.
10
+ YAML.add_ruby_type("object:Module") do |type, val|
11
+ val.constantize
12
+ end
13
+
14
+ class Module
15
+ def to_yaml(opts = {})
16
+ YAML.quick_emit(self.object_id, opts) do |out|
17
+ out.scalar(taguri, name)
18
+ end
19
+ end
20
+ end
21
+
22
+ # Now we have to do the same for Class.
23
+ YAML.add_ruby_type("object:Class") do |type, val|
24
+ val.constantize
25
+ end
26
+
27
+ class Class
28
+ def to_yaml(opts = {})
29
+ YAML.quick_emit(self.object_id, opts) do |out|
30
+ out.scalar(taguri, name)
31
+ end
32
+ end
33
+ end
34
+
35
+ # Now, tell YAML how to intelligently load ActiveRecord objects, using the
36
+ # database rather than just serializing their attributes to the YAML. This
37
+ # ensures the object is up to date when we use it in the job.
38
+ class ActiveRecord::Base
39
+ yaml_as "tag:ruby.yaml.org,2002:ActiveRecord"
40
+
41
+ def to_yaml(opts = {})
42
+ if id.nil?
43
+ raise("Can't serialize unsaved ActiveRecord object for delayed job: #{self.inspect}")
44
+ end
45
+ YAML.quick_emit(self.object_id, opts) do |out|
46
+ out.scalar(taguri, id.to_s)
47
+ end
48
+ end
49
+
50
+ def self.yaml_new(klass, tag, val)
51
+ klass.find(val)
52
+ rescue ActiveRecord::RecordNotFound
53
+ raise Delayed::Backend::RecordNotFound, "Couldn't find #{klass} with id #{val.inspect}"
54
+ end
55
+ end
56
+
57
+ # Load Module/Class from yaml tag.
58
+ class Module
59
+ def yaml_tag_read_class(name)
60
+ name.constantize
61
+ name
62
+ end
63
+ end
@@ -0,0 +1,40 @@
1
+ module Delayed
2
+ MIN_PRIORITY = 0
3
+ HIGH_PRIORITY = 0
4
+ NORMAL_PRIORITY = 10
5
+ LOW_PRIORITY = 20
6
+ LOWER_PRIORITY = 50
7
+ MAX_PRIORITY = 1_000_000
8
+
9
+ def self.select_backend(backend)
10
+ remove_const(:Job) if defined?(::Delayed::Job)
11
+ const_set(:Job, backend)
12
+ end
13
+ end
14
+
15
+ require 'rails'
16
+ require 'active_support/core_ext/module/attribute_accessors'
17
+ require 'active_record'
18
+ require 'after_transaction_commit'
19
+
20
+ require 'delayed/settings'
21
+ require 'delayed/yaml_extensions'
22
+
23
+ require 'delayed/backend/base'
24
+ require 'delayed/backend/active_record'
25
+ require 'delayed/backend/redis/job'
26
+ require 'delayed/batch'
27
+ require 'delayed/job_tracking'
28
+ require 'delayed/lifecycle'
29
+ require 'delayed/message_sending'
30
+ require 'delayed/performable_method'
31
+ require 'delayed/periodic'
32
+ require 'delayed/pool'
33
+ require 'delayed/worker'
34
+
35
+ require 'delayed/engine'
36
+
37
+ Delayed.select_backend(Delayed::Backend::ActiveRecord::Job)
38
+
39
+ Object.send(:include, Delayed::MessageSending)
40
+ Module.send(:include, Delayed::MessageSending::ClassMethods)
@@ -0,0 +1,61 @@
1
+ require File.expand_path("../spec_helper", __FILE__)
2
+
3
+ describe 'Delayed::Backed::ActiveRecord::Job' do
4
+ before :all do
5
+ Delayed.select_backend(Delayed::Backend::ActiveRecord::Job)
6
+ end
7
+
8
+ after :all do
9
+ Delayed.send(:remove_const, :Job)
10
+ end
11
+
12
+ before do
13
+ Delayed::Job.delete_all
14
+ Delayed::Job::Failed.delete_all
15
+ end
16
+
17
+ include_examples 'a delayed_jobs implementation'
18
+
19
+ it "should recover as well as possible from a failure failing a job" do
20
+ allow(Delayed::Job::Failed).to receive(:create).and_raise(RuntimeError)
21
+ job = "test".send_later_enqueue_args :reverse, no_delay: true
22
+ job_id = job.id
23
+ proc { job.fail! }.should raise_error
24
+ proc { Delayed::Job.find(job_id) }.should raise_error(ActiveRecord::RecordNotFound)
25
+ Delayed::Job.count.should == 0
26
+ end
27
+
28
+ context "when another worker has worked on a task since the job was found to be available, it" do
29
+ before :each do
30
+ @job = Delayed::Job.create :payload_object => SimpleJob.new
31
+ @job_copy_for_worker_2 = Delayed::Job.find(@job.id)
32
+ end
33
+
34
+ it "should not allow a second worker to get exclusive access if already successfully processed by worker1" do
35
+ @job.destroy
36
+ @job_copy_for_worker_2.lock_exclusively!('worker2').should == false
37
+ end
38
+
39
+ it "should not allow a second worker to get exclusive access if failed to be processed by worker1 and run_at time is now in future (due to backing off behaviour)" do
40
+ @job.update_attributes(:attempts => 1, :run_at => 1.day.from_now)
41
+ @job_copy_for_worker_2.lock_exclusively!('worker2').should == false
42
+ end
43
+
44
+ it "should select the next job at random if enabled" do
45
+ begin
46
+ Delayed::Settings.select_random_from_batch = true
47
+ 15.times { "test".send_later :length }
48
+ founds = []
49
+ 15.times do
50
+ job = Delayed::Job.get_and_lock_next_available('tester')
51
+ founds << job
52
+ job.unlock
53
+ job.save!
54
+ end
55
+ founds.uniq.size.should > 1
56
+ ensure
57
+ Delayed::Settings.select_random_from_batch = false
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec :path=>"../../"
4
+
5
+ gem "rails", "~> 3.2.19"
6
+
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec :path=>"../../"
4
+
5
+ gem "rails", "~> 4.0.10"
6
+
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec :path=>"../../"
4
+
5
+ gem "rails", "~> 4.1.6"
6
+
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec :path=>"../../"
4
+
5
+ gem "rails", "~> 4.2.0.beta2"
6
+
@@ -0,0 +1,7 @@
1
+ class AddStoryTable < ActiveRecord::Migration
2
+ def change
3
+ create_table :stories do |table|
4
+ table.string :text
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,77 @@
1
+ require File.expand_path("../spec_helper", __FILE__)
2
+
3
+ describe 'Delayed::Backend::Redis::Job' do
4
+ before :all do
5
+ Delayed.select_backend(Delayed::Backend::Redis::Job)
6
+ end
7
+
8
+ after :all do
9
+ Delayed.send(:remove_const, :Job)
10
+ end
11
+
12
+ before do
13
+ Delayed::Job.redis.flushdb
14
+ end
15
+
16
+ include_examples 'a delayed_jobs implementation'
17
+
18
+ describe "tickle_strand" do
19
+ it "should continue trying to tickle until the strand is empty" do
20
+ jobs = []
21
+ 3.times { jobs << "test".send_later_enqueue_args(:to_s, :strand => "s1", :no_delay => true) }
22
+ job = "test".send_later_enqueue_args(:to_s, :strand => "s1", :no_delay => true)
23
+ # manually delete the first jobs, bypassing the strand book-keeping
24
+ jobs.each { |j| Delayed::Job.redis.del(Delayed::Job::Keys::JOB[j.id]) }
25
+ Delayed::Job.redis.llen(Delayed::Job::Keys::STRAND['s1']).should == 4
26
+ job.destroy
27
+ Delayed::Job.redis.llen(Delayed::Job::Keys::STRAND['s1']).should == 0
28
+ end
29
+
30
+ it "should tickle until it finds an existing job" do
31
+ jobs = []
32
+ 3.times { jobs << "test".send_later_enqueue_args(:to_s, :strand => "s1", :no_delay => true) }
33
+ job = "test".send_later_enqueue_args(:to_s, :strand => "s1", :no_delay => true)
34
+ # manually delete the first jobs, bypassing the strand book-keeping
35
+ jobs[0...-1].each { |j| Delayed::Job.redis.del(Delayed::Job::Keys::JOB[j.id]) }
36
+ Delayed::Job.redis.llen(Delayed::Job::Keys::STRAND['s1']).should == 4
37
+ jobs[-1].destroy
38
+ Delayed::Job.redis.lrange(Delayed::Job::Keys::STRAND['s1'], 0, -1).should == [job.id]
39
+ found = [Delayed::Job.get_and_lock_next_available('test worker'),
40
+ Delayed::Job.get_and_lock_next_available('test worker')]
41
+ found.should =~ [job, nil]
42
+ end
43
+ end
44
+
45
+ describe "missing jobs in queues" do
46
+ before do
47
+ @job = "test".send_later_enqueue_args(:to_s, :no_delay => true)
48
+ @job2 = "test".send_later_enqueue_args(:to_s, :no_delay => true)
49
+ # manually delete the job from redis
50
+ Delayed::Job.redis.del(Delayed::Job::Keys::JOB[@job.id])
51
+ end
52
+
53
+ it "should discard when trying to lock" do
54
+ found = [Delayed::Job.get_and_lock_next_available("test worker"),
55
+ Delayed::Job.get_and_lock_next_available("test worker")]
56
+ found.should =~ [@job2, nil]
57
+ end
58
+
59
+ it "should filter for find_available" do
60
+ found = [Delayed::Job.find_available(1),
61
+ Delayed::Job.find_available(1)]
62
+ found.should be_include([@job2])
63
+ end
64
+ end
65
+
66
+ describe "send_later" do
67
+ it "should schedule job on transaction commit" do
68
+ before_count = Delayed::Job.jobs_count(:current)
69
+ ActiveRecord::Base.transaction do
70
+ job = "string".send_later :reverse
71
+ job.should be_nil
72
+ Delayed::Job.jobs_count(:current).should == before_count
73
+ end
74
+ Delayed::Job.jobs_count(:current) == before_count + 1
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,26 @@
1
+ class SimpleJob
2
+ cattr_accessor :runs; self.runs = 0
3
+ def perform; @@runs += 1; end
4
+ end
5
+
6
+ class ErrorJob
7
+ cattr_accessor :runs; self.runs = 0
8
+ def perform; raise 'did not work'; end
9
+
10
+ cattr_accessor :failure_runs; self.failure_runs = 0
11
+ def on_failure(error); @@failure_runs += 1; end
12
+
13
+ cattr_accessor :permanent_failure_runs; self.permanent_failure_runs = 0
14
+ def on_permanent_failure(error); @@permanent_failure_runs += 1; end
15
+ end
16
+
17
+ class LongRunningJob
18
+ def perform; sleep 250; end
19
+ end
20
+
21
+ module M
22
+ class ModuleJob
23
+ cattr_accessor :runs; self.runs = 0
24
+ def perform; @@runs += 1; end
25
+ end
26
+ end
@@ -0,0 +1,85 @@
1
+ shared_examples_for 'Delayed::Batch' do
2
+ context "batching" do
3
+ it "should batch up all deferrable delayed methods" do
4
+ later = 1.hour.from_now
5
+ Delayed::Batch.serial_batch {
6
+ "string".send_later_enqueue_args(:size, no_delay: true).should be true
7
+ "string".send_later_enqueue_args(:reverse, run_at: later, no_delay: true).should be_truthy # won't be batched, it'll get its own job
8
+ "string".send_later_enqueue_args(:gsub, { no_delay: true }, /./, "!").should be_truthy
9
+ }
10
+ batch_jobs = Delayed::Job.find_available(5)
11
+ regular_jobs = Delayed::Job.list_jobs(:future, 5)
12
+ regular_jobs.size.should == 1
13
+ regular_jobs.first.batch?.should == false
14
+ batch_jobs.size.should == 1
15
+ batch_job = batch_jobs.first
16
+ batch_job.batch?.should == true
17
+ batch_job.payload_object.mode.should == :serial
18
+ batch_job.payload_object.jobs.map { |j| [j.payload_object.object, j.payload_object.method, j.payload_object.args] }.should == [
19
+ ["string", :size, []],
20
+ ["string", :gsub, [/./, "!"]]
21
+ ]
22
+ end
23
+
24
+ it "should not let you invoke it directly" do
25
+ later = 1.hour.from_now
26
+ Delayed::Batch.serial_batch {
27
+ "string".send_later_enqueue_args(:size, no_delay: true).should be true
28
+ "string".send_later_enqueue_args(:gsub, { no_delay: true }, /./, "!").should be true
29
+ }
30
+ Delayed::Job.jobs_count(:current).should == 1
31
+ job = Delayed::Job.find_available(1).first
32
+ expect{ job.invoke_job }.to raise_error
33
+ end
34
+
35
+ it "should create valid jobs" do
36
+ Delayed::Batch.serial_batch {
37
+ "string".send_later_enqueue_args(:size, no_delay: true).should be true
38
+ "string".send_later_enqueue_args(:gsub, { no_delay: true }, /./, "!").should be true
39
+ }
40
+ Delayed::Job.jobs_count(:current).should == 1
41
+
42
+ batch_job = Delayed::Job.find_available(1).first
43
+ batch_job.batch?.should == true
44
+ jobs = batch_job.payload_object.jobs
45
+ jobs.size.should == 2
46
+ jobs[0].should be_new_record
47
+ jobs[0].payload_object.class.should == Delayed::PerformableMethod
48
+ jobs[0].payload_object.method.should == :size
49
+ jobs[0].payload_object.args.should == []
50
+ jobs[0].payload_object.perform.should == 6
51
+ jobs[1].should be_new_record
52
+ jobs[1].payload_object.class.should == Delayed::PerformableMethod
53
+ jobs[1].payload_object.method.should == :gsub
54
+ jobs[1].payload_object.args.should == [/./, "!"]
55
+ jobs[1].payload_object.perform.should == "!!!!!!"
56
+ end
57
+
58
+ it "should create a different batch for each priority" do
59
+ later = 1.hour.from_now
60
+ Delayed::Batch.serial_batch {
61
+ "string".send_later_enqueue_args(:size, :priority => Delayed::LOW_PRIORITY, :no_delay => true).should be true
62
+ "string".send_later_enqueue_args(:gsub, { :no_delay => true }, /./, "!").should be true
63
+ }
64
+ Delayed::Job.jobs_count(:current).should == 2
65
+ end
66
+
67
+ it "should use the given priority for all, if specified" do
68
+ Delayed::Batch.serial_batch(:priority => 11) {
69
+ "string".send_later_enqueue_args(:size, :priority => 20, :no_delay => true).should be true
70
+ "string".send_later_enqueue_args(:gsub, { :priority => 15, :no_delay => true }, /./, "!").should be true
71
+ }
72
+ Delayed::Job.jobs_count(:current).should == 1
73
+ Delayed::Job.find_available(1).first.priority.should == 11
74
+ end
75
+
76
+ it "should just create the job, if there's only one in the batch" do
77
+ Delayed::Batch.serial_batch(:priority => 11) {
78
+ "string".send_later_enqueue_args(:size, no_delay: true).should be true
79
+ }
80
+ Delayed::Job.jobs_count(:current).should == 1
81
+ Delayed::Job.find_available(1).first.tag.should == "String#size"
82
+ Delayed::Job.find_available(1).first.priority.should == 11
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,419 @@
1
+ shared_examples_for 'random ruby objects' do
2
+ def set_queue(name)
3
+ old_name = Delayed::Settings.queue
4
+ Delayed::Settings.queue = name
5
+ ensure
6
+ Delayed::Settings.queue = old_name
7
+ end
8
+
9
+ it "should respond_to :send_later method" do
10
+ Object.new.respond_to?(:send_later)
11
+ end
12
+
13
+ it "should raise a ArgumentError if send_later is called but the target method doesn't exist" do
14
+ lambda { Object.new.send_later(:method_that_deos_not_exist) }.should raise_error(NoMethodError)
15
+ end
16
+
17
+ it "should add a new entry to the job table when send_later is called on it" do
18
+ lambda { Object.new.send_later(:to_s) }.should change { Delayed::Job.jobs_count(:current) }.by(1)
19
+ end
20
+
21
+ it "should add a new entry to the job table when send_later_with_queue is called on it" do
22
+ lambda { Object.new.send_later_with_queue(:to_s, "testqueue") }.should change { Delayed::Job.jobs_count(:current, "testqueue") }.by(1)
23
+ end
24
+
25
+ it "should add a new entry to the job table when send_later is called on the class" do
26
+ lambda { Object.send_later(:to_s) }.should change { Delayed::Job.jobs_count(:current) }.by(1)
27
+ end
28
+
29
+ it "should add a new entry to the job table when send_later_with_queue is called on the class" do
30
+ lambda { Object.send_later_with_queue(:to_s, "testqueue") }.should change { Delayed::Job.jobs_count(:current, "testqueue") }.by(1)
31
+ end
32
+
33
+ context "class methods" do
34
+ context "add_send_later_methods" do
35
+ it "should work with default_async" do
36
+ class TestObject
37
+ attr_reader :ran
38
+ def test_method; @ran = true; end
39
+ add_send_later_methods :test_method, {}, true
40
+ end
41
+ obj = TestObject.new
42
+ lambda { obj.test_method }.should change { Delayed::Job.jobs_count(:current) }.by(1)
43
+ lambda { obj.test_method_with_send_later }.should change { Delayed::Job.jobs_count(:current) }.by(1)
44
+ obj.ran.should be_falsey
45
+ lambda { obj.test_method_without_send_later }.should_not change { Delayed::Job.jobs_count(:current) }
46
+ obj.ran.should be true
47
+ end
48
+
49
+ it "should work without default_async" do
50
+ class TestObject
51
+ attr_accessor :ran
52
+ def test_method; @ran = true; end
53
+ add_send_later_methods :test_method, {}, false
54
+ end
55
+ obj = TestObject.new
56
+ lambda { obj.test_method_with_send_later }.should change { Delayed::Job.jobs_count(:current) }.by(1)
57
+ obj.ran.should be_falsey
58
+ lambda { obj.test_method }.should_not change { Delayed::Job.jobs_count(:current) }
59
+ obj.ran.should be true
60
+ obj.ran = false
61
+ obj.ran.should be false
62
+ lambda { obj.test_method_without_send_later }.should_not change { Delayed::Job.jobs_count(:current) }
63
+ obj.ran.should be true
64
+ end
65
+
66
+ it "should send along enqueue args and args default async" do
67
+ class TestObject
68
+ attr_reader :ran
69
+ def test_method(*args); @ran = args; end
70
+ add_send_later_methods(:test_method, {:enqueue_arg_1 => :thing}, true)
71
+ end
72
+ obj = TestObject.new
73
+ method = double()
74
+ expect(Delayed::PerformableMethod).to receive(:new).with(obj, :test_method_without_send_later, [1,2,3]).and_return(method)
75
+ expect(Delayed::Job).to receive(:enqueue).with(method, :enqueue_arg_1 => :thing)
76
+ obj.test_method(1,2,3)
77
+ expect(Delayed::PerformableMethod).to receive(:new).with(obj, :test_method_without_send_later, [4]).and_return(method)
78
+ expect(Delayed::Job).to receive(:enqueue).with(method, :enqueue_arg_1 => :thing)
79
+ obj.test_method(4)
80
+ expect(Delayed::PerformableMethod).to receive(:new).with(obj, :test_method_without_send_later, [6]).and_return(method)
81
+ expect(Delayed::Job).to receive(:enqueue).with(method, :enqueue_arg_1 => :thing)
82
+ obj.test_method_with_send_later(6)
83
+ expect(Delayed::PerformableMethod).to receive(:new).with(obj, :test_method_without_send_later, [5,6]).and_return(method)
84
+ expect(Delayed::Job).to receive(:enqueue).with(method, :enqueue_arg_1 => :thing)
85
+ obj.test_method_with_send_later(5,6)
86
+ obj.ran.should be_nil
87
+ obj.test_method_without_send_later(7)
88
+ obj.ran.should == [7]
89
+ obj.ran = nil
90
+ obj.ran.should == nil
91
+ obj.test_method_without_send_later(8,9)
92
+ obj.ran.should == [8,9]
93
+ end
94
+
95
+ it "should send along enqueue args and args without default async" do
96
+ class TestObject
97
+ attr_reader :ran
98
+ def test_method(*args); @ran = args; end
99
+ add_send_later_methods(:test_method, {:enqueue_arg_2 => :thing2}, false)
100
+ end
101
+ obj = TestObject.new
102
+ method = double()
103
+ expect(Delayed::PerformableMethod).to receive(:new).with(obj, :test_method_without_send_later, [6]).and_return(method)
104
+ expect(Delayed::Job).to receive(:enqueue).with(method, :enqueue_arg_2 => :thing2)
105
+ obj.test_method_with_send_later(6)
106
+ expect(Delayed::PerformableMethod).to receive(:new).with(obj, :test_method_without_send_later, [5,6]).and_return(method)
107
+ expect(Delayed::Job).to receive(:enqueue).with(method, :enqueue_arg_2 => :thing2)
108
+ obj.test_method_with_send_later(5,6)
109
+ obj.ran.should be_nil
110
+ obj.test_method(1,2,3)
111
+ obj.ran.should == [1,2,3]
112
+ obj.ran = nil
113
+ obj.ran.should == nil
114
+ obj.test_method(4)
115
+ obj.ran.should == [4]
116
+ obj.ran = nil
117
+ obj.ran.should == nil
118
+ obj.test_method_without_send_later(7)
119
+ obj.ran.should == [7]
120
+ obj.ran = nil
121
+ obj.ran.should == nil
122
+ obj.test_method_without_send_later(8,9)
123
+ obj.ran.should == [8,9]
124
+ end
125
+
126
+ it "should handle punctuation correctly with default_async" do
127
+ class TestObject
128
+ attr_reader :ran
129
+ def test_method?; @ran = true; end
130
+ add_send_later_methods :test_method?, {}, true
131
+ end
132
+ obj = TestObject.new
133
+ lambda { obj.test_method? }.should change { Delayed::Job.jobs_count(:current) }.by(1)
134
+ lambda { obj.test_method_with_send_later? }.should change { Delayed::Job.jobs_count(:current) }.by(1)
135
+ obj.ran.should be_falsey
136
+ lambda { obj.test_method_without_send_later? }.should_not change { Delayed::Job.jobs_count(:current) }
137
+ obj.ran.should be true
138
+ end
139
+
140
+ it "should handle punctuation correctly without default_async" do
141
+ class TestObject
142
+ attr_accessor :ran
143
+ def test_method?; @ran = true; end
144
+ add_send_later_methods :test_method?, {}, false
145
+ end
146
+ obj = TestObject.new
147
+ lambda { obj.test_method_with_send_later? }.should change { Delayed::Job.jobs_count(:current) }.by(1)
148
+ obj.ran.should be_falsey
149
+ lambda { obj.test_method? }.should_not change { Delayed::Job.jobs_count(:current) }
150
+ obj.ran.should be true
151
+ obj.ran = false
152
+ obj.ran.should be false
153
+ lambda { obj.test_method_without_send_later? }.should_not change { Delayed::Job.jobs_count(:current) }
154
+ obj.ran.should be true
155
+ end
156
+
157
+ it "should handle assignment punctuation correctly with default_async" do
158
+ class TestObject
159
+ attr_reader :ran
160
+ def test_method=(val); @ran = val; end
161
+ add_send_later_methods :test_method=, {}, true
162
+ end
163
+ obj = TestObject.new
164
+ lambda { obj.test_method = 3 }.should change { Delayed::Job.jobs_count(:current) }.by(1)
165
+ lambda { obj.test_method_with_send_later = 4 }.should change { Delayed::Job.jobs_count(:current) }.by(1)
166
+ obj.ran.should be_nil
167
+ lambda { obj.test_method_without_send_later = 5 }.should_not change { Delayed::Job.jobs_count(:current) }
168
+ obj.ran.should == 5
169
+ end
170
+
171
+ it "should handle assignment punctuation correctly without default_async" do
172
+ class TestObject
173
+ attr_accessor :ran
174
+ def test_method=(val); @ran = val; end
175
+ add_send_later_methods :test_method=, {}, false
176
+ end
177
+ obj = TestObject.new
178
+ lambda { obj.test_method_with_send_later = 1 }.should change { Delayed::Job.jobs_count(:current) }.by(1)
179
+ obj.ran.should be_nil
180
+ lambda { obj.test_method = 2 }.should_not change { Delayed::Job.jobs_count(:current) }
181
+ obj.ran.should == 2
182
+ lambda { obj.test_method_without_send_later = 3 }.should_not change { Delayed::Job.jobs_count(:current) }
183
+ obj.ran.should == 3
184
+ end
185
+
186
+ it "should correctly sort out method accessibility with default async" do
187
+ class TestObject1
188
+ def test_method; end
189
+ add_send_later_methods :test_method, {}, true
190
+ end
191
+ class TestObject2
192
+ protected
193
+ def test_method; end
194
+ add_send_later_methods :test_method, {}, true
195
+ end
196
+ class TestObject3
197
+ private
198
+ def test_method; end
199
+ add_send_later_methods :test_method, {}, true
200
+ end
201
+ TestObject1.public_method_defined?(:test_method).should be true
202
+ TestObject2.public_method_defined?(:test_method).should be false
203
+ TestObject3.public_method_defined?(:test_method).should be false
204
+ TestObject2.protected_method_defined?(:test_method).should be true
205
+ TestObject3.protected_method_defined?(:test_method).should be false
206
+ TestObject3.private_method_defined?(:test_method).should be true
207
+ end
208
+ end
209
+
210
+ context "handle_asynchonously_if_production" do
211
+ it "should work in production" do
212
+ expect(Rails.env).to receive(:production?).and_return(true)
213
+ class TestObject; end
214
+ expect(TestObject).to receive(:add_send_later_methods).with(:test_method, {}, true)
215
+ class TestObject
216
+ def test_method; end
217
+ handle_asynchronously_if_production :test_method
218
+ end
219
+ end
220
+
221
+ it "should work in other environments" do
222
+ class TestObject; end
223
+ expect(TestObject).to receive(:add_send_later_methods).with(:test_method, {}, false)
224
+ class TestObject
225
+ def test_method; end
226
+ handle_asynchronously_if_production :test_method
227
+ end
228
+ end
229
+
230
+ it "should pass along enqueue args in production" do
231
+ expect(Rails.env).to receive(:production?).and_return(true)
232
+ class TestObject; end
233
+ expect(TestObject).to receive(:add_send_later_methods).with(:test_method, {:enqueue_arg_1 => true}, true)
234
+ class TestObject
235
+ def test_method; end
236
+ handle_asynchronously_if_production :test_method, :enqueue_arg_1 => true
237
+ end
238
+ end
239
+
240
+ it "should pass along enqueue args in other environments" do
241
+ class TestObject; end
242
+ expect(TestObject).to receive(:add_send_later_methods).with(:test_method, {:enqueue_arg_2 => "thing", :enqueue_arg_3 => 4}, false)
243
+ class TestObject
244
+ def test_method; end
245
+ handle_asynchronously_if_production :test_method, :enqueue_arg_2 => "thing", :enqueue_arg_3 => 4
246
+ end
247
+ end
248
+ end
249
+
250
+ context "handle_asynchronously" do
251
+ it "should work without enqueue_args" do
252
+ class TestObject; end
253
+ expect(TestObject).to receive(:add_send_later_methods).with(:test_method, {}, true)
254
+ class TestObject
255
+ def test_method; end
256
+ handle_asynchronously :test_method
257
+ end
258
+ end
259
+
260
+ it "should work with enqueue_args" do
261
+ class TestObject; end
262
+ expect(TestObject).to receive(:add_send_later_methods).with(:test_method, {:enqueue_arg_1 => :thing}, true)
263
+ class TestObject
264
+ def test_method; end
265
+ handle_asynchronously :test_method, :enqueue_arg_1 => :thing
266
+ end
267
+ end
268
+ end
269
+
270
+ context "handle_asynchronously_with_queue" do
271
+ it "should pass along the queue" do
272
+ class TestObject; end
273
+ expect(TestObject).to receive(:add_send_later_methods).with(:test_method, {:queue => "myqueue"}, true)
274
+ class TestObject
275
+ def test_method; end
276
+ handle_asynchronously_with_queue :test_method, "myqueue"
277
+ end
278
+ end
279
+ end
280
+ end
281
+
282
+ it "should call send later on methods which are wrapped with handle_asynchronously" do
283
+ story = Story.create :text => 'Once upon...'
284
+
285
+ expect { story.whatever(1, 5) }.to change { Delayed::Job.jobs_count(:current) }.by(1)
286
+
287
+ job = Delayed::Job.list_jobs(:current, 1).first
288
+ job.payload_object.class.should == Delayed::PerformableMethod
289
+ job.payload_object.method.should == :whatever_without_send_later
290
+ job.payload_object.args.should == [1, 5]
291
+ job.payload_object.perform.should == 'Once upon...'
292
+ end
293
+
294
+ it "should call send later on methods which are wrapped with handle_asynchronously_with_queue" do
295
+ story = Story.create :text => 'Once upon...'
296
+
297
+ expect { story.whatever_else(1, 5) }.to change { Delayed::Job.jobs_count(:current, "testqueue") }.by(1)
298
+
299
+ job = Delayed::Job.list_jobs(:current, 1, 0, "testqueue").first
300
+ job.payload_object.class.should == Delayed::PerformableMethod
301
+ job.payload_object.method.should == :whatever_else_without_send_later
302
+ job.payload_object.args.should == [1, 5]
303
+ job.payload_object.perform.should == 'Once upon...'
304
+ end
305
+
306
+ context "send_later" do
307
+ it "should use the default queue if there is one" do
308
+ set_queue("testqueue") do
309
+ "string".send_later :reverse
310
+ job = Delayed::Job.list_jobs(:current, 1).first
311
+ job.queue.should == "testqueue"
312
+
313
+ "string".send_later :reverse, :queue => nil
314
+ job2 = Delayed::Job.list_jobs(:current, 2).last
315
+ job2.queue.should == "testqueue"
316
+ end
317
+ end
318
+
319
+ it "should require a queue" do
320
+ expect { set_queue(nil) }.to raise_error(ArgumentError)
321
+ end
322
+ end
323
+
324
+ context "send_at" do
325
+ it "should queue a new job" do
326
+ lambda do
327
+ "string".send_at(1.hour.from_now, :length)
328
+ end.should change { Delayed::Job.jobs_count(:future) }.by(1)
329
+ end
330
+
331
+ it "should schedule the job in the future" do
332
+ time = 1.hour.from_now
333
+ "string".send_at(time, :length)
334
+ job = Delayed::Job.list_jobs(:future, 1).first
335
+ job.run_at.to_i.should == time.to_i
336
+ end
337
+
338
+ it "should store payload as PerformableMethod" do
339
+ "string".send_at(1.hour.from_now, :count, 'r')
340
+ job = Delayed::Job.list_jobs(:future, 1).first
341
+ job.payload_object.class.should == Delayed::PerformableMethod
342
+ job.payload_object.method.should == :count
343
+ job.payload_object.args.should == ['r']
344
+ job.payload_object.perform.should == 1
345
+ end
346
+
347
+ it "should use the default queue if there is one" do
348
+ set_queue("testqueue") do
349
+ "string".send_at 1.hour.from_now, :reverse
350
+ job = Delayed::Job.list_jobs(:current, 1).first
351
+ job.queue.should == "testqueue"
352
+ end
353
+ end
354
+ end
355
+
356
+ context "send_at_with_queue" do
357
+ it "should queue a new job" do
358
+ lambda do
359
+ "string".send_at_with_queue(1.hour.from_now, :length, "testqueue")
360
+ end.should change { Delayed::Job.jobs_count(:future, "testqueue") }.by(1)
361
+ end
362
+
363
+ it "should schedule the job in the future" do
364
+ time = 1.hour.from_now
365
+ "string".send_at_with_queue(time, :length, "testqueue")
366
+ job = Delayed::Job.list_jobs(:future, 1, 0, "testqueue").first
367
+ job.run_at.to_i.should == time.to_i
368
+ end
369
+
370
+ it "should override the default queue" do
371
+ set_queue("default_queue") do
372
+ "string".send_at_with_queue(1.hour.from_now, :length, "testqueue")
373
+ job = Delayed::Job.list_jobs(:future, 1, 0, "testqueue").first
374
+ job.queue.should == "testqueue"
375
+ end
376
+ end
377
+
378
+ it "should store payload as PerformableMethod" do
379
+ "string".send_at_with_queue(1.hour.from_now, :count, "testqueue", 'r')
380
+ job = Delayed::Job.list_jobs(:future, 1, 0, "testqueue").first
381
+ job.payload_object.class.should == Delayed::PerformableMethod
382
+ job.payload_object.method.should == :count
383
+ job.payload_object.args.should == ['r']
384
+ job.payload_object.perform.should == 1
385
+ end
386
+ end
387
+
388
+ describe "send_later_unless_in_job" do
389
+ module UnlessInJob
390
+ @runs = 0
391
+ def self.runs; @runs; end
392
+
393
+ def self.run
394
+ @runs += 1
395
+ end
396
+
397
+ def self.run_later
398
+ self.send_later_unless_in_job :run
399
+ end
400
+ end
401
+
402
+ before do
403
+ UnlessInJob.class_eval { @runs = 0 }
404
+ end
405
+
406
+ it "should perform immediately if in job" do
407
+ UnlessInJob.send_later :run_later
408
+ job = Delayed::Job.list_jobs(:current, 1).first
409
+ job.invoke_job
410
+ UnlessInJob.runs.should == 1
411
+ end
412
+
413
+ it "should queue up for later if not in job" do
414
+ UnlessInJob.run_later
415
+ UnlessInJob.runs.should == 0
416
+ end
417
+ end
418
+
419
+ end