delayed_cron 0.2.9 → 0.2.10

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: 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