sidekiq-scheduler 2.0.8 → 2.0.9

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a2ca5f12c843c8653e3a174431625f89622daee3
4
- data.tar.gz: e479e32ea285d53200078971da9b1eb3e7dfd323
3
+ metadata.gz: 0f7d8c790cf456b0a62f67d543f1c7532f21e16b
4
+ data.tar.gz: ca1112b924897da7d6d06d72e071ea63a92ac8e8
5
5
  SHA512:
6
- metadata.gz: 2ab648c207104060b36cfebb38fae68bb8af05b63ab2760485d6b958e1145312b7b1ceca631089eb297d8a84f6963969920e04f36e5fc9f641f45635b58d5c10
7
- data.tar.gz: ef5e376ba307b8b0fc62c6fcb61b7be50c50cd37971715e501de8220dad7e8743dfb5856ba18c10aa963c990827d194b807df2fe45927a38c3027aa7310a247b
6
+ metadata.gz: 666ed752c0a891f3ad99af53402ff3274e881dc0540d79d0025c191cdcb6d80d73066d4e9c4021f85c0e04d4d65ecb6a63d41e2b4d6b0420f58070ab791b28b2
7
+ data.tar.gz: f8dec2ee3b6add1cd02bd6760dc58fc49fc65cef3b1ae1a01891fb77e5fd535c9052ab78c98f21c5a396bbe30e457466a92bcf17dbf104c6e5be631879aa2f63
data/README.md CHANGED
@@ -81,8 +81,7 @@ if Rails.env == 'production' && (defined?(Rails::Server) || defined?(Unicorn))
81
81
  Sidekiq.configure_server do |config|
82
82
 
83
83
  config.on(:startup) do
84
- Sidekiq.schedule = YAML
85
- .load_file(File.expand_path('../../../config/scheduler.yml', __FILE__))
84
+ Sidekiq.schedule = YAML.load_file(File.expand_path('../../config/scheduler.yml', __FILE__))
86
85
  Sidekiq::Scheduler.reload_schedule!
87
86
  end
88
87
  end
@@ -157,7 +156,7 @@ require 'sidekiq/scheduler'
157
156
 
158
157
  Sidekiq.configure_server do |config|
159
158
  config.on(:startup) do
160
- Sidekiq.schedule = YAML.load_file(File.expand_path("../../../config/scheduler.yml",__FILE__))
159
+ Sidekiq.schedule = YAML.load_file(File.expand_path("../../config/scheduler.yml", __FILE__))
161
160
  Sidekiq::Scheduler.reload_schedule!
162
161
  end
163
162
  end
@@ -167,11 +166,11 @@ If you are running a non Rails project you should add code to load the workers c
167
166
 
168
167
  ```ruby
169
168
  require 'sidekiq/scheduler'
170
- Dir[File.expand_path('../lib/workers/*.rb',__FILE__)].each do |file| load file; end
169
+ Dir[File.expand_path('../lib/workers/*.rb', __FILE__)].each do |file| load file; end
171
170
 
172
171
  Sidekiq.configure_server do |config|
173
172
  config.on(:startup) do
174
- Sidekiq.schedule = YAML.load_file(File.expand_path("../../../config/scheduler.yml",__FILE__))
173
+ Sidekiq.schedule = YAML.load_file(File.expand_path("../../config/scheduler.yml", __FILE__))
175
174
  Sidekiq::Scheduler.reload_schedule!
176
175
  end
177
176
  end
@@ -218,15 +217,11 @@ You can set that flag in the following ways.
218
217
  ```ruby
219
218
  Sidekiq.configure_server do |config|
220
219
  # ...
221
-
222
- config.on(:startup) do
223
- # ...
224
- Sidekiq::Scheduler.dynamic = true
225
- end
220
+ Sidekiq::Scheduler.dynamic = true
226
221
  end
227
222
  ```
228
223
 
229
- If `:dynamic` flag is set to false, you have to reload the schedule manually in sidekiq
224
+ If `:dynamic` flag is set to `false`, you have to reload the schedule manually in sidekiq
230
225
  side:
231
226
 
232
227
  ```ruby
@@ -0,0 +1,58 @@
1
+ begin
2
+ require 'sidekiq/web/helpers'
3
+ rescue LoadError
4
+ require 'sidekiq/web_helpers'
5
+ end
6
+
7
+ module SidekiqScheduler
8
+ class JobPresenter
9
+ attr_reader :name
10
+
11
+ include Sidekiq::WebHelpers
12
+
13
+ def initialize(name, attributes)
14
+ @name = name
15
+ @attributes = attributes
16
+ end
17
+
18
+ # Returns the next time execution for the job
19
+ #
20
+ # @return [String] with the job's next time
21
+ def next_time
22
+ execution_time = Sidekiq.redis { |r| r.hget(Sidekiq::Scheduler.next_times_key, name) }
23
+
24
+ relative_time(Time.parse(execution_time)) if execution_time
25
+ end
26
+
27
+ # Returns the interval for the job
28
+ #
29
+ # @return [String] with the job's interval
30
+ def interval
31
+ @attributes['cron'] || @attributes['interval'] || @attributes['every']
32
+ end
33
+
34
+ # Returns the queue of the job
35
+ #
36
+ # @return [String] with the job's queue
37
+ def queue
38
+ @attributes.fetch('queue', 'default')
39
+ end
40
+
41
+ # Delegates the :[] method to the attributes' hash
42
+ #
43
+ # @return [String] with the value for that key
44
+ def [](key)
45
+ @attributes[key]
46
+ end
47
+
48
+ # Builds the presenter instances for the schedule hash
49
+ #
50
+ # @param schedule_hash [Hash] with the redis schedule
51
+ # @return [Array<JobPresenter>] an array with the instances of presenters
52
+ def self.build_collection(schedule_hash)
53
+ Hash(schedule_hash).map do |name, job_spec|
54
+ new(name, job_spec)
55
+ end
56
+ end
57
+ end
58
+ end
@@ -18,7 +18,7 @@ module SidekiqScheduler
18
18
  Sidekiq::Scheduler.enabled = options[:enabled]
