delayed_job_active_record 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2005 Tobias Luetke
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOa AND
17
+ NONINFRINGEMENT. IN NO EVENT SaALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,16 @@
1
+ # DelayedJob ActiveRecord Backend
2
+
3
+ Please note! This is a pre-release version intended for use only with DelayedJob 3.0
4
+
5
+ ## Installation
6
+
7
+ Add the gems to your Gemfile:
8
+
9
+ gem 'delayed_job'
10
+ gem 'delayed_job_active_record'
11
+
12
+ Run `bundle install`
13
+
14
+ If you're using Rails, run the generator to create the migration for the delayed_job table: `rails g delayed_job:active_record`
15
+
16
+ That's it. Use [delayed_job as normal](http://github.com/collectiveidea/delayed_job).
@@ -0,0 +1,98 @@
1
+ require 'active_record'
2
+ require 'active_record/version'
3
+ module Delayed
4
+ module Backend
5
+ module ActiveRecord
6
+ # A job object that is persisted to the database.
7
+ # Contains the work object as a YAML field.
8
+ class Job < ::ActiveRecord::Base
9
+ include Delayed::Backend::Base
10
+ set_table_name :delayed_jobs
11
+
12
+ before_save :set_default_run_at
13
+
14
+ def self.rails3?
15
+ ::ActiveRecord::VERSION::MAJOR == 3
16
+ end
17
+
18
+ if rails3?
19
+ scope :ready_to_run, lambda{|worker_name, max_run_time|
20
+ where('(run_at <= ? AND (locked_at IS NULL OR locked_at < ?) OR locked_by = ?) AND failed_at IS NULL', db_time_now, db_time_now - max_run_time, worker_name)
21
+ }
22
+ scope :by_priority, order('priority ASC, run_at ASC')
23
+ else
24
+ named_scope :ready_to_run, lambda {|worker_name, max_run_time|
25
+ { :conditions => ['(run_at <= ? AND (locked_at IS NULL OR locked_at < ?) OR locked_by = ?) AND failed_at IS NULL', db_time_now, db_time_now - max_run_time, worker_name] }
26
+ }
27
+ named_scope :by_priority, :order => 'priority ASC, run_at ASC'
28
+ end
29
+
30
+ def self.before_fork
31
+ ::ActiveRecord::Base.clear_all_connections!
32
+ end
33
+
34
+ def self.after_fork
35
+ ::ActiveRecord::Base.establish_connection
36
+ end
37
+
38
+ # When a worker is exiting, make sure we don't have any locked jobs.
39
+ def self.clear_locks!(worker_name)
40
+ update_all("locked_by = null, locked_at = null", ["locked_by = ?", worker_name])
41
+ end
42
+
43
+ # Find a few candidate jobs to run (in case some immediately get locked by others).
44
+ def self.find_available(worker_name, limit = 5, max_run_time = Worker.max_run_time)
45
+ scope = self.ready_to_run(worker_name, max_run_time)
46
+ scope = scope.scoped(:conditions => ['priority >= ?', Worker.min_priority]) if Worker.min_priority
47
+ scope = scope.scoped(:conditions => ['priority <= ?', Worker.max_priority]) if Worker.max_priority
48
+ scope = scope.scoped(:conditions => ["queue IN (?)", Worker.queues]) if Worker.queues.any?
49
+
50
+ ::ActiveRecord::Base.silence do
51
+ scope.by_priority.all(:limit => limit)
52
+ end
53
+ end
54
+
55
+ # Lock this job for this worker.
56
+ # Returns true if we have the lock, false otherwise.
57
+ def lock_exclusively!(max_run_time, worker)
58
+ now = self.class.db_time_now
59
+ affected_rows = if locked_by != worker
60
+ # We don't own this job so we will update the locked_by name and the locked_at
61
+ self.class.update_all(["locked_at = ?, locked_by = ?", now, worker], ["id = ? and (locked_at is null or locked_at < ?) and (run_at <= ?)", id, (now - max_run_time.to_i), now])
62
+ else
63
+ # We already own this job, this may happen if the job queue crashes.
64
+ # Simply resume and update the locked_at
65
+ self.class.update_all(["locked_at = ?", now], ["id = ? and locked_by = ?", id, worker])
66
+ end
67
+ if affected_rows == 1
68
+ self.locked_at = now
69
+ self.locked_by = worker
70
+ self.locked_at_will_change!
71
+ self.locked_by_will_change!
72
+ return true
73
+ else
74
+ return false
75
+ end
76
+ end
77
+
78
+ # Get the current time (GMT or local depending on DB)
79
+ # Note: This does not ping the DB to get the time, so all your clients
80
+ # must have syncronized clocks.
81
+ def self.db_time_now
82
+ if Time.zone
83
+ Time.zone.now
84
+ elsif ::ActiveRecord::Base.default_timezone == :utc
85
+ Time.now.utc
86
+ else
87
+ Time.now
88
+ end
89
+ end
90
+
91
+ def reload(*args)
92
+ reset
93
+ super
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,5 @@
1
+ require 'active_record'
2
+ require 'delayed_job'
3
+ require 'delayed/backend/active_record'
4
+
5
+ Delayed::Worker.backend = :active_record
@@ -0,0 +1,17 @@
1
+ require 'generators/delayed_job/delayed_job_generator'
2
+ require 'rails/generators/migration'
3
+ require 'rails/generators/active_record/migration'
4
+
5
+ # Extend the DelayedJobGenerator so that it creates an AR migration
6
+ module DelayedJob
7
+ class ActiveRecordGenerator < ::DelayedJobGenerator
8
+ include Rails::Generators::Migration
9
+ extend ActiveRecord::Generators::Migration
10
+
11
+ self.source_paths << File.join(File.dirname(__FILE__), 'templates')
12
+
13
+ def create_migration_file
14
+ migration_template 'migration.rb', 'db/migrate/create_delayed_jobs.rb'
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,22 @@
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.string :queue # The name of the queue this job is in
13
+ table.timestamps
14
+ end
15
+
16
+ add_index :delayed_jobs, [:priority, :run_at], :name => 'delayed_jobs_priority'
17
+ end
18
+
19
+ def self.down
20
+ drop_table :delayed_jobs
21
+ end
22
+ end
@@ -0,0 +1,8 @@
1
+ sqlite:
2
+ adapter: sqlite3
3
+ database: ":memory:"
4
+
5
+ mysql:
6
+ adapter: mysql
7
+ database: delayed_job
8
+ uesrname: root
@@ -0,0 +1,44 @@
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
+
37
+ describe "enqueue" do
38
+ it "should allow enqueue hook to modify job at DB level" do
39
+ later = described_class.db_time_now + 20.minutes
40
+ job = Delayed::Backend::ActiveRecord::Job.enqueue :payload_object => EnqueueJobMod.new
41
+ Delayed::Backend::ActiveRecord::Job.find(job.id).run_at.should be_within(1).of(later)
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,15 @@
1
+ require 'spec_helper'
2
+
3
+ describe ActiveRecord do
4
+ it 'should load classes with non-default primary key' do
5
+ lambda {
6
+ YAML.load(Story.create.to_yaml)
7
+ }.should_not raise_error
8
+ end
9
+
10
+ it 'should load classes even if not in default scope' do
11
+ lambda {
12
+ YAML.load(Story.create(:scoped => false).to_yaml)
13
+ }.should_not raise_error
14
+ end
15
+ end
@@ -0,0 +1,53 @@
1
+ $:.unshift(File.dirname(__FILE__) + '/../lib')
2
+
3
+ require 'rubygems'
4
+ require 'bundler/setup'
5
+ require 'rspec'
6
+ require 'logger'
7
+
8
+ require 'delayed_job_active_record'
9
+ require 'delayed/backend/shared_spec'
10
+
11
+ Delayed::Worker.logger = Logger.new('/tmp/dj.log')
12
+ ENV['RAILS_ENV'] = 'test'
13
+
14
+ config = YAML.load(File.read('spec/database.yml'))
15
+ # ActiveRecord::Base.configurations = {'test' => config['sqlite']}
16
+ ActiveRecord::Base.establish_connection config['sqlite']
17
+ ActiveRecord::Base.logger = Delayed::Worker.logger
18
+ ActiveRecord::Migration.verbose = false
19
+
20
+ ActiveRecord::Schema.define do
21
+ create_table :delayed_jobs, :force => true do |table|
22
+ table.integer :priority, :default => 0
23
+ table.integer :attempts, :default => 0
24
+ table.text :handler
25
+ table.text :last_error
26
+ table.datetime :run_at
27
+ table.datetime :locked_at
28
+ table.datetime :failed_at
29
+ table.string :locked_by
30
+ table.string :queue
31
+ table.timestamps
32
+ end
33
+
34
+ add_index :delayed_jobs, [:priority, :run_at], :name => 'delayed_jobs_priority'
35
+
36
+ create_table :stories, :primary_key => :story_id, :force => true do |table|
37
+ table.string :text
38
+ table.boolean :scoped, :default => true
39
+ end
40
+ end
41
+
42
+ # Purely useful for test cases...
43
+ class Story < ActiveRecord::Base
44
+ set_primary_key :story_id
45
+ def tell; text; end
46
+ def whatever(n, _); tell*n; end
47
+ default_scope where(:scoped => true)
48
+
49
+ handle_asynchronously :whatever
50
+ end
51
+
52
+ # Add this directory so the ActiveSupport autoloading works
53
+ ActiveSupport::Dependencies.autoload_paths << File.dirname(__FILE__)
metadata ADDED
@@ -0,0 +1,157 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: delayed_job_active_record
3
+ version: !ruby/object:Gem::Version
4
+ hash: 23
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 2
9
+ - 0
10
+ version: 0.2.0
11
+ platform: ruby
12
+ authors:
13
+ - Matt Griffin
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-09-29 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ type: :runtime
22
+ prerelease: false
23
+ name: activerecord
24
+ version_requirements: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">"
28
+ - !ruby/object:Gem::Version
29
+ hash: 11
30
+ segments:
31
+ - 2
32
+ - 1
33
+ - 0
34
+ version: 2.1.0
35
+ requirement: *id001
36
+ - !ruby/object:Gem::Dependency
37
+ type: :runtime
38
+ prerelease: false
39
+ name: delayed_job
40
+ version_requirements: &id002 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - "="
44
+ - !ruby/object:Gem::Version
45
+ hash: 961916020
46
+ segments:
47
+ - 3
48
+ - 0
49
+ - 0
50
+ - pre
51
+ version: 3.0.0.pre
52
+ requirement: *id002
53
+ - !ruby/object:Gem::Dependency
54
+ type: :development
55
+ prerelease: false
56
+ name: rspec
57
+ version_requirements: &id003 !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ~>
61
+ - !ruby/object:Gem::Version
62
+ hash: 3
63
+ segments:
64
+ - 2
65
+ - 0
66
+ version: "2.0"
67
+ requirement: *id003
68
+ - !ruby/object:Gem::Dependency
69
+ type: :development
70
+ prerelease: false
71
+ name: rake
72
+ version_requirements: &id004 !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ hash: 27
78
+ segments:
79
+ - 0
80
+ - 8
81
+ version: "0.8"
82
+ requirement: *id004
83
+ - !ruby/object:Gem::Dependency
84
+ type: :development
85
+ prerelease: false
86
+ name: sqlite3
87
+ version_requirements: &id005 !ruby/object:Gem::Requirement
88
+ none: false
89
+ requirements:
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ hash: 3
93
+ segments:
94
+ - 0
95
+ version: "0"
96
+ requirement: *id005
97
+ description: ActiveRecord backend for DelayedJob, originally authored by Tobias Luetke
98
+ email:
99
+ - matt@griffinonline.org
100
+ executables: []
101
+
102
+ extensions: []
103
+
104
+ extra_rdoc_files:
105
+ - README.md
106
+ files:
107
+ - lib/delayed/backend/active_record.rb
108
+ - lib/delayed_job_active_record.rb
109
+ - lib/generators/delayed_job/active_record_generator.rb
110
+ - lib/generators/delayed_job/templates/migration.rb
111
+ - spec/database.yml
112
+ - spec/delayed/backend/active_record_spec.rb
113
+ - spec/delayed/serialization/active_record_spec.rb
114
+ - spec/spec_helper.rb
115
+ - LICENSE
116
+ - README.md
117
+ homepage: http://github.com/collectiveidea/delayed_job_active_record
118
+ licenses: []
119
+
120
+ post_install_message:
121
+ rdoc_options:
122
+ - --main
123
+ - README.md
124
+ - --inline-source
125
+ - --line-numbers
126
+ require_paths:
127
+ - lib
128
+ required_ruby_version: !ruby/object:Gem::Requirement
129
+ none: false
130
+ requirements:
131
+ - - ">="
132
+ - !ruby/object:Gem::Version
133
+ hash: 3
134
+ segments:
135
+ - 0
136
+ version: "0"
137
+ required_rubygems_version: !ruby/object:Gem::Requirement
138
+ none: false
139
+ requirements:
140
+ - - ">="
141
+ - !ruby/object:Gem::Version
142
+ hash: 3
143
+ segments:
144
+ - 0
145
+ version: "0"
146
+ requirements: []
147
+
148
+ rubyforge_project:
149
+ rubygems_version: 1.8.6
150
+ signing_key:
151
+ specification_version: 3
152
+ summary: ActiveRecord backend for DelayedJob
153
+ test_files:
154
+ - spec/database.yml
155
+ - spec/delayed/backend/active_record_spec.rb
156
+ - spec/delayed/serialization/active_record_spec.rb
157
+ - spec/spec_helper.rb