qsagi 0.0.3 → 0.1.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.
@@ -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