simple_scheduler 0.2.1 → 0.2.2

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: b71b4f5c3e484c52baefe6f0a7bb053190cdd0a0
4
- data.tar.gz: b24a48c76cd13c18c175bd6cf43dafd2beb45632
3
+ metadata.gz: 2fcaf4fe9556dbb85aec2ed64578416c97121157
4
+ data.tar.gz: 6cd57daf1dd3a2d8fe31c4eed3ba18904d2f4121
5
5
  SHA512:
6
- metadata.gz: ba009c0d2a0565a03ffcee90c92e69de5cec1aab98f22ec523cee894bc7d200c05229053bebfef02ff9283213002a21f1d1db96ac1aa5b91d5d762f9c349173d
7
- data.tar.gz: 2ee4e84837c33e00e3d6b2648446ee7cb6d4ecbb6462b20b6cd96acf41e2c26d655c9daa446749cc1a9857ca6c3d3e84de20335580da4b803b2fe0a7c1f0f3fa
6
+ metadata.gz: 2a6c2563947e0450d48f6d3729b3fe9746917056747894f8ad2d887c29b5a81bb2eb34a7d02785355c1b65a4bedf1fd97e6d303da1845eb9396e0bffb65707b2
7
+ data.tar.gz: 475b59cac94a988e02cb4eccd6f907b64221bb64c67184beefdc0d5089d104600b5598ceee675cf89ccea197f29380f2ec55e93b1e9b0dbc7eb80456a93b269b
@@ -1,5 +1,6 @@
1
1
  require "active_job"
2
2
  require "sidekiq/api"
3
+ require_relative "./simple_scheduler/at"
3
4
  require_relative "./simple_scheduler/future_job"
4
5
  require_relative "./simple_scheduler/railtie"
5
6
  require_relative "./simple_scheduler/scheduler_job"
@@ -0,0 +1,99 @@
1
+ module SimpleScheduler
2
+ # A Time class for parsing the :at option on a task into the first time it should run.
3
+ # Time.now
4
+ # # => 2016-12-09 08:24:11 -0600
5
+ # SimpleScheduler::At.new("*:30")
6
+ # # => 2016-12-09 08:30:00 -0600
7
+ # SimpleScheduler::At.new("1:00")
8
+ # # => 2016-12-10 01:00:00 -0600
9
+ # SimpleScheduler::At.new("Sun 0:00")
10
+ # # => 2016-12-11 01:00:00 -0600
11
+ class At < Time
12
+ AT_PATTERN = /(Sun|Mon|Tue|Wed|Thu|Fri|Sat)?\s?(?:\*{1,2}|(\d{1,2})):(\d{1,2})/
13
+ DAYS = %w(Sun Mon Tue Wed Thu Fri Sat).freeze
14
+
15
+ # Accepts a time string to determine when a task should be run for the first time.
16
+ # Valid formats:
17
+ # "18:00"
18
+ # "3:30"
19
+ # "**:00"
20
+ # "*:30"
21
+ # "Sun 2:00"
22
+ # "[Sun|Mon|Tue|Wed|Thu|Fri|Sat] 00:00"
23
+ # @param at [String] The formatted string for a task's run time
24
+ # @param time_zone [ActiveSupport::TimeZone] The time zone to parse the at time in
25
+ def initialize(at, time_zone = Time.zone)
26
+ @at = at
27
+ @time_zone = time_zone
28
+ super(parsed_time.year, parsed_time.month, parsed_time.day,
29
+ parsed_time.hour, parsed_time.min, parsed_time.sec, parsed_time.utc_offset)
30
+ end
31
+
32
+ # Always returns the specified hour.
33
+ # @return [Integer]
34
+ def hour
35
+ hour? ? at_hour : super
36
+ end
37
+
38
+ # Returns whether or not the hour was specified in the :at string.
39
+ # @return [Boolean]
40
+ def hour?
41
+ at_match[2].present?
42
+ end
43
+
44
+ private
45
+
46
+ def at_match
47
+ @at_match ||= AT_PATTERN.match(@at) || []
48
+ end
49
+
50
+ def at_hour
51
+ @at_hour ||= (at_match[2] || now.hour).to_i
52
+ end
53
+
54
+ def at_min
55
+ @at_min ||= (at_match[3] || now.min).to_i
56
+ end
57
+
58
+ def at_wday
59
+ @wday ||= DAYS.index(at_match[1])
60
+ end
61
+
62
+ def at_wday?
63
+ at_match[1].present?
64
+ end
65
+
66
+ def next_hour?
67
+ !hour? && at_hour == now.hour && at_min < now.min
68
+ end
69
+
70
+ def now
71
+ @now ||= @time_zone.now.beginning_of_minute
72
+ end
73
+
74
+ def parsed_day
75
+ parsed_day = now.beginning_of_day
76
+
77
+ # If no day of the week is given, return today
78
+ return parsed_day unless at_wday?
79
+
80
+ # Shift to the correct day of the week if given
81
+ add_days = at_wday - parsed_day.wday
82
+ add_days += 7 if parsed_day.wday > at_wday
83
+ parsed_day + add_days.days
84
+ end
85
+
86
+ # Returns the very first time a job should be run for the scheduled task.
87
+ # @return [Time]
88
+ def parsed_time
89
+ return @parsed_time if @parsed_time
90
+
91
+ @parsed_time = parsed_day
92
+ change_hour = at_hour
93
+ change_hour += 1 if next_hour?
94
+ @parsed_time = @parsed_time.change(hour: change_hour, min: at_min)
95
+ @parsed_time += at_wday? ? 1.week : 1.day if now > @parsed_time
96
+ @parsed_time
97
+ end
98
+ end
99
+ end
@@ -23,12 +23,7 @@ module SimpleScheduler
23
23
  @task = Task.new(task_params)
