multiple_man 0.8.1 → 1.0.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/README.md +24 -1
- data/circle.yml +1 -1
- data/lib/multiple_man/channel_maintenance/gc.rb +1 -1
- data/lib/multiple_man/configuration.rb +27 -10
- data/lib/multiple_man/consumers/general.rb +75 -0
- data/lib/multiple_man/consumers/seed.rb +15 -0
- data/lib/multiple_man/consumers/transitional.rb +50 -0
- data/lib/multiple_man/mixins/listener.rb +8 -11
- data/lib/multiple_man/mixins/subscriber.rb +1 -1
- data/lib/multiple_man/subscribers/base.rb +7 -7
- data/lib/multiple_man/subscribers/registry.rb +7 -6
- data/lib/multiple_man/tasks/worker.rake +47 -6
- data/lib/multiple_man/version.rb +1 -1
- data/lib/multiple_man.rb +13 -6
- data/multiple_man.gemspec +5 -3
- data/spec/connection_spec.rb +10 -11
- data/spec/consumers/general_spec.rb +107 -0
- data/spec/consumers/seed_spec.rb +66 -0
- data/spec/consumers/transitional_spec.rb +33 -0
- data/spec/mixins/listener_spec.rb +22 -0
- data/spec/{subscriber_spec.rb → mixins/subscriber_spec.rb} +1 -1
- data/spec/subscribers/base_spec.rb +0 -7
- data/spec/subscribers/registry_spec.rb +6 -5
- metadata +48 -43
- data/lib/multiple_man/listeners/listener.rb +0 -76
- data/lib/multiple_man/listeners/seeder_listener.rb +0 -16
- data/spec/listeners/listener_spec.rb +0 -75
- data/spec/listeners/seeder_listener_spec.rb +0 -22
- /data/spec/{publisher_spec.rb → mixins/publisher_spec.rb} +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: eff9e5989a0fe4702caf87df4d8a50b0fb5cea03
|
4
|
+
data.tar.gz: 04361d38e491938b791dfb98e16727240ee33daa
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4fdf426a23e43e4ee9f13d19d2b940d329b8ead830fe0155043370c0341d7e8b1ef012d8712375f85d9235c9aea2a61e9084b80b6837dd8b37e01f0952c7988a
|
7
|
+
data.tar.gz: 092424fa6b574af0eef20f6585b0fe1026cc0baee453eb4d349bb106d9d71ca2f612df050dee82030140dea45f43f017043d784d5bf3ee70c2b2e74ea9fd1ae3
|
data/README.md
CHANGED
@@ -68,6 +68,9 @@ exceptions encountered in an `after_commit` block, meaning
|
|
68
68
|
that without handling these errors through the configuration,
|
69
69
|
they will be silently ignored.
|
70
70
|
|
71
|
+
Errors will be captured and wrapped in a MultipleMan::Error. The
|
72
|
+
cause will be preserved in Exception#cause.
|
73
|
+
|
71
74
|
### Publishing models
|
72
75
|
|
73
76
|
#### Directly from the model
|
@@ -111,7 +114,7 @@ By default, MultipleMan will publish all of your models whenever you save a mode
|
|
111
114
|
|
112
115
|
```
|
113
116
|
# Publish all widgets to MultipleMan
|
114
|
-
Widget.multiple_man_publish
|
117
|
+
Widget.multiple_man_publish
|
115
118
|
|
116
119
|
# Publish a subset of widgets to MultipleMan
|
117
120
|
Widget.where(published: true).multiple_man_publish
|
@@ -184,6 +187,26 @@ MyModel.multiple_man_publish(:seed)
|
|
184
187
|
|
185
188
|
3. Stop the seeder rake task when all of your messages have been processed. You can check your RabbitMQ server
|
186
189
|
|
190
|
+
## Upgrading to 1.0
|
191
|
+
|
192
|
+
The major change is that MultipleMan will no longer create a queue per listener.
|
193
|
+
There is only 1 queue that will have multiple bindings to the exchange so that
|
194
|
+
you have a chance to maintain causal consistency.
|
195
|
+
|
196
|
+
Assuming you are using vanilla MultipleMan you will need to run both the
|
197
|
+
regular worker and the new 'transition_worker' for a short period. The
|
198
|
+
transitional worker will connect to your old queues, unbind them and allow them
|
199
|
+
to drain. Once the queues are empty you can safely shut the transitional worker
|
200
|
+
down and delete the old queues.
|
201
|
+
|
202
|
+
So for example if you use a Procfile:
|
203
|
+
|
204
|
+
```
|
205
|
+
multiple_man_worker: rake multiple_man:worker
|
206
|
+
# Temporary until old queues are drained
|
207
|
+
transition_worker: rake multiple_man:transition_worker
|
208
|
+
```
|
209
|
+
|
187
210
|
## Contributing
|
188
211
|
|
189
212
|
1. Fork it
|
data/circle.yml
CHANGED
@@ -1,5 +1,19 @@
|
|
1
1
|
module MultipleMan
|
2
|
+
def self.configuration
|
3
|
+
@configuration ||= Configuration.new
|
4
|
+
end
|
5
|
+
|
6
|
+
def self.configure
|
7
|
+
yield(configuration) if block_given?
|
8
|
+
end
|
9
|
+
|
2
10
|
class Configuration
|
11
|
+
attr_reader :subscriber_registry
|
12
|
+
attr_accessor :topic_name, :app_name, :connection, :enabled, :error_handler,
|
13
|
+
:worker_concurrency, :reraise_errors, :connection_recovery,
|
14
|
+
:queue_name, :prefetch_size
|
15
|
+
|
16
|
+
attr_writer :logger
|
3
17
|
|
4
18
|
def initialize
|
5
19
|
self.topic_name = "multiple_man"
|
@@ -7,11 +21,18 @@ module MultipleMan
|
|
7
21
|
self.enabled = true
|
8
22
|
self.worker_concurrency = 1
|
9
23
|
self.reraise_errors = true
|
24
|
+
self.prefetch_size = 100
|
10
25
|
self.connection_recovery = {
|
11
26
|
time_before_reconnect: 0.2,
|
12
27
|
time_between_retries: 0.8,
|
13
28
|
max_retries: 5
|
14
29
|
}
|
30
|
+
|
31
|
+
@subscriber_registry = Subscribers::Registry.new
|
32
|
+
end
|
33
|
+
|
34
|
+
def queue_name
|
35
|
+
@queue_name ||= "#{topic_name}.#{app_name}"
|
15
36
|
end
|
16
37
|
|
17
38
|
def logger
|
@@ -22,16 +43,12 @@ module MultipleMan
|
|
22
43
|
@error_handler = block
|
23
44
|
end
|
24
45
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
end
|
29
|
-
|
30
|
-
def self.configuration
|
31
|
-
@configuration ||= Configuration.new
|
32
|
-
end
|
46
|
+
def listeners
|
47
|
+
subscriber_registry.subscriptions
|
48
|
+
end
|
33
49
|
|
34
|
-
|
35
|
-
|
50
|
+
def register_listener(listener)
|
51
|
+
subscriber_registry.register(listener)
|
52
|
+
end
|
36
53
|
end
|
37
54
|
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'active_support/core_ext/hash'
|
3
|
+
|
4
|
+
module MultipleMan
|
5
|
+
module Consumers
|
6
|
+
class General
|
7
|
+
def initialize(subscribers:, queue:, topic:)
|
8
|
+
self.subscribers = subscribers
|
9
|
+
@topic = topic
|
10
|
+
@queue = queue
|
11
|
+
end
|
12
|
+
|
13
|
+
def listen
|
14
|
+
MultipleMan.logger.debug "Starting listeners."
|
15
|
+
create_bindings
|
16
|
+
|
17
|
+
queue.subscribe(manual_ack: true) do |delivery_info, meta_data, payload|
|
18
|
+
MultipleMan.logger.debug "Processing message for #{delivery_info.routing_key}."
|
19
|
+
message = JSON.parse(payload).with_indifferent_access
|
20
|
+
receive(delivery_info, meta_data, message)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
attr_reader :subscribers, :topic, :queue
|
27
|
+
|
28
|
+
def create_bindings
|
29
|
+
subscribers.values.each do |subscriber|
|
30
|
+
MultipleMan.logger.info "Listening for #{subscriber.listen_to} with routing key #{subscriber.routing_key}."
|
31
|
+
queue.bind(topic, routing_key: routing_key_for_subscriber(subscriber))
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def receive(delivery_info, _, message)
|
36
|
+
method = operation(message, delivery_info.routing_key)
|
37
|
+
dispatch_subscribers(message, method, delivery_info.routing_key)
|
38
|
+
queue.channel.acknowledge(delivery_info.delivery_tag, false)
|
39
|
+
|
40
|
+
MultipleMan.logger.debug "Successfully processed! #{delivery_info.routing_key}"
|
41
|
+
rescue => ex
|
42
|
+
begin
|
43
|
+
raise ConsumerError
|
44
|
+
rescue => wrapped_ex
|
45
|
+
MultipleMan.logger.debug "\tError #{wrapped_ex.message} \n#{wrapped_ex.backtrace}"
|
46
|
+
MultipleMan.error(wrapped_ex, reraise: false, payload: message, delivery_info: delivery_info)
|
47
|
+
queue.channel.nack(delivery_info.delivery_tag)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def dispatch_subscribers(message, method, routing_key)
|
52
|
+
subscribers.select { |k,s| k.match(routing_key) }
|
53
|
+
.values
|
54
|
+
.each do |s|
|
55
|
+
s.send(method, message)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def operation(message, routing_key)
|
60
|
+
message['operation'] || routing_key.split('.').last
|
61
|
+
end
|
62
|
+
|
63
|
+
def routing_key_for_subscriber(subscriber)
|
64
|
+
subscriber.routing_key
|
65
|
+
end
|
66
|
+
|
67
|
+
def subscribers=(subscribers)
|
68
|
+
@subscribers = subscribers.map { |s|
|
69
|
+
key = routing_key_for_subscriber(s).gsub('.', '\.').gsub('#', '.*')
|
70
|
+
[/^#{key}$/, s]
|
71
|
+
}.to_h
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'active_support/core_ext/hash'
|
3
|
+
|
4
|
+
module MultipleMan::Consumers
|
5
|
+
class Transitional
|
6
|
+
|
7
|
+
attr_reader :subscription, :queue, :topic
|
8
|
+
|
9
|
+
def initialize(subscription:, queue:, topic:)
|
10
|
+
@subscription = subscription
|
11
|
+
@topic = topic
|
12
|
+
@queue = queue
|
13
|
+
end
|
14
|
+
|
15
|
+
def listen
|
16
|
+
MultipleMan.logger.info "Listening for #{subscription.listen_to} with routing key #{routing_key}."
|
17
|
+
queue.unbind(topic, routing_key: routing_key).subscribe(manual_ack: true) do |delivery_info, _, payload|
|
18
|
+
process_message(delivery_info, payload)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def process_message(delivery_info, payload)
|
23
|
+
MultipleMan.logger.debug "Processing message for #{delivery_info.routing_key}."
|
24
|
+
|
25
|
+
payload = JSON.parse(payload).with_indifferent_access
|
26
|
+
subscription.send(operation(delivery_info, payload), payload)
|
27
|
+
MultipleMan.logger.debug " Successfully processed!"
|
28
|
+
queue.channel.acknowledge(delivery_info.delivery_tag, false)
|
29
|
+
rescue => ex
|
30
|
+
raise MultipleMan::ConsumerError rescue handle_error($!, delivery_info)
|
31
|
+
end
|
32
|
+
|
33
|
+
def handle_error(ex, delivery_info)
|
34
|
+
MultipleMan.logger.error " Error - #{ex.message}\n\n#{ex.backtrace}"
|
35
|
+
MultipleMan.error(ex, reraise: false)
|
36
|
+
|
37
|
+
# Requeue the message
|
38
|
+
queue.channel.nack(delivery_info.delivery_tag)
|
39
|
+
end
|
40
|
+
|
41
|
+
def operation(delivery_info, payload)
|
42
|
+
payload['operation'] || delivery_info.routing_key.split(".").last
|
43
|
+
end
|
44
|
+
|
45
|
+
def routing_key
|
46
|
+
subscription.routing_key
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
@@ -1,15 +1,15 @@
|
|
1
1
|
module MultipleMan
|
2
2
|
module Listener
|
3
|
-
def
|
3
|
+
def self.included(base)
|
4
4
|
base.extend(ClassMethods)
|
5
5
|
end
|
6
6
|
|
7
|
-
def routing_key(operation=self.operation)
|
8
|
-
MultipleMan::RoutingKey.new(
|
7
|
+
def routing_key(operation = self.operation)
|
8
|
+
MultipleMan::RoutingKey.new(listen_to, operation).to_s
|
9
9
|
end
|
10
10
|
|
11
|
-
attr_accessor :klass
|
12
11
|
attr_accessor :operation
|
12
|
+
attr_accessor :listen_to
|
13
13
|
|
14
14
|
def create(payload)
|
15
15
|
# noop
|
@@ -23,16 +23,13 @@ module MultipleMan
|
|
23
23
|
# noop
|
24
24
|
end
|
25
25
|
|
26
|
-
def queue_name
|
27
|
-
"#{MultipleMan.configuration.topic_name}.#{MultipleMan.configuration.app_name}.#{klass}"
|
28
|
-
end
|
29
|
-
|
30
26
|
module ClassMethods
|
31
27
|
def listen_to(model, operation: '#')
|
32
|
-
listener =
|
33
|
-
listener.
|
28
|
+
listener = new
|
29
|
+
listener.listen_to = model
|
34
30
|
listener.operation = operation
|
35
|
-
|
31
|
+
MultipleMan.configuration.register_listener(listener)
|
32
|
+
listener
|
36
33
|
end
|
37
34
|
end
|
38
35
|
end
|
@@ -6,7 +6,7 @@ module MultipleMan
|
|
6
6
|
|
7
7
|
module ClassMethods
|
8
8
|
def subscribe(options = {})
|
9
|
-
|
9
|
+
::MultipleMan.configuration.register_listener(Subscribers::ModelSubscriber.new(self, options))
|
10
10
|
end
|
11
11
|
end
|
12
12
|
end
|
@@ -7,19 +7,19 @@ module MultipleMan::Subscribers
|
|
7
7
|
|
8
8
|
attr_reader :klass
|
9
9
|
|
10
|
-
def create(
|
10
|
+
def create(_)
|
11
11
|
# noop
|
12
12
|
end
|
13
13
|
|
14
|
-
def update(
|
14
|
+
def update(_)
|
15
15
|
# noop
|
16
16
|
end
|
17
17
|
|
18
|
-
def destroy(
|
18
|
+
def destroy(_)
|
19
19
|
# noop
|
20
20
|
end
|
21
21
|
|
22
|
-
def seed(
|
22
|
+
def seed(_)
|
23
23
|
# noop
|
24
24
|
end
|
25
25
|
|
@@ -27,11 +27,11 @@ module MultipleMan::Subscribers
|
|
27
27
|
MultipleMan::RoutingKey.new(klass, operation).to_s
|
28
28
|
end
|
29
29
|
|
30
|
-
def
|
31
|
-
|
30
|
+
def listen_to
|
31
|
+
klass
|
32
32
|
end
|
33
33
|
|
34
|
-
|
34
|
+
private
|
35
35
|
|
36
36
|
attr_writer :klass
|
37
37
|
end
|
@@ -1,12 +1,13 @@
|
|
1
1
|
module MultipleMan::Subscribers
|
2
2
|
class Registry
|
3
|
-
|
4
|
-
class << self
|
5
|
-
attr_accessor :subscriptions
|
3
|
+
attr_reader :subscriptions
|
6
4
|
|
7
|
-
|
8
|
-
|
9
|
-
|
5
|
+
def initialize
|
6
|
+
@subscriptions = []
|
7
|
+
end
|
8
|
+
|
9
|
+
def register(subscription)
|
10
|
+
self.subscriptions << subscription
|
10
11
|
end
|
11
12
|
end
|
12
13
|
end
|
@@ -1,18 +1,58 @@
|
|
1
1
|
namespace :multiple_man do
|
2
2
|
desc "Run multiple man listeners"
|
3
|
-
task :
|
4
|
-
|
3
|
+
task worker: :environment do
|
4
|
+
channel = MultipleMan::Connection.connection.create_channel
|
5
|
+
channel.prefetch(MultipleMan.configuration.prefetch_size)
|
6
|
+
queue_name = MultipleMan.configuration.queue_name
|
7
|
+
queue = channel.queue(queue_name, durable: true, auto_delete: false)
|
8
|
+
|
9
|
+
run_listener(MultipleMan::Consumers::General, queue)
|
5
10
|
end
|
6
11
|
|
7
12
|
desc 'Run a seeding listener'
|
8
13
|
task seed: :environment do
|
9
|
-
|
14
|
+
channel = MultipleMan::Connection.connection.create_channel
|
15
|
+
channel.prefetch(MultipleMan.configuration.prefetch_size)
|
16
|
+
queue_name = MultipleMan.configuration.queue_name + '.seed'
|
17
|
+
queue = channel.queue(queue_name, durable: false, auto_delete: true)
|
18
|
+
|
19
|
+
run_listener(MultipleMan::Consumers::Seed, queue)
|
20
|
+
end
|
21
|
+
|
22
|
+
def run_listener(listener, queue)
|
23
|
+
Rails.application.eager_load! if defined?(Rails)
|
24
|
+
|
25
|
+
subscribers = MultipleMan.configuration.listeners
|
26
|
+
topic = MultipleMan.configuration.topic_name
|
27
|
+
|
28
|
+
listener.new(subscribers: subscribers, queue: queue, topic: topic).listen
|
29
|
+
|
30
|
+
Signal.trap("INT") { puts "received INT"; exit }
|
31
|
+
Signal.trap("QUIT") { puts "received QUIT"; exit }
|
32
|
+
Signal.trap("TERM") { puts "received TERM"; exit }
|
33
|
+
|
34
|
+
sleep
|
10
35
|
end
|
11
36
|
|
12
|
-
|
13
|
-
|
37
|
+
desc 'Run transitional worker'
|
38
|
+
task transition_worker: :environment do
|
39
|
+
Rails.application.eager_load! if defined?(Rails)
|
14
40
|
|
15
|
-
|
41
|
+
topic = MultipleMan.configuration.topic_name
|
42
|
+
app_name = MultipleMan.configuration.app_name
|
43
|
+
channel = MultipleMan::Connection.connection.create_channel
|
44
|
+
channel.prefetch(MultipleMan.configuration.prefetch_size)
|
45
|
+
|
46
|
+
MultipleMan.configuration.listeners.each do |listener|
|
47
|
+
queue_name = listener.respond_to?(:queue_name) ?
|
48
|
+
listener.queue_name :
|
49
|
+
"#{topic}.#{app_name}.#{listener.listen_to}"
|
50
|
+
|
51
|
+
next unless MultipleMan::Connection.connection.queue_exists?(queue_name)
|
52
|
+
queue = channel.queue(queue_name, durable: true, auto_delete: false)
|
53
|
+
|
54
|
+
MultipleMan::Consumers::Transitional.new(subscription: listener, queue: queue, topic: topic).listen
|
55
|
+
end
|
16
56
|
|
17
57
|
Signal.trap("INT") { puts "received INT"; exit }
|
18
58
|
Signal.trap("QUIT") { puts "received QUIT"; exit }
|
@@ -20,4 +60,5 @@ namespace :multiple_man do
|
|
20
60
|
|
21
61
|
sleep
|
22
62
|
end
|
63
|
+
|
23
64
|
end
|
data/lib/multiple_man/version.rb
CHANGED
data/lib/multiple_man.rb
CHANGED
@@ -2,6 +2,9 @@ require "multiple_man/version"
|
|
2
2
|
require 'active_support'
|
3
3
|
|
4
4
|
module MultipleMan
|
5
|
+
Error = Class.new(StandardError)
|
6
|
+
ConsumerError = Class.new(Error)
|
7
|
+
|
5
8
|
require 'multiple_man/railtie' if defined?(Rails)
|
6
9
|
|
7
10
|
require 'multiple_man/mixins/publisher'
|
@@ -16,8 +19,9 @@ module MultipleMan
|
|
16
19
|
require 'multiple_man/payload_generator'
|
17
20
|
require 'multiple_man/connection'
|
18
21
|
require 'multiple_man/routing_key'
|
19
|
-
require 'multiple_man/
|
20
|
-
require 'multiple_man/
|
22
|
+
require 'multiple_man/consumers/general'
|
23
|
+
require 'multiple_man/consumers/transitional'
|
24
|
+
require 'multiple_man/consumers/seed'
|
21
25
|
require 'multiple_man/model_populator'
|
22
26
|
require 'multiple_man/identity'
|
23
27
|
require 'multiple_man/publish'
|
@@ -38,11 +42,14 @@ module MultipleMan
|
|
38
42
|
end
|
39
43
|
|
40
44
|
def self.error(ex, options = {})
|
41
|
-
|
42
|
-
|
43
|
-
|
45
|
+
raise ex unless configuration.error_handler
|
46
|
+
|
47
|
+
if configuration.error_handler.arity == 3
|
48
|
+
configuration.error_handler.call(ex, options[:payload], options[:delivery_info])
|
44
49
|
else
|
45
|
-
|
50
|
+
configuration.error_handler.call(ex)
|
46
51
|
end
|
52
|
+
|
53
|
+
raise ex if configuration.reraise_errors && options[:reraise] != false
|
47
54
|
end
|
48
55
|
end
|
data/multiple_man.gemspec
CHANGED
@@ -10,13 +10,17 @@ Gem::Specification.new do |spec|
|
|
10
10
|
spec.email = ["ryan@influitive.com"]
|
11
11
|
spec.description = %q{MultipleMan syncs changes to ActiveRecord models via AMQP}
|
12
12
|
spec.summary = %q{MultipleMan syncs changes to ActiveRecord models via AMQP}
|
13
|
-
spec.homepage = ""
|
13
|
+
spec.homepage = "http://github.com/influitive/multiple_man"
|
14
14
|
spec.license = "MIT"
|
15
15
|
|
16
16
|
spec.files = `git ls-files`.split($/)
|
17
17
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
19
|
spec.require_paths = ["lib"]
|
20
|
+
spec.required_ruby_version = '>= 2.1'
|
21
|
+
|
22
|
+
spec.add_runtime_dependency "bunny", '>= 1.2'
|
23
|
+
spec.add_runtime_dependency "activesupport", '>= 3.0'
|
20
24
|
|
21
25
|
spec.add_development_dependency "bundler", "~> 1.3"
|
22
26
|
spec.add_development_dependency "pry"
|
@@ -25,6 +29,4 @@ Gem::Specification.new do |spec|
|
|
25
29
|
spec.add_development_dependency 'codeclimate-test-reporter'
|
26
30
|
spec.add_development_dependency 'guard'
|
27
31
|
spec.add_development_dependency 'guard-rspec'
|
28
|
-
spec.add_runtime_dependency "bunny", '>= 1.2'
|
29
|
-
spec.add_runtime_dependency "activesupport", '>= 3.0'
|
30
32
|
end
|
data/spec/connection_spec.rb
CHANGED
@@ -3,19 +3,18 @@ require 'spec_helper'
|
|
3
3
|
describe MultipleMan::Connection do
|
4
4
|
|
5
5
|
let(:mock_bunny) { double(Bunny, open?: true, close: nil) }
|
6
|
-
let(:mock_channel) { double(Bunny::Channel, close: nil, open?: true, topic: nil) }
|
6
|
+
let(:mock_channel) { double(Bunny::Channel, close: nil, open?: true, topic: nil, number: 1) }
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
mock_bunny.should_receive(:create_channel).once.and_return(mock_channel)
|
12
|
-
|
13
|
-
described_class.connect { }
|
14
|
-
end
|
8
|
+
after do
|
9
|
+
Thread.current.thread_variable_set(:multiple_man_current_channel, nil)
|
10
|
+
MultipleMan::Connection.reset!
|
15
11
|
end
|
16
12
|
|
17
|
-
|
18
|
-
|
19
|
-
|
13
|
+
it "should open a connection and a channel" do
|
14
|
+
MultipleMan::Connection.should_receive(:connection).and_return(mock_bunny)
|
15
|
+
mock_bunny.should_receive(:create_channel).once.and_return(mock_channel)
|
16
|
+
expect(mock_channel).to receive(:topic).with(MultipleMan.configuration.topic_name)
|
20
17
|
|
18
|
+
described_class.connect { }
|
19
|
+
end
|
21
20
|
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe MultipleMan::Consumers::General do
|
4
|
+
let(:listener1) {
|
5
|
+
Class.new do
|
6
|
+
include MultipleMan::Listener
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
self.listen_to = 'SomeClass'
|
10
|
+
self.operation = '#'
|
11
|
+
end
|
12
|
+
end
|
13
|
+
}
|
14
|
+
|
15
|
+
let(:listener2) {
|
16
|
+
Class.new do
|
17
|
+
include MultipleMan::Listener
|
18
|
+
|
19
|
+
def initialize
|
20
|
+
self.listen_to = 'SomeOtherClass'
|
21
|
+
self.operation = '#'
|
22
|
+
end
|
23
|
+
end
|
24
|
+
}
|
25
|
+
|
26
|
+
it "listens to each subscription" do
|
27
|
+
subscriptions = [listener1.new, listener2.new]
|
28
|
+
queue = double(Bunny::Queue)
|
29
|
+
|
30
|
+
expect(queue).to receive(:bind).with('some-topic', routing_key: subscriptions.first.routing_key).ordered
|
31
|
+
expect(queue).to receive(:bind).with('some-topic', routing_key: subscriptions.last.routing_key).ordered
|
32
|
+
expect(queue).to receive(:subscribe).with(manual_ack: true).ordered
|
33
|
+
|
34
|
+
subject = described_class.new(subscribers: subscriptions, queue: queue, topic: 'some-topic')
|
35
|
+
|
36
|
+
subject.listen
|
37
|
+
end
|
38
|
+
|
39
|
+
it "sends the correct data" do
|
40
|
+
channel = double(Bunny::Channel)
|
41
|
+
queue = double(Bunny::Queue, channel: channel).as_null_object
|
42
|
+
|
43
|
+
subscriber = listener1.new
|
44
|
+
subject = described_class.new(subscribers:[subscriber], queue: queue, topic: 'some-topic')
|
45
|
+
|
46
|
+
expect(channel).to receive(:acknowledge)
|
47
|
+
expect(subscriber).to receive(:create).with({"a" => 1, "b" => 2})
|
48
|
+
|
49
|
+
delivery_info = OpenStruct.new(routing_key: "multiple_man.SomeClass.create")
|
50
|
+
payload = '{"a":1,"b":2}'
|
51
|
+
allow(queue).to receive(:subscribe).and_yield(delivery_info, double(:meta), payload)
|
52
|
+
|
53
|
+
subject.listen
|
54
|
+
end
|
55
|
+
|
56
|
+
it "uses the payload to determine the operation if it's available" do
|
57
|
+
channel = double(Bunny::Channel).as_null_object
|
58
|
+
queue = double(Bunny::Queue, channel: channel).as_null_object
|
59
|
+
|
60
|
+
subscriber = listener1.new
|
61
|
+
subject = described_class.new(subscribers:[subscriber], queue: queue, topic: 'some-topic')
|
62
|
+
|
63
|
+
delivery_info = OpenStruct.new(routing_key: "multiple_man.SomeClass.some_other_operation")
|
64
|
+
payload = '{"operation": "create", "a":1,"b":2}'
|
65
|
+
allow(queue).to receive(:subscribe).and_yield(delivery_info, double(:meta), payload)
|
66
|
+
|
67
|
+
subscriber.should_receive(:create)
|
68
|
+
|
69
|
+
subject.listen
|
70
|
+
end
|
71
|
+
|
72
|
+
it "sends a nack on failure" do
|
73
|
+
allow(MultipleMan.configuration).to receive(:error_handler) { double(:handler).as_null_object}
|
74
|
+
|
75
|
+
channel = double(Bunny::Channel)
|
76
|
+
queue = double(Bunny::Queue, channel: channel).as_null_object
|
77
|
+
|
78
|
+
delivery_info = OpenStruct.new(routing_key: "multiple_man.SomeClass.create")
|
79
|
+
payload = '{"a":1,"b":2}'
|
80
|
+
allow(queue).to receive(:subscribe).and_yield(delivery_info, double(:meta), payload)
|
81
|
+
|
82
|
+
subscriber = listener1.new
|
83
|
+
allow(subscriber).to receive(:create).and_raise('anything')
|
84
|
+
|
85
|
+
expect(channel).to receive(:nack)
|
86
|
+
subject = described_class.new(subscribers:[subscriber], queue: queue, topic: 'some-topic')
|
87
|
+
subject.listen
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'wraps errors in ConsumerError' do
|
91
|
+
channel = double(Bunny::Channel)
|
92
|
+
queue = double(Bunny::Queue, channel: channel).as_null_object
|
93
|
+
|
94
|
+
delivery_info = OpenStruct.new(routing_key: "multiple_man.SomeClass.create")
|
95
|
+
payload = '{"a":1,"b":2}'
|
96
|
+
allow(queue).to receive(:subscribe).and_yield(delivery_info, double(:meta), payload)
|
97
|
+
|
98
|
+
subscriber = listener1.new
|
99
|
+
allow(subscriber).to receive(:create).and_raise('anything')
|
100
|
+
|
101
|
+
allow(channel).to receive(:nack)
|
102
|
+
|
103
|
+
expect(MultipleMan).to receive(:error).with(kind_of(MultipleMan::ConsumerError), kind_of(Hash))
|
104
|
+
subject = described_class.new(subscribers:[subscriber], queue: queue, topic: 'some-topic')
|
105
|
+
subject.listen
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe MultipleMan::Consumers::Seed do
|
4
|
+
let(:listener1) {
|
5
|
+
Class.new do
|
6
|
+
include MultipleMan::Listener
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
self.listen_to = 'SomeClass'
|
10
|
+
self.operation = '#'
|
11
|
+
end
|
12
|
+
end
|
13
|
+
}
|
14
|
+
|
15
|
+
it 'binds for seeding' do
|
16
|
+
channel = double(Bunny::Channel).as_null_object
|
17
|
+
queue = double(Bunny::Queue, channel: channel)
|
18
|
+
|
19
|
+
expect(queue).to receive(:bind).with('some-topic', routing_key: "multiple_man.seed.SomeClass")
|
20
|
+
expect(queue).to receive(:subscribe).with(manual_ack: true)
|
21
|
+
|
22
|
+
subject = described_class.new(subscribers: [listener1.new], queue: queue, topic: 'some-topic')
|
23
|
+
subject.listen
|
24
|
+
end
|
25
|
+
|
26
|
+
it "sends the correct data" do
|
27
|
+
channel = double(Bunny::Channel)
|
28
|
+
queue = double(Bunny::Queue, channel: channel).as_null_object
|
29
|
+
|
30
|
+
subscriber = listener1.new
|
31
|
+
subject = described_class.new(subscribers:[subscriber], queue: queue, topic: 'some-topic')
|
32
|
+
|
33
|
+
expect(channel).to receive(:acknowledge)
|
34
|
+
expect(subscriber).to receive(:create).with({"a" => 1, "b" => 2})
|
35
|
+
|
36
|
+
delivery_info = OpenStruct.new(routing_key: "multiple_man.seed.SomeClass")
|
37
|
+
payload = '{"a":1,"b":2}'
|
38
|
+
allow(queue).to receive(:subscribe).and_yield(delivery_info, double(:meta), payload)
|
39
|
+
|
40
|
+
subject.listen
|
41
|
+
end
|
42
|
+
|
43
|
+
let(:group) { Class.new { include MultipleMan::Listener } }
|
44
|
+
let(:group_contact) { Class.new { include MultipleMan::Listener } }
|
45
|
+
|
46
|
+
# BUGFIX:
|
47
|
+
it "correctly matches subscribers" do
|
48
|
+
channel = double(Bunny::Channel)
|
49
|
+
queue = double(Bunny::Queue, channel: channel).as_null_object
|
50
|
+
|
51
|
+
g = group.listen_to 'Group'
|
52
|
+
gc = group_contact.listen_to 'GroupContact'
|
53
|
+
|
54
|
+
subject = described_class.new(subscribers:[g, gc], queue: queue, topic: 'some-topic')
|
55
|
+
|
56
|
+
expect(channel).to receive(:acknowledge)
|
57
|
+
expect(g).to_not receive(:create)
|
58
|
+
expect(gc).to receive(:create).with({"a" => 1, "b" => 2})
|
59
|
+
|
60
|
+
delivery_info = OpenStruct.new(routing_key: "multiple_man.seed.GroupContact")
|
61
|
+
payload = '{"a":1,"b":2}'
|
62
|
+
allow(queue).to receive(:subscribe).and_yield(delivery_info, double(:meta), payload)
|
63
|
+
|
64
|
+
subject.listen
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe MultipleMan::Consumers::Transitional do
|
4
|
+
let(:listener1) {
|
5
|
+
Class.new do
|
6
|
+
include MultipleMan::Listener
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
self.listen_to = 'SomeClass'
|
10
|
+
self.operation = '#'
|
11
|
+
end
|
12
|
+
end
|
13
|
+
}
|
14
|
+
|
15
|
+
it 'wraps errors in ConsumerError' do
|
16
|
+
channel = double(Bunny::Channel)
|
17
|
+
queue = double(Bunny::Queue, channel: channel).as_null_object
|
18
|
+
|
19
|
+
delivery_info = OpenStruct.new(routing_key: "multiple_man.SomeClass.create")
|
20
|
+
payload = '{"a":1,"b":2}'
|
21
|
+
allow(queue).to receive(:subscribe).and_yield(delivery_info, double(:meta), payload)
|
22
|
+
|
23
|
+
subscriber = listener1.new
|
24
|
+
allow(subscriber).to receive(:create).and_raise('anything')
|
25
|
+
|
26
|
+
allow(channel).to receive(:nack)
|
27
|
+
|
28
|
+
expect(MultipleMan).to receive(:error).with(kind_of(MultipleMan::ConsumerError), kind_of(Hash))
|
29
|
+
subject = described_class.new(subscription:subscriber, queue: queue, topic: 'some-topic')
|
30
|
+
subject.listen
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe MultipleMan::Subscriber do
|
4
|
+
let(:mock_class) do
|
5
|
+
Class.new do
|
6
|
+
|
7
|
+
include MultipleMan::Listener
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
describe "listen_to" do
|
12
|
+
it "should register itself" do
|
13
|
+
MultipleMan.configuration.should_receive(:register_listener).with(instance_of(mock_class))
|
14
|
+
mock_class.listen_to "Model"
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should have a routing key for what it's listening to" do
|
18
|
+
listener = mock_class.listen_to "Model"
|
19
|
+
expect(listener.routing_key).to eq("multiple_man.Model.#")
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -7,7 +7,7 @@ describe MultipleMan::Subscriber do
|
|
7
7
|
|
8
8
|
describe "subscribe" do
|
9
9
|
it "should register itself" do
|
10
|
-
MultipleMan
|
10
|
+
expect(MultipleMan.configuration).to receive(:register_listener).with(instance_of(MultipleMan::Subscribers::ModelSubscriber))
|
11
11
|
MockClass.subscribe fields: [:foo, :bar]
|
12
12
|
end
|
13
13
|
end
|
@@ -8,13 +8,6 @@ describe MultipleMan::Subscribers::Base do
|
|
8
8
|
described_class.new(MockClass).routing_key.should =~ /\.MockClass\.\#$/
|
9
9
|
end
|
10
10
|
|
11
|
-
specify "queue name should be the app name + class" do
|
12
|
-
MultipleMan.configure do |config|
|
13
|
-
config.app_name = "test"
|
14
|
-
end
|
15
|
-
described_class.new(MockClass).queue_name.should =~ /\.test\.MockClass$/
|
16
|
-
end
|
17
|
-
|
18
11
|
specify "it should be alright to use a string for a class name" do
|
19
12
|
described_class.new("MockClass").routing_key.should =~ /\.MockClass\.\#$/
|
20
13
|
end
|
@@ -1,11 +1,12 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
describe MultipleMan::Subscribers::Registry do
|
4
|
-
describe "register" do
|
3
|
+
describe MultipleMan::Subscribers::Registry do
|
4
|
+
describe "register" do
|
5
5
|
it "should add a subscriber" do
|
6
6
|
subscription = double(:subscriber)
|
7
|
-
|
8
|
-
|
7
|
+
|
8
|
+
subject.register(subscription)
|
9
|
+
subject.subscriptions[0].should == subscription
|
9
10
|
end
|
10
11
|
end
|
11
|
-
end
|
12
|
+
end
|
metadata
CHANGED
@@ -1,15 +1,43 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: multiple_man
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ryan Brunner
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2016-06-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bunny
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.2'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.2'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: activesupport
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '3.0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '3.0'
|
13
41
|
- !ruby/object:Gem::Dependency
|
14
42
|
name: bundler
|
15
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -108,34 +136,6 @@ dependencies:
|
|
108
136
|
- - ">="
|
109
137
|
- !ruby/object:Gem::Version
|
110
138
|
version: '0'
|
111
|
-
- !ruby/object:Gem::Dependency
|
112
|
-
name: bunny
|
113
|
-
requirement: !ruby/object:Gem::Requirement
|
114
|
-
requirements:
|
115
|
-
- - ">="
|
116
|
-
- !ruby/object:Gem::Version
|
117
|
-
version: '1.2'
|
118
|
-
type: :runtime
|
119
|
-
prerelease: false
|
120
|
-
version_requirements: !ruby/object:Gem::Requirement
|
121
|
-
requirements:
|
122
|
-
- - ">="
|
123
|
-
- !ruby/object:Gem::Version
|
124
|
-
version: '1.2'
|
125
|
-
- !ruby/object:Gem::Dependency
|
126
|
-
name: activesupport
|
127
|
-
requirement: !ruby/object:Gem::Requirement
|
128
|
-
requirements:
|
129
|
-
- - ">="
|
130
|
-
- !ruby/object:Gem::Version
|
131
|
-
version: '3.0'
|
132
|
-
type: :runtime
|
133
|
-
prerelease: false
|
134
|
-
version_requirements: !ruby/object:Gem::Requirement
|
135
|
-
requirements:
|
136
|
-
- - ">="
|
137
|
-
- !ruby/object:Gem::Version
|
138
|
-
version: '3.0'
|
139
139
|
description: MultipleMan syncs changes to ActiveRecord models via AMQP
|
140
140
|
email:
|
141
141
|
- ryan@influitive.com
|
@@ -157,9 +157,10 @@ files:
|
|
157
157
|
- lib/multiple_man/channel_maintenance/reaper.rb
|
158
158
|
- lib/multiple_man/configuration.rb
|
159
159
|
- lib/multiple_man/connection.rb
|
160
|
+
- lib/multiple_man/consumers/general.rb
|
161
|
+
- lib/multiple_man/consumers/seed.rb
|
162
|
+
- lib/multiple_man/consumers/transitional.rb
|
160
163
|
- lib/multiple_man/identity.rb
|
161
|
-
- lib/multiple_man/listeners/listener.rb
|
162
|
-
- lib/multiple_man/listeners/seeder_listener.rb
|
163
164
|
- lib/multiple_man/mixins/listener.rb
|
164
165
|
- lib/multiple_man/mixins/publisher.rb
|
165
166
|
- lib/multiple_man/mixins/subscriber.rb
|
@@ -177,22 +178,24 @@ files:
|
|
177
178
|
- multiple_man.gemspec
|
178
179
|
- spec/attribute_extractor_spec.rb
|
179
180
|
- spec/connection_spec.rb
|
181
|
+
- spec/consumers/general_spec.rb
|
182
|
+
- spec/consumers/seed_spec.rb
|
183
|
+
- spec/consumers/transitional_spec.rb
|
180
184
|
- spec/identity_spec.rb
|
181
185
|
- spec/integration/ephermal_model_spec.rb
|
182
|
-
- spec/listeners/listener_spec.rb
|
183
|
-
- spec/listeners/seeder_listener_spec.rb
|
184
186
|
- spec/logger_spec.rb
|
187
|
+
- spec/mixins/listener_spec.rb
|
188
|
+
- spec/mixins/publisher_spec.rb
|
189
|
+
- spec/mixins/subscriber_spec.rb
|
185
190
|
- spec/model_populator_spec.rb
|
186
191
|
- spec/model_publisher_spec.rb
|
187
192
|
- spec/payload_generator_spec.rb
|
188
|
-
- spec/publisher_spec.rb
|
189
193
|
- spec/routing_key_spec.rb
|
190
194
|
- spec/spec_helper.rb
|
191
|
-
- spec/subscriber_spec.rb
|
192
195
|
- spec/subscribers/base_spec.rb
|
193
196
|
- spec/subscribers/model_subscriber_spec.rb
|
194
197
|
- spec/subscribers/registry_spec.rb
|
195
|
-
homepage:
|
198
|
+
homepage: http://github.com/influitive/multiple_man
|
196
199
|
licenses:
|
197
200
|
- MIT
|
198
201
|
metadata: {}
|
@@ -204,7 +207,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
204
207
|
requirements:
|
205
208
|
- - ">="
|
206
209
|
- !ruby/object:Gem::Version
|
207
|
-
version: '
|
210
|
+
version: '2.1'
|
208
211
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
209
212
|
requirements:
|
210
213
|
- - ">="
|
@@ -212,25 +215,27 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
212
215
|
version: '0'
|
213
216
|
requirements: []
|
214
217
|
rubyforge_project:
|
215
|
-
rubygems_version: 2.4.5
|
218
|
+
rubygems_version: 2.4.5.1
|
216
219
|
signing_key:
|
217
220
|
specification_version: 4
|
218
221
|
summary: MultipleMan syncs changes to ActiveRecord models via AMQP
|
219
222
|
test_files:
|
220
223
|
- spec/attribute_extractor_spec.rb
|
221
224
|
- spec/connection_spec.rb
|
225
|
+
- spec/consumers/general_spec.rb
|
226
|
+
- spec/consumers/seed_spec.rb
|
227
|
+
- spec/consumers/transitional_spec.rb
|
222
228
|
- spec/identity_spec.rb
|
223
229
|
- spec/integration/ephermal_model_spec.rb
|
224
|
-
- spec/listeners/listener_spec.rb
|
225
|
-
- spec/listeners/seeder_listener_spec.rb
|
226
230
|
- spec/logger_spec.rb
|
231
|
+
- spec/mixins/listener_spec.rb
|
232
|
+
- spec/mixins/publisher_spec.rb
|
233
|
+
- spec/mixins/subscriber_spec.rb
|
227
234
|
- spec/model_populator_spec.rb
|
228
235
|
- spec/model_publisher_spec.rb
|
229
236
|
- spec/payload_generator_spec.rb
|
230
|
-
- spec/publisher_spec.rb
|
231
237
|
- spec/routing_key_spec.rb
|
232
238
|
- spec/spec_helper.rb
|
233
|
-
- spec/subscriber_spec.rb
|
234
239
|
- spec/subscribers/base_spec.rb
|
235
240
|
- spec/subscribers/model_subscriber_spec.rb
|
236
241
|
- spec/subscribers/registry_spec.rb
|
@@ -1,76 +0,0 @@
|
|
1
|
-
require 'json'
|
2
|
-
require 'active_support/core_ext/hash'
|
3
|
-
|
4
|
-
module MultipleMan::Listeners
|
5
|
-
class Listener
|
6
|
-
|
7
|
-
class << self
|
8
|
-
def start
|
9
|
-
MultipleMan.logger.debug "Starting listeners."
|
10
|
-
|
11
|
-
MultipleMan::Subscribers::Registry.subscriptions.each do |subscription|
|
12
|
-
new(subscription).listen
|
13
|
-
end
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
delegate :queue_name, to: :subscription
|
18
|
-
|
19
|
-
def initialize(subscription)
|
20
|
-
self.subscription = subscription
|
21
|
-
self.init_connection
|
22
|
-
end
|
23
|
-
|
24
|
-
def init_connection
|
25
|
-
connection = Bunny.new(MultipleMan.configuration.connection)
|
26
|
-
connection.start
|
27
|
-
channel = connection.create_channel(nil, MultipleMan.configuration.worker_concurrency)
|
28
|
-
channel.prefetch(100)
|
29
|
-
self.connection = MultipleMan::Connection.new(channel)
|
30
|
-
end
|
31
|
-
|
32
|
-
attr_accessor :subscription, :connection
|
33
|
-
|
34
|
-
def listen
|
35
|
-
|
36
|
-
MultipleMan.logger.info "Listening for #{subscription.klass} with routing key #{routing_key}."
|
37
|
-
queue.bind(connection.topic, routing_key: routing_key).subscribe(ack: true) do |delivery_info, meta_data, payload|
|
38
|
-
process_message(delivery_info, payload)
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
def process_message(delivery_info, payload)
|
43
|
-
MultipleMan.logger.info "Processing message for #{delivery_info.routing_key}."
|
44
|
-
begin
|
45
|
-
payload = JSON.parse(payload).with_indifferent_access
|
46
|
-
subscription.send(operation(delivery_info, payload), payload)
|
47
|
-
rescue Exception => ex
|
48
|
-
handle_error(ex, delivery_info)
|
49
|
-
else
|
50
|
-
MultipleMan.logger.debug " Successfully processed!"
|
51
|
-
queue.channel.acknowledge(delivery_info.delivery_tag, false)
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
def handle_error(ex, delivery_info)
|
56
|
-
MultipleMan.logger.error " Error - #{ex.message}\n\n#{ex.backtrace}"
|
57
|
-
MultipleMan.error(ex, reraise: false)
|
58
|
-
|
59
|
-
# Requeue the message
|
60
|
-
queue.channel.nack(delivery_info.delivery_tag)
|
61
|
-
end
|
62
|
-
|
63
|
-
def operation(delivery_info, payload)
|
64
|
-
payload['operation'] || delivery_info.routing_key.split(".").last
|
65
|
-
end
|
66
|
-
|
67
|
-
def queue
|
68
|
-
connection.queue(queue_name, durable: true, auto_delete: false)
|
69
|
-
end
|
70
|
-
|
71
|
-
def routing_key
|
72
|
-
subscription.routing_key
|
73
|
-
end
|
74
|
-
|
75
|
-
end
|
76
|
-
end
|
@@ -1,16 +0,0 @@
|
|
1
|
-
module MultipleMan::Listeners
|
2
|
-
class SeederListener < Listener
|
3
|
-
def routing_key
|
4
|
-
subscription.routing_key(:seed)
|
5
|
-
end
|
6
|
-
|
7
|
-
# seeds should only ever be a create
|
8
|
-
def operation(delivery_info, payload)
|
9
|
-
"create"
|
10
|
-
end
|
11
|
-
|
12
|
-
def queue
|
13
|
-
connection.queue(subscription.queue_name + ".seed", auto_delete: true)
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
@@ -1,75 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
describe MultipleMan::Listeners::Listener do
|
4
|
-
class MockClass1; end
|
5
|
-
class MockClass2; end
|
6
|
-
|
7
|
-
before { MultipleMan::Connection.stub(:connection).and_return(double(Bunny).as_null_object)}
|
8
|
-
|
9
|
-
describe "start" do
|
10
|
-
it "should listen to each subscription" do
|
11
|
-
MultipleMan::Subscribers::Registry.stub(:subscriptions).and_return([
|
12
|
-
mock1 = double(MultipleMan::Subscribers::ModelSubscriber, klass: MockClass1),
|
13
|
-
mock2 = double(MultipleMan::Subscribers::ModelSubscriber, klass: MockClass2)
|
14
|
-
])
|
15
|
-
|
16
|
-
mock_listener = double(described_class)
|
17
|
-
described_class.should_receive(:new).twice.and_return(mock_listener)
|
18
|
-
|
19
|
-
# Would actually be two seperate objects in reality, this is for
|
20
|
-
# ease of stubbing.
|
21
|
-
mock_listener.should_receive(:listen).twice
|
22
|
-
|
23
|
-
described_class.start
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
describe "listen" do
|
28
|
-
let(:connection_stub) { double(MultipleMan::Connection, queue: queue_stub, topic: 'app') }
|
29
|
-
let(:queue_stub) { double(Bunny::Queue, bind: bind_stub) }
|
30
|
-
let(:bind_stub) { double(:bind, subscribe: nil)}
|
31
|
-
|
32
|
-
before { MultipleMan::Connection.stub(:new).and_return(connection_stub) }
|
33
|
-
|
34
|
-
it "should listen to the right topic, and for all updates to a model" do
|
35
|
-
listener = described_class.new(double(MultipleMan::Subscribers::ModelSubscriber, klass: MockClass1, routing_key: "MockClass1.#", queue_name: "MockClass1"))
|
36
|
-
queue_stub.should_receive(:bind).with('app', routing_key: "MockClass1.#")
|
37
|
-
listener.listen
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
specify "process_message should send the correct data" do
|
42
|
-
connection_stub = double(MultipleMan::Connection).as_null_object
|
43
|
-
MultipleMan::Connection.stub(:new).and_return(connection_stub)
|
44
|
-
subscriber = double(MultipleMan::Subscribers::ModelSubscriber, klass: MockClass1, routing_key: "MockClass1.#").as_null_object
|
45
|
-
listener = described_class.new(subscriber)
|
46
|
-
|
47
|
-
connection_stub.should_receive(:acknowledge)
|
48
|
-
subscriber.should_receive(:create).with({"a" => 1, "b" => 2})
|
49
|
-
listener.process_message(OpenStruct.new(routing_key: "app.MockClass1.create"), '{"a":1,"b":2}')
|
50
|
-
end
|
51
|
-
|
52
|
-
specify "process_message should use the payload to determine the operation if it's available" do
|
53
|
-
connection_stub = double(MultipleMan::Connection).as_null_object
|
54
|
-
MultipleMan::Connection.stub(:new).and_return(connection_stub)
|
55
|
-
subscriber = double(MultipleMan::Subscribers::ModelSubscriber, klass: MockClass1, routing_key: "MockClass1.#").as_null_object
|
56
|
-
listener = described_class.new(subscriber)
|
57
|
-
|
58
|
-
connection_stub.should_receive(:acknowledge)
|
59
|
-
subscriber.should_receive(:create)
|
60
|
-
listener.process_message(OpenStruct.new(routing_key: "some random routing key"), '{"operation":"create","data":{"a":1,"b":2}}')
|
61
|
-
end
|
62
|
-
|
63
|
-
it "should nack on failure" do
|
64
|
-
connection_stub = double(MultipleMan::Connection).as_null_object
|
65
|
-
MultipleMan::Connection.stub(:new).and_return(connection_stub)
|
66
|
-
subscriber = double(MultipleMan::Subscribers::ModelSubscriber, klass: MockClass1, routing_key: "MockClass1.#").as_null_object
|
67
|
-
listener = described_class.new(subscriber)
|
68
|
-
|
69
|
-
connection_stub.should_receive(:nack)
|
70
|
-
MultipleMan.should_receive(:error)
|
71
|
-
subscriber.should_receive(:create).with({"a" => 1, "b" => 2}).and_raise("fail!")
|
72
|
-
|
73
|
-
listener.process_message(OpenStruct.new(routing_key: "app.MockClass1.create"), '{"a":1,"b":2}')
|
74
|
-
end
|
75
|
-
end
|
@@ -1,22 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
describe MultipleMan::Listeners::SeederListener do
|
4
|
-
class MockClass1; end
|
5
|
-
|
6
|
-
before { MultipleMan::Connection.stub(:connection).and_return(double(Bunny).as_null_object)}
|
7
|
-
let(:connection_stub) { double(MultipleMan::Connection, queue: queue_stub, topic: 'app') }
|
8
|
-
let(:queue_stub) { double(Bunny::Queue, bind: bind_stub) }
|
9
|
-
let(:bind_stub) { double(:bind, subscribe: nil)}
|
10
|
-
|
11
|
-
before { MultipleMan::Connection.stub(:new).and_return(connection_stub) }
|
12
|
-
|
13
|
-
it 'listens to seed events' do
|
14
|
-
listener = described_class.new(double(MultipleMan::Subscribers::ModelSubscriber,
|
15
|
-
klass: MockClass1,
|
16
|
-
routing_key: "seed.MockClass1",
|
17
|
-
queue_name: "MockClass1"))
|
18
|
-
|
19
|
-
queue_stub.should_receive(:bind).with('app', routing_key: "seed.MockClass1")
|
20
|
-
listener.listen
|
21
|
-
end
|
22
|
-
end
|
File without changes
|