19
19
  Sidekiq::Scheduler.dynamic = options[:dynamic]
20
20
  Sidekiq::Scheduler.listened_queues_only = options[:listened_queues_only]
21
- Sidekiq.schedule = options[:schedule] if options[:schedule]
21
+ Sidekiq.schedule = options[:schedule] || {}
22
22
  end
23
23
 
24
24
  def stop
@@ -1,5 +1,5 @@
1
1
  module SidekiqScheduler
2
2
 
3
- VERSION = '2.0.8'
3
+ VERSION = '2.0.9'
4
4
 
5
5
  end
@@ -1,3 +1,5 @@
1
+ require_relative 'job_presenter'
2
+
1
3
  module SidekiqScheduler
2
4
  # Hook into *Sidekiq::Web* Sinatra app which adds a new '/recurring-jobs' page
3
5
 
@@ -6,7 +8,7 @@ module SidekiqScheduler
6
8
 
7
9
  def self.registered(app)
8
10
  app.get '/recurring-jobs' do
9
- @schedule = (Sidekiq.schedule! || [])
11
+ @presented_jobs = JobPresenter.build_collection(Sidekiq.schedule!)
10
12
 
11
13
  erb File.read(File.join(VIEW_PATH, 'recurring_jobs.erb'))
12
14
  end
@@ -14,7 +16,7 @@ module SidekiqScheduler
14
16
  app.get '/recurring-jobs/:name/enqueue' do
15
17
  schedule = Sidekiq.get_schedule(params[:name])
16
18
  Sidekiq::Scheduler.enqueue_job(schedule)
17
- redirect to('/recurring-jobs')
19
+ redirect '/recurring-jobs'
18
20
  end
19
21
  end
20
22
  end
@@ -23,4 +25,4 @@ end
23
25
  require 'sidekiq/web' unless defined?(Sidekiq::Web)
24
26
  Sidekiq::Web.register(SidekiqScheduler::Web)
25
27
  Sidekiq::Web.tabs['recurring_jobs'] = 'recurring-jobs'
26
- Sidekiq::Web.set :locales, Sidekiq::Web.locales << File.expand_path(File.dirname(__FILE__) + "/../../web/locales")
28
+ Sidekiq::Web.locales << File.expand_path(File.dirname(__FILE__) + "/../../web/locales")
@@ -8,6 +8,7 @@ module Sidekiq
8
8
  extend Sidekiq::Util
9
9
 
10
10
  REGISTERED_JOBS_THRESHOLD_IN_SECONDS = 24 * 60 * 60
11
+ RUFUS_METADATA_KEYS = %w(description at cron every in interval)
11
12
 
12
13
  # We expect rufus jobs to have #params
13
14
  Rufus::Scheduler::Job.module_eval do
@@ -26,299 +27,340 @@ module Sidekiq
26
27
 
27
28
  # Set to schedule jobs only when will be pushed to queues listened by sidekiq
28
29
  attr_accessor :listened_queues_only
29
- end
30
30
 
31
- # the Rufus::Scheduler jobs that are scheduled
32
- def self.scheduled_jobs
33
- @@scheduled_jobs
34
- end
31
+ # the Rufus::Scheduler jobs that are scheduled
32
+ def scheduled_jobs
33
+ @@scheduled_jobs
34
+ end
35
35
 
36
- def self.print_schedule
37
- if self.rufus_scheduler
38
- logger.info "Scheduling Info\tLast Run"
39
- scheduler_jobs = self.rufus_scheduler.all_jobs
40
- scheduler_jobs.each do |_, v|
41
- logger.info "#{v.t}\t#{v.last}\t"
36
+ def print_schedule
37
+ if rufus_scheduler
38
+ logger.info "Scheduling Info\tLast Run"
39
+ scheduler_jobs = rufus_scheduler.all_jobs
40
+ scheduler_jobs.each do |_, v|
41
+ logger.info "#{v.t}\t#{v.last}\t"
42
+ end
42
43
  end
43
44
  end
44
- end
45
45
 
46
- # Pulls the schedule from Sidekiq.schedule and loads it into the
47
- # rufus scheduler instance
48
- def self.load_schedule!
49
- if enabled
50
- logger.info 'Loading Schedule'
51
-
52
- # Load schedule from redis for the first time if dynamic
53
- if dynamic
54
- Sidekiq.reload_schedule!
55
- self.rufus_scheduler.every('5s') do
56
- self.update_schedule
46
+ # Pulls the schedule from Sidekiq.schedule and loads it into the
47
+ # rufus scheduler instance
48
+ def load_schedule!
49
+ if enabled
50
+ logger.info 'Loading Schedule'
51
+
52
+ # Load schedule from redis for the first time if dynamic
53
+ if dynamic
54
+ Sidekiq.reload_schedule!
55
+ rufus_scheduler.every('5s') do
56
+ update_schedule
57
+ end
57
58
  end
58
- end
59
59
 
60
- logger.info 'Schedule empty! Set Sidekiq.schedule' if Sidekiq.schedule.empty?
60
+ logger.info 'Schedule empty! Set Sidekiq.schedule' if Sidekiq.schedule.empty?
61
61
 
