drewda_delayed_job 3.0.3

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 (43) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.textile +314 -0
  3. data/contrib/delayed_job.monitrc +14 -0
  4. data/contrib/delayed_job_multiple.monitrc +23 -0
  5. data/lib/delayed/backend/base.rb +184 -0
  6. data/lib/delayed/backend/shared_spec.rb +595 -0
  7. data/lib/delayed/command.rb +108 -0
  8. data/lib/delayed/deserialization_error.rb +4 -0
  9. data/lib/delayed/lifecycle.rb +84 -0
  10. data/lib/delayed/message_sending.rb +54 -0
  11. data/lib/delayed/performable_mailer.rb +21 -0
  12. data/lib/delayed/performable_method.rb +37 -0
  13. data/lib/delayed/plugin.rb +15 -0
  14. data/lib/delayed/plugins/clear_locks.rb +15 -0
  15. data/lib/delayed/psych_ext.rb +132 -0
  16. data/lib/delayed/railtie.rb +16 -0
  17. data/lib/delayed/recipes.rb +50 -0
  18. data/lib/delayed/serialization/active_record.rb +19 -0
  19. data/lib/delayed/syck_ext.rb +34 -0
  20. data/lib/delayed/tasks.rb +11 -0
  21. data/lib/delayed/worker.rb +242 -0
  22. data/lib/delayed/yaml_ext.rb +10 -0
  23. data/lib/delayed_job.rb +21 -0
  24. data/lib/generators/delayed_job/delayed_job_generator.rb +11 -0
  25. data/lib/generators/delayed_job/templates/script +5 -0
  26. data/recipes/delayed_job.rb +1 -0
  27. data/spec/autoloaded/clazz.rb +7 -0
  28. data/spec/autoloaded/instance_clazz.rb +6 -0
  29. data/spec/autoloaded/instance_struct.rb +6 -0
  30. data/spec/autoloaded/struct.rb +7 -0
  31. data/spec/delayed/backend/test.rb +113 -0
  32. data/spec/delayed/serialization/test.rb +0 -0
  33. data/spec/fixtures/bad_alias.yml +1 -0
  34. data/spec/lifecycle_spec.rb +67 -0
  35. data/spec/message_sending_spec.rb +113 -0
  36. data/spec/performable_mailer_spec.rb +44 -0
  37. data/spec/performable_method_spec.rb +89 -0
  38. data/spec/sample_jobs.rb +75 -0
  39. data/spec/spec_helper.rb +53 -0
  40. data/spec/test_backend_spec.rb +13 -0
  41. data/spec/worker_spec.rb +19 -0
  42. data/spec/yaml_ext_spec.rb +41 -0
  43. metadata +214 -0
