sidekiq-scheduler 2.0.8 → 2.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
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