delayed_cron_job 0.7.2 → 0.7.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 2b6628291d42c1bc4beef0cb884fb06474d5fea7
4
- data.tar.gz: 3676a138edd3bece7b144579d4c013e0311b40bb
2
+ SHA256:
3
+ metadata.gz: 4fd3901277379b8a03de511f904d2fc6c4ef3ccf1e3ed968592f2c2a6f097c7a
4
+ data.tar.gz: caed3c5171d1c4be7860196607f377dc37a1b5f852f61e468bb9e73559a84176
5
5
  SHA512:
6
- metadata.gz: 3b4856da97ea1a3f57d085f4df9bace1322d6d02ebb7835a50ddf64c469bbcd30c15a93f80d906a78955b6e6e743f5ccf2b02f3f7628245e410e5b377f8c33d9
7
- data.tar.gz: a8fb3f19c7096e2d800bef4a7824e6f8d7e28ea420eae2764664ca28f2c6c7b9729d1bbaa7957740a7277373a43d546f244d42d871041e4103c57dbe6ecf06dd
6
+ metadata.gz: 06bd045e0eeb858c8eb226b286bc8de8a063a655fd2598b086fa9c7bf41c08b6c28ecd5d8767cb02793f950ed4253f6eb8ea551e93a497cae0ef6cc8cd10aed4
7
+ data.tar.gz: aa2ec557d380171cb518ce64ec2c558eecc97c8855040e48a4b95b0669aa911861ebb09e03ab3ee8551de0e4531efa79cf5b108f1e1071ff618178d9063486a7
data/.gitignore CHANGED
@@ -20,4 +20,5 @@ tmp
20
20
  *.o
21
21
  *.a
22
22
  mkmf.log
23
- .project
23
+ .project
24
+ file::memory:*
@@ -1,7 +1,7 @@
1
1
  language: ruby
2
2
 
3
3
  rvm:
4
- - "2.3.0"
4
+ - "2.5.8"
5
5
 
6
6
  sudo: false
7
7
 
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Delayed::Cron::Job
2
2
 
3
- {<img src="https://secure.travis-ci.org/codez/delayed_cron_job.png" />}[http://travis-ci.org/codez/delayed_cron_job]
3
+ [![Build Status](https://travis-ci.org/codez/delayed_cron_job.svg)](https://travis-ci.org/codez/delayed_cron_job)
4
4
 
5
5
  Delayed::Cron::Job is an extension to Delayed::Job that allows you to set
6
6
  cron expressions for your jobs to run repeatedly.
@@ -28,16 +28,123 @@ There are no additional steps for `delayed_job_mongoid`.
28
28
 
29
29
  When enqueuing a job, simply pass the `cron` option, e.g.:
30
30
 
31
- Delayed::Job.enqueue(MyRepeatedJob.new, cron: '15 */6 * * 1-5')
31
+ ```ruby
32
+ Delayed::Job.enqueue(MyRepeatedJob.new, cron: '15 */6 * * 1-5')
33
+ ```
32
34
 
33
- Or, when using Active Job:
35
+ Or, when using ActiveJob:
34
36
 
35
- MyJob.set(cron: '*/5 * * * *').perform_later
37
+ ```ruby
38
+ MyJob.set(cron: '*/5 * * * *').perform_later
39
+ ```
36
40
 
37
41
  Any crontab compatible cron expressions are supported (see `man 5 crontab`).
38
42
  The credits for the `Cronline` class used go to
39
43
  [rufus-scheduler](https://github.com/jmettraux/rufus-scheduler).
40
44
 
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
+
41
148
  ## Details
42
149
 
43
150
  The initial `run_at` value is computed during the `#enqueue` method call.
@@ -71,4 +178,5 @@ jobs.
71
178
  ## License
72
179
 
73
180
  Delayed::Cron::Job is released under the terms of the MIT License.
74
- Copyright 2014-2017 Pascal Zumkehr. See LICENSE for further information.
181
+ Copyright 2014-2020 Pascal Zumkehr. See [LICENSE](LICENSE) for further
182
+ information.
@@ -4,6 +4,7 @@ module DelayedCronJob
4
4
 
5
5
  def self.included(klass)
6
6
  klass.send(:before_save, :set_next_run_at, :if => :cron_changed?)
7
+ klass.attr_accessor :schedule_instead_of_destroy
7
8
  end
8
9
 
9
10
  def set_next_run_at
@@ -12,6 +13,17 @@ module DelayedCronJob
12
13
  end
13
14
  end
14
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
+
15
27
  end
16
28
  end
17
- end
29
+ end
@@ -17,7 +17,6 @@ module DelayedCronJob
17
17
  worker.job_say(job,
18
18
  "FAILED with #{$ERROR_INFO.class.name}: #{$ERROR_INFO.message}",
19
19
  Logger::ERROR)
20
- job.destroy
21
20
  else
22
21
  # No cron job - proceed as normal
23
22
  block.call(worker, job)
@@ -26,31 +25,26 @@ module DelayedCronJob
26
25
 
27
26
  # Reset the last_error to have the correct status of the last run.
28
27
  lifecycle.before(:perform) do |worker, job|
29
- if cron?(job)
30
- job.last_error = nil
31
- end
28
+ job.last_error = nil if cron?(job)
32
29
  end
33
30
 
34
- # Update the cron expression from the database in case it was updated.
31
+ # Prevent destruction of cron jobs
35
32
  lifecycle.after(:invoke_job) do |job|
36
- if cron?(job)
37
- job.cron = job.class.where(:id => job.id).pluck(:cron).first
38
- end
33
+ job.schedule_instead_of_destroy = true if cron?(job)
39
34
  end
40
35
 
41
36
  # Schedule the next run based on the cron attribute.
42
37
  lifecycle.after(:perform) do |worker, job|
43
- if cron?(job)
44
- next_job = job.dup
45
- next_job.id = job.id
46
- next_job.created_at = job.created_at
47
- next_job.locked_at = nil
48
- next_job.locked_by = nil
49
- next_job.attempts += 1
50
- 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
51
46
  end
52
47
  end
53
48
  end
54
-
55
49
  end
56
50
  end
@@ -1,3 +1,3 @@
1
1
  module DelayedCronJob
2
- VERSION = '0.7.2'
2
+ VERSION = '0.7.3'
3
3
  end
@@ -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,8 +35,8 @@ 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
@@ -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("execution expired")
87
+ expect(j.last_error).to match('execution expired')
78
88
  end
79
89
 
80
- it 'schedules new job after deserialization error' do
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(1)
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
@@ -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 "This test only makes sense in non-UTC time zone"
126
+ pending 'This test only makes sense in non-UTC time zone'
120
127
  end
121
128
  end
122
129
 
@@ -168,6 +175,35 @@ describe DelayedCronJob do
168
175
  expect { worker.work_off }.to change { Delayed::Job.count }.by(-1)
169
176
  end
170
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
171
207
  end
172
208
 
173
209
  context 'without cron' do
@@ -23,7 +23,7 @@ ENV['RAILS_ENV'] = 'test'
23
23
 
24
24
  ActiveJob::Base.queue_adapter = :delayed_job
25
25
 
26
- ActiveRecord::Base.establish_connection :adapter => 'sqlite3', :database => ':memory:'
26
+ ActiveRecord::Base.establish_connection :adapter => 'sqlite3', :database => 'file::memory:?cache=shared'
27
27
  ActiveRecord::Base.logger = Delayed::Worker.logger
28
28
  ActiveRecord::Migration.verbose = false
29
29
 
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.7.2
4
+ version: 0.7.3
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: 2017-06-30 00:00:00.000000000 Z
11
+ date: 2020-06-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: delayed_job
@@ -142,7 +142,7 @@ homepage: https://github.com/codez/delayed_cron_job
142
142
  licenses:
143
143
  - MIT
144
144
  metadata: {}
145
- post_install_message:
145
+ post_install_message:
146
146
  rdoc_options: []
147
147
  require_paths:
148
148
  - lib
@@ -157,9 +157,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
157
157
  - !ruby/object:Gem::Version
158
158
  version: '0'
159
159
  requirements: []
160
- rubyforge_project:
161
- rubygems_version: 2.4.5.1
162
- signing_key:
160
+ rubygems_version: 3.1.2
161
+ signing_key:
163
162
  specification_version: 4
164
163
  summary: An extension to Delayed::Job that allows you to set cron expressions for
165
164
  your jobs to run regularly.