simple_scheduler 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: []