24
24
  @scheduled_time = Time.at(scheduled_time).in_time_zone(@task.time_zone)
25
25
  raise Expired if expired?
26
-
27
- if @task.job_class.included_modules.include?(Sidekiq::Worker)
28
- queue_sidekiq_worker
29
- else
30
- queue_active_job
31
- end
26
+ queue_task
32
27
  end
33
28
 
34
29
  private
@@ -62,21 +57,22 @@ module SimpleScheduler
62
57
  end
63
58
  end
64
59
 
65
- # Queue the job for immediate execution using Active Job.
66
- def queue_active_job
67
- if @task.job_class.instance_method(:perform).arity > 0
68
- @task.job_class.perform_later(@scheduled_time.to_i)
60
+ # The name of the method used to queue the task's job or worker.
61
+ # @return [Symbol]
62
+ def perform_method
63
+ if @task.job_class.included_modules.include?(Sidekiq::Worker)
64
+ :perform_async
69
65
  else
70
- @task.job_class.perform_later
66
+ :perform_later
71
67
  end
72
68
  end
73
69
 
74
- # Queue the job for immediate execution using Sidekiq.
75
- def queue_sidekiq_worker
70
+ # Queue the task with the scheduled time if the job allows.
71
+ def queue_task
76
72
  if @task.job_class.instance_method(:perform).arity > 0
77
- @task.job_class.perform_async(@scheduled_time.to_i)
73
+ @task.job_class.send(perform_method, @scheduled_time.to_i)
78
74
  else
79
- @task.job_class.perform_async
75
+ @task.job_class.send(perform_method)
80
76
  end
81
77
  end
82
78
  end
@@ -2,30 +2,13 @@ module SimpleScheduler
2
2
  # Class for parsing each task in the scheduler config YAML file and returning
3
3
  # the values needed to schedule the task in the future.
4
4
  #
5
- # @!attribute [r] at
6
- # @return [String] The starting time for the interval
7
- # @!attribute [r] expires_after
8
- # @return [String] The time between the scheduled and actual run time that should cause the job not to run
9
- # @!attribute [r] frequency
10
- # @return [ActiveSupport::Duration] How often the job will be run
11
5
  # @!attribute [r] job_class
12
- # @return [Class] The class of the job or worker
13
- # @!attribute [r] job_class_name
14
- # @return [String] The class name of the job or worker
15
- # @!attribute [r] name
16
- # @return [String] The name of the task as defined in the YAML config
6
+ # @return [Class] The class of the job or worker.
17
7
  # @!attribute [r] params
