recurring_job 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,15 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
15
+ *.gem
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ ruby-1.9.3-p327
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in recurring_job.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 OL2, Inc.
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,157 @@
1
+ # RecurringJob
2
+
3
+ RecurringJob creates a framework for creating custom DelayedJob jobs that are automatically rescheduled to run again.
4
+
5
+ ## Installation
6
+ RecurringJob requires delayed_job_active_record ( > 4.0).
7
+ Follow the instructions to [install DelayedJob](https://github.com/collectiveidea/delayed_job_active_record) first.
8
+
9
+ Then add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'recurring_job'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install recurring_job
22
+
23
+ By default, RecurringJob logs to STDOUT. If you want it to log to your rails logger, put the following somewhere in your rails configuration files
24
+ (I use environment.rb)
25
+
26
+ ```ruby
27
+ RecurringJob.logger = Rails.logger
28
+ ```
29
+
30
+ ## Usage
31
+
32
+ RecurringJob extends the functionality of [Custom Jobs](https://github.com/collectiveidea/delayed_job#custom-jobs)
33
+ within DelayedJob. It uses the job's queue field to identify each type of recurring job, and to ensure a single instance of each
34
+ one is scheduled in the job queue at a time.
35
+
36
+ To use RecurringJob, you need to create a custom job class for each type of job you wish to schedule.
37
+ I like to put job classes in a `lib/jobs` folder in my rails app, but you're free to put them anywhere you like.
38
+
39
+ This example, which is similar to how we use RecurringJob at OnLive, shows how to subclass the RecurringJob class and provide a `perform` method that does the actual work.
40
+ We can send in our own options (in this example an `app_id` for a database Model named App) and those will be passed on each
41
+ time when the job is scheduled. In addition we are automatically passed in the job id of the DelayedJob job, which in this
42
+ case we use for locking (Of course, you can also just ignore the job id if you don't need it!).
43
+
44
+ ```ruby
45
+ class AppStatusJob < RecurringJob
46
+
47
+ def perform
48
+ return unless options
49
+ app_id = options[:app_id]
50
+ recurring_job_id = options[:delayed_job_id]
51
+
52
+ apps_to_process = app_id ? App.where(id:app_id) : App.all
53
+
54
+ apps_to_process.each do |app|
55
+ app.lock_for_status_check(recurring_job_id) do
56
+ # if no one else is modifying the app right now
57
+ # this block gets executed
58
+ app.do_whatever_it_means_to_check_status
59
+ end
60
+ end
61
+ end
62
+
63
+ def after(job)
64
+ super # have to allow RecurringJob to do its work!!
65
+ send_email_about_job_success(job)
66
+ end
67
+
68
+ end
69
+ ```
70
+ We can run this job a single time to check a single app
71
+
72
+ ```ruby
73
+ AppStatusJob.queue_once(app_id:App.first.id)
74
+ ```
75
+
76
+ Or we can set it up to run as a scheduled job to check all apps every hour
77
+ ```ruby
78
+ AppStatusJob.schedule_job(interval:1.hour)
79
+ ```
80
+
81
+ Or we can set it up to run for each particular app on a schedule, using the (unique)
82
+ name of the app as the name of the queue
83
+
84
+ ```ruby
85
+ App.all.each do |app|
86
+ AppStatusJob.schedule_job(interval:1.hour, app_id:app.id, queue:app.name)
87
+ end
88
+ ```
89
+
90
+ Once AppStatusJob has been set up as a RecurringJob, the scheduled jobs will automatically add themselves back into the
91
+ queue to run an hour after they finish (or whatever interval you choose), continuing indefinitely! And like all DelayedJobs,
92
+ you can specify actions to happen when these jobs succeed, or fail, and the jobs live in the DelayedJob queue between runs.
93
+ (See more info about [DelayedJob hooks](https://github.com/collectiveidea/delayed_job#hooks)).
94
+
95
+ **Important:** If you implement any of the DelayedJob hooks (`before`, `after`, `success`, `error`, `failure`, or `enqueue`) in your RecurringJob, you must call super to allow the RecurringJob hooks
96
+ to do its work!
97
+
98
+ ## More info
99
+ If you want to change the interval of a job, you can call schedule_job again and it will change the
100
+ currently scheduled job.
101
+ ```ruby
102
+ AppStatusJob.schedule_job(interval:30.minutes)
103
+ ```
104
+ You can stop a job from running anymore by taking it out of the queue
105
+ ```ruby
106
+ AppStatusJob.unschedule_job
107
+ ```
108
+ To get a list of all the RecurringJobs you have scheduled
109
+ ```ruby
110
+ RecurringJob.all
111
+ ```
112
+
113
+ ## Another example
114
+ You might want to use RecurringJob to send emails in batches,
115
+ for example if your email service has limits to the number of API calls per day, like iPost does. If you had
116
+ a queue of pending emails in a table called pending_emails, it might look something like this (*all actual email
117
+ details left as an exercise for the reader*).
118
+
119
+ ```ruby
120
+ class BatchEmailJob < RecurringJob
121
+
122
+ def perform
123
+ # send any pending emails
124
+ @email_status_hash = PendingEmail.send_all # returns a hash of # emails were sent, any failures, etc
125
+ end
126
+
127
+ def success(job)
128
+ super # allow RecurringJob to do its work!!
129
+
130
+ notify_job_succeeded(@email_status_hash)
131
+ end
132
+
133
+ def max_attempts
134
+ 3
135
+ end
136
+
137
+ end
138
+ ```
139
+ And then set it up to run as often as you want to send the emails, say every 5 hours.
140
+
141
+ ```ruby
142
+ BatchEmailJob.schedule_job(interval:5.hours)
143
+ ```
144
+
145
+
146
+ ## Contributing
147
+
148
+ 1. Fork it ( https://github.com/[my-github-username]/recurring_job/fork )
149
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
150
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
151
+ 4. Push to the branch (`git push origin my-new-feature`)
152
+ 5. Create a new Pull Request
153
+
154
+ ## License and Copying
155
+
156
+ Recurring Job is released under the MIT License by OL2, Inc.
157
+ Please see the file LICENSE.txt for a copy of this license.
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.libs << "test"
6
+ t.test_files = FileList["test/*_test.rb"]
7
+ end
@@ -0,0 +1,5 @@
1
+ # Copyright (C) 2015 OL2, Inc. See LICENSE.txt for details.
2
+
3
+ require "recurring_job/recurring_job"
4
+ require "recurring_job/version"
5
+
@@ -0,0 +1,3 @@
1
+ # Copyright (C) 2015 OL2, Inc. See LICENSE.txt for details.
2
+ class RecurringJob < Struct.new(:options )
3
+ end
@@ -0,0 +1,221 @@
1
+ # Copyright (C) 2014-2015 OL2, Inc. See LICENSE.txt for details.
2
+ require 'active_record'
3
+ require 'delayed_job_active_record'
4
+
5
+ require "recurring_job/class_decl"
6
+
7
+ # Class declaration in recurring_job/class_decl gives the parent class,
8
+ # which is a new Struct().
9
+ class RecurringJob
10
+ # (parts inspired by https://gist.github.com/JoshMcKin/1648242)
11
+
12
+ def self.logger=(new_logger)
13
+ @@logger = new_logger
14
+ end
15
+
16
+ def self.logger
17
+ @@logger ||= Logger.new(STDOUT)
18
+ @@logger
19
+ end
20
+
21
+ def logger
22
+ RecurringJob.logger
23
+ end
24
+
25
+ def self.schedule_job(options = {}, this_job=nil)
26
+ # schedule this job (if you just want job to run once, just use queue_once )
27
+ # this_job is currently running instance (if any)(so we can check against it)
28
+ # options -
29
+ # :interval => number of seconds between job runs (from end of one to beginning of next)
30
+ # default, once a day
31
+ # :queue => name of queue to use
32
+ # default: the name of this class
33
+ # only one job will be scheduled at a time for any given queue
34
+ # :first_start_time => specify a specific time for this run, then use interval after that
35
+ # Plus any other options (if any) you want sent through to the underlying job.
36
+
37
+ options ||= {} # in case sent in explicitly as nil
38
+ options[:interval] ||= default_interval
39
+ options[:queue] ||= default_queue
40
+
41
+ queue_name = options[:queue]
42
+ other_job = next_scheduled_job(this_job, queue_name)
43
+ if other_job
44
+ logger.info "#{queue_name} job is already scheduled for #{other_job.run_at}."
45
+ # Still set any new start time or interval options for next time.
46
+ if job_interval(other_job) != options[:interval].to_i
47
+ logger.info " Updating interval to #{options[:interval]}"
48
+ set_job_interval(other_job, options[:interval])
49
+ end
50
+ if options[:first_start_time] && options[:first_start_time] != other_job.run_at
51
+ logger.info " Updating start time to #{options[:first_start_time]}"
52
+
53
+ other_job.run_at = options[:first_start_time]
54
+ other_job.save
55
+ end
56
+ else
57
+ # if start time is specified, use it ONLY this time (to start), don't pass on in options
58
+ run_time = options.delete(:first_start_time)
59
+ run_time ||= Time.now + options[:interval].to_i # make sure it's an integer (e.g. if sent in as 1.day)
60
+ other_job = Delayed::Job.enqueue self.new(options), :run_at => run_time, :queue=> queue_name
61
+ logger.info "The next #{queue_name} job has been scheduled for #{other_job.run_at}."
62
+ end
63
+ other_job
64
+ end
65
+
66
+ def self.unschedule_job
67
+ # shortcut for deleting the job from Delayed Job
68
+ # returns true if there was a job to delete, false otherwise
69
+ recurring_job = self.next_scheduled_job
70
+ recurring_job.destroy if recurring_job
71
+ return recurring_job # true if there was a job to unschedule
72
+ end
73
+
74
+
75
+ def self.queue_once(options = {})
76
+ # just run this to add the queue to run one time only (not scheduled)
77
+ # IMPORTANT: don't put in same queue name as recurring job and DON'T specify an interval in the options!
78
+ # Can use the queue field, but do not use the job name!
79
+ queue = options[:queue]
80
+ raise "Can't run Recurring Job once in queue: #{default_queue}" if queue == default_queue
81
+ Delayed::Job.enqueue(self.new(options), :queue => queue)
82
+ end
83
+
84
+ def self.in_queue_or_running?(queue)
85
+ # is this job currently in progress?
86
+ # will return false if the job is already done
87
+ # (the job is out of the queue when it's done.)
88
+ queue ||= default_queue
89
+ job = Delayed::Job.find_by(queue:queue)
90
+ job
91
+ end
92
+
93
+ def self.default_interval
94
+ 1.day
95
+ end
96
+
97
+ def self.default_queue
98
+ self.name
99
+ end
100
+
101
+ def self.next_scheduled_job(this_job=nil, queue_name = nil)
102
+ # return job if it exists
103
+ queue_name ||= default_queue
104
+ conditions = ['queue = ? AND failed_at IS NULL', queue_name]
105
+
106
+ unless this_job.blank?
107
+ conditions[0] << " AND id != ?"
108
+ conditions << this_job.id
109
+ end
110
+
111
+ Delayed::Job.where(conditions).first
112
+ end
113
+
114
+
115
+ def self.running?(queue=nil)
116
+ # is this job currently running?
117
+ queue ||= default_queue
118
+ job = Delayed::Job.find_by(queue:queue)
119
+ job && job.locked_by
120
+ end
121
+
122
+ def self.job_id_running?(job_id)
123
+ # is a job with the given id running?
124
+ job = Delayed::Job.find_by(id:job_id) # don't use find, don't want to raise an error if not found
125
+ #logger.debug("Is job #{job_id} running? #{job.inspect}")
126
+ job && job.locked_by
127
+ end
128
+
129
+ def self.set_option(job, option, value)
130
+ # given a job from the queue,
131
+ # parse the handler yaml and set options[option] to value
132
+ y = YAML.load(job.handler)
133
+ y.options ||= {}
134
+ y.options[option] = value
135
+ job.handler = y.to_yaml
136
+ job.save!
137
+ end
138
+
139
+ def self.get_option(job, option)
140
+ # given a job from the queue,
141
+ # parse the handler yaml and return options[option]
142
+ y = YAML.load(job.handler)
143
+ y.options && y.options[option]
144
+ end
145
+
146
+ def self.job_interval(job)
147
+ # given a job from the queue
148
+ # parse the handler yaml and give back the current interval
149
+ # nil means no interval set
150
+ get_option(job, :interval)
151
+ end
152
+
153
+ def self.set_job_interval(job, interval)
154
+ # given a job from the queue
155
+ # parse the handler yaml and set the job interval
156
+ interval ||= default_interval
157
+ set_option(job, :interval, interval.to_i)
158
+ end
159
+
160
+ def self.all
161
+ # Return all jobs with named queues (may get extra jobs if other jobs use named queues)
162
+ Delayed::Job.all.where.not(queue:nil)
163
+ end
164
+
165
+ def self.list_job_intervals
166
+ # lists all jobs that have a queue associated with them and intervals (if any)
167
+ # {queue_name => {interval:interval, next_run:<run_at>}}
168
+ job_list = {}
169
+ self.all.each do |job|
170
+ queue = job.queue
171
+ next unless queue
172
+ job_list[queue] = {interval:self.job_interval(job), next_run:job.run_at}
173
+ end
174
+ job_list
175
+ end
176
+
177
+ def perform
178
+ # should be overridden by real job to do the actual work!
179
+ # (don't call super for this...)
180
+ raise "Must override perform in #{self.class.name}"
181
+ end
182
+
183
+ def before(job)
184
+ # Remember to call super if you implement this hook in your own job!
185
+
186
+ # Send in the job_id so that the RecurringJob can use it if it needs it.
187
+ # (during perform we otherwise don't have access to "job")
188
+ options[:delayed_job_id] = job.id.to_s
189
+ end
190
+
191
+ def after(job)
192
+ # Remember to call super if you implement this hook in your own job!
193
+
194
+ # Reschedule this job when we're done. Whether there's an error in this run
195
+ # or not, we always want to reschedule if an interval was specified
196
+ if options[:interval]
197
+ # if an interval was specified, make sure there's a future job using the same options as before.
198
+ # Otherwise, this is a one time run so don't reschedule.
199
+ options.delete(:delayed_job_id) # don't pass on the previous delayed job id
200
+ # logger.debug("Scheduling again: #{options.inspect}")
201
+ self.class.schedule_job(options, job)
202
+ end
203
+ end
204
+
205
+ def success(job)
206
+ # Remember to call super if you implement this hook in your own job!
207
+ end
208
+
209
+ def error(job, exception)
210
+ # Remember to call super if you implement this hook in your own job!
211
+ end
212
+
213
+ def failure(job)
214
+ # Remember to call super if you implement this hook in your own job!
215
+ end
216
+
217
+ def enqueue(job)
218
+ # Remember to call super if you implement this hook in your own job!
219
+ end
220
+
221
+ end
@@ -0,0 +1,5 @@
1
+ # Copyright (C) 2015 OL2, Inc. See LICENSE.txt for details.
2
+
3
+ require "recurring_job/class_decl"
4
+
5
+ RecurringJob::VERSION = "0.0.4"
@@ -0,0 +1,34 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'recurring_job/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "recurring_job"
8
+ spec.version = RecurringJob::VERSION
9
+ spec.authors = ["Ruth Helfinstein", "Noah Gibbs"]
10
+ spec.email = ["ruth.helfinstein@onlive.com", "noah@onlive.com"]
11
+ spec.summary = %q{Schedule DelayedJob tasks to repeat after a given interval.}
12
+ spec.description = <<DESC
13
+ Recurring_job creates a framework for creating custom DelayedJob jobs
14
+ that are automatically rescheduled to run again at a given interval.
15
+ DESC
16
+ spec.homepage = ""
17
+ spec.license = "MIT"
18
+
19
+ spec.files = `git ls-files -z`.split("\x0")
20
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
21
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
22
+ spec.require_paths = ["lib"]
23
+
24
+ spec.add_runtime_dependency 'activesupport', ['>= 3.0', '< 5.0']
25
+ spec.add_runtime_dependency 'activerecord', '>= 3.0', '< 5.0'
26
+ spec.add_runtime_dependency 'delayed_job_active_record', '~> 4.0'
27
+
28
+ spec.add_development_dependency "bundler", "~> 1.6"
29
+ spec.add_development_dependency "rake", "~> 10.0"
30
+ spec.add_development_dependency "minitest", "~> 5.4"
31
+ spec.add_development_dependency "rr", '~> 1.1'
32
+ spec.add_dependency 'sqlite3', '~> 1'
33
+
34
+ end
@@ -0,0 +1,208 @@
1
+ # Copyright (C) 2014-2015 OL2, Inc. See LICENSE.txt for details.
2
+
3
+ require 'test_helper'
4
+
5
+ class RecurringJobTest < ActiveSupport::TestCase
6
+ @@last_job_id = nil
7
+
8
+ class MyRecurringJob < RecurringJob
9
+ def perform
10
+ raise "Must have options!" unless options
11
+ case options[:action]
12
+ when :error
13
+ raise 'FAILING'
14
+ when :delay
15
+ logger.debug("Sleeping")
16
+ sleep(5)
17
+ when :test_id
18
+ logger.debug("Job id is #{options[:delayed_job_id]}")
19
+ RecurringJobTest.last_job_id = options[:delayed_job_id]
20
+ else
21
+ logger.debug("Performing")
22
+ end
23
+ end
24
+ end
25
+
26
+ def self.last_job_id=(value)
27
+ @@last_job_id = value
28
+ end
29
+
30
+ def setup
31
+ RecurringJob.logger.debug("------------------------------------")
32
+ Delayed::Job.delete_all
33
+ end
34
+
35
+ def test_schedule_job
36
+ # nothing is scheduled when we start
37
+ assert_nil(MyRecurringJob.next_scheduled_job)
38
+ # schedule a job which will run immediately and reschedule itself
39
+ # (since not running automatically this will work)
40
+ job = MyRecurringJob.schedule_job({interval:1, action: :perform, first_start_time: Time.now})
41
+ # now this job is scheduled
42
+ assert_equal(job, MyRecurringJob.next_scheduled_job)
43
+ assert_equal(RecurringJob.all, [job])
44
+ # now run the job from the queue
45
+ Delayed::Worker.new.work_off
46
+
47
+ # should have added a new job that's different from this one
48
+ job2 = MyRecurringJob.next_scheduled_job
49
+ assert(job2)
50
+ refute_equal(job2, job)
51
+
52
+ # if we try to schedule a new one now it should be the same one
53
+ job3 = MyRecurringJob.schedule_job({interval:1, first_start_time: Time.now})
54
+ assert_equal(job2, job3)
55
+ end
56
+
57
+ def test_failed_job
58
+ Delayed::Worker.max_attempts = 3 # delete after third failed attempt
59
+
60
+ worker = Delayed::Worker.new
61
+
62
+ job = MyRecurringJob.schedule_job({interval:1, action: :error})
63
+
64
+ # make sure the jobs will run now
65
+ RecurringJob.all.each do |j|
66
+ j.run_at = Time.now
67
+ j.save!
68
+ end
69
+ # run job, will fail
70
+ worker.work_off
71
+ RecurringJob.all.each do |j|
72
+ j.run_at = Time.now
73
+ j.save!
74
+ end
75
+
76
+ # there should now be two jobs in the queue
77
+ # since it adds the next job even if there's an error
78
+ assert_equal(2,RecurringJob.all.size, "Should be the new job in the queue")
79
+ jobs = RecurringJob.all
80
+ # they should both run this time and both get errors
81
+ worker.work_off
82
+
83
+ RecurringJob.all.each do |j|
84
+ j.run_at = Time.now
85
+ j.save!
86
+ end
87
+
88
+ assert_equal(jobs, RecurringJob.all)
89
+ # There should still only be the same two jobs in the queue.
90
+ assert_equal(2,RecurringJob.all.size)
91
+
92
+ worker.work_off
93
+ # it's been 3 attempts so original job should be deleted
94
+
95
+ RecurringJob.all.each do |j|
96
+ j.run_at = Time.now
97
+ j.save!
98
+ end
99
+
100
+ refute_includes(RecurringJob.all, job)
101
+
102
+ # The second job will fail a third time and get deleted, but make sure it puts a new job in the
103
+ # queue before it does (to show we will always have at least one job in the queue)
104
+ worker.work_off
105
+
106
+ refute_empty(RecurringJob.all)
107
+
108
+ end
109
+
110
+ def test_uses_different_queues
111
+ job = MyRecurringJob.schedule_job({interval:1, queue:'queue1'})
112
+ job2 = MyRecurringJob.schedule_job({interval:1})
113
+ refute_equal(job, job2)
114
+ assert_equal(job, MyRecurringJob.next_scheduled_job(nil, 'queue1'))
115
+ assert_equal(job2, MyRecurringJob.next_scheduled_job(nil))
116
+ end
117
+
118
+ def test_first_start_time
119
+ first_start_time = Date.today.midnight
120
+ job = MyRecurringJob.schedule_job({interval:1, first_start_time:first_start_time})
121
+ assert_equal(first_start_time, job.run_at)
122
+ end
123
+
124
+ def test_job_with_no_schedule
125
+ # run MyRecurringJob one time only with no interval set and check that it's not rescheduled
126
+ job = MyRecurringJob.queue_once(action: :something)
127
+
128
+ assert_equal(Delayed::Job.all, [job])
129
+ # now run the job from the queue
130
+ Delayed::Worker.new.work_off
131
+
132
+ # There should be no jobs in the queue (wasn't rescheduled)
133
+ assert_empty(Delayed::Job.all, "Queue should be empty")
134
+ end
135
+
136
+ def test_get_and_set_interval
137
+ job = MyRecurringJob.schedule_job
138
+ interval = MyRecurringJob.job_interval(job)
139
+ assert_equal(interval, MyRecurringJob.default_interval)
140
+
141
+ refute_equal(interval, 0)
142
+
143
+ MyRecurringJob.set_job_interval(job, 0)
144
+ job.reload # make sure it saved the change
145
+ assert_equal(0, MyRecurringJob.job_interval(job))
146
+
147
+ end
148
+
149
+ def test_schedule_job_new_interval
150
+ # nothing is scheduled when we start
151
+ assert_nil(MyRecurringJob.next_scheduled_job)
152
+ # schedule a job which will run immediately and reschedule itself immediately
153
+ # (since not running automatically this will work)
154
+ job = MyRecurringJob.schedule_job({interval:1})
155
+ # now this job is scheduled
156
+ assert_equal(job, MyRecurringJob.next_scheduled_job)
157
+ assert_equal(1, MyRecurringJob.job_interval(job))
158
+
159
+ job2 = MyRecurringJob.schedule_job({interval:0})
160
+ assert_equal(job, job2) # same object
161
+ assert_equal(0, MyRecurringJob.job_interval(job2))
162
+
163
+ job3 = MyRecurringJob.schedule_job({interval:1})
164
+ assert_equal(job, job3) # same object
165
+ assert_equal(1, MyRecurringJob.job_interval(job3))
166
+
167
+
168
+ end
169
+
170
+ def test_schedule_job_new_first_start_time
171
+ # nothing is scheduled when we start
172
+ assert_nil(MyRecurringJob.next_scheduled_job)
173
+
174
+ time1 = Time.now + 1.day
175
+ job = MyRecurringJob.schedule_job({first_start_time:time1})
176
+ # now this job is scheduled
177
+ assert_equal(job, MyRecurringJob.next_scheduled_job)
178
+ assert_equal(time1.to_i, job.run_at.to_i)
179
+
180
+ job2 = MyRecurringJob.schedule_job({interval:0}) # no start time
181
+ assert_equal(job, job2) # same object
182
+ assert_equal(time1.to_i, job2.run_at.to_i)
183
+
184
+ time2 = Time.now
185
+ job3 = MyRecurringJob.schedule_job({first_start_time:time2})
186
+ assert_equal(job, job3) # same object
187
+ assert_equal(time2.to_i, job3.run_at.to_i)
188
+ end
189
+
190
+ def test_job_id
191
+ # make sure the delayed job id is available to the job.
192
+ assert_empty(RecurringJob.all)
193
+
194
+ RecurringJobTest.last_job_id = nil
195
+ job = MyRecurringJob.schedule_job({interval:1, action: :test_id, first_start_time: Time.now})
196
+ assert_equal(RecurringJob.all, [job])
197
+
198
+ refute(MyRecurringJob.get_option(job, :delayed_job_id)) # it's set only when the job is running
199
+ assert_equal(:test_id, MyRecurringJob.get_option(job, :action))
200
+
201
+ # now run the job from the queue
202
+ Delayed::Worker.new.work_off
203
+
204
+ assert_equal(job.id, @@last_job_id.to_i)
205
+
206
+ end
207
+
208
+ end
@@ -0,0 +1,21 @@
1
+ # Copyright (C) 2015 OL2, Inc. See LICENSE.txt for details.
2
+
3
+ # set up database just for testing. When using the gem, users will set up
4
+ # the database structure when setting up delayed job to work with their code and
5
+ # we use that same database.
6
+ ActiveRecord::Base.establish_connection :adapter => 'sqlite3', :database => ':memory:'
7
+
8
+ # set up the DelayedJob schema in our test database
9
+ ActiveRecord::Base.connection.create_table "delayed_jobs", force: true do |t|
10
+ t.integer "priority", default: 0, null: false
11
+ t.integer "attempts", default: 0, null: false
12
+ t.text "handler", null: false
13
+ t.text "last_error"
14
+ t.datetime "run_at"
15
+ t.datetime "locked_at"
16
+ t.datetime "failed_at"
17
+ t.string "locked_by"
18
+ t.string "queue"
19
+ t.datetime "created_at"
20
+ t.datetime "updated_at"
21
+ end
@@ -0,0 +1,30 @@
1
+ # Copyright (C) 2015 OL2, Inc. See LICENSE.txt for details.
2
+
3
+ # Test local copy first
4
+ $LOAD_PATH.unshift File.join(File.dirname(__FILE__), "..", "lib")
5
+
6
+ require "rr"
7
+ require 'active_support/dependencies'
8
+ require 'active_record'
9
+ require 'logger'
10
+
11
+ require "support/db"
12
+ require "recurring_job"
13
+ require 'minitest/autorun'
14
+
15
+ require 'tempfile'
16
+
17
+ if !Dir.exists?('tmp')
18
+ Dir.mkdir('tmp')
19
+ end
20
+ RecurringJob.logger = Logger.new('tmp/rj_test.log')
21
+
22
+ ENV['RAILS_ENV'] = 'test'
23
+
24
+
25
+ ActiveSupport::TestCase.test_order = :random
26
+ ActiveRecord::Base.logger = RecurringJob.logger
27
+ ActiveSupport::LogSubscriber.colorize_logging = false
28
+
29
+
30
+
metadata ADDED
@@ -0,0 +1,209 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: recurring_job
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.4
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Ruth Helfinstein
9
+ - Noah Gibbs
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2015-03-05 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: activesupport
17
+ requirement: !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ! '>='
21
+ - !ruby/object:Gem::Version
22
+ version: '3.0'
23
+ - - <
24
+ - !ruby/object:Gem::Version
25
+ version: '5.0'
26
+ type: :runtime
27
+ prerelease: false
28
+ version_requirements: !ruby/object:Gem::Requirement
29
+ none: false
30
+ requirements:
31
+ - - ! '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '3.0'
34
+ - - <
35
+ - !ruby/object:Gem::Version
36
+ version: '5.0'
37
+ - !ruby/object:Gem::Dependency
38
+ name: activerecord
39
+ requirement: !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ! '>='
43
+ - !ruby/object:Gem::Version
44
+ version: '3.0'
45
+ - - <
46
+ - !ruby/object:Gem::Version
47
+ version: '5.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ none: false
52
+ requirements:
53
+ - - ! '>='
54
+ - !ruby/object:Gem::Version
55
+ version: '3.0'
56
+ - - <
57
+ - !ruby/object:Gem::Version
58
+ version: '5.0'
59
+ - !ruby/object:Gem::Dependency
60
+ name: delayed_job_active_record
61
+ requirement: !ruby/object:Gem::Requirement
62
+ none: false
63
+ requirements:
64
+ - - ~>
65
+ - !ruby/object:Gem::Version
66
+ version: '4.0'
67
+ type: :runtime
68
+ prerelease: false
69
+ version_requirements: !ruby/object:Gem::Requirement
70
+ none: false
71
+ requirements:
72
+ - - ~>
73
+ - !ruby/object:Gem::Version
74
+ version: '4.0'
75
+ - !ruby/object:Gem::Dependency
76
+ name: bundler
77
+ requirement: !ruby/object:Gem::Requirement
78
+ none: false
79
+ requirements:
80
+ - - ~>
81
+ - !ruby/object:Gem::Version
82
+ version: '1.6'
83
+ type: :development
84
+ prerelease: false
85
+ version_requirements: !ruby/object:Gem::Requirement
86
+ none: false
87
+ requirements:
88
+ - - ~>
89
+ - !ruby/object:Gem::Version
90
+ version: '1.6'
91
+ - !ruby/object:Gem::Dependency
92
+ name: rake
93
+ requirement: !ruby/object:Gem::Requirement
94
+ none: false
95
+ requirements:
96
+ - - ~>
97
+ - !ruby/object:Gem::Version
98
+ version: '10.0'
99
+ type: :development
100
+ prerelease: false
101
+ version_requirements: !ruby/object:Gem::Requirement
102
+ none: false
103
+ requirements:
104
+ - - ~>
105
+ - !ruby/object:Gem::Version
106
+ version: '10.0'
107
+ - !ruby/object:Gem::Dependency
108
+ name: minitest
109
+ requirement: !ruby/object:Gem::Requirement
110
+ none: false
111
+ requirements:
112
+ - - ~>
113
+ - !ruby/object:Gem::Version
114
+ version: '5.4'
115
+ type: :development
116
+ prerelease: false
117
+ version_requirements: !ruby/object:Gem::Requirement
118
+ none: false
119
+ requirements:
120
+ - - ~>
121
+ - !ruby/object:Gem::Version
122
+ version: '5.4'
123
+ - !ruby/object:Gem::Dependency
124
+ name: rr
125
+ requirement: !ruby/object:Gem::Requirement
126
+ none: false
127
+ requirements:
128
+ - - ~>
129
+ - !ruby/object:Gem::Version
130
+ version: '1.1'
131
+ type: :development
132
+ prerelease: false
133
+ version_requirements: !ruby/object:Gem::Requirement
134
+ none: false
135
+ requirements:
136
+ - - ~>
137
+ - !ruby/object:Gem::Version
138
+ version: '1.1'
139
+ - !ruby/object:Gem::Dependency
140
+ name: sqlite3
141
+ requirement: !ruby/object:Gem::Requirement
142
+ none: false
143
+ requirements:
144
+ - - ~>
145
+ - !ruby/object:Gem::Version
146
+ version: '1'
147
+ type: :runtime
148
+ prerelease: false
149
+ version_requirements: !ruby/object:Gem::Requirement
150
+ none: false
151
+ requirements:
152
+ - - ~>
153
+ - !ruby/object:Gem::Version
154
+ version: '1'
155
+ description: ! 'Recurring_job creates a framework for creating custom DelayedJob jobs
156
+
157
+ that are automatically rescheduled to run again at a given interval.
158
+
159
+ '
160
+ email:
161
+ - ruth.helfinstein@onlive.com
162
+ - noah@onlive.com
163
+ executables: []
164
+ extensions: []
165
+ extra_rdoc_files: []
166
+ files:
167
+ - .gitignore
168
+ - .ruby-version
169
+ - Gemfile
170
+ - LICENSE.txt
171
+ - README.md
172
+ - Rakefile
173
+ - lib/recurring_job.rb
174
+ - lib/recurring_job/class_decl.rb
175
+ - lib/recurring_job/recurring_job.rb
176
+ - lib/recurring_job/version.rb
177
+ - recurring_job.gemspec
178
+ - test/recurring_job_test.rb
179
+ - test/support/db.rb
180
+ - test/test_helper.rb
181
+ homepage: ''
182
+ licenses:
183
+ - MIT
184
+ post_install_message:
185
+ rdoc_options: []
186
+ require_paths:
187
+ - lib
188
+ required_ruby_version: !ruby/object:Gem::Requirement
189
+ none: false
190
+ requirements:
191
+ - - ! '>='
192
+ - !ruby/object:Gem::Version
193
+ version: '0'
194
+ required_rubygems_version: !ruby/object:Gem::Requirement
195
+ none: false
196
+ requirements:
197
+ - - ! '>='
198
+ - !ruby/object:Gem::Version
199
+ version: '0'
200
+ requirements: []
201
+ rubyforge_project:
202
+ rubygems_version: 1.8.23
203
+ signing_key:
204
+ specification_version: 3
205
+ summary: Schedule DelayedJob tasks to repeat after a given interval.
206
+ test_files:
207
+ - test/recurring_job_test.rb
208
+ - test/support/db.rb
209
+ - test/test_helper.rb