philotic 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +25 -0
- data/.rspec +2 -0
- data/.travis.yml +9 -0
- data/Gemfile +11 -0
- data/LICENSE +22 -0
- data/README.md +7 -0
- data/Rakefile +24 -0
- data/examples/README.md +13 -0
- data/examples/creating_named_queues/manually.rb +26 -0
- data/examples/creating_named_queues/philotic_named_queues.yml +6 -0
- data/examples/creating_named_queues/with_rake.rb +11 -0
- data/examples/publishing/publish.rb +51 -0
- data/examples/subscribing/anonymous_queue.rb +19 -0
- data/examples/subscribing/multiple_named_queues.rb +33 -0
- data/examples/subscribing/named_queue.rb +23 -0
- data/lib/philotic.rb +144 -0
- data/lib/philotic/config.rb +79 -0
- data/lib/philotic/connection.rb +83 -0
- data/lib/philotic/dummy_event.rb +8 -0
- data/lib/philotic/event.rb +69 -0
- data/lib/philotic/publisher.rb +58 -0
- data/lib/philotic/routable.rb +106 -0
- data/lib/philotic/subscriber.rb +67 -0
- data/lib/philotic/tasks.rb +1 -0
- data/lib/philotic/tasks/init_queues.rb +28 -0
- data/lib/philotic/version.rb +3 -0
- data/philotic.gemspec +24 -0
- data/philotic.yml.example +36 -0
- data/philotic_queues.yml.example +19 -0
- data/spec/connection_spec.rb +19 -0
- data/spec/event_spec.rb +44 -0
- data/spec/publisher_spec.rb +102 -0
- data/spec/routable_spec.rb +58 -0
- data/spec/spec_helper.rb +23 -0
- data/spec/subscriber_spec.rb +72 -0
- data/tasks/bump.rake +30 -0
- metadata +157 -0
@@ -0,0 +1 @@
|
|
1
|
+
require 'philotic/tasks/init_queues'
|
@@ -0,0 +1,28 @@
|
|
1
|
+
desc "initialize named durable queues"
|
2
|
+
namespace :philotic do
|
3
|
+
task :init_queues, :filename do |t, args|
|
4
|
+
raise "You must specify a file name for #{t.name}: rake #{t.name}[FILENAME] #yes, you need the brackets, no space." if !args[:filename]
|
5
|
+
|
6
|
+
# ENV['INITIALIZE_NAMED_QUEUE'] must equal 'true' to run Philotic.initialize_named_queue!
|
7
|
+
ENV['INITIALIZE_NAMED_QUEUE'] = 'true'
|
8
|
+
|
9
|
+
require 'philotic'
|
10
|
+
|
11
|
+
@filename = args[:filename]
|
12
|
+
queues = YAML.load_file(@filename)
|
13
|
+
|
14
|
+
EM.run do
|
15
|
+
def init_queues queues, index = 0
|
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
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/philotic.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/philotic/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
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
|
+
|
11
|
+
gem.files = `git ls-files`.split($\)
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
|
+
gem.name = 'philotic'
|
15
|
+
gem.require_paths = ['lib']
|
16
|
+
gem.version = Philotic::VERSION
|
17
|
+
gem.licenses = ['MIT']
|
18
|
+
|
19
|
+
gem.add_dependency 'activesupport', '~> 4.0'
|
20
|
+
gem.add_dependency 'activerecord', '~> 4.0'
|
21
|
+
gem.add_dependency 'amqp', '~> 1.2'
|
22
|
+
gem.add_dependency 'awesome_print', '~> 1.2'
|
23
|
+
gem.add_dependency 'json', '~> 1.8'
|
24
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
defaults: &defaults
|
2
|
+
#connection settings
|
3
|
+
rabbit_host: localhost
|
4
|
+
# connection_failed_handler: method/proc
|
5
|
+
# connection_loss_handler: method/proc
|
6
|
+
timeout: 2
|
7
|
+
|
8
|
+
#exchange settings
|
9
|
+
exchange_name: philotic.headers
|
10
|
+
# message_return_handler: method/proc
|
11
|
+
|
12
|
+
#message settings
|
13
|
+
routing_key: ~
|
14
|
+
persistent: true
|
15
|
+
# immediate: true
|
16
|
+
mandatory: true
|
17
|
+
content_type: ~
|
18
|
+
content_encoding: ~
|
19
|
+
priority: ~
|
20
|
+
message_id: ~
|
21
|
+
correlation_id: ~
|
22
|
+
reply_to: ~
|
23
|
+
type: ~
|
24
|
+
user_id: ~
|
25
|
+
app_id: MY_APP
|
26
|
+
timestamp: ~
|
27
|
+
expiration: ~
|
28
|
+
|
29
|
+
development:
|
30
|
+
<<: *defaults
|
31
|
+
|
32
|
+
test:
|
33
|
+
<<: *defaults
|
34
|
+
|
35
|
+
production:
|
36
|
+
<<: *defaults
|
@@ -0,0 +1,19 @@
|
|
1
|
+
available_or_male:
|
2
|
+
gender: Male
|
3
|
+
available: true
|
4
|
+
x-match: any
|
5
|
+
|
6
|
+
available_male:
|
7
|
+
gender: Male
|
8
|
+
available: true
|
9
|
+
x-match: all
|
10
|
+
|
11
|
+
available_or_female:
|
12
|
+
gender: Female
|
13
|
+
available: true
|
14
|
+
x-match: any
|
15
|
+
|
16
|
+
available_female:
|
17
|
+
gender: Female
|
18
|
+
available: true
|
19
|
+
x-match: all
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Philotic::Connection do
|
4
|
+
let(:connection){ Philotic::Connection }
|
5
|
+
subject { connection }
|
6
|
+
|
7
|
+
describe "config" do
|
8
|
+
it "should return the Philotic::Config singleton" do
|
9
|
+
subject.config.should == Philotic::Config
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "exchange" do
|
14
|
+
#TODO make sure rabbit is running for CI to run this
|
15
|
+
xit "should return an instance of AMQP::Exchange" do
|
16
|
+
subject.exchange.should be_a AMQP::Exchange
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/spec/event_spec.rb
ADDED
@@ -0,0 +1,44 @@
|
|
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 { subject.class.methods.should include method_name.to_sym }
|
15
|
+
end
|
16
|
+
|
17
|
+
Philotic::MESSAGE_OPTIONS.each do |method_name|
|
18
|
+
specify { subject.methods.should include method_name.to_sym }
|
19
|
+
specify { subject.methods.should include "#{method_name}=".to_sym }
|
20
|
+
end
|
21
|
+
|
22
|
+
Philotic::PHILOTIC_HEADERS.each do |method_name|
|
23
|
+
specify { subject.methods.should include method_name.to_sym }
|
24
|
+
specify { subject.methods.should include "#{method_name}=".to_sym }
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "message_metadata" do
|
28
|
+
it "should have a timestamp" do
|
29
|
+
Timecop.freeze
|
30
|
+
subject.message_metadata.should == {timestamp: Time.now.to_i}
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should reflect changes in the event properties" do
|
34
|
+
subject.message_metadata[:app_id]. should == nil
|
35
|
+
subject.app_id = 'ANSIBLE'
|
36
|
+
subject.message_metadata[:app_id]. should == 'ANSIBLE'
|
37
|
+
end
|
38
|
+
end
|
39
|
+
describe "headers" do
|
40
|
+
it "should include :philotic_product" do
|
41
|
+
subject.headers.keys.should include :philotic_product
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'philotic/dummy_event'
|
3
|
+
|
4
|
+
describe Philotic::Publisher do
|
5
|
+
before(:each) do
|
6
|
+
@event = Philotic::DummyEvent.new
|
7
|
+
@event.subject = "Hello"
|
8
|
+
@event.message = "How are you?"
|
9
|
+
@event.gender = :M
|
10
|
+
@event.available = true
|
11
|
+
end
|
12
|
+
let(:publisher) { Philotic::Publisher }
|
13
|
+
subject { publisher }
|
14
|
+
|
15
|
+
describe "config" do
|
16
|
+
it "should return the Philotic::Config singleton" do
|
17
|
+
subject.config.should == Philotic::Config
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe "exchange" do
|
22
|
+
#TODO make sure rabbit is running for CI to run this
|
23
|
+
xit "should return an instance of AMQP::Exchange" do
|
24
|
+
subject.exchange.should be_a AMQP::Exchange
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe "publish" do
|
29
|
+
it "should call raw_publish with the right values" do
|
30
|
+
Timecop.freeze
|
31
|
+
subject.should_receive(:raw_publish).with(
|
32
|
+
{
|
33
|
+
subject: 'Hello',
|
34
|
+
message: "How are you?"
|
35
|
+
},
|
36
|
+
{
|
37
|
+
headers: {
|
38
|
+
philotic_firehose: true,
|
39
|
+
philotic_product: nil,
|
40
|
+
philotic_component: nil,
|
41
|
+
philotic_event_type: nil,
|
42
|
+
gender: :M,
|
43
|
+
available: true
|
44
|
+
},
|
45
|
+
timestamp: Time.now.to_i
|
46
|
+
}
|
47
|
+
)
|
48
|
+
subject.publish(@event)
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
describe "raw_publish" do
|
54
|
+
|
55
|
+
xit "should call exchange.publish with the right values" do
|
56
|
+
Timecop.freeze
|
57
|
+
Philotic::Connection.instance.should_receive(:connected?).and_return { true }
|
58
|
+
|
59
|
+
AMQP::Exchange.any_instance.should_receive(:publish).with(
|
60
|
+
{
|
61
|
+
subject: 'Hello',
|
62
|
+
message: "How are you?"
|
63
|
+
}.to_json,
|
64
|
+
{
|
65
|
+
routing_key: nil,
|
66
|
+
persistent: true,
|
67
|
+
mandatory: true,
|
68
|
+
content_type: nil,
|
69
|
+
content_encoding: nil,
|
70
|
+
priority: nil,
|
71
|
+
message_id: nil,
|
72
|
+
correlation_id: nil,
|
73
|
+
reply_to: nil,
|
74
|
+
type: nil,
|
75
|
+
user_id: nil,
|
76
|
+
app_id: nil,
|
77
|
+
expiration: nil,
|
78
|
+
headers: {
|
79
|
+
philotic_firehose: true,
|
80
|
+
philotic_product: nil,
|
81
|
+
philotic_component: nil,
|
82
|
+
philotic_event_type: nil,
|
83
|
+
gender: :M,
|
84
|
+
available: true
|
85
|
+
},
|
86
|
+
timestamp: Time.now.to_i
|
87
|
+
}
|
88
|
+
)
|
89
|
+
subject.publish(@event)
|
90
|
+
end
|
91
|
+
|
92
|
+
xit "should log an error when there is no connection" do
|
93
|
+
|
94
|
+
4.times do
|
95
|
+
Philotic::Connection.instance.should_receive(:connected?).and_return { false }
|
96
|
+
end
|
97
|
+
Philotic.logger.should_receive(:error)
|
98
|
+
subject.publish(@event)
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Philotic::Routable do
|
4
|
+
context "including the module on class" do
|
5
|
+
let(:routable_event_class){
|
6
|
+
Class.new do
|
7
|
+
include Philotic::Routable
|
8
|
+
attr_routable :routable_attr
|
9
|
+
attr_payload :payload_attr
|
10
|
+
end
|
11
|
+
}
|
12
|
+
subject { routable_event_class}
|
13
|
+
|
14
|
+
%w{ attr_payload_reader attr_payload_readers
|
15
|
+
attr_payload_writer attr_payload_writers
|
16
|
+
attr_payload
|
17
|
+
|
18
|
+
attr_routable_reader attr_routable_readers
|
19
|
+
attr_routable_writers attr_routable_writer
|
20
|
+
attr_routable }.each do |method_name|
|
21
|
+
specify { subject.methods.should include method_name.to_sym }
|
22
|
+
end
|
23
|
+
|
24
|
+
context " and then instantiating it" do
|
25
|
+
let(:routable_event_instance){ routable_event_class.new }
|
26
|
+
subject { routable_event_instance }
|
27
|
+
|
28
|
+
it 'should have proper headers' do
|
29
|
+
subject.headers.should == { routable_attr: nil }
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'should have proper payload' do
|
33
|
+
subject.payload.should == { payload_attr: nil }
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'should have proper attributes' do
|
37
|
+
subject.attributes.should == { routable_attr: nil,
|
38
|
+
payload_attr: nil }
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'should call Philotic::Publisher.publish with subject' do
|
42
|
+
Philotic::Publisher.should_receive(:publish).with(subject)
|
43
|
+
subject.publish
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'should have empty message_metadata' do
|
47
|
+
subject.message_metadata.should == {}
|
48
|
+
end
|
49
|
+
|
50
|
+
context " overriding a value with message_metadata=" do
|
51
|
+
before do
|
52
|
+
routable_event_instance.message_metadata = { mandatory: false }
|
53
|
+
end
|
54
|
+
its(:message_metadata) { should eq( mandatory: false ) }
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
2
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
3
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), 'support'))
|
4
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), 'config'))
|
5
|
+
|
6
|
+
|
7
|
+
require 'rubygems'
|
8
|
+
require 'bundler/setup'
|
9
|
+
require 'philotic'
|
10
|
+
|
11
|
+
Bundler.require(:default, :test)
|
12
|
+
|
13
|
+
RSpec.configure do |config|
|
14
|
+
#Run any specs tagged with focus: true or all specs if none tagged
|
15
|
+
config.filter_run focus: true
|
16
|
+
config.run_all_when_everything_filtered = true
|
17
|
+
|
18
|
+
config.after do
|
19
|
+
Timecop.return
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
#Philotic.logger = Logger.new("/dev/null")
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'philotic/dummy_event'
|
3
|
+
|
4
|
+
describe Philotic::Subscriber do
|
5
|
+
let(:subscriber) do
|
6
|
+
subscriber = Philotic::Subscriber.new(arguments: {'x-match' => 'any', philotic_firehose: true}) do |metadata, payload|
|
7
|
+
true
|
8
|
+
end
|
9
|
+
subscriber
|
10
|
+
end
|
11
|
+
|
12
|
+
describe "subscribe" do
|
13
|
+
xit "should call AMQP.channel.queue with the right values" do
|
14
|
+
queue = double(AMQP::Queue)
|
15
|
+
queue.stub(:bind) { queue }
|
16
|
+
queue.stub(:subscribe) { queue }
|
17
|
+
channel = double(AMQP::Channel)
|
18
|
+
exchange = double(AMQP::Exchange)
|
19
|
+
channel.stub(:queue) { queue }
|
20
|
+
channel.stub(:headers) { exchange }
|
21
|
+
AMQP.stub(:channel) { channel }
|
22
|
+
|
23
|
+
channel.should_receive(:queue).with("", {auto_delete: true, durable: false})
|
24
|
+
queue.should_receive(:bind).with(exchange, {arguments: {"x-match" => "any", philotic_firehose: true}})
|
25
|
+
queue.should_receive(:subscribe).with({})
|
26
|
+
Philotic::Subscriber.subscribe(arguments: {'x-match' => 'any', philotic_firehose: true}) do |metadata, payload|
|
27
|
+
true
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe "subscribe_to_any_of" do
|
33
|
+
xit "should call AMQP.channel.queue with the right values" do
|
34
|
+
queue = double(AMQP::Queue)
|
35
|
+
queue.stub(:bind) { queue }
|
36
|
+
queue.stub(:subscribe) { queue }
|
37
|
+
channel = double(AMQP::Channel)
|
38
|
+
exchange = double(AMQP::Exchange)
|
39
|
+
channel.stub(:queue) { queue }
|
40
|
+
channel.stub(:headers) { exchange }
|
41
|
+
AMQP.stub(:channel) { channel }
|
42
|
+
|
43
|
+
channel.should_receive(:queue).with("", {auto_delete: true, durable: false})
|
44
|
+
queue.should_receive(:bind).with(exchange, {arguments: {"x-match" => "any", philotic_firehose: true}})
|
45
|
+
queue.should_receive(:subscribe).with({})
|
46
|
+
Philotic::Subscriber.subscribe_to_any_of(arguments: {philotic_firehose: true}) do |metadata, payload|
|
47
|
+
true
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe "subscribe_to_all_of" do
|
53
|
+
xit "should call AMQP.channel.queue with the right values" do
|
54
|
+
queue = double(AMQP::Queue)
|
55
|
+
queue.stub(:bind) { queue }
|
56
|
+
queue.stub(:subscribe) { queue }
|
57
|
+
channel = double(AMQP::Channel)
|
58
|
+
exchange = double(AMQP::Exchange)
|
59
|
+
channel.stub(:queue) { queue }
|
60
|
+
channel.stub(:headers) { exchange }
|
61
|
+
AMQP.stub(:channel) { channel }
|
62
|
+
|
63
|
+
channel.should_receive(:queue).with("", {auto_delete: true, durable: false})
|
64
|
+
queue.should_receive(:bind).with(exchange, {arguments: {"x-match" => "all", philotic_firehose: true}})
|
65
|
+
queue.should_receive(:subscribe).with({})
|
66
|
+
Philotic::Subscriber.subscribe_to_all_of(arguments: {philotic_firehose: true}) do |metadata, payload|
|
67
|
+
true
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|