hutch 0.6.0 → 0.7.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4ad7051316dda86d731e7422ebd9c126618210e3
4
- data.tar.gz: e5a4e0fa91debe583046b45076b3373768c77d2e
3
+ metadata.gz: 2432ba126d871fbe57468e88a39c6d589a01aa2f
4
+ data.tar.gz: 865526ad5d702eaaea80ee000b6e8ef879b5ac18
5
5
  SHA512:
6
- metadata.gz: 5b7444175bfd7abbbe2219214506a1a884da83f4ba9e0182547acc65ce2e5285691cb786d04985e16be7cabee26acbf94d4e51052a30192f8fbf4cdafb9951f4
7
- data.tar.gz: d0b377c4b5d9f695d0e247c456352209f5e8798d52a3e8c025c88dbdd1598280f6a2766fb7a49d4f5810f64ca0965a141a2a0bb069812d9ecf62d464a26039c4
6
+ metadata.gz: 87d496756e3df55679391977995de5eb836a949cd80ca0ecafb7d99966512c736be01307e72976cad95fb122fcd048c51154a7456c9cfec7b1e4d2080ef9c20f
7
+ data.tar.gz: 5b8f3e3fd23af0451da805d64f90ff8afc5b8ee0be93c04a4fec7be45598c32fc2ad96d59442d660974f8b3d18e47f2e507aa6988b369207721173e5e1812877
@@ -1,5 +1,6 @@
1
+ language: ruby
1
2
  rvm:
2
- - 2.0.0
3
- - 1.9.3
3
+ - "2.0.0"
4
+ - "1.9.3"
4
5
  services:
5
6
  - rabbitmq