62
62
 
63
- @@scheduled_jobs = {}
63
+ @@scheduled_jobs = {}
64
64
 
65
- Sidekiq.schedule.each do |name, config|
66
- if !listened_queues_only || enabled_queue?(config['queue'])
67
- self.load_schedule_job(name, config)
68
- else
69
- logger.info { "Ignoring #{name}, job's queue is not enabled." }
65
+ Sidekiq.schedule.each do |name, config|
66
+ if !listened_queues_only || enabled_queue?(config['queue'])
67
+ load_schedule_job(name, config)
68
+ else
69
+ logger.info { "Ignoring #{name}, job's queue is not enabled." }
70
+ end
70
71
  end
71
- end
72
72
 
73
- Sidekiq.redis { |r| r.del(:schedules_changed) }
73
+ Sidekiq.redis { |r| r.del(:schedules_changed) }
74
74
 
75
- logger.info 'Schedules Loaded'
76
- else
77
- logger.info 'SidekiqScheduler is disabled'
75
+ logger.info 'Schedules Loaded'
76
+ else
77
+ logger.info 'SidekiqScheduler is disabled'
78
+ end
78
79
  end
79
- end
80
80
 
81
- # modify interval type value to value with options if options available
82
- def self.optionizate_interval_value(value)
83
- args = value
84
- if args.is_a?(::Array)
85
- return args.first if args.size > 2 || !args.last.is_a?(::Hash)
86
- # symbolize keys of hash for options
87
- args[1] = args[1].inject({}) do |m, i|
88
- key, value = i
89
- m[(key.to_sym rescue key) || key] = value
90
- m
81
+ # modify interval type value to value with options if options available
82
+ def optionizate_interval_value(value)
83
+ args = value
84
+ if args.is_a?(::Array)
85
+ return args.first if args.size > 2 || !args.last.is_a?(::Hash)
86
+ # symbolize keys of hash for options
87
+ args[1] = args[1].inject({}) do |m, i|
88
+ key, value = i
89
+ m[(key.to_sym rescue key) || key] = value
90
+ m
91
+ end
91
92
  end
93
+ args
92
94
  end
93
- args
94
- end
95
95
 
96
- # Loads a job schedule into the Rufus::Scheduler and stores it in @@scheduled_jobs
97
- def self.load_schedule_job(name, config)
98
- # If rails_env is set in the config, enforce ENV['RAILS_ENV'] as
99
- # required for the jobs to be scheduled. If rails_env is missing, the
100
- # job should be scheduled regardless of what ENV['RAILS_ENV'] is set
101
- # to.
102
- if config['rails_env'].nil? || self.rails_env_matches?(config)
103
- logger.info "Scheduling #{name} #{config}"
104
- interval_defined = false
105
- interval_types = %w{cron every at in interval}
106
- interval_types.each do |interval_type|
107
- config_interval_type = config[interval_type]
96
+ # Loads a job schedule into the Rufus::Scheduler and stores it in @@scheduled_jobs
97
+ def load_schedule_job(name, config)
98
+ # If rails_env is set in the config, enforce ENV['RAILS_ENV'] as
99
+ # required for the jobs to be scheduled. If rails_env is missing, the
100
+ # job should be scheduled regardless of what ENV['RAILS_ENV'] is set
101
+ # to.
102
+ if config['rails_env'].nil? || rails_env_matches?(config)
103
+ logger.info "Scheduling #{name} #{config}"
104
+ interval_defined = false
105
+ interval_types = %w{cron every at in interval}
106
+ interval_types.each do |interval_type|
107
+ config_interval_type = config[interval_type]
108
108
 
109
- if !config_interval_type.nil? && config_interval_type.length > 0
109
+ if !config_interval_type.nil? && config_interval_type.length > 0
110
110
 
111
- args = self.optionizate_interval_value(config_interval_type)
111
+ args = optionizate_interval_value(config_interval_type)
112
112
 
113
- # We want rufus_scheduler to return a job object, not a job id
114
- opts = { :job => true }
113
+ rufus_job = new_job(name, interval_type, config, args)
114
+ @@scheduled_jobs[name] = rufus_job
115
+ update_job_next_time(name, rufus_job.next_time)
115
116
 
116
- @@scheduled_jobs[name] = self.rufus_scheduler.send(interval_type, *args, opts) do |job, time|
117
- config.delete(interval_type)
117
+ interval_defined = true
118
118
 
119
- idempotent_job_enqueue(name, time, config)
119
+ break
120
120
  end
121
+ end
121
122
 
122
- interval_defined = true
123
-
124
- break
123
+ unless interval_defined
124
+ logger.info "no #{interval_types.join(' / ')} found for #{config['class']} (#{name}) - skipping"
125
125
  end
126
126
  end
127
+ end
128
+
129
+ # Pushes the job into Sidekiq if not already pushed for the given time
130
+ #
131
+ # @param [String] job_name The job's name
132
+ # @param [Time] time The time when the job got cleared for triggering
133
+ # @param [Hash] config Job's config hash
134
+ def idempotent_job_enqueue(job_name, time, config)
135
+ registered = register_job_instance(job_name, time)
136
+
137
+ if registered
138
+ logger.info "queueing #{config['class']} (#{job_name})"
139
+
140
+ handle_errors { enqueue_job(config) }
141
+
142
+ remove_elder_job_instances(job_name)
143
+ else
144
+ logger.debug { "Ignoring #{job_name} job as it has been already enqueued" }
145
+ end
146
+ end
127
147
 
