qsagi 0.0.3 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 5abc20946ce817158e4ccbcd23c4297457dfa967
4
+ data.tar.gz: b454ffe9a3616b7796f6c4b83d6b7f003b8fe8da
5
+ SHA512:
6
+ metadata.gz: 4c29980ce4a2bb9bfb1a069731f6568c3f09354d3da65499fee67cd08bfa487e7f8504eb7447103a0f46e3d04c958722e5d7e75f708fa9181b7b95a6fb6bbe28
7
+ data.tar.gz: 4d9dad24f276baec8bd09c2b2d5c9ba19cc25538b452e83f0c9a87fa80da66b56a65d2de19f43322324c0fd1ee7cd210f739661a5df51d822645d5454e8411f6
@@ -5,4 +5,6 @@ require "qsagi/message"
5
5
  require "qsagi/default_serializer"
6
6
  require "qsagi/json_serializer"
7
7
  require "qsagi/queue"
8
+ require "qsagi/standard_queue"
9
+ require "qsagi/confirmed_queue"
8
10
  require "qsagi/version"
@@ -0,0 +1,66 @@
1
+ module Qsagi
2
+ class ConfirmedQueue
3
+ attr_reader :nacked_messages
4
+
5
+ def initialize(queue)
6
+ @queue = queue
7
+ @nacked_messages = []
8
+ @unconfirmed_messages = {}
9
+ @wait_for_confirms = false
10
+ end
11
+
12
+ def connect
13
+ @queue.connect
14
+ _confirm_select
15
+ end
16
+
17
+ def disconnect
18
+ @queue.disconnect
19
+ end
20
+
21
+ def push(message)
22
+ @unconfirmed_messages[_channel.next_publish_seq_no] = message
23
+ @queue.push(message)
24
+ @wait_for_confirms = true
25
+ end
26
+
27
+ def pop(opts={})
28
+ @queue.pop(opts)
29
+ end
30
+
31
+ def wait_for_confirms
32
+ _channel.wait_for_confirms if _wait_for_confirms?
33
+ end
34
+
35
+ def _channel
36
+ @queue.channel
37
+ end
38
+
39
+ def _confirm_messages!(attributes)
40
+ if attributes[:is_nack]
41
+ if attributes[:multiple]
42
+ @nacked_messages += @unconfirmed_messages.select { |k,v| k <= attributes[:delivery_tag] }.values
43
+ @unconfirmed_messages.delete_if { |k,v| k <= attributes[:delivery_tag] }
44
+ else
45
+ @nacked_messages << @unconfirmed_messages.delete(attributes[:delivery_tag])
46
+ end
47
+ else
48
+ if attributes[:multiple]
49
+ @unconfirmed_messages.delete_if { |k,v| k <= attributes[:delivery_tag] }
50
+ else
51
+ @unconfirmed_messages.delete(attributes[:delivery_tag])
52
+ end
53
+ end
54
+ end
55
+
56
+ def _confirm_select
57
+ _channel.confirm_select lambda { |delivery_tag, multiple, is_nack|
58
+ _confirm_messages!(:delivery_tag => delivery_tag, :multiple => multiple, :is_nack => is_nack)
59
+ }
60
+ end
61
+
62
+ def _wait_for_confirms?
63
+ @wait_for_confirms
64
+ end
65
+ end
66
+ end
@@ -2,21 +2,17 @@ module Qsagi
2
2
  class Message
3
3
  attr_reader :payload
4
4
 
5
- def initialize(message, payload)
6
- @message = message
5
+ def initialize(delivery_details, payload)
6
+ @delivery_details = delivery_details
7
7
  @payload = payload
8
8
  end
9
9
 
10
10
  def delivery_tag
11
- _delivery_details[:delivery_tag]
11
+ @delivery_details.delivery_tag
12
12
  end
13
13
 
14
14
  def exchange
15
- _delivery_details[:exchange]
16
- end
17
-
18
- def _delivery_details
19
- @delivery_details ||= @message.fetch(:delivery_details, {})
15
+ @delivery_details.exchange
20
16
  end
21
17
  end
22
18
  end
@@ -1,58 +1,13 @@
1
1
  module Qsagi
