philotic 0.0.1 → 0.1.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 +6 -1
- data/Gemfile +0 -7
- data/README.md +2 -0
- data/Rakefile +1 -1
- data/examples/README.md +1 -1
- data/examples/creating_named_queues/manually.rb +8 -21
- data/examples/creating_named_queues/with_rake.rb +2 -2
- data/examples/publishing/publish.rb +26 -33
- data/examples/subscribing/acks.rb +25 -0
- data/examples/subscribing/anonymous_queue.rb +6 -10
- data/examples/subscribing/multiple_named_queues.rb +9 -24
- data/examples/subscribing/named_queue.rb +7 -17
- data/lib/philotic.rb +37 -81
- data/lib/philotic/config.rb +63 -24
- data/lib/philotic/connection.rb +35 -51
- data/lib/philotic/constants.rb +49 -0
- data/lib/philotic/event.rb +27 -14
- data/lib/philotic/logging.rb +8 -0
- data/lib/philotic/logging/event.rb +18 -0
- data/lib/philotic/logging/logger.rb +39 -0
- data/lib/philotic/publisher.rb +28 -31
- data/lib/philotic/routable.rb +40 -40
- data/lib/philotic/subscriber.rb +61 -42
- data/lib/philotic/tasks/init_queues.rb +4 -14
- data/lib/philotic/version.rb +1 -1
- data/philotic.gemspec +21 -10
- data/spec/philotic/config_spec.rb +40 -0
- data/spec/philotic/connection_spec.rb +58 -0
- data/spec/philotic/event_spec.rb +69 -0
- data/spec/philotic/logging/logger_spec.rb +26 -0
- data/spec/philotic/publisher_spec.rb +99 -0
- data/spec/{routable_spec.rb → philotic/routable_spec.rb} +15 -14
- data/spec/philotic/subscriber_spec.rb +111 -0
- data/spec/philotic_spec.rb +66 -0
- data/spec/spec_helper.rb +12 -4
- data/tasks/bump.rake +10 -10
- metadata +173 -36
- data/spec/connection_spec.rb +0 -19
- data/spec/event_spec.rb +0 -44
- data/spec/publisher_spec.rb +0 -102
- data/spec/subscriber_spec.rb +0 -72
data/lib/philotic/routable.rb
CHANGED
@@ -11,46 +11,6 @@ module Philotic
|
|
11
11
|
base.extend ClassMethods
|
12
12
|
end
|
13
13
|
|
14
|
-
def payload
|
15
|
-
attribute_hash = {}
|
16
|
-
self.class.attr_payload_readers.each do |attr|
|
17
|
-
attr = attr.to_sym
|
18
|
-
attribute_hash[attr] = send(attr)
|
19
|
-
end
|
20
|
-
attribute_hash
|
21
|
-
end
|
22
|
-
|
23
|
-
def headers
|
24
|
-
attribute_hash = {}
|
25
|
-
self.class.attr_routable_readers.each do |attr|
|
26
|
-
attr = attr.to_sym
|
27
|
-
attribute_hash[attr] = send(attr)
|
28
|
-
end
|
29
|
-
attribute_hash
|
30
|
-
end
|
31
|
-
|
32
|
-
def attributes
|
33
|
-
attribute_hash = {}
|
34
|
-
(self.class.attr_payload_readers + self.class.attr_routable_readers).each do |attr|
|
35
|
-
attr = attr.to_sym
|
36
|
-
attribute_hash[attr] = send(attr)
|
37
|
-
end
|
38
|
-
attribute_hash
|
39
|
-
end
|
40
|
-
|
41
|
-
def message_metadata
|
42
|
-
@message_metadata ||= {}
|
43
|
-
end
|
44
|
-
|
45
|
-
def message_metadata= options
|
46
|
-
@message_metadata ||= {}
|
47
|
-
@message_metadata.merge! options
|
48
|
-
end
|
49
|
-
|
50
|
-
def publish &block
|
51
|
-
Philotic::Publisher.publish(self, &block)
|
52
|
-
end
|
53
|
-
|
54
14
|
module ClassMethods
|
55
15
|
def attr_payload_reader *names
|
56
16
|
attr_payload_readers.concat(names)
|
@@ -101,6 +61,46 @@ module Philotic
|
|
101
61
|
attr_routable_writers.concat(names)
|
102
62
|
attr_accessor(*names)
|
103
63
|
end
|
64
|
+
|
65
|
+
def publish(*args, &block)
|
66
|
+
self.new(*args).publish(&block)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def payload
|
71
|
+
_payload_or_headers(:payload)
|
72
|
+
end
|
73
|
+
|
74
|
+
def headers
|
75
|
+
_payload_or_headers(:routable)
|
76
|
+
end
|
77
|
+
|
78
|
+
def attributes
|
79
|
+
payload.merge headers
|
80
|
+
end
|
81
|
+
|
82
|
+
def message_metadata
|
83
|
+
@message_metadata ||= {}
|
84
|
+
end
|
85
|
+
|
86
|
+
def message_metadata= options
|
87
|
+
@message_metadata ||= {}
|
88
|
+
@message_metadata.merge! options
|
89
|
+
end
|
90
|
+
|
91
|
+
def publish &block
|
92
|
+
Philotic::Publisher.publish(self, &block)
|
93
|
+
end
|
94
|
+
|
95
|
+
private
|
96
|
+
|
97
|
+
def _payload_or_headers(payload_or_headers)
|
98
|
+
attribute_hash = {}
|
99
|
+
self.class.send("attr_#{payload_or_headers}_readers").each do |attr|
|
100
|
+
attr = attr.to_sym
|
101
|
+
attribute_hash[attr] = send(attr)
|
102
|
+
end
|
103
|
+
attribute_hash
|
104
104
|
end
|
105
105
|
end
|
106
106
|
end
|
data/lib/philotic/subscriber.rb
CHANGED
@@ -1,67 +1,86 @@
|
|
1
1
|
module Philotic
|
2
2
|
class Subscriber
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
_subscribe(options, subscribe_options, &block)
|
9
|
-
end
|
3
|
+
class Metadata
|
4
|
+
attr_accessor :attributes
|
5
|
+
|
6
|
+
def initialize(attributes)
|
7
|
+
self.attributes = attributes
|
10
8
|
end
|
11
9
|
end
|
12
10
|
|
13
|
-
def self.
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
arguments['x-match'] = 'any'
|
11
|
+
def self.subscription_callback
|
12
|
+
lambda do |delivery_info, metadata, payload|
|
13
|
+
hash_payload = JSON.parse payload
|
18
14
|
|
19
|
-
|
15
|
+
event = {
|
16
|
+
payload: hash_payload,
|
17
|
+
headers: metadata[:headers],
|
18
|
+
delivery_info: delivery_info,
|
19
|
+
attributes: metadata[:headers] ? hash_payload.merge(metadata[:headers]) : hash_payload
|
20
|
+
}
|
21
|
+
yield(Metadata.new(metadata), event)
|
22
|
+
end
|
20
23
|
end
|
21
24
|
|
22
|
-
def self.
|
23
|
-
|
24
|
-
|
25
|
+
def self.subscribe(subscription = {}, subscribe_options = Philotic::DEFAULT_SUBSCRIBE_OPTIONS, &block)
|
26
|
+
Philotic.connect!
|
27
|
+
@exchange = Philotic::Connection.exchange
|
28
|
+
|
29
|
+
subscription_settings = get_subscription_settings subscription, subscribe_options
|
30
|
+
|
31
|
+
q = Philotic::Connection.channel.queue(subscription_settings[:queue_name], subscription_settings[:queue_options])
|
25
32
|
|
26
|
-
arguments[
|
33
|
+
q.bind(@exchange, arguments: subscription_settings[:arguments]) if subscription_settings[:arguments]
|
34
|
+
|
35
|
+
q.subscribe(subscription_settings[:subscribe_options], &subscription_callback(&block))
|
27
36
|
|
28
|
-
self.subscribe(options, &block)
|
29
37
|
end
|
30
38
|
|
31
|
-
|
32
|
-
def self._subscribe(options = {}, subscribe_options = Philotic::DEFAULT_SUBSCRIBE_OPTIONS, &block)
|
33
|
-
@@exchange = Philotic::Connection.exchange
|
39
|
+
def self.get_subscription_settings(subscription, subscribe_options)
|
34
40
|
|
35
|
-
if
|
36
|
-
queue_name
|
41
|
+
if subscription.is_a? String
|
42
|
+
queue_name = subscription
|
43
|
+
subscription = subscribe_options
|
37
44
|
queue_options = Philotic::DEFAULT_NAMED_QUEUE_OPTIONS
|
38
|
-
else
|
39
|
-
queue_name = options[:queue_name] || ""
|
40
45
|
|
41
|
-
|
42
|
-
|
43
|
-
|
46
|
+
else
|
47
|
+
queue_name = subscription[:queue_name] || ''
|
48
|
+
queue_options = Philotic::DEFAULT_ANONYMOUS_QUEUE_OPTIONS
|
49
|
+
subscribe_options = subscribe_options.merge(subscription[:subscribe_options]) if subscription[:subscribe_options]
|
50
|
+
arguments = subscription[:arguments] || subscription
|
44
51
|
arguments['x-match'] ||= 'all'
|
45
52
|
end
|
46
53
|
|
47
|
-
queue_options[:
|
54
|
+
queue_options.merge!(subscription[:queue_options] || {})
|
48
55
|
|
49
|
-
|
50
|
-
hash_payload = JSON.parse payload
|
56
|
+
queue_options[:auto_delete] ||= true if queue_name == ''
|
51
57
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
q = AMQP.channel.queue(queue_name, queue_options)
|
58
|
+
{
|
59
|
+
queue_name: queue_name,
|
60
|
+
queue_options: queue_options,
|
61
|
+
arguments: arguments,
|
62
|
+
subscribe_options: subscribe_options,
|
63
|
+
}
|
64
|
+
end
|
60
65
|
|
61
|
-
|
66
|
+
def self.acknowledge(message, up_to_and_including=false)
|
67
|
+
Philotic::Connection.channel.acknowledge(message[:delivery_info].delivery_tag, up_to_and_including)
|
68
|
+
end
|
62
69
|
|
63
|
-
|
70
|
+
def self.reject(message, requeue=true)
|
71
|
+
Philotic::Connection.channel.reject(message[:delivery_info].delivery_tag, requeue)
|
72
|
+
end
|
64
73
|
|
74
|
+
def self.subscribe_to_any(options = {})
|
75
|
+
if block_given?
|
76
|
+
self.subscribe(options.merge(:'x-match' => :any), &Proc.new)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.endure
|
81
|
+
while true
|
82
|
+
Thread.pass
|
83
|
+
end
|
65
84
|
end
|
66
85
|
end
|
67
|
-
end
|
86
|
+
end
|
@@ -9,20 +9,10 @@ namespace :philotic do
|
|
9
9
|
require 'philotic'
|
10
10
|
|
11
11
|
@filename = args[:filename]
|
12
|
-
queues
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
Philotic.initialize_named_queue!("#{queues.keys.sort[index]}", queues[queues.keys.sort[index]]) do |q|
|
17
|
-
if index == queues.size - 1
|
18
|
-
Philotic::Connection.close { EM.stop }
|
19
|
-
else
|
20
|
-
init_queues queues, index + 1
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
init_queues queues
|
12
|
+
queues = YAML.load_file(@filename)
|
13
|
+
Philotic.connect!
|
14
|
+
queues.each_pair do |queue_name, queue_options|
|
15
|
+
Philotic.initialize_named_queue!(queue_name, queue_options)
|
26
16
|
end
|
27
17
|
end
|
28
18
|
end
|
data/lib/philotic/version.rb
CHANGED
data/philotic.gemspec
CHANGED
@@ -2,23 +2,34 @@
|
|
2
2
|
require File.expand_path('../lib/philotic/version', __FILE__)
|
3
3
|
|
4
4
|
Gem::Specification.new do |gem|
|
5
|
-
gem.authors
|
6
|
-
gem.email
|
7
|
-
gem.description
|
8
|
-
gem.summary
|
9
|
-
gem.homepage
|
5
|
+
gem.authors = ['Nathan Keyes']
|
6
|
+
gem.email = ['nkeyes@gmail.com']
|
7
|
+
gem.description = %q{Lightweight, opinionated wrapper for using RabbitMQ headers exchanges}
|
8
|
+
gem.summary = %q{Lightweight, opinionated wrapper for using RabbitMQ headers exchanges}
|
9
|
+
gem.homepage = 'https://github.com/nkeyes/philotic'
|
10
10
|
|
11
11
|
gem.files = `git ls-files`.split($\)
|
12
|
-
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
|
13
13
|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
14
|
gem.name = 'philotic'
|
15
15
|
gem.require_paths = ['lib']
|
16
16
|
gem.version = Philotic::VERSION
|
17
|
-
gem.licenses
|
17
|
+
gem.licenses = ['MIT']
|
18
18
|
|
19
|
-
|
20
|
-
gem.
|
21
|
-
gem.
|
19
|
+
|
20
|
+
gem.add_development_dependency 'codeclimate-test-reporter'
|
21
|
+
gem.add_development_dependency 'bundler', '~> 1.6'
|
22
|
+
gem.add_development_dependency 'evented-spec', '~> 0.9'
|
23
|
+
gem.add_development_dependency 'pry', '~> 0.10'
|
24
|
+
gem.add_development_dependency 'rake', '~> 10.3'
|
25
|
+
gem.add_development_dependency 'rspec', '~> 3.1'
|
26
|
+
gem.add_development_dependency 'rspec-its', '~> 1.1'
|
27
|
+
gem.add_development_dependency 'timecop', '~> 0.7'
|
28
|
+
gem.add_development_dependency 'simplecov'
|
29
|
+
|
30
|
+
gem.add_dependency 'activesupport', '>= 3.2'
|
31
|
+
gem.add_dependency 'activerecord', '>= 3.2'
|
22
32
|
gem.add_dependency 'awesome_print', '~> 1.2'
|
33
|
+
gem.add_dependency 'bunny', '~> 1.2.1'
|
23
34
|
gem.add_dependency 'json', '~> 1.8'
|
24
35
|
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Philotic::Config do
|
4
|
+
|
5
|
+
describe '.defaults' do
|
6
|
+
end
|
7
|
+
|
8
|
+
describe '.load' do
|
9
|
+
end
|
10
|
+
|
11
|
+
describe '.load_file' do
|
12
|
+
end
|
13
|
+
|
14
|
+
describe '.parse_rabbit_uri' do
|
15
|
+
let(:url) { 'amqp://user:pass@host:12345/vhost' }
|
16
|
+
before do
|
17
|
+
Philotic::Config.rabbit_url = url
|
18
|
+
end
|
19
|
+
subject { lambda { Philotic::Config.parse_rabbit_uri } }
|
20
|
+
|
21
|
+
it do
|
22
|
+
should change {
|
23
|
+
[
|
24
|
+
Philotic::Config.rabbit_user,
|
25
|
+
Philotic::Config.rabbit_password,
|
26
|
+
Philotic::Config.rabbit_host,
|
27
|
+
Philotic::Config.rabbit_port,
|
28
|
+
Philotic::Config.rabbit_vhost,
|
29
|
+
]
|
30
|
+
}
|
31
|
+
.to [
|
32
|
+
'user',
|
33
|
+
'pass',
|
34
|
+
'host',
|
35
|
+
12345,
|
36
|
+
'vhost',
|
37
|
+
]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Philotic::Connection do
|
4
|
+
|
5
|
+
describe '.config' do
|
6
|
+
its(:config) { should eq Philotic::Config }
|
7
|
+
end
|
8
|
+
|
9
|
+
describe '.connect!' do
|
10
|
+
context 'not connected' do
|
11
|
+
context 'success' do
|
12
|
+
specify do
|
13
|
+
expect(subject).to receive(:connected?).and_return(false, true)
|
14
|
+
expect(subject).to receive(:start_connection!)
|
15
|
+
expect(subject).to receive(:set_exchange_return_handler!)
|
16
|
+
|
17
|
+
subject.connect!
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
context 'failure' do
|
23
|
+
specify do
|
24
|
+
expect(subject).to receive(:connected?).and_return(false, false)
|
25
|
+
expect(subject).to receive(:start_connection!)
|
26
|
+
expect(subject).not_to receive(:set_exchange_return_handler!)
|
27
|
+
expect(Philotic.logger).to receive(:error)
|
28
|
+
|
29
|
+
subject.connect!
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
context 'not connected' do
|
36
|
+
context 'success' do
|
37
|
+
specify do
|
38
|
+
expect(subject).to receive(:connected?).and_return(true)
|
39
|
+
expect(subject).not_to receive(:start_connection!)
|
40
|
+
expect(subject).not_to receive(:set_exchange_return_handler!)
|
41
|
+
|
42
|
+
subject.connect!
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe '.start_connection!' do
|
50
|
+
let(:connection) { double }
|
51
|
+
specify do
|
52
|
+
expect(Bunny).to receive(:new).with(Philotic::Config.rabbit_url, Philotic::Connection.connection_settings).and_return(connection)
|
53
|
+
expect(connection).to receive(:start)
|
54
|
+
|
55
|
+
Philotic::Connection.start_connection!
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
# create 'deep' inheritance to test self.inherited
|
4
|
+
class TestEventParent < Philotic::Event
|
5
|
+
end
|
6
|
+
class TestEvent < TestEventParent
|
7
|
+
end
|
8
|
+
|
9
|
+
describe Philotic::Event do
|
10
|
+
let(:event) { TestEvent.new }
|
11
|
+
subject { event }
|
12
|
+
|
13
|
+
Philotic::Routable::ClassMethods.instance_methods.sort.each do |method_name|
|
14
|
+
specify { expect(subject.class.methods).to include method_name.to_sym }
|
15
|
+
end
|
16
|
+
|
17
|
+
Philotic::MESSAGE_OPTIONS.each do |method_name|
|
18
|
+
specify { expect(subject.methods).to include method_name.to_sym }
|
19
|
+
specify { expect(subject.methods).to include "#{method_name}=".to_sym }
|
20
|
+
end
|
21
|
+
|
22
|
+
Philotic::PHILOTIC_HEADERS.each do |method_name|
|
23
|
+
specify { expect(subject.methods).to include method_name.to_sym }
|
24
|
+
specify { expect(subject.methods).to include "#{method_name}=".to_sym }
|
25
|
+
end
|
26
|
+
|
27
|
+
describe 'message_metadata' do
|
28
|
+
it 'should have a timestamp' do
|
29
|
+
Timecop.freeze
|
30
|
+
expect(subject.message_metadata).to eq(timestamp: Time.now.to_i)
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'should reflect changes in the event properties' do
|
34
|
+
expect(subject.message_metadata[:app_id]).to eq nil
|
35
|
+
subject.app_id = 'ANSIBLE'
|
36
|
+
expect(subject.message_metadata[:app_id]).to eq 'ANSIBLE'
|
37
|
+
end
|
38
|
+
end
|
39
|
+
describe 'headers' do
|
40
|
+
it 'should include :philotic_product' do
|
41
|
+
expect(subject.headers.keys).to include :philotic_product
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context 'generic event' do
|
46
|
+
let(:headers) do
|
47
|
+
{
|
48
|
+
header1: 'h1',
|
49
|
+
header2: 'h2',
|
50
|
+
header3: 'h3',
|
51
|
+
}
|
52
|
+
end
|
53
|
+
|
54
|
+
let(:payloads) do
|
55
|
+
{
|
56
|
+
payload1: 'h1',
|
57
|
+
payload2: 'h2',
|
58
|
+
payload3: 'h3',
|
59
|
+
}
|
60
|
+
end
|
61
|
+
it 'builds an event with dynamic headers and payloads' do
|
62
|
+
event = Philotic::Event.new(headers, payloads)
|
63
|
+
|
64
|
+
expect(event.headers).to include(headers)
|
65
|
+
expect(event.payload).to eq payloads
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|