From a9352f1a9d0adc4c74b943e700436a6fe2df4e86 Mon Sep 17 00:00:00 2001 From: Jeff Luckett Date: Wed, 22 May 2019 12:30:58 -0400 Subject: [PATCH 1/6] Adds cron syntax scheduling --- delayed_job_recurring.gemspec | 1 + lib/delayed/recurring_job.rb | 15 +++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/delayed_job_recurring.gemspec b/delayed_job_recurring.gemspec index acaea5f..3f7457e 100644 --- a/delayed_job_recurring.gemspec +++ b/delayed_job_recurring.gemspec @@ -25,4 +25,5 @@ Gem::Specification.new do |s| s.add_runtime_dependency 'delayed_job', '>= 3.0' s.add_runtime_dependency 'delayed_job_active_record' + s.add_runtime_dependency 'fugit', '~> 1.2.1' end diff --git a/lib/delayed/recurring_job.rb b/lib/delayed/recurring_job.rb index e87cb9a..008279b 100644 --- a/lib/delayed/recurring_job.rb +++ b/lib/delayed/recurring_job.rb @@ -6,6 +6,7 @@ module Delayed module RecurringJob def self.included(base) + require 'fugit' unless defined?(Fugit) base.extend(ClassMethods) base.class_eval do @@logger = Delayed::Worker.logger @@ -51,6 +52,7 @@ def schedule! options = {} end def next_run_time + @cron.next_time if defined? @cron && @cron times = @schedule_options[:run_at] times = [times] unless times.is_a? Array times = times.map{|time| parse_time(time, @schedule_options[:timezone])} @@ -114,6 +116,19 @@ def next_future_time(times) end module ClassMethods + + def cron(cronline = false) + return @cron if defined? @cron && cronline == false + return (@cron = nil) if cronline.nil? + + if cronline + @cron = Fugit.parse(cronline) + raise ArgumentError, 'Only cron and "natural language" syntax supported' unless @cron.is_a?(Fugit::Cron) + end + + @cron + end + def run_at(*times) if times.length == 0 @run_at || run_every.from_now From 62c7d641f41b775434c715c6b56e83b704ac4aca Mon Sep 17 00:00:00 2001 From: Jeff Luckett Date: Wed, 22 May 2019 12:56:15 -0400 Subject: [PATCH 2/6] Adds to 'inherited' --- lib/delayed/recurring_job.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/delayed/recurring_job.rb b/lib/delayed/recurring_job.rb index 008279b..d7f8db4 100644 --- a/lib/delayed/recurring_job.rb +++ b/lib/delayed/recurring_job.rb @@ -213,7 +213,7 @@ def scheduled?(options = {}) end def inherited(subclass) - [:@run_at, :@run_interval, :@tz, :@priority].each do |var| + [:@run_at, :@run_interval, :@tz, :@priority, :@cron].each do |var| next unless instance_variable_defined? var subclass.instance_variable_set var, self.instance_variable_get(var) subclass.instance_variable_set "#{var}_inherited", true From d6283330c1e989a69c5f376dbb9d42654227b626 Mon Sep 17 00:00:00 2001 From: Jeff Luckett Date: Wed, 22 May 2019 13:16:52 -0400 Subject: [PATCH 3/6] Set up cron when scheduled. --- lib/delayed/recurring_job.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/delayed/recurring_job.rb b/lib/delayed/recurring_job.rb index d7f8db4..7064680 100644 --- a/lib/delayed/recurring_job.rb +++ b/lib/delayed/recurring_job.rb @@ -31,6 +31,7 @@ def schedule! options = {} end @schedule_options = options.reverse_merge(@schedule_options || {}).reverse_merge( + cron: self.class.cron, run_at: self.class.run_at, timezone: self.class.timezone, run_interval: serialize_duration(self.class.run_every), @@ -52,7 +53,8 @@ def schedule! options = {} end def next_run_time - @cron.next_time if defined? @cron && @cron + return @schedule_options[:cron].next_time if @schedule_options[:cron].respond_to?(:next_time) + times = @schedule_options[:run_at] times = [times] unless times.is_a? Array times = times.map{|time| parse_time(time, @schedule_options[:timezone])} From 0eb2a224fad49fcb3f26326e8cdca765c458382f Mon Sep 17 00:00:00 2001 From: Jeff Luckett Date: Wed, 22 May 2019 14:17:30 -0400 Subject: [PATCH 4/6] Return string-ified time --- lib/delayed/recurring_job.rb | 39 +++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/lib/delayed/recurring_job.rb b/lib/delayed/recurring_job.rb index 7064680..382c559 100644 --- a/lib/delayed/recurring_job.rb +++ b/lib/delayed/recurring_job.rb @@ -23,7 +23,7 @@ def success end # Schedule this "repeating" job - def schedule! options = {} + def schedule!(options = {}) options = options.dup if run_every = options.delete(:run_every) @@ -53,17 +53,17 @@ def schedule! options = {} end def next_run_time - return @schedule_options[:cron].next_time if @schedule_options[:cron].respond_to?(:next_time) + return @schedule_options[:cron].next_time.to_s if @schedule_options[:cron].respond_to?(:next_time) times = @schedule_options[:run_at] times = [times] unless times.is_a? Array - times = times.map{|time| parse_time(time, @schedule_options[:timezone])} - times = times.map{|time| time.in_time_zone @schedule_options[:timezone]} if @schedule_options[:timezone] + times = times.map { |time| parse_time(time, @schedule_options[:timezone]) } + times = times.map { |time| time.in_time_zone @schedule_options[:timezone] } if @schedule_options[:timezone] interval = deserialize_duration(@schedule_options[:run_interval]) until next_time = next_future_time(times) - times.map!{ |time| time + interval } + times.map! { |time| time + interval } end # Update @schedule_options to avoid growing number of calculations each time @@ -72,13 +72,14 @@ def next_run_time next_time end - private + private + # We don't want the run_interval to be serialized as a number of seconds. # 1.day is not the same as 86400 (not all days are 86400 seconds long!) def serialize_duration(duration) case duration when ActiveSupport::Duration - {value: duration.value, parts: duration.parts} + { value: duration.value, parts: duration.parts } else duration end @@ -114,13 +115,12 @@ def get_timezone(zone) end def next_future_time(times) - times.select{|time| time > Time.now}.min + times.select { |time| time > Time.now }.min end module ClassMethods - def cron(cronline = false) - return @cron if defined? @cron && cronline == false + return @cron if defined?(@cron) && cronline == false return (@cron = nil) if cronline.nil? if cronline @@ -132,7 +132,7 @@ def cron(cronline = false) end def run_at(*times) - if times.length == 0 + if times.empty? @run_at || run_every.from_now else if @run_at_inherited @@ -169,7 +169,7 @@ def priority(priority = nil) end def queue(*args) - if args.length == 0 + if args.empty? @queue else @queue = args.first @@ -181,7 +181,7 @@ def jobs(options = {}) options = options.with_indifferent_access # Construct dynamic query with 'job_matching_param' if present - query = ["((handler LIKE ?) OR (handler LIKE ?))", "--- !ruby/object:#{name} %", "--- !ruby/object:#{name}\n%"] + query = ['((handler LIKE ?) OR (handler LIKE ?))', "--- !ruby/object:#{name} %", "--- !ruby/object:#{name}\n%"] if options[:job_matching_param].present? matching_key = options[:job_matching_param] matching_value = options[matching_key] @@ -195,7 +195,7 @@ def jobs(options = {}) # Remove all jobs for this schedule (Stop the schedule) def unschedule(options = {}) - jobs(options).each{|j| j.destroy} + jobs(options).each(&:destroy) end # Main interface to start this schedule (adds it to the jobs table). @@ -206,6 +206,7 @@ def schedule(options = {}) def schedule!(options = {}) return unless Delayed::Worker.delay_jobs + unschedule(options) new.schedule!(options) end @@ -215,20 +216,22 @@ def scheduled?(options = {}) end def inherited(subclass) - [:@run_at, :@run_interval, :@tz, :@priority, :@cron].each do |var| + %i[@run_at @run_interval @tz @priority @cron].each do |var| next unless instance_variable_defined? var - subclass.instance_variable_set var, self.instance_variable_get(var) + + subclass.instance_variable_set var, instance_variable_get(var) subclass.instance_variable_set "#{var}_inherited", true end end - private + private + def yaml_quote(value) # In order to ensure matching indentation, place the element inside a # two-level hash (the first level mimicking 'schedule_options', the second # for #{job_matching_param}), and strip out the leading "---\n:a:\n :a: " # but keep the trailing newline. - ({a: {a: value}}).to_yaml[14..-1] + { a: { a: value } }.to_yaml[14..-1] end end # ClassMethods end # RecurringJob From ae95c77bd1731669b2921c8269cafbd01b02fe2b Mon Sep 17 00:00:00 2001 From: Jeff Luckett Date: Wed, 22 May 2019 16:14:41 -0400 Subject: [PATCH 5/6] Remove rubocop reformatting --- lib/delayed/recurring_job.rb | 35 ++++++++++++++++------------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/lib/delayed/recurring_job.rb b/lib/delayed/recurring_job.rb index 382c559..740d648 100644 --- a/lib/delayed/recurring_job.rb +++ b/lib/delayed/recurring_job.rb @@ -23,7 +23,7 @@ def success end # Schedule this "repeating" job - def schedule!(options = {}) + def schedule! options = {} options = options.dup if run_every = options.delete(:run_every) @@ -57,13 +57,13 @@ def next_run_time times = @schedule_options[:run_at] times = [times] unless times.is_a? Array - times = times.map { |time| parse_time(time, @schedule_options[:timezone]) } - times = times.map { |time| time.in_time_zone @schedule_options[:timezone] } if @schedule_options[:timezone] + times = times.map{|time| parse_time(time, @schedule_options[:timezone])} + times = times.map{|time| time.in_time_zone @schedule_options[:timezone]} if @schedule_options[:timezone] interval = deserialize_duration(@schedule_options[:run_interval]) until next_time = next_future_time(times) - times.map! { |time| time + interval } + times.map!{ |time| time + interval } end # Update @schedule_options to avoid growing number of calculations each time @@ -72,14 +72,13 @@ def next_run_time next_time end - private - + private # We don't want the run_interval to be serialized as a number of seconds. # 1.day is not the same as 86400 (not all days are 86400 seconds long!) def serialize_duration(duration) case duration when ActiveSupport::Duration - { value: duration.value, parts: duration.parts } + {value: duration.value, parts: duration.parts} else duration end @@ -115,10 +114,11 @@ def get_timezone(zone) end def next_future_time(times) - times.select { |time| time > Time.now }.min + times.select{|time| time > Time.now}.min end module ClassMethods + def cron(cronline = false) return @cron if defined?(@cron) && cronline == false return (@cron = nil) if cronline.nil? @@ -132,7 +132,7 @@ def cron(cronline = false) end def run_at(*times) - if times.empty? + if times.length == 0 @run_at || run_every.from_now else if @run_at_inherited @@ -169,7 +169,7 @@ def priority(priority = nil) end def queue(*args) - if args.empty? + if args.length == 0 @queue else @queue = args.first @@ -181,7 +181,7 @@ def jobs(options = {}) options = options.with_indifferent_access # Construct dynamic query with 'job_matching_param' if present - query = ['((handler LIKE ?) OR (handler LIKE ?))', "--- !ruby/object:#{name} %", "--- !ruby/object:#{name}\n%"] + query = ["((handler LIKE ?) OR (handler LIKE ?))", "--- !ruby/object:#{name} %", "--- !ruby/object:#{name}\n%"] if options[:job_matching_param].present? matching_key = options[:job_matching_param] matching_value = options[matching_key] @@ -195,7 +195,7 @@ def jobs(options = {}) # Remove all jobs for this schedule (Stop the schedule) def unschedule(options = {}) - jobs(options).each(&:destroy) + jobs(options).each{|j| j.destroy} end # Main interface to start this schedule (adds it to the jobs table). @@ -206,7 +206,6 @@ def schedule(options = {}) def schedule!(options = {}) return unless Delayed::Worker.delay_jobs - unschedule(options) new.schedule!(options) end @@ -216,22 +215,20 @@ def scheduled?(options = {}) end def inherited(subclass) - %i[@run_at @run_interval @tz @priority @cron].each do |var| + [:@run_at, :@run_interval, :@tz, :@priority, :@cron].each do |var| next unless instance_variable_defined? var - - subclass.instance_variable_set var, instance_variable_get(var) + subclass.instance_variable_set var, self.instance_variable_get(var) subclass.instance_variable_set "#{var}_inherited", true end end - private - + private def yaml_quote(value) # In order to ensure matching indentation, place the element inside a # two-level hash (the first level mimicking 'schedule_options', the second # for #{job_matching_param}), and strip out the leading "---\n:a:\n :a: " # but keep the trailing newline. - { a: { a: value } }.to_yaml[14..-1] + ({a: {a: value}}).to_yaml[14..-1] end end # ClassMethods end # RecurringJob From 46b76f74f6729bff596cc9022a5a2b59b139551d Mon Sep 17 00:00:00 2001 From: Jeff Luckett Date: Thu, 23 May 2019 11:30:33 -0400 Subject: [PATCH 6/6] Fix travis build on olde rubies. --- .travis.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 49dea9e..7af856a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,6 +13,5 @@ matrix: - rvm: "2.5.3" gemfile: "gemfiles/delayed_job_3.gemfile" before_install: - # Travis bundler versions are quite out of date and can cause install errors - # see: https://github.com/rubygems/rubygems/issues/1419 - - "gem install bundler" + - gem uninstall -v '>= 2' -i $(rvm gemdir)@global -ax bundler || true + - gem install bundler -v '< 2'