qe 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/.rspec +1 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +100 -0
- data/README.md +144 -0
- data/Rakefile +5 -0
- data/examples/.env +3 -0
- data/examples/Gemfile +14 -0
- data/examples/Gemfile.lock +119 -0
- data/examples/Procfile +20 -0
- data/examples/README.md +9 -0
- data/examples/Rakefile +14 -0
- data/examples/beanstalkd.ru +5 -0
- data/examples/migrate.rb +16 -0
- data/examples/redis.conf +1 -0
- data/examples/resque.ru +6 -0
- data/examples/sample.rb +11 -0
- data/examples/sidekiq.ru +10 -0
- data/examples/workers.rb +43 -0
- data/lib/qe.rb +79 -0
- data/lib/qe/beanstalk.rb +21 -0
- data/lib/qe/delayed_job.rb +18 -0
- data/lib/qe/immediate.rb +7 -0
- data/lib/qe/qu.rb +21 -0
- data/lib/qe/resque.rb +19 -0
- data/lib/qe/sidekiq.rb +21 -0
- data/lib/qe/testing.rb +11 -0
- data/lib/qe/testing/rspec.rb +59 -0
- data/lib/qe/version.rb +3 -0
- data/lib/qe/worker.rb +71 -0
- data/qe.gemspec +31 -0
- data/spec/qe/beanstalk_spec.rb +45 -0
- data/spec/qe/delayed_job_spec.rb +59 -0
- data/spec/qe/enqueue_matcher_spec.rb +33 -0
- data/spec/qe/immediate_spec.rb +26 -0
- data/spec/qe/qu_spec.rb +42 -0
- data/spec/qe/resque_spec.rb +42 -0
- data/spec/qe/sidekiq_spec.rb +49 -0
- data/spec/qe/worker_spec.rb +82 -0
- data/spec/spec_helper.rb +12 -0
- metadata +247 -0
data/examples/migrate.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require "./workers"
|
2
|
+
|
3
|
+
ActiveRecord::Schema.define(:version => 0) do
|
4
|
+
create_table :delayed_jobs, :force => true do |table|
|
5
|
+
table.integer :priority, :default => 0 # Allows some jobs to jump to the front of the queue
|
6
|
+
table.integer :attempts, :default => 0 # Provides for retries, but still fail eventually.
|
7
|
+
table.text :handler # YAML-encoded string of the object that will do work
|
8
|
+
table.text :last_error # reason for last failure (See Note below)
|
9
|
+
table.datetime :run_at # When to run. Could be Time.zone.now for immediately, or sometime in the future.
|
10
|
+
table.datetime :locked_at # Set when a client is working on this object
|
11
|
+
table.datetime :failed_at # Set when all retries have failed (actually, by default, the record is deleted instead)
|
12
|
+
table.string :locked_by # Who is working on this object (if locked)
|
13
|
+
table.string :queue # The name of the queue this job is in
|
14
|
+
table.timestamps
|
15
|
+
end
|
16
|
+
end
|
data/examples/redis.conf
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
loglevel notice
|
data/examples/resque.ru
ADDED
data/examples/sample.rb
ADDED
data/examples/sidekiq.ru
ADDED
data/examples/workers.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
require "bundler"
|
2
|
+
Bundler.setup(:default)
|
3
|
+
Bundler.require
|
4
|
+
|
5
|
+
$:.unshift File.expand_path("../../lib", __FILE__)
|
6
|
+
require "qe"
|
7
|
+
|
8
|
+
$stdout.sync = true
|
9
|
+
|
10
|
+
Qe.adapter = Qe::Sidekiq
|
11
|
+
# Qe.adapter = Qe::Beanstalk
|
12
|
+
# Qe.adapter = Qe::Resque
|
13
|
+
# Qe.adapter = Qe::DelayedJob
|
14
|
+
# Qe.adapter = Qe::Qu
|
15
|
+
# Qe.adapter = Qe::Testing
|
16
|
+
|
17
|
+
ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => "jobs.sqlite3")
|
18
|
+
|
19
|
+
class ClockWorker
|
20
|
+
include Qe::Worker
|
21
|
+
|
22
|
+
def perform
|
23
|
+
puts "=> Time: #{Time.now}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class MailerWorker
|
28
|
+
include Qe::Worker
|
29
|
+
queue :mail
|
30
|
+
|
31
|
+
def before
|
32
|
+
puts "=> Running before"
|
33
|
+
end
|
34
|
+
|
35
|
+
def perform
|
36
|
+
puts "=> Performing"
|
37
|
+
puts "=> Options: #{options.inspect}"
|
38
|
+
end
|
39
|
+
|
40
|
+
def after
|
41
|
+
puts "=> Running after"
|
42
|
+
end
|
43
|
+
end
|
data/lib/qe.rb
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
require "qe/immediate"
|
2
|
+
require "qe/version"
|
3
|
+
require "qe/worker"
|
4
|
+
|
5
|
+
# In this wild world where a new asynchronous job processing
|
6
|
+
# library is released every once in a while, Qe tries to keep a unified
|
7
|
+
# interface that works with most famous libraries:
|
8
|
+
#
|
9
|
+
# # Sidekiq
|
10
|
+
# # Resque
|
11
|
+
# # DelayedJob
|
12
|
+
# # Qu
|
13
|
+
# # Beanstalk/Backburner
|
14
|
+
#
|
15
|
+
# See an example:
|
16
|
+
#
|
17
|
+
# You can choose an adapter:
|
18
|
+
#
|
19
|
+
# Qe.adapter = Qe::Sidekiq
|
20
|
+
# Qe.adapter = Qe::Resque
|
21
|
+
# Qe.adapter = Qe::Qu
|
22
|
+
# Qe.adapter = Qe::DelayedJob
|
23
|
+
# Qe.adapter = Qe::Beanstalk
|
24
|
+
#
|
25
|
+
# Create our worker that will send e-mails through +ActionMailer+.
|
26
|
+
#
|
27
|
+
# class MailerWorker
|
28
|
+
# include Qe::Worker
|
29
|
+
#
|
30
|
+
# def perform
|
31
|
+
# Mailer.public_send(options[:mail], options).deliver
|
32
|
+
# end
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
# Define our +Mailer+ class.
|
36
|
+
#
|
37
|
+
# class Mailer < ActionMailer::Base
|
38
|
+
# def welcome(options)
|
39
|
+
# @options = options
|
40
|
+
# mail :to => options[:email]
|
41
|
+
# end
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
# Enqueue a job to be processed asynchronously.
|
45
|
+
#
|
46
|
+
# MailerWorker.enqueue({
|
47
|
+
# :mail => :welcome,
|
48
|
+
# :email => "john@example.org",
|
49
|
+
# :name => "John Doe"
|
50
|
+
# })
|
51
|
+
#
|
52
|
+
# == Testing support
|
53
|
+
#
|
54
|
+
# Qe comes with testing support. Just require the <tt>qe/testing.rb</tt> file
|
55
|
+
# and a fake queuing adapter will be used. All enqueued jobs will be stored
|
56
|
+
# at <tt>Qe.jobs</tt>. Note that this method is only available on testing mode.
|
57
|
+
#
|
58
|
+
# require "qe/testing"
|
59
|
+
# Qe.adapter = Qe::Testing
|
60
|
+
#
|
61
|
+
# If you're using RSpec, you can require the <tt>qe/testing/rspec.rb</tt> file
|
62
|
+
# instead. This will reset <tt>Qe.jobs</tt> before every spec and will add a
|
63
|
+
# +enqueue+ matcher.
|
64
|
+
#
|
65
|
+
# require "qe/testing/rspec"
|
66
|
+
#
|
67
|
+
# describe "Enqueuing a job" do
|
68
|
+
# it "enqueues job" do
|
69
|
+
# expect {
|
70
|
+
# # do something
|
71
|
+
# }.to enqueue(MailerWorker).with(:email => "john@example.org")
|
72
|
+
# end
|
73
|
+
# end
|
74
|
+
#
|
75
|
+
module Qe
|
76
|
+
class << self
|
77
|
+
attr_accessor :adapter
|
78
|
+
end
|
79
|
+
end
|
data/lib/qe/beanstalk.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require "qe"
|
2
|
+
require "backburner"
|
3
|
+
|
4
|
+
module Qe
|
5
|
+
class Beanstalk
|
6
|
+
class Worker
|
7
|
+
include Backburner::Queue
|
8
|
+
|
9
|
+
def self.perform(*args)
|
10
|
+
Qe::Worker.perform(*args)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.enqueue(worker, options = {})
|
15
|
+
Worker.queue worker.queue
|
16
|
+
Backburner.enqueue Worker, worker.name, options
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
self.adapter = Beanstalk
|
21
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require "qe"
|
2
|
+
require "delayed_job"
|
3
|
+
|
4
|
+
module Qe
|
5
|
+
class DelayedJob
|
6
|
+
class Worker < Struct.new(:worker_name, :options)
|
7
|
+
def perform
|
8
|
+
Qe::Worker.perform(worker_name, options)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.enqueue(worker, options = {})
|
13
|
+
Delayed::Job.enqueue Worker.new(worker.name, options), :queue => worker.queue
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
self.adapter = DelayedJob
|
18
|
+
end
|
data/lib/qe/immediate.rb
ADDED
data/lib/qe/qu.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require "qe"
|
2
|
+
require "qu"
|
3
|
+
|
4
|
+
module Qe
|
5
|
+
class Qu
|
6
|
+
class Worker
|
7
|
+
include ::Sidekiq::Worker
|
8
|
+
|
9
|
+
def self.perform(*args)
|
10
|
+
Qe::Worker.perform(*args)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.enqueue(worker, options = {})
|
15
|
+
Worker.instance_variable_set("@queue", worker.queue)
|
16
|
+
::Qu.enqueue Worker, worker.name, options
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
self.adapter = Qu
|
21
|
+
end
|
data/lib/qe/resque.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require "qe"
|
2
|
+
require "resque"
|
3
|
+
|
4
|
+
module Qe
|
5
|
+
class Resque
|
6
|
+
class Worker
|
7
|
+
def self.perform(*args)
|
8
|
+
Qe::Worker.perform(*args)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.enqueue(worker, options = {})
|
13
|
+
Worker.instance_variable_set "@queue", worker.queue
|
14
|
+
::Resque.enqueue Worker, worker.name, options
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
self.adapter = Resque
|
19
|
+
end
|
data/lib/qe/sidekiq.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require "qe"
|
2
|
+
require "sidekiq"
|
3
|
+
|
4
|
+
module Qe
|
5
|
+
class Sidekiq
|
6
|
+
class Worker
|
7
|
+
include ::Sidekiq::Worker
|
8
|
+
|
9
|
+
def perform(*args)
|
10
|
+
Qe::Worker.perform(*args)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.enqueue(worker, options = {})
|
15
|
+
Worker.sidekiq_options :queue => worker.queue
|
16
|
+
Worker.perform_async(worker.name, options)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
self.adapter = Sidekiq
|
21
|
+
end
|
data/lib/qe/testing.rb
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
require "qe/testing"
|
2
|
+
|
3
|
+
module Qe
|
4
|
+
module EnqueueMatcher
|
5
|
+
class Matcher
|
6
|
+
attr_reader :worker, :options
|
7
|
+
|
8
|
+
def initialize(worker)
|
9
|
+
@worker = worker
|
10
|
+
end
|
11
|
+
|
12
|
+
def with(options)
|
13
|
+
@options = options
|
14
|
+
self
|
15
|
+
end
|
16
|
+
|
17
|
+
def matches?(block)
|
18
|
+
block.call
|
19
|
+
|
20
|
+
Qe.jobs.find do |job|
|
21
|
+
condition = job[:worker] == worker
|
22
|
+
condition = condition && job[:options] == options if options
|
23
|
+
condition
|
24
|
+
end != nil
|
25
|
+
end
|
26
|
+
|
27
|
+
def description
|
28
|
+
"enqueue job for #{worker.inspect} worker"
|
29
|
+
end
|
30
|
+
|
31
|
+
def failure_message_for_should
|
32
|
+
build_message "expect #{worker.inspect} to be enqueued"
|
33
|
+
end
|
34
|
+
|
35
|
+
def failure_message_for_should_not
|
36
|
+
build_message "expect #{worker.inspect} not to be enqueued"
|
37
|
+
end
|
38
|
+
|
39
|
+
def build_message(base)
|
40
|
+
base << (options.empty? ? "" : " with #{options.inspect}")
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
#
|
45
|
+
# expect {}.to enqueue(MailerWorker).with(options)
|
46
|
+
#
|
47
|
+
def enqueue(worker)
|
48
|
+
Matcher.new(worker)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
RSpec.configure do |config|
|
54
|
+
config.include Qe::EnqueueMatcher
|
55
|
+
config.before(:each) do
|
56
|
+
Qe.adapter = Qe::Testing
|
57
|
+
Qe.jobs.clear
|
58
|
+
end
|
59
|
+
end
|
data/lib/qe/version.rb
ADDED
data/lib/qe/worker.rb
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
module Qe
|
2
|
+
module Worker
|
3
|
+
def self.included(base)
|
4
|
+
base.class_eval do
|
5
|
+
include InstanceMethods
|
6
|
+
extend ClassMethods
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
module InstanceMethods
|
11
|
+
def initialize(options)
|
12
|
+
@options = options
|
13
|
+
end
|
14
|
+
|
15
|
+
# Return options that were provided when
|
16
|
+
# adding job to the queue.
|
17
|
+
def options
|
18
|
+
@options
|
19
|
+
end
|
20
|
+
|
21
|
+
# Set before hook.
|
22
|
+
def before
|
23
|
+
end
|
24
|
+
|
25
|
+
# Set after hook.
|
26
|
+
def after
|
27
|
+
end
|
28
|
+
|
29
|
+
# Set the error hook.
|
30
|
+
def error(error)
|
31
|
+
raise error
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
module ClassMethods
|
36
|
+
# Enqueue job on given worker class.
|
37
|
+
def enqueue(options = {})
|
38
|
+
Qe.adapter.enqueue(self, options)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Set the queue name when receiving on argument.
|
42
|
+
# Return queue name otherwise.
|
43
|
+
def queue(*args)
|
44
|
+
@queue = args.first unless args.empty?
|
45
|
+
(@queue || :default).to_s
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Find a worker by its name.
|
50
|
+
# If worker constant is not found, raises a +NameError+
|
51
|
+
# exception.
|
52
|
+
def self.find(name)
|
53
|
+
name.split("::").reduce(Object) do |const, name|
|
54
|
+
const.const_get(name)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Perform the specified worker if given options.
|
59
|
+
def self.perform(worker_name, options)
|
60
|
+
find(worker_name).new(options).tap do |job|
|
61
|
+
begin
|
62
|
+
job.before
|
63
|
+
job.perform
|
64
|
+
job.after
|
65
|
+
rescue Exception => error
|
66
|
+
job.error(error)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|