128
- unless interval_defined
129
- logger.info "no #{interval_types.join(' / ')} found for #{config['class']} (#{name}) - skipping"
148
+ # Pushes job's next time execution
149
+ #
150
+ # @param [String] name The job's name
151
+ # @param [Time] next_time The job's next time execution
152
+ def update_job_next_time(name, next_time)
153
+ Sidekiq.redis do |r|
154
+ next_time ? r.hset(next_times_key, name, next_time) : r.hdel(next_times_key, name)
130
155
  end
131
156
  end
132
- end
133
157
 
134
- # Pushes the job into Sidekiq if not already pushed for the given time
135
- #
136
- # @param [String] job_name The job's name
137
- # @param [Time] time The time when the job got cleared for triggering
138
- # @param [Hash] config Job's config hash
139
- def self.idempotent_job_enqueue(job_name, time, config)
140
- registered = register_job_instance(job_name, time)
158
+ # Returns true if the given schedule config hash matches the current
159
+ # ENV['RAILS_ENV']
160
+ def rails_env_matches?(config)
161
+ config['rails_env'] && ENV['RAILS_ENV'] && config['rails_env'].gsub(/\s/, '').split(',').include?(ENV['RAILS_ENV'])
162
+ end
141
163
 
142
- if registered
143
- logger.info "queueing #{config['class']} (#{job_name})"
164
+ def handle_errors
165
+ begin
166
+ yield
167
+ rescue StandardError => e
168
+ logger.info "#{e.class.name}: #{e.message}"
169
+ end
170
+ end
144
171
 
145
- self.handle_errors { self.enqueue_job(config) }
172
+ # Enqueue a job based on a config hash
173
+ def enqueue_job(job_config)
174
+ config = prepare_arguments(job_config.dup)
146
175
 
147
- remove_elder_job_instances(job_name)
148
- else
149
- logger.debug { "Ignoring #{job_name} job as it has been already enqueued" }
176
+ if active_job_enqueue?(config['class'])
177
+ enque_with_active_job(config)
178
+ else
179
+ enque_with_sidekiq(config)
180
+ end
150
181
  end
151
- end
152
182
 
153
- # Returns true if the given schedule config hash matches the current
154
- # ENV['RAILS_ENV']
155
- def self.rails_env_matches?(config)
156
- config['rails_env'] && ENV['RAILS_ENV'] && config['rails_env'].gsub(/\s/, '').split(',').include?(ENV['RAILS_ENV'])
157
- end
183
+ def rufus_scheduler_options
184
+ @rufus_scheduler_options ||= {}
185
+ end
158
186
 
159
- def self.handle_errors
160
- begin
161
- yield
162
- rescue StandardError => e
163
- logger.info "#{e.class.name}: #{e.message}"
187
+ def rufus_scheduler_options=(options)
188
+ @rufus_scheduler_options = options
164
189
  end
165
- end
166
190
 
167
- # Enqueue a job based on a config hash
168
- def self.enqueue_job(job_config)
169
- config = prepare_arguments(job_config.dup)
191
+ def rufus_scheduler
192
+ @rufus_scheduler ||= new_rufus_scheduler
193
+ end
170
194
 
171
- if active_job_enqueue?(config['class'])
172
- enque_with_active_job(config)
173
- else
174
- enque_with_sidekiq(config)
195
+ # Stops old rufus scheduler and creates a new one. Returns the new
196
+ # rufus scheduler
197
+ def clear_schedule!
198
+ rufus_scheduler.stop
199
+ @rufus_scheduler = nil
200
+ @@scheduled_jobs = {}
201
+ rufus_scheduler
175
202
  end
176
- end
177
203
 
178
- def self.rufus_scheduler_options
179
- @rufus_scheduler_options ||= {}
180
- end
204
+ def reload_schedule!
205
+ if enabled
206
+ logger.info 'Reloading Schedule'
207
+ clear_schedule!
208
+ load_schedule!
209
+ else
210
+ logger.info 'SidekiqScheduler is disabled'
211
+ end
212
+ end
181
213
 
182
- def self.rufus_scheduler_options=(options)
183
- @rufus_scheduler_options = options
184
- end
214
+ def update_schedule
215
+ if Sidekiq.redis { |r| r.scard(:schedules_changed) } > 0
216
+ logger.info 'Updating schedule'
217
+ Sidekiq.reload_schedule!
218
+ while schedule_name = Sidekiq.redis { |r| r.spop(:schedules_changed) }
219
+ if Sidekiq.schedule.keys.include?(schedule_name)
220
+ unschedule_job(schedule_name)
221
+ load_schedule_job(schedule_name, Sidekiq.schedule[schedule_name])
222
+ else
223
+ unschedule_job(schedule_name)
224
+ end
225
+ end
226
+ logger.info 'Schedules Loaded'
227
+ end
228
+ end
185
229
 
186
- def self.rufus_scheduler
187
- @rufus_scheduler ||= Rufus::Scheduler.new(rufus_scheduler_options)
188
- end
230
+ def unschedule_job(name)
231
+ if scheduled_jobs[name]
232
+ logger.debug "Removing schedule #{name}"
233
+ scheduled_jobs[name].unschedule
234
+ scheduled_jobs.delete(name)
235
+ end
236
+ end
189
237
 
190
- # Stops old rufus scheduler and creates a new one. Returns the new
191
- # rufus scheduler
192
- def self.clear_schedule!
193
- self.rufus_scheduler.stop
194
- @rufus_scheduler = nil
195
- @@scheduled_jobs = {}
196
- self.rufus_scheduler
197
- end
238
+ def enque_with_active_job(config)
239
+ initialize_active_job(config['class'], config['args']).enqueue(config)
240
+ end
198
241
 