18
8
  # @return [Hash] The params used to create the task
19
- # @!attribute [r] queue_ahead
20
- # @return [String] The name of the task as defined in the YAML config
21
- # @!attribute [r] time_zone
22
- # @return [ActiveSupport::TimeZone] The time zone to use when parsing the `at` option
23
9
  class Task
24
- attr_reader :at, :expires_after, :frequency, :job_class, :job_class_name
25
- attr_reader :name, :params, :queue_ahead, :time_zone
10
+ attr_reader :job_class, :params
26
11
 
27
- AT_PATTERN = /(Sun|Mon|Tue|Wed|Thu|Fri|Sat)?\s?(?:\*{1,2}|(\d{1,2})):(\d{1,2})/
28
- DAYS = %w(Sun Mon Tue Wed Thu Fri Sat).freeze
29
12
  DEFAULT_QUEUE_AHEAD_MINUTES = 360
30
13
 
31
14
  # Initializes a task by parsing the params so the task can be queued in the future.
@@ -39,15 +22,19 @@ module SimpleScheduler
39
22
  # @option params [String] :tz The time zone to use when parsing the `at` option
40
23
  def initialize(params)
41
24
  validate_params!(params)
42
- @at = params[:at]
43
- @expires_after = params[:expires_after]
44
- @frequency = parse_frequency(params[:every])
45
- @job_class_name = params[:class]
46
- @job_class = @job_class_name.constantize
47
- @queue_ahead = params[:queue_ahead] || DEFAULT_QUEUE_AHEAD_MINUTES
48
- @name = params[:name]
49
- @params = params
50
- @time_zone = params[:tz] ? ActiveSupport::TimeZone.new(params[:tz]) : Time.zone
25
+ @params = params
26
+ end
27
+
28
+ # The task's first run time as a Time-like object.
29
+ # @return [SimpleScheduler::At]
30
+ def at
31
+ @at ||= At.new(@params[:at], time_zone)
32
+ end
33
+
34
+ # The time between the scheduled and actual run time that should cause the job not to run.
35
+ # @return [String]
36
+ def expires_after
37
+ @params[:expires_after]
51
38
  end
52
39
 
53
40
  # Returns an array of existing jobs matching the job class of the task.
@@ -66,15 +53,10 @@ module SimpleScheduler
66
53
  @existing_run_times ||= existing_jobs.map(&:at)
67
54
  end
68
55
 
69
- # Returns the very first time a job should be run for the scheduled task.
70
- # @return [Time]
71
- def first_run_time
72
- first_run_time = first_run_day
73
- change_hour = first_run_hour
74
- change_hour += 1 if at_match[2].nil? && first_run_hour == now.hour && first_run_min < now.min
75
- first_run_time = first_run_time.change(hour: change_hour, min: first_run_min)
76
- first_run_time += at_match[1] ? 1.week : 1.day if now > first_run_time
77
- first_run_time
56
+ # How often the job will be run.
57
+ # @return [ActiveSupport::Duration]
58
+ def frequency
59
+ @frequency ||= parse_frequency(@params[:every])
78
60
  end
79
61
 
80
62
  # Returns an array Time objects for future run times based on
@@ -82,60 +64,51 @@ module SimpleScheduler
82
64
  # @return [Array<Time>]
83
65
  def future_run_times
84
66
  future_run_times = existing_run_times.dup
85
- last_run_time = future_run_times.last || first_run_time - frequency
67
+ last_run_time = future_run_times.last || at - frequency
86
68
  last_run_time = last_run_time.in_time_zone(time_zone)
87
69
 
88
- while future_run_times.length < 2 || ((last_run_time - now) / 1.minute) < queue_ahead
70
+ # Ensure there are at least two future jobs scheduled and that the queue ahead time is filled
71
+ while future_run_times.length < 2 || ((last_run_time - Time.now) / 1.minute) < queue_ahead
89
72
  last_run_time = frequency.from_now(last_run_time)
90
- last_run_time = last_run_time.change(hour: first_run_hour, min: first_run_min) if at_match[2]
73
+ last_run_time = last_run_time.change(hour: at.hour, min: at.min) if at.hour?
91
74
  future_run_times << last_run_time
