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 +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:
|