delayed_cron_job 0.6.0 → 0.7.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.gitignore +2 -1
- data/.rspec +0 -1
- data/.travis.yml +10 -0
- data/{LICENSE.txt → LICENSE} +1 -1
- data/README.md +122 -3
- data/delayed_cron_job.gemspec +2 -0
- data/lib/delayed_cron_job.rb +34 -2
- data/lib/delayed_cron_job/active_job/enqueuing.rb +16 -0
- data/lib/delayed_cron_job/active_job/queue_adapter.rb +33 -0
- data/lib/delayed_cron_job/backend/active_record/railtie.rb +14 -0
- data/lib/delayed_cron_job/backend/updatable_cron.rb +29 -0
- data/lib/delayed_cron_job/cronline.rb +1 -1
- data/lib/delayed_cron_job/plugin.rb +14 -22
- data/lib/delayed_cron_job/version.rb +1 -1
- data/lib/generators/delayed_job/cron_generator.rb +13 -2
- data/lib/generators/delayed_job/templates/cron_migration.rb +2 -2
- data/spec/active_job_spec.rb +69 -0
- data/spec/delayed_cron_job_spec.rb +79 -13
- data/spec/spec_helper.rb +5 -2
- metadata +28 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 853453d17f852e33afef72373ceec5a6fe462679272e5aa8a2cee37bccf26f3b
|
4
|
+
data.tar.gz: e88869f8b206ad9acd3214eb63ee4366e61c8113fc29084e8d8edaf1fd85db22
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b7bdef74835bd41fe795bc61889361bea65521b7c1771c6e2c29ef693fe15afeeafe74fecb0152a2be7aa40483578866ceec03711e24b71eafa7c66e454c05ad
|
7
|
+
data.tar.gz: 991d3d78e52d7932e96d99dacb0bd869bf5ac314fb0a2e89fa5b9d2c0063a63f595b22fad950bf72397003d3621bfabbe79e4e2863b41fee9bac410cb1043286
|
data/.gitignore
CHANGED
data/.rspec
CHANGED
data/.travis.yml
ADDED
data/{LICENSE.txt → LICENSE}
RENAMED
data/README.md
CHANGED
@@ -1,11 +1,13 @@
|
|
1
1
|
# Delayed::Cron::Job
|
2
2
|
|
3
|
+
[![Build Status](https://travis-ci.org/codez/delayed_cron_job.svg)](https://travis-ci.org/codez/delayed_cron_job)
|
4
|
+
|
3
5
|
Delayed::Cron::Job is an extension to Delayed::Job that allows you to set
|
4
6
|
cron expressions for your jobs to run repeatedly.
|
5
7
|
|
6
8
|
## Installation
|
7
9
|
|
8
|
-
Add
|
10
|
+
Add the following line to your application's Gemfile. Add it after the lines for all other `delayed_job*` gems so the gem can properly integrate with the Delayed::Job code.
|
9
11
|
|
10
12
|
gem 'delayed_cron_job'
|
11
13
|
|
@@ -26,13 +28,124 @@ There are no additional steps for `delayed_job_mongoid`.
|
|
26
28
|
|
27
29
|
When enqueuing a job, simply pass the `cron` option, e.g.:
|
28
30
|
|
29
|
-
|
31
|
+
```ruby
|
32
|
+
Delayed::Job.enqueue(MyRepeatedJob.new, cron: '15 */6 * * 1-5')
|
33
|
+
```
|
34
|
+
|
35
|
+
Or, when using ActiveJob:
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
MyJob.set(cron: '*/5 * * * *').perform_later
|
39
|
+
```
|
30
40
|
|
31
41
|
Any crontab compatible cron expressions are supported (see `man 5 crontab`).
|
32
42
|
The credits for the `Cronline` class used go to
|
33
43
|
[rufus-scheduler](https://github.com/jmettraux/rufus-scheduler).
|
34
44
|
|
35
|
-
##
|
45
|
+
## Scheduling
|
46
|
+
|
47
|
+
Usually, you want to schedule all existing cron jobs when deploying the
|
48
|
+
application. Using a common super class makes this simple.
|
49
|
+
|
50
|
+
### Custom CronJob superclass
|
51
|
+
|
52
|
+
`app/jobs/cron_job.rb`:
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
# Default configuration in `app/jobs/application_job.rb`, or subclass
|
56
|
+
# ActiveJob::Base .
|
57
|
+
class CronJob < ApplicationJob
|
58
|
+
|
59
|
+
class_attribute :cron_expression
|
60
|
+
|
61
|
+
class << self
|
62
|
+
|
63
|
+
def schedule
|
64
|
+
set(cron: cron_expression).perform_later unless scheduled?
|
65
|
+
end
|
66
|
+
|
67
|
+
def remove
|
68
|
+
delayed_job.destroy if scheduled?
|
69
|
+
end
|
70
|
+
|
71
|
+
def scheduled?
|
72
|
+
delayed_job.present?
|
73
|
+
end
|
74
|
+
|
75
|
+
def delayed_job
|
76
|
+
Delayed::Job
|
77
|
+
.where('handler LIKE ?', "%job_class: #{name}%")
|
78
|
+
.first
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
end
|
83
|
+
```
|
84
|
+
|
85
|
+
### Example Job inheriting from CronJob
|
86
|
+
|
87
|
+
Then, an example job that triggers E-Mail-sending with default cron time at
|
88
|
+
noon every day:
|
89
|
+
|
90
|
+
`app/jobs/noon_job.rb`:
|
91
|
+
|
92
|
+
```ruby
|
93
|
+
|
94
|
+
# Note that it inherits from `CronJob`
|
95
|
+
class NoonJob < CronJob
|
96
|
+
# set the (default) cron expression
|
97
|
+
self.cron_expression = '0 12 * * *'
|
98
|
+
|
99
|
+
# will enqueue the mailing delivery job
|
100
|
+
def perform
|
101
|
+
UserMailer.daily_notice(User.first).deliver_later
|
102
|
+
end
|
103
|
+
end
|
104
|
+
```
|
105
|
+
|
106
|
+
### Scheduling "trigger"
|
107
|
+
|
108
|
+
Jobs with a `cron` definition are rescheduled automatically only when a job
|
109
|
+
instance finished its work. So there needs to be an initial scheduling of all
|
110
|
+
cron jobs. If you do not want to do this manually (e.g. using `rails console`)
|
111
|
+
or with your application logic, you can e.g. hook into the `rails db:*` rake
|
112
|
+
tasks:
|
113
|
+
|
114
|
+
`lib/tasks/jobs.rake`:
|
115
|
+
|
116
|
+
```ruby
|
117
|
+
namespace :db do
|
118
|
+
desc 'Schedule all cron jobs'
|
119
|
+
task :schedule_jobs => :environment do
|
120
|
+
# Need to load all jobs definitions in order to find subclasses
|
121
|
+
glob = Rails.root.join('app', 'jobs', '**', '*_job.rb')
|
122
|
+
Dir.glob(glob).each { |file| require file }
|
123
|
+
CronJob.subclasses.each { |job| job.schedule }
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
# invoke schedule_jobs automatically after every migration and schema load.
|
128
|
+
%w(db:migrate db:schema:load).each do |task|
|
129
|
+
Rake::Task[task].enhance do
|
130
|
+
Rake::Task['db:schedule_jobs'].invoke
|
131
|
+
end
|
132
|
+
end
|
133
|
+
```
|
134
|
+
|
135
|
+
Now, if you run `rails db:migrate`, `rails db:schema:load` or `rails
|
136
|
+
db:schedule_jobs` all jobs inheriting from `CronJob` are scheduled.
|
137
|
+
|
138
|
+
*If you are not using ActiveJob, the same approach may be used with minor
|
139
|
+
adjustments.*
|
140
|
+
|
141
|
+
### Changing the schedule
|
142
|
+
|
143
|
+
Note that if you have a CronJob scheduled and change its `cron_expression` in
|
144
|
+
its source file, you will have to remove any scheduled instances of the Job and
|
145
|
+
reschedule it (e.g. with the snippet above: `rails db:migrate`). This is because
|
146
|
+
the `cron_expression` is already persisted in the database (as `cron`).
|
147
|
+
|
148
|
+
## Details
|
36
149
|
|
37
150
|
The initial `run_at` value is computed during the `#enqueue` method call.
|
38
151
|
If you create `Delayed::Job` database entries directly, make sure to set
|
@@ -61,3 +174,9 @@ jobs.
|
|
61
174
|
3. Commit your changes (`git commit -am 'Add some feature'`)
|
62
175
|
4. Push to the branch (`git push origin my-new-feature`)
|
63
176
|
5. Create a new Pull Request
|
177
|
+
|
178
|
+
## License
|
179
|
+
|
180
|
+
Delayed::Cron::Job is released under the terms of the MIT License.
|
181
|
+
Copyright 2014-2020 Pascal Zumkehr. See [LICENSE](LICENSE) for further
|
182
|
+
information.
|
data/delayed_cron_job.gemspec
CHANGED
data/lib/delayed_cron_job.rb
CHANGED
@@ -3,6 +3,15 @@ require 'English'
|
|
3
3
|
require 'delayed_cron_job/cronline'
|
4
4
|
require 'delayed_cron_job/plugin'
|
5
5
|
require 'delayed_cron_job/version'
|
6
|
+
require 'delayed_cron_job/backend/updatable_cron'
|
7
|
+
|
8
|
+
begin
|
9
|
+
require 'delayed_job_active_record'
|
10
|
+
rescue LoadError; end
|
11
|
+
|
12
|
+
begin
|
13
|
+
require 'delayed_job_mongoid'
|
14
|
+
rescue LoadError; end
|
6
15
|
|
7
16
|
module DelayedCronJob
|
8
17
|
|
@@ -11,10 +20,33 @@ end
|
|
11
20
|
if defined?(Delayed::Backend::Mongoid)
|
12
21
|
Delayed::Backend::Mongoid::Job.field :cron, :type => String
|
13
22
|
Delayed::Backend::Mongoid::Job.attr_accessible(:cron) if Delayed::Backend::Mongoid::Job.respond_to?(:attr_accessible)
|
23
|
+
Delayed::Backend::Mongoid::Job.send(:include, DelayedCronJob::Backend::UpdatableCron)
|
14
24
|
end
|
15
25
|
|
16
|
-
if defined?(Delayed::Backend::ActiveRecord)
|
17
|
-
|
26
|
+
if defined?(Delayed::Backend::ActiveRecord)
|
27
|
+
if defined?(Rails::Railtie)
|
28
|
+
# Postpone initialization to railtie for correct order
|
29
|
+
require 'delayed_cron_job/backend/active_record/railtie'
|
30
|
+
else
|
31
|
+
# Do the same as in the railtie
|
32
|
+
Delayed::Backend::ActiveRecord::Job.send(:include, DelayedCronJob::Backend::UpdatableCron)
|
33
|
+
if Delayed::Backend::ActiveRecord::Job.respond_to?(:attr_accessible)
|
34
|
+
Delayed::Backend::ActiveRecord::Job.attr_accessible(:cron)
|
35
|
+
end
|
36
|
+
end
|
18
37
|
end
|
19
38
|
|
20
39
|
Delayed::Worker.plugins << DelayedCronJob::Plugin
|
40
|
+
|
41
|
+
if defined?(::ActiveJob)
|
42
|
+
require 'delayed_cron_job/active_job/enqueuing'
|
43
|
+
require 'delayed_cron_job/active_job/queue_adapter'
|
44
|
+
|
45
|
+
ActiveJob::Base.send(:include, DelayedCronJob::ActiveJob::Enqueuing)
|
46
|
+
if ActiveJob::QueueAdapters::DelayedJobAdapter.respond_to?(:enqueue)
|
47
|
+
ActiveJob::QueueAdapters::DelayedJobAdapter.extend(DelayedCronJob::ActiveJob::QueueAdapter)
|
48
|
+
else
|
49
|
+
ActiveJob::QueueAdapters::DelayedJobAdapter.send(:include, DelayedCronJob::ActiveJob::QueueAdapter)
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module DelayedCronJob
|
2
|
+
module ActiveJob
|
3
|
+
module QueueAdapter
|
4
|
+
|
5
|
+
def self.included(klass)
|
6
|
+
klass.send(:alias_method, :enqueue, :enqueue_with_cron)
|
7
|
+
klass.send(:alias_method, :enqueue_at, :enqueue_at_with_cron)
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.extended(klass)
|
11
|
+
meta = class << klass; self; end
|
12
|
+
meta.send(:alias_method, :enqueue, :enqueue_with_cron)
|
13
|
+
meta.send(:alias_method, :enqueue_at, :enqueue_at_with_cron)
|
14
|
+
end
|
15
|
+
|
16
|
+
def enqueue_with_cron(job)
|
17
|
+
enqueue_at(job, nil)
|
18
|
+
end
|
19
|
+
|
20
|
+
def enqueue_at_with_cron(job, timestamp)
|
21
|
+
options = { queue: job.queue_name,
|
22
|
+
cron: job.cron }
|
23
|
+
options[:run_at] = Time.at(timestamp) if timestamp
|
24
|
+
options[:priority] = job.priority if job.respond_to?(:priority)
|
25
|
+
wrapper = ::ActiveJob::QueueAdapters::DelayedJobAdapter::JobWrapper.new(job.serialize)
|
26
|
+
delayed_job = Delayed::Job.enqueue(wrapper, options)
|
27
|
+
job.provider_job_id = delayed_job.id if job.respond_to?(:provider_job_id=)
|
28
|
+
delayed_job
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module DelayedCronJob
|
2
|
+
module Backend
|
3
|
+
module ActiveRecord
|
4
|
+
class Railtie < ::Rails::Railtie
|
5
|
+
config.after_initialize do
|
6
|
+
Delayed::Backend::ActiveRecord::Job.send(:include, DelayedCronJob::Backend::UpdatableCron)
|
7
|
+
if Delayed::Backend::ActiveRecord::Job.respond_to?(:attr_accessible)
|
8
|
+
Delayed::Backend::ActiveRecord::Job.attr_accessible(:cron)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module DelayedCronJob
|
2
|
+
module Backend
|
3
|
+
module UpdatableCron
|
4
|
+
|
5
|
+
def self.included(klass)
|
6
|
+
klass.send(:before_save, :set_next_run_at, :if => :cron_changed?)
|
7
|
+
klass.attr_accessor :schedule_instead_of_destroy
|
8
|
+
end
|
9
|
+
|
10
|
+
def set_next_run_at
|
11
|
+
if cron.present?
|
12
|
+
self.run_at = Cronline.new(cron).next_time(Delayed::Job.db_time_now)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def destroy
|
17
|
+
super unless schedule_instead_of_destroy
|
18
|
+
end
|
19
|
+
|
20
|
+
def schedule_next_run
|
21
|
+
self.attempts += 1
|
22
|
+
unlock
|
23
|
+
set_next_run_at
|
24
|
+
save!
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -2,20 +2,12 @@ module DelayedCronJob
|
|
2
2
|
class Plugin < Delayed::Plugin
|
3
3
|
|
4
4
|
class << self
|
5
|
-
def next_run_at(job)
|
6
|
-
job.run_at = Cronline.new(job.cron).next_time(Delayed::Job.db_time_now)
|
7
|
-
end
|
8
|
-
|
9
5
|
def cron?(job)
|
10
6
|
job.cron.present?
|
11
7
|
end
|
12
8
|
end
|
13
9
|
|
14
10
|
callbacks do |lifecycle|
|
15
|
-
# Calculate the next run_at based on the cron attribute before enqueue.
|
16
|
-
lifecycle.before(:enqueue) do |job|
|
17
|
-
next_run_at(job) if cron?(job)
|
18
|
-
end
|
19
11
|
|
20
12
|
# Prevent rescheduling of failed jobs as this is already done
|
21
13
|
# after perform.
|
@@ -25,7 +17,6 @@ module DelayedCronJob
|
|
25
17
|
worker.job_say(job,
|
26
18
|
"FAILED with #{$ERROR_INFO.class.name}: #{$ERROR_INFO.message}",
|
27
19
|
Logger::ERROR)
|
28
|
-
job.destroy
|
29
20
|
else
|
30
21
|
# No cron job - proceed as normal
|
31
22
|
block.call(worker, job)
|
@@ -34,25 +25,26 @@ module DelayedCronJob
|
|
34
25
|
|
35
26
|
# Reset the last_error to have the correct status of the last run.
|
36
27
|
lifecycle.before(:perform) do |worker, job|
|
37
|
-
if cron?(job)
|
38
|
-
|
39
|
-
|
28
|
+
job.last_error = nil if cron?(job)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Prevent destruction of cron jobs
|
32
|
+
lifecycle.after(:invoke_job) do |job|
|
33
|
+
job.schedule_instead_of_destroy = true if cron?(job)
|
40
34
|
end
|
41
35
|
|
42
36
|
# Schedule the next run based on the cron attribute.
|
43
37
|
lifecycle.after(:perform) do |worker, job|
|
44
|
-
if cron?(job)
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
next_job.save!
|
38
|
+
if cron?(job) && !job.destroyed?
|
39
|
+
job.cron = job.class.where(:id => job.id).pluck(:cron).first
|
40
|
+
if job.cron.present?
|
41
|
+
job.schedule_next_run
|
42
|
+
else
|
43
|
+
job.schedule_instead_of_destroy = false
|
44
|
+
job.destroy
|
45
|
+
end
|
53
46
|
end
|
54
47
|
end
|
55
48
|
end
|
56
|
-
|
57
49
|
end
|
58
50
|
end
|
@@ -12,11 +12,22 @@ module DelayedJob
|
|
12
12
|
self.source_paths << File.join(File.dirname(__FILE__), 'templates')
|
13
13
|
|
14
14
|
def create_migration_file
|
15
|
-
migration_template('cron_migration.rb',
|
15
|
+
migration_template('cron_migration.rb',
|
16
|
+
'db/migrate/add_cron_to_delayed_jobs.rb',
|
17
|
+
migration_version: migration_version)
|
16
18
|
end
|
17
19
|
|
18
20
|
def self.next_migration_number(dirname)
|
19
21
|
ActiveRecord::Generators::Base.next_migration_number(dirname)
|
20
22
|
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def migration_version
|
27
|
+
if ActiveRecord::VERSION::MAJOR >= 5
|
28
|
+
"[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
21
32
|
end
|
22
|
-
end
|
33
|
+
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
class AddCronToDelayedJobs < ActiveRecord::Migration
|
1
|
+
class AddCronToDelayedJobs < ActiveRecord::Migration<%= migration_version %>
|
2
2
|
def self.up
|
3
3
|
add_column :delayed_jobs, :cron, :string
|
4
4
|
end
|
@@ -6,4 +6,4 @@ class AddCronToDelayedJobs < ActiveRecord::Migration
|
|
6
6
|
def self.down
|
7
7
|
remove_column :delayed_jobs, :cron
|
8
8
|
end
|
9
|
-
end
|
9
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe ActiveJob do
|
4
|
+
|
5
|
+
class CronJob < ActiveJob::Base
|
6
|
+
class_attribute :cron
|
7
|
+
|
8
|
+
def perform; end
|
9
|
+
|
10
|
+
def cron
|
11
|
+
@cron ||= self.class.cron
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
before { Delayed::Job.delete_all }
|
16
|
+
|
17
|
+
let(:cron) { '5 1 * * *' }
|
18
|
+
let(:job) { CronJob.set(cron: cron).perform_later }
|
19
|
+
let(:delayed_job) { Delayed::Job.first }
|
20
|
+
let(:worker) { Delayed::Worker.new }
|
21
|
+
let(:now) { Delayed::Job.db_time_now }
|
22
|
+
let(:next_run) do
|
23
|
+
run = now.hour * 60 + now.min >= 65 ? now + 1.day : now
|
24
|
+
Time.utc(run.year, run.month, run.day, 1, 5)
|
25
|
+
end
|
26
|
+
|
27
|
+
context 'with cron' do
|
28
|
+
it 'sets run_at on enqueue' do
|
29
|
+
expect { job }.to change { Delayed::Job.count }.by(1)
|
30
|
+
expect(delayed_job.run_at).to eq(next_run)
|
31
|
+
expect(delayed_job.cron).to eq(cron)
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'schedules a new job after success' do
|
35
|
+
job
|
36
|
+
delayed_job.update_column(:run_at, now)
|
37
|
+
delayed_job.reload
|
38
|
+
|
39
|
+
worker.work_off
|
40
|
+
|
41
|
+
expect(Delayed::Job.count).to eq(1)
|
42
|
+
j = Delayed::Job.first
|
43
|
+
expect(j.id).to eq(delayed_job.id)
|
44
|
+
expect(j.cron).to eq(delayed_job.cron)
|
45
|
+
expect(j.run_at).to eq(next_run)
|
46
|
+
expect(j.attempts).to eq(1)
|
47
|
+
expect(j.last_error).to eq(nil)
|
48
|
+
expect(j.created_at).to eq(delayed_job.created_at)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
context 'without cron' do
|
53
|
+
let(:job) { CronJob.perform_later }
|
54
|
+
|
55
|
+
it 'sets run_at but not cron on enqueue' do
|
56
|
+
CronJob.cron = nil
|
57
|
+
expect { job }.to change { Delayed::Job.count }.by(1)
|
58
|
+
expect(delayed_job.run_at).to be <= now
|
59
|
+
expect(delayed_job.cron).to be_nil
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'uses default cron on enqueue' do
|
63
|
+
CronJob.cron = cron
|
64
|
+
expect { job }.to change { Delayed::Job.count }.by(1)
|
65
|
+
expect(delayed_job.run_at).to eq(next_run)
|
66
|
+
expect(delayed_job.cron).to eq(cron)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
require 'spec_helper'
|
2
2
|
|
3
3
|
describe DelayedCronJob do
|
4
4
|
|
@@ -6,6 +6,16 @@ describe DelayedCronJob do
|
|
6
6
|
def perform; end
|
7
7
|
end
|
8
8
|
|
9
|
+
class DatabaseDisconnectPlugin < Delayed::Plugin
|
10
|
+
|
11
|
+
callbacks do |lifecycle|
|
12
|
+
lifecycle.after(:perform) do
|
13
|
+
ActiveRecord::Base.connection.disconnect!
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
9
19
|
before { Delayed::Job.delete_all }
|
10
20
|
|
11
21
|
let(:cron) { '5 1 * * *' }
|
@@ -25,13 +35,13 @@ describe DelayedCronJob do
|
|
25
35
|
end
|
26
36
|
|
27
37
|
it 'enqueue fails with invalid cron' do
|
28
|
-
expect { Delayed::Job.enqueue(handler, cron: 'no valid cron') }
|
29
|
-
to raise_error(ArgumentError)
|
38
|
+
expect { Delayed::Job.enqueue(handler, cron: 'no valid cron') }
|
39
|
+
.to raise_error(ArgumentError)
|
30
40
|
end
|
31
41
|
|
32
42
|
it 'schedules a new job after success' do
|
33
43
|
job.update_column(:run_at, now)
|
34
|
-
job.reload
|
44
|
+
job.reload # adjusts granularity of run_at datetime
|
35
45
|
|
36
46
|
worker.work_off
|
37
47
|
|
@@ -48,7 +58,7 @@ describe DelayedCronJob do
|
|
48
58
|
it 'schedules a new job after failure' do
|
49
59
|
allow_any_instance_of(TestJob).to receive(:perform).and_raise('Fail!')
|
50
60
|
job.update(run_at: now)
|
51
|
-
job.reload
|
61
|
+
job.reload # adjusts granularity of run_at datetime
|
52
62
|
|
53
63
|
worker.work_off
|
54
64
|
|
@@ -74,19 +84,16 @@ describe DelayedCronJob do
|
|
74
84
|
expect(j.cron).to eq(job.cron)
|
75
85
|
expect(j.run_at).to eq(next_run)
|
76
86
|
expect(j.attempts).to eq(1)
|
77
|
-
expect(j.last_error).to match(
|
87
|
+
expect(j.last_error).to match('execution expired')
|
78
88
|
end
|
79
89
|
|
80
|
-
it '
|
81
|
-
Delayed::Worker.max_run_time = 1.second
|
90
|
+
it 'does not schedule new job after deserialization error' do
|
82
91
|
job.update_column(:run_at, now)
|
83
92
|
allow_any_instance_of(TestJob).to receive(:perform).and_raise(Delayed::DeserializationError)
|
84
93
|
|
85
94
|
worker.work_off
|
86
95
|
|
87
|
-
expect(Delayed::Job.count).to eq(
|
88
|
-
j = Delayed::Job.first
|
89
|
-
expect(j.last_error).to match("Delayed::DeserializationError")
|
96
|
+
expect(Delayed::Job.count).to eq(0)
|
90
97
|
end
|
91
98
|
|
92
99
|
it 'has empty last_error after success' do
|
@@ -98,7 +105,7 @@ describe DelayedCronJob do
|
|
98
105
|
expect(j.last_error).to eq(nil)
|
99
106
|
end
|
100
107
|
|
101
|
-
it 'has
|
108
|
+
it 'has updated last_error after failure' do
|
102
109
|
allow_any_instance_of(TestJob).to receive(:perform).and_raise('Fail!')
|
103
110
|
job.update(run_at: now, last_error: 'Last error')
|
104
111
|
|
@@ -116,7 +123,7 @@ describe DelayedCronJob do
|
|
116
123
|
run_at = Time.utc(run.year, run.month, run.day, hour, (now.min + 1) % 60)
|
117
124
|
expect(job.run_at).to eq(run_at)
|
118
125
|
else
|
119
|
-
pending
|
126
|
+
pending 'This test only makes sense in non-UTC time zone'
|
120
127
|
end
|
121
128
|
end
|
122
129
|
|
@@ -138,6 +145,65 @@ describe DelayedCronJob do
|
|
138
145
|
j = Delayed::Job.first
|
139
146
|
expect(j.attempts).to eq(job.attempts + 1)
|
140
147
|
end
|
148
|
+
|
149
|
+
it 'updates run_at if cron is changed' do
|
150
|
+
job.update!(cron: '1 10 * * *')
|
151
|
+
expect(job.run_at.min).to eq(1)
|
152
|
+
end
|
153
|
+
|
154
|
+
it 'uses new cron when this is updated while job is running' do
|
155
|
+
job.update_column(:run_at, now)
|
156
|
+
allow_any_instance_of(TestJob).to receive(:perform) { job.update!(cron: '1 10 * * *') }
|
157
|
+
|
158
|
+
worker.work_off
|
159
|
+
|
160
|
+
j = Delayed::Job.first
|
161
|
+
expect(j.run_at.min).to eq(1)
|
162
|
+
end
|
163
|
+
|
164
|
+
it 'does not reschedule job if cron is cleared while job is running' do
|
165
|
+
job.update_column(:run_at, now)
|
166
|
+
allow_any_instance_of(TestJob).to receive(:perform) { job.update!(cron: '') }
|
167
|
+
|
168
|
+
expect { worker.work_off }.to change { Delayed::Job.count }.by(-1)
|
169
|
+
end
|
170
|
+
|
171
|
+
it 'does not reschedule job if model is deleted while job is running' do
|
172
|
+
job.update_column(:run_at, now)
|
173
|
+
allow_any_instance_of(TestJob).to receive(:perform) { job.destroy! }
|
174
|
+
|
175
|
+
expect { worker.work_off }.to change { Delayed::Job.count }.by(-1)
|
176
|
+
end
|
177
|
+
|
178
|
+
context 'when database connection is lost' do
|
179
|
+
around(:each) do |example|
|
180
|
+
Delayed::Worker.plugins.unshift DatabaseDisconnectPlugin
|
181
|
+
# hold onto a connection so the in-memory database isn't lost when disconnected
|
182
|
+
temp_connection = ActiveRecord::Base.connection_pool.checkout
|
183
|
+
example.run
|
184
|
+
ActiveRecord::Base.connection_pool.checkin temp_connection
|
185
|
+
Delayed::Worker.plugins.delete DatabaseDisconnectPlugin
|
186
|
+
end
|
187
|
+
|
188
|
+
it 'does not lose the job if database connection is lost' do
|
189
|
+
job.update_column(:run_at, now)
|
190
|
+
job.reload # adjusts granularity of run_at datetime
|
191
|
+
|
192
|
+
begin
|
193
|
+
worker.work_off
|
194
|
+
rescue StandardError
|
195
|
+
# Attempting to save the clone delayed_job will raise an exception due to the database connection being closed
|
196
|
+
end
|
197
|
+
|
198
|
+
ActiveRecord::Base.connection.reconnect!
|
199
|
+
|
200
|
+
expect(Delayed::Job.count).to eq(1)
|
201
|
+
j = Delayed::Job.first
|
202
|
+
expect(j.id).to eq(job.id)
|
203
|
+
expect(j.cron).to eq(job.cron)
|
204
|
+
expect(j.attempts).to eq(0)
|
205
|
+
end
|
206
|
+
end
|
141
207
|
end
|
142
208
|
|
143
209
|
context 'without cron' do
|
data/spec/spec_helper.rb
CHANGED
@@ -15,12 +15,15 @@
|
|
15
15
|
#
|
16
16
|
|
17
17
|
require 'delayed_job_active_record'
|
18
|
+
require 'active_job'
|
18
19
|
require 'delayed_cron_job'
|
19
20
|
|
20
21
|
Delayed::Worker.logger = Logger.new('/tmp/dj.log')
|
21
22
|
ENV['RAILS_ENV'] = 'test'
|
22
23
|
|
23
|
-
|
24
|
+
ActiveJob::Base.queue_adapter = :delayed_job
|
25
|
+
|
26
|
+
ActiveRecord::Base.establish_connection :adapter => 'sqlite3', :database => 'file::memory:?cache=shared'
|
24
27
|
ActiveRecord::Base.logger = Delayed::Worker.logger
|
25
28
|
ActiveRecord::Migration.verbose = false
|
26
29
|
|
@@ -45,5 +48,5 @@ end
|
|
45
48
|
|
46
49
|
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
47
50
|
RSpec.configure do |config|
|
48
|
-
|
51
|
+
|
49
52
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: delayed_cron_job
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Pascal Zumkehr
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-12-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: delayed_job
|
@@ -94,6 +94,20 @@ dependencies:
|
|
94
94
|
- - ">="
|
95
95
|
- !ruby/object:Gem::Version
|
96
96
|
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: activejob
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
97
111
|
description: |-
|
98
112
|
Delayed Cron Job is an extension to Delayed::Job
|
99
113
|
that allows you to set cron expressions for your
|
@@ -106,24 +120,30 @@ extra_rdoc_files: []
|
|
106
120
|
files:
|
107
121
|
- ".gitignore"
|
108
122
|
- ".rspec"
|
123
|
+
- ".travis.yml"
|
109
124
|
- Gemfile
|
110
|
-
- LICENSE
|
125
|
+
- LICENSE
|
111
126
|
- README.md
|
112
127
|
- Rakefile
|
113
128
|
- delayed_cron_job.gemspec
|
114
129
|
- lib/delayed_cron_job.rb
|
130
|
+
- lib/delayed_cron_job/active_job/enqueuing.rb
|
131
|
+
- lib/delayed_cron_job/active_job/queue_adapter.rb
|
132
|
+
- lib/delayed_cron_job/backend/active_record/railtie.rb
|
133
|
+
- lib/delayed_cron_job/backend/updatable_cron.rb
|
115
134
|
- lib/delayed_cron_job/cronline.rb
|
116
135
|
- lib/delayed_cron_job/plugin.rb
|
117
136
|
- lib/delayed_cron_job/version.rb
|
118
137
|
- lib/generators/delayed_job/cron_generator.rb
|
119
138
|
- lib/generators/delayed_job/templates/cron_migration.rb
|
139
|
+
- spec/active_job_spec.rb
|
120
140
|
- spec/delayed_cron_job_spec.rb
|
121
141
|
- spec/spec_helper.rb
|
122
142
|
homepage: https://github.com/codez/delayed_cron_job
|
123
143
|
licenses:
|
124
144
|
- MIT
|
125
145
|
metadata: {}
|
126
|
-
post_install_message:
|
146
|
+
post_install_message:
|
127
147
|
rdoc_options: []
|
128
148
|
require_paths:
|
129
149
|
- lib
|
@@ -138,12 +158,12 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
138
158
|
- !ruby/object:Gem::Version
|
139
159
|
version: '0'
|
140
160
|
requirements: []
|
141
|
-
|
142
|
-
|
143
|
-
signing_key:
|
161
|
+
rubygems_version: 3.0.6
|
162
|
+
signing_key:
|
144
163
|
specification_version: 4
|
145
164
|
summary: An extension to Delayed::Job that allows you to set cron expressions for
|
146
165
|
your jobs to run regularly.
|
147
166
|
test_files:
|
167
|
+
- spec/active_job_spec.rb
|
148
168
|
- spec/delayed_cron_job_spec.rb
|
149
169
|
- spec/spec_helper.rb
|