recurring_job 0.0.4

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