canvas-jobs 0.9.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 (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