amqp-subscribe-many 0.1.1

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 ADDED
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
data/Gemfile ADDED
@@ -0,0 +1,12 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem "amqp", ">= 0.9.6"
4
+
5
+ group :development do
6
+ gem "bundler"
7
+ gem "minitest", ">= 3.0"
8
+ gem "mocha"
9
+ gem "jeweler"
10
+ gem "redcarpet"
11
+ gem "yard"
12
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,40 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ amq-client (0.9.4)
5
+ amq-protocol (>= 0.9.4)
6
+ eventmachine
7
+ amq-protocol (0.9.4)
8
+ amqp (0.9.7)
9
+ amq-client (~> 0.9.4)
10
+ amq-protocol (>= 0.9.4)
11
+ eventmachine
12
+ eventmachine (0.12.10)
13
+ git (1.2.5)
14
+ jeweler (1.8.4)
15
+ bundler (~> 1.0)
16
+ git (>= 1.2.5)
17
+ rake
18
+ rdoc
19
+ json (1.7.3)
20
+ metaclass (0.0.1)
21
+ minitest (3.2.0)
22
+ mocha (0.12.0)
23
+ metaclass (~> 0.0.1)
24
+ rake (0.9.2.2)
25
+ rdoc (3.12)
26
+ json (~> 1.4)
27
+ redcarpet (2.1.1)
28
+ yard (0.8.2.1)
29
+
30
+ PLATFORMS
31
+ ruby
32
+
33
+ DEPENDENCIES
34
+ amqp (>= 0.9.6)
35
+ bundler
36
+ jeweler
37
+ minitest (>= 3.0)
38
+ mocha
39
+ redcarpet
40
+ yard
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) Brendan Hay
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.
data/Makefile ADDED
@@ -0,0 +1,19 @@
1
+ BUNDLE=`which bundle`
2
+
3
+ #
4
+ # Targets
5
+ #
6
+
7
+ .PHONY: deps doc
8
+
9
+ all: deps test
10
+
11
+ deps:
12
+ $(BUNDLE) install
13
+
14
+ test:
15
+ $(BUNDLE) exec rake test
16
+
17
+ doc:
18
+ $(BUNDLE) exec rake yard
19
+
data/README.md ADDED
@@ -0,0 +1,4 @@
1
+ Publish One, Subscribe Many
2
+ ===========
3
+
4
+ [![Build Status](https://secure.travis-ci.org/brendanhay/amqp-subscribe-many.png)](http://travis-ci.org/brendanhay/amqp-subscribe-many)
data/Rakefile ADDED
@@ -0,0 +1,68 @@
1
+ #
2
+ # Bundler
3
+ #
4
+
5
+ require "rubygems"
6
+ require "bundler"
7
+
8
+ begin
9
+ Bundler.setup(:default)
10
+ rescue Bundler::BundlerError => ex
11
+ $stderr.puts ex.message
12
+ $stderr.puts "Run `bundle install` to install missing gems"
13
+ exit ex.status_code
14
+ end
15
+
16
+
17
+ #
18
+ # Tests
19
+ #
20
+
21
+ require "rake"
22
+ require "rake/testtask"
23
+
24
+ Rake::TestTask.new do |t|
25
+ t.libs.concat ["lib", "test"]
26
+ t.test_files = FileList["test/*_test.rb"]
27
+ t.verbose = true
28
+ end
29
+
30
+ task :default => :test
31
+
32
+
33
+ #
34
+ # Console
35
+ #
36
+
37
+ task :console do
38
+ sh "irb -rubygems -I lib -r messaging.rb"
39
+ end
40
+
41
+
42
+ #
43
+ # Gemify
44
+ #
45
+
46
+ require "jeweler"
47
+
48
+ Jeweler::RubygemsDotOrgTasks.new
49
+
50
+ Jeweler::Tasks.new do |gem|
51
+ gem.name = "amqp-subscribe-many"
52
+ gem.version = "0.1.1"
53
+ gem.homepage = "http://github.com/brendanhay/amqp-subscribe-many"
54
+ gem.license = "BSD"
55
+ gem.summary = "'Publish-one, subscribe-many' pattern implementation"
56
+ gem.description = gem.summary
57
+ gem.email = "brendan@soundcloud.com"
58
+ gem.authors = ["brendanhay"]
59
+ end
60
+
61
+
62
+ #
63
+ # Docs
64
+ #
65
+
66
+ require "yard"
67
+
68
+ YARD::Rake::YardocTask.new
@@ -0,0 +1,76 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{amqp-subscribe-many}
8
+ s.version = "0.1.1"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = [%q{brendanhay}]
12
+ s.date = %q{2012-07-10}
13
+ s.description = %q{'Publish-one, subscribe-many' pattern implementation}
14
+ s.email = %q{brendan@soundcloud.com}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.md"
18
+ ]
19
+ s.files = [
20
+ ".travis.yml",
21
+ "Gemfile",
22
+ "Gemfile.lock",
23
+ "LICENSE",
24
+ "Makefile",
25
+ "README.md",
26
+ "Rakefile",
27
+ "amqp-subscribe-many.gemspec",
28
+ "lib/messaging.rb",
29
+ "lib/messaging/client.rb",
30
+ "lib/messaging/configuration.rb",
31
+ "lib/messaging/consumer.rb",
32
+ "lib/messaging/producer.rb",
33
+ "script/config.yml",
34
+ "script/run.rb",
35
+ "test/client_test.rb",
36
+ "test/configuration_test.rb",
37
+ "test/consumer_test.rb",
38
+ "test/test_helper.rb"
39
+ ]
40
+ s.homepage = %q{http://github.com/brendanhay/amqp-subscribe-many}
41
+ s.licenses = [%q{BSD}]
42
+ s.require_paths = [%q{lib}]
43
+ s.rubygems_version = %q{1.8.7}
44
+ s.summary = %q{'Publish-one, subscribe-many' pattern implementation}
45
+
46
+ if s.respond_to? :specification_version then
47
+ s.specification_version = 3
48
+
49
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
50
+ s.add_runtime_dependency(%q<amqp>, [">= 0.9.6"])
51
+ s.add_development_dependency(%q<bundler>, [">= 0"])
52
+ s.add_development_dependency(%q<minitest>, [">= 3.0"])
53
+ s.add_development_dependency(%q<mocha>, [">= 0"])
54
+ s.add_development_dependency(%q<jeweler>, [">= 0"])
55
+ s.add_development_dependency(%q<redcarpet>, [">= 0"])
56
+ s.add_development_dependency(%q<yard>, [">= 0"])
57
+ else
58
+ s.add_dependency(%q<amqp>, [">= 0.9.6"])
59
+ s.add_dependency(%q<bundler>, [">= 0"])
60
+ s.add_dependency(%q<minitest>, [">= 3.0"])
61
+ s.add_dependency(%q<mocha>, [">= 0"])
62
+ s.add_dependency(%q<jeweler>, [">= 0"])
63
+ s.add_dependency(%q<redcarpet>, [">= 0"])
64
+ s.add_dependency(%q<yard>, [">= 0"])
65
+ end
66
+ else
67
+ s.add_dependency(%q<amqp>, [">= 0.9.6"])
68
+ s.add_dependency(%q<bundler>, [">= 0"])
69
+ s.add_dependency(%q<minitest>, [">= 3.0"])
70
+ s.add_dependency(%q<mocha>, [">= 0"])
71
+ s.add_dependency(%q<jeweler>, [">= 0"])
72
+ s.add_dependency(%q<redcarpet>, [">= 0"])
73
+ s.add_dependency(%q<yard>, [">= 0"])
74
+ end
75
+ end
76
+
data/lib/messaging.rb ADDED
@@ -0,0 +1,4 @@
1
+ require_relative "messaging/configuration"
2
+ require_relative "messaging/client"
3
+ require_relative "messaging/producer"
4
+ require_relative "messaging/consumer"
@@ -0,0 +1,148 @@
1
+ require "amqp"
2
+
3
+ module Messaging
4
+
5
+ #
6
+ # Raised when unrecoverable connection and channel errors are encountered.
7
+ #
8
+ class MessagingError < StandardError; end
9
+
10
+ #
11
+ # Provides methods and constants required to establish an AMQP
12
+ # connection and channel with failure handling and recovery.
13
+ # @see http://www.rabbitmq.com/amqp-0-9-1-reference.html#constants
14
+ # For a list of error codes that will cause an exception to be raised
15
+ # rather than invoking automatic recovery.
16
+ #
17
+ module Client
18
+
19
+ # Create an AMQP::Connection with auto-reconnect and error handling.
20
+ #
21
+ # @param uri [String] The AMQP URI to connect to.
22
+ # @param delay [Integer, nil] Time to delay between reconnection attempts.
23
+ # @return [AMQP::Connection]
24
+ # @api public
25
+ def open_connection(uri, delay = nil)
26
+ delay ||= config.reconnect_delay
27
+ options = AMQP::Client.parse_connection_uri(uri)
28
+
29
+ AMQP.connect(options) do |connection, open_ok|
30
+ # Handle TCP connection errors
31
+ connection.on_tcp_connection_loss do |conn, settings|
32
+ log.error("Connection to #{uri.inspect} lost, reconnecting")
33
+
34
+ conn.periodically_reconnect(delay)
35
+ end
36
+
37
+ # Handle general errors
38
+ connection.on_error do |conn, error|
39
+ log.error("Connection to #{uri.inspect} lost, reconnecting")
40
+
41
+ if (402..540).include?(error.reply_code)
42
+ raise(MessagingError, "Channel exception: #{error.reply_text.inspect}")
43
+ end
44
+
45
+ conn.periodically_reconnect(delay)
46
+ end
47
+
48
+ log.debug("Connection to #{uri.inspect} started")
49
+ end
50
+ end
51
+
52
+ # Open an AMQP::Channel with auto-recovery and error handling.
53
+ #
54
+ # @param connection [AMQP::Connection]
55
+ # @param prefetch [Integer, nil]
56
+ # @return [AMQP::Channel]
57
+ # @api public
58
+ def open_channel(connection, prefetch = nil)
59
+ AMQP::Channel.new(connection) do |channel, open_ok|
60
+ channel.auto_recovery = true
61
+ channel.prefetch(prefetch) if prefetch
62
+
63
+ channel.on_error do |ch, error|
64
+ log.error("Channel error #{error.reply_text.inspect}, recovering")
65
+
66
+ # Raise erroneous channel calls/conditions
67
+ # rather than endlessly retrying
68
+ if (403..406).include?(error.reply_code)
69
+ raise(MessagingError, "Channel exception: #{error.reply_text.inspect}")
70
+ end
71
+ end
72
+
73
+ log.debug("Channel #{channel.id} created")
74
+ end
75
+ end
76
+
77
+ # Declare an exchange on the specified channel.
78
+ #
79
+ # @param channel [AMQP::Channel]
80
+ # @param name [String]
81
+ # @param type [String]
82
+ # @param options [Hash]
83
+ # @return [AMQP::Exchange]
84
+ # @api public
85
+ def declare_exchange(channel, name, type, options = {})
86
+ exchange =
87
+ # Check if default options need to be supplied to a non-default delcaration
88
+ if default_exchange?(name)
89
+ channel.default_exchange
90
+ else
91
+ channel.send(type, name, options)
92
+ end
93
+
94
+ log.debug("Exchange #{exchange.name.inspect} declared")
95
+
96
+ exchange
97
+ end
98
+
99
+ # Declare and bind a queue to the specified exchange via the
100
+ # supplied routing key.
101
+ #
102
+ # @param channel [AMQP::Channel]
103
+ # @param exchange [AMQP::Exchange]
104
+ # @param name [String]
105
+ # @param key [String]
106
+ # @param options [Hash]
107
+ # @return [AMQP::Queue]
108
+ # @api public
109
+ def declare_queue(channel, exchange, name, key, options = {})
110
+ channel.queue(name, options) do |queue|
111
+ # Check if additional bindings are needed
112
+ unless default_exchange?(exchange.name)
113
+ queue.bind(exchange, { :routing_key => key })
114
+ end
115
+
116
+ log.debug("Queue #{queue.name.inspect} bound to #{exchange.name.inspect}")
117
+ end
118
+ end
119
+
120
+ protected
121
+
122
+ # @return [#info, #debug, #error]
123
+ # @api protected
124
+ def log
125
+ config.logger
126
+ end
127
+
128
+ # @return [Messaging::Configuration]
129
+ # @api protected
130
+ def config
131
+ Configuration.instance
132
+ end
133
+
134
+ private
135
+
136
+ # @param name [String]
137
+ # @return [Boolean]
138
+ # @api private
139
+ def default_exchange?(name)
140
+ ["amq.direct",
141
+ "amq.fanout",
142
+ "amq.topic",
143
+ "amqp.headers",
144
+ "amqp.match"].include?(name)
145
+ end
146
+ end
147
+
148
+ end
@@ -0,0 +1,56 @@
1
+ require "singleton"
2
+ require "logger"
3
+
4
+ module Messaging
5
+
6
+ # Global configuration for producer and consumer mixins.
7
+ class Configuration
8
+ include Singleton
9
+
10
+ # @yieldparam [Messaging::Configuration] config
11
+ # @api public
12
+ def self.setup(&block)
13
+ yield(Configuration.instance)
14
+ end
15
+
16
+ # @!attribute [r] publish_to
17
+ # @return [String]
18
+ attr_accessor :publish_to
19
+
20
+ # @!attribute [r] consume_from
21
+ # @return [Array<String>]
22
+ attr_accessor :consume_from
23
+
24
+ # @!attribute [r] prefetch
25
+ # @return [Integer]
26
+ attr_accessor :prefetch
27
+
28
+ # @!attribute [r] exchange_options
29
+ # @return [Hash]
30
+ attr_accessor :exchange_options
31
+
32
+ # @!attribute [r] queue_options
33
+ # @return [Hash]
34
+ attr_accessor :queue_options
35
+
36
+ # @!attribute [r] reconnect_delay
37
+ # @return [Integer]
38
+ attr_accessor :reconnect_delay
39
+
40
+ # @!attribute [r] logger
41
+ # @return [#info, #debug, #error]
42
+ attr_accessor :logger
43
+
44
+ # @api private
45
+ def initialize
46
+ @publish_to = "amqp://guest:guest@localhost:5672"
47
+ @consume_from = [publish_to]
48
+ @prefetch = 1
49
+ @exchange_options = { :auto_delete => false, :durable => true }
50
+ @queue_options = exchange_options
51
+ @reconnect_delay = 5
52
+ @logger = Logger.new(STDOUT)
53
+ end
54
+ end
55
+
56
+ end
@@ -0,0 +1,123 @@
1
+ module Messaging
2
+
3
+ module Consumer
4
+ include Client
5
+
6
+ # DSL methods which are used to extend the target when
7
+ # {Messaging::Consumer} is included into a class.
8
+ module Extensions
9
+
10
+ # Subscribe to a queue which will invoke {Messaging::Consumer#on_message}
11
+ # upon receiving a message.
12
+ #
13
+ # @param exchange [String]
14
+ # @param type [String]
15
+ # @param queue [String]
16
+ # @param key [String]
17
+ # @return [Array<Array(String, String, String, String)>]
18
+ # @api public
19
+ def subscribe(exchange, type, queue, key)
20
+ subscriptions << [exchange, type, queue, key]
21
+ end
22
+
23
+ # A list of subscriptions intended for internal use.
24
+ #
25
+ # @return [Array<Array(String, String, String, String)>]
26
+ # @api private
27
+ def subscriptions
28
+ @subscriptions ||= []
29
+ end
30
+
31
+ end
32
+
33
+ def self.included(base)
34
+ base.send(:extend, Extensions)
35
+ end
36
+
37
+ # @return [Messaging::Consumer]
38
+ # @api public
39
+ def consume
40
+ unless consumer_channels
41
+ @consumer_channels ||= consumer_connections.map do |conn|
42
+ open_channel(conn, config.prefetch)
43
+ end
44
+
45
+ subscriptions.each { |args| subscribe(*args) }
46
+ end
47
+
48
+ self
49
+ end
50
+
51
+ # Subscribe to a queue which will invoke the supplied block when
52
+ # a message is received.
53
+ # Additionally declaring a binding to the specified exchange/key pair.
54
+ #
55
+ # @param exchange [String]
56
+ # @param type [String]
57
+ # @param queue [String]
58
+ # @param key [String]
59
+ # @return [Messaging::Consumer]
60
+ # @api public
61
+ def subscribe(exchange, type, queue, key)
62
+ consumer_channels.each do |channel|
63
+ ex = declare_exchange(channel, exchange, type, config.exchange_options)
64
+ q = declare_queue(channel, ex, queue, key, config.queue_options)
65
+
66
+ q.subscribe(:ack => true) do |meta, payload|
67
+ log.debug("Receieved message on channel #{meta.channel.id} from queue #{queue.inspect}")
68
+
69
+ # If this raises an exception, the connection
70
+ # will be closed, and the message requeued by the broker.
71
+ on_message(meta, payload)
72
+
73
+ meta.ack
74
+ end
75
+ end
76
+
77
+ self
78
+ end
79
+
80
+ # @raise [NotImplementedError]
81
+ # @api protected
82
+ def on_message(meta, payload)
83
+ raise NotImplementedError
84
+ end
85
+
86
+ # Close all consumer_channels and then disconnect all the consumer_connections.
87
+ #
88
+ # @return []
89
+ # @api public
90
+ def disconnect
91
+ consumer_channels.each do |chan|
92
+ chan.close
93
+ end
94
+
95
+ consumer_connections.each do |conn|
96
+ conn.disconnect
97
+ end
98
+ end
99
+
100
+ private
101
+
102
+ # @return [Array<AMQP::Connection>]
103
+ # @api private
104
+ def consumer_connections
105
+ @consumer_connections ||= config.consume_from.map do |uri|
106
+ open_connection(uri)
107
+ end
108
+ end
109
+
110
+ # @return [Array<AMQP::Channel>]
111
+ # @api private
112
+ def consumer_channels
113
+ @consumer_channels
114
+ end
115
+
116
+ # @return [Array<Array(String, String, String, String)>]
117
+ # @api private
118
+ def subscriptions
119
+ self.class.subscriptions
120
+ end
121
+ end
122
+
123
+ end
@@ -0,0 +1,59 @@
1
+ module Messaging
2
+
3
+ module Producer
4
+ include Client
5
+
6
+ # Publish a payload to the specified exchange/key pair.
7
+ #
8
+ # @param exchange [String]
9
+ # @param type [String]
10
+ # @param key [String]
11
+ # @param payload [Object]
12
+ # @return [Messaging::Producer]
13
+ # @api public
14
+ def publish(exchange, type, key, payload)
15
+ ex = producer_exchanges[exchange] ||=
16
+ declare_exchange(producer_channel, exchange, type, config.exchange_options)
17
+
18
+ log.debug("Publishing to exchange #{exchange.inspect} via #{key.inspect}")
19
+
20
+ ex.publish(payload, {
21
+ :exchange => exchange,
22
+ :routing_key => key
23
+ })
24
+
25
+ self
26
+ end
27
+
28
+ # Close the channel and then disconnect the connection.
29
+ #
30
+ # @return []
31
+ # @api public
32
+ def disconnect
33
+ producer_channel.close do |close_ok|
34
+ producer_connection.disconnect
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ # @return [Hash(String, AMQP::Exchange)]
41
+ # @api private
42
+ def producer_exchanges
43
+ @producer_exchanges ||= {}
44
+ end
45
+
46
+ # @return [AMQP::Connection]
47
+ # @api private
48
+ def producer_connection
49
+ @producer_connection ||= open_connection(config.publish_to)
50
+ end
51
+
52
+ # @return [AMQP::Channel]
53
+ # @api private
54
+ def producer_channel
55
+ @producer_channel ||= open_channel(producer_connection)
56
+ end
57
+ end
58
+
59
+ end
data/script/config.yml ADDED
@@ -0,0 +1,6 @@
1
+ ---
2
+ :publish_to: amqp://localhost
3
+ :consume_from:
4
+ - amqp://localhost
5
+ - amqp://localhost
6
+ - amqp://localhost
data/script/run.rb ADDED
@@ -0,0 +1,86 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "rubygems"
4
+ require "bundler"
5
+
6
+ Bundler.setup(:default)
7
+
8
+ $:.unshift(File.dirname(__FILE__) + "/../lib")
9
+
10
+ require "messaging"
11
+
12
+ EXCHANGE = "exchange"
13
+ TYPE = "direct"
14
+ QUEUE = "queue"
15
+ KEY = "key"
16
+
17
+ # Load the config
18
+ yml = YAML::load_file(File.dirname(__FILE__) + "/config.yml")
19
+
20
+ # Setup configuration
21
+ Messaging::Configuration.setup do |config|
22
+ config.publish_to = yml[:publish_to]
23
+ config.consume_from = yml[:consume_from]
24
+ end
25
+
26
+ # Consume example
27
+ class ConsumerProcessor
28
+ include Messaging::Consumer
29
+
30
+ subscribe(EXCHANGE, TYPE, QUEUE, KEY)
31
+
32
+ def on_message(meta, payload)
33
+ log.info("ConsumeProcessor channel #{meta.channel.id} received payload #{payload.inspect}")
34
+ end
35
+ end
36
+
37
+ # Publish example
38
+ class ProducerProcessor
39
+ include Messaging::Producer
40
+ end
41
+
42
+ # Consume + publish example
43
+ class DuplexProcessor
44
+ include Messaging::Producer
45
+ include Messaging::Consumer
46
+
47
+ subscribe(EXCHANGE, TYPE, QUEUE, KEY)
48
+
49
+ def on_message(meta, payload)
50
+ log.info("DuplexProcessor channel #{meta.channel.id} received payload #{payload.inspect}")
51
+ end
52
+ end
53
+
54
+ EM.run do
55
+
56
+ # Instantiate the processors
57
+ consumer = ConsumerProcessor.new
58
+ producer = ProducerProcessor.new
59
+ duplex = DuplexProcessor.new
60
+
61
+ # Start the consumers
62
+ consumer.consume
63
+ duplex.consume
64
+
65
+ # Create a handle to the publish timer, to cancel later
66
+ timer = EM.add_periodic_timer(0.5) do
67
+ producer.publish(EXCHANGE, TYPE, KEY, "a_producer_payload")
68
+ duplex.publish(EXCHANGE, TYPE, KEY, "a_duplex_payload")
69
+ end
70
+
71
+ # Handle Ctrl-C interrupt
72
+ trap("INT") do
73
+ puts "Stopping..."
74
+
75
+ # Cancel the publisher timer
76
+ EM.cancel_timer(timer)
77
+
78
+ # Disconnect the producer/consumer connections
79
+ consumer.disconnect
80
+ producer.disconnect
81
+ duplex.disconnect
82
+
83
+ # Shutdown the EM loop
84
+ EM.add_timer(1) { EM.stop }
85
+ end
86
+ end
@@ -0,0 +1,55 @@
1
+ require_relative "test_helper"
2
+
3
+ class DummyClient
4
+ include Messaging::Client
5
+ end
6
+
7
+ class ClientTest < MiniTest::Unit::TestCase
8
+ def setup
9
+ @client = DummyClient.new
10
+ @uri = "amqp://guest:guest@localhost:5672"
11
+ end
12
+
13
+ def test_open_connection_adds_retry_handlers
14
+ delay = 3
15
+
16
+ # Connection yield param
17
+ conn = mock()
18
+
19
+ # on_tcp_connection_loss handler sets periodically reconnect
20
+ on_tcp_loss = mock()
21
+ on_tcp_loss.expects(:periodically_reconnect).with(delay)
22
+ on_tcp_loss.expects(:to_ary)
23
+
24
+ # A retryable/recoverable error code
25
+ error = mock()
26
+ error.expects(:reply_code).returns(1)
27
+
28
+ # on_error handler sets periodically reconnect
29
+ on_error = mock()
30
+ on_error.expects(:periodically_reconnect).with(delay)
31
+
32
+ conn.expects(:on_tcp_connection_loss).yields(on_tcp_loss)
33
+ conn.expects(:on_error).yields(on_error, error)
34
+ conn.expects(:to_ary)
35
+
36
+ AMQP.stubs(:connect).yields(conn)
37
+
38
+ @client.open_connection(@uri, delay)
39
+ end
40
+
41
+ def test_open_channel_adds_recovery_handlers
42
+ prefetch = 16
43
+
44
+ # Channel yield param
45
+ chan = mock()
46
+ chan.expects(:on_error)
47
+ chan.expects(:auto_recovery=).with(true)
48
+ chan.expects(:prefetch).with(prefetch)
49
+ chan.expects(:id)
50
+
51
+ AMQP::Channel.stubs(:new).yields(chan, {})
52
+
53
+ @client.open_channel(mock(), prefetch)
54
+ end
55
+ end
@@ -0,0 +1,75 @@
1
+ require_relative "test_helper"
2
+
3
+ class ConfigurationTest < MiniTest::Unit::TestCase
4
+ def setup
5
+ Messaging::Configuration.instance_variable_set(
6
+ "@__instance__",
7
+ Messaging::Configuration.send(:new)
8
+ )
9
+
10
+ @config = Messaging::Configuration
11
+ end
12
+
13
+ def test_publish_to
14
+ assert(@config.instance.publish_to)
15
+
16
+ expected = "ballsacks"
17
+ @config.setup { |c| c.publish_to = expected }
18
+
19
+ assert_equal(expected, @config.instance.publish_to)
20
+ end
21
+
22
+ def test_consume_from
23
+ assert(@config.instance.consume_from.length > 0)
24
+
25
+ expected = "nutsacks"
26
+ @config.setup { |c| c.consume_from = expected }
27
+
28
+ assert_equal(expected, @config.instance.consume_from)
29
+ end
30
+
31
+ def test_prefetch
32
+ assert(@config.instance.prefetch > 0)
33
+
34
+ expected = 7
35
+ @config.setup { |c| c.prefetch = expected }
36
+
37
+ assert_equal(expected, @config.instance.prefetch)
38
+ end
39
+
40
+ def test_exchange_options
41
+ assert(@config.instance.exchange_options)
42
+
43
+ expected = { :nonsense => true }
44
+ @config.setup { |c| c.exchange_options = expected }
45
+
46
+ assert_equal(expected, @config.instance.exchange_options)
47
+ end
48
+
49
+ def test_queue_options
50
+ assert(@config.instance.queue_options)
51
+
52
+ expected = { :high_five => "ok!" }
53
+ @config.setup { |c| c.queue_options = expected }
54
+
55
+ assert_equal(expected, @config.instance.queue_options)
56
+ end
57
+
58
+ def test_reconnect_delay
59
+ assert(@config.instance.reconnect_delay > 0)
60
+
61
+ expected = 12
62
+ @config.setup { |c| c.reconnect_delay = expected }
63
+
64
+ assert_equal(expected, @config.instance.reconnect_delay)
65
+ end
66
+
67
+ def test_logger
68
+ assert(@config.instance.logger)
69
+
70
+ expected = "ballsacks"
71
+ @config.setup { |c| c.logger = expected }
72
+
73
+ assert_equal(expected, @config.instance.logger)
74
+ end
75
+ end
@@ -0,0 +1 @@
1
+ require_relative "test_helper"
@@ -0,0 +1,16 @@
1
+ require "minitest/autorun"
2
+ require "mocha"
3
+ require "messaging"
4
+ require "logger"
5
+
6
+ class MiniTestSetup < MiniTest::Unit
7
+ def _run_suites(suites, type)
8
+ Messaging::Configuration.setup do |config|
9
+ config.logger.level = Logger::Severity::UNKNOWN
10
+ end
11
+
12
+ super
13
+ end
14
+ end
15
+
16
+ MiniTest::Unit.runner = MiniTestSetup.new
metadata ADDED
@@ -0,0 +1,146 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: amqp-subscribe-many
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - brendanhay
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-07-10 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: amqp
16
+ requirement: &70180070452160 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: 0.9.6
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *70180070452160
25
+ - !ruby/object:Gem::Dependency
26
+ name: bundler
27
+ requirement: &70180070451500 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *70180070451500
36
+ - !ruby/object:Gem::Dependency
37
+ name: minitest
38
+ requirement: &70180070450860 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '3.0'
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *70180070450860
47
+ - !ruby/object:Gem::Dependency
48
+ name: mocha
49
+ requirement: &70180070450260 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: *70180070450260
58
+ - !ruby/object:Gem::Dependency
59
+ name: jeweler
60
+ requirement: &70180070449620 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ type: :development
67
+ prerelease: false
68
+ version_requirements: *70180070449620
69
+ - !ruby/object:Gem::Dependency
70
+ name: redcarpet
71
+ requirement: &70180070449020 !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ! '>='
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: *70180070449020
80
+ - !ruby/object:Gem::Dependency
81
+ name: yard
82
+ requirement: &70180070448440 !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ! '>='
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ type: :development
89
+ prerelease: false
90
+ version_requirements: *70180070448440
91
+ description: ! '''Publish-one, subscribe-many'' pattern implementation'
92
+ email: brendan@soundcloud.com
93
+ executables: []
94
+ extensions: []
95
+ extra_rdoc_files:
96
+ - LICENSE
97
+ - README.md
98
+ files:
99
+ - .travis.yml
100
+ - Gemfile
101
+ - Gemfile.lock
102
+ - LICENSE
103
+ - Makefile
104
+ - README.md
105
+ - Rakefile
106
+ - amqp-subscribe-many.gemspec
107
+ - lib/messaging.rb
108
+ - lib/messaging/client.rb
109
+ - lib/messaging/configuration.rb
110
+ - lib/messaging/consumer.rb
111
+ - lib/messaging/producer.rb
112
+ - script/config.yml
113
+ - script/run.rb
114
+ - test/client_test.rb
115
+ - test/configuration_test.rb
116
+ - test/consumer_test.rb
117
+ - test/test_helper.rb
118
+ homepage: http://github.com/brendanhay/amqp-subscribe-many
119
+ licenses:
120
+ - BSD
121
+ post_install_message:
122
+ rdoc_options: []
123
+ require_paths:
124
+ - lib
125
+ required_ruby_version: !ruby/object:Gem::Requirement
126
+ none: false
127
+ requirements:
128
+ - - ! '>='
129
+ - !ruby/object:Gem::Version
130
+ version: '0'
131
+ segments:
132
+ - 0
133
+ hash: 235469886921977757
134
+ required_rubygems_version: !ruby/object:Gem::Requirement
135
+ none: false
136
+ requirements:
137
+ - - ! '>='
138
+ - !ruby/object:Gem::Version
139
+ version: '0'
140
+ requirements: []
141
+ rubyforge_project:
142
+ rubygems_version: 1.8.7
143
+ signing_key:
144
+ specification_version: 3
145
+ summary: ! '''Publish-one, subscribe-many'' pattern implementation'
146
+ test_files: []