philotic 0.0.1
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 +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
|