2
2
  module Queue
3
- def ack(message)
4
- @queue.ack(:delivery_tag => message.delivery_tag)
5
- end
6
-
7
- def clear
8
- loop do
9
- message = @queue.pop
10
- break if message[:payload] == :queue_empty
11
- end
12
- end
13
-
14
- def connect
15
- @client = Bunny.new(:host => self.class.host, :port => self.class.port, :heartbeat => self.class.heartbeat)
16
- @client.start
17
- @queue = @client.queue(self.class.queue_name, :durable => true, :arguments => {"x-ha-policy" => "all"})
18
- @exchange = @client.exchange(self.class._exchange)
19
- @queue.bind(@exchange, :key => self.class.queue_name) unless self.class._exchange.empty?
20
- end
21
-
22
- def disconnect
23
- @client.send(:close_socket) unless @client.nil?
24
- end
25
-
26
- def length
27
- @queue.status[:message_count]
28
- end
29
-
30
- def pop(options = {})
31
- auto_ack = options.fetch(:auto_ack, true)
32
- message = @queue.pop(:ack => !auto_ack)
33
-
34
- unless message[:payload] == :queue_empty
35
- self.class._message_class.new(message, self.class._serializer.deserialize(message[:payload]))
36
- end
37
- end
38
-
39
- def push(message)
40
- serialized_message = self.class._serializer.serialize(message)
41
- @exchange.publish(serialized_message, :key => @queue.name, :persistent => true, :mandatory => true)
42
- end
43
-
44
- def reconnect
45
- disconnect
46
- connect
47
- end
48
-
49
3
  def self.included(klass)
50
4
  klass.extend ClassMethods
51
5
  end
52
6
 
53
7
  module ClassMethods
54
- def connect(&block)
55
- queue = new
8
+ def connect(opts={}, &block)
9
+ options = default_options.merge(opts)
10
+ queue = _queue(options)
56
11
 
57
12
  begin
58
13
  queue.connect
@@ -63,8 +18,36 @@ module Qsagi
63
18
  end
64
19
  end
65
20
 
66
- def exchange(exchange)
21
+ def _queue(options)
22
+ standard_queue = StandardQueue.new(options)
23
+ if options[:queue_type] == :confirmed
24
+ ConfirmedQueue.new(standard_queue)
25
+ else
26
+ standard_queue
27
+ end
28
+ end
29
+
30
+ def default_options
31
+ {
32
+ :host => host,
33
+ :port => port,
34
+ :queue_type => :standard,
35
+ :heartbeat => heartbeat,
36
+ :message_class => _message_class,
37
+ :queue_name => queue_name,
38
+ :durable => true,
39
+ :queue_arguments => {"x-ha-policy" => "all"},
40
+ :persistent => true,
41
+ :mandatory => true,
42
+ :serializer => _serializer,
43
+ :exchange_options => _exchange_options,
44
+ :exchange => _exchange
45
+ }
46
+ end
47
+
48
+ def exchange(exchange, options = {})
67
49
  @exchange = exchange
50
+ @exchange_options = {:type => :direct}.merge(options)
68
51
  end
69
52
 
70
53
  def message_class(message_class)
@@ -79,6 +62,10 @@ module Qsagi
79
62
  @exchange || ""
80
63
  end
81
64
 
65
+ def _exchange_options
66
+ @exchange_options || {}
67
+ end
68
+
82
69
  def _message_class
83
70
  @message_class || Qsagi::Message
84
71
  end
