delayed_job_hooked 2.1.5

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.
@@ -0,0 +1,41 @@
1
+ # These extensions allow properly serializing and autoloading of
2
+ # Classes, Modules and Structs
3
+
4
+ require 'yaml'
5
+ YAML::ENGINE.yamler = "syck" if defined?(YAML::ENGINE)
6
+
7
+ class Module
8
+ yaml_as "tag:ruby.yaml.org,2002:module"
9
+
10
+ def self.yaml_new(klass, tag, val)
11
+ val.constantize
12
+ end
13
+
14
+ def to_yaml( opts = {} )
15
+ YAML::quick_emit( nil, opts ) { |out|
16
+ out.scalar(taguri, self.name, :plain)
17
+ }
18
+ end
19
+
20
+ def yaml_tag_read_class(name)
21
+ # Constantize the object so that ActiveSupport can attempt
22
+ # its auto loading magic. Will raise LoadError if not successful.
23
+ name.constantize
24
+ name
25
+ end
26
+
27
+ end
28
+
29
+ class Class
30
+ yaml_as "tag:ruby.yaml.org,2002:class"
31
+ remove_method :to_yaml if respond_to?(:to_yaml) && method(:to_yaml).owner == Class # use Module's to_yaml
32
+ end
33
+
34
+ class Struct
35
+ def self.yaml_tag_read_class(name)
36
+ # Constantize the object so that ActiveSupport can attempt
37
+ # its auto loading magic. Will raise LoadError if not successful.
38
+ name.constantize
39
+ "Struct::#{ name }"
40
+ end
41
+ end
@@ -0,0 +1,13 @@
1
+ require 'active_support'
2
+
3
+ require File.dirname(__FILE__) + '/delayed/message_sending'
4
+ require File.dirname(__FILE__) + '/delayed/performable_method'
5
+ require File.dirname(__FILE__) + '/delayed/performable_mailer' if defined?(ActionMailer)
6
+ require File.dirname(__FILE__) + '/delayed/yaml_ext'
7
+ require File.dirname(__FILE__) + '/delayed/backend/base'
8
+ require File.dirname(__FILE__) + '/delayed/worker'
9
+ require File.dirname(__FILE__) + '/delayed/deserialization_error'
10
+ require File.dirname(__FILE__) + '/delayed/railtie' if defined?(Rails::Railtie)
11
+
12
+ Object.send(:include, Delayed::MessageSending)
13
+ Module.send(:include, Delayed::MessageSending::ClassMethods)
@@ -0,0 +1,34 @@
1
+ require 'rails/generators'
2
+ require 'rails/generators/migration'
3
+
4
+ class DelayedJobGenerator < Rails::Generators::Base
5
+
6
+ include Rails::Generators::Migration
7
+
8
+ def self.source_root
9
+ @source_root ||= File.join(File.dirname(__FILE__), 'templates')
10
+ end
11
+
12
+ # Implement the required interface for Rails::Generators::Migration.
13
+ #
14
+ def self.next_migration_number(dirname) #:nodoc:
15
+ next_migration_number = current_migration_number(dirname) + 1
16
+ if ActiveRecord::Base.timestamped_migrations
17
+ [Time.now.utc.strftime("%Y%m%d%H%M%S"), "%.14d" % next_migration_number].max
18
+ else
19
+ "%.3d" % next_migration_number
20
+ end
21
+ end
22
+
23
+ def create_script_file
24
+ template 'script', 'script/delayed_job'
25
+ chmod 'script/delayed_job', 0755
26
+ end
27
+
28
+ def create_migration_file
29
+ if defined?(ActiveRecord)
30
+ migration_template 'migration.rb', 'db/migrate/create_delayed_jobs.rb'
31
+ end
32
+ end
33
+
34
+ end
@@ -0,0 +1,21 @@
1
+ class CreateDelayedJobs < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :delayed_jobs, :force => true do |table|
4
+ table.integer :priority, :default => 0 # Allows some jobs to jump to the front of the queue
5
+ table.integer :attempts, :default => 0 # Provides for retries, but still fail eventually.
6
+ table.text :handler # YAML-encoded string of the object that will do work
7
+ table.text :last_error # reason for last failure (See Note below)
8
+ table.datetime :run_at # When to run. Could be Time.zone.now for immediately, or sometime in the future.
9
+ table.datetime :locked_at # Set when a client is working on this object
10
+ table.datetime :failed_at # Set when all retries have failed (actually, by default, the record is deleted instead)
11
+ table.string :locked_by # Who is working on this object (if locked)
12
+ table.timestamps
13
+ end
14
+
15
+ add_index :delayed_jobs, [:priority, :run_at], :name => 'delayed_jobs_priority'
16
+ end
17
+
18
+ def self.down
19
+ drop_table :delayed_jobs
20
+ end
21
+ end
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'config', 'environment'))
4
+ require 'delayed/command'
5
+ Delayed::Command.new(ARGV).daemonize
@@ -0,0 +1 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'delayed', 'recipes'))
@@ -0,0 +1,36 @@
1
+ require 'spec_helper'
2
+ require 'delayed/backend/active_record'
3
+
4
+ describe Delayed::Backend::ActiveRecord::Job do
5
+ after do
6
+ Time.zone = nil
7
+ end
8
+
9
+ it_should_behave_like 'a delayed_job backend'
10
+
11
+ context "db_time_now" do
12
+ it "should return time in current time zone if set" do
13
+ Time.zone = 'Eastern Time (US & Canada)'
14
+ %w(EST EDT).should include(Delayed::Job.db_time_now.zone)
15
+ end
16
+
17
+ it "should return UTC time if that is the AR default" do
18
+ Time.zone = nil
19
+ ActiveRecord::Base.default_timezone = :utc
20
+ Delayed::Backend::ActiveRecord::Job.db_time_now.zone.should == 'UTC'
21
+ end
22
+
23
+ it "should return local time if that is the AR default" do
24
+ Time.zone = 'Central Time (US & Canada)'
25
+ ActiveRecord::Base.default_timezone = :local
26
+ %w(CST CDT).should include(Delayed::Backend::ActiveRecord::Job.db_time_now.zone)
27
+ end
28
+ end
29
+
30
+ describe "after_fork" do
31
+ it "should call reconnect on the connection" do
32
+ ActiveRecord::Base.should_receive(:establish_connection)
33
+ Delayed::Backend::ActiveRecord::Job.after_fork
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,7 @@
1
+ # Make sure this file does not get required manually
2
+ module Autoloaded
3
+ class Clazz
4
+ def perform
5
+ end
6
+ end
7
+ 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,4 @@
1
+ mysql:
2
+ adapter: mysql
3
+ database: delayed_job
4
+ username: root
@@ -0,0 +1,116 @@
1
+ require 'spec_helper'
2
+
3
+ describe Delayed::MessageSending do
4
+ describe "handle_asynchronously" do
5
+ class Story < ActiveRecord::Base
6
+ def tell!(arg)
7
+ end
8
+ handle_asynchronously :tell!
9
+ end
10
+
11
+ it "should alias original method" do
12
+ Story.new.should respond_to(:tell_without_delay!)
13
+ Story.new.should respond_to(:tell_with_delay!)
14
+ end
15
+
16
+ it "should create a PerformableMethod" do
17
+ story = Story.create!
18
+ lambda {
19
+ job = story.tell!(1)
20
+ job.payload_object.class.should == Delayed::PerformableMethod
21
+ job.payload_object.method_name.should == :tell_without_delay!
22
+ job.payload_object.args.should == [1]
23
+ }.should change { Delayed::Job.count }
24
+ end
25
+
26
+ describe 'with options' do
27
+ class Fable
28
+ class << self
29
+ attr_accessor :importance
30
+ end
31
+ def tell
32
+ end
33
+ handle_asynchronously :tell, :priority => Proc.new { self.importance }
34
+ end
35
+
36
+ it 'should set the priority based on the Fable importance' do
37
+ Fable.importance = 10
38
+ job = Fable.new.tell
39
+ job.priority.should == 10
40
+
41
+ Fable.importance = 20
42
+ job = Fable.new.tell
43
+ job.priority.should == 20
44
+ end
45
+
46
+ describe 'using a proc with parament' do
47
+ class Yarn
48
+ attr_accessor :importance
49
+ def spin
50
+ end
51
+ handle_asynchronously :spin, :priority => Proc.new {|y| y.importance }
52
+ end
53
+
54
+ it 'should set the priority based on the Fable importance' do
55
+ job = Yarn.new.tap {|y| y.importance = 10 }.spin
56
+ job.priority.should == 10
57
+
58
+ job = Yarn.new.tap {|y| y.importance = 20 }.spin
59
+ job.priority.should == 20
60
+ end
61
+ end
62
+ end
63
+ end
64
+
65
+ context "delay" do
66
+ it "should create a new PerformableMethod job" do
67
+ lambda {
68
+ job = "hello".delay.count('l')
69
+ job.payload_object.class.should == Delayed::PerformableMethod
70
+ job.payload_object.method_name.should == :count
71
+ job.payload_object.args.should == ['l']
72
+ }.should change { Delayed::Job.count }.by(1)
73
+ end
74
+
75
+ it "should set default priority" do
76
+ Delayed::Worker.default_priority = 99
77
+ job = Object.delay.to_s
78
+ job.priority.should == 99
79
+ Delayed::Worker.default_priority = 0
80
+ end
81
+
82
+ it "should set job options" do
83
+ run_at = Time.parse('2010-05-03 12:55 AM')
84
+ job = Object.delay(:priority => 20, :run_at => run_at).to_s
85
+ job.run_at.should == run_at
86
+ job.priority.should == 20
87
+ end
88
+
89
+ class FairyTail
90
+ attr_accessor :happy_ending
91
+ def tell
92
+ @happy_ending = true
93
+ end
94
+ end
95
+
96
+ it "should not delay the job when delay_jobs is false" do
97
+ Delayed::Worker.delay_jobs = false
98
+ fairy_tail = FairyTail.new
99
+ lambda {
100
+ lambda {
101
+ fairy_tail.delay.tell
102
+ }.should change(fairy_tail, :happy_ending).from(nil).to(true)
103
+ }.should_not change { Delayed::Job.count }
104
+ end
105
+
106
+ it "should delay the job when delay_jobs is true" do
107
+ Delayed::Worker.delay_jobs = true
108
+ fairy_tail = FairyTail.new
109
+ lambda {
110
+ lambda {
111
+ fairy_tail.delay.tell
112
+ }.should_not change(fairy_tail, :happy_ending)
113
+ }.should change { Delayed::Job.count }.by(1)
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,46 @@
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"
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
+ before 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
+ end
37
+
38
+ it "should call the method and #deliver on the mailer" do
39
+ @mailer_class.should_receive(:signup).with('john@example.com')
40
+ @email.should_receive(:deliver)
41
+ @mailer.perform
42
+ end
43
+ end
44
+ end
45
+
46
+ end
@@ -0,0 +1,64 @@
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.new
46
+ story.should_receive(hook).with(an_instance_of(Delayed::Job))
47
+ story.delay.tell.invoke_job
48
+ end
49
+ end
50
+
51
+ it "should delegate error hook to object" do
52
+ story = Story.new
53
+ story.should_receive(:error).with(an_instance_of(Delayed::Job), an_instance_of(RuntimeError))
54
+ story.should_receive(:tell).and_raise(RuntimeError)
55
+ lambda { story.delay.tell.invoke_job }.should raise_error
56
+ end
57
+
58
+ it "should delegate failure hook to object" do
59
+ method = Delayed::PerformableMethod.new("object", :size, [])
60
+ method.object.should_receive(:failure)
61
+ method.failure
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,73 @@
1
+ class NamedJob < Struct.new(:perform)
2
+ def display_name
3
+ 'named_job'
4
+ end
5
+ end
6
+
7
+ class SimpleJob
8
+ cattr_accessor :runs; self.runs = 0
9
+ def perform; @@runs += 1; end
10
+ end
11
+
12
+ class ErrorJob
13
+ cattr_accessor :runs; self.runs = 0
14
+ def perform; raise 'did not work'; end
15
+ end
16
+
17
+ class CustomRescheduleJob < Struct.new(:offset)
18
+ cattr_accessor :runs; self.runs = 0
19
+ def perform; raise 'did not work'; end
20
+ def reschedule_at(time, attempts); time + offset; end
21
+ end
22
+
23
+ class LongRunningJob
24
+ def perform; sleep 250; end
25
+ end
26
+
27
+ class OnPermanentFailureJob < SimpleJob
28
+ def failure; end
29
+ def max_attempts; 1; end
30
+ end
31
+
32
+ module M
33
+ class ModuleJob
34
+ cattr_accessor :runs; self.runs = 0
35
+ def perform; @@runs += 1; end
36
+ end
37
+ end
38
+
39
+ class CallbackJob
40
+ cattr_accessor :messages
41
+
42
+ def enqueue(job)
43
+ self.class.messages << 'enqueue'
44
+ end
45
+
46
+ def before(job)
47
+ self.class.messages << 'before'
48
+ end
49
+
50
+ def perform
51
+ self.class.messages << 'perform'
52
+ end
53
+
54
+ def after(job)
55
+ self.class.messages << 'after'
56
+ end
57
+
58
+ def success(job)
59
+ self.class.messages << 'success'
60
+ end
61
+
62
+ def error(job, error)
63
+ self.class.messages << "error: #{error.class}"
64
+ end
65
+
66
+ def failure(job)
67
+ self.class.messages << 'failure'
68
+ end
69
+
70
+ def completed(job)
71
+ self.class.messages << 'completed'
72
+ end
73
+ end