opsb-delayed_job 2.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. data/.gitignore +2 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.textile +213 -0
  4. data/Rakefile +46 -0
  5. data/VERSION +1 -0
  6. data/benchmarks.rb +33 -0
  7. data/contrib/delayed_job.monitrc +14 -0
  8. data/contrib/delayed_job_multiple.monitrc +23 -0
  9. data/delayed_job.gemspec +115 -0
  10. data/generators/delayed_job/delayed_job_generator.rb +22 -0
  11. data/generators/delayed_job/templates/migration.rb +21 -0
  12. data/generators/delayed_job/templates/script +5 -0
  13. data/init.rb +1 -0
  14. data/lib/delayed/backend/active_record.rb +90 -0
  15. data/lib/delayed/backend/base.rb +111 -0
  16. data/lib/delayed/backend/data_mapper.rb +125 -0
  17. data/lib/delayed/backend/mongo_mapper.rb +110 -0
  18. data/lib/delayed/command.rb +101 -0
  19. data/lib/delayed/message_sending.rb +22 -0
  20. data/lib/delayed/performable_method.rb +62 -0
  21. data/lib/delayed/railtie.rb +10 -0
  22. data/lib/delayed/recipes.rb +31 -0
  23. data/lib/delayed/tasks.rb +15 -0
  24. data/lib/delayed/worker.rb +183 -0
  25. data/lib/delayed_job.rb +14 -0
  26. data/rails/init.rb +5 -0
  27. data/recipes/delayed_job.rb +1 -0
  28. data/spec/backend/active_record_job_spec.rb +46 -0
  29. data/spec/backend/data_mapper_job_spec.rb +16 -0
  30. data/spec/backend/mongo_mapper_job_spec.rb +94 -0
  31. data/spec/backend/shared_backend_spec.rb +265 -0
  32. data/spec/delayed_method_spec.rb +59 -0
  33. data/spec/performable_method_spec.rb +42 -0
  34. data/spec/sample_jobs.rb +25 -0
  35. data/spec/setup/active_record.rb +33 -0
  36. data/spec/setup/data_mapper.rb +8 -0
  37. data/spec/setup/mongo_mapper.rb +17 -0
  38. data/spec/spec_helper.rb +26 -0
  39. data/spec/story_spec.rb +17 -0
  40. data/spec/worker_spec.rb +216 -0
  41. data/tasks/jobs.rake +1 -0
  42. metadata +256 -0