199
- def self.reload_schedule!
200
- if enabled
201
- logger.info 'Reloading Schedule'
202
- self.clear_schedule!
203
- self.load_schedule!
204
- else
205
- logger.info 'SidekiqScheduler is disabled'
242
+ def enque_with_sidekiq(config)
243
+ Sidekiq::Client.push(sanitize_job_config(config))
206
244
  end
207
- end
208
245
 
209
- def self.update_schedule
210
- if Sidekiq.redis { |r| r.scard(:schedules_changed) } > 0
211
- logger.info 'Updating schedule'
212
- Sidekiq.reload_schedule!
213
- while schedule_name = Sidekiq.redis { |r| r.spop(:schedules_changed) }
214
- if Sidekiq.schedule.keys.include?(schedule_name)
215
- self.unschedule_job(schedule_name)
216
- self.load_schedule_job(schedule_name, Sidekiq.schedule[schedule_name])
217
- else
218
- self.unschedule_job(schedule_name)
219
- end
246
+ def initialize_active_job(klass, args)
247
+ if args.is_a?(Array)
248
+ klass.new(*args)
249
+ else
250
+ klass.new(args)
220
251
  end
221
- logger.info 'Schedules Loaded'
222
252
  end
223
- end
224
253
 
225
- def self.unschedule_job(name)
226
- if self.scheduled_jobs[name]
227
- logger.debug "Removing schedule #{name}"
228
- self.scheduled_jobs[name].unschedule
229
- self.scheduled_jobs.delete(name)
254
+ # Returns true if the enqueuing needs to be done for an ActiveJob
255
+ # class false otherwise.
256
+ #
257
+ # @param [Class] klass the class to check is decendant from ActiveJob
258
+ #
259
+ # @return [Boolean]
260
+ def active_job_enqueue?(klass)
261
+ klass.is_a?(Class) && defined?(ActiveJob::Enqueuing) &&
262
+ klass.included_modules.include?(ActiveJob::Enqueuing)
230
263
  end
231
- end
232
264
 
233
- def self.enque_with_active_job(config)
234
- initialize_active_job(config['class'], config['args']).enqueue(config)
235
- end
265
+ # Convert the given arguments in the format expected to be enqueued.
266
+ #
267
+ # @param [Hash] config the options to be converted
268
+ # @option config [String] class the job class
269
+ # @option config [Hash/Array] args the arguments to be passed to the job
270
+ # class
271
+ #
272
+ # @return [Hash]
273
+ def prepare_arguments(config)
274
+ config['class'] = try_to_constantize(config['class'])
275
+
276
+ if config['args'].is_a?(Hash)
277
+ config['args'].symbolize_keys! if config['args'].respond_to?(:symbolize_keys!)
278
+ else
279
+ config['args'] = Array(config['args'])
280
+ end
236
281
 
237
- def self.enque_with_sidekiq(config)
238
- Sidekiq::Client.push(config)
239
- end
282
+ config
283
+ end
240
284
 
241
- def self.initialize_active_job(klass, args)
242
- if args.is_a?(Array)
243
- klass.new(*args)
244
- else
245
- klass.new(args)
285
+ def try_to_constantize(klass)
286
+ klass.is_a?(String) ? klass.constantize : klass
287
+ rescue NameError
288
+ klass
246
289
  end
247
- end
248
290
 
249
- # Returns true if the enqueuing needs to be done for an ActiveJob
250
- # class false otherwise.
251
- #
252
- # @param [Class] klass the class to check is decendant from ActiveJob
253
- #
254
- # @return [Boolean]
255
- def self.active_job_enqueue?(klass)
256
- defined?(ActiveJob::Enqueuing) && klass.included_modules.include?(ActiveJob::Enqueuing)
257
- end
291
+ # Returns true if a job's queue is being listened on by sidekiq
292
+ #
293
+ # @param [String] job_queue Job's queue name
294
+ #
295
+ # @return [Boolean]
296
+ def enabled_queue?(job_queue)
297
+ queues = Sidekiq.options[:queues]
258
298
 
259
- # Convert the given arguments in the format expected to be enqueued.
260
- #
261
- # @param [Hash] config the options to be converted
262
- # @option config [String] class the job class
263
- # @option config [Hash/Array] args the arguments to be passed to the job
264
- # class
265
- #
266
- # @return [Hash]
267
- def self.prepare_arguments(config)
268
- config['class'] = config['class'].constantize if config['class'].is_a?(String)
269
-
270
- if config['args'].is_a?(Hash)
271
- config['args'].symbolize_keys! if config['args'].respond_to?(:symbolize_keys!)
272
- else
273
- config['args'] = Array(config['args'])
299
+ queues.empty? || queues.include?(job_queue)
274
300
  end
275
301
 
276
- config
277
- end
302
+ # Registers a queued job instance
303
+ #
304
+ # @param [String] job_name The job's name
305
+ # @param [Time] time Time at which the job was cleared by the scheduler
306
+ #
307
+ # @return [Boolean] true if the job was registered, false when otherwise
308
+ def register_job_instance(job_name, time)
309
+ pushed_job_key = pushed_job_key(job_name)
310
+
311
+ registered, _ = Sidekiq.redis do |r|
312
+ r.pipelined do
313
+ r.zadd(pushed_job_key, time.to_i, time.to_i)
314
+ r.expire(pushed_job_key, REGISTERED_JOBS_THRESHOLD_IN_SECONDS)
315
+ end
316
+ end
317
+
318
+ registered
319
+ end
278
320
 