@@ -0,0 +1,70 @@
1
+ module Qsagi
2
+ class StandardQueue
3
+ attr_reader :channel, :options
4
+
5
+ def initialize(options={})
6
+ @options = options
7
+ end
8
+
9
+ def ack(message)
10
+ @channel.ack(message.delivery_tag, false)
11
+ end
12
+
13
+ def reject(message, options={})
14
+ @channel.reject(message.delivery_tag, options.fetch(:requeue, true))
15
+ end
16
+
17
+ def clear
18
+ @queue.purge
19
+ end
20
+
21
+ def connect
22
+ @client = Bunny.new(
23
+ :host => options[:host],
24
+ :port => options[:port],
25
+ :heartbeat => options[:heartbeat],
26
+ :continuation_timeout => options[:continuation_timeout]
27
+ )
28
+ @client.start
29
+ @channel = @client.create_channel
30
+ @exchange = @channel.exchange(options[:exchange], options[:exchange_options])
31
+ @queue = @channel.queue(options[:queue_name], :durable => options[:durable], :arguments => options[:queue_arguments])
32
+ @queue.bind(@exchange, :routing_key => options[:queue_name]) unless options[:exchange].empty?
33
+ end
34
+
35
+ def disconnect
36
+ @client.close unless @client.nil?
37
+ end
38
+
39
+ def length
40
+ @queue.status[:message_count]
41
+ end
42
+
43
+ def pop(options = {})
44
+ auto_ack = options.fetch(:auto_ack, true)
45
+ delivery_info, properties, message = @queue.pop(:ack => !auto_ack)
46
+
47
+ unless message.nil?
48
+ _message_class.new(delivery_info, _serializer.deserialize(message))
49
+ end
50
+ end
51
+
52
+ def push(message)
53
+ serialized_message = options[:serializer].serialize(message)
54
+ @exchange.publish(serialized_message, :routing_key => @queue.name, :persistent => options[:persistent], :mandatory => options[:mandatory])
55
+ end
56
+
57
+ def reconnect
58
+ disconnect
59
+ connect
60
+ end
61
+
62
+ def _message_class
63
+ options[:message_class]
64
+ end
65
+
66
+ def _serializer
67
+ options[:serializer]
68
+ end
69
+ end
70
+ end
@@ -1,3 +1,3 @@
1
1
  module Qsagi
2
- VERSION = "0.0.3"
2
+ VERSION = "0.1.0"
3
3
  end
@@ -17,6 +17,6 @@ Gem::Specification.new do |gem|
17
17
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
18
  gem.require_paths = ["lib"]
19
19
 
20
- gem.add_dependency "bunny", "~> 0.8.0"
20
+ gem.add_dependency "bunny", "~> 1.1.0"
21
21
  gem.add_dependency "json", "~> 1.7.0"
22
22
  end
@@ -0,0 +1,33 @@
1
+ require "spec_helper"
2
+
3
+ describe Qsagi::ConfirmedQueue do
4
+ describe "_confirm_messages!" do
5
+ it "adds a single nacked message to nacked_messages" do
6
+ queue = Qsagi::ConfirmedQueue.new(nil)
7
+ queue.instance_variable_set(:@unconfirmed_messages, {2 => "message"})
8
+ queue._confirm_messages!(:delivery_tag => 2, :multiple => false, :is_nack => true)
9
+ queue.nacked_messages.should == ["message"]
10
+ end
11
+
12
+ it "adds multiple nacked messages to nacked_messages" do
13
+ queue = Qsagi::ConfirmedQueue.new(nil)
14
+ queue.instance_variable_set(:@unconfirmed_messages, {2 => "message", 3 => "other_message"})
15
+ queue._confirm_messages!(:delivery_tag => 3, :multiple => true, :is_nack => true)
16
+ queue.nacked_messages.should == ["message", "other_message"]
17
+ end
18
+
19
+ it "removes a single acked message from unconfirmed_messages" do
20
+ queue = Qsagi::ConfirmedQueue.new(nil)
21
+ queue.instance_variable_set(:@unconfirmed_messages, {2 => "message", 3 => "other_message"})
22
+ queue._confirm_messages!(:delivery_tag => 2, :multiple => false, :is_nack => false)
23
+ queue.instance_variable_get(:@unconfirmed_messages).should == {3 => "other_message"}
24
+ end
25
+
26
+ it "removes multiple acked messages from unconfirmed_messages" do
27
+ queue = Qsagi::ConfirmedQueue.new(nil)
28
+ queue.instance_variable_set(:@unconfirmed_messages, {2 => "message", 3 => "other_message", 4 => "this_dude"})
29
+ queue._confirm_messages!(:delivery_tag => 3, :multiple => true, :is_nack => false)
30
+ queue.instance_variable_get(:@unconfirmed_messages).should == {4 => "this_dude"}
31
+ end
32
+ end
33
+ end
@@ -3,39 +3,21 @@ require "spec_helper"
3
3
  describe Qsagi::Message do
