backburner 1.4.1 → 1.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +6 -2
- data/CHANGELOG.md +4 -0
- data/README.md +5 -2
- data/backburner.gemspec +1 -1
- data/lib/backburner/connection.rb +1 -1
- data/lib/backburner/job.rb +23 -7
- data/lib/backburner/logger.rb +3 -3
- data/lib/backburner/version.rb +1 -1
- data/lib/backburner/worker.rb +1 -1
- data/lib/backburner/workers/threading.rb +48 -14
- data/test/fixtures/test_jobs.rb +18 -0
- data/test/job_test.rb +33 -8
- data/test/test_helper.rb +1 -1
- data/test/workers/forking_worker_test.rb +1 -1
- data/test/workers/simple_worker_test.rb +2 -2
- data/test/workers/threading_worker_test.rb +40 -6
- data/test/workers/threads_on_fork_worker_test.rb +1 -1
- metadata +8 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9dfd7f06d2b08cb31ed2cf463506169544e0920d
|
4
|
+
data.tar.gz: 5d7585331fa5ccc082901017e9149d9982cb9de3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 38f1deb4da6faef87915b8fca99d99209adf76c5059f3478a75883b38a5e7abf89a9c0c9cec439641530f2b3e272362bfb0b8cc3343b9c1f9fcabf256b5bee24
|
7
|
+
data.tar.gz: d881653ea35fa55df59d53c5e104bc6dea13d857cabebacd8ecb5ba8ff845b9e736263ac7d1c386bbd6ff1d062e9abe0137ff8cf995262accd7231c7e4dc5a0b
|
data/.travis.yml
CHANGED
@@ -2,6 +2,11 @@
|
|
2
2
|
rvm:
|
3
3
|
- 1.9.3
|
4
4
|
- 2.0.0
|
5
|
+
- 2.1
|
6
|
+
- 2.2
|
7
|
+
- 2.3
|
8
|
+
- 2.4
|
9
|
+
- 2.5
|
5
10
|
- rbx-2
|
6
11
|
before_install:
|
7
12
|
- curl -L https://github.com/kr/beanstalkd/archive/v1.9.tar.gz | tar xz -C /tmp
|
@@ -11,11 +16,10 @@ before_install:
|
|
11
16
|
- cd $TRAVIS_BUILD_DIR
|
12
17
|
- gem update --system
|
13
18
|
- gem update bundler
|
14
|
-
install:
|
15
|
-
- bundle install
|
16
19
|
matrix:
|
17
20
|
allow_failures:
|
18
21
|
- rvm: rbx-2
|
22
|
+
- rvm: 2.0.0
|
19
23
|
script:
|
20
24
|
- bundle exec rake test
|
21
25
|
gemfile: Gemfile
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Backburner
|
1
|
+
# Backburner [![Build Status](https://travis-ci.org/nesquena/backburner.svg?branch=master)](https://travis-ci.org/nesquena/backburner)
|
2
2
|
|
3
3
|
Backburner is a [beanstalkd](http://kr.github.com/beanstalkd/)-powered job queue that can handle a very high volume of jobs.
|
4
4
|
You create background jobs and place them on multiple work queues to be processed later.
|
@@ -34,7 +34,7 @@ The real question then is... "Why Beanstalk?".
|
|
34
34
|
|
35
35
|
Illya has an excellent blog post
|
36
36
|
[Scalable Work Queues with Beanstalk](http://www.igvita.com/2010/05/20/scalable-work-queues-with-beanstalk/) and
|
37
|
-
Adam Wiggins posted [an excellent comparison](http://adam.
|
37
|
+
Adam Wiggins posted [an excellent comparison](http://adam.herokuapp.com/past/2010/4/24/beanstalk_a_simple_and_fast_queueing_backend/).
|
38
38
|
|
39
39
|
You will quickly see that **beanstalkd** is an underrated but incredible project that is extremely well-suited as a job queue.
|
40
40
|
Significantly better suited for this task than Redis or a database. Beanstalk is a simple,
|
@@ -513,6 +513,9 @@ end
|
|
513
513
|
|
514
514
|
Now all backburner queue errors will appear on airbrake for deeper inspection.
|
515
515
|
|
516
|
+
If you wish to retry a job without logging an error (for example when handling transient issues in a cloud or service oriented environment),
|
517
|
+
simply raise a `Backburner::Job::RetryJob` error.
|
518
|
+
|
516
519
|
### Logging
|
517
520
|
|
518
521
|
Logging in backburner is rather simple. When a job is run, the log records that. When a job
|
data/backburner.gemspec
CHANGED
@@ -18,7 +18,7 @@ Gem::Specification.new do |s|
|
|
18
18
|
|
19
19
|
s.add_runtime_dependency 'beaneater', '~> 1.0'
|
20
20
|
s.add_runtime_dependency 'dante', '> 0.1.5'
|
21
|
-
s.add_runtime_dependency 'concurrent-ruby', '~> 1.0.1'
|
21
|
+
s.add_runtime_dependency 'concurrent-ruby', '~> 1.0', '>= 1.0.1'
|
22
22
|
|
23
23
|
s.add_development_dependency 'rake'
|
24
24
|
s.add_development_dependency 'minitest', '3.2.0'
|
@@ -34,7 +34,7 @@ module Backburner
|
|
34
34
|
def connected?
|
35
35
|
begin
|
36
36
|
!!(@beanstalk && @beanstalk.connection && @beanstalk.connection.connection && !@beanstalk.connection.connection.closed?) # Would be nice if beaneater provided a connected? method
|
37
|
-
rescue
|
37
|
+
rescue
|
38
38
|
false
|
39
39
|
end
|
40
40
|
end
|
data/lib/backburner/job.rb
CHANGED
@@ -7,6 +7,7 @@ module Backburner
|
|
7
7
|
class JobTimeout < RuntimeError; end
|
8
8
|
class JobNotFound < RuntimeError; end
|
9
9
|
class JobFormatInvalid < RuntimeError; end
|
10
|
+
class RetryJob < RuntimeError; end
|
10
11
|
|
11
12
|
attr_accessor :task, :body, :name, :args
|
12
13
|
|
@@ -43,10 +44,10 @@ module Backburner
|
|
43
44
|
#
|
44
45
|
def process
|
45
46
|
# Invoke before hook and stop if false
|
46
|
-
res = @hooks.invoke_hook_events(
|
47
|
+
res = @hooks.invoke_hook_events(job_name, :before_perform, *args)
|
47
48
|
return false unless res
|
48
49
|
# Execute the job
|
49
|
-
@hooks.around_hook_events(
|
50
|
+
@hooks.around_hook_events(job_name, :around_perform, *args) do
|
50
51
|
# We subtract one to ensure we timeout before beanstalkd does, except if:
|
51
52
|
# a) ttr == 0, to support never timing out
|
52
53
|
# b) ttr == 1, so that we don't accidentally set it to never time out
|
@@ -56,19 +57,19 @@ module Backburner
|
|
56
57
|
end
|
57
58
|
task.delete
|
58
59
|
# Invoke after perform hook
|
59
|
-
@hooks.invoke_hook_events(
|
60
|
+
@hooks.invoke_hook_events(job_name, :after_perform, *args)
|
60
61
|
rescue => e
|
61
|
-
@hooks.invoke_hook_events(
|
62
|
+
@hooks.invoke_hook_events(job_name, :on_failure, e, *args)
|
62
63
|
raise e
|
63
64
|
end
|
64
65
|
|
65
66
|
def bury
|
66
|
-
@hooks.invoke_hook_events(
|
67
|
+
@hooks.invoke_hook_events(job_name, :on_bury, *args)
|
67
68
|
task.bury
|
68
69
|
end
|
69
70
|
|
70
71
|
def retry(count, delay)
|
71
|
-
@hooks.invoke_hook_events(
|
72
|
+
@hooks.invoke_hook_events(job_name, :on_retry, count, delay, *args)
|
72
73
|
task.release(delay: delay)
|
73
74
|
end
|
74
75
|
|
@@ -80,11 +81,26 @@ module Backburner
|
|
80
81
|
# job_class # => NewsletterSender
|
81
82
|
#
|
82
83
|
def job_class
|
83
|
-
handler =
|
84
|
+
handler = try_job_class
|
84
85
|
raise(JobNotFound, self.name) unless handler
|
85
86
|
handler
|
86
87
|
end
|
87
88
|
|
89
|
+
# Attempts to return a constantized job name, otherwise reverts to the name string
|
90
|
+
#
|
91
|
+
# @example
|
92
|
+
# job_name # => "SomeUnknownJob"
|
93
|
+
def job_name
|
94
|
+
handler = try_job_class
|
95
|
+
handler ? handler : self.name
|
96
|
+
end
|
97
|
+
|
98
|
+
def try_job_class
|
99
|
+
constantize(self.name)
|
100
|
+
rescue NameError
|
101
|
+
nil
|
102
|
+
end
|
103
|
+
|
88
104
|
# Timeout job within specified block after given time.
|
89
105
|
#
|
90
106
|
# @example
|
data/lib/backburner/logger.rb
CHANGED
@@ -10,7 +10,7 @@ module Backburner
|
|
10
10
|
# Print out when a job is about to begin
|
11
11
|
def log_job_begin(name, args)
|
12
12
|
log_info "Work job #{name} with #{args.inspect}"
|
13
|
-
|
13
|
+
Thread.current[:job_started_at] = Time.now
|
14
14
|
end
|
15
15
|
|
16
16
|
# Print out when a job completed
|
@@ -24,7 +24,7 @@ module Backburner
|
|
24
24
|
|
25
25
|
# Returns true if the job logging started
|
26
26
|
def job_started_at
|
27
|
-
|
27
|
+
Thread.current[:job_started_at]
|
28
28
|
end
|
29
29
|
|
30
30
|
# Print a message to stdout
|
@@ -50,4 +50,4 @@ module Backburner
|
|
50
50
|
Backburner.configuration.logger
|
51
51
|
end
|
52
52
|
end
|
53
|
-
end
|
53
|
+
end
|
data/lib/backburner/version.rb
CHANGED
data/lib/backburner/worker.rb
CHANGED
@@ -140,7 +140,7 @@ module Backburner
|
|
140
140
|
rescue Backburner::Job::JobFormatInvalid => e
|
141
141
|
self.log_error self.exception_message(e)
|
142
142
|
rescue => e # Error occurred processing job
|
143
|
-
self.log_error self.exception_message(e)
|
143
|
+
self.log_error self.exception_message(e) unless e.is_a?(Backburner::Job::RetryJob)
|
144
144
|
|
145
145
|
unless job
|
146
146
|
self.log_error "Error occurred before we were able to assign a job. Giving up without retrying!"
|
@@ -3,9 +3,13 @@ require 'concurrent'
|
|
3
3
|
module Backburner
|
4
4
|
module Workers
|
5
5
|
class Threading < Worker
|
6
|
+
attr_accessor :self_read, :self_write, :exit_on_shutdown
|
7
|
+
|
8
|
+
@shutdown_timeout = 10
|
9
|
+
|
6
10
|
class << self
|
7
|
-
attr_accessor :shutdown
|
8
11
|
attr_accessor :threads_number
|
12
|
+
attr_accessor :shutdown_timeout
|
9
13
|
end
|
10
14
|
|
11
15
|
# Custom initializer just to set @tubes_data
|
@@ -13,6 +17,7 @@ module Backburner
|
|
13
17
|
@tubes_data = {}
|
14
18
|
super
|
15
19
|
self.process_tube_options
|
20
|
+
@exit_on_shutdown = true
|
16
21
|
end
|
17
22
|
|
18
23
|
# Used to prepare job queues before processing jobs.
|
@@ -48,16 +53,19 @@ module Backburner
|
|
48
53
|
connection.on_reconnect = lambda { |conn| conn.tubes.watch!(tube_name) }
|
49
54
|
|
50
55
|
# Make it work jobs using its own connection per thread
|
51
|
-
pool.post(connection)
|
52
|
-
|
56
|
+
pool.post(connection) do |memo_connection|
|
57
|
+
# TODO: use read-write lock?
|
58
|
+
loop do
|
53
59
|
begin
|
54
|
-
|
55
|
-
|
60
|
+
break if @in_shutdown
|
61
|
+
work_one_job(memo_connection)
|
56
62
|
rescue => e
|
57
63
|
log_error("Exception caught in thread pool loop. Continuing. -> #{e.message}\nBacktrace: #{e.backtrace}")
|
58
64
|
end
|
59
|
-
|
60
|
-
|
65
|
+
end
|
66
|
+
|
67
|
+
connection.close
|
68
|
+
end
|
61
69
|
end
|
62
70
|
end
|
63
71
|
|
@@ -111,18 +119,44 @@ module Backburner
|
|
111
119
|
|
112
120
|
# Wait for the shutdown signel
|
113
121
|
def wait_for_shutdown!
|
114
|
-
while
|
115
|
-
|
122
|
+
raise Interrupt while IO.select([self_read])
|
123
|
+
rescue Interrupt
|
124
|
+
shutdown
|
125
|
+
end
|
126
|
+
|
127
|
+
def shutdown_threadpools
|
128
|
+
@thread_pools.each { |_name, pool| pool.shutdown }
|
129
|
+
shutdown_time = Time.now
|
130
|
+
@in_shutdown = true
|
131
|
+
all_shutdown = @thread_pools.all? do |_name, pool|
|
132
|
+
time_to_wait = self.class.shutdown_timeout - (Time.now - shutdown_time).to_i
|
133
|
+
pool.wait_for_termination(time_to_wait) if time_to_wait > 0
|
116
134
|
end
|
135
|
+
rescue Interrupt
|
136
|
+
log_info "graceful shutdown aborted, shutting down immediately"
|
137
|
+
ensure
|
138
|
+
kill unless all_shutdown
|
139
|
+
end
|
117
140
|
|
118
|
-
|
119
|
-
|
120
|
-
@thread_pools.each { |name, pool| pool.kill }
|
141
|
+
def kill
|
142
|
+
@thread_pools.each { |_name, pool| pool.kill unless pool.shutdown? }
|
121
143
|
end
|
122
144
|
|
123
145
|
def shutdown
|
124
|
-
|
125
|
-
|
146
|
+
log_info "beginning graceful worker shutdown"
|
147
|
+
shutdown_threadpools
|
148
|
+
super if @exit_on_shutdown
|
149
|
+
end
|
150
|
+
|
151
|
+
# Registers signal handlers TERM and INT to trigger
|
152
|
+
def register_signal_handlers!
|
153
|
+
@self_read, @self_write = IO.pipe
|
154
|
+
%w[TERM INT].each do |sig|
|
155
|
+
trap(sig) do
|
156
|
+
raise Interrupt if @in_shutdown
|
157
|
+
self_write.puts(sig)
|
158
|
+
end
|
159
|
+
end
|
126
160
|
end
|
127
161
|
end # Threading
|
128
162
|
end # Workers
|
data/test/fixtures/test_jobs.rb
CHANGED
@@ -13,6 +13,24 @@ class TestJob
|
|
13
13
|
def self.perform(x, y); $worker_test_count += x + y; end
|
14
14
|
end
|
15
15
|
|
16
|
+
class TestSlowJob
|
17
|
+
include Backburner::Queue
|
18
|
+
queue_priority :medium
|
19
|
+
queue_respond_timeout 300
|
20
|
+
def self.perform(x, y); sleep 1; $worker_test_count += x + y; end
|
21
|
+
end
|
22
|
+
|
23
|
+
class TestStuckJob
|
24
|
+
include Backburner::Queue
|
25
|
+
queue_priority :medium
|
26
|
+
queue_respond_timeout 300
|
27
|
+
def self.perform(_x, _y)
|
28
|
+
loop do
|
29
|
+
sleep 0.5
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
16
34
|
class TestFailJob
|
17
35
|
include Backburner::Queue
|
18
36
|
def self.perform(x, y); raise RuntimeError; end
|
data/test/job_test.rb
CHANGED
@@ -113,16 +113,41 @@ describe "Backburner::Job module" do
|
|
113
113
|
end # process
|
114
114
|
|
115
115
|
describe "for simple delegation method" do
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
116
|
+
describe "with valid class" do
|
117
|
+
before do
|
118
|
+
@task_body = { "class" => "NestedDemo::TestJobC", "args" => [56] }
|
119
|
+
@task = stub(:body => @task_body, :ttr => 120, :delete => true, :bury => true)
|
120
|
+
@task.expects(:bury).once
|
121
|
+
end
|
122
|
+
|
123
|
+
it "should call bury for task" do
|
124
|
+
@job = Backburner::Job.new(@task)
|
125
|
+
@job.bury
|
126
|
+
end # bury
|
120
127
|
end
|
121
128
|
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
129
|
+
describe "with invalid class" do
|
130
|
+
before do
|
131
|
+
@task_body = { "class" => "AnUnknownClass", "args" => [] }
|
132
|
+
@task = stub(:body => @task_body, :ttr => 120, :delete => true, :bury => true, :release => true)
|
133
|
+
end
|
134
|
+
|
135
|
+
it "should call bury for task" do
|
136
|
+
@task.expects(:bury).once
|
137
|
+
@job = Backburner::Job.new(@task)
|
138
|
+
Backburner::Hooks.expects(:invoke_hook_events)
|
139
|
+
.with("AnUnknownClass", :on_bury, anything)
|
140
|
+
@job.bury
|
141
|
+
end
|
142
|
+
|
143
|
+
it "should call retry for task" do
|
144
|
+
@task.expects(:release).once
|
145
|
+
@job = Backburner::Job.new(@task)
|
146
|
+
Backburner::Hooks.expects(:invoke_hook_events)
|
147
|
+
.with("AnUnknownClass", :on_retry, 0, is_a(Integer), anything)
|
148
|
+
@job.retry(0, 0)
|
149
|
+
end
|
150
|
+
end
|
126
151
|
end # simple delegation
|
127
152
|
|
128
153
|
describe "timing out for various values of ttr" do
|
data/test/test_helper.rb
CHANGED
@@ -11,7 +11,7 @@ describe "Backburner::Workers::Forking module" do
|
|
11
11
|
describe "for prepare method" do
|
12
12
|
it "should make tube names array always unique to avoid duplication" do
|
13
13
|
worker = @worker_class.new(["foo", "demo.test.foo"])
|
14
|
-
worker.prepare
|
14
|
+
capture_stdout { worker.prepare }
|
15
15
|
assert_equal ["demo.test.foo"], worker.tube_names
|
16
16
|
end
|
17
17
|
|
@@ -11,7 +11,7 @@ describe "Backburner::Workers::Simple module" do
|
|
11
11
|
describe "for prepare method" do
|
12
12
|
it "should make tube names array always unique to avoid duplication" do
|
13
13
|
worker = @worker_class.new(["foo", "demo.test.foo"])
|
14
|
-
worker.prepare
|
14
|
+
capture_stdout { worker.prepare }
|
15
15
|
assert_equal ["demo.test.foo"], worker.tube_names
|
16
16
|
end
|
17
17
|
|
@@ -309,7 +309,7 @@ describe "Backburner::Workers::Simple module" do
|
|
309
309
|
worker = @worker_class.new('foo.bar')
|
310
310
|
connection = mock('connection')
|
311
311
|
worker.expects(:reserve_job).with(connection).returns(stub_everything('job'))
|
312
|
-
worker.work_one_job(connection)
|
312
|
+
capture_stdout { worker.work_one_job(connection) }
|
313
313
|
end
|
314
314
|
|
315
315
|
after do
|
@@ -6,24 +6,25 @@ describe "Backburner::Workers::Threading worker" do
|
|
6
6
|
before do
|
7
7
|
Backburner.default_queues.clear
|
8
8
|
@worker_class = Backburner::Workers::Threading
|
9
|
+
@worker_class.shutdown_timeout = 2
|
9
10
|
end
|
10
11
|
|
11
12
|
describe "for prepare method" do
|
12
13
|
it "should make tube names array always unique to avoid duplication" do
|
13
14
|
worker = @worker_class.new(["foo", "demo.test.foo"])
|
14
|
-
worker.prepare
|
15
|
+
capture_stdout { worker.prepare }
|
15
16
|
assert_equal ["demo.test.foo"], worker.tube_names
|
16
17
|
end
|
17
18
|
|
18
19
|
it 'creates a thread pool per queue' do
|
19
20
|
worker = @worker_class.new(%w(foo bar))
|
20
|
-
|
21
|
+
capture_stdout { worker.prepare }
|
21
22
|
assert_equal 2, worker.instance_variable_get("@thread_pools").keys.size
|
22
23
|
end
|
23
24
|
|
24
25
|
it 'uses Concurrent.processor_count if no custom thread count is provided' do
|
25
26
|
worker = @worker_class.new("foo")
|
26
|
-
|
27
|
+
capture_stdout { worker.prepare }
|
27
28
|
assert_equal ::Concurrent.processor_count, worker.instance_variable_get("@thread_pools")["demo.test.foo"].max_length
|
28
29
|
end
|
29
30
|
end # prepare
|
@@ -61,10 +62,43 @@ describe "Backburner::Workers::Threading worker" do
|
|
61
62
|
it 'runs work_on_job per thread' do
|
62
63
|
clear_jobs!("foo")
|
63
64
|
job_count=10
|
64
|
-
|
65
|
-
@
|
66
|
-
|
65
|
+
# TestJob adds the given arguments together and then to $worker_test_count
|
66
|
+
job_count.times { @worker_class.enqueue TestJob, [1, 0], :queue => "foo" }
|
67
|
+
capture_stdout do
|
68
|
+
@worker.start(false) # don't wait for shutdown
|
69
|
+
sleep 0.5 # Wait for threads to do their work
|
70
|
+
end
|
67
71
|
assert_equal job_count, $worker_test_count
|
68
72
|
end
|
69
73
|
end # working a queue
|
74
|
+
|
75
|
+
describe 'shutting down' do
|
76
|
+
before do
|
77
|
+
@thread_count = 3
|
78
|
+
@worker = @worker_class.new(["threaded-shutdown:#{@thread_count}"])
|
79
|
+
@worker.exit_on_shutdown = false
|
80
|
+
$worker_test_count = 0
|
81
|
+
clear_jobs!("threaded-shutdown")
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'gracefully exits and completes all in-flight jobs' do
|
85
|
+
6.times { @worker_class.enqueue TestSlowJob, [1, 0], :queue => "threaded-shutdown" }
|
86
|
+
Thread.new { sleep 0.1; @worker.self_write.puts("TERM") }
|
87
|
+
capture_stdout do
|
88
|
+
@worker.start
|
89
|
+
end
|
90
|
+
|
91
|
+
assert_equal @thread_count, $worker_test_count
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'forces an exit when a job is stuck' do
|
95
|
+
6.times { @worker_class.enqueue TestStuckJob, [1, 0], :queue => "threaded-shutdown" }
|
96
|
+
Thread.new { sleep 0.1; @worker.self_write.puts("TERM") }
|
97
|
+
capture_stdout do
|
98
|
+
@worker.start
|
99
|
+
end
|
100
|
+
|
101
|
+
assert_equal 0, $worker_test_count
|
102
|
+
end
|
103
|
+
end
|
70
104
|
end
|
@@ -70,7 +70,7 @@ describe "Backburner::Workers::ThreadsOnFork module" do
|
|
70
70
|
|
71
71
|
it "should make tube names array always unique to avoid duplication" do
|
72
72
|
worker = @worker_class.new(["foo", "demo.test.foo"])
|
73
|
-
worker.prepare
|
73
|
+
capture_stdout { worker.prepare }
|
74
74
|
assert_equal ["demo.test.foo"], worker.tube_names
|
75
75
|
end
|
76
76
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: backburner
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nathan Esquenazi
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2018-09-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: beaneater
|
@@ -43,6 +43,9 @@ dependencies:
|
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.0'
|
48
|
+
- - ">="
|
46
49
|
- !ruby/object:Gem::Version
|
47
50
|
version: 1.0.1
|
48
51
|
type: :runtime
|
@@ -50,6 +53,9 @@ dependencies:
|
|
50
53
|
version_requirements: !ruby/object:Gem::Requirement
|
51
54
|
requirements:
|
52
55
|
- - "~>"
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: '1.0'
|
58
|
+
- - ">="
|
53
59
|
- !ruby/object:Gem::Version
|
54
60
|
version: 1.0.1
|
55
61
|
- !ruby/object:Gem::Dependency
|