279
- # Returns true if a job's queue is being listened on by sidekiq
280
- #
281
- # @param [String] job_queue Job's queue name
282
- #
283
- # @return [Boolean]
284
- def self.enabled_queue?(job_queue)
285
- queues = Sidekiq.options[:queues]
321
+ def remove_elder_job_instances(job_name)
322
+ Sidekiq.redis do |r|
323
+ r.zremrangebyscore(pushed_job_key(job_name), 0, Time.now.to_i - REGISTERED_JOBS_THRESHOLD_IN_SECONDS)
324
+ end
325
+ end
286
326
 
287
- queues.empty? || queues.include?(job_queue)
288
- end
327
+ # Returns the key of the Redis sorted set used to store job enqueues
328
+ #
329
+ # @param [String] job_name The name of the job
330
+ #
331
+ # @return [String]
332
+ def pushed_job_key(job_name)
333
+ "sidekiq-scheduler:pushed:#{job_name}"
334
+ end
289
335
 
290
- # Registers a queued job instance
291
- #
292
- # @param [String] job_name The job's name
293
- # @param [Time] time Time at which the job was cleared by the scheduler
294
- #
295
- # @return [Boolean] true if the job was registered, false when otherwise
296
- def self.register_job_instance(job_name, time)
297
- pushed_job_key = pushed_job_key(job_name)
298
-
299
- registered, _ = Sidekiq.redis do |r|
300
- r.pipelined do
301
- r.zadd(pushed_job_key, time.to_i, time.to_i)
302
- r.expire(pushed_job_key, REGISTERED_JOBS_THRESHOLD_IN_SECONDS)
336
+ # Returns the key of the Redis hash for job's execution times hash
337
+ #
338
+ def next_times_key
339
+ 'sidekiq-scheduler:next_times'
340
+ end
341
+
342
+ private
343
+
344
+ def new_rufus_scheduler
345
+ Rufus::Scheduler.new(rufus_scheduler_options).tap do |scheduler|
346
+ scheduler.define_singleton_method(:on_post_trigger) do |job, triggered_time|
347
+ Sidekiq::Scheduler.update_job_next_time(job.tags[0], job.next_time)
348
+ end
303
349
  end
304
350
  end
305
351
 
306
- registered
307
- end
352
+ def new_job(name, interval_type, config, args)
353
+ opts = { :job => true, :tags => [name] }
308
354
 
309
- def self.remove_elder_job_instances(job_name)
310
- Sidekiq.redis do |r|
311
- r.zremrangebyscore(pushed_job_key(job_name), 0, Time.now.to_i - REGISTERED_JOBS_THRESHOLD_IN_SECONDS)
355
+ rufus_scheduler.send(interval_type, *args, opts) do |job, time|
356
+ idempotent_job_enqueue(name, time, sanitize_job_config(config))
357
+ end
358
+ end
359
+
360
+ def sanitize_job_config(config)
361
+ config.reject { |k, _| RUFUS_METADATA_KEYS.include?(k) }
312
362
  end
313
- end
314
363
 
315
- # Returns the key of the Redis sorted set used to store job enqueues
316
- #
317
- # @param [String] job_name The name of the job
318
- #
319
- # @return [String]
320
- def self.pushed_job_key(job_name)
321
- "sidekiq-scheduler:pushed:#{job_name}"
322
364
  end
323
365
  end
324
366
  end
@@ -6,4 +6,6 @@ cs:
6
6
  class: Třída
7
7
  queue: Fronta
8
8
  arguments: Argumenty
9
- enqueue_now: Zařadit nyní
9
+ enqueue_now: Zařadit nyní
10
+ next_time: Příště
11
+ no_next_time: no next execution for this job
@@ -6,4 +6,6 @@ en:
6
6
  class: Class
7
7
  queue: Queue
8
8
  arguments: Arguments
9
- enqueue_now: Enqueue now
9
+ enqueue_now: Enqueue now
10
+ next_time: Next Time
11
+ no_next_time: no next execution for this job
@@ -6,4 +6,6 @@ es:
6
6
  class: Clase
7
7
  queue: Cola
8
8
  arguments: Argumentos
9
- enqueue_now: Encolar ahora
9
+ enqueue_now: Encolar ahora
10
+ next_time: Próxima ejecución
11
+ no_next_time: esta tarea no se volverá a ejecutar
@@ -10,23 +10,25 @@
10
10
  <th><%= t('class') %></th>
11
11
  <th><%= t('queue') %></th>
12
12
  <th><%= t('arguments') %></th>
13
+ <th><%= t('next_time') %></th>
13
14
  <th></th>
14
15
  </tr>
15
16
  </thead>
16
17
 
17
18
  <tbody>
18
- <% @schedule.each do |name, job_spec| %>
19
+ <% @presented_jobs.each do |job| %>
19
20
  <tr>
20
- <td><%= name %></td>
21
- <td><%= job_spec['description'] %></td>
22
- <td><%= job_spec.fetch 'cron', job_spec['every'] %></td>
23
- <td><%= job_spec['class'] %></td>
21
+ <td><%= job.name %></td>
22
+ <td><%= job['description'] %></td>
23
+ <td><%= job.interval %></td>
24
+ <td><%= job['class'] %></td>
24
25
  <td>
25
- <a href="<%= root_path %>queues/<%= job_spec.fetch('queue', 'default') %>"><%= job_spec.fetch('queue', 'default') %></a>
26
+ <a href="<%= root_path %>queues/<%= job.queue %>"><%= job.queue %></a>
26
27
  </td>
27
- <td><%= job_spec['args'] %></td>
28
+ <td><%= job['args'] %></td>
29
+ <td><%= job.next_time || t('no_next_time') %></td>
28
30
  <td class="text-center">
