simple_scheduler 0.2.1 → 0.2.2

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