delayed_cron 0.2.9 → 0.2.10

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: 5ff17f816e19f148c452438b48c45bbc1f9c30fc
4
- data.tar.gz: 7fcc1e2435d54c239893f281a10fd0561ad5f11f
3
+ metadata.gz: d7a2942e092055795c248a6eb82433c760c26733
4
+ data.tar.gz: 195e7f6a46052e687611a825afede8ab3d4ef4f5
5
5
  SHA512:
6
- metadata.gz: 4356a31a22fb9713486ccc55d77998d0487c27424c6bfe68b8bfee2124e31d52c112fa883216546e4dd0b4db16c1a20159c06349e56471f6f851202cb4b36a47
7
- data.tar.gz: d40f0c45bec851dc5e0985a50eee5b114ff409b137818377c80f822c7019a07f8da408fb54736fee6ce1763ce956191463c07b85734b51f6ba191234d1485bca
6
+ metadata.gz: 596b466d8708c44a8023995a0df0ff7c6fa87b2c74b1d2e42521f5671e6d1b41147525fde9e1bdaecfc981978efcadcbf503a01e49d3fc4f1243765586d60f6a
7
+ data.tar.gz: f2c640b98de5f67f384a54b7c493715b4b14a1a7024ad52c903bb103fc004810a1de8c33c1f259df4efb4ca571774de81d89a3ba15832022a68539c30d5e8fd2
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- ruby-2.0.0-p247
1
+ 2.2.5
data/CHANGES.md CHANGED
@@ -1,3 +1,8 @@
1
+ # 0.2.10
2
+
3
+ - Add ability to set a job time zone and also allow running a job at a specific minute of each hour.
4
+ - PR https://github.com/sellect/delayed_cron/pull/17
5
+
1
6
  # 0.2.9
2
7
 
3
8
  - fix scheduling
data/README.md CHANGED
@@ -23,12 +23,22 @@ class Product < ActiveRecord::Base
23
23
  # * this is an alternative to the cron_jobs array in config
24
24
  #
25
25
  # OPTIONS: *optional
26
- # - interval - override default_inteveral from setup
27
- # - at - set time of day the cron should be run, timezone and seconds are optional
28
- cron_job :some_method_to_run_as_cron, interval: 3.days, at: "00:00:00 -0400"
26
+ # - interval - override default_interval from setup
27
+ # - at - set time of day the cron should be run, timezone and seconds are optional
28
+ # - time_zone - override default time zone for this job
29
+ # - precision - set to :hourly to run job at an hourly interval instead of daily
30
+ #
31
+ # NOTE: only one of interval or at will be used, at will take precedence if
32
+ # both are specified
33
+ cron_job :some_method_to_run_as_cron, at: "00:00:00 -0400"
34
+ cron_job :some_hourly_job, at: "15:00", precision: :hourly
29
35
 
30
36
  def self.some_method_to_run_as_cron
31
- # this method will be run every 3 days at midnight
37
+ # this method will be run every every day at midnight
38
+ end
39
+
40
+ def self.some_hourly_job
41
+ # this method will be run hourly at fifteen minutes past the hour
32
42
  end
33
43
 
34
44
  ...
@@ -43,6 +53,11 @@ DelayedCron.setup do |config|
43
53
  # default interval to run cron jobs
44
54
  config.default_interval = 10.minutes
45
55
 
56
+ # default time zone for scheduling `:at` jobs
57
+ # accepts an ActiveSupport or TZInfo time zone name
58
+ config.default_time_zone = "Eastern Time (US & Canada)"
59
+
60
+
46
61
  # array of methods to run at the above configured interval
