hutch 0.6.0 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +3 -2
- data/CHANGELOG.md +4 -0
- data/LICENSE +1 -1
- data/README.md +19 -3
- data/hutch.gemspec +1 -1
- data/lib/hutch.rb +12 -4
- data/lib/hutch/broker.rb +116 -63
- data/lib/hutch/cli.rb +6 -2
- data/lib/hutch/config.rb +2 -1
- data/lib/hutch/consumer.rb +9 -2
- data/lib/hutch/version.rb +1 -1
- data/lib/hutch/worker.rb +1 -6
- data/spec/hutch/broker_spec.rb +60 -1
- data/spec/hutch/consumer_spec.rb +29 -9
- data/spec/hutch/worker_spec.rb +2 -2
- data/spec/hutch_spec.rb +45 -0
- metadata +6 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2432ba126d871fbe57468e88a39c6d589a01aa2f
|
4
|
+
data.tar.gz: 865526ad5d702eaaea80ee000b6e8ef879b5ac18
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 87d496756e3df55679391977995de5eb836a949cd80ca0ecafb7d99966512c736be01307e72976cad95fb122fcd048c51154a7456c9cfec7b1e4d2080ef9c20f
|
7
|
+
data.tar.gz: 5b8f3e3fd23af0451da805d64f90ff8afc5b8ee0be93c04a4fec7be45598c32fc2ad96d59442d660974f8b3d18e47f2e507aa6988b369207721173e5e1812877
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
data/LICENSE
CHANGED
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
|
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
|
-
|
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
|
-
|
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
|
data/hutch.gemspec
CHANGED
@@ -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.
|
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'
|
data/lib/hutch.rb
CHANGED
@@ -21,10 +21,18 @@ module Hutch
|
|
21
21
|
Hutch::Logging.logger
|
22
22
|
end
|
23
23
|
|
24
|
-
def self.
|
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(
|
41
|
-
|
48
|
+
def self.publish(*args)
|
49
|
+
broker.publish(*args)
|
42
50
|
end
|
43
51
|
end
|
44
52
|
|
data/lib/hutch/broker.rb
CHANGED
@@ -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
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
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
|
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:
|
65
|
+
heartbeat: 30, automatically_recover: true,
|
49
66
|
network_recovery_interval: 1)
|
50
|
-
@connection.start
|
51
67
|
|
52
|
-
|
53
|
-
|
68
|
+
with_bunny_connection_handler(sanitized_uri) do
|
69
|
+
@connection.start
|
70
|
+
end
|
54
71
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
logger.
|
60
|
-
|
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
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
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
|
-
|
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
|
-
|
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,
|
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(
|
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
|
|
data/lib/hutch/cli.rb
CHANGED
@@ -97,7 +97,7 @@ module Hutch
|
|
97
97
|
Hutch::Config.mq_port = port
|
98
98
|
end
|
99
99
|
|
100
|
-
opts.on(
|
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(
|
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
|
data/lib/hutch/config.rb
CHANGED
data/lib/hutch/consumer.rb
CHANGED
@@ -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
|
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}" }
|
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.
|
data/lib/hutch/version.rb
CHANGED
data/lib/hutch/worker.rb
CHANGED
@@ -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.
|
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|
|
data/spec/hutch/broker_spec.rb
CHANGED
@@ -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
|
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
|
data/spec/hutch/consumer_spec.rb
CHANGED
@@ -58,22 +58,42 @@ describe Hutch::Consumer do
|
|
58
58
|
end
|
59
59
|
|
60
60
|
describe '.queue_name' do
|
61
|
-
it '
|
62
|
-
|
63
|
-
|
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
|
-
|
75
|
+
Foo.get_queue_name.should == "bar"
|
76
|
+
end
|
69
77
|
end
|
70
78
|
|
71
|
-
|
72
|
-
|
73
|
-
|
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
|
-
|
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
|
data/spec/hutch/worker_spec.rb
CHANGED
@@ -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
|
-
|
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.
|
27
|
+
broker.should_receive(:queue).with(consumer.get_queue_name).and_return(queue)
|
28
28
|
worker.setup_queue(consumer)
|
29
29
|
end
|
30
30
|
|
data/spec/hutch_spec.rb
CHANGED
@@ -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.
|
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:
|
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.
|
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.
|
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.
|
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:
|