@@ -0,0 +1,5 @@
1
+ require 'delayed_job'
2
+
3
+ config.after_initialize do
4
+ Delayed::Worker.guess_backend
5
+ end
@@ -0,0 +1 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'delayed', 'recipes'))
@@ -0,0 +1,46 @@
1
+ require 'spec_helper'
2
+ require 'backend/shared_backend_spec'
3
+ require 'delayed/backend/active_record'
4
+
5
+ describe Delayed::Backend::ActiveRecord::Job do
6
+ before(:all) do
7
+ @backend = Delayed::Backend::ActiveRecord::Job
8
+ end
9
+
10
+ before(:each) do
11
+ Delayed::Backend::ActiveRecord::Job.delete_all
12
+ SimpleJob.runs = 0
13
+ end
14
+
15
+ after do
16
+ Time.zone = nil
17
+ end
18
+
19
+ it_should_behave_like 'a backend'
20
+
21
+ context "db_time_now" do
22
+ it "should return time in current time zone if set" do
23
+ Time.zone = 'Eastern Time (US & Canada)'
24
+ %w(EST EDT).should include(Delayed::Job.db_time_now.zone)
25
+ end
26
+
27
+ it "should return UTC time if that is the AR default" do
28
+ Time.zone = nil
29
+ ActiveRecord::Base.default_timezone = :utc
30
+ Delayed::Backend::ActiveRecord::Job.db_time_now.zone.should == 'UTC'
31
+ end
32
+
33
+ it "should return local time if that is the AR default" do
34
+ Time.zone = 'Central Time (US & Canada)'
35
+ ActiveRecord::Base.default_timezone = :local
36
+ %w(CST CDT).should include(Delayed::Backend::ActiveRecord::Job.db_time_now.zone)
37
+ end
38
+ end
39
+
40
+ describe "after_fork" do
41
+ it "should call reconnect on the connection" do
42
+ ActiveRecord::Base.connection.should_receive(:reconnect!)
43
+ Delayed::Backend::ActiveRecord::Job.after_fork
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,16 @@
1
+ require 'spec_helper'
2
+ require 'backend/shared_backend_spec'
3
+ require 'delayed/backend/data_mapper'
4
+
5
+ describe Delayed::Backend::DataMapper::Job do
6
+ before(:all) do
7
+ @backend = Delayed::Backend::DataMapper::Job
8
+ end
9
+
10
+ before(:each) do
11
+ # reset database before each example is run
12
+ DataMapper.auto_migrate!
13
+ end
14
+
15
+ it_should_behave_like 'a backend'
16
+ end
@@ -0,0 +1,94 @@
1
+ require 'spec_helper'
2
+ require 'backend/shared_backend_spec'
3
+ require 'delayed/backend/mongo_mapper'
4
+
5
+ describe Delayed::Backend::MongoMapper::Job do
6
+ before(:all) do
7
+ @backend = Delayed::Backend::MongoMapper::Job
8
+ end
9
+
10
+ before(:each) do
11
+ MongoMapper.database.collections.each(&:remove)
12
+ end
13
+
14
+ it_should_behave_like 'a backend'
15
+
16
+ describe "indexes" do
17
+ it "should have combo index on priority and run_at" do
18
+ @backend.collection.index_information.detect { |index| index[0] == 'priority_1_run_at_1' }.should_not be_nil
19
+ end
20
+
21
+ it "should have index on locked_by" do
22
+ @backend.collection.index_information.detect { |index| index[0] == 'locked_by_1' }.should_not be_nil
23
+ end
24
+ end
25
+
26
+ describe "delayed method" do
27
+ class MongoStoryReader
28
+ def read(story)
29
+ "Epilog: #{story.tell}"
30
+ end
31
+ end
32
+
33
+ class MongoStory
34
+ include ::MongoMapper::Document
35
+ key :text, String
36
+
37
+ def tell
38
+ text
39
+ end
40
+ end
41
+
42
+ it "should ignore not found errors because they are permanent" do
43
+ story = MongoStory.create :text => 'Once upon a time...'
44
+ job = story.send_later(:tell)
45
+ story.destroy
46
+ lambda { job.invoke_job }.should_not raise_error
47
+ end
48
+
49
+ it "should store the object as string" do
50
+ story = MongoStory.create :text => 'Once upon a time...'
51
+ job = story.send_later(:tell)
52
+
53
+ job.payload_object.class.should == Delayed::PerformableMethod
54
+ job.payload_object.object.should == "LOAD;MongoStory;#{story.id}"
55
+ job.payload_object.method.should == :tell
56
+ job.payload_object.args.should == []
57
+ job.payload_object.perform.should == 'Once upon a time...'
58
+ end
59
+
60
+ it "should store arguments as string" do
61
+ story = MongoStory.create :text => 'Once upon a time...'
62
+ job = MongoStoryReader.new.send_later(:read, story)
63
+ job.payload_object.class.should == Delayed::PerformableMethod
64
+ job.payload_object.method.should == :read
65
+ job.payload_object.args.should == ["LOAD;MongoStory;#{story.id}"]
66
+ job.payload_object.perform.should == 'Epilog: Once upon a time...'
67
+ end
68
+ end
69
+
70
+ describe "before_fork" do
71
+ after do
72
+ MongoMapper.connection.connect_to_master
73
+ end
74
+
75
+ it "should disconnect" do
76
+ lambda do
77
+ Delayed::Backend::MongoMapper::Job.before_fork
78
+ end.should change { !!MongoMapper.connection.connected? }.from(true).to(false)
79
+ end
80
+ end
81
+
82
+ describe "after_fork" do
83
+ before do
84
+ MongoMapper.connection.close
85
+ end
86
+
87
+ it "should call reconnect" do
88
+ lambda do
89
+ Delayed::Backend::MongoMapper::Job.after_fork
90
+ end.should change { !!MongoMapper.connection.connected? }.from(false).to(true)
91
+ end
92
+ end
93
+
94
+ end
@@ -0,0 +1,265 @@
1
+ shared_examples_for 'a backend' do
2
+ def create_job(opts = {})
3
+ @backend.create(opts.merge(:payload_object => SimpleJob.new))
4
+ end
5
+
6
+ before do
7
+ Delayed::Worker.max_priority = nil
8
+ Delayed::Worker.min_priority = nil
9
+ SimpleJob.runs = 0
10
+ end
11
+
12
+ it "should set run_at automatically if not set" do
13
+ @backend.create(:payload_object => ErrorJob.new ).run_at.should_not be_nil
14
+ end
15
+
16
+ it "should not set run_at automatically if already set" do
17
+ later = @backend.db_time_now + 5.minutes
18
+ @backend.create(:payload_object => ErrorJob.new, :run_at => later).run_at.should be_close(later, 1)
19
+ end
20
+
21
+ it "should raise ArgumentError when handler doesn't respond_to :perform" do
22
+ lambda { @backend.enqueue(Object.new) }.should raise_error(ArgumentError)
23
+ end
24
+
25
+ it "should increase count after enqueuing items" do
26
+ @backend.enqueue SimpleJob.new
27
+ @backend.count.should == 1
28
+ end
29
+
30
+ it "should be able to set priority when enqueuing items" do
31
+ @job = @backend.enqueue SimpleJob.new, 5
32
+ @job.priority.should == 5
33
+ end
34
+
35
+ it "should be able to set run_at when enqueuing items" do
36
+ later = @backend.db_time_now + 5.minutes
37
+ @job = @backend.enqueue SimpleJob.new, 5, later
38
+ @job.run_at.should be_close(later, 1)
39
+ end
40
+
41
+ it "should work with jobs in modules" do
42
+ M::ModuleJob.runs = 0
43
+ job = @backend.enqueue M::ModuleJob.new
44
+ lambda { job.invoke_job }.should change { M::ModuleJob.runs }.from(0).to(1)
45
+ end
46
+
47
+ it "should raise an DeserializationError when the job class is totally unknown" do
48
+ job = @backend.new :handler => "--- !ruby/object:JobThatDoesNotExist {}"
49
+ lambda { job.payload_object.perform }.should raise_error(Delayed::Backend::DeserializationError)
50
+ end
51
+
52
+ it "should try to load the class when it is unknown at the time of the deserialization" do
53
+ job = @backend.new :handler => "--- !ruby/object:JobThatDoesNotExist {}"
54
+ job.should_receive(:attempt_to_load).with('JobThatDoesNotExist').and_return(true)
55
+ lambda { job.payload_object.perform }.should raise_error(Delayed::Backend::DeserializationError)
56
+ end
57
+
58
+ it "should try include the namespace when loading unknown objects" do
59
+ job = @backend.new :handler => "--- !ruby/object:Delayed::JobThatDoesNotExist {}"
60
+ job.should_receive(:attempt_to_load).with('Delayed::JobThatDoesNotExist').and_return(true)
61
+ lambda { job.payload_object.perform }.should raise_error(Delayed::Backend::DeserializationError)
62
+ end
63
+
64
+ it "should also try to load structs when they are unknown (raises TypeError)" do
65
+ job = @backend.new :handler => "--- !ruby/struct:JobThatDoesNotExist {}"
66
+ job.should_receive(:attempt_to_load).with('JobThatDoesNotExist').and_return(true)
67
+ lambda { job.payload_object.perform }.should raise_error(Delayed::Backend::DeserializationError)
68
+ end
69
+
70
+ it "should try include the namespace when loading unknown structs" do
71
+ job = @backend.new :handler => "--- !ruby/struct:Delayed::JobThatDoesNotExist {}"
72
+ job.should_receive(:attempt_to_load).with('Delayed::JobThatDoesNotExist').and_return(true)
73
+ lambda { job.payload_object.perform }.should raise_error(Delayed::Backend::DeserializationError)
74
+ end
75
+
76
+ describe "find_available" do
77
+ it "should not find failed jobs" do
78
+ @job = create_job :attempts => 50, :failed_at => @backend.db_time_now
79
+ @backend.find_available('worker', 5, 1.second).should_not include(@job)
80
+ end
81
+
82
+ it "should not find jobs scheduled for the future" do
83
+ @job = create_job :run_at => (@backend.db_time_now + 1.minute)
84
+ @backend.find_available('worker', 5, 4.hours).should_not include(@job)
85
+ end
86
+
87
+ it "should not find jobs locked by another worker" do
88
+ @job = create_job(:locked_by => 'other_worker', :locked_at => @backend.db_time_now - 1.minute)
89
+ @backend.find_available('worker', 5, 4.hours).should_not include(@job)
90
+ end
91
+
92
+ it "should find open jobs" do
93
+ @job = create_job
94
+ @backend.find_available('worker', 5, 4.hours).should include(@job)
95
+ end
96
+
97
+ it "should find expired jobs" do
98
+ @job = create_job(:locked_by => 'worker', :locked_at => @backend.db_time_now - 2.minutes)
99
+ @backend.find_available('worker', 5, 1.minute).should include(@job)
100
+ end
101
+
102
+ it "should find own jobs" do
103
+ @job = create_job(:locked_by => 'worker', :locked_at => (@backend.db_time_now - 1.minutes))
104
+ @backend.find_available('worker', 5, 4.hours).should include(@job)
105
+ end
106
+
107
+ it "should find only the right amount of jobs" do
108
+ 10.times { create_job }
109
+ @backend.find_available('worker', 7, 4.hours).should have(7).jobs
110
+ end
111
+ end
112
+
113
+ context "when another worker is already performing an task, it" do
114
+
115
+ before :each do
116
+ @job = @backend.create :payload_object => SimpleJob.new, :locked_by => 'worker1', :locked_at => @backend.db_time_now - 5.minutes
117
+ end
118
+
119
+ it "should not allow a second worker to get exclusive access" do
120
+ @job.lock_exclusively!(4.hours, 'worker2').should == false
121
+ end
122
+
123
+ it "should allow a second worker to get exclusive access if the timeout has passed" do
124
+ @job.lock_exclusively!(1.minute, 'worker2').should == true
125
+ end
126
+
127
+ it "should be able to get access to the task if it was started more then max_age ago" do
128
+ @job.locked_at = 5.hours.ago
129
+ @job.save
130
+
131
+ @job.lock_exclusively! 4.hours, 'worker2'
132
+ @job.reload
133
+ @job.locked_by.should == 'worker2'
134
+ @job.locked_at.should > 1.minute.ago
135
+ end
136
+
137
+ it "should not be found by another worker" do
138
+ @backend.find_available('worker2', 1, 6.minutes).length.should == 0
139
+ end
140
+
141
+ it "should be found by another worker if the time has expired" do
142
+ @backend.find_available('worker2', 1, 4.minutes).length.should == 1
143
+ end
144
+
145
+ it "should be able to get exclusive access again when the worker name is the same" do
146
+ @job.lock_exclusively!(5.minutes, 'worker1').should be_true
147
+ @job.lock_exclusively!(5.minutes, 'worker1').should be_true
148
+ @job.lock_exclusively!(5.minutes, 'worker1').should be_true
149
+ end
150
+ end
151
+
152
+ context "when another worker has worked on a task since the job was found to be available, it" do
153
+
154
+ before :each do
155
+ @job = @backend.create :payload_object => SimpleJob.new
156
+ @job_copy_for_worker_2 = @backend.find(@job.id)
157
+ end
158
+
159
+ it "should not allow a second worker to get exclusive access if already successfully processed by worker1" do
160
+ @job.destroy
161
+ @job_copy_for_worker_2.lock_exclusively!(4.hours, 'worker2').should == false
162
+ end
163
+
164
+ 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
165
+ @job.update_attributes(:attempts => 1, :run_at => 1.day.from_now)
166
+ @job_copy_for_worker_2.lock_exclusively!(4.hours, 'worker2').should == false
167
+ end
168
+ end
169
+
170
+ context "#name" do
171
+ it "should be the class name of the job that was enqueued" do
172
+ @backend.create(:payload_object => ErrorJob.new ).name.should == 'ErrorJob'
173
+ end
174
+
175
+ it "should be the method that will be called if its a performable method object" do
176
+ @job = Story.send_later(:create)
177
+ @job.name.should == "Story.create"
178
+ end
179
+
180
+ it "should be the instance method that will be called if its a performable method object" do
181
+ @job = Story.create(:text => "...").send_later(:save)
182
+ @job.name.should == 'Story#save'
183
+ end
184
+ end
185
+
186
+ context "worker prioritization" do
187
+ before(:each) do
188
+ Delayed::Worker.max_priority = nil
189
+ Delayed::Worker.min_priority = nil
190
+ end
191
+
192
+ it "should fetch jobs ordered by priority" do
193
+ 10.times { @backend.enqueue SimpleJob.new, rand(10) }
194
+ jobs = @backend.find_available('worker', 10)
195
+ jobs.size.should == 10
196
+ jobs.each_cons(2) do |a, b|
197
+ a.priority.should <= b.priority
198
+ end
199
+ end
200
+
201
+ it "should only find jobs greater than or equal to min priority" do
202
+ min = 5
203
+ Delayed::Worker.min_priority = min
204
+ 10.times {|i| @backend.enqueue SimpleJob.new, i }
205
+ jobs = @backend.find_available('worker', 10)
206
+ jobs.each {|job| job.priority.should >= min}
207
+ end
208
+
209
+ it "should only find jobs less than or equal to max priority" do
210
+ max = 5
211
+ Delayed::Worker.max_priority = max
212
+ 10.times {|i| @backend.enqueue SimpleJob.new, i }
213
+ jobs = @backend.find_available('worker', 10)
214
+ jobs.each {|job| job.priority.should <= max}
215
+ end
216
+ end
217
+
218
+ context "clear_locks!" do
219
+ before do
220
+ @job = create_job(:locked_by => 'worker', :locked_at => @backend.db_time_now)
221
+ end
222
+
223
+ it "should clear locks for the given worker" do
224
+ @backend.clear_locks!('worker')
225
+ @backend.find_available('worker2', 5, 1.minute).should include(@job)
226
+ end
227
+
228
+ it "should not clear locks for other workers" do
229
+ @backend.clear_locks!('worker1')
230
+ @backend.find_available('worker1', 5, 1.minute).should_not include(@job)
231
+ end
232
+ end
233
+
234
+ context "unlock" do
235
+ before do
236
+ @job = create_job(:locked_by => 'worker', :locked_at => @backend.db_time_now)
237
+ end
238
+
239
+ it "should clear locks" do
240
+ @job.unlock
241
+ @job.locked_by.should be_nil
242
+ @job.locked_at.should be_nil
243
+ end
244
+ end
245
+
246
+ context "large handler" do
247
+ @@text = %{Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus eu vehicula augue. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Quisque odio lectus, volutpat sed dictum rutrum, interdum aliquam neque. Vivamus quis velit nisi, quis dictum purus. Duis magna nisi, faucibus nec molestie vitae, dictum eget odio. Nunc nulla mauris, vestibulum at dapibus nec, dapibus et lectus. Nullam sapien lacus, consectetur eget mattis in, rhoncus sed ipsum. Nullam nec nibh nisl. Integer ut erat in arcu feugiat semper. Nulla gravida sapien quam. Vestibulum pharetra elementum posuere. Fusce mattis justo auctor nibh facilisis vitae consectetur nibh vehicula.
248
+
249
+ Ut at pharetra justo. Donec dictum ornare tortor in feugiat. Sed ac purus sem. Aenean dignissim, erat vel bibendum mollis, elit neque mollis mauris, vitae pretium diam enim non leo. Aliquam aliquet, odio id iaculis varius, metus nibh fermentum sapien, a euismod turpis lectus sit amet turpis. Morbi sapien est, scelerisque in placerat in, varius nec mauris. Aliquam erat volutpat. Quisque suscipit tincidunt libero, sed tincidunt libero iaculis et. Vivamus sed faucibus elit. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus dignissim sem sed tortor semper et lacinia leo viverra. Nulla nec quam at arcu ullamcorper imperdiet vitae in ligula. Quisque placerat vulputate orci sit amet tempor. Duis sed quam nulla. Cras quis mi nibh, at euismod velit. Etiam nec nunc libero, sed condimentum diam.
250
+
251
+ Duis nec mauris in est suscipit viverra a in nibh. Suspendisse nec nulla tortor. Etiam et nulla tellus. Nam feugiat adipiscing commodo. Curabitur scelerisque varius lacus non hendrerit. Vivamus nec enim non turpis auctor tempus sit amet in nisi. Sed ligula nulla, condimentum sed tempor vel, imperdiet id mauris. Quisque mollis ante eu magna tempus porttitor. Integer est libero, consectetur sed tristique a, scelerisque id risus. Donec lacinia justo eget diam fringilla vitae egestas dolor feugiat. Vivamus massa ante, mattis et hendrerit nec, dictum vitae nulla. Pellentesque at nisl et odio suscipit ullamcorper cursus quis enim. Ut nec tellus molestie erat dignissim mollis. Curabitur quis ipsum sapien, sed tincidunt massa. Vestibulum volutpat pretium fringilla.
252
+
253
+ Integer at lorem sit amet nibh suscipit euismod et ut ante. Maecenas feugiat hendrerit dolor, eget egestas velit consequat eget. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Suspendisse ut nunc odio. Vivamus semper, sem vitae sollicitudin auctor, leo mi vulputate augue, eget venenatis libero nunc ut dolor. Phasellus vulputate, metus et dapibus tempus, tellus arcu ullamcorper leo, porttitor dictum lectus turpis blandit sapien. Pellentesque et accumsan justo. Maecenas elit nisi, tincidunt eget consequat a, laoreet et magna. Pellentesque venenatis felis ut massa ultrices bibendum. Duis vulputate tempor leo at bibendum. Curabitur aliquet, turpis sit amet porta porttitor, nibh mi vehicula dolor, suscipit aliquet mi augue quis magna. Praesent tellus turpis, malesuada at ultricies id, feugiat a urna. Curabitur sed mi magna.
254
+
255
+ Quisque adipiscing dignissim mollis. Aenean blandit, diam porttitor bibendum bibendum, leo neque tempus risus, in rutrum dolor elit a lorem. Aenean sollicitudin scelerisque ullamcorper. Nunc tristique ultricies nunc et imperdiet. Duis vitae egestas mauris. Suspendisse odio nisi, accumsan vel volutpat nec, aliquam vitae odio. Praesent elementum fermentum suscipit. Quisque quis tellus eu tellus bibendum luctus a quis nunc. Praesent dictum velit sed lacus dapibus ut ultricies mauris facilisis. Vivamus bibendum, ipsum sit amet facilisis consequat, leo lectus aliquam augue, eu consectetur magna nunc gravida sapien. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Duis tempor nisl ac odio molestie ut tincidunt purus varius. Nunc quis lorem nibh, vestibulum cursus lorem. Nunc sit amet est ut magna suscipit tempor vitae a augue.}
256
+
257
+ before do
258
+ @job = @backend.enqueue Delayed::PerformableMethod.new(@@text, :length, {})
259
+ end
260
+
261
+ it "should have an id" do
262
+ @job.id.should_not be_nil
263
+ end
264
+ end
265
+ end
@@ -0,0 +1,59 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'random ruby objects' do
4
+ before { Delayed::Job.delete_all }
5
+
6
+ it "should respond_to :send_later method" do
7
+ Object.new.respond_to?(:send_later)
8
+ end
9
+
10
+ it "should raise a ArgumentError if send_later is called but the target method doesn't exist" do
11
+ lambda { Object.new.send_later(:method_that_deos_not_exist) }.should raise_error(NoMethodError)
12
+ end
13
+
14
+ it "should add a new entry to the job table when send_later is called on it" do
15
+ lambda { Object.new.send_later(:to_s) }.should change { Delayed::Job.count }.by(1)
16
+ end
17
+
18
+ it "should add a new entry to the job table when send_later is called on the class" do
19
+ lambda { Object.send_later(:to_s) }.should change { Delayed::Job.count }.by(1)
20
+ end
21
+
22
+ it "should call send later on methods which are wrapped with handle_asynchronously" do
23
+ story = Story.create :text => 'Once upon...'
24
+
25
+ Delayed::Job.count.should == 0
26
+
27
+ story.whatever(1, 5)
28
+
29
+ Delayed::Job.count.should == 1
30
+ job = Delayed::Job.first
31
+ job.payload_object.class.should == Delayed::PerformableMethod
32
+ job.payload_object.method.should == :whatever_without_send_later
33
+ job.payload_object.args.should == [1, 5]
34
+ job.payload_object.perform.should == 'Once upon...'
35
+ end
36
+
37
+ context "send_at" do
38
+ it "should queue a new job" do
39
+ lambda do
40
+ "string".send_at(1.hour.from_now, :length)
41
+ end.should change { Delayed::Job.count }.by(1)
42
+ end
43
+
44
+ it "should schedule the job in the future" do
45
+ time = 1.hour.from_now.utc.to_time
46
+ job = "string".send_at(time, :length)
47
+ job.run_at.to_i.should == time.to_i
48
+ end
49
+
50
+ it "should store payload as PerformableMethod" do
51
+ job = "string".send_at(1.hour.from_now, :count, 'r')
52
+ job.payload_object.class.should == Delayed::PerformableMethod
53
+ job.payload_object.method.should == :count
54
+ job.payload_object.args.should == ['r']
55
+ job.payload_object.perform.should == 1
56
+ end
57
+ end
58
+
59
+ end