92
75
  end
93
76
 
94
77
  future_run_times
95
78
  end
96
79
 
97
- # Loads the scheduled jobs from Sidekiq once to avoid loading from
98
- # Redis for each task when looking up existing scheduled jobs.
99
- # @return [Sidekiq::ScheduledSet]
100
- def self.scheduled_set
101
- @scheduled_set ||= Sidekiq::ScheduledSet.new
80
+ # The class name of the job or worker.
81
+ # @return [String]
82
+ def job_class_name
83
+ @params[:class]
102
84
  end
103
85
 
104
- private
105
-
106
- def at_match
107
- @at_match ||= AT_PATTERN.match(@at) || []
86
+ # The name of the task as defined in the YAML config.
87
+ # @return [String]
88
+ def name
89
+ @params[:name]
108
90
  end
109
91
 
110
- def first_run_day
111
- return @first_run_day if @first_run_day
112
-
113
- @first_run_day = now.beginning_of_day
114
-
115
- # If no day of the week is given, return today
116
- return @first_run_day unless first_run_wday
117
-
118
- # Shift to the correct day of the week if given
119
- add_days = first_run_wday - first_run_day.wday
120
- add_days += 7 if first_run_day.wday > first_run_wday
121
- @first_run_day += add_days.days
122
- end
123
-
124
- def first_run_hour
125
- @first_run_hour ||= (at_match[2] || now.hour).to_i
92
+ # The number of minutes that jobs should be queued in the future.
93
+ # @return [Integer]
94
+ def queue_ahead
95
+ @queue_ahead ||= @params[:queue_ahead] || DEFAULT_QUEUE_AHEAD_MINUTES
126
96
  end
127
97
 
128
- def first_run_min
129
- @first_run_min ||= (at_match[3] || now.min).to_i
98
+ # The time zone to use when parsing the `at` option.
99
+ # @return [ActiveSupport::TimeZone]
100
+ def time_zone
101
+ @time_zone ||= params[:tz] ? ActiveSupport::TimeZone.new(params[:tz]) : Time.zone
130
102
  end
131
103
 
132
- def first_run_wday
133
- @first_run_wday ||= DAYS.index(at_match[1])
104
+ # Loads the scheduled jobs from Sidekiq once to avoid loading from
105
+ # Redis for each task when looking up existing scheduled jobs.
106
+ # @return [Sidekiq::ScheduledSet]
107
+ def self.scheduled_set
108
+ @scheduled_set ||= Sidekiq::ScheduledSet.new
134
109
  end
135
110
 
136
- def now
137
- @now ||= @time_zone.now.beginning_of_minute
138
- end
111
+ private
139
112
 
140
113
  def parse_frequency(every_string)
141
114
  split_duration = every_string.split(".")
@@ -145,9 +118,10 @@ module SimpleScheduler
145
118
  end
146
119
 
147
120
  def validate_params!(params)
148
- params[:name] ||= params[:class]
149
121
  raise ArgumentError, "Missing param `class` specifying the class of the job to run." unless params.key?(:class)
150
122
  raise ArgumentError, "Missing param `every` specifying how often the job should run." unless params.key?(:every)
123
+ @job_class = params[:class].constantize
124
+ params[:name] ||= params[:class]
151
125
  end
152
126
  end
153
127
  end
@@ -1,3 +1,3 @@
1
1
  module SimpleScheduler
2
- VERSION = "0.2.1".freeze
2
+ VERSION = "0.2.2".freeze
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: simple_scheduler
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brian Pattison
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-12-08 00:00:00.000000000 Z
11
+ date: 2016-12-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '4.2'
41
+ - !ruby/object:Gem::Dependency
42
+ name: appraisal
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: rspec-rails
43
57
  requirement: !ruby/object:Gem::Requirement
@@ -108,6 +122,7 @@ files:
108
122
  - README.md
109
123
  - Rakefile
110
124
  - lib/simple_scheduler.rb
125
+ - lib/simple_scheduler/at.rb
111
126
  - lib/simple_scheduler/future_job.rb
112
127
  - lib/simple_scheduler/railtie.rb
113
128
  - lib/simple_scheduler/scheduler_job.rb