29
- <a class="btn btn-warn btn-xs" href="<%= root_path %>recurring-jobs/<%= URI.escape(name) %>/enqueue">
31
+ <a class="btn btn-warn btn-xs" href="<%= root_path %>recurring-jobs/<%= URI.escape(job.name) %>/enqueue">
30
32
  <%= t('enqueue_now') %>
31
33
  </a>
32
34
  </td>
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sidekiq-scheduler
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.8
4
+ version: 2.0.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Morton Jonuschat
@@ -9,216 +9,216 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2016-07-06 00:00:00.000000000 Z
12
+ date: 2016-09-15 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: sidekiq
16
16
  requirement: !ruby/object:Gem::Requirement
17
17
  requirements:
18
- - - ">="
18
+ - - '>='
19
19
  - !ruby/object:Gem::Version
20
20
  version: '3'
21
21
  type: :runtime
22
22
  prerelease: false
23
23
  version_requirements: !ruby/object:Gem::Requirement
24
24
  requirements:
25
- - - ">="
25
+ - - '>='
26
26
  - !ruby/object:Gem::Version
27
27
  version: '3'
28
28
  - !ruby/object:Gem::Dependency
29
29
  name: redis
30
30
  requirement: !ruby/object:Gem::Requirement
31
31
  requirements:
32
- - - "~>"
32
+ - - ~>
33
33
  - !ruby/object:Gem::Version
34
34
  version: '3'
35
35
  type: :runtime
36
36
  prerelease: false
37
37
  version_requirements: !ruby/object:Gem::Requirement
38
38
  requirements:
39
- - - "~>"
39
+ - - ~>
40
40
  - !ruby/object:Gem::Version
41
41
  version: '3'
42
42
  - !ruby/object:Gem::Dependency
43
43
  name: rufus-scheduler
44
44
  requirement: !ruby/object:Gem::Requirement
45
45
  requirements:
46
- - - "~>"
46
+ - - ~>
47
47
  - !ruby/object:Gem::Version
48
48
  version: 3.1.8
49
49
  type: :runtime
50
50
  prerelease: false
51
51
  version_requirements: !ruby/object:Gem::Requirement
52
52
  requirements:
53
- - - "~>"
53
+ - - ~>
54
54
  - !ruby/object:Gem::Version
55
55
  version: 3.1.8
56
56
  - !ruby/object:Gem::Dependency
57
57
  name: multi_json
58
58
  requirement: !ruby/object:Gem::Requirement
59
59
  requirements:
60
- - - "~>"
60
+ - - ~>
61
61
  - !ruby/object:Gem::Version
62
62
  version: '1'
63
63
  type: :runtime
64
64
  prerelease: false
65
65
  version_requirements: !ruby/object:Gem::Requirement
66
66
  requirements:
67
- - - "~>"
67
+ - - ~>
68
68
  - !ruby/object:Gem::Version
69
69
  version: '1'
70
70
  - !ruby/object:Gem::Dependency
71
71
  name: tilt
72
72
  requirement: !ruby/object:Gem::Requirement
73
73
  requirements:
74
- - - ">="
74
+ - - '>='
75
75
  - !ruby/object:Gem::Version
76
76
  version: 1.4.0
77
77
  type: :runtime
78
78
  prerelease: false
79
79
  version_requirements: !ruby/object:Gem::Requirement
80
80
  requirements:
81
- - - ">="
81
+ - - '>='
82
82
  - !ruby/object:Gem::Version
83
83
  version: 1.4.0
84
84
  - !ruby/object:Gem::Dependency
85
85
  name: rake
86
86
  requirement: !ruby/object:Gem::Requirement
87
87
  requirements:
88
- - - "~>"
88
+ - - ~>
89
89
  - !ruby/object:Gem::Version
90
90
  version: '10.0'
91
91
  type: :development
92
92
  prerelease: false
93
93
  version_requirements: !ruby/object:Gem::Requirement
94
94
  requirements:
95
- - - "~>"
95
+ - - ~>
96
96
  - !ruby/object:Gem::Version
97
97
  version: '10.0'
98
98
  - !ruby/object:Gem::Dependency
99
99
  name: timecop
100
100
  requirement: !ruby/object:Gem::Requirement
101
101
  requirements:
102
- - - "~>"
102
+ - - ~>
103
103
  - !ruby/object:Gem::Version
104
104
  version: '0'
105
105
  type: :development
106
106
  prerelease: false
107
107
  version_requirements: !ruby/object:Gem::Requirement
108
108
  requirements:
109
- - - "~>"
109
+ - - ~>
110
110
  - !ruby/object:Gem::Version
111
111
  version: '0'
112
112
  - !ruby/object:Gem::Dependency
113
113
  name: mocha
114
114
  requirement: !ruby/object:Gem::Requirement
115
115
  requirements:
116
- - - "~>"
116
+ - - ~>
117
117
  - !ruby/object:Gem::Version
118
118
  version: '0'
119
119
  type: :development
120
120
  prerelease: false
121
121
  version_requirements: !ruby/object:Gem::Requirement
122
122
  requirements:
123
- - - "~>"
123
+ - - ~>
124
124
  - !ruby/object:Gem::Version
125
125
  version: '0'
126
126
  - !ruby/object:Gem::Dependency
127
127
  name: rspec
128
128
  requirement: !ruby/object:Gem::Requirement
129
129
  requirements:
130
- - - ">="
130
+ - - '>='
131
131
  - !ruby/object:Gem::Version
132
132
  version: '0'
133
133
  type: :development
134
134
  prerelease: false
135
135
  version_requirements: !ruby/object:Gem::Requirement
136
136
  requirements:
137
- - - ">="
137
+ - - '>='
138
138
  - !ruby/object:Gem::Version
139
139
  version: '0'
140
140
  - !ruby/object:Gem::Dependency
141
141
  name: mock_redis
142
142
  requirement: !ruby/object:Gem::Requirement
143
143
  requirements:
144
- - - "~>"
144
+ - - ~>
145
145
  - !ruby/object:Gem::Version
146
146
  version: '0'
147
147
  type: :development
148
148
  prerelease: false
149
149
  version_requirements: !ruby/object:Gem::Requirement
150
150
  requirements:
151
- - - "~>"
151
+ - - ~>
152
152
  - !ruby/object:Gem::Version
153
153
  version: '0'
154
154
  - !ruby/object:Gem::Dependency
155
155
  name: simplecov
156
156
  requirement: !ruby/object:Gem::Requirement
157
157
  requirements:
158
- - - "~>"
158
+ - - ~>
159
159
  - !ruby/object:Gem::Version
160
160
  version: '0'
161
161
  type: :development
162
162
  prerelease: false
163
163
  version_requirements: !ruby/object:Gem::Requirement
164
164
  requirements:
165
- - - "~>"
165
+ - - ~>
166
166
  - !ruby/object:Gem::Version
167
167
  version: '0'
168
168
  - !ruby/object:Gem::Dependency
169
169
  name: activejob
170
170
  requirement: !ruby/object:Gem::Requirement
171
171
  requirements:
172
- - - "<"
172
+ - - <
173
173
  - !ruby/object:Gem::Version
174
174
  version: '5'
175
175
  type: :development
176
176
  prerelease: false
177
177
  version_requirements: !ruby/object:Gem::Requirement
178
178
  requirements:
179
- - - "<"
179
+ - - <
180
180
  - !ruby/object:Gem::Version
181
181
  version: '5'
182
182
  - !ruby/object:Gem::Dependency
183
183
  name: coveralls
184
184
  requirement: !ruby/object:Gem::Requirement
185
185
  requirements:
186
- - - ">="
186
+ - - '>='
187
187
  - !ruby/object:Gem::Version
188
188
  version: '0'
189
189
  type: :development
190
190
  prerelease: false
191
191
  version_requirements: !ruby/object:Gem::Requirement
192
192
  requirements:
193
- - - ">="
193
+ - - '>='
194
194
  - !ruby/object:Gem::Version
195
195
  version: '0'
196
196
  - !ruby/object:Gem::Dependency
197
197
  name: rack-test
198
198
  requirement: !ruby/object:Gem::Requirement
199
199
  requirements:
200
- - - ">="
200
+ - - '>='
201
201
  - !ruby/object:Gem::Version
202
202
  version: '0'
203
203
  type: :development
204
204
  prerelease: false
205
205
  version_requirements: !ruby/object:Gem::Requirement
206
206
  requirements:
207
- - - ">="
207
+ - - '>='
208
208
  - !ruby/object:Gem::Version
209
209
  version: '0'
210
210
  - !ruby/object:Gem::Dependency
211
211
  name: sinatra
212
212
  requirement: !ruby/object:Gem::Requirement
213
213
  requirements:
214
- - - ">="
214
+ - - '>='
215
215
  - !ruby/object:Gem::Version
216
216
  version: '0'
217
217
  type: :development
218
218
  prerelease: false
219
219
  version_requirements: !ruby/object:Gem::Requirement
220
220
  requirements:
221
- - - ">="
221
+ - - '>='
222
222
  - !ruby/object:Gem::Version
223
223
  version: '0'
224
224
  description: Light weight job scheduling extension for Sidekiq that adds support for
@@ -229,19 +229,20 @@ executables: []
229
229
  extensions: []
230
230
  extra_rdoc_files: []
231
231
  files:
232
- - MIT-LICENSE
233
- - README.md
234
- - Rakefile
235
232
  - lib/sidekiq-scheduler.rb
236
- - lib/sidekiq-scheduler/manager.rb
233
+ - lib/sidekiq/scheduler.rb
237
234
  - lib/sidekiq-scheduler/schedule.rb
238
235
  - lib/sidekiq-scheduler/version.rb
236
+ - lib/sidekiq-scheduler/job_presenter.rb
237
+ - lib/sidekiq-scheduler/manager.rb
239
238
  - lib/sidekiq-scheduler/web.rb
240
- - lib/sidekiq/scheduler.rb
239
+ - web/views/recurring_jobs.erb
241
240
  - web/locales/cs.yml
242
241
  - web/locales/en.yml
243
242
  - web/locales/es.yml
244
- - web/views/recurring_jobs.erb
243
+ - MIT-LICENSE
244
+ - Rakefile
245
+ - README.md
245
246
  homepage: https://github.com/moove-it/sidekiq-scheduler
246
247
  licenses:
247
248
  - MIT
@@ -252,17 +253,17 @@ require_paths:
252
253
  - lib
253
254
  required_ruby_version: !ruby/object:Gem::Requirement
254
255
  requirements:
255
- - - ">="
256
+ - - '>='
256
257
  - !ruby/object:Gem::Version
257
258
  version: '0'
258
259
  required_rubygems_version: !ruby/object:Gem::Requirement
259
260
  requirements:
260
- - - ">="
261
+ - - '>='
261
262
  - !ruby/object:Gem::Version
262
263
  version: '0'
263
264
  requirements: []
264
265
  rubyforge_project:
265
- rubygems_version: 2.4.5
266
+ rubygems_version: 2.0.14.1
266
267
  signing_key:
267
268
  specification_version: 4
268
269
  summary: Light weight job scheduling extension for Sidekiq