philotic 0.0.1 → 0.1.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.
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