4
4
  describe "delivery_tag" do
5
5
  it "returns the delivery_tag" do
6
- data = {
7
- :delivery_details => {:delivery_tag => "tag"},
8
- :payload => "raw_payload"
9
- }
10
- Qsagi::Message.new(data, :parsed_payload).delivery_tag.should == "tag"
11
- end
12
-
13
- it "gracefully handles no delivery details" do
14
- Qsagi::Message.new({}, :parsed_payload).delivery_tag.should be_nil
6
+ delivery_details = OpenStruct.new(:delivery_tag => "tag")
7
+ Qsagi::Message.new(delivery_details, :parsed_payload).delivery_tag.should == "tag"
15
8
  end
16
9
  end
17
10
 
18
11
  describe "exchange" do
19
12
  it "returns the exchange" do
20
- data = {
21
- :delivery_details => {:exchange => "the_exchange"},
22
- :payload => "raw_payload"
23
- }
24
- Qsagi::Message.new(data, :parsed_payload).exchange.should == "the_exchange"
25
- end
26
-
27
- it "gracefully handles no delivery details" do
28
- Qsagi::Message.new({}, :parsed_payload).exchange.should be_nil
13
+ delivery_details = OpenStruct.new(:exchange => "the_exchange")
14
+ Qsagi::Message.new(delivery_details, :parsed_payload).exchange.should == "the_exchange"
29
15
  end
30
16
  end
31
17
 
32
18
  describe "payload" do
33
19
  it "returns the parsed payload" do
34
- data = {
35
- :delivery_details => {:delivery_tag => "tag"},
36
- :payload => "raw_payload"
37
- }
38
- Qsagi::Message.new(data, :parsed_payload).payload.should == :parsed_payload
20
+ Qsagi::Message.new(:deliver_details, :parsed_payload).payload.should == :parsed_payload
39
21
  end
40
22
  end
41
23
  end
@@ -12,7 +12,7 @@ describe Qsagi::Queue do
12
12
  describe "self.exchange" do
13
13
  it "configures the exchange" do
14
14
  queue_on_exchange1 = Class.new(ExampleQueue) do
15
- exchange "exchange1"
15
+ exchange "exchange1", :type => :direct
16
16
  end
17
17
  queue_on_exchange2 = Class.new(ExampleQueue) do
18
18
  exchange "exchange2"
@@ -54,6 +54,30 @@ describe Qsagi::Queue do
54
54
  end
55
55
  end
56
56
 
57
+ describe "reject" do
58
+ it "rejects the message and places it back on the queue" do
59
+ ExampleQueue.connect do |queue|
60
+ queue.push("message")
61
+ message = queue.pop(:auto_ack => false)
62
+ queue.reject(message, :requeue => true)
63
+ end
64
+ ExampleQueue.connect do |queue|
65
+ queue.length.should == 1
66
+ end
67
+ end
68
+
69
+ it "rejects and discards the message" do
70
+ ExampleQueue.connect do |queue|
71
+ queue.push("message")
72
+ message = queue.pop(:auto_ack => false)
73
+ queue.reject(message, :requeue => false)
74
+ end
75
+ ExampleQueue.connect do |queue|
76
+ queue.length.should == 0
77
+ end
78
+ end
79
+ end
80
+
57
81
  describe "pop" do
58
82
  it "automatically acks if :auto_ack is not passed in" do
59
83
  ExampleQueue.connect do |queue|
@@ -84,4 +108,14 @@ describe Qsagi::Queue do
84
108
  end
85
109
  end
86
110
  end
111
+
112
+ describe "queue_type confirmed" do
113
+ it "should use a ConfirmedQueue" do
114
+ ExampleQueue.connect(:queue_type => :confirmed) do |queue|
115
+ queue.push("message")
116
+ queue.wait_for_confirms
117
+ queue.nacked_messages.size.should == 0
118
+ end
119
+ end
120
+ end
87
121
  end
@@ -1,4 +1,5 @@
1
1
  require "qsagi"
2
+ require "ostruct"
2
3
 
3
4
  class ExampleQueue
4
5
  include Qsagi::Queue
