mb-minion 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +30 -0
- data/examples/batch.rb +31 -0
- data/examples/batch_wait.rb +36 -0
- data/examples/batch_wait_2.rb +52 -0
- data/examples/math.rb +42 -0
- data/examples/sandwich.rb +38 -0
- data/examples/sandwich_batch.rb +44 -0
- data/examples/when.rb +34 -0
- data/lib/mb-ext/string.rb +7 -0
- data/lib/mb-minion.rb +231 -0
- data/lib/mb-minion/handler.rb +177 -0
- data/lib/mb-minion/message.rb +58 -0
- data/lib/mb-minion/version.rb +4 -0
- data/spec/minion/handler_spec.rb +147 -0
- data/spec/minion/message_spec.rb +38 -0
- data/spec/minion_spec.rb +238 -0
- data/spec/spec_helper.rb +11 -0
- metadata +201 -0
@@ -0,0 +1,177 @@
|
|
1
|
+
require 'pp'
|
2
|
+
# encoding: utf-8
|
3
|
+
module Minion
|
4
|
+
class Handler
|
5
|
+
attr_reader :queue_name, :block, :batch_size, :wait
|
6
|
+
|
7
|
+
# Executes the handler. Will subscribe to a queue or unsubscribe to it
|
8
|
+
# depending on the conditions.
|
9
|
+
#
|
10
|
+
# @example Execute the handler.
|
11
|
+
# handler.execute
|
12
|
+
def execute
|
13
|
+
subscribable? ? subscribe : unsubscribe
|
14
|
+
end
|
15
|
+
|
16
|
+
# Instantiate the new handler. Takes a queue name and optional lambda to
|
17
|
+
# determine conditionally if a queue is subscribable.
|
18
|
+
#
|
19
|
+
# @example Create the new handler.
|
20
|
+
# Handler.new("minion.test")
|
21
|
+
#
|
22
|
+
# @param [ String ] queue_name The name of the queue.
|
23
|
+
# @param [ Hash ]
|
24
|
+
# @option options [ lambda ] :when The block for conditionally subscribing.
|
25
|
+
# @option options [ fixnum ] :batch_size The number of elements per batch
|
26
|
+
# @option options [ symbol ] :map The type of map operation: fanout or reduce
|
27
|
+
def initialize(queue_name, block, options = {})
|
28
|
+
@queue_name, @block = queue_name, block
|
29
|
+
@subscribable = options[:when]
|
30
|
+
@batch_size = options[:batch_size]
|
31
|
+
@wait = options[:wait] || false
|
32
|
+
raise ArgumentError, "wait parameter makes no sense without a batch_size" if (@wait && ! @batch_size)
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
# Returns true if the handler is already subscribed to the queue.
|
38
|
+
#
|
39
|
+
# @example Is the handler running?
|
40
|
+
# handler.running?
|
41
|
+
#
|
42
|
+
# @return [ true, false ] Is the handler running?
|
43
|
+
def running?
|
44
|
+
!!@running
|
45
|
+
end
|
46
|
+
|
47
|
+
# Determines if the queue is able to be subscribed to.
|
48
|
+
#
|
49
|
+
# @example Is the queue subscribable?
|
50
|
+
# handler.subscribable?
|
51
|
+
def subscribable?
|
52
|
+
@subscribable ? @subscribable.call : true
|
53
|
+
end
|
54
|
+
|
55
|
+
# Subscribe to the queue. Will do so if the handler is not already
|
56
|
+
# subscribed.
|
57
|
+
#
|
58
|
+
# @example Subscribe to the queue.
|
59
|
+
# handler.subscribe
|
60
|
+
def subscribe
|
61
|
+
unless running?
|
62
|
+
Minion.info("Subscribing to #{queue_name}")
|
63
|
+
chan = AMQP::Channel.new
|
64
|
+
chan.prefetch(1)
|
65
|
+
queue = chan.queue(queue_name, :durable => true, :auto_delete => false)
|
66
|
+
if batch_size && batch_size > 1
|
67
|
+
process_batch(queue)
|
68
|
+
else
|
69
|
+
process_single_message(queue)
|
70
|
+
end
|
71
|
+
@running = true
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
# Process a multiple messages from a queue as a batch
|
77
|
+
#
|
78
|
+
# @example Subscribe to the queue.
|
79
|
+
# handler.process_batch(queue)
|
80
|
+
#
|
81
|
+
# @param [ AMQP::Queue ]
|
82
|
+
#
|
83
|
+
def process_batch(queue)
|
84
|
+
# Our batch message will have an array for it's content
|
85
|
+
msg = Message.new
|
86
|
+
queue.subscribe(:ack => true) do |h, m|
|
87
|
+
return if AMQP.closing?
|
88
|
+
Minion.info("Received: #{queue_name}:#{m}, #{h}")
|
89
|
+
args = decode(m)
|
90
|
+
|
91
|
+
# All messages in the batch get the callbacks from
|
92
|
+
# the first message. This is why when using chained
|
93
|
+
# callbacks on batches, you always have to use the
|
94
|
+
# same combo of callback-queues!
|
95
|
+
msg.callbacks = args['callbacks']
|
96
|
+
msg.batch << args['content']
|
97
|
+
h.ack # acks are useless in batch-mode.
|
98
|
+
# You'll have to make sure you requeue manually
|
99
|
+
if (msg.batch.size == batch_size) || process_anyway?
|
100
|
+
msg.content = block.call(msg)
|
101
|
+
msg.callback
|
102
|
+
msg.batch.clear
|
103
|
+
end
|
104
|
+
Minion.execute_handlers
|
105
|
+
end
|
106
|
+
rescue Object => e
|
107
|
+
Minion.alert(e)
|
108
|
+
end
|
109
|
+
|
110
|
+
# Process a single message from a queue
|
111
|
+
#
|
112
|
+
# @example Subscribe to the queue.
|
113
|
+
# handler.process_single_message(queue)
|
114
|
+
#
|
115
|
+
# @param [ AMQP::Queue ]
|
116
|
+
#
|
117
|
+
def process_single_message(queue)
|
118
|
+
queue.subscribe(:ack => true) do |h, m|
|
119
|
+
return if AMQP.closing?
|
120
|
+
Minion.info("Received: #{queue_name}:#{m}, #{h}")
|
121
|
+
msg = Message.new(m, h)
|
122
|
+
msg.content = block.call(msg)
|
123
|
+
h.ack
|
124
|
+
msg.callback
|
125
|
+
Minion.execute_handlers
|
126
|
+
end
|
127
|
+
rescue Object => e
|
128
|
+
Minion.alert(e)
|
129
|
+
end
|
130
|
+
|
131
|
+
# Get a string respresentation of the handler.
|
132
|
+
#
|
133
|
+
# @example Print out the string.
|
134
|
+
# handler.to_s
|
135
|
+
#
|
136
|
+
# @return [ String ] The handler as a string.
|
137
|
+
def to_s
|
138
|
+
"<handler queue_name=#{@queue_name} on=#{@on}>"
|
139
|
+
end
|
140
|
+
|
141
|
+
# Unsubscribe from the queue.
|
142
|
+
#
|
143
|
+
# @example Unsubscribe from the queue.
|
144
|
+
# handler.unsubscribe
|
145
|
+
def unsubscribe
|
146
|
+
Minion.info("Unsubscribing to #{queue_name}")
|
147
|
+
AMQP::Channel.new.queue(queue_name, :durable => true, :auto_delete => false).unsubscribe
|
148
|
+
@running = false
|
149
|
+
end
|
150
|
+
|
151
|
+
private
|
152
|
+
|
153
|
+
def decode(json)
|
154
|
+
defined?(ActiveSupport::JSON) ?
|
155
|
+
ActiveSupport::JSON.decode(json) : JSON.load(json)
|
156
|
+
end
|
157
|
+
|
158
|
+
# Determine if we should process a batch even if
|
159
|
+
# we haven't reached the batch_size
|
160
|
+
#
|
161
|
+
# @return [ Boolean ] if we should go ahead and process the batch
|
162
|
+
def process_anyway?
|
163
|
+
return false if Minion.message_count(queue_name) != 0 # there's work to be done!
|
164
|
+
case wait
|
165
|
+
when true then false # Wait indefinitely
|
166
|
+
when false then true # Don't wait at all
|
167
|
+
when Numeric
|
168
|
+
(0..wait).each do |i|
|
169
|
+
return false if Minion.message_count(queue_name) != 0
|
170
|
+
sleep 1
|
171
|
+
end
|
172
|
+
# Wait this many, then if the queue is still empty, go ahead
|
173
|
+
Minion.message_count(queue_name) == 0
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require File.join 'active_support', 'core_ext', 'module', 'delegation'
|
2
|
+
|
3
|
+
module Minion
|
4
|
+
class Message
|
5
|
+
attr_accessor :content, :callbacks, :headers, :batch
|
6
|
+
delegate :clear, :map, :each, :size, :count, :[], :each_with_index, :cycle, :shuffle, :to => :content
|
7
|
+
|
8
|
+
def initialize json="{}", header=nil
|
9
|
+
data = decode(json)
|
10
|
+
@headers = [header]
|
11
|
+
@callbacks = data['callbacks']
|
12
|
+
@content = data['content']
|
13
|
+
@batch = data['batch'] || []
|
14
|
+
end
|
15
|
+
|
16
|
+
def << data
|
17
|
+
@content << data
|
18
|
+
end
|
19
|
+
|
20
|
+
# Enqueue a job for the next callback in the chain
|
21
|
+
#
|
22
|
+
# @return void
|
23
|
+
def callback
|
24
|
+
headers.clear
|
25
|
+
if callbacks and not callbacks.empty?
|
26
|
+
queue_name = callbacks.shift
|
27
|
+
Minion.enqueue(queue_name, as_json)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
# Decode the json string into a hash.
|
34
|
+
#
|
35
|
+
# @example Decode the json.
|
36
|
+
# decode("{ field : "value" }")
|
37
|
+
#
|
38
|
+
# @param [ String ] json The json string.
|
39
|
+
#
|
40
|
+
# @return [ Hash ] The json as a hash.
|
41
|
+
def decode(json)
|
42
|
+
defined?(ActiveSupport::JSON) ?
|
43
|
+
ActiveSupport::JSON.decode(json) : JSON.load(json)
|
44
|
+
end
|
45
|
+
|
46
|
+
def as_json
|
47
|
+
{ 'callbacks' => callbacks,
|
48
|
+
'headers' => headers,
|
49
|
+
'content' => content
|
50
|
+
}
|
51
|
+
end
|
52
|
+
|
53
|
+
def to_json
|
54
|
+
JSON.dump(as_json || {}).force_encoding("ISO-8859-1")
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,147 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Minion::Handler do
|
4
|
+
|
5
|
+
before(:all) do
|
6
|
+
Minion.logger {}
|
7
|
+
end
|
8
|
+
|
9
|
+
let(:channel) do
|
10
|
+
stub.quacks_like(AMQP::Channel.allocate)
|
11
|
+
end
|
12
|
+
|
13
|
+
let(:queue) do
|
14
|
+
stub.quacks_like(AMQP::Queue.allocate)
|
15
|
+
end
|
16
|
+
|
17
|
+
describe "#execute" do
|
18
|
+
|
19
|
+
before do
|
20
|
+
channel.expects(:prefetch).at_most_once
|
21
|
+
AMQP::Channel.stubs(:new).returns(channel)
|
22
|
+
end
|
23
|
+
|
24
|
+
context "when the queue is subscribable" do
|
25
|
+
|
26
|
+
let(:handler) do
|
27
|
+
described_class.new("minion.test", lambda{ true })
|
28
|
+
end
|
29
|
+
|
30
|
+
context "when the handler is not already running" do
|
31
|
+
|
32
|
+
before do
|
33
|
+
channel.expects(:queue).with(
|
34
|
+
"minion.test", :durable => true, :auto_delete => false
|
35
|
+
).returns(queue)
|
36
|
+
end
|
37
|
+
|
38
|
+
it "subscribes to the queue" do
|
39
|
+
queue.expects(:subscribe)
|
40
|
+
handler.execute
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context "when the handler is running" do
|
45
|
+
|
46
|
+
before do
|
47
|
+
handler.instance_variable_set(:@running, true)
|
48
|
+
end
|
49
|
+
|
50
|
+
it "does not subscribe again" do
|
51
|
+
channel.expects(:queue).never
|
52
|
+
handler.execute
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
context "when the queue is not subscribable" do
|
58
|
+
|
59
|
+
let(:handler) do
|
60
|
+
described_class.new("minion.test", lambda{ true }, :when => lambda{ false })
|
61
|
+
end
|
62
|
+
|
63
|
+
before do
|
64
|
+
channel.expects(:queue).with(
|
65
|
+
"minion.test", :durable => true, :auto_delete => false
|
66
|
+
).returns(queue)
|
67
|
+
end
|
68
|
+
|
69
|
+
it "unsubscribes from the queue" do
|
70
|
+
queue.expects(:unsubscribe)
|
71
|
+
handler.execute
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
context "when wait parameter is specified" do
|
76
|
+
it "should raise an error" do
|
77
|
+
expect do
|
78
|
+
described_class.new("minion.test", lambda{ |batch| {"content" => true} }, :wait => true)
|
79
|
+
end.to raise_error(ArgumentError)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
context "when a batch size is specified" do
|
84
|
+
let(:block) do
|
85
|
+
lambda{ |batch| {"content" => true} }
|
86
|
+
end
|
87
|
+
|
88
|
+
let(:handler) do
|
89
|
+
described_class.new("minion.test", block, :batch_size => 10)
|
90
|
+
end
|
91
|
+
|
92
|
+
let(:header) do
|
93
|
+
stub.quacks_like(AMQP::Header.allocate)
|
94
|
+
end
|
95
|
+
|
96
|
+
let(:serialized) do
|
97
|
+
'{"content":{"field":"value"}}'
|
98
|
+
end
|
99
|
+
|
100
|
+
let(:batch) do
|
101
|
+
[header, serialized] * 10
|
102
|
+
end
|
103
|
+
|
104
|
+
before do
|
105
|
+
queue.expects(:subscribe).multiple_yields(batch)
|
106
|
+
channel.expects(:queue).with(
|
107
|
+
"minion.test", :durable => true, :auto_delete => false
|
108
|
+
).returns(queue)
|
109
|
+
Minion.expects(:execute_handlers)
|
110
|
+
header.expects(:ack)
|
111
|
+
end
|
112
|
+
|
113
|
+
it "calls once for 10 messages" do
|
114
|
+
block.expects(:call).once
|
115
|
+
handler.execute
|
116
|
+
end
|
117
|
+
|
118
|
+
context "when wait parameter is specified" do
|
119
|
+
let(:handler) do
|
120
|
+
described_class.new("minion.test", block, :batch_size => 10, :wait => true)
|
121
|
+
end
|
122
|
+
|
123
|
+
it "doesn't call for 9 messages" do
|
124
|
+
block.expects(:call).never
|
125
|
+
handler.execute
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
context "when wait paramter is numeric" do
|
130
|
+
let(:batch) do
|
131
|
+
[header, serialized] * 9
|
132
|
+
end
|
133
|
+
|
134
|
+
let(:handler) do
|
135
|
+
described_class.new("minion.test", block, :batch_size => 10, :wait => 2)
|
136
|
+
end
|
137
|
+
|
138
|
+
it "calls once for 9 messages after waiting a bit" do
|
139
|
+
block.expects(:call).once
|
140
|
+
handler.execute
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
|
146
|
+
end
|
147
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Minion::Message do
|
4
|
+
let(:header) do
|
5
|
+
stub.quacks_like(AMQP::Header.allocate)
|
6
|
+
end
|
7
|
+
|
8
|
+
let(:serialized) do
|
9
|
+
'{"content":{"field":"value"}, "callbacks":["minion.second", "minion.third"]}'
|
10
|
+
end
|
11
|
+
|
12
|
+
subject do
|
13
|
+
Minion::Message.new(serialized, header)
|
14
|
+
end
|
15
|
+
|
16
|
+
its(:content){ should eql({"field"=>"value"}) }
|
17
|
+
its(:callbacks){ should eql ["minion.second", "minion.third"] }
|
18
|
+
its(:headers){ should eql [header]}
|
19
|
+
|
20
|
+
context "when callback is executed" do
|
21
|
+
|
22
|
+
let(:data) do
|
23
|
+
{'callbacks' => ['minion.third'], 'headers' => [], 'content' => {'field' => 'value'}}
|
24
|
+
end
|
25
|
+
|
26
|
+
before do
|
27
|
+
subject.headers.clear
|
28
|
+
subject.headers.expects(:clear)
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should enqueue the next job" do
|
32
|
+
Minion.expects(:enqueue).with('minion.second', data)
|
33
|
+
subject.callback
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
data/spec/minion_spec.rb
ADDED
@@ -0,0 +1,238 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "spec_helper"
|
3
|
+
|
4
|
+
describe Minion do
|
5
|
+
|
6
|
+
let(:bunny) do
|
7
|
+
Bunny.new(Minion.config).tap do |bunny|
|
8
|
+
bunny.start
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
before(:all) do
|
13
|
+
Minion.logger {}
|
14
|
+
end
|
15
|
+
|
16
|
+
describe ".alert" do
|
17
|
+
|
18
|
+
context "when an error handler is provided" do
|
19
|
+
|
20
|
+
let(:error) do
|
21
|
+
RuntimeError.new("testing")
|
22
|
+
end
|
23
|
+
|
24
|
+
before do
|
25
|
+
Minion.error do |error|
|
26
|
+
error.message
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
after do
|
31
|
+
Minion.error
|
32
|
+
end
|
33
|
+
|
34
|
+
it "delegates to the handler" do
|
35
|
+
Minion.alert(error).should == "testing"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
context "when an error handler is not provided" do
|
40
|
+
|
41
|
+
let(:error) do
|
42
|
+
RuntimeError.new("testing")
|
43
|
+
end
|
44
|
+
|
45
|
+
it "raises the error" do
|
46
|
+
expect { Minion.alert(error) }.to raise_error(RuntimeError)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe ".enqueue" do
|
52
|
+
|
53
|
+
let(:queue) do
|
54
|
+
bunny.queue("minion.test", :durable => true, :auto_delete => false)
|
55
|
+
end
|
56
|
+
|
57
|
+
before do
|
58
|
+
queue.purge
|
59
|
+
end
|
60
|
+
|
61
|
+
context "when provided a string" do
|
62
|
+
|
63
|
+
context "when no data is provided" do
|
64
|
+
|
65
|
+
before do
|
66
|
+
Minion.enqueue("minion.test")
|
67
|
+
end
|
68
|
+
|
69
|
+
let(:message) do
|
70
|
+
JSON.parse(queue.pop[:payload])
|
71
|
+
end
|
72
|
+
|
73
|
+
it "adds empty json to the queue" do
|
74
|
+
message.should == {"content" => {}}
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
context "when nil data is provided" do
|
79
|
+
|
80
|
+
before do
|
81
|
+
Minion.enqueue("minion.test", nil)
|
82
|
+
end
|
83
|
+
|
84
|
+
let(:message) do
|
85
|
+
JSON.parse(queue.pop[:payload])
|
86
|
+
end
|
87
|
+
|
88
|
+
it "adds empty json to the queue" do
|
89
|
+
message.should == {"content" => nil}
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
context "when data is provided" do
|
94
|
+
|
95
|
+
context "when the data has no special characters" do
|
96
|
+
|
97
|
+
let(:data) do
|
98
|
+
{"content"=>{"field"=>"value"}}
|
99
|
+
end
|
100
|
+
|
101
|
+
before do
|
102
|
+
Minion.enqueue("minion.test", data)
|
103
|
+
end
|
104
|
+
|
105
|
+
let(:message) do
|
106
|
+
JSON.parse(queue.pop[:payload])
|
107
|
+
end
|
108
|
+
|
109
|
+
it "adds the json to the queue" do
|
110
|
+
message.should == data
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
context "when the data contains special characters" do
|
115
|
+
|
116
|
+
let(:data) do
|
117
|
+
{"content"=>{"field"=>"öüäßÖÜÄ"}}
|
118
|
+
end
|
119
|
+
|
120
|
+
before do
|
121
|
+
Minion.enqueue("minion.test", data)
|
122
|
+
end
|
123
|
+
|
124
|
+
let(:message) do
|
125
|
+
JSON.parse(queue.pop[:payload])
|
126
|
+
end
|
127
|
+
|
128
|
+
it "adds the json to the queue" do
|
129
|
+
message.should == data
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
context "when provided a nil queue" do
|
136
|
+
|
137
|
+
it "raises an error" do
|
138
|
+
expect { Minion.enqueue(nil, {}) }.to raise_error(RuntimeError)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
context "when passed an array" do
|
143
|
+
|
144
|
+
let(:first) do
|
145
|
+
bunny.queue("minion.first", :durable => true, :auto_delete => false)
|
146
|
+
end
|
147
|
+
|
148
|
+
before do
|
149
|
+
first.purge
|
150
|
+
end
|
151
|
+
|
152
|
+
context "when the array is empty" do
|
153
|
+
|
154
|
+
it "raises an error" do
|
155
|
+
expect { Minion.enqueue([], {}) }.to raise_error(RuntimeError)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
context "when the array has queue names" do
|
160
|
+
|
161
|
+
let(:data) do
|
162
|
+
{"field"=>"value"}
|
163
|
+
end
|
164
|
+
|
165
|
+
let(:serialized) do
|
166
|
+
{"content"=>{"field"=>"value"}, "callbacks"=>["minion.second", "minion.third"]}
|
167
|
+
end
|
168
|
+
|
169
|
+
before do
|
170
|
+
Minion.enqueue([ "minion.first", "minion.second", "minion.third" ], data)
|
171
|
+
end
|
172
|
+
|
173
|
+
let(:message) do
|
174
|
+
JSON.parse(first.pop[:payload])
|
175
|
+
end
|
176
|
+
|
177
|
+
it "adds the serialized data to the first queue" do
|
178
|
+
message.should == serialized
|
179
|
+
end
|
180
|
+
|
181
|
+
context "when the data has already been serialized" do
|
182
|
+
before do
|
183
|
+
Minion.enqueue([ "minion.first", "minion.second", "minion.third" ], serialized)
|
184
|
+
end
|
185
|
+
|
186
|
+
let(:message) do
|
187
|
+
JSON.parse(first.pop[:payload])
|
188
|
+
end
|
189
|
+
|
190
|
+
it "adds has the same serialized data in the first queue" do
|
191
|
+
message.should == serialized
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
|
199
|
+
describe ".error_handling" do
|
200
|
+
|
201
|
+
context "when nothing has been defined" do
|
202
|
+
|
203
|
+
it "returns nil" do
|
204
|
+
Minion.send(:error_handling).should be_nil
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
describe ".error" do
|
210
|
+
|
211
|
+
let(:block) do
|
212
|
+
lambda{ "testing" }
|
213
|
+
end
|
214
|
+
|
215
|
+
before do
|
216
|
+
Minion.error(&block)
|
217
|
+
end
|
218
|
+
|
219
|
+
it "sets the error handling to the provided block" do
|
220
|
+
Minion.send(:error_handling).should == block
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
describe ".info" do
|
225
|
+
|
226
|
+
let(:block) do
|
227
|
+
lambda{ |message| message }
|
228
|
+
end
|
229
|
+
|
230
|
+
before do
|
231
|
+
Minion.logger(&block)
|
232
|
+
end
|
233
|
+
|
234
|
+
it "delegates the logging to the provided block" do
|
235
|
+
Minion.info("testing").should == "testing"
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|