@@ -1,3 +1,7 @@
1
+ ## 0.7.0 — unreleased
2
+
3
+ - [Global properties can now be specified](https://github.com/gocardless/hutch/pull/62) for publishing
4
+
1
5
  ## 0.6.0 - November 4, 2013
2
6
 
3
7
  - Metadata can now be passed in to `#publish`
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2013 GoCardless
1
+ Copyright (c) 2013-2014 GoCardless
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person
4
4
  obtaining a copy of this software and associated documentation
data/README.md CHANGED
@@ -61,6 +61,21 @@ class FailedPaymentConsumer
61
61
  end
62
62
  ```
63
63
 
64
+ By default, the queue name will be named using the consumer class. You can set
65
+ the queue name explicitly by using the `queue_name` method:
66
+
67
+ ```ruby
68
+ class FailedPaymentConsumer
69
+ include Hutch::Consumer
70
+ consume 'gc.ps.payment.failed'
71
+ queue_name 'failed_payments'
72
+
73
+ def process(message)
74
+ mark_payment_as_failed(message[:id])
75
+ end
76
+ end
77
+ ```
78
+
64
79
  If you are using Hutch with Rails and want to make Hutch log to the Rails
65
80
  logger rather than `stdout`, add this to `config/initializers/hutch.rb`
66
81
 
@@ -68,7 +83,8 @@ logger rather than `stdout`, add this to `config/initializers/hutch.rb`
68
83
  Hutch::Logging.logger = Rails.logger
69
84
  ```
70
85
 
71
- [topic-docs]: http://www.rabbitmq.com/tutorials/tutorial-five-python.html
86
+ See this [RabbitMQ tutorial on topic exchanges](http://www.rabbitmq.com/tutorials/tutorial-five-ruby.html)
87
+ to learn more.
72
88
 
73
89
 
74
90
  ## Running Hutch
@@ -81,7 +97,7 @@ $ hutch -h
81
97
  usage: hutch [options]
82
98
  --mq-host HOST Set the RabbitMQ host
83
99
  --mq-port PORT Set the RabbitMQ port
84
- --mq-tls Use TLS for the AMQP connection
100
+ -t, --[no-]mq-tls Use TLS for the AMQP connection
85
101
  --mq-tls-cert FILE Certificate for TLS client verification
86
102
  --mq-tls-key FILE Private key for TLS client verification
87
103
  --mq-exchange EXCHANGE Set the RabbitMQ exchange
@@ -90,7 +106,7 @@ usage: hutch [options]
90
106
  --mq-password PASSWORD Set the RabbitMQ password
91
107
  --mq-api-host HOST Set the RabbitMQ API host
92
108
  --mq-api-port PORT Set the RabbitMQ API port
93
- --mq-api-ssl Use SSL for the RabbitMQ API
109
+ -s, --[no-]mq-api-ssl Use SSL for the RabbitMQ API
94
110
  --config FILE Load Hutch configuration from a file
95
111
  --require PATH Require a Rails app or path
96
112
  --[no-]autoload-rails Require the current rails app directory
@@ -1,7 +1,7 @@
1
1
  require File.expand_path('../lib/hutch/version', __FILE__)
2
2
 
3
3
  Gem::Specification.new do |gem|
4
- gem.add_runtime_dependency 'bunny', '~> 1.0.0'
4
+ gem.add_runtime_dependency 'bunny', '~> 1.1.0'
5
5
  gem.add_runtime_dependency 'carrot-top', '~> 0.0.7'
6
6
  gem.add_runtime_dependency 'multi_json', '~> 1.5'
7
7
  gem.add_development_dependency 'rspec', '~> 2.12.0'
@@ -21,10 +21,18 @@ module Hutch
21
21
  Hutch::Logging.logger
22
22
  end
23
23
 
24
- def self.connect(config = Hutch::Config)
24
+ def self.global_properties=(properties)
25
+ @global_properties = properties
26
+ end
27
+
28
+ def self.global_properties
29
+ @global_properties ||= {}
30
+ end
31
+
32
+ def self.connect(options = {}, config = Hutch::Config)
25
33
  unless connected?
26
34
  @broker = Hutch::Broker.new(config)
27
- @broker.connect
35
+ @broker.connect(options)
28
36
  @connected = true
29
37
  end
30
38
  end
@@ -37,8 +45,8 @@ module Hutch
37
45
  @connected
38
46
  end
39
47
 
40
- def self.publish(routing_key, message)
41
- @broker.publish(routing_key, message)
48
+ def self.publish(*args)
49
+ broker.publish(*args)
42
50
  end
43
51
  end
44
52
 
@@ -14,9 +14,9 @@ module Hutch
14
14
  @config = config || Hutch::Config
15
15
  end
16
16
 
17
- def connect
17
+ def connect(options = {})
18
18
  set_up_amqp_connection
19
- set_up_api_connection
19
+ set_up_api_connection if options.fetch(:enable_http_api_use, true)
20
20
 
21
21
  if block_given?
22
22
  yield
@@ -34,69 +34,72 @@ module Hutch
34
34
  # channel we use for talking to RabbitMQ. It also ensures the existance of
35
35
  # the exchange we'll be using.
36
36
  def set_up_amqp_connection
37
- host, port, vhost = @config[:mq_host], @config[:mq_port]
38
- username, password = @config[:mq_username], @config[:mq_password]
39
- vhost, tls = @config[:mq_vhost], @config[:mq_tls]
40
- tls_key, tls_cert = @config[:mq_tls_key], @config[:mq_tls_cert]
37
+ open_connection!
38
+ open_channel!
39
+
40
+ exchange_name = @config[:mq_exchange]
41
+ logger.info "using topic exchange '#{exchange_name}'"
42
+
43
+ with_bunny_precondition_handler('exchange') do
44
+ @exchange = @channel.topic(exchange_name, durable: true)
45
+ end
46
+ end
47
+
48
+ def open_connection!
49
+ host = @config[:mq_host]
50
+ port = @config[:mq_port]
51
+ vhost = @config[:mq_vhost]
52
+ username = @config[:mq_username]
53
+ password = @config[:mq_password]
54
+ tls = @config[:mq_tls]
55
+ tls_key = @config[:mq_tls_cert]
56
+ tls_cert = @config[:mq_tls_key]
41
57
  protocol = tls ? "amqps://" : "amqp://"
42
- uri = "#{username}:#{password}@#{host}:#{port}/#{vhost.sub(/^\//, '')}"
58
+ uri = "#{username}:#{password}@#{host}:#{port}/#{vhost.sub(/^\//, '')}"
59
+ sanitized_uri = "#{protocol}#{host}:#{port}"
43
60
  logger.info "connecting to rabbitmq (#{protocol}#{uri})"
44
61
 
45
62
  @connection = Bunny.new(host: host, port: port, vhost: vhost,
46
63
  tls: tls, tls_key: tls_key, tls_cert: tls_cert,
47
64
  username: username, password: password,
48
- heartbeat: 1, automatically_recover: true,
65
+ heartbeat: 30, automatically_recover: true,
49
66
  network_recovery_interval: 1)
50
- @connection.start
51
67
 
52
- logger.info 'opening rabbitmq channel'
53
- @channel = @connection.create_channel
68
+ with_bunny_connection_handler(sanitized_uri) do
69
+ @connection.start
70
+ end
54
71
 
55
- exchange_name = @config[:mq_exchange]
56
- logger.info "using topic exchange '#{exchange_name}'"
57
- @exchange = @channel.topic(exchange_name, durable: true)
58
- rescue Bunny::TCPConnectionFailed => ex
59
- logger.error "amqp connection error: #{ex.message.downcase}"
60
- uri = "#{protocol}#{host}:#{port}"
61
- raise ConnectionError.new("couldn't connect to rabbitmq at #{uri}")
62
- rescue Bunny::PreconditionFailed => ex
63
- logger.error ex.message
64
- raise WorkerSetupError.new('could not create exchange due to a type ' +
65
- 'conflict with an existing exchange, ' +
66
- 'remove the existing exchange and try again')
72
+ @connection
73
+ end
74
+
75
+ def open_channel!
76
+ logger.info 'opening rabbitmq channel'
77
+ @channel = connection.create_channel
67
78
  end
68
79
 
69
80
  # Set up the connection to the RabbitMQ management API. Unfortunately, this
70
81
  # is necessary to do a few things that are impossible over AMQP. E.g.
71
82
  # listing queues and bindings.
72
83
  def set_up_api_connection
73
- host, port = @config[:mq_api_host], @config[:mq_api_port]
74
- username, password = @config[:mq_username], @config[:mq_password]
75
- ssl = @config[:mq_api_ssl]
76
-
77
- protocol = ssl ? "https://" : "http://"
78
- management_uri = "#{protocol}#{username}:#{password}@#{host}:#{port}/"
79
- logger.info "connecting to rabbitmq management api (#{management_uri})"
80
-
81
- @api_client = CarrotTop.new(host: host, port: port,
82
- user: username, password: password,
83
- ssl: ssl)
84
- @api_client.exchanges
85
- rescue Errno::ECONNREFUSED => ex
86
- logger.error "api connection error: #{ex.message.downcase}"
87
- raise ConnectionError.new("couldn't connect to api at #{management_uri}")
88
- rescue Net::HTTPServerException => ex
89
- logger.error "api connection error: #{ex.message.downcase}"
90
- if ex.response.code == '401'
91
- raise AuthenticationError.new('invalid api credentials')
92
- else
93
- raise
84
+ logger.info "connecting to rabbitmq management api (#{api_config.management_uri})"
85
+
86
+ with_authentication_error_handler do
87
+ with_connection_error_handler do
88
+ @api_client = CarrotTop.new(host: api_config.host, port: api_config.port,
89
+ user: api_config.username, password: api_config.password,
90
+ ssl: api_config.ssl)
91
+ @api_client.exchanges
92
+ end
94
93
  end
95
94
  end
96
95
 
97
- # Create / get a durable queue.
96
+ # Create / get a durable queue and apply namespace if it exists.
98
97
  def queue(name)
99
- @channel.queue(name, durable: true)
98
+ with_bunny_precondition_handler('queue') do
99
+ namespace = @config[:namespace].to_s.downcase.gsub(/\W|:/, "")
100
+ name = name.prepend(namespace + ":") unless namespace.empty?
101
+ channel.queue(name, durable: true)
102
+ end
100
103
  end
101
104
 
102
105
  # Return a mapping of queue names to the routing keys they're bound to.
@@ -149,35 +152,81 @@ module Hutch
149
152
  end
150
153
 
151
154
  def publish(routing_key, message, properties = {})
152
- payload = JSON.dump(message)
153
-
154
- unless @connection
155
- msg = "Unable to publish - no connection to broker. " +
156
- "Message: #{message.inspect}, Routing key: #{routing_key}."
157
- logger.error(msg)
158
- raise PublishError, msg
159
- end
160
-
161
- unless @connection.open?
162
- msg = "Unable to publish - connection is closed. " +
163
- "Message: #{message.inspect}, Routing key: #{routing_key}."
164
- logger.error(msg)
165
- raise PublishError, msg
166
- end
155
+ ensure_connection!(routing_key, message)
167
156
 
168
157
  non_overridable_properties = {
169
- routing_key: routing_key, timestamp: Time.now.to_i
158
+ routing_key: routing_key,
159
+ timestamp: Time.now.to_i,
160
+ content_type: 'application/json'
170
161
  }
171
162
  properties[:message_id] ||= generate_id
172
163
 
173
164
  logger.info("publishing message '#{message.inspect}' to #{routing_key}")
174
- @exchange.publish(payload, {persistent: true}.
165
+ @exchange.publish(JSON.dump(message), {persistent: true}.
175
166
  merge(properties).
167
+ merge(global_properties).
176
168
  merge(non_overridable_properties))
177
169
  end
178
170
 
179
171
  private
180
172
 
173
+ def raise_publish_error(reason, routing_key, message)
174
+ msg = "Unable to publish - #{reason}. Message: #{message.inspect}, Routing key: #{routing_key}."
175
+ logger.error(msg)
176
+ raise PublishError, msg
177
+ end
178
+
179
+ def ensure_connection!(routing_key, message)
180
+ raise_publish_error('no connection to broker', routing_key, message) unless @connection
181
+ raise_publish_error('connection is closed', routing_key, message) unless @connection.open?
182
+ end
183
+
184
+ def api_config
185
+ @api_config ||= OpenStruct.new.tap do |config|
186
+ config.host = @config[:mq_api_host]
187
+ config.port = @config[:mq_api_port]
188
+ config.username = @config[:mq_username]
189
+ config.password = @config[:mq_password]
190
+ config.ssl = @config[:mq_api_ssl]
191
+ config.protocol = config.ssl ? "https://" : "http://"
192
+ config.management_uri = "#{config.protocol}#{config.username}:#{config.password}@#{config.host}:#{config.port}/"
193
+ end
194
+ end
195
+
196
+ def with_authentication_error_handler
197
+ yield
198
+ rescue Net::HTTPServerException => ex
199
+ logger.error "api connection error: #{ex.message.downcase}"
200
+ if ex.response.code == '401'
201
+ raise AuthenticationError.new('invalid api credentials')
202
+ else
203
+ raise
204
+ end
205
+ end
206
+
207
+ def with_connection_error_handler
208
+ yield
209
+ rescue Errno::ECONNREFUSED => ex
210
+ logger.error "api connection error: #{ex.message.downcase}"
211
+ raise ConnectionError.new("couldn't connect to api at #{api_config.management_uri}")
212
+ end
213
+
214
+ def with_bunny_precondition_handler(item)
215
+ yield
216
+ rescue Bunny::PreconditionFailed => ex
217
+ logger.error ex.message
218
+ s = "RabbitMQ responded with Precondition Failed when creating this #{item}. " +
219
+ "Perhaps it is being redeclared with non-matching attributes"
220
+ raise WorkerSetupError.new(s)
221
+ end
222
+
223
+ def with_bunny_connection_handler(uri)
224
+ yield
225
+ rescue Bunny::TCPConnectionFailed => ex
226
+ logger.error "amqp connection error: #{ex.message.downcase}"
227
+ raise ConnectionError.new("couldn't connect to rabbitmq at #{uri}")
228
+ end
229
+
181
230
  def work_pool_threads
182
231
  @channel.work_pool.threads || []
183
232
  end
@@ -185,6 +234,10 @@ module Hutch
185
234
  def generate_id
186
235
  SecureRandom.uuid
187
236
  end
237
+
238
+ def global_properties
239
+ Hutch.global_properties.respond_to?(:call) ? Hutch.global_properties.call : Hutch.global_properties
240
+ end
188
241
  end
189
242
  end
190
243
 
@@ -97,7 +97,7 @@ module Hutch
97
97
  Hutch::Config.mq_port = port
98
98
  end
99
99
 
100
- opts.on('--mq-tls', 'Use TLS for the AMQP connection') do |tls|
100
+ opts.on("-t", "--[no-]mq-tls", 'Use TLS for the AMQP connection') do |tls|
101
101
  Hutch::Config.mq_tls = tls
102
102
  end
103
103
 
@@ -138,7 +138,7 @@ module Hutch
138
138
  Hutch::Config.mq_api_port = port
139
139
  end
140
140
 
141
- opts.on('--mq-api-ssl', 'Use SSL for the RabbitMQ API') do |api_ssl|
141
+ opts.on("-s", "--[no-]mq-api-ssl", 'Use SSL for the RabbitMQ API') do |api_ssl|
142
142
  Hutch::Config.mq_api_ssl = api_ssl
143
143
  end
144
144
 
@@ -166,6 +166,10 @@ module Hutch
166
166
  Hutch::Config.log_level = Logger::DEBUG
167
167
  end
168
168
 
169
+ opts.on('--namespace NAMESPACE', 'Queue namespace') do |namespace|
170
+ Hutch::Config.namespace = namespace
171
+ end
172
+
169
173
  opts.on('--version', 'Print the version and exit') do
170
174
  puts "hutch v#{VERSION}"
171
175
  exit 0
@@ -24,7 +24,8 @@ module Hutch
24
24
  log_level: Logger::INFO,
25
25
  require_paths: [],
26
26
  autoload_rails: true,
27
- error_handlers: [Hutch::ErrorHandlers::Logger.new]
27
+ error_handlers: [Hutch::ErrorHandlers::Logger.new],
28
+ namespace: nil
28
29
  }
29
30
  end
30
31
 
@@ -15,12 +15,19 @@ module Hutch
15
15
  @routing_keys = self.routing_keys.union(routing_keys)
16
16
  end
17
17
 
18
+ # Explicitly set the queue name
19
+ def queue_name(name)
20
+ @queue_name = name
21
+ end
22
+
18
23
  # The RabbitMQ queue name for the consumer. This is derived from the
19
24
  # fully-qualified class name. Module separators are replaced with single
20
25
  # colons, camelcased class names are converted to snake case.
21
- def queue_name
26
+ def get_queue_name
27
+ return @queue_name unless @queue_name.nil?
22
28
  queue_name = self.name.gsub(/::/, ':')
23
- queue_name.gsub(/([^A-Z:])([A-Z])/) { "#{$1}_#{$2}" }.downcase
29
+ queue_name.gsub!(/([^A-Z:])([A-Z])/) { "#{$1}_#{$2}" }
30
+ queue_name.downcase
24
31
  end
25
32
 
26
33
  # Accessor for the consumer's routing key.
@@ -1,4 +1,4 @@
1
1
  module Hutch
2
- VERSION = '0.6.0'.freeze
2
+ VERSION = '0.7.0'.freeze
3
3
  end
4
4
 
@@ -24,11 +24,6 @@ module Hutch
24
24
  # Take a break from Thread#join every 0.1 seconds to check if we've
25
25
  # been sent any signals
26
26
  handle_signals until @broker.wait_on_threads(0.1)
27
- rescue Bunny::PreconditionFailed => ex
28
- logger.error ex.message
29
- raise WorkerSetupError.new('could not create queue due to a type ' +
30
- 'conflict with an existing queue, remove ' +
31
- 'the existing queue and try again')
32
27
  end
33
28
 
34
29
  # Register handlers for SIG{QUIT,TERM,INT} to shut down the worker
@@ -67,7 +62,7 @@ module Hutch
67
62
  # Bind a consumer's routing keys to its queue, and set up a subscription to
68
63
  # receive messages sent to the queue.
69
64
  def setup_queue(consumer)
70
- queue = @broker.queue(consumer.queue_name)
65
+ queue = @broker.queue(consumer.get_queue_name)
71
66
  @broker.bind_queue(queue, consumer.routing_keys)
72
67
 
73
68
  queue.subscribe(ack: true) do |delivery_info, properties, payload|
@@ -31,6 +31,15 @@ describe Hutch::Broker do
31
31
  broker.connect { }
32
32
  end
33
33
  end
34
+
35
+ context "with options" do
36
+ let(:options) { { enable_http_api_use: false } }
37
+
38
+ it "doesnt set up api" do
39
+ broker.should_not_receive(:set_up_api_connection)
40
+ broker.connect options
41
+ end
42
+ end
34
43
  end
35
44
 
36
45
  describe '#set_up_amqp_connection', rabbitmq: true do
@@ -68,6 +77,17 @@ describe Hutch::Broker do
68
77
  end
69
78
  end
70
79
 
80
+ describe '#queue' do
81
+ let(:channel) { double('Channel') }
82
+ before { broker.stub(:channel) { channel } }
83
+
84
+ it 'applies a global namespace' do
85
+ config[:namespace] = 'service'
86
+ broker.channel.should_receive(:queue).with { |*args| args.first == 'service:test' }
87
+ broker.queue('test')
88
+ end
89
+ end
90
+
71
91
  describe '#bindings', rabbitmq: true do
72
92
  around { |example| broker.connect { example.run } }
73
93
  subject { broker.bindings }
@@ -88,7 +108,9 @@ describe Hutch::Broker do
88
108
  end
89
109
 
90
110
  describe '#bind_queue' do
111
+
91
112
  around { |example| broker.connect { example.run } }
113
+
92
114
  let(:routing_keys) { %w( a b c ) }
93
115
  let(:queue) { double('Queue', bind: nil, unbind: nil, name: 'consumer') }
94
116
  before { broker.stub(bindings: { 'consumer' => ['d'] }) }
@@ -145,10 +167,47 @@ describe Hutch::Broker do
145
167
  broker.publish('test.key', 'message')
146
168
  end
147
169
 
148
- it "allows passing message properties" do
170
+ it 'sets default properties' do
171
+ broker.exchange.should_receive(:publish).with(
172
+ JSON.dump("message"),
173
+ hash_including(
174
+ persistent: true,
175
+ routing_key: 'test.key',
176
+ content_type: 'application/json'
177
+ )
178
+ )
179
+
180
+ broker.publish('test.key', 'message')
181
+ end
182
+
183
+ it 'allows passing message properties' do
149
184
  broker.exchange.should_receive(:publish).once
150
185
  broker.publish('test.key', 'message', {expiration: "2000", persistent: false})
151
186
  end
187
+
188
+ context 'when there are global properties' do
189
+ context 'as a hash' do
190
+ before do
191
+ Hutch.stub global_properties: { app_id: 'app' }
192
+ end
193
+
194
+ it 'merges the properties' do
195
+ broker.exchange.should_receive(:publish).with('"message"', hash_including(app_id: 'app'))
196
+ broker.publish('test.key', 'message')
197
+ end
198
+ end
199
+
200
+ context 'as a callable object' do
201
+ before do
202
+ Hutch.stub global_properties: proc { { app_id: 'app' } }
203
+ end
204
+
205
+ it 'calls the proc and merges the properties' do
206
+ broker.exchange.should_receive(:publish).with('"message"', hash_including(app_id: 'app'))
207
+ broker.publish('test.key', 'message')
208
+ end
209
+ end
210
+ end
152
211
  end
153
212
 
154
213
  context 'without a valid connection' do
@@ -58,22 +58,42 @@ describe Hutch::Consumer do
58
58
  end
59
59
 
60
60
  describe '.queue_name' do
61
- it 'replaces module separators with colons' do
62
- module Foo
63
- class Bar
61
+ it 'overrides the queue name' do
62
+
63
+ end
64
+ end
65
+
66
+ describe '.get_queue_name' do
67
+
68
+ context 'when queue name has been set explicitly' do
69
+ it 'returns the give queue name' do
70
+ class Foo
64
71
  include Hutch::Consumer
72
+ queue_name "bar"
65
73
  end
66
- end
67
74
 
68
- Foo::Bar.queue_name.should == 'foo:bar'
75
+ Foo.get_queue_name.should == "bar"
76
+ end
69
77
  end
70
78
 
71
- it 'converts camelcase class names to snake case' do
72
- class FooBarBAZ
73
- include Hutch::Consumer
79
+ context 'when no queue name has been set' do
80
+ it 'replaces module separators with colons' do
81
+ module Foo
82
+ class Bar
83
+ include Hutch::Consumer
84
+ end
85
+ end
86
+
87
+ Foo::Bar.get_queue_name.should == 'foo:bar'
74
88
  end
75
89
 
76
- FooBarBAZ.queue_name.should == 'foo_bar_baz'
90
+ it 'converts camelcase class names to snake case' do
91
+ class FooBarBAZ
92
+ include Hutch::Consumer
93
+ end
94
+
95
+ FooBarBAZ.get_queue_name.should == 'foo_bar_baz'
96
+ end
77
97
  end
78
98
  end
79
99
  end
@@ -4,7 +4,7 @@ require 'hutch/worker'
4
4
  describe Hutch::Worker do
5
5
  before { Raven.as_null_object }
6
6
  let(:consumer) { double('Consumer', routing_keys: %w( a b c ),
7
- queue_name: 'consumer') }
7
+ get_queue_name: 'consumer') }
8
8
  let(:consumers) { [consumer, double('Consumer')] }