@@ -22,10 +23,8 @@ end
22
23
 
23
24
  RSpec.configure do |c|
24
25
  c.before(:each) do
25
- client = Bunny.new(:host => ExampleQueue.host, :port => ExampleQueue.port)
26
- client.start
27
- queue = client.queue(ExampleQueue.queue_name, :durable => true, :arguments => {"x-ha-policy" => "all"})
28
- queue.delete rescue nil
29
- client.send(:close_socket)
26
+ ExampleQueue.connect do |queue|
27
+ queue.clear
28
+ end
30
29
  end
31
30
  end
metadata CHANGED
@@ -1,36 +1,32 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: qsagi
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
5
- prerelease:
4
+ version: 0.1.0
6
5
  platform: ruby
7
6
  authors:
8
7
  - Braintree
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2013-05-29 00:00:00.000000000 Z
11
+ date: 2014-05-15 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
14
  name: bunny
16
15
  requirement: !ruby/object:Gem::Requirement
17
- none: false
18
16
  requirements:
19
17
  - - ~>
20
18
  - !ruby/object:Gem::Version
21
- version: 0.8.0
19
+ version: 1.1.0
22
20
  type: :runtime
23
21
  prerelease: false
24
22
  version_requirements: !ruby/object:Gem::Requirement
25
- none: false
26
23
  requirements:
27
24
  - - ~>
28
25
  - !ruby/object:Gem::Version
29
- version: 0.8.0
26
+ version: 1.1.0
30
27
  - !ruby/object:Gem::Dependency
31
28
  name: json
32
29
  requirement: !ruby/object:Gem::Requirement
33
- none: false
34
30
  requirements:
35
31
  - - ~>
36
32
  - !ruby/object:Gem::Version
@@ -38,7 +34,6 @@ dependencies:
38
34
  type: :runtime
39
35
  prerelease: false
40
36
  version_requirements: !ruby/object:Gem::Requirement
41
- none: false
42
37
  requirements:
43
38
  - - ~>
44
39
  - !ruby/object:Gem::Version
@@ -56,41 +51,44 @@ files:
56
51
  - README.md
57
52
  - Rakefile
58
53
  - lib/qsagi.rb
54
+ - lib/qsagi/confirmed_queue.rb
59
55
  - lib/qsagi/default_serializer.rb
60
56
  - lib/qsagi/json_serializer.rb
61
57
  - lib/qsagi/message.rb
62
58
  - lib/qsagi/queue.rb
59
+ - lib/qsagi/standard_queue.rb
63
60
  - lib/qsagi/version.rb
64
61
  - qsagi.gemspec
62
+ - spec/qsagi/confirmed_queue_spec.rb
65
63
  - spec/qsagi/json_serializer_spec.rb
66
64
  - spec/qsagi/message_spec.rb
67
65
  - spec/qsagi/queue_spec.rb
68
66
  - spec/spec_helper.rb
69
67
  homepage: https://github.com/braintree/qsagi
70
68
  licenses: []
69
+ metadata: {}
71
70
  post_install_message:
72
71
  rdoc_options: []
73
72
  require_paths:
74
73
  - lib
75
74
  required_ruby_version: !ruby/object:Gem::Requirement
76
- none: false
77
75
  requirements:
78
- - - ! '>='
76
+ - - '>='
79
77
  - !ruby/object:Gem::Version
80
78
  version: '0'
81
79
  required_rubygems_version: !ruby/object:Gem::Requirement
82
- none: false
83
80
  requirements:
84
- - - ! '>='
81
+ - - '>='
85
82
  - !ruby/object:Gem::Version
86
83
  version: '0'
87
84
  requirements: []
88
85
  rubyforge_project:
89
- rubygems_version: 1.8.23
86
+ rubygems_version: 2.2.2
90
87
  signing_key:
91
- specification_version: 3
88
+ specification_version: 4
92
89
  summary: A friendly way to talk to RabbitMQ
93
90
  test_files:
91
+ - spec/qsagi/confirmed_queue_spec.rb
94
92
  - spec/qsagi/json_serializer_spec.rb
95
93
  - spec/qsagi/message_spec.rb
96
94
  - spec/qsagi/queue_spec.rb