resque-scheduler 2.3.1 → 2.4.0
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.
Potentially problematic release.
This version of resque-scheduler might be problematic. Click here for more details.
- data/.gitignore +3 -0
- data/.rubocop.yml +11 -11
- data/.simplecov +1 -0
- data/.travis.yml +5 -2
- data/AUTHORS.md +3 -0
- data/HISTORY.md +26 -2
- data/LICENSE +1 -1
- data/README.md +120 -31
- data/ROADMAP.md +10 -0
- data/Rakefile +7 -19
- data/bin/resque-scheduler +5 -0
- data/examples/Rakefile +2 -0
- data/examples/config/initializers/resque-web.rb +37 -0
- data/examples/dynamic-scheduling/README.md +28 -0
- data/examples/dynamic-scheduling/app/jobs/fix_schedules_job.rb +54 -0
- data/examples/dynamic-scheduling/app/jobs/send_email_job.rb +9 -0
- data/examples/dynamic-scheduling/app/models/user.rb +16 -0
- data/examples/dynamic-scheduling/config/resque.yml +4 -0
- data/examples/dynamic-scheduling/config/static_schedule.yml +7 -0
- data/examples/dynamic-scheduling/lib/tasks/resque.rake +48 -0
- data/examples/run-resque-web +3 -0
- data/lib/resque-scheduler.rb +2 -0
- data/lib/resque/scheduler.rb +130 -41
- data/lib/resque/scheduler/lock/resilient.rb +1 -1
- data/lib/resque/scheduler_locking.rb +3 -1
- data/lib/resque_scheduler.rb +73 -31
- data/lib/resque_scheduler/cli.rb +160 -0
- data/lib/resque_scheduler/logger_builder.rb +27 -8
- data/lib/resque_scheduler/plugin.rb +10 -7
- data/lib/resque_scheduler/server.rb +52 -11
- data/lib/resque_scheduler/server/views/delayed.erb +2 -0
- data/lib/resque_scheduler/server/views/delayed_schedules.erb +20 -0
- data/lib/resque_scheduler/server/views/scheduler.erb +4 -12
- data/lib/resque_scheduler/tasks.rb +15 -27
- data/lib/resque_scheduler/version.rb +1 -1
- data/resque-scheduler.gemspec +2 -0
- data/test/cli_test.rb +286 -0
- data/test/delayed_queue_test.rb +70 -1
- data/test/resque-web_test.rb +36 -1
- data/test/scheduler_args_test.rb +51 -17
- data/test/scheduler_hooks_test.rb +1 -1
- data/test/scheduler_locking_test.rb +63 -1
- data/test/scheduler_setup_test.rb +54 -18
- data/test/scheduler_task_test.rb +35 -0
- data/test/scheduler_test.rb +130 -42
- data/test/support/redis_instance.rb +8 -3
- data/test/test_helper.rb +47 -20
- metadata +77 -6
- checksums.yaml +0 -15
@@ -79,7 +79,9 @@ module Resque
|
|
79
79
|
end
|
80
80
|
|
81
81
|
def master_lock_key
|
82
|
-
|
82
|
+
lock_prefix = ENV['RESQUE_SCHEDULER_MASTER_LOCK_PREFIX'] || ''
|
83
|
+
lock_prefix += ':' if lock_prefix != ''
|
84
|
+
"#{Resque.redis.namespace}:#{lock_prefix}resque_scheduler_master_lock"
|
83
85
|
end
|
84
86
|
|
85
87
|
def redis_master_version
|
data/lib/resque_scheduler.rb
CHANGED
@@ -6,6 +6,8 @@ require 'resque/scheduler'
|
|
6
6
|
require 'resque_scheduler/plugin'
|
7
7
|
|
8
8
|
module ResqueScheduler
|
9
|
+
autoload :Cli, 'resque_scheduler/cli'
|
10
|
+
|
9
11
|
#
|
10
12
|
# Accepts a new schedule configuration of the form:
|
11
13
|
#
|
@@ -91,7 +93,7 @@ module ResqueScheduler
|
|
91
93
|
def clean_schedules
|
92
94
|
if redis.exists(:schedules)
|
93
95
|
redis.hkeys(:schedules).each do |key|
|
94
|
-
remove_schedule(key)
|
96
|
+
remove_schedule(key) if !schedule_persisted?(key)
|
95
97
|
end
|
96
98
|
end
|
97
99
|
@schedule = nil
|
@@ -109,9 +111,15 @@ module ResqueScheduler
|
|
109
111
|
# :args => '/tmp/poop'})
|
110
112
|
def set_schedule(name, config)
|
111
113
|
existing_config = get_schedule(name)
|
114
|
+
persist = config.delete(:persist) || config.delete('persist')
|
112
115
|
unless existing_config && existing_config == config
|
113
|
-
redis.
|
114
|
-
|
116
|
+
redis.pipelined do
|
117
|
+
redis.hset(:schedules, name, encode(config))
|
118
|
+
redis.sadd(:schedules_changed, name)
|
119
|
+
if persist
|
120
|
+
redis.sadd(:persisted_schedules, name)
|
121
|
+
end
|
122
|
+
end
|
115
123
|
end
|
116
124
|
config
|
117
125
|
end
|
@@ -121,10 +129,17 @@ module ResqueScheduler
|
|
121
129
|
decode(redis.hget(:schedules, name))
|
122
130
|
end
|
123
131
|
|
132
|
+
def schedule_persisted?(name)
|
133
|
+
redis.sismember(:persisted_schedules, name)
|
134
|
+
end
|
135
|
+
|
124
136
|
# remove a given schedule by name
|
125
137
|
def remove_schedule(name)
|
126
|
-
redis.
|
127
|
-
|
138
|
+
redis.pipelined do
|
139
|
+
redis.hdel(:schedules, name)
|
140
|
+
redis.srem(:persisted_schedules, name)
|
141
|
+
redis.sadd(:schedules_changed, name)
|
142
|
+
end
|
128
143
|
end
|
129
144
|
|
130
145
|
# This method is nearly identical to +enqueue+ only it also
|
@@ -272,6 +287,33 @@ module ResqueScheduler
|
|
272
287
|
remove_delayed(klass, *args).times { Resque::Scheduler.enqueue_from_config(hash) }
|
273
288
|
end
|
274
289
|
|
290
|
+
# Given a block, remove jobs that return true from a block
|
291
|
+
#
|
292
|
+
# This allows for removal of delayed jobs that have arguments matching certain criteria
|
293
|
+
def remove_delayed_selection
|
294
|
+
fail ArgumentError, "Please supply a block" unless block_given?
|
295
|
+
|
296
|
+
destroyed = 0
|
297
|
+
# There is no way to search Redis list entries for a partial match, so we query for all
|
298
|
+
# delayed job tasks and do our matching after decoding the payload data
|
299
|
+
jobs = Resque.redis.keys("delayed:*")
|
300
|
+
jobs.each do |job|
|
301
|
+
index = Resque.redis.llen(job) - 1
|
302
|
+
while index >= 0
|
303
|
+
payload = Resque.redis.lindex(job, index)
|
304
|
+
decoded_payload = decode(payload)
|
305
|
+
if yield(decoded_payload['args'])
|
306
|
+
removed = redis.lrem job, 0, payload
|
307
|
+
destroyed += removed
|
308
|
+
index -= removed
|
309
|
+
else
|
310
|
+
index -= 1
|
311
|
+
end
|
312
|
+
end
|
313
|
+
end
|
314
|
+
destroyed
|
315
|
+
end
|
316
|
+
|
275
317
|
# Given a timestamp and job (klass + args) it removes all instances and
|
276
318
|
# returns the count of jobs removed.
|
277
319
|
#
|
@@ -306,39 +348,39 @@ module ResqueScheduler
|
|
306
348
|
|
307
349
|
private
|
308
350
|
|
309
|
-
|
310
|
-
|
311
|
-
|
351
|
+
def job_to_hash(klass, args)
|
352
|
+
{:class => klass.to_s, :args => args, :queue => queue_from_class(klass)}
|
353
|
+
end
|
312
354
|
|
313
|
-
|
314
|
-
|
315
|
-
|
355
|
+
def job_to_hash_with_queue(queue, klass, args)
|
356
|
+
{:class => klass.to_s, :args => args, :queue => queue}
|
357
|
+
end
|
316
358
|
|
317
|
-
|
318
|
-
|
359
|
+
def clean_up_timestamp(key, timestamp)
|
360
|
+
# If the list is empty, remove it.
|
319
361
|
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
end
|
328
|
-
else
|
329
|
-
redis.unwatch
|
362
|
+
# Use a watch here to ensure nobody adds jobs to this delayed
|
363
|
+
# queue while we're removing it.
|
364
|
+
redis.watch key
|
365
|
+
if 0 == redis.llen(key).to_i
|
366
|
+
redis.multi do
|
367
|
+
redis.del key
|
368
|
+
redis.zrem :delayed_queue_schedule, timestamp.to_i
|
330
369
|
end
|
370
|
+
else
|
371
|
+
redis.unwatch
|
331
372
|
end
|
373
|
+
end
|
332
374
|
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
end
|
340
|
-
prepared_hash
|
375
|
+
def prepare_schedule(schedule_hash)
|
376
|
+
prepared_hash = {}
|
377
|
+
schedule_hash.each do |name, job_spec|
|
378
|
+
job_spec = job_spec.dup
|
379
|
+
job_spec['class'] = name unless job_spec.key?('class') || job_spec.key?(:class)
|
380
|
+
prepared_hash[name] = job_spec
|
341
381
|
end
|
382
|
+
prepared_hash
|
383
|
+
end
|
342
384
|
end
|
343
385
|
|
344
386
|
Resque.extend ResqueScheduler
|
@@ -0,0 +1,160 @@
|
|
1
|
+
# vim:fileencoding=utf-8
|
2
|
+
|
3
|
+
require 'optparse'
|
4
|
+
|
5
|
+
module ResqueScheduler
|
6
|
+
class Cli
|
7
|
+
BANNER = <<-EOF.gsub(/ {6}/, '')
|
8
|
+
Usage: resque-scheduler [options]
|
9
|
+
|
10
|
+
Runs a resque scheduler process directly (rather than via rake).
|
11
|
+
|
12
|
+
EOF
|
13
|
+
OPTIONS = [
|
14
|
+
{
|
15
|
+
args: ['-n', '--app-name [APP_NAME]', 'Application name for procline'],
|
16
|
+
callback: ->(options) { ->(n) { options[:app_name] = n } }
|
17
|
+
},
|
18
|
+
{
|
19
|
+
args: ['-B', '--background', 'Run in the background [BACKGROUND]'],
|
20
|
+
callback: ->(options) { ->(b) { options[:background] = b } }
|
21
|
+
},
|
22
|
+
{
|
23
|
+
args: ['-D', '--dynamic-schedule',
|
24
|
+
'Enable dynamic scheduling [DYNAMIC_SCHEDULE]'],
|
25
|
+
callback: ->(options) { ->(d) { options[:dynamic] = d } }
|
26
|
+
},
|
27
|
+
{
|
28
|
+
args: ['-E', '--environment [RAILS_ENV]', 'Environment name'],
|
29
|
+
callback: ->(options) { ->(e) { options[:env] = e } }
|
30
|
+
},
|
31
|
+
{
|
32
|
+
args: ['-I', '--initializer-path [INITIALIZER_PATH]',
|
33
|
+
'Path to optional initializer ruby file'],
|
34
|
+
callback: ->(options) { ->(i) { options[:initializer_path] = i } }
|
35
|
+
},
|
36
|
+
{
|
37
|
+
args: ['-i', '--interval [RESQUE_SCHEDULER_INTERVAL]',
|
38
|
+
'Interval for checking if a scheduled job must run'],
|
39
|
+
callback: ->(options) { ->(i) { options[:poll_sleep_amount] = i } }
|
40
|
+
},
|
41
|
+
{
|
42
|
+
args: ['-l', '--logfile [LOGFILE]', 'Log file name'],
|
43
|
+
callback: ->(options) { ->(l) { options[:logfile] = l } }
|
44
|
+
},
|
45
|
+
{
|
46
|
+
args: ['-F', '--logformat [LOGFORMAT]', 'Log output format'],
|
47
|
+
callback: ->(options) { ->(f) { options[:logformat] = f } }
|
48
|
+
},
|
49
|
+
{
|
50
|
+
args: ['-P', '--pidfile [PIDFILE]', 'PID file name'],
|
51
|
+
callback: ->(options) { ->(p) { options[:pidfile] = p } }
|
52
|
+
},
|
53
|
+
{
|
54
|
+
args: ['-q', '--quiet', 'Run with minimal output [QUIET] (or [MUTE])'],
|
55
|
+
callback: ->(options) { ->(q) { options[:mute] = q } }
|
56
|
+
},
|
57
|
+
{
|
58
|
+
args: ['-v', '--verbose', 'Run with verbose output [VERBOSE]'],
|
59
|
+
callback: ->(options) { ->(v) { options[:verbose] = v } }
|
60
|
+
}
|
61
|
+
].freeze
|
62
|
+
|
63
|
+
def self.run!(argv = ARGV, env = ENV)
|
64
|
+
new(argv, env).run!
|
65
|
+
end
|
66
|
+
|
67
|
+
def initialize(argv = ARGV, env = ENV)
|
68
|
+
@argv = argv
|
69
|
+
@env = env
|
70
|
+
end
|
71
|
+
|
72
|
+
def run!
|
73
|
+
pre_run
|
74
|
+
run_forever
|
75
|
+
end
|
76
|
+
|
77
|
+
def pre_run
|
78
|
+
parse_options
|
79
|
+
pre_setup
|
80
|
+
setup_env
|
81
|
+
end
|
82
|
+
|
83
|
+
def parse_options
|
84
|
+
OptionParser.new do |opts|
|
85
|
+
opts.banner = BANNER
|
86
|
+
OPTIONS.each do |opt|
|
87
|
+
opts.on(*opt[:args], &(opt[:callback].call(options)))
|
88
|
+
end
|
89
|
+
end.parse!(argv.dup)
|
90
|
+
end
|
91
|
+
|
92
|
+
def pre_setup
|
93
|
+
if options[:initializer_path]
|
94
|
+
load options[:initializer_path].to_s.strip
|
95
|
+
else
|
96
|
+
false
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def setup_env
|
101
|
+
require 'resque'
|
102
|
+
require 'resque/scheduler'
|
103
|
+
|
104
|
+
# Need to set this here for conditional Process.daemon redirect of
|
105
|
+
# stderr/stdout to /dev/null
|
106
|
+
Resque::Scheduler.mute = !!options[:mute]
|
107
|
+
|
108
|
+
if options[:background]
|
109
|
+
unless Process.respond_to?('daemon')
|
110
|
+
abort 'background option is set, which requires ruby >= 1.9'
|
111
|
+
end
|
112
|
+
|
113
|
+
Process.daemon(true, !Resque::Scheduler.mute)
|
114
|
+
Resque.redis.client.reconnect
|
115
|
+
end
|
116
|
+
|
117
|
+
File.open(options[:pidfile], 'w') do |f|
|
118
|
+
f.puts $PROCESS_ID
|
119
|
+
end if options[:pidfile]
|
120
|
+
|
121
|
+
Resque::Scheduler.configure do |c|
|
122
|
+
# These settings are somewhat redundant given the defaults present
|
123
|
+
# in the attr reader methods. They are left here for clarity and
|
124
|
+
# to serve as an example of how to use `.configure`.
|
125
|
+
|
126
|
+
c.app_name = options[:app_name]
|
127
|
+
c.dynamic = !!options[:dynamic]
|
128
|
+
c.env = options[:env]
|
129
|
+
c.logfile = options[:logfile]
|
130
|
+
c.logformat = options[:logformat]
|
131
|
+
c.poll_sleep_amount = Float(options[:poll_sleep_amount] || '5')
|
132
|
+
c.verbose = !!options[:verbose]
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def run_forever
|
137
|
+
Resque::Scheduler.run
|
138
|
+
end
|
139
|
+
|
140
|
+
private
|
141
|
+
|
142
|
+
attr_reader :argv, :env
|
143
|
+
|
144
|
+
def options
|
145
|
+
@options ||= {
|
146
|
+
app_name: env['APP_NAME'],
|
147
|
+
background: env['BACKGROUND'],
|
148
|
+
dynamic: env['DYNAMIC_SCHEDULE'],
|
149
|
+
env: env['RAILS_ENV'],
|
150
|
+
initializer_path: env['INITIALIZER_PATH'],
|
151
|
+
logfile: env['LOGFILE'],
|
152
|
+
logformat: env['LOGFORMAT'],
|
153
|
+
mute: env['MUTE'] || env['QUIET'],
|
154
|
+
pidfile: env['PIDFILE'],
|
155
|
+
poll_sleep_amount: env['RESQUE_SCHEDULER_INTERVAL'],
|
156
|
+
verbose: env['VERBOSE']
|
157
|
+
}
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# vim:fileencoding=utf-8
|
2
|
+
|
1
3
|
module ResqueScheduler
|
2
4
|
# Just builds a logger, with specified verbosity and destination.
|
3
5
|
# The simplest example:
|
@@ -10,23 +12,25 @@ module ResqueScheduler
|
|
10
12
|
# - :mute if logger needs to be silent for all levels. Default - false
|
11
13
|
# - :verbose if there is a need in debug messages. Default - false
|
12
14
|
# - :log_dev to output logs into a desired file. Default - STDOUT
|
15
|
+
# - :format log format, either 'text' or 'json'. Default - 'text'
|
13
16
|
#
|
14
17
|
# Example:
|
15
18
|
#
|
16
|
-
# LoggerBuilder.new(
|
19
|
+
# LoggerBuilder.new(
|
20
|
+
# :mute => false, :verbose => true, :log_dev => 'log/scheduler.log'
|
21
|
+
# )
|
17
22
|
def initialize(opts={})
|
18
|
-
@muted
|
23
|
+
@muted = !!opts[:mute]
|
19
24
|
@verbose = !!opts[:verbose]
|
20
|
-
@log_dev = opts[:log_dev] ||
|
25
|
+
@log_dev = opts[:log_dev] || $stdout
|
26
|
+
@format = opts[:format] || 'text'
|
21
27
|
end
|
22
28
|
|
23
29
|
# Returns an instance of Logger
|
24
30
|
def build
|
25
31
|
logger = Logger.new(@log_dev)
|
26
32
|
logger.level = level
|
27
|
-
logger.
|
28
|
-
logger.formatter = formatter
|
29
|
-
|
33
|
+
logger.formatter = send(:"#{@format}_formatter")
|
30
34
|
logger
|
31
35
|
end
|
32
36
|
|
@@ -42,9 +46,24 @@ module ResqueScheduler
|
|
42
46
|
end
|
43
47
|
end
|
44
48
|
|
45
|
-
def
|
49
|
+
def text_formatter
|
50
|
+
proc do |severity, datetime, progname, msg|
|
51
|
+
"resque-scheduler: [#{severity}] #{datetime.iso8601}: #{msg}\n"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def json_formatter
|
46
56
|
proc do |severity, datetime, progname, msg|
|
47
|
-
|
57
|
+
require 'json'
|
58
|
+
JSON.dump(
|
59
|
+
{
|
60
|
+
:name => 'resque-scheduler',
|
61
|
+
:progname => progname,
|
62
|
+
:level => severity,
|
63
|
+
:timestamp => datetime.iso8601,
|
64
|
+
:msg => msg
|
65
|
+
}
|
66
|
+
) + "\n"
|
48
67
|
end
|
49
68
|
end
|
50
69
|
end
|
@@ -13,13 +13,16 @@ module ResqueScheduler
|
|
13
13
|
results.all? { |result| result != false }
|
14
14
|
end
|
15
15
|
|
16
|
-
def
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
16
|
+
def run_before_delayed_enqueue_hooks(klass, *args)
|
17
|
+
run_hooks(klass, 'before_delayed_enqueue', *args)
|
18
|
+
end
|
19
|
+
|
20
|
+
def run_before_schedule_hooks(klass, *args)
|
21
|
+
run_hooks(klass, 'before_schedule', *args)
|
22
|
+
end
|
23
|
+
|
24
|
+
def run_after_schedule_hooks(klass, *args)
|
25
|
+
run_hooks(klass, 'after_schedule', *args)
|
23
26
|
end
|
24
27
|
end
|
25
28
|
end
|
@@ -1,14 +1,12 @@
|
|
1
1
|
require 'resque_scheduler'
|
2
2
|
require 'resque/server'
|
3
|
+
require 'json'
|
4
|
+
|
3
5
|
# Extend Resque::Server to add tabs
|
4
6
|
module ResqueScheduler
|
5
|
-
|
6
7
|
module Server
|
7
|
-
|
8
8
|
def self.included(base)
|
9
|
-
|
10
9
|
base.class_eval do
|
11
|
-
|
12
10
|
helpers do
|
13
11
|
def format_time(t)
|
14
12
|
t.strftime("%Y-%m-%d %H:%M:%S %z")
|
@@ -17,6 +15,41 @@ module ResqueScheduler
|
|
17
15
|
def queue_from_class_name(class_name)
|
18
16
|
Resque.queue_from_class(ResqueScheduler::Util.constantize(class_name))
|
19
17
|
end
|
18
|
+
|
19
|
+
def schedule_interval(config)
|
20
|
+
if config['every']
|
21
|
+
schedule_interval_every(config['every'])
|
22
|
+
elsif config['cron']
|
23
|
+
'cron: ' + config['cron'].to_s
|
24
|
+
else
|
25
|
+
'Not currently scheduled'
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def schedule_interval_every(every)
|
30
|
+
s = 'every: '
|
31
|
+
if every.respond_to?(:first)
|
32
|
+
s << every.first
|
33
|
+
else
|
34
|
+
s << every
|
35
|
+
end
|
36
|
+
|
37
|
+
return s unless every.respond_to?(:last) && every.length > 1
|
38
|
+
|
39
|
+
s << ' ('
|
40
|
+
meta = every.last.map do |key, value|
|
41
|
+
"#{key.to_s.gsub(/_/, ' ')} #{value}"
|
42
|
+
end
|
43
|
+
s << meta.join(', ') << ')'
|
44
|
+
end
|
45
|
+
|
46
|
+
def schedule_class(config)
|
47
|
+
if config['class'].nil? && !config['custom_job_class'].nil?
|
48
|
+
config['custom_job_class']
|
49
|
+
else
|
50
|
+
config['class']
|
51
|
+
end
|
52
|
+
end
|
20
53
|
end
|
21
54
|
|
22
55
|
get "/schedule" do
|
@@ -59,6 +92,18 @@ module ResqueScheduler
|
|
59
92
|
erb File.read(File.join(File.dirname(__FILE__), 'server/views/delayed.erb'))
|
60
93
|
end
|
61
94
|
|
95
|
+
get "/delayed/jobs/:klass" do
|
96
|
+
begin
|
97
|
+
klass = ResqueScheduler::Util::constantize(params[:klass])
|
98
|
+
@args = JSON.load(URI.decode(params[:args]))
|
99
|
+
@timestamps = Resque.scheduled_at(klass, *@args)
|
100
|
+
rescue => err
|
101
|
+
@timestamps = []
|
102
|
+
end
|
103
|
+
|
104
|
+
erb File.read(File.join(File.dirname(__FILE__), 'server/views/delayed_schedules.erb'))
|
105
|
+
end
|
106
|
+
|
62
107
|
get "/delayed/:timestamp" do
|
63
108
|
# Is there a better way to specify alternate template locations with sinatra?
|
64
109
|
erb File.read(File.join(File.dirname(__FILE__), 'server/views/delayed_timestamp.erb'))
|
@@ -74,18 +119,14 @@ module ResqueScheduler
|
|
74
119
|
Resque.reset_delayed_queue
|
75
120
|
redirect u('delayed')
|
76
121
|
end
|
77
|
-
|
78
122
|
end
|
79
|
-
|
80
123
|
end
|
81
|
-
|
82
|
-
Resque::Server.tabs << 'Schedule'
|
83
|
-
Resque::Server.tabs << 'Delayed'
|
84
|
-
|
85
124
|
end
|
86
|
-
|
87
125
|
end
|
88
126
|
|
127
|
+
Resque::Server.tabs << 'Schedule'
|
128
|
+
Resque::Server.tabs << 'Delayed'
|
129
|
+
|
89
130
|
Resque::Server.class_eval do
|
90
131
|
include ResqueScheduler::Server
|
91
132
|
end
|