9
9
  let(:broker) { Hutch::Broker.new }
10
10
  subject(:worker) { Hutch::Worker.new(broker, consumers) }
@@ -24,7 +24,7 @@ describe Hutch::Worker do
24
24
  before { broker.stub(queue: queue, bind_queue: nil) }
25
25
 
26
26
  it 'creates a queue' do
27
- broker.should_receive(:queue).with(consumer.queue_name).and_return(queue)
27
+ broker.should_receive(:queue).with(consumer.get_queue_name).and_return(queue)
28
28
  worker.setup_queue(consumer)
29
29
  end
30
30
 
@@ -12,5 +12,50 @@ describe Hutch do
12
12
  Hutch.consumers.should include consumer_b
13
13
  end
14
14
  end
15
+
16
+ describe '.connect' do
17
+ context 'not connected' do
18
+ let(:options) { double 'options' }
19
+ let(:config) { double 'config' }
20
+ let(:broker) { double 'broker' }
21
+ let(:action) { Hutch.connect(options, config) }
22
+
23
+ it 'passes options and config' do
24
+ Hutch::Broker.should_receive(:new).with(config).and_return broker
25
+ broker.should_receive(:connect).with options
26
+
27
+ action
28
+ end
29
+
30
+ it 'sets @connect' do
31
+ action
32
+
33
+ expect(Hutch.connected?).to be_true
34
+ end
35
+ end
36
+
37
+ context 'connected' do
38
+ before { Hutch.stub(:connected?).and_return true }
39
+
40
+ it 'does not reconnect' do
41
+ Hutch::Broker.should_not_receive :new
42
+ Hutch.connect
43
+ end
44
+ end
45
+ end
46
+
47
+ describe '#publish' do
48
+ let(:broker) { double(Hutch::Broker) }
49
+ let(:args) { ['test.key', 'message', { headers: { foo: 'bar' } }] }
50
+
51
+ before do
52
+ Hutch.stub broker: broker
53
+ end
54
+
55
+ it 'delegates to Hutch::Broker#publish' do
56
+ broker.should_receive(:publish).with(*args)
57
+ Hutch.publish(*args)
58
+ end
59
+ end
15
60
  end
16
61
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hutch
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Harry Marr
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-11-04 00:00:00.000000000 Z
11
+ date: 2014-01-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bunny
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - ~>
18
18
  - !ruby/object:Gem::Version
19
- version: 1.0.0
19
+ version: 1.1.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ~>
25
25
  - !ruby/object:Gem::Version
26
- version: 1.0.0
26
+ version: 1.1.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: carrot-top
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -146,7 +146,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
146
146
  version: '0'
147
147
  requirements: []
148
148
  rubyforge_project:
149
- rubygems_version: 2.0.3
149
+ rubygems_version: 2.1.11
150
150
  signing_key:
151
151
  specification_version: 4
152
152
  summary: Easy inter-service communication using RabbitMQ.
@@ -162,3 +162,4 @@ test_files:
162
162
  - spec/hutch/worker_spec.rb
163
163
  - spec/hutch_spec.rb
164
164
  - spec/spec_helper.rb
165
+ has_rdoc: