delayed_cron_job 0.7.2 → 0.7.3

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.
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.