kick_ahead 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 +7 -0
- data/.gitignore +8 -0
- data/.travis.yml +8 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +24 -0
- data/README.md +257 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/kick_ahead.gemspec +35 -0
- data/lib/kick_ahead/job.rb +49 -0
- data/lib/kick_ahead/version.rb +3 -0
- data/lib/kick_ahead.rb +98 -0
- metadata +112 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 59e2d5091b3c8f9f670398b61d29f1622316acb8
|
4
|
+
data.tar.gz: c55ea15fbd789c84a2a4e92a01e7619f4359c69c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 8da428f05bc52542d04eff590ac7189318c3788544945fdcce98cd66c3a45a8425dd2889816eb3b846ec0572c39020a6318c6682eb9debccfbd046e339a237f7
|
7
|
+
data.tar.gz: 4399934d5ea49829ba721e45acbf5be03f023e98ba4929c8b94d54794871f69ad6119d111f8eef306c4b9663b68787b65bd163f74029ffa438eee3c316685dec
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
kick_ahead (0.1.0)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: https://rubygems.org/
|
8
|
+
specs:
|
9
|
+
minitest (5.10.3)
|
10
|
+
rake (10.5.0)
|
11
|
+
timecop (0.9.1)
|
12
|
+
|
13
|
+
PLATFORMS
|
14
|
+
ruby
|
15
|
+
|
16
|
+
DEPENDENCIES
|
17
|
+
bundler (~> 1.16)
|
18
|
+
kick_ahead!
|
19
|
+
minitest (~> 5.0)
|
20
|
+
rake (~> 10.0)
|
21
|
+
timecop
|
22
|
+
|
23
|
+
BUNDLED WITH
|
24
|
+
1.16.0
|
data/README.md
ADDED
@@ -0,0 +1,257 @@
|
|
1
|
+
# KickAhead
|
2
|
+
|
3
|
+
Allows you to push code to be executed in the future. The code is organized in Jobs with the same API
|
4
|
+
sidekiq has:
|
5
|
+
|
6
|
+
```ruby
|
7
|
+
class MyJob < KickAhead::Job
|
8
|
+
def perform(some, args)
|
9
|
+
# Your code
|
10
|
+
end
|
11
|
+
end
|
12
|
+
```
|
13
|
+
|
14
|
+
```ruby
|
15
|
+
MyJob.run_in 3600, "some", "args"
|
16
|
+
MyJob.run_at Time.now + 2 * 3600, "some", "args"
|
17
|
+
```
|
18
|
+
|
19
|
+
This library is minimalist and the user must provide a persistent storage system as well as a polling
|
20
|
+
mechanism, that will be used to control the timings.
|
21
|
+
|
22
|
+
|
23
|
+
## Installation
|
24
|
+
|
25
|
+
Add this line to your application's Gemfile:
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
gem 'kick_ahead'
|
29
|
+
```
|
30
|
+
|
31
|
+
And then execute:
|
32
|
+
|
33
|
+
$ bundle
|
34
|
+
|
35
|
+
Or install it yourself as:
|
36
|
+
|
37
|
+
$ gem install kick_ahead
|
38
|
+
|
39
|
+
## Usage
|
40
|
+
|
41
|
+
|
42
|
+
First, you need to provide a polling mechanism that allows you to call `KickAhead.tick` at regular intervals.
|
43
|
+
This interval must be also configured at `KickAhead.tick_interval`. You application can call `tick` more
|
44
|
+
frequently than what you establish here, but never less frequently. If your polling source is not accurate
|
45
|
+
but have a predictable behavior / margin of error, configure the `tick_interval` to be your maximum possible
|
46
|
+
frequency even if your real polling source frequency is usually higher.
|
47
|
+
|
48
|
+
If you fail to call `tick` frequently enough, you may have dropped jobs (see below).
|
49
|
+
|
50
|
+
Also importantly, your call to `tick` must be atomic in your application. Only one `tick` must be in execution
|
51
|
+
at any given time. You must use some sort of locking mechanism to ensure this, like ruby's `Mutex#synchronize`
|
52
|
+
if you only have one process that may tick in your application, or some other application-wide lock mechanism
|
53
|
+
otherwise (i.e. using redis or postgres advisory locks).
|
54
|
+
|
55
|
+
You'll also need to provide a Repository object, to offer a persistent storage (see below in Configuration).
|
56
|
+
|
57
|
+
The basic idea is that you write code in Jobs, and then "push" those jobs to be executed at some point in the
|
58
|
+
future. Examples:
|
59
|
+
|
60
|
+
```ruby
|
61
|
+
MyJob.run_in 3600, "some", "args"
|
62
|
+
MyJob.run_at Time.now + 2 * 3600, "some_args"
|
63
|
+
```
|
64
|
+
|
65
|
+
This lib guarantees that your job will be executed between your given time and your given time + your configured
|
66
|
+
tick_interval at most. If you want more precision, you'll need to decrease your tick_interval.
|
67
|
+
|
68
|
+
If, for some reason, your polling fail and the `tick` method is not called for a long time, any job that
|
69
|
+
was configured to run during that time will not run as expected. On the next tick, we'll detect those
|
70
|
+
stale jobs and then the following may occur:
|
71
|
+
|
72
|
+
If the job is configured with a `tolerance` value, and we're still inside the tolerance period, the job will
|
73
|
+
still run normally.
|
74
|
+
|
75
|
+
Tolerance can be configured from the job class and can be different per job:
|
76
|
+
|
77
|
+
```ruby
|
78
|
+
class MyJob < KickAhead::Job
|
79
|
+
self.tolerance = 30.minutes
|
80
|
+
|
81
|
+
def perform(some, args)
|
82
|
+
# Your code
|
83
|
+
end
|
84
|
+
end
|
85
|
+
```
|
86
|
+
|
87
|
+
Or externally as well:
|
88
|
+
|
89
|
+
`MyJob.tolerance = 2.hours`
|
90
|
+
|
91
|
+
If tolerance is not satisfied, then the behavior depends on the `out_of_time_strategy` configured. This
|
92
|
+
can be configured on a per job basis and by default is "raise_exception". The options are:
|
93
|
+
|
94
|
+
- `raise_exception`: An exception will be raised. The job is kept in the repository, waiting for an external
|
95
|
+
action to correct the situation (remove the job, change it's schedule time, etc.). The exception gives
|
96
|
+
information about the job class and arguments. No further jobs will be executed until this is corrected.
|
97
|
+
|
98
|
+
- `ignore`: The out of time jobs will be simply deleted with no execution.
|
99
|
+
|
100
|
+
- `hook`: In this case, the method `out_of_time_hook` will be called on the job instance in the same way
|
101
|
+
the `perform` method would, giving you the change to do specific logics. The first argument, however, will be the
|
102
|
+
original scheduling time, so you can perform comparisons with this information.
|
103
|
+
|
104
|
+
Configure it with:
|
105
|
+
|
106
|
+
```ruby
|
107
|
+
class MyJob < KickAhead::Job
|
108
|
+
# self.out_of_time_strategy = :raise_exception
|
109
|
+
# self.out_of_time_strategy = :ignore
|
110
|
+
self.out_of_time_strategy = :hook
|
111
|
+
|
112
|
+
def perform(some, args)
|
113
|
+
# Your code
|
114
|
+
end
|
115
|
+
|
116
|
+
def out_of_time_hook(scheduled_at, some, args)
|
117
|
+
# Your code when out of time
|
118
|
+
end
|
119
|
+
end
|
120
|
+
```
|
121
|
+
|
122
|
+
In case an exception occurs inside your Job, nothing extraordinary will happen. Kick Ahead will not capture
|
123
|
+
the exception, any other possible jobs will not be processed.
|
124
|
+
|
125
|
+
Your jobs are expected to be quick. If a heavy work has to be done, delegate it to the background.
|
126
|
+
|
127
|
+
|
128
|
+
## Configuration
|
129
|
+
|
130
|
+
### Polling interval
|
131
|
+
|
132
|
+
`KickAhead.tick_interval = 3600`
|
133
|
+
|
134
|
+
This value must be set to the expected polling interval, in seconds. In the example, the polling frequency
|
135
|
+
is 1 hour. Your code is then expected to call:
|
136
|
+
|
137
|
+
`KickAhead.tick`
|
138
|
+
|
139
|
+
every hour.
|
140
|
+
|
141
|
+
|
142
|
+
### Repository
|
143
|
+
|
144
|
+
You're also expected to provide a repository to implement persistence over the data used to store jobs.
|
145
|
+
|
146
|
+
A repository is any object that responds to the following methods:
|
147
|
+
|
148
|
+
- `create(klass, schedule_at, *args)`: Used to create a new job. `klass` is a string representing the
|
149
|
+
job class, `schedule_at` is a datetime representing the moment in time when this is expected to be executed,
|
150
|
+
and `*args` is an expandable list of arguments (you can use json columns in postgres to store those easily).
|
151
|
+
|
152
|
+
This method is expected to return an identifier as a string, whatever you want that to be, so that it can be
|
153
|
+
used in the future to reference this job in the persistent repository.
|
154
|
+
|
155
|
+
- `each_job_in_the_past`: Returns a collection of job objects (hashes). In no particular order, but always
|
156
|
+
jobs that are in the past respect current time. Kick Ahead will then either execute or discard them depending
|
157
|
+
on the configurations.
|
158
|
+
|
159
|
+
A job is a hash with the following properties, example:
|
160
|
+
|
161
|
+
```ruby
|
162
|
+
{
|
163
|
+
id: "11",
|
164
|
+
job_class: "MyJob",
|
165
|
+
job_args: [1, "foo"],
|
166
|
+
scheduled_at: Time.new(2017, 1, 1, 1, 1, 1)
|
167
|
+
}
|
168
|
+
```
|
169
|
+
|
170
|
+
- id: The identifier you gave to the job
|
171
|
+
- job_class: same first argument of the `create` call.
|
172
|
+
- job_args: same third argument of the `create` call.
|
173
|
+
- scheduled_at: same second argument of the `create` call.
|
174
|
+
|
175
|
+
- `delete(id)`: Used to remove a job from the persistent storage. The given "id" is the identifier of the
|
176
|
+
job as returned by the `create` or `each_job_in_the_past` methods.
|
177
|
+
|
178
|
+
|
179
|
+
### Current time
|
180
|
+
|
181
|
+
Since this library is heavily based on time, it cannot make any assumption about how are you managing
|
182
|
+
the time in your application. Ruby's default behavior is to return the system time, but for a distributed
|
183
|
+
application that may run in different machines this is usually not desirable, and instead you should instead use
|
184
|
+
some other way to get the current time (i.e. rails `Time.current`).
|
185
|
+
|
186
|
+
Since this is a choice of the host app, you must also configure how KickAhead should get the current time by
|
187
|
+
providing a lambda to return it.
|
188
|
+
|
189
|
+
```ruby
|
190
|
+
KickAhead.current_time = -> { Time.current }
|
191
|
+
```
|
192
|
+
|
193
|
+
This way you can control what is considered to be the current time, and then make sure that this value is consistent
|
194
|
+
with the `each_job_in_the_past` method in the repository, so time comparisons work as expected.
|
195
|
+
|
196
|
+
|
197
|
+
### Repository example with ActiveRecord
|
198
|
+
|
199
|
+
```ruby
|
200
|
+
# create_table "kick_ahead_jobs", force: :cascade do |t|
|
201
|
+
# t.string "job_class", null: false
|
202
|
+
# t.jsonb "job_args", null: false
|
203
|
+
# t.datetime "scheduled_at", null: false
|
204
|
+
# t.index ["scheduled_at"], name: "index_kick_ahead_jobs_on_scheduled_at"
|
205
|
+
# end
|
206
|
+
class KickAheadJob < ActiveRecord::Base
|
207
|
+
end
|
208
|
+
|
209
|
+
module RepositoryExample
|
210
|
+
extend self
|
211
|
+
|
212
|
+
def each_job_in_the_past
|
213
|
+
KickAheadJob.where('scheduled_at <= ?', Time.current).find_each do |job|
|
214
|
+
yield(as_hash(job))
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
def create(klass, schedule_at, *args)
|
219
|
+
job = KickAheadJob.create! job_class: klass, job_args: args, scheduled_at: schedule_at
|
220
|
+
job.id
|
221
|
+
end
|
222
|
+
|
223
|
+
def delete(id)
|
224
|
+
KickAheadJob.find(id).delete
|
225
|
+
end
|
226
|
+
|
227
|
+
private
|
228
|
+
|
229
|
+
def as_hash(job)
|
230
|
+
{
|
231
|
+
id: job.id,
|
232
|
+
job_class: job.job_class,
|
233
|
+
job_args: job.job_args,
|
234
|
+
scheduled_at: job.scheduled_at
|
235
|
+
}
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
```
|
240
|
+
|
241
|
+
## FAQS
|
242
|
+
|
243
|
+
Q: What If I don't care about jobs executing out of time? I want the job to execute after time X, but after that, I
|
244
|
+
don't need to be specific (ej: fail if the time doesn't fit).
|
245
|
+
|
246
|
+
A: You can set the tolerance value to an incredible high value (ie: 300 years).
|
247
|
+
|
248
|
+
|
249
|
+
## Development
|
250
|
+
|
251
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
252
|
+
|
253
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
254
|
+
|
255
|
+
## Contributing
|
256
|
+
|
257
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/rogercampos/kick_ahead.
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "kick_ahead"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/kick_ahead.gemspec
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
lib = File.expand_path("../lib", __FILE__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require "kick_ahead/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "kick_ahead"
|
7
|
+
spec.version = KickAhead::VERSION
|
8
|
+
spec.authors = ["Roger Campos"]
|
9
|
+
spec.email = ["roger@rogercampos.com"]
|
10
|
+
|
11
|
+
spec.summary = %q{Push code to execute in the future, without dependencies}
|
12
|
+
spec.description = %q{Push code to execute in the future, without dependencies}
|
13
|
+
spec.homepage = "https://github.com/rogercampos/kick_ahead"
|
14
|
+
|
15
|
+
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
16
|
+
# to allow pushing to a single host or delete this section to allow pushing to any host.
|
17
|
+
if spec.respond_to?(:metadata)
|
18
|
+
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
19
|
+
else
|
20
|
+
raise "RubyGems 2.0 or newer is required to protect against " \
|
21
|
+
"public gem pushes."
|
22
|
+
end
|
23
|
+
|
24
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
25
|
+
f.match(%r{^(test|spec|features)/})
|
26
|
+
end
|
27
|
+
spec.bindir = "exe"
|
28
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
29
|
+
spec.require_paths = ["lib"]
|
30
|
+
|
31
|
+
spec.add_development_dependency "bundler", "~> 1.16"
|
32
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
33
|
+
spec.add_development_dependency "minitest", "~> 5.0"
|
34
|
+
spec.add_development_dependency "timecop"
|
35
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module KickAhead
|
4
|
+
class Job
|
5
|
+
# Simplified version extracted from ActiveSupport
|
6
|
+
def self.class_attribute(name)
|
7
|
+
define_singleton_method(name) { nil }
|
8
|
+
|
9
|
+
ivar = "@#{name}"
|
10
|
+
|
11
|
+
define_singleton_method("#{name}=") do |val|
|
12
|
+
singleton_class.class_eval do
|
13
|
+
define_method(name) { val }
|
14
|
+
end
|
15
|
+
|
16
|
+
if singleton_class?
|
17
|
+
class_eval do
|
18
|
+
define_method(name) do
|
19
|
+
if instance_variable_defined? ivar
|
20
|
+
instance_variable_get ivar
|
21
|
+
else
|
22
|
+
singleton_class.send name
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
val
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class_attribute :tolerance
|
32
|
+
class_attribute :out_of_time_strategy
|
33
|
+
|
34
|
+
self.tolerance = 0
|
35
|
+
self.out_of_time_strategy = :raise_exception
|
36
|
+
|
37
|
+
def self.run_in(delta, *args)
|
38
|
+
KickAhead.repository.create(name, KickAhead.current_time.call + delta, *args)
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.run_at(time, *args)
|
42
|
+
KickAhead.repository.create(name, time, *args)
|
43
|
+
end
|
44
|
+
|
45
|
+
def perform(*)
|
46
|
+
raise NotImplementedError
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
data/lib/kick_ahead.rb
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "kick_ahead/version"
|
4
|
+
require "kick_ahead/job"
|
5
|
+
|
6
|
+
module KickAhead
|
7
|
+
OutOfInterval = Class.new(StandardError)
|
8
|
+
NoTickIntervalConfigured = Class.new(RuntimeError)
|
9
|
+
|
10
|
+
RAISE_EXCEPTION_STRATEGY = :raise_exception
|
11
|
+
IGNORE_STRATEGY = :ignore
|
12
|
+
HOOK_STRATEGY = :hook
|
13
|
+
|
14
|
+
ALL_STRATEGIES = [RAISE_EXCEPTION_STRATEGY, IGNORE_STRATEGY, HOOK_STRATEGY].freeze
|
15
|
+
|
16
|
+
class << self
|
17
|
+
attr_accessor :tick_interval
|
18
|
+
attr_accessor :repository
|
19
|
+
attr_accessor :current_time
|
20
|
+
|
21
|
+
def tick
|
22
|
+
if tick_interval.nil?
|
23
|
+
raise NoTickIntervalConfigured, 'No tick_interval configured! Please set `KickAhead.tick_interval`'
|
24
|
+
end
|
25
|
+
|
26
|
+
if current_time.nil?
|
27
|
+
raise 'You must configure a way for me to know the current time!'
|
28
|
+
end
|
29
|
+
|
30
|
+
KickAhead.repository.each_job_in_the_past do |job|
|
31
|
+
if job[:scheduled_at] < KickAhead.current_time.call - tick_interval - constantize(job[:job_class]).tolerance
|
32
|
+
out_of_time_job(job)
|
33
|
+
else
|
34
|
+
run_job(job)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def run_job(job)
|
40
|
+
constantize(job[:job_class]).new.perform(*job[:job_args])
|
41
|
+
KickAhead.repository.delete(job[:id])
|
42
|
+
end
|
43
|
+
|
44
|
+
def out_of_time_job(job)
|
45
|
+
case constantize(job[:job_class]).out_of_time_strategy.to_sym
|
46
|
+
when RAISE_EXCEPTION_STRATEGY
|
47
|
+
raise OutOfInterval, "The job of class #{job[:job_class]} with args #{job[:job_args].inspect} "\
|
48
|
+
'was not possible to run because of an out of interval tick '\
|
49
|
+
"(we didn't receive a tick in time to run it) and it's maximum tolerance "\
|
50
|
+
'threshold is also overdue.'
|
51
|
+
|
52
|
+
when IGNORE_STRATEGY
|
53
|
+
KickAhead.repository.delete(job[:id])
|
54
|
+
|
55
|
+
when HOOK_STRATEGY
|
56
|
+
constantize(job[:job_class]).new.out_of_time_hook(job[:scheduled_at], *job[:job_args])
|
57
|
+
KickAhead.repository.delete(job[:id])
|
58
|
+
|
59
|
+
else
|
60
|
+
raise 'Invalid strategy'
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
# Extracted from activesupport/lib/active_support/inflector/methods.rb, line 258
|
67
|
+
def constantize(camel_cased_word)
|
68
|
+
names = camel_cased_word.split("::".freeze)
|
69
|
+
|
70
|
+
# Trigger a built-in NameError exception including the ill-formed constant in the message.
|
71
|
+
Object.const_get(camel_cased_word) if names.empty?
|
72
|
+
|
73
|
+
# Remove the first blank element in case of '::ClassName' notation.
|
74
|
+
names.shift if names.size > 1 && names.first.empty?
|
75
|
+
|
76
|
+
names.inject(Object) do |constant, name|
|
77
|
+
if constant == Object
|
78
|
+
constant.const_get(name)
|
79
|
+
else
|
80
|
+
candidate = constant.const_get(name)
|
81
|
+
next candidate if constant.const_defined?(name, false)
|
82
|
+
next candidate unless Object.const_defined?(name)
|
83
|
+
|
84
|
+
# Go down the ancestors to check if it is owned directly. The check
|
85
|
+
# stops when we reach Object or the end of ancestors tree.
|
86
|
+
constant = constant.ancestors.inject(constant) do |const, ancestor|
|
87
|
+
break const if ancestor == Object
|
88
|
+
break ancestor if ancestor.const_defined?(name, false)
|
89
|
+
const
|
90
|
+
end
|
91
|
+
|
92
|
+
# owner is in Object, so raise
|
93
|
+
constant.const_get(name, false)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
metadata
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: kick_ahead
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Roger Campos
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-11-29 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.16'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.16'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: minitest
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '5.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '5.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: timecop
|
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
|
+
description: Push code to execute in the future, without dependencies
|
70
|
+
email:
|
71
|
+
- roger@rogercampos.com
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- ".gitignore"
|
77
|
+
- ".travis.yml"
|
78
|
+
- Gemfile
|
79
|
+
- Gemfile.lock
|
80
|
+
- README.md
|
81
|
+
- Rakefile
|
82
|
+
- bin/console
|
83
|
+
- bin/setup
|
84
|
+
- kick_ahead.gemspec
|
85
|
+
- lib/kick_ahead.rb
|
86
|
+
- lib/kick_ahead/job.rb
|
87
|
+
- lib/kick_ahead/version.rb
|
88
|
+
homepage: https://github.com/rogercampos/kick_ahead
|
89
|
+
licenses: []
|
90
|
+
metadata:
|
91
|
+
allowed_push_host: https://rubygems.org
|
92
|
+
post_install_message:
|
93
|
+
rdoc_options: []
|
94
|
+
require_paths:
|
95
|
+
- lib
|
96
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
97
|
+
requirements:
|
98
|
+
- - ">="
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: '0'
|
101
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
102
|
+
requirements:
|
103
|
+
- - ">="
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
version: '0'
|
106
|
+
requirements: []
|
107
|
+
rubyforge_project:
|
108
|
+
rubygems_version: 2.5.2
|
109
|
+
signing_key:
|
110
|
+
specification_version: 4
|
111
|
+
summary: Push code to execute in the future, without dependencies
|
112
|
+
test_files: []
|