faktory_worker_ruby 0.6.1 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Changes.md +5 -0
- data/Gemfile.lock +2 -2
- data/faktory_worker_ruby.gemspec +1 -1
- data/lib/faktory/cli.rb +12 -1
- data/lib/faktory/client.rb +11 -10
- data/lib/faktory/launcher.rb +3 -2
- data/lib/faktory/middleware/chain.rb +4 -4
- data/lib/faktory/processor.rb +3 -3
- data/lib/faktory/testing.rb +336 -0
- data/lib/faktory/version.rb +1 -1
- metadata +5 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 672affacf993528eedff478961a96bbc222e62bc
|
4
|
+
data.tar.gz: 8b58b6e46e4d6965690bc4a17f3067548671edd0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7210a8933baf0a671a608d41b5bb183fec3ea93b5c233c937c3882e921634d1baedb5985be1ecd6142ee1ca9257d3483012a7511f40c4f58fc4c13d0f34a4f4f
|
7
|
+
data.tar.gz: 1a2e769ad7b96d539842e29c7056e36523d878cb81ccda8610247b99e8f239877dfbe7caee93be6d194213801c2a31fbfb876c69f5a5850bad6b15461661c4e4
|
data/Changes.md
CHANGED
data/Gemfile.lock
CHANGED
data/faktory_worker_ruby.gemspec
CHANGED
@@ -7,7 +7,7 @@ Gem::Specification.new do |gem|
|
|
7
7
|
gem.email = ["mike@contribsys.com"]
|
8
8
|
gem.summary = "Ruby worker for Faktory"
|
9
9
|
gem.description = "Ruby worker for Faktory."
|
10
|
-
gem.homepage = "
|
10
|
+
gem.homepage = "https://github.com/contribsys/faktory_worker_ruby"
|
11
11
|
gem.license = "LGPL-3.0"
|
12
12
|
|
13
13
|
gem.executables = ['faktory-worker']
|
data/lib/faktory/cli.rb
CHANGED
@@ -99,7 +99,18 @@ module Faktory
|
|
99
99
|
|
100
100
|
def self.banner
|
101
101
|
%q{
|
102
|
-
|
102
|
+
,,,,
|
103
|
+
,,,, | |
|
104
|
+
| | | |
|
105
|
+
| | | |
|
106
|
+
| |,,~~' '~, ___ _
|
107
|
+
,,~~'' ,~~, '~, / __) | | _
|
108
|
+
,,~~'' ,~~, | | | _| |__ _____| | _ _| |_ ___ ____ _ _
|
109
|
+
| ,~~, | | | | | (_ __|____ | |_/ |_ _) _ \ / ___) | | |
|
110
|
+
| | | | | | | | | | / ___ | _ ( | || |_| | | | |_| |
|
111
|
+
| |__| |__| |__| | |_| \_____|_| \_) \__)___/|_| \__ |
|
112
|
+
|__________________________| (____/
|
113
|
+
|
103
114
|
}
|
104
115
|
end
|
105
116
|
|
data/lib/faktory/client.rb
CHANGED
@@ -35,9 +35,9 @@ module Faktory
|
|
35
35
|
# MY_FAKTORY_URL=tcp://:somepass@my-server.example.com:7419
|
36
36
|
#
|
37
37
|
# Note above, the URL can contain the password for secure installations.
|
38
|
-
def initialize(url: 'tcp://localhost:7419', debug: false)
|
38
|
+
def initialize(url: uri_from_env || 'tcp://localhost:7419', debug: false)
|
39
39
|
@debug = debug
|
40
|
-
@location =
|
40
|
+
@location = URI(url)
|
41
41
|
open
|
42
42
|
end
|
43
43
|
|
@@ -235,14 +235,15 @@ module Faktory
|
|
235
235
|
# MY_FAKTORY_URL=tcp://:some-pass@some-hostname:7419
|
236
236
|
def uri_from_env
|
237
237
|
prov = ENV['FAKTORY_PROVIDER']
|
238
|
-
|
239
|
-
|
240
|
-
Invalid FAKTORY_PROVIDER '#{prov}', it should be the name of the ENV variable that contains the URL
|
241
|
-
|
242
|
-
|
243
|
-
EOM
|
244
|
-
|
245
|
-
|
238
|
+
if prov
|
239
|
+
raise(ArgumentError, <<-EOM) if prov.index(":")
|
240
|
+
Invalid FAKTORY_PROVIDER '#{prov}', it should be the name of the ENV variable that contains the URL
|
241
|
+
FAKTORY_PROVIDER=MY_FAKTORY_URL
|
242
|
+
MY_FAKTORY_URL=tcp://:some-pass@some-hostname:7419
|
243
|
+
EOM
|
244
|
+
val = ENV[prov]
|
245
|
+
return URI(val) if val
|
246
|
+
end
|
246
247
|
|
247
248
|
val = ENV['FAKTORY_URL']
|
248
249
|
return URI(val) if val
|
data/lib/faktory/launcher.rb
CHANGED
@@ -9,9 +9,10 @@ module Faktory
|
|
9
9
|
attr_accessor :manager
|
10
10
|
|
11
11
|
def initialize(options)
|
12
|
-
|
12
|
+
merged_options = Faktory.options.merge(options)
|
13
|
+
@manager = Faktory::Manager.new(merged_options)
|
13
14
|
@done = false
|
14
|
-
@options =
|
15
|
+
@options = merged_options
|
15
16
|
end
|
16
17
|
|
17
18
|
def run
|
@@ -9,7 +9,7 @@ module Faktory
|
|
9
9
|
# To add middleware to run when a job is pushed to Faktory:
|
10
10
|
#
|
11
11
|
# Faktory.configure_client do |config|
|
12
|
-
# config.
|
12
|
+
# config.client_middleware do |chain|
|
13
13
|
# chain.add MyClientHook
|
14
14
|
# end
|
15
15
|
# end
|
@@ -27,15 +27,15 @@ module Faktory
|
|
27
27
|
# To insert immediately preceding another entry:
|
28
28
|
#
|
29
29
|
# Faktory.configure_client do |config|
|
30
|
-
# config.
|
31
|
-
# chain.insert_before
|
30
|
+
# config.client_middleware do |chain|
|
31
|
+
# chain.insert_before SomeOtherMiddleware, MyClientHook
|
32
32
|
# end
|
33
33
|
# end
|
34
34
|
#
|
35
35
|
# To insert immediately after another entry:
|
36
36
|
#
|
37
37
|
# Faktory.configure_client do |config|
|
38
|
-
# config.
|
38
|
+
# config.client_middleware do |chain|
|
39
39
|
# chain.insert_after ActiveRecord, MyClientHook
|
40
40
|
# end
|
41
41
|
# end
|
data/lib/faktory/processor.rb
CHANGED
@@ -39,9 +39,9 @@ module Faktory
|
|
39
39
|
@down = false
|
40
40
|
@done = false
|
41
41
|
@thread = nil
|
42
|
-
@reloader =
|
42
|
+
@reloader = mgr.options[:reloader]
|
43
43
|
@logging = (mgr.options[:job_logger] || Faktory::JobLogger).new
|
44
|
-
@fetcher = Faktory::Fetcher.new(
|
44
|
+
@fetcher = Faktory::Fetcher.new(mgr.options)
|
45
45
|
end
|
46
46
|
|
47
47
|
def terminate(wait=false)
|
@@ -153,7 +153,7 @@ module Faktory
|
|
153
153
|
# within the timeout. Don't acknowledge the work since
|
154
154
|
# we didn't properly finish it.
|
155
155
|
rescue Exception => ex
|
156
|
-
handle_exception(ex, { :context => "Job raised exception", :job => job })
|
156
|
+
handle_exception(ex, { :context => "Job raised exception", :job => work.job })
|
157
157
|
work.fail(ex)
|
158
158
|
raise ex
|
159
159
|
end
|
@@ -0,0 +1,336 @@
|
|
1
|
+
module Faktory
|
2
|
+
module Testing
|
3
|
+
def self.__test_mode
|
4
|
+
@__test_mode
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.__test_mode=(mode)
|
8
|
+
@__test_mode = mode
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.__set_test_mode(mode)
|
12
|
+
if block_given?
|
13
|
+
current_mode = self.__test_mode
|
14
|
+
begin
|
15
|
+
self.__test_mode = mode
|
16
|
+
yield
|
17
|
+
ensure
|
18
|
+
self.__test_mode = current_mode
|
19
|
+
end
|
20
|
+
else
|
21
|
+
self.__test_mode = mode
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.disable!(&block)
|
26
|
+
__set_test_mode(:disable, &block)
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.fake!(&block)
|
30
|
+
__set_test_mode(:fake, &block)
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.inline!(&block)
|
34
|
+
# Only allow blockless inline via `blockless_inline_is_a_bad_idea_but_I_wanna_do_it_anyway!`
|
35
|
+
# https://github.com/mperham/sidekiq/issues/3495
|
36
|
+
unless block_given?
|
37
|
+
raise 'Must provide a block to Faktory::Testing.inline!'
|
38
|
+
end
|
39
|
+
|
40
|
+
__set_test_mode(:inline, &block)
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.blockless_inline_is_a_bad_idea_but_I_wanna_do_it_anyway!
|
44
|
+
__set_test_mode(:inline)
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.enabled?
|
48
|
+
self.__test_mode != :disable
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.disabled?
|
52
|
+
self.__test_mode == :disable
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.fake?
|
56
|
+
self.__test_mode == :fake
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.inline?
|
60
|
+
self.__test_mode == :inline
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.constantize(str)
|
64
|
+
names = str.split('::')
|
65
|
+
names.shift if names.empty? || names.first.empty?
|
66
|
+
|
67
|
+
names.inject(Object) do |constant, name|
|
68
|
+
# the false flag limits search for name to under the constant namespace
|
69
|
+
# which mimics Rails' behaviour
|
70
|
+
constant.const_defined?(name, false) ? constant.const_get(name, false) : constant.const_missing(name)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Test modes have to be opted into explicitly.
|
76
|
+
# Just requiring the testing module shouldn't automatically change the testing mode.
|
77
|
+
Faktory::Testing.disable!
|
78
|
+
|
79
|
+
class EmptyQueueError < RuntimeError; end
|
80
|
+
|
81
|
+
class Client
|
82
|
+
alias_method :real_push, :push
|
83
|
+
alias_method :real_open, :open
|
84
|
+
|
85
|
+
def push(job)
|
86
|
+
if Faktory::Testing.inline?
|
87
|
+
job = Faktory.load_json(Faktory.dump_json(job))
|
88
|
+
job_class = Faktory::Testing.constantize(job['jobtype'])
|
89
|
+
job_class.new.perform(*job['args'])
|
90
|
+
return job['jid']
|
91
|
+
elsif Faktory::Testing.fake?
|
92
|
+
job = Faktory.load_json(Faktory.dump_json(job))
|
93
|
+
job.merge!('enqueued_at' => Time.now.to_f) unless job['at']
|
94
|
+
Queues.push(job['queue'], job['jobtype'], job)
|
95
|
+
return job['jid']
|
96
|
+
else
|
97
|
+
real_push(job)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def open
|
102
|
+
unless Faktory::Testing.enabled?
|
103
|
+
real_open
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
module Queues
|
109
|
+
##
|
110
|
+
# The Queues class is only for testing the fake queue implementation.
|
111
|
+
# There are 2 data structures involved in tandem. This is due to the
|
112
|
+
# Rspec syntax of change(QueueWorker.jobs, :size). It keeps a reference
|
113
|
+
# to the array. Because the array was dervied from a filter of the total
|
114
|
+
# jobs enqueued, it appeared as though the array didn't change.
|
115
|
+
#
|
116
|
+
# To solve this, we'll keep 2 hashes containing the jobs. One with keys based
|
117
|
+
# on the queue, and another with keys of the worker names, so the array for
|
118
|
+
# QueueWorker.jobs is a straight reference to a real array.
|
119
|
+
#
|
120
|
+
# Queue-based hash:
|
121
|
+
#
|
122
|
+
# {
|
123
|
+
# "default"=>[
|
124
|
+
# {
|
125
|
+
# "class"=>"TestTesting::QueueWorker",
|
126
|
+
# "args"=>[1, 2],
|
127
|
+
# "retry"=>true,
|
128
|
+
# "queue"=>"default",
|
129
|
+
# "jid"=>"abc5b065c5c4b27fc1102833",
|
130
|
+
# "created_at"=>1447445554.419934
|
131
|
+
# }
|
132
|
+
# ]
|
133
|
+
# }
|
134
|
+
#
|
135
|
+
# Worker-based hash:
|
136
|
+
#
|
137
|
+
# {
|
138
|
+
# "TestTesting::QueueWorker"=>[
|
139
|
+
# {
|
140
|
+
# "class"=>"TestTesting::QueueWorker",
|
141
|
+
# "args"=>[1, 2],
|
142
|
+
# "retry"=>true,
|
143
|
+
# "queue"=>"default",
|
144
|
+
# "jid"=>"abc5b065c5c4b27fc1102833",
|
145
|
+
# "created_at"=>1447445554.419934
|
146
|
+
# }
|
147
|
+
# ]
|
148
|
+
# }
|
149
|
+
#
|
150
|
+
# Example:
|
151
|
+
#
|
152
|
+
# require 'faktory/testing'
|
153
|
+
#
|
154
|
+
# assert_equal 0, Faktory::Queues["default"].size
|
155
|
+
# HardWorker.perform_async(:something)
|
156
|
+
# assert_equal 1, Faktory::Queues["default"].size
|
157
|
+
# assert_equal :something, Faktory::Queues["default"].first['args'][0]
|
158
|
+
#
|
159
|
+
# You can also clear all workers' jobs:
|
160
|
+
#
|
161
|
+
# assert_equal 0, Faktory::Queues["default"].size
|
162
|
+
# HardWorker.perform_async(:something)
|
163
|
+
# Faktory::Queues.clear_all
|
164
|
+
# assert_equal 0, Faktory::Queues["default"].size
|
165
|
+
#
|
166
|
+
# This can be useful to make sure jobs don't linger between tests:
|
167
|
+
#
|
168
|
+
# RSpec.configure do |config|
|
169
|
+
# config.before(:each) do
|
170
|
+
# Faktory::Queues.clear_all
|
171
|
+
# end
|
172
|
+
# end
|
173
|
+
#
|
174
|
+
class << self
|
175
|
+
def [](queue)
|
176
|
+
jobs_by_queue[queue]
|
177
|
+
end
|
178
|
+
|
179
|
+
def push(queue, klass, job)
|
180
|
+
jobs_by_queue[queue] << job
|
181
|
+
jobs_by_worker[klass] << job
|
182
|
+
end
|
183
|
+
|
184
|
+
def jobs_by_queue
|
185
|
+
@jobs_by_queue ||= Hash.new { |hash, key| hash[key] = [] }
|
186
|
+
end
|
187
|
+
|
188
|
+
def jobs_by_worker
|
189
|
+
@jobs_by_worker ||= Hash.new { |hash, key| hash[key] = [] }
|
190
|
+
end
|
191
|
+
|
192
|
+
def delete_for(jid, queue, klass)
|
193
|
+
jobs_by_queue[queue.to_s].delete_if { |job| job["jid"] == jid }
|
194
|
+
jobs_by_worker[klass].delete_if { |job| job["jid"] == jid }
|
195
|
+
end
|
196
|
+
|
197
|
+
def clear_for(queue, klass)
|
198
|
+
jobs_by_queue[queue].clear
|
199
|
+
jobs_by_worker[klass].clear
|
200
|
+
end
|
201
|
+
|
202
|
+
def clear_all
|
203
|
+
jobs_by_queue.clear
|
204
|
+
jobs_by_worker.clear
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
module Job
|
210
|
+
##
|
211
|
+
# The Faktory testing infrastructure overrides perform_async
|
212
|
+
# so that it does not actually touch the network. Instead it
|
213
|
+
# stores the asynchronous jobs in a per-class array so that
|
214
|
+
# their presence/absence can be asserted by your tests.
|
215
|
+
#
|
216
|
+
# This is similar to ActionMailer's :test delivery_method and its
|
217
|
+
# ActionMailer::Base.deliveries array.
|
218
|
+
#
|
219
|
+
# Example:
|
220
|
+
#
|
221
|
+
# require 'faktory/testing'
|
222
|
+
#
|
223
|
+
# assert_equal 0, HardWorker.jobs.size
|
224
|
+
# HardWorker.perform_async(:something)
|
225
|
+
# assert_equal 1, HardWorker.jobs.size
|
226
|
+
# assert_equal :something, HardWorker.jobs[0]['args'][0]
|
227
|
+
#
|
228
|
+
# assert_equal 0, Faktory::Extensions::DelayedMailer.jobs.size
|
229
|
+
# MyMailer.delay.send_welcome_email('foo@example.com')
|
230
|
+
# assert_equal 1, Faktory::Extensions::DelayedMailer.jobs.size
|
231
|
+
#
|
232
|
+
# You can also clear and drain all workers' jobs:
|
233
|
+
#
|
234
|
+
# assert_equal 0, Faktory::Extensions::DelayedMailer.jobs.size
|
235
|
+
# assert_equal 0, Faktory::Extensions::DelayedModel.jobs.size
|
236
|
+
#
|
237
|
+
# MyMailer.delay.send_welcome_email('foo@example.com')
|
238
|
+
# MyModel.delay.do_something_hard
|
239
|
+
#
|
240
|
+
# assert_equal 1, Faktory::Extensions::DelayedMailer.jobs.size
|
241
|
+
# assert_equal 1, Faktory::Extensions::DelayedModel.jobs.size
|
242
|
+
#
|
243
|
+
# Faktory::Worker.clear_all # or .drain_all
|
244
|
+
#
|
245
|
+
# assert_equal 0, Faktory::Extensions::DelayedMailer.jobs.size
|
246
|
+
# assert_equal 0, Faktory::Extensions::DelayedModel.jobs.size
|
247
|
+
#
|
248
|
+
# This can be useful to make sure jobs don't linger between tests:
|
249
|
+
#
|
250
|
+
# RSpec.configure do |config|
|
251
|
+
# config.before(:each) do
|
252
|
+
# Faktory::Worker.clear_all
|
253
|
+
# end
|
254
|
+
# end
|
255
|
+
#
|
256
|
+
# or for acceptance testing, i.e. with cucumber:
|
257
|
+
#
|
258
|
+
# AfterStep do
|
259
|
+
# Faktory::Worker.drain_all
|
260
|
+
# end
|
261
|
+
#
|
262
|
+
# When I sign up as "foo@example.com"
|
263
|
+
# Then I should receive a welcome email to "foo@example.com"
|
264
|
+
#
|
265
|
+
module ClassMethods
|
266
|
+
|
267
|
+
# Queue for this worker
|
268
|
+
def queue
|
269
|
+
self.faktory_options["queue"]
|
270
|
+
end
|
271
|
+
|
272
|
+
# Jobs queued for this worker
|
273
|
+
def jobs
|
274
|
+
Queues.jobs_by_worker[self.to_s]
|
275
|
+
end
|
276
|
+
|
277
|
+
# Clear all jobs for this worker
|
278
|
+
def clear
|
279
|
+
Queues.clear_for(queue, self.to_s)
|
280
|
+
end
|
281
|
+
|
282
|
+
# Drain and run all jobs for this worker
|
283
|
+
def drain
|
284
|
+
while jobs.any?
|
285
|
+
next_job = jobs.first
|
286
|
+
Queues.delete_for(next_job["jid"], next_job["queue"], self.to_s)
|
287
|
+
process_job(next_job)
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
# Pop out a single job and perform it
|
292
|
+
def perform_one
|
293
|
+
raise(EmptyQueueError, "perform_one called with empty job queue") if jobs.empty?
|
294
|
+
next_job = jobs.first
|
295
|
+
Queues.delete_for(next_job["jid"], queue, self.to_s)
|
296
|
+
process_job(next_job)
|
297
|
+
end
|
298
|
+
|
299
|
+
def process_job(job)
|
300
|
+
worker = new
|
301
|
+
worker.jid = job['jid']
|
302
|
+
worker.bid = job['bid'] if worker.respond_to?(:bid=)
|
303
|
+
#Faktory::Testing.server_middleware.invoke(worker, job, job['queue']) do
|
304
|
+
execute_job(worker, job['args'])
|
305
|
+
#end
|
306
|
+
end
|
307
|
+
|
308
|
+
def execute_job(worker, args)
|
309
|
+
worker.perform(*args)
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
class << self
|
314
|
+
def jobs # :nodoc:
|
315
|
+
Queues.jobs_by_queue.values.flatten
|
316
|
+
end
|
317
|
+
|
318
|
+
# Clear all queued jobs across all workers
|
319
|
+
def clear_all
|
320
|
+
Queues.clear_all
|
321
|
+
end
|
322
|
+
|
323
|
+
# Drain all queued jobs across all workers
|
324
|
+
def drain_all
|
325
|
+
while jobs.any?
|
326
|
+
worker_classes = jobs.map { |job| job["jobtype"] }.uniq
|
327
|
+
|
328
|
+
worker_classes.each do |worker_class|
|
329
|
+
Faktory::Testing.constantize(worker_class).drain
|
330
|
+
end
|
331
|
+
end
|
332
|
+
end
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
end
|
data/lib/faktory/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: faktory_worker_ruby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mike Perham
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2018-03-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: connection_pool
|
@@ -104,10 +104,11 @@ files:
|
|
104
104
|
- lib/faktory/middleware/i18n.rb
|
105
105
|
- lib/faktory/processor.rb
|
106
106
|
- lib/faktory/rails.rb
|
107
|
+
- lib/faktory/testing.rb
|
107
108
|
- lib/faktory/util.rb
|
108
109
|
- lib/faktory/version.rb
|
109
110
|
- lib/faktory_worker_ruby.rb
|
110
|
-
homepage:
|
111
|
+
homepage: https://github.com/contribsys/faktory_worker_ruby
|
111
112
|
licenses:
|
112
113
|
- LGPL-3.0
|
113
114
|
metadata: {}
|
@@ -127,7 +128,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
127
128
|
version: '0'
|
128
129
|
requirements: []
|
129
130
|
rubyforge_project:
|
130
|
-
rubygems_version: 2.6.
|
131
|
+
rubygems_version: 2.6.13
|
131
132
|
signing_key:
|
132
133
|
specification_version: 4
|
133
134
|
summary: Ruby worker for Faktory
|