simple_scheduler 0.1.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 8d15cd2b0b48692188a8b470d258e667dae84d08
4
+ data.tar.gz: 20eca360c888dbf7e482377f8d78053e988906c5
5
+ SHA512:
6
+ metadata.gz: 452dae7f622c805289ade38235a8041d9ed23faeb9bd5a1acfa51577570afd07667dbb70d5d099c7630c9ca4fbe04245d80c53655f14464c50369bdb8747a52c
7
+ data.tar.gz: f1d49ad79d145d281ce24d4476c752c1d51ae8b37cba98e6e25830c0bc355a2b12e4c3b36a475a25f86a2456e74b0796c237ecae28f0eabd127ebf7d3ec79ac7
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2016 Brian Pattison
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,234 @@
1
+ # Simple Scheduler
2
+
3
+ [![Build Status](https://travis-ci.org/simplymadeapps/simple_scheduler.svg?branch=master)](https://travis-ci.org/simplymadeapps/simple_scheduler)
4
+ [![Code Climate](https://codeclimate.com/github/simplymadeapps/simple_scheduler/badges/gpa.svg)](https://codeclimate.com/github/simplymadeapps/simple_scheduler)
5
+ [![Yard Docs](http://img.shields.io/badge/yard-docs-blue.svg)](http://www.rubydoc.info/github/simplymadeapps/simple_scheduler/)
6
+
7
+ Simple Scheduler is a scheduling add-on that is designed to be used with
8
+ [Sidekiq](http://sidekiq.org) and
9
+ [Heroku Scheduler](https://elements.heroku.com/addons/scheduler). It
10
+ gives you the ability to **schedule tasks at any interval** without adding
11
+ a clock process. Heroku Scheduler only allows you to schedule tasks every 10 minutes,
12
+ every hour, or every day.
13
+
14
+ ## Requirements
15
+
16
+ You must be using:
17
+
18
+ - Rails 4.2+
19
+ - [Sidekiq](http://sidekiq.org)
20
+ - [Heroku Scheduler](https://elements.heroku.com/addons/scheduler)
21
+
22
+ Both Active Job and Sidekiq::Worker classes can be queued by the scheduler.
23
+
24
+ ## Installation
25
+
26
+ Add this line to your application's Gemfile:
27
+
28
+ ```ruby
29
+ gem "simple_scheduler"
30
+ ```
31
+
32
+ And then execute:
33
+
34
+ ```bash
35
+ $ bundle
36
+ ```
37
+
38
+ ## Getting Started
39
+
40
+ Create a configuration file `config/simple_scheduler.yml`:
41
+
42
+ ```yml
43
+ # Global configuration options and their defaults. These can also be set on each task.
44
+ queue_ahead: 360 # Number of minutes to queue jobs into the future
45
+ tz: nil # The application time zone will be used by default
46
+
47
+ # Runs once every 2 minutes
48
+ simple_task:
49
+ class: "SomeActiveJob"
50
+ every: "2.minutes"
51
+
52
+ # Runs once every day at 4:00 AM
53
+ overnight_task:
54
+ class: "SomeSidekiqWorker"
55
+ every: "1.day"
56
+ at: "4:00"
57
+
58
+ # Runs once every hour at the half hour
59
+ half_hour_task:
60
+ class: "HalfHourTask"
61
+ every: "30.minutes"
62
+ at: "*:30"
63
+
64
+ # Runs once every week on Saturdays at 12:00 AM
65
+ weekly_task:
66
+ class: "WeeklyJob"
67
+ every: "1.week"
68
+ at: "Sat 0:00"
69
+ tz: "America/Chicago"
70
+ ```
71
+
72
+ ### Task options
73
+
74
+ #### :class
75
+
76
+ The class name of the ActiveJob or Sidekiq::Worker. Your job or
77
+ worker class should accept the expected run time as a parameter
78
+ on the `perform` method.
79
+
80
+ #### :every
81
+
82
+ How frequently the task should be performed as an ActiveSupport duration definition.
83
+
84
+ ```ruby
85
+ 1.day
86
+ 5.days
87
+ 12.hours
88
+ 20.minutes
89
+ 1.week
90
+ ```
91
+
92
+ #### :at (optional)
93
+
94
+ This is the starting point for the `every` duration. If not given, the job will
95
+ run immediately when the configuration file is loaded for the first time and will
96
+ follow the `every` duration to determine future execution times.
97
+
98
+ Valid string formats/examples:
99
+
100
+ ```
101
+ 18:00
102
+ 3:30
103
+ **:00
104
+ *:30
105
+ Sun 2:00
106
+ [Sun|Mon|Tue|Wed|Thu|Fri|Sat] 00:00
107
+ ```
108
+
109
+ Add the rake task to Heroku Scheduler and set it to run every 10 minutes:
110
+
111
+ ```
112
+ rake simple_scheduler
113
+ ```
114
+
115
+ The file `config/simple_scheduler.yml` will be used by default, but it may be
116
+ useful to point to another configuration file in non-production environments.
117
+
118
+ ```
119
+ rake simple_scheduler["config/simple_scheduler.staging.yml"]
120
+ ```
121
+
122
+ ## Writing Your Jobs
123
+
124
+ Your Active Job or Sidekiq Worker must accept the task name and time stamp
125
+ as arguments. This is so they can be queued properly and so your job can
126
+ use the scheduled time to know when it was supposed to run vs the current time.
127
+
128
+ ```ruby
129
+ class ExampleJob < ActiveJob::Base
130
+ # @param task_name [String] This is the key used in the YAML file to define the task
131
+ # @param time [Integer] The epoch time for when the job was scheduled to be run
132
+ def perform(task_name, time)
133
+ puts task_name
134
+ puts Time.at(time)
135
+ end
136
+ end
137
+ ```
138
+
139
+ When writing your jobs, you need to account for any possible server downtime.
140
+ The most common downtime would be caused by Heroku's required daily restart.
141
+
142
+ To ensure that your tasks always run, the jobs are queued in advance and it's
143
+ possible the jobs may not be executed at the exact time that you configured
144
+ them to run. If there is extended downtime, your jobs may back up and there
145
+ is no guarantee of the order they will be executed when your worker process
146
+ comes back online.
147
+
148
+ Because there is no guarantee that the job is run at the exact time given in
149
+ the configuration, the time the job was expected to run will be passed to
150
+ the job so you can handle situations where the time it was run doesn't match
151
+ the time it was expected to run.
152
+
153
+ ### How It Works
154
+
155
+ Once the rake task is added to Heroku Scheduler, the Simple Scheduler library
156
+ will load the configuration file every 10 minutes, and ensure that each task
157
+ has jobs scheduled in the future be checking the `Sidekiq::ScheduledSet`.
158
+
159
+ A minimum of two jobs is always added to the scheduled set. By default all
160
+ jobs for the next six hours are queued in advance. This ensures that there is
161
+ always one job in the queue that can be used to determine the next run time,
162
+ even if one of the two was executed during the 10 minute scheduler wait time.
163
+
164
+ ### Server Downtime Example
165
+
166
+ If you're using a gem like [clockwork](https://github.com/Rykian/clockwork), there is no way for the clock process to
167
+ know that the task was never run. If your task is scheduled for `12:00:00`, your
168
+ clock process could possibly be restarted at `11:59:59` and your dyno might not
169
+ be available until `12:00:20`.
170
+
171
+ Simple Scheduler would have already enqueued the task hours before the task should actually
172
+ run, so you still have to worry about the worker dyno restarting, but when the worker
173
+ dyno becomes available, the enqueued task will be there and will be executed immediately.
174
+
175
+ ### Daily Digest Email Example
176
+
177
+ Here's an example of a daily digest email that needs to go out at 8:00 AM for
178
+ users in their local time zone. We need to run this every 15 minutes to handle
179
+ all time zone offsets.
180
+
181
+ config/simple_scheduler.yml:
182
+
183
+ ```yml
184
+ # Runs every hour starting at the top of the hour + every 15 minutes
185
+ daily_digest_task:
186
+ class: "DailyDigestEmailJob"
187
+ every: "15.minutes"
188
+ at: "*:00"
189
+ ```
190
+
191
+ app/jobs/daily_digest_email_job.rb:
192
+
193
+ ```ruby
194
+ class DailyDigestEmailJob < ApplicationJob
195
+ queue_as :default
196
+
197
+ # Called by Simple Scheduler and is given the scheduled time so decisions can be made
198
+ # based on when the job was scheduled to be run rather than when it was actually run.
199
+ # @param task_name [String] This is the key used in the YAML file to define the task
200
+ # @param time [Integer] The epoch time for when the job was scheduled to be run
201
+ def perform(task_name, time)
202
+ # Don't do this! This will be way too slow!
203
+ User.find_each do |user|
204
+ if user.digest_time == Time.at(time)
205
+ DigestMailer.daily(user).deliver_later
206
+ end
207
+ end
208
+ end
209
+ end
210
+ ```
211
+
212
+ app/models/user.rb:
213
+
214
+ ```ruby
215
+ class User < ApplicationRecord
216
+ # Returns the time the user's daily digest should be
217
+ # delivered today based on the user's time zone.
218
+ # @return [Time]
219
+ def digest_time
220
+ "8:00 AM".in_time_zone(self.time_zone)
221
+ end
222
+ end
223
+ ```
224
+
225
+ ## Contributing
226
+
227
+ 1. Fork it
228
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
229
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
230
+ 4. Push to the branch (`git push origin my-new-feature`)
231
+ 5. Create new Pull Request
232
+
233
+ ## License
234
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,13 @@
1
+ begin
2
+ require "bundler/setup"
3
+ rescue LoadError
4
+ puts "You must `gem install bundler` and `bundle install` to run rake tasks"
5
+ end
6
+
7
+ begin
8
+ require "rspec/core/rake_task"
9
+ RSpec::Core::RakeTask.new(:spec)
10
+ task default: :spec
11
+ rescue LoadError
12
+ puts "Could not load Rspec Rake task"
13
+ end
@@ -0,0 +1,10 @@
1
+ require "active_job"
2
+ require "sidekiq/api"
3
+ require_relative "./simple_scheduler/railtie"
4
+ require_relative "./simple_scheduler/scheduler_job"
5
+ require_relative "./simple_scheduler/task"
6
+ require_relative "./simple_scheduler/version"
7
+
8
+ # Module for scheduling jobs at specific times using Sidekiq.
9
+ module SimpleScheduler
10
+ end
@@ -0,0 +1,8 @@
1
+ module SimpleScheduler
2
+ # Load the rake task into the Rails app
3
+ class Railtie < Rails::Railtie
4
+ rake_tasks do
5
+ load File.join(File.dirname(__FILE__), "../tasks/simple_scheduler_tasks.rake")
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,68 @@
1
+ module SimpleScheduler
2
+ # Active Job class that queues jobs defined in the config file.
3
+ class SchedulerJob < ActiveJob::Base
4
+ # Load the global scheduler config from the YAML file.
5
+ # @param config_path [String]
6
+ def load_config(config_path)
7
+ @config = YAML.load_file(config_path)
8
+ @queue_ahead = @config["queue_ahead"] || Task::DEFAULT_QUEUE_AHEAD_MINUTES
9
+ @time_zone = @config["tz"] ? ActiveSupport::TimeZone.new(@config["tz"]) : Time.zone
10
+ @config.delete("queue_ahead")
11
+ @config.delete("tz")
12
+ end
13
+
14
+ # Accepts a file path to read the scheduler configuration.
15
+ # @param config_path [String]
16
+ def perform(config_path = nil)
17
+ config_path ||= "config/simple_scheduler.yml"
18
+ load_config(config_path)
19
+ queue_future_jobs
20
+ end
21
+
22
+ # Queue each of the future jobs into Sidekiq from the defined tasks.
23
+ def queue_future_jobs
24
+ tasks.each do |task|
25
+ new_run_times = task.future_run_times - task.existing_run_times
26
+ next if new_run_times.empty?
27
+
28
+ if task.job_class.included_modules.include?(Sidekiq::Worker)
29
+ queue_future_sidekiq_workers(task, new_run_times)
30
+ else
31
+ queue_future_active_jobs(task, new_run_times)
32
+ end
33
+ end
34
+ end
35
+
36
+ # The array of tasks loaded from the config YAML.
37
+ # @return [Array<SimpleScheduler::SchedulerJob]
38
+ def tasks
39
+ @config.map do |task_name, options|
40
+ task_params = options.symbolize_keys
41
+ task_params[:queue_ahead] ||= @queue_ahead
42
+ task_params[:name] = task_name
43
+ task_params[:tz] ||= @time_zone
44
+ Task.new(task_params)
45
+ end
46
+ end
47
+
48
+ private
49
+
50
+ # Queues jobs in the future using Active Job based on the task options.
51
+ # @param task [SimpleScheduler::Task]
52
+ # @param run_times [Array<Time>]
53
+ def queue_future_active_jobs(task, run_times)
54
+ run_times.each do |time|
55
+ task.job_class.set(wait_until: time).perform_later(task.name, time.to_i)
56
+ end
57
+ end
58
+
59
+ # Queues jobs in the future using Sidekiq based on the task options.
60
+ # @param task [SimpleScheduler::Task]
61
+ # @param run_times [Array<Time>]
62
+ def queue_future_sidekiq_workers(task, run_times)
63
+ run_times.each do |time|
64
+ task.job_class.perform_at(time, task.name, time.to_i)
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,127 @@
1
+ module SimpleScheduler
2
+ # Class for parsing each task in the scheduler config YAML file and returning
3
+ # the values needed to schedule the task in the future.
4
+ class Task
5
+ attr_accessor :at, :frequency, :job_class, :job_class_name, :name, :queue_ahead, :time_zone
6
+
7
+ AT_PATTERN = /(Sun|Mon|Tue|Wed|Thu|Fri|Sat)?\s?(?:\*{1,2}|(\d{1,2})):(\d{1,2})/
8
+ DAYS = %w(Sun Mon Tue Wed Thu Fri Sat).freeze
9
+ DEFAULT_QUEUE_AHEAD_MINUTES = 360
10
+
11
+ # Initializes a task by parsing the params so the task can be queued in the future.
12
+ # @param params [Hash]
13
+ # @option params [String] :class The class of the Active Job or Sidekiq Worker
14
+ # @option params [String] :every How frequently the job will be performed
15
+ # @option params [String] :at The starting time for the interval
16
+ # @option params [Integer] :queue_ahead The number of minutes that jobs should be queued in the future
17
+ # @option params [String] :task_name The name of the task as defined in the YAML config
18
+ # @option params [ActiveSupport::TimeZone] :tz The time zone to use when parsing the `at` option
19
+ def initialize(params)
20
+ validate_params!(params)
21
+ @at = params[:at]
22
+ @frequency = parse_frequency(params[:every])
23
+ @job_class_name = params[:class]
24
+ @job_class = @job_class_name.constantize
25
+ @queue_ahead = params[:queue_ahead] || DEFAULT_QUEUE_AHEAD_MINUTES
26
+ @name = params[:name] || @job_class_name
27
+ @time_zone = params[:tz] || Time.zone
28
+ end
29
+
30
+ # Returns an array of existing jobs matching the job class of the task.
31
+ # @return [Array<Sidekiq::SortedEntry>]
32
+ def existing_jobs
33
+ @existing_jobs ||= SimpleScheduler::Task.scheduled_set.select do |job|
34
+ job.display_class == @job_class_name && job.display_args[0] == @name
35
+ end.to_a
36
+ end
37
+
38
+ # Returns an array of existing future run times that have already been scheduled.
39
+ # @return [Array<Time>]
40
+ def existing_run_times
41
+ @existing_run_times ||= existing_jobs.map(&:at)
42
+ end
43
+
44
+ # Returns the very first time a job should be run for the scheduled task.
45
+ # @return [Time]
46
+ def first_run_time
47
+ first_run_time = first_run_day
48
+ change_hour = first_run_hour
49
+ change_hour += 1 if at_match[2].nil? && first_run_hour == now.hour && first_run_min < now.min
50
+ first_run_time = first_run_time.change(hour: change_hour, min: first_run_min)
51
+ first_run_time += at_match[1] ? 1.week : 1.day if now > first_run_time
52
+ first_run_time
53
+ end
54
+
55
+ # Returns an array Time objects for future run times based on
56
+ # the current time and the given minutes to look ahead.
57
+ # @return [Array<Time>]
58
+ def future_run_times
59
+ future_run_times = existing_run_times.dup
60
+ last_run_time = future_run_times.last || first_run_time - frequency
61
+ last_run_time = last_run_time.in_time_zone(@time_zone)
62
+
63
+ while future_run_times.length < 2 || ((last_run_time - now) / 1.minute) < @queue_ahead
64
+ last_run_time = frequency.from_now(last_run_time)
65
+ last_run_time = last_run_time.change(hour: first_run_hour, min: first_run_min) if at_match[2]
66
+ future_run_times << last_run_time
67
+ end
68
+
69
+ future_run_times
70
+ end
71
+
72
+ # Loads the scheduled jobs from Sidekiq once to avoid loading from
73
+ # Redis for each task when looking up existing scheduled jobs.
74
+ # @return [Sidekiq::ScheduledSet]
75
+ def self.scheduled_set
76
+ @scheduled_set ||= Sidekiq::ScheduledSet.new
77
+ end
78
+
79
+ private
80
+
81
+ def at_match
82
+ @at_match ||= AT_PATTERN.match(@at) || []
83
+ end
84
+
85
+ def first_run_day
86
+ return @first_run_day if @first_run_day
87
+
88
+ @first_run_day = now.beginning_of_day
89
+
90
+ # If no day of the week is given, return today
91
+ return @first_run_day unless first_run_wday
92
+
93
+ # Shift to the correct day of the week if given
94
+ add_days = first_run_wday - first_run_day.wday
95
+ add_days += 7 if first_run_day.wday > first_run_wday
96
+ @first_run_day += add_days.days
97
+ end
98
+
99
+ def first_run_hour
100
+ @first_run_hour ||= (at_match[2] || now.hour).to_i
101
+ end
102
+
103
+ def first_run_min
104
+ @first_run_min ||= (at_match[3] || now.min).to_i
105
+ end
106
+
107
+ def first_run_wday
108
+ @first_run_wday ||= DAYS.index(at_match[1])
109
+ end
110
+
111
+ def now
112
+ @now ||= @time_zone.now.beginning_of_minute
113
+ end
114
+
115
+ def parse_frequency(every_string)
116
+ split_duration = every_string.split(".")
117
+ frequency = split_duration[0].to_i
118
+ frequency_units = split_duration[1]
119
+ frequency.send(frequency_units)
120
+ end
121
+
122
+ def validate_params!(params)
123
+ raise ArgumentError, "Missing param `class` specifying the class of the job to run." unless params.key?(:class)
124
+ raise ArgumentError, "Missing param `every` specifying how often the job should run." unless params.key?(:every)
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,3 @@
1
+ module SimpleScheduler
2
+ VERSION = "0.1.0".freeze
3
+ end
@@ -0,0 +1,4 @@
1
+ desc "Queue future jobs defined using Simple Scheduler"
2
+ task :simple_scheduler, [:config_path] => [:environment] do |_, args|
3
+ SimpleScheduler::SchedulerJob.perform_now(args[:config_path])
4
+ end
metadata ADDED
@@ -0,0 +1,155 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: simple_scheduler
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Brian Pattison
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-12-07 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '4.2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '4.2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: sidekiq
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '4.2'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '4.2'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec-rails
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'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rubocop
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: simplecov
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: simplecov-rcov
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: timecop
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - '>='
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - '>='
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ description: |2
112
+ Simple Scheduler adds the ability to enhance Heroku Scheduler by using Sidekiq to queue
113
+ jobs in the future. This allows for defining specific run times (Ex: Every Sunday at 4 AM)
114
+ and running tasks more often than Heroku Scheduler's 10 minute limit.
115
+ email:
116
+ - brian@brianpattison.com
117
+ executables: []
118
+ extensions: []
119
+ extra_rdoc_files: []
120
+ files:
121
+ - lib/simple_scheduler/railtie.rb
122
+ - lib/simple_scheduler/scheduler_job.rb
123
+ - lib/simple_scheduler/task.rb
124
+ - lib/simple_scheduler/version.rb
125
+ - lib/simple_scheduler.rb
126
+ - lib/tasks/simple_scheduler_tasks.rake
127
+ - MIT-LICENSE
128
+ - Rakefile
129
+ - README.md
130
+ homepage: https://github.com/simplymadeapps/simple_scheduler
131
+ licenses:
132
+ - MIT
133
+ metadata: {}
134
+ post_install_message:
135
+ rdoc_options: []
136
+ require_paths:
137
+ - lib
138
+ required_ruby_version: !ruby/object:Gem::Requirement
139
+ requirements:
140
+ - - '>='
141
+ - !ruby/object:Gem::Version
142
+ version: '0'
143
+ required_rubygems_version: !ruby/object:Gem::Requirement
144
+ requirements:
145
+ - - '>='
146
+ - !ruby/object:Gem::Version
147
+ version: '0'
148
+ requirements: []
149
+ rubyforge_project:
150
+ rubygems_version: 2.0.14.1
151
+ signing_key:
152
+ specification_version: 4
153
+ summary: An enhancement for Heroku Scheduler + Sidekiq for scheduling jobs at specific
154
+ times.
155
+ test_files: []