philotic 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +6 -1
  3. data/Gemfile +0 -7
  4. data/README.md +2 -0
  5. data/Rakefile +1 -1
  6. data/examples/README.md +1 -1
  7. data/examples/creating_named_queues/manually.rb +8 -21
  8. data/examples/creating_named_queues/with_rake.rb +2 -2
  9. data/examples/publishing/publish.rb +26 -33
  10. data/examples/subscribing/acks.rb +25 -0
  11. data/examples/subscribing/anonymous_queue.rb +6 -10
  12. data/examples/subscribing/multiple_named_queues.rb +9 -24
  13. data/examples/subscribing/named_queue.rb +7 -17
  14. data/lib/philotic.rb +37 -81
  15. data/lib/philotic/config.rb +63 -24
  16. data/lib/philotic/connection.rb +35 -51
  17. data/lib/philotic/constants.rb +49 -0
  18. data/lib/philotic/event.rb +27 -14
  19. data/lib/philotic/logging.rb +8 -0
  20. data/lib/philotic/logging/event.rb +18 -0
  21. data/lib/philotic/logging/logger.rb +39 -0
  22. data/lib/philotic/publisher.rb +28 -31
  23. data/lib/philotic/routable.rb +40 -40
  24. data/lib/philotic/subscriber.rb +61 -42
  25. data/lib/philotic/tasks/init_queues.rb +4 -14
  26. data/lib/philotic/version.rb +1 -1
  27. data/philotic.gemspec +21 -10
  28. data/spec/philotic/config_spec.rb +40 -0
  29. data/spec/philotic/connection_spec.rb +58 -0
  30. data/spec/philotic/event_spec.rb +69 -0
  31. data/spec/philotic/logging/logger_spec.rb +26 -0
  32. data/spec/philotic/publisher_spec.rb +99 -0
  33. data/spec/{routable_spec.rb → philotic/routable_spec.rb} +15 -14
  34. data/spec/philotic/subscriber_spec.rb +111 -0
  35. data/spec/philotic_spec.rb +66 -0
  36. data/spec/spec_helper.rb +12 -4
  37. data/tasks/bump.rake +10 -10
  38. metadata +173 -36
  39. data/spec/connection_spec.rb +0 -19
  40. data/spec/event_spec.rb +0 -44
  41. data/spec/publisher_spec.rb +0 -102
  42. data/spec/subscriber_spec.rb +0 -72
@@ -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
@@ -1,67 +1,86 @@
1
1
  module Philotic
2
2
  class Subscriber
3
- def self.subscribe(options = {}, subscribe_options = Philotic::DEFAULT_SUBSCRIBE_OPTIONS, &block)
4
- if Philotic.connected?
5
- _subscribe(options, subscribe_options, &block)
6
- else
7
- Philotic.connect! do
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.subscribe_to_any_of(options = {}, &block)
14
- arguments = options[:arguments] || {}
15
- queue_options = options[:queue_options] || {}
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
- self.subscribe(options, &block)
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.subscribe_to_all_of(options = {}, &block)
23
- arguments = options[:arguments] || {}
24
- queue_options = options[:queue_options] || {}
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['x-match'] = 'all'
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
- private
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 options.is_a? String
36
- queue_name = options
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
- queue_options = Philotic::DEFAULT_ANONYMOUS_QUEUE_OPTIONS.merge(options[:queue_options] || {})
42
- subscribe_options = subscribe_options.merge(options[:subscribe_options]) if options[:subscribe_options]
43
- arguments = options[:arguments] || options
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[:auto_delete] ||= true if queue_name == ""
54
+ queue_options.merge!(subscription[:queue_options] || {})
48
55
 
49
- callback = Proc.new do |metadata, payload|
50
- hash_payload = JSON.parse payload
56
+ queue_options[:auto_delete] ||= true if queue_name == ''
51
57
 
52
- event = {
53
- payload: hash_payload,
54
- headers: metadata.attributes[:headers],
55
- attributes: metadata.attributes[:headers] ? hash_payload.merge(metadata.attributes[:headers]) : hash_payload
56
- }
57
- block.call(metadata, event)
58
- end
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
- q.bind(@@exchange, arguments: arguments) if arguments
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
- q.subscribe(subscribe_options, &callback)
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 = 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
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
@@ -1,3 +1,3 @@
1
1
  module Philotic
2
- VERSION = '0.0.1'
2
+ VERSION = '0.1.0'
3
3
  end
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 = ['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'
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 = ['MIT']
17
+ gem.licenses = ['MIT']
18
18
 
19
- gem.add_dependency 'activesupport', '~> 4.0'
20
- gem.add_dependency 'activerecord', '~> 4.0'
21
- gem.add_dependency 'amqp', '~> 1.2'
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