47
62
  config.cron_jobs = [
48
63
  "SomeClass.expensive_task", # will run at default interval
@@ -56,7 +71,7 @@ end
56
71
 
57
72
  - when using with sidekiq and rails there can be a config/initializer load order issue. Below is a fix to insure sidekiq is loaded first
58
73
  ```ruby
59
- Rails.application.config.after_initialize do
74
+ Rails.application.config.after_initialize do
60
75
  DelayedCron.setup do |config|
61
76
  ...
62
77
  end
data/lib/delayed_cron.rb CHANGED
@@ -4,7 +4,7 @@ require 'delayed_cron/railtie'
4
4
 
5
5
  module DelayedCron
6
6
 
7
- mattr_accessor :default_interval, :cron_jobs
7
+ mattr_accessor :default_interval, :default_time_zone, :cron_jobs
8
8
 
9
9
  class << self
10
10
 
@@ -17,15 +17,12 @@ module DelayedCron
17
17
 
18
18
  def define_cron_jobs
19
19
  return false unless cron_jobs.present?
20
+
20
21
  cron_jobs.each do |job|
21
- obj = job
22
- job_is_hash = job.is_a?(Hash)
23
- job = job_is_hash ? obj[:job] : job
24
- interval = job_is_hash ? obj[:interval] : default_interval
25
- options_at = job_is_hash ? obj[:at] : nil
26
- klass, name = job.split(".")
22
+ job = job.is_a?(Hash) ? job : { job: job }
23
+ klass, name = job[:job].split(".")
27
24
  # TODO: raise error if interval is not set
28
- options = timing_opts(interval, options_at)
25
+ options = timing_opts(job)
29
26
  DelayedCron.schedule(klass, name, options)
30
27
  end
31
28
  end
@@ -44,6 +41,10 @@ module DelayedCron
44
41
  schedule(klass, method_name, symbolized_options)
45
42
  end
46
43
 
44
+ def default_time_zone
45
+ @@default_time_zone || "Eastern Time (US & Canada)"
46
+ end
47
+
47
48
  end
48
49
 
49
50
  module Glue
@@ -0,0 +1,58 @@
1
+ module DelayedCron
2
+ class CronJob
3
+
4
+ attr_accessor :klass, :method_name
5
+
6
+ def initialize(options)
7
+ self.klass = options.delete(:klass)
8
+ self.method_name = options.delete(:method_name)
9
+
10
+ self.raw_options = options
11
+ end
12
+
13
+ def enqueue(processor)
14
+ schedule.each do |opts|
15
+ processor.enqueue_delayed_cron(klass, method_name, opts)
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ attr_accessor :raw_options
22
+
23
+ def schedule
24
+ return [raw_options] if raw_options[:at].blank?
25
+
26
+ Array.wrap(raw_options[:at]).map do |at_option|
27
+ interval_from_at(raw_options.merge(at: at_option))
28
+ end
29
+ end
30
+
31
+ def interval_from_at(options)
32
+ interval = convert_time_string_to_seconds_interval(options[:at], options[:time_zone])
33
+ options.merge(interval: interval)
34
+ end
35
+
36
+ def convert_time_string_to_seconds_interval(scheduled_time_string, zone_name)
37
+ zone_name ||= DelayedCron.default_time_zone
38
+ zone = Time.find_zone!(zone_name)
39
+
40
+ if hourly?
41
+ period_in_seconds = 60 * 60
42
+ scheduled_time_string = "%H:#{scheduled_time_string}"
43
+ else
44
+ period_in_seconds = 60 * 60 * 24
45
+ end
46
+
47
+ scheduled_time = zone.now.strftime("%Y-%m-%d #{scheduled_time_string}")
48
+ scheduled_time = zone.parse(scheduled_time)
49
+ scheduled_time += period_in_seconds if zone.now >= scheduled_time
50
+ scheduled_time.to_i - zone.now.to_i
51
+ end
52
+
53
+ def hourly?
54
+ raw_options[:precision].to_s == "hourly"
55
+ end
56
+
57
+ end
58
+ end
@@ -1,48 +1,20 @@
1
+ require 'delayed_cron/cron_job'
2
+
1
3
  module DelayedCron
2
4
  module Scheduling
3
5
 
4
6
  def schedule(klass, method_name, options)
5
- parsed_options = parse_options(options)
6
- if parsed_options.is_a?(Array)
7
- parsed_options.each do |opts|
8
- processor.enqueue_delayed_cron(klass, method_name, opts)
9
- end
10
- else
11
- processor.enqueue_delayed_cron(klass, method_name, parsed_options)
12
- end
13
- end
14
-
15
- def parse_options(options)
16
- original_options = options
17
- if at = options[:at]
18
- options = if at.is_a?(Array)
19
- at.map do |at_option|
20
- add_interval(original_options.merge(at: at_option))
21
- end
22
- else
23
- add_interval(options)
24
- end
25
- end
26
- options
27
- end
28
-
29
- def add_interval(options)
30
- options[:interval] = convert_time_string_to_seconds_interval(options[:at])
31
- options
32
- end
33
-
34
- def convert_time_string_to_seconds_interval(scheduled_time_string)
35
- day_in_seconds = 60 * 60 * 24
36
- scheduled_time = Time.now.strftime("%Y-%m-%d #{scheduled_time_string}")
37
- scheduled_time = DateTime.parse(scheduled_time, false).to_time
38
- scheduled_time += day_in_seconds if Time.now >= scheduled_time
39
- scheduled_time.to_i - Time.now.to_i
7
+ job = CronJob.new(options.merge(klass: klass, method_name: method_name))
8
+ job.enqueue(processor)
40
9
  end
41
10
 
42
- def timing_opts(interval, options_at)
43
- timing_opts = { interval: interval }
44
- timing_opts.merge!(at: options_at) if options_at.present?
45
- timing_opts
11
+ def timing_opts(job)
12
+ {
13
+ interval: job[:interval] || default_interval,
14
+ time_zone: job[:time_zone],
15
+ precision: job[:precision],
16
+ at: job[:at]
17
+ }.select { |_, value| !value.nil? }
46
18
  end
47
19
 
48
20
  end
@@ -1,3 +1,3 @@
1
1
  module DelayedCron
2
- VERSION = "0.2.9"
2
+ VERSION = "0.2.10"
3
3
  end
@@ -0,0 +1,139 @@
1
+ require "spec_helper"
2
+
3
+ module DelayedCron
4
+ RSpec.describe CronJob do
5
+ describe "#new" do
6
+ let(:job) { described_class.new(klass: "Foo", method_name: "bar") }
7
+
8
+ it "stores klass" do
9
+ expect(job.klass).to eq "Foo"
10
+ end
11
+
12
+ it "stores method_name" do
13
+ expect(job.method_name).to eq "bar"
14
+ end
15
+ end
16
+
17
+ describe "#enqueue" do
18
+ let(:job) { described_class.new(klass: "Foo", method_name: "bar", interval: 3600) }
19
+ let(:processor) { class_double(Jobs::Sidekiq) }
20
+
21
+ before { Timecop.freeze(Time.utc(2014, 1, 1, 12, 0, 0)) }
22
+
23
+ it "passes interval through for periodic jobs" do
24
+ job = described_class.new(klass: "Foo", method_name: "bar", interval: 3600)
25
+
26
+ expect(processor).to receive(:enqueue_delayed_cron)
27
+ .with("Foo", "bar", interval: 3600)
28
+
29
+ job.enqueue(processor)
30
+ end
31
+
32
+ it "converts :at to interval" do
33
+ job = described_class.new(klass: "Foo", method_name: "bar", interval: 3600, at: "10:00:00")
34
+
35
+ expect(processor).to receive(:enqueue_delayed_cron)
36
+ .with("Foo", "bar", interval: 10800, at: "10:00:00")
37
+
38
+ job.enqueue(processor)
39
+ end
40
+
41
+ it "enqueues multiple jobs from array of :at times" do
42
+ job = described_class.new(
43
+ klass: "Foo",
44
+ method_name: "bar",
45
+ interval: 3600,
46
+ at: ["10:00:00", "09:00:00"]
47
+ )
48
+
49
+ expect(processor).to receive(:enqueue_delayed_cron)
50
+ .with("Foo", "bar", interval: 10800, at: "10:00:00")
51
+ expect(processor).to receive(:enqueue_delayed_cron)
52
+ .with("Foo", "bar", interval: 7200, at: "09:00:00")
53
+
54
+ job.enqueue(processor)
55
+ end
56
+
57
+ it 'respects time zone parameter' do
58
+ job = described_class.new(
59
+ klass: "Foo",
60
+ method_name: "bar",
61
+ interval: 3600,
62
+ at: "13:00:00",
63
+ time_zone: "UTC"
64
+ )
65
+
66
+ expect(processor).to receive(:enqueue_delayed_cron)
67
+ .with("Foo", "bar", interval: 3600, at: "13:00:00", time_zone: "UTC")
68
+
69
+ job.enqueue(processor)
70
+ end
71
+ end
72
+
73
+ describe ".convert_time_string_to_seconds_interval" do
74
+ let(:job) { described_class.new({}) }
75
+
76
+ let(:next_occurrence) do
77
+ job.send(:convert_time_string_to_seconds_interval, scheduled_time, "Eastern Time (US & Canada)")
78
+ end
79
+ # Set Time.now to January 1, 2014 12:00:00 PM
80
+ before { Timecop.freeze(Time.utc(2014, 1, 1, 12, 0, 0)) }
81
+ context "next occurrence is today" do
82
+ let(:known_interval) { 21600 }
83
+ let(:scheduled_time) { "13:00:00 -0500" }
84
+ it "converts a time string to seconds" do
85
+ expect(next_occurrence).to be(known_interval)
86
+ end
87
+ end
88
+
89
+ context "next occurrence is tomorrow" do
90
+ let(:known_interval) { 14400 }
91
+ let(:scheduled_time) { "11:00:00 -0500" }
92
+ it "converts a time string to seconds" do
93
+ expect(next_occurrence).to be(known_interval)
94
+ end
95
+ end
96
+
97
+ context "with time zone" do
98
+ let(:known_interval) { 14400 }
99
+ let(:scheduled_time) { "11:00:00 Eastern Time (US & Canada)" }
100
+ it "converts a time string to seconds" do
101
+ expect(next_occurrence).to be(known_interval)
102
+ end
103
+ end
104
+
105
+ context "with DST" do
106
+ let(:known_interval) { 14400 }
107
+ let(:scheduled_time) { "11:00:00 -0500" }
108
+ it "converts a time string to seconds" do
109
+ Timecop.freeze(Time.utc(2014, 6, 1, 12, 0, 0))
110
+
111
+ expect(next_occurrence).to be(known_interval)
112
+ end
113
+ end
114
+
115
+ context "hourly interval" do
116
+ let(:job) { described_class.new(precision: :hourly) }
117
+ let(:known_interval) { 300 }
118
+
119
+ let(:scheduled_time) { "05:00 -0500" }
120
+ it "converts a time string to seconds" do
121
+ expect(next_occurrence).to be(known_interval)
122
+ end
123
+ end
124
+
125
+ context "next hour" do
126
+ let(:job) { described_class.new(precision: :hourly) }
127
+ let(:known_interval) { 300 }
128
+
129
+ let(:scheduled_time) { "00:00" }
130
+ it "converts a time string to seconds" do
131
+ Timecop.freeze(Time.utc(2014, 1, 1, 12, 55, 0))
132
+
133
+ expect(next_occurrence).to be(known_interval)
134
+ end
135
+ end
136
+
137
+ end
138
+ end
139
+ end
@@ -26,6 +26,17 @@ describe DelayedCron do
26
26
  expect(DelayedCron.default_interval).not_to be_nil
27
27
  end
28
28
 
29
+ it "should have a default_time_zone" do
30
+ setup(default_time_zone: "UTC")
31
+ expect(DelayedCron.default_time_zone).to eq "UTC"
32
+ end
33
+
34
+ it "should default TZ to Eastern Time when not specified" do
35
+ setup(cron_jobs: [{job: "Test"}])
36
+
37
+ expect(DelayedCron.default_time_zone).to eq "Eastern Time (US & Canada)"
38
+ end
39
+
29
40
  it "should have an array of cron_jobs" do
30
41
  setup(default_interval: 1.hour)
31
42
  expect(DelayedCron.cron_jobs).to be_an(Array)
@@ -9,61 +9,10 @@ describe DelayedCron::Scheduling do
9
9
  DelayedCron.schedule("SomeClass", "long_method", { interval: 1.hour })
10
10
  expect(DelayedCron.processor).to be_processed_in :cron_job
11
11
  expect(DelayedCron.processor.jobs.size).to eq(1)
12
- end
13
- end
14
-
15
- describe ".parse_options" do
16
- let(:at_string) { { interval: 1.day, at: "00:00:00 -0500" } }
17
- let(:at_array) { { interval: 1.day, at: ["00:00:00 -0500", "01:00:00 -0500"]} }
18
- let(:no_at) { { interval: 1.day } }
19
-
20
- it "parses options `at` option as string" do
21
- expect(DelayedCron.parse_options(at_string)[:at]).to eq("00:00:00 -0500")
22
- end
23
-
24
- it "parses options `at` option as array" do
25
- expected_options_array = [
26
- { interval: 123, at: "00:00:00 -0500" },
27
- { interval: 123, at: "01:00:00 -0500" }
28
- ]
29
- expect(DelayedCron.parse_options(at_array)[0][:at]).to eq("00:00:00 -0500")
30
- expect(DelayedCron.parse_options(at_array)[1][:at]).to eq("01:00:00 -0500")
31
- end
32
-
33
- it "does not change options if `at` is not present" do
34
- expect(DelayedCron.parse_options(no_at)).to eq(no_at)
35
- end
36
- end
37
-
38
- describe ".add_interval" do
39
- it 'adds an interval key and value to the options hash' do
40
- options = DelayedCron.add_interval(at: '12:00:00 -0500')
41
- expect(options).to include(:interval)
42
- end
43
- end
44
-
45
- describe ".convert_time_string_to_seconds_interval" do
46
- let(:next_occurrence) do
47
- DelayedCron.convert_time_string_to_seconds_interval(scheduled_time)
48
- end
49
- # Set Time.now to January 1, 2014 12:00:00 PM
50
- before { Timecop.freeze(Time.local(2014, 1, 1, 12, 0, 0)) }
51
- context "next occurrence is today" do
52
- let(:known_interval) { 3600 }
53
- let(:scheduled_time) { "13:00:00 -0500" }
54
- it "converts a time string to seconds" do
55
- expect(next_occurrence).to be(known_interval)
56
- end
57
- end
58
12
 
59
- context "next occurrence is tomorrow" do
60
- let(:known_interval) { 82800 }
61
- let(:scheduled_time) { "11:00:00 -0500" }
62
- it "converts a time string to seconds" do
63
- expect(next_occurrence).to be(known_interval)
64
- end
13
+ expect(DelayedCron.processor.jobs.last["args"])
14
+ .to eq(["SomeClass", "long_method", { "interval" => 1.hour.to_s }])
65
15
  end
66
-
67
16
  end
68
17
 
69
18
  describe ".timing_opts" do
@@ -74,10 +23,22 @@ describe DelayedCron::Scheduling do
74
23
 
75
24
  it "collects the timing options" do
76
25
  interval = { interval: 1.day }
77
- timing_opts = DelayedCron.timing_opts(options[:interval], options[:at])
26
+ timing_opts = DelayedCron.timing_opts(options)
78
27
  expect(timing_opts).to eq(options)
79
28
  expect(timing_opts).not_to eq(interval)
80
29
  end
30
+
31
+ it "passes time_zone through" do
32
+ options_with_zone = options.merge(time_zone: "UTC")
33
+ timing_opts = DelayedCron.timing_opts(options_with_zone)
34
+ expect(timing_opts).to eq(options_with_zone)
35
+ end
36
+
37
+ it "ignores unknown parameters" do
38
+ timing_opts = DelayedCron.timing_opts(foo: :bar)
39
+
40
+ expect(timing_opts).to_not have_key(:foo)
41
+ end
81
42
  end
82
43
 
83
44
  end
data/spec/spec_helper.rb CHANGED
@@ -21,6 +21,7 @@ end
21
21
  def setup(options)
22
22
  DelayedCron.setup do |config|
23
23
  config.default_interval = options[:default_interval]
24
+ config.default_time_zone = options[:default_time_zone]
24
25
  config.cron_jobs = options[:cron_jobs] || []
25
26
  end
26
27
  end
@@ -44,6 +45,6 @@ def build_class(class_name, name, options = {})
44
45
  end
45
46
 
46
47
  end
47
-
48
+
48
49
  klass
49
- end
50
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: delayed_cron
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.9
4
+ version: 0.2.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Grubbs
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-01-17 00:00:00.000000000 Z
11
+ date: 2017-06-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: delayed_job
@@ -181,6 +181,7 @@ files:
181
181
  - delayed_cron.gemspec
182
182
  - init.rb
183
183
  - lib/delayed_cron.rb
184
+ - lib/delayed_cron/cron_job.rb
184
185
  - lib/delayed_cron/jobs.rb
185
186
  - lib/delayed_cron/jobs/delayed_job.rb
186
187
  - lib/delayed_cron/jobs/resque.rb
@@ -190,6 +191,7 @@ files:
190
191
  - lib/delayed_cron/scheduling.rb
191
192
  - lib/delayed_cron/version.rb
192
193
  - rails/init.rb
194
+ - spec/cron_job_spec.rb
193
195
  - spec/delayed_cron_spec.rb
194
196
  - spec/jobs/sidekiq_spec.rb
195
197
  - spec/scheduling_spec.rb
@@ -218,6 +220,7 @@ signing_key:
218
220
  specification_version: 4
219
221
  summary: Run your cron jobs with sidekiq, delayed_job, resque, or sucker_punch.
220
222
  test_files:
223
+ - spec/cron_job_spec.rb
221
224
  - spec/delayed_cron_spec.rb
222
225
  - spec/jobs/sidekiq_spec.rb
223
226
  - spec/scheduling_spec.rb