quebert 1.12.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +6 -0
- data/Gemfile +1 -1
- data/LICENSE.md +19 -0
- data/{README.rdoc → README.md} +13 -33
- data/lib/quebert.rb +6 -0
- data/lib/quebert/async_sender.rb +1 -0
- data/lib/quebert/async_sender/active_record.rb +5 -7
- data/lib/quebert/async_sender/instance.rb +21 -11
- data/lib/quebert/async_sender/object.rb +3 -2
- data/lib/quebert/async_sender/promise.rb +54 -0
- data/lib/quebert/job.rb +9 -19
- data/lib/quebert/version.rb +1 -1
- data/spec/async_sender_spec.rb +45 -13
- data/spec/job_spec.rb +34 -13
- data/spec/serializer_spec.rb +61 -35
- metadata +9 -7
- data/LICENSE +0 -20
data/.travis.yml
ADDED
data/Gemfile
CHANGED
data/LICENSE.md
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2010 Brad Gessler
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/{README.rdoc → README.md}
RENAMED
@@ -1,16 +1,18 @@
|
|
1
|
-
|
1
|
+
# Quebert
|
2
|
+
|
3
|
+
[![Build Status](https://travis-ci.org/polleverywhere/quebert.png?branch=master)](https://travis-ci.org/polleverywhere/quebert)
|
2
4
|
|
3
5
|
async_observer is great, but is dated and doesn't really support running jobs outside of the async_send idiom. Quebert is an attempt to mix how jobs are run in other popular worker queue frameworks, like resque and dj, with async_observer so that you can have it both ways.
|
4
6
|
|
5
|
-
|
7
|
+
# Why Quebert (or how is it different from DJ and Resque)?
|
6
8
|
|
7
9
|
Because it has really low latency. Other Ruby queuing frameworks, like DJ or Resque, have to poll their queue servers periodicly. You could think of it as a "pull" queue. Quebert is a "push" queue. It maintains a persistent connection with beanstalkd and when is enqueud, its instantly pushed to the workers and executed.
|
8
10
|
|
9
|
-
|
11
|
+
# Who uses it?
|
10
12
|
|
11
13
|
Quebert is a serious project. Its used in a production environment at Poll Everywhere to handle everything from SMS message processing to account downgrades.
|
12
14
|
|
13
|
-
|
15
|
+
# Features
|
14
16
|
|
15
17
|
* Multiple back-ends (InProcess, Sync, and Beanstalk)
|
16
18
|
* Rails/ActiveRecord integration similar to async_observer
|
@@ -22,11 +24,11 @@ Some features that are currently missing that I will soon add include:
|
|
22
24
|
* Rails plugin support (The AR integrations have to be done manually today)
|
23
25
|
* Auto-detecting serializers. Enhanced ClassRegistry to more efficiently look up serializers for objects.
|
24
26
|
|
25
|
-
|
27
|
+
# How to use
|
26
28
|
|
27
29
|
There are two ways to enqueue jobs with Quebert: through the Job itself, provided you set a default back-end for the job, or put it on the backend.
|
28
30
|
|
29
|
-
|
31
|
+
## Jobs
|
30
32
|
|
31
33
|
Quebert includes a Job class so you can implement how you want certain types of Jobs performed.
|
32
34
|
|
@@ -51,14 +53,14 @@ Then perform the jobs!
|
|
51
53
|
Quebert.backend.reserve.perform # => 6
|
52
54
|
Quebert.backend.reserve.perform # => 15
|
53
55
|
|
54
|
-
|
56
|
+
## Async Sender
|
55
57
|
|
56
58
|
Take any ol' class and include the Quebert::AsyncSender.
|
57
59
|
|
58
60
|
Quebert.backend = Quebert::Backend::InProcess.new
|
59
61
|
|
60
62
|
class Greeter
|
61
|
-
include Quebert::AsyncSender
|
63
|
+
include Quebert::AsyncSender::Class
|
62
64
|
|
63
65
|
def initialize(name)
|
64
66
|
@name = name
|
@@ -82,9 +84,9 @@ Remember the send method in ruby?
|
|
82
84
|
# ... time passes, you wait as greeter snores obnoxiously ...
|
83
85
|
# => "Oh! Hi Brad! Good morning."
|
84
86
|
|
85
|
-
What if the method takes a long time to run and you want to queue it?
|
87
|
+
What if the method takes a long time to run and you want to queue it? async.send it!
|
86
88
|
|
87
|
-
walmart_greeter.
|
89
|
+
walmart_greeter.async.sleep_and_greet("morning")
|
88
90
|
# ... do some shopping and come back later when the dude wakes up
|
89
91
|
|
90
92
|
Quebert figures out how to *serialize the class, throw it on a worker queue, re-instantiate it on the other side, and finish up the work.
|
@@ -94,29 +96,7 @@ Quebert figures out how to *serialize the class, throw it on a worker queue, re-
|
|
94
96
|
|
95
97
|
Does it work on Class methods? Yeah, that was easier than making instance methods work:
|
96
98
|
|
97
|
-
Quebert.
|
99
|
+
Quebert.async.budweiser_greeting("Corey")
|
98
100
|
Quebert.backend.reserve.perform # => "waazup Corey!"
|
99
101
|
|
100
102
|
* Only basic data types are included for serialization. Serializers may be customized to include support for different types.
|
101
|
-
|
102
|
-
= License
|
103
|
-
|
104
|
-
Copyright (c) 2010 Brad Gessler
|
105
|
-
|
106
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
107
|
-
of this software and associated documentation files (the "Software"), to deal
|
108
|
-
in the Software without restriction, including without limitation the rights
|
109
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
110
|
-
copies of the Software, and to permit persons to whom the Software is
|
111
|
-
furnished to do so, subject to the following conditions:
|
112
|
-
|
113
|
-
The above copyright notice and this permission notice shall be included in
|
114
|
-
all copies or substantial portions of the Software.
|
115
|
-
|
116
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
117
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
118
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
119
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
120
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
121
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
122
|
-
THE SOFTWARE.
|
data/lib/quebert.rb
CHANGED
@@ -32,6 +32,12 @@ module Quebert
|
|
32
32
|
def logger
|
33
33
|
config.logger
|
34
34
|
end
|
35
|
+
|
36
|
+
# Deprecation notice for code within block.
|
37
|
+
def deprecate(message, &block)
|
38
|
+
logger.warn "Quebert Deprecation Notice: #{message}"
|
39
|
+
block.call
|
40
|
+
end
|
35
41
|
end
|
36
42
|
|
37
43
|
# Register built-in Quebert backends
|
data/lib/quebert/async_sender.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
module Quebert
|
2
2
|
module AsyncSender
|
3
|
-
|
4
3
|
# I'm not sure if I want to do this or build serializers per type of object...
|
5
4
|
module ActiveRecord
|
6
5
|
class RecordJob < Job
|
@@ -10,17 +9,16 @@ module Quebert
|
|
10
9
|
end
|
11
10
|
|
12
11
|
def self.included(base)
|
13
|
-
base.send(:include,
|
12
|
+
base.send(:include, AsyncSender::Promise::DSL)
|
14
13
|
base.send(:include, AsyncSender::Object)
|
14
|
+
base.send(:include, InstanceMethods)
|
15
15
|
end
|
16
|
-
|
16
|
+
|
17
17
|
module InstanceMethods
|
18
|
-
|
19
|
-
|
20
|
-
RecordJob.new(self, meth, *args).enqueue
|
18
|
+
def build_job(meth, *args)
|
19
|
+
RecordJob.new(self, meth, *args)
|
21
20
|
end
|
22
21
|
end
|
23
22
|
end
|
24
|
-
|
25
23
|
end
|
26
24
|
end
|
@@ -3,16 +3,18 @@ module Quebert
|
|
3
3
|
# Perform jobs on instances of classes
|
4
4
|
module Instance
|
5
5
|
class InstanceJob < Job
|
6
|
-
def perform(klass,
|
7
|
-
Support.constantize(klass).new(*
|
6
|
+
def perform(klass, initialize_args, meth, *args)
|
7
|
+
Support.constantize(klass).new(*initialize_args).send(meth, *args)
|
8
8
|
end
|
9
9
|
end
|
10
|
-
|
10
|
+
|
11
11
|
def self.included(base)
|
12
12
|
# Its not as simple as including initialize in a class, we
|
13
13
|
# have to do some tricks to make it work so we can put the include
|
14
14
|
# before the initialize method as opposed to after. Ah, and thanks PivotalLabs for this.
|
15
|
-
base.
|
15
|
+
base.send(:include, AsyncSender::Promise::DSL)
|
16
|
+
base.send(:include, InstanceMethods)
|
17
|
+
base.send(:extend, ClassMethods)
|
16
18
|
base.overwrite_initialize
|
17
19
|
base.instance_eval do
|
18
20
|
def method_added(name)
|
@@ -21,12 +23,24 @@ module Quebert
|
|
21
23
|
end
|
22
24
|
end
|
23
25
|
end
|
24
|
-
|
25
|
-
|
26
|
-
|
26
|
+
|
27
|
+
module InstanceMethods
|
28
|
+
# Build a job that uses the @__initialize_args
|
29
|
+
def build_job(meth, *args)
|
30
|
+
InstanceJob.new(self.class.name, @__initialize_args, meth, *args)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Remember the args used to initialize the class so that
|
34
|
+
# we can serialize them into a Job.
|
35
|
+
def initialize_with_async_sender(*args)
|
36
|
+
initialize_without_async_sender(*(@__initialize_args = args))
|
37
|
+
end
|
27
38
|
end
|
28
39
|
|
29
40
|
module ClassMethods
|
41
|
+
# Hack into the class initialize method so that we can grab
|
42
|
+
# the arguments used to create an instance of the class that
|
43
|
+
# we can serialize into a job.
|
30
44
|
def overwrite_initialize
|
31
45
|
class_eval do
|
32
46
|
unless method_defined?(:initialize_with_async_sender)
|
@@ -42,10 +56,6 @@ module Quebert
|
|
42
56
|
end
|
43
57
|
end
|
44
58
|
end
|
45
|
-
|
46
|
-
def async_send(meth, *args)
|
47
|
-
InstanceJob.new(self.class.name, @_init_args, meth, *args).enqueue
|
48
|
-
end
|
49
59
|
end
|
50
60
|
end
|
51
61
|
end
|
@@ -9,12 +9,13 @@ module Quebert
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def self.included(base)
|
12
|
+
base.send(:extend, AsyncSender::Promise::DSL)
|
12
13
|
base.send(:extend, ClassMethods)
|
13
14
|
end
|
14
15
|
|
15
16
|
module ClassMethods
|
16
|
-
def
|
17
|
-
ObjectJob.new(self.name, meth, *args)
|
17
|
+
def build_job(meth, *args)
|
18
|
+
ObjectJob.new(self.name, meth, *args)
|
18
19
|
end
|
19
20
|
end
|
20
21
|
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Quebert
|
2
|
+
module AsyncSender
|
3
|
+
# Decorates AsyncSender classes with an #async() proxy. This seperates
|
4
|
+
# the concern of configuring job specific parameters, like :ttr, :delay, etc.
|
5
|
+
# from calling the method.
|
6
|
+
class Promise
|
7
|
+
attr_reader :target, :opts, :job
|
8
|
+
|
9
|
+
def initialize(target, opts={}, &block)
|
10
|
+
@target, @opts, @block = target, opts, block
|
11
|
+
self
|
12
|
+
end
|
13
|
+
|
14
|
+
# Proxies the method call from async to the target, then tries
|
15
|
+
# to build a job with the targets `build_job` function.
|
16
|
+
def method_missing(meth, *args)
|
17
|
+
if @target.respond_to? meth, true # The second `true` argument checks private methods.
|
18
|
+
# Create an instance of the job through the proxy and
|
19
|
+
# configure it with the options given to the proxy.
|
20
|
+
@block.call configure @target.build_job(meth, *args)
|
21
|
+
else
|
22
|
+
super
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Configures a job with the options provied to the Promise
|
27
|
+
# upon initialization.
|
28
|
+
def configure(job)
|
29
|
+
@opts.each do |attr, val|
|
30
|
+
job.send("#{attr}=", val)
|
31
|
+
end
|
32
|
+
job
|
33
|
+
end
|
34
|
+
|
35
|
+
# Methods/DSL that we mix into classes, objects, etc. so that we
|
36
|
+
# can easily enqueue jobs to these.
|
37
|
+
module DSL
|
38
|
+
def async(opts={})
|
39
|
+
Promise.new(self, opts) { |job| job.enqueue }
|
40
|
+
end
|
41
|
+
|
42
|
+
# Legacy way of enqueueing jobs.
|
43
|
+
def async_send(*args)
|
44
|
+
meth = args.shift
|
45
|
+
beanstalk = args.last.delete(:beanstalk) if args.last.is_a?(::Hash)
|
46
|
+
|
47
|
+
Quebert.deprecate "#async_send should be called via #{self.class.name}.async(#{beanstalk.inspect}).#{args.first}(#{args.map(&:inspect).join(', ')})" do
|
48
|
+
async(beanstalk || {}).send(meth, *args)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
data/lib/quebert/job.rb
CHANGED
@@ -14,7 +14,10 @@ module Quebert
|
|
14
14
|
HIGH = 0
|
15
15
|
end
|
16
16
|
|
17
|
+
# Delay a job for 0 seconds on the jobqueue
|
17
18
|
DEFAULT_JOB_DELAY = 0
|
19
|
+
|
20
|
+
# By default, the job should live for 10 seconds tops.
|
18
21
|
DEFAULT_JOB_TTR = 10
|
19
22
|
|
20
23
|
# A buffer time in seconds added to the Beanstalk TTR for Quebert to do its own job cleanup
|
@@ -22,10 +25,10 @@ module Quebert
|
|
22
25
|
# little longer so that Quebert can bury the job or schedule a retry with the appropriate delay
|
23
26
|
QUEBERT_TTR_BUFFER = 5
|
24
27
|
|
28
|
+
# Exceptions are used for signaling job status... ewww. Yank this out and
|
29
|
+
# replace with a more well thought out controller.
|
25
30
|
NotImplemented = Class.new(StandardError)
|
26
|
-
|
27
31
|
Action = Class.new(Exception)
|
28
|
-
|
29
32
|
Bury = Class.new(Action)
|
30
33
|
Delete = Class.new(Action)
|
31
34
|
Release = Class.new(Action)
|
@@ -33,24 +36,11 @@ module Quebert
|
|
33
36
|
Retry = Class.new(Action)
|
34
37
|
|
35
38
|
def initialize(*args)
|
36
|
-
opts = args.last.is_a?(::Hash) ? args.pop : nil
|
37
|
-
|
38
39
|
@priority = Job::Priority::MEDIUM
|
39
|
-
@delay
|
40
|
-
@ttr
|
41
|
-
|
42
|
-
|
43
|
-
beanstalk_opts = opts.delete(:beanstalk)
|
44
|
-
args << opts unless opts.empty?
|
45
|
-
|
46
|
-
if beanstalk_opts
|
47
|
-
@priority = beanstalk_opts[:priority] if beanstalk_opts[:priority]
|
48
|
-
@delay = beanstalk_opts[:delay] if beanstalk_opts[:delay]
|
49
|
-
@ttr = beanstalk_opts[:ttr] if beanstalk_opts[:ttr]
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
@args = args.dup.freeze
|
40
|
+
@delay = DEFAULT_JOB_DELAY
|
41
|
+
@ttr = DEFAULT_JOB_TTR
|
42
|
+
@args = args.dup.freeze
|
43
|
+
self
|
54
44
|
end
|
55
45
|
|
56
46
|
def perform(*args)
|
data/lib/quebert/version.rb
CHANGED
data/spec/async_sender_spec.rb
CHANGED
@@ -22,18 +22,50 @@ describe AsyncSender::Class do
|
|
22
22
|
def self.hi(name)
|
23
23
|
"hi #{name}!"
|
24
24
|
end
|
25
|
+
|
26
|
+
private
|
27
|
+
def bye(desc)
|
28
|
+
"bye #{@name}, you look #{desc}"
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.bye(name)
|
32
|
+
"bye #{name}!"
|
33
|
+
end
|
25
34
|
end
|
26
35
|
|
27
|
-
|
28
|
-
|
29
|
-
|
36
|
+
describe "#async_send" do
|
37
|
+
it "should async send class methods" do
|
38
|
+
Greeter.async_send(:hi, 'Jeannette')
|
39
|
+
@q.reserve.perform.should eql(Greeter.send(:hi, 'Jeannette'))
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should async send instance methods" do
|
43
|
+
Greeter.new("brad").async_send(:hi, 'stunning')
|
44
|
+
@q.reserve.perform.should eql(Greeter.new("brad").hi('stunning'))
|
45
|
+
end
|
30
46
|
end
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
47
|
+
|
48
|
+
describe "#async() promise" do
|
49
|
+
it "should async send class methods" do
|
50
|
+
Greeter.async.hi('Jeannette')
|
51
|
+
@q.reserve.perform.should eql(Greeter.send(:hi, 'Jeannette'))
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should async send instance methods" do
|
55
|
+
Greeter.new("brad").async.hi('stunning')
|
56
|
+
@q.reserve.perform.should eql(Greeter.new("brad").hi('stunning'))
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should async send private instance methods" do
|
60
|
+
Greeter.new("brad").async.send(:bye, 'stunning')
|
61
|
+
@q.reserve.perform.should eql(Greeter.new("brad").send(:bye, 'stunning'))
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should async send private class methods" do
|
65
|
+
Greeter.async.send(:bye, 'Jeannette')
|
66
|
+
@q.reserve.perform.should eql(Greeter.send(:bye, 'Jeannette'))
|
67
|
+
end
|
35
68
|
end
|
36
|
-
|
37
69
|
end
|
38
70
|
|
39
71
|
describe AsyncSender::ActiveRecord do
|
@@ -62,27 +94,27 @@ describe AsyncSender::ActiveRecord do
|
|
62
94
|
end
|
63
95
|
|
64
96
|
context "unpersisted" do
|
65
|
-
it "should
|
97
|
+
it "should async instance method" do
|
66
98
|
user = User.new do |u|
|
67
99
|
u.email = 'barf@jones.com'
|
68
100
|
u.first_name = "Barf"
|
69
101
|
u.send(:write_attribute, :last_name, "Jones")
|
70
102
|
end
|
71
|
-
user.
|
103
|
+
user.async.name
|
72
104
|
@q.reserve.perform.should eql("Barf Jones")
|
73
105
|
end
|
74
106
|
end
|
75
107
|
|
76
|
-
it "should
|
108
|
+
it "should async class method" do
|
77
109
|
email = "brad@bradgessler.com"
|
78
|
-
User.
|
110
|
+
User.async.emailizer(email)
|
79
111
|
@q.reserve.perform.should eql(email)
|
80
112
|
end
|
81
113
|
|
82
114
|
it "should async_send and successfully serialize param object" do
|
83
115
|
user = User.new(:first_name => 'Brad')
|
84
116
|
user2 = User.new(:first_name => 'Steel')
|
85
|
-
user.
|
117
|
+
user.async.email(user2)
|
86
118
|
@q.reserve.perform.first_name.should eql('Steel')
|
87
119
|
end
|
88
120
|
end
|
data/spec/job_spec.rb
CHANGED
@@ -83,21 +83,42 @@ describe Quebert::Job do
|
|
83
83
|
@q.drain!
|
84
84
|
end
|
85
85
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
86
|
+
describe "async promise DSL" do
|
87
|
+
it "should enqueue and honor beanstalk options" do
|
88
|
+
user = User.new(:first_name => "Steel")
|
89
|
+
user.async(:priority => 1, :delay => 2, :ttr => 300).email("somebody", nil, nil)
|
90
|
+
job = @q.reserve
|
91
|
+
job.beanstalk_job.pri.should eql(1)
|
92
|
+
job.beanstalk_job.delay.should eql(2)
|
93
|
+
job.beanstalk_job.ttr.should eql(300 + Job::QUEBERT_TTR_BUFFER)
|
94
|
+
end
|
95
|
+
|
96
|
+
it "should enqueue and honor beanstalk options" do
|
97
|
+
User.async(:priority => 1, :delay => 2, :ttr => 300).emailizer("somebody", nil, nil)
|
98
|
+
job = @q.reserve
|
99
|
+
job.beanstalk_job.pri.should eql(1)
|
100
|
+
job.beanstalk_job.delay.should eql(2)
|
101
|
+
job.beanstalk_job.ttr.should eql(300 + Job::QUEBERT_TTR_BUFFER)
|
102
|
+
end
|
93
103
|
end
|
94
104
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
105
|
+
describe "legacy async_send" do
|
106
|
+
it "should enqueue and honor beanstalk options" do
|
107
|
+
user = User.new(:first_name => "Steel")
|
108
|
+
user.async_send(:email, "somebody", nil, nil, :beanstalk => {:priority => 1, :delay => 2, :ttr => 300})
|
109
|
+
job = @q.reserve
|
110
|
+
job.beanstalk_job.pri.should eql(1)
|
111
|
+
job.beanstalk_job.delay.should eql(2)
|
112
|
+
job.beanstalk_job.ttr.should eql(300 + Job::QUEBERT_TTR_BUFFER)
|
113
|
+
end
|
114
|
+
|
115
|
+
it "should enqueue and honor beanstalk options" do
|
116
|
+
User.async_send(:emailizer, "somebody", nil, nil, :beanstalk => {:priority => 1, :delay => 2, :ttr => 300})
|
117
|
+
job = @q.reserve
|
118
|
+
job.beanstalk_job.pri.should eql(1)
|
119
|
+
job.beanstalk_job.delay.should eql(2)
|
120
|
+
job.beanstalk_job.ttr.should eql(300 + Job::QUEBERT_TTR_BUFFER)
|
121
|
+
end
|
101
122
|
end
|
102
123
|
end
|
103
124
|
end
|
data/spec/serializer_spec.rb
CHANGED
@@ -2,66 +2,92 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe Serializer::ActiveRecord do
|
4
4
|
context "persisted" do
|
5
|
-
|
6
|
-
|
7
|
-
end
|
8
|
-
|
5
|
+
let(:user) { User.create!(:first_name => 'Tom', :last_name => 'Jones') }
|
6
|
+
|
9
7
|
it "should serialize" do
|
10
|
-
h = Serializer::ActiveRecord.serialize(
|
8
|
+
h = Serializer::ActiveRecord.serialize(user)
|
11
9
|
h['model'].should eql('User')
|
12
10
|
h['attributes']['first_name'].should eql('Tom')
|
13
|
-
h['attributes']['id'].should eql(
|
11
|
+
h['attributes']['id'].should eql(user.id)
|
14
12
|
end
|
15
13
|
|
16
14
|
it "should deserialize" do
|
17
|
-
u = Serializer::ActiveRecord.deserialize(Serializer::ActiveRecord.serialize(
|
15
|
+
u = Serializer::ActiveRecord.deserialize(Serializer::ActiveRecord.serialize(user))
|
18
16
|
u.first_name.should eql('Tom')
|
19
|
-
u.id.should eql(
|
17
|
+
u.id.should eql(user.id)
|
20
18
|
end
|
21
19
|
end
|
22
20
|
|
23
21
|
context "unpersisted" do
|
24
|
-
|
25
|
-
|
26
|
-
end
|
27
|
-
|
22
|
+
let(:user) { User.new(:first_name => 'brad') }
|
23
|
+
|
28
24
|
it "should serialize" do
|
29
|
-
h = Serializer::ActiveRecord.serialize(
|
25
|
+
h = Serializer::ActiveRecord.serialize(user)
|
30
26
|
h['model'].should eql('User')
|
31
27
|
h['attributes']['first_name'].should eql('brad')
|
32
28
|
h['attributes']['id'].should be_nil
|
33
29
|
end
|
34
30
|
|
35
31
|
it "should deserialize" do
|
36
|
-
u = Serializer::ActiveRecord.deserialize(Serializer::ActiveRecord.serialize(
|
32
|
+
u = Serializer::ActiveRecord.deserialize(Serializer::ActiveRecord.serialize(user))
|
37
33
|
u.first_name.should eql('brad')
|
38
34
|
end
|
39
35
|
end
|
40
36
|
end
|
41
37
|
|
42
38
|
describe Serializer::Job do
|
43
|
-
|
44
|
-
|
45
|
-
|
39
|
+
let(:user) { User.new(:first_name => 'Brad') }
|
40
|
+
let(:args) { [100, user] }
|
41
|
+
let(:job) do
|
42
|
+
job = Job.new(*args)
|
43
|
+
job.priority = 1
|
44
|
+
job.delay = 2
|
45
|
+
job.ttr = 300
|
46
|
+
job
|
46
47
|
end
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
48
|
+
let(:serialized) { Serializer::Job.serialize(job) }
|
49
|
+
let(:deserialized) { Serializer::Job.deserialize(serialized) }
|
50
|
+
|
51
|
+
describe "#serialize" do
|
52
|
+
it "shold have job" do
|
53
|
+
serialized['job'].should eql('Quebert::Job')
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should have args" do
|
57
|
+
serialized['args'][0]['payload'].should eql(100)
|
58
|
+
serialized['args'][1]['payload'].should eql(Serializer::ActiveRecord.serialize(args[1]))
|
59
|
+
serialized['args'][1]['serializer'].should eql('Quebert::Serializer::ActiveRecord')
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should have priority" do
|
63
|
+
serialized['priority'].should eql(1)
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should have delay" do
|
67
|
+
serialized['delay'].should eql(2)
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should have ttr" do
|
71
|
+
serialized['ttr'].should eql(300)
|
72
|
+
end
|
57
73
|
end
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
74
|
+
|
75
|
+
describe "#deserialize" do
|
76
|
+
it "should have args" do
|
77
|
+
deserialized.args[0].should eql(100)
|
78
|
+
deserialized.args[1].first_name.should eql('Brad')
|
79
|
+
end
|
80
|
+
|
81
|
+
it "should have delay" do
|
82
|
+
deserialized.delay.should eql(2)
|
83
|
+
end
|
84
|
+
|
85
|
+
it "should have priority" do
|
86
|
+
deserialized.priority.should eql(1)
|
87
|
+
end
|
88
|
+
|
89
|
+
it "should have ttr" do
|
90
|
+
deserialized.ttr.should eql(300)
|
91
|
+
end
|
66
92
|
end
|
67
93
|
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: quebert
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 15
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
|
-
-
|
8
|
-
- 12
|
7
|
+
- 2
|
9
8
|
- 0
|
10
|
-
|
9
|
+
- 0
|
10
|
+
version: 2.0.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Brad Gessler
|
@@ -17,7 +17,7 @@ autorequire:
|
|
17
17
|
bindir: bin
|
18
18
|
cert_chain: []
|
19
19
|
|
20
|
-
date: 2013-
|
20
|
+
date: 2013-04-27 00:00:00 Z
|
21
21
|
dependencies:
|
22
22
|
- !ruby/object:Gem::Dependency
|
23
23
|
name: json
|
@@ -77,10 +77,11 @@ files:
|
|
77
77
|
- .gitignore
|
78
78
|
- .rbenv-version
|
79
79
|
- .rspec
|
80
|
+
- .travis.yml
|
80
81
|
- Gemfile
|
81
82
|
- Guardfile
|
82
|
-
- LICENSE
|
83
|
-
- README.
|
83
|
+
- LICENSE.md
|
84
|
+
- README.md
|
84
85
|
- Rakefile
|
85
86
|
- bin/quebert
|
86
87
|
- lib/quebert.rb
|
@@ -89,6 +90,7 @@ files:
|
|
89
90
|
- lib/quebert/async_sender/class.rb
|
90
91
|
- lib/quebert/async_sender/instance.rb
|
91
92
|
- lib/quebert/async_sender/object.rb
|
93
|
+
- lib/quebert/async_sender/promise.rb
|
92
94
|
- lib/quebert/backend.rb
|
93
95
|
- lib/quebert/backend/beanstalk.rb
|
94
96
|
- lib/quebert/backend/in_process.rb
|
data/LICENSE
DELETED
@@ -1,20 +0,0 @@
|
|
1
|
-
Copyright (c) 2009 Brad Gessler
|
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.
|