@@ -0,0 +1,6 @@
1
+ module Autoloaded
2
+ class InstanceStruct < ::Struct.new(nil)
3
+ def perform
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,7 @@
1
+ # Make sure this file does not get required manually
2
+ module Autoloaded
3
+ class Struct < ::Struct.new(nil)
4
+ def perform
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,113 @@
1
+ require 'ostruct'
2
+
3
+ # An in-memory backend suitable only for testing. Tries to behave as if it were an ORM.
4
+ module Delayed
5
+ module Backend
6
+ module Test
7
+ class Job
8
+ attr_accessor :id
9
+ attr_accessor :priority
10
+ attr_accessor :attempts
11
+ attr_accessor :handler
12
+ attr_accessor :last_error
13
+ attr_accessor :run_at
14
+ attr_accessor :locked_at
15
+ attr_accessor :locked_by
16
+ attr_accessor :failed_at
17
+ attr_accessor :queue
18
+
19
+ include Delayed::Backend::Base
20
+
21
+ cattr_accessor :id
22
+ self.id = 0
23
+
24
+ def initialize(hash = {})
25
+ self.attempts = 0
26
+ self.priority = 0
27
+ self.id = (self.class.id += 1)
28
+ hash.each{|k,v| send(:"#{k}=", v)}
29
+ end
30
+
31
+ @jobs = []
32
+ def self.all
33
+ @jobs
34
+ end
35
+
36
+ def self.count
37
+ all.size
38
+ end
39
+
40
+ def self.delete_all
41
+ all.clear
42
+ end
43
+
44
+ def self.create(attrs = {})
45
+ new(attrs).tap do |o|
46
+ o.save
47
+ end
48
+ end
49
+
50
+ def self.create!(*args); create(*args); end
51
+
52
+ def self.clear_locks!(worker_name)
53
+ all.select{|j| j.locked_by == worker_name}.each {|j| j.locked_by = nil; j.locked_at = nil; j.is_locked = false}
54
+ end
55
+
56
+ # Find a few candidate jobs to run (in case some immediately get locked by others).
57
+ def self.find_available(worker_name, limit = 5, max_run_time = Worker.max_run_time)
58
+ jobs = all.select do |j|
59
+ j.run_at <= db_time_now &&
60
+ (j.locked_at.nil? || j.locked_at < db_time_now - max_run_time || j.locked_by == worker_name) &&
61
+ !j.failed?
62
+ end
63
+
64
+ jobs = jobs.select{|j| Worker.queues.include?(j.queue)} if Worker.queues.any?
65
+ jobs = jobs.select{|j| j.priority >= Worker.min_priority} if Worker.min_priority
66
+ jobs = jobs.select{|j| j.priority <= Worker.max_priority} if Worker.max_priority
67
+ jobs.sort_by{|j| [j.priority, j.run_at]}[0..limit-1]
68
+ end
69
+
70
+ # Lock this job for this worker.
71
+ # Returns true if we have the lock, false otherwise.
72
+ def lock_exclusively!(max_run_time, worker)
73
+ now = self.class.db_time_now
74
+ if locked_by != worker
75
+ # We don't own this job so we will update the locked_by name and the locked_at
76
+ self.locked_at = now
77
+ self.locked_by = worker
78
+ self.is_locked = true
79
+ end
80
+
81
+ return true
82
+ end
83
+
84
+ def self.db_time_now
85
+ Time.current
86
+ end
87
+
88
+ def update_attributes(attrs = {})
89
+ attrs.each{|k,v| send(:"#{k}=", v)}
90
+ save
91
+ end
92
+
93
+ def destroy
94
+ self.class.all.delete(self)
95
+ end
96
+
97
+ def save
98
+ self.run_at ||= Time.current
99
+
100
+ self.class.all << self unless self.class.all.include?(self)
101
+ true
102
+ end
103
+
104
+ def save!; save; end
105
+
106
+ def reload
107
+ reset
108
+ self
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
File without changes
@@ -0,0 +1 @@
1
+ foo: *bar
@@ -0,0 +1,67 @@
1
+ require 'spec_helper'
2
+
3
+ describe Delayed::Lifecycle do
4
+ let(:lifecycle) { Delayed::Lifecycle.new }
5
+ let(:callback) { lambda {|*args|} }
6
+ let(:arguments) { [1] }
7
+ let(:behavior) { mock(Object, :before! => nil, :after! => nil, :inside! => nil) }
8
+ let(:wrapped_block) { Proc.new { behavior.inside! } }
9
+
10
+ describe "before callbacks" do
11
+ before(:each) do
12
+ lifecycle.before(:execute, &callback)
13
+ end
14
+
15
+ it 'should execute before wrapped block' do
16
+ callback.should_receive(:call).with(*arguments).ordered
17
+ behavior.should_receive(:inside!).ordered
18
+ lifecycle.run_callbacks :execute, *arguments, &wrapped_block
19
+ end
20
+ end
21
+
22
+ describe "after callbacks" do
23
+ before(:each) do
24
+ lifecycle.after(:execute, &callback)
25
+ end
26
+
27
+ it 'should execute after wrapped block' do
28
+ behavior.should_receive(:inside!).ordered
29
+ callback.should_receive(:call).with(*arguments).ordered
30
+ lifecycle.run_callbacks :execute, *arguments, &wrapped_block
31
+ end
32
+ end
33
+
34
+ describe "around callbacks" do
35
+ before(:each) do
36
+ lifecycle.around(:execute) do |*args, &block|
37
+ behavior.before!
38
+ block.call(*args)
39
+ behavior.after!
40
+ end
41
+ end
42
+
43
+ it 'should before and after wrapped block' do
44
+ behavior.should_receive(:before!).ordered
45
+ behavior.should_receive(:inside!).ordered
46
+ behavior.should_receive(:after!).ordered
47
+ lifecycle.run_callbacks :execute, *arguments, &wrapped_block
48
+ end
49
+
50
+ it "should execute multiple callbacks in order" do
51
+ behavior.should_receive(:one).ordered
52
+ behavior.should_receive(:two).ordered
53
+ behavior.should_receive(:three).ordered
54
+
55
+ lifecycle.around(:execute) { |*args, &block| behavior.one; block.call(*args) }
56
+ lifecycle.around(:execute) { |*args, &block| behavior.two; block.call(*args) }
57
+ lifecycle.around(:execute) { |*args, &block| behavior.three; block.call(*args) }
58
+
59
+ lifecycle.run_callbacks(:execute, *arguments, &wrapped_block)
60
+ end
61
+ end
62
+
63
+ it "should raise if callback is executed with wrong number of parameters" do
64
+ lifecycle.before(:execute, &callback)
65
+ expect { lifecycle.run_callbacks(:execute, 1,2,3) {} }.to raise_error(ArgumentError, /1 parameter/)
66
+ end
67
+ end
@@ -0,0 +1,113 @@
1
+ require 'spec_helper'
2
+
3
+ describe Delayed::MessageSending do
4
+ describe "handle_asynchronously" do
5
+ class Story
6
+ def tell!(arg);end
7
+ handle_asynchronously :tell!
8
+ end
9
+
10
+ it "should alias original method" do
11
+ Story.new.should respond_to(:tell_without_delay!)
12
+ Story.new.should respond_to(:tell_with_delay!)
13
+ end
14
+
15
+ it "should create a PerformableMethod" do
16
+ story = Story.create
17
+ lambda {
18
+ job = story.tell!(1)
19
+ job.payload_object.class.should == Delayed::PerformableMethod
20
+ job.payload_object.method_name.should == :tell_without_delay!
21
+ job.payload_object.args.should == [1]
22
+ }.should change { Delayed::Job.count }
23
+ end
24
+
25
+ describe 'with options' do
26
+ class Fable
27
+ cattr_accessor :importance
28
+ def tell;end
29
+ handle_asynchronously :tell, :priority => Proc.new { self.importance }
30
+ end
31
+
32
+ it 'should set the priority based on the Fable importance' do
33
+ Fable.importance = 10
34
+ job = Fable.new.tell
35
+ job.priority.should == 10
36
+
37
+ Fable.importance = 20
38
+ job = Fable.new.tell
39
+ job.priority.should == 20
40
+ end
41
+
42
+ describe 'using a proc with parameters' do
43
+ class Yarn
44
+ attr_accessor :importance
45
+ def spin
46
+ end
47
+ handle_asynchronously :spin, :priority => Proc.new {|y| y.importance }
48
+ end
49
+
50
+ it 'should set the priority based on the Fable importance' do
51
+ job = Yarn.new.tap {|y| y.importance = 10 }.spin
52
+ job.priority.should == 10
53
+
54
+ job = Yarn.new.tap {|y| y.importance = 20 }.spin
55
+ job.priority.should == 20
56
+ end
57
+ end
58
+ end
59
+ end
60
+
61
+ context "delay" do
62
+ class FairyTail
63
+ attr_accessor :happy_ending
64
+ def self.princesses;end
65
+ def tell
66
+ @happy_ending = true
67
+ end
68
+ end
69
+
70
+ it "should create a new PerformableMethod job" do
71
+ lambda {
72
+ job = "hello".delay.count('l')
73
+ job.payload_object.class.should == Delayed::PerformableMethod
74
+ job.payload_object.method_name.should == :count
75
+ job.payload_object.args.should == ['l']
76
+ }.should change { Delayed::Job.count }.by(1)
77
+ end
78
+
79
+ it "should set default priority" do
80
+ Delayed::Worker.default_priority = 99
81
+ job = FairyTail.delay.to_s
82
+ job.priority.should == 99
83
+ Delayed::Worker.default_priority = 0
84
+ end
85
+
86
+ it "should set job options" do
87
+ run_at = Time.parse('2010-05-03 12:55 AM')
88
+ job = FairyTail.delay(:priority => 20, :run_at => run_at).to_s
89
+ job.run_at.should == run_at
90
+ job.priority.should == 20
91
+ end
92
+
93
+ it "should not delay the job when delay_jobs is false" do
94
+ Delayed::Worker.delay_jobs = false
95
+ fairy_tail = FairyTail.new
96
+ lambda {
97
+ lambda {
98
+ fairy_tail.delay.tell
99
+ }.should change(fairy_tail, :happy_ending).from(nil).to(true)
100
+ }.should_not change { Delayed::Job.count }
101
+ end
102
+
103
+ it "should delay the job when delay_jobs is true" do
104
+ Delayed::Worker.delay_jobs = true
105
+ fairy_tail = FairyTail.new
106
+ lambda {
107
+ lambda {
108
+ fairy_tail.delay.tell
109
+ }.should_not change(fairy_tail, :happy_ending)
110
+ }.should change { Delayed::Job.count }.by(1)
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,44 @@
1
+ require 'spec_helper'
2
+
3
+ require 'action_mailer'
4
+ class MyMailer < ActionMailer::Base
5
+ def signup(email)
6
+ mail :to => email, :subject => "Delaying Emails", :from => "delayedjob@example.com"
7
+ end
8
+ end
9
+
10
+ describe ActionMailer::Base do
11
+ describe "delay" do
12
+ it "should enqueue a PerformableEmail job" do
13
+ lambda {
14
+ job = MyMailer.delay.signup('john@example.com')
15
+ job.payload_object.class.should == Delayed::PerformableMailer
16
+ job.payload_object.method_name.should == :signup
17
+ job.payload_object.args.should == ['john@example.com']
18
+ }.should change { Delayed::Job.count }.by(1)
19
+ end
20
+ end
21
+
22
+ describe "delay on a mail object" do
23
+ it "should raise an exception" do
24
+ lambda {
25
+ MyMailer.signup('john@example.com').delay
26
+ }.should raise_error(RuntimeError)
27
+ end
28
+ end
29
+
30
+ describe Delayed::PerformableMailer do
31
+ describe "perform" do
32
+ it "should call the method and #deliver on the mailer" do
33
+ email = mock('email', :deliver => true)
34
+ mailer_class = mock('MailerClass', :signup => email)
35
+ mailer = Delayed::PerformableMailer.new(mailer_class, :signup, ['john@example.com'])
36
+
37
+ mailer_class.should_receive(:signup).with('john@example.com')
38
+ email.should_receive(:deliver)
39
+ mailer.perform
40
+ end
41
+ end
42
+ end
43
+
44
+ end
@@ -0,0 +1,89 @@
1
+ require 'spec_helper'
2
+
3
+ describe Delayed::PerformableMethod do
4
+ describe "perform" do
5
+ before do
6
+ @method = Delayed::PerformableMethod.new("foo", :count, ['o'])
7
+ end
8
+
9
+ context "with the persisted record cannot be found" do
10
+ before do
11
+ @method.object = nil
12
+ end
13
+
14
+ it "should be a no-op if object is nil" do
15
+ lambda { @method.perform }.should_not raise_error
16
+ end
17
+ end
18
+
19
+ it "should call the method on the object" do
20
+ @method.object.should_receive(:count).with('o')
21
+ @method.perform
22
+ end
23
+ end
24
+
25
+ it "should raise a NoMethodError if target method doesn't exist" do
26
+ lambda {
27
+ Delayed::PerformableMethod.new(Object, :method_that_does_not_exist, [])
28
+ }.should raise_error(NoMethodError)
29
+ end
30
+
31
+ it "should not raise NoMethodError if target method is private" do
32
+ clazz = Class.new do
33
+ def private_method
34
+ end
35
+ private :private_method
36
+ end
37
+ lambda {
38
+ Delayed::PerformableMethod.new(clazz.new, :private_method, [])
39
+ }.should_not raise_error(NoMethodError)
40
+ end
41
+
42
+ describe "hooks" do
43
+ %w(enqueue before after success).each do |hook|
44
+ it "should delegate #{hook} hook to object" do
45
+ story = Story.create
46
+ story.should_receive(hook).with(an_instance_of(Delayed::Job))
47
+ story.delay.tell.invoke_job
48
+ end
49
+ end
50
+
51
+ %w(before after success).each do |hook|
52
+ it "should delegate #{hook} hook to object when delay_jobs = false" do
53
+ Delayed::Worker.delay_jobs = false
54
+ story = Story.create
55
+ story.should_receive(hook).with(an_instance_of(Delayed::Job))
56
+ story.delay.tell
57
+ end
58
+ end
59
+
60
+ it "should delegate error hook to object" do
61
+ story = Story.create
62
+ story.should_receive(:error).with(an_instance_of(Delayed::Job), an_instance_of(RuntimeError))
63
+ story.should_receive(:tell).and_raise(RuntimeError)
64
+ lambda { story.delay.tell.invoke_job }.should raise_error
65
+ end
66
+
67
+ it "should delegate error hook to object when delay_jobs = false" do
68
+ Delayed::Worker.delay_jobs = false
69
+ story = Story.create
70
+ story.should_receive(:error).with(an_instance_of(Delayed::Job), an_instance_of(RuntimeError))
71
+ story.should_receive(:tell).and_raise(RuntimeError)
72
+ lambda { story.delay.tell }.should raise_error
73
+ end
74
+
75
+ it "should delegate failure hook to object" do
76
+ method = Delayed::PerformableMethod.new("object", :size, [])
77
+ method.object.should_receive(:failure)
78
+ method.failure
79
+ end
80
+
81
+ it "should delegate failure hook to object when delay_jobs = false" do
82
+ Delayed::Worker.delay_jobs = false
83
+ method = Delayed::PerformableMethod.new("object", :size, [])
84
+ method.object.should_receive(:failure)
85
+ method.failure
86
+ end
87
+
88
+ end
89
+ end