quebert 1.12.0 → 2.0.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.
- 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
|
+
[](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.
|