philotic 0.8.1 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +7 -7
  2. data/.travis.yml +5 -6
  3. data/README.md +10 -13
  4. data/examples/creating_named_queues/manually.rb +4 -4
  5. data/examples/creating_named_queues/with_rake.rb +4 -1
  6. data/examples/publishing/publish.rb +14 -14
  7. data/examples/simple_instance.rb +4 -4
  8. data/examples/simple_singleton.rb +3 -3
  9. data/examples/subscribing/acks.rb +6 -6
  10. data/examples/subscribing/anonymous_queue.rb +2 -2
  11. data/examples/subscribing/consumer.rb +47 -0
  12. data/examples/subscribing/multiple_named_queues.rb +4 -4
  13. data/examples/subscribing/named_queue.rb +3 -3
  14. data/lib/philotic.rb +2 -3
  15. data/lib/philotic/config.rb +1 -1
  16. data/lib/philotic/connection.rb +1 -0
  17. data/lib/philotic/constants.rb +1 -1
  18. data/lib/philotic/consumer.rb +93 -0
  19. data/lib/philotic/{dummy_event.rb → dummy_message.rb} +3 -3
  20. data/lib/philotic/logging.rb +1 -1
  21. data/lib/philotic/logging/logger.rb +6 -6
  22. data/lib/philotic/logging/{event.rb → message.rb} +2 -2
  23. data/lib/philotic/message.rb +146 -0
  24. data/lib/philotic/publisher.rb +24 -24
  25. data/lib/philotic/subscriber.rb +9 -12
  26. data/lib/philotic/version.rb +1 -1
  27. data/philotic.gemspec +8 -11
  28. data/philotic_queues.yml.example +8 -8
  29. data/spec/philotic/connection_spec.rb +2 -0
  30. data/spec/philotic/consumer_spec.rb +186 -0
  31. data/spec/philotic/logging/logger_spec.rb +15 -15
  32. data/spec/philotic/message_spec.rb +147 -0
  33. data/spec/philotic/publisher_spec.rb +39 -38
  34. data/spec/philotic/subscriber_spec.rb +6 -3
  35. metadata +173 -144
  36. data/lib/philotic/event.rb +0 -100
  37. data/lib/philotic/routable.rb +0 -98
  38. data/spec/philotic/event_spec.rb +0 -109
  39. data/spec/philotic/routable_spec.rb +0 -54
@@ -1,10 +1,10 @@
1
- require 'philotic/event'
1
+ require 'philotic/message'
2
2
 
3
3
  module Philotic
4
- class DummyEvent < Philotic::Event
4
+ class DummyMessage < Philotic::Message
5
5
  attr_payload :subject
6
6
  attr_payload :message
7
- attr_routable :gender
7
+ attr_routable :hue
8
8
  attr_routable :available
9
9
  end
10
10
  end
@@ -1,4 +1,4 @@
1
- require 'philotic/logging/event'
1
+ require 'philotic/logging/message'
2
2
  require 'philotic/logging/logger'
3
3
 
4
4
  module Philotic
@@ -1,15 +1,15 @@
1
1
  require 'logger'
2
- require 'philotic/logging/event'
2
+ require 'philotic/logging/message'
3
3
 
4
4
  module Philotic
5
5
  module Logging
6
6
  class Logger < ::Logger
7
7
 
8
- attr_writer :event_class
8
+ attr_writer :message_class
9
9
  attr_accessor :connection
10
10
 
11
- def event_class
12
- @event_class ||= Philotic::Logging::Event
11
+ def message_class
12
+ @message_class ||= Philotic::Logging::Message
13
13
  end
14
14
 
15
15
  def add(severity, message = nil, progname = nil)
@@ -28,8 +28,8 @@ module Philotic
28
28
  end
29
29
  @logdev.write(format_message(format_severity(severity), Time.now, progname, message))
30
30
  begin
31
- event = event_class.new(severity, message, progname)
32
- connection.publish event if connection
31
+ message = message_class.new(severity, message, progname)
32
+ connection.publish message if connection
33
33
  rescue => e
34
34
  @logdev.write(format_message(format_severity(Logger::ERROR), Time.now, progname, e.message))
35
35
  end
@@ -1,8 +1,8 @@
1
- require 'philotic/event'
1
+ require 'philotic/message'
2
2
 
3
3
  module Philotic
4
4
  module Logging
5
- class Event < Philotic::Event
5
+ class Message < Philotic::Message
6
6
  attr_routable :severity, :progname
7
7
  attr_payload :message
8
8
 
@@ -0,0 +1,146 @@
1
+ require 'philotic/constants'
2
+ require 'philotic/singleton'
3
+
4
+ module Philotic
5
+ class Message
6
+
7
+ attr_accessor :connection, :publish_error, :delivery_info
8
+ attr_writer :published
9
+
10
+ class << self
11
+ def attr_routable_accessors
12
+ @attr_routable_accessors ||= Set.new
13
+ end
14
+
15
+ def attr_payload_accessors
16
+ @attr_payload_accessors ||= Set.new
17
+ end
18
+
19
+ def attr_routable(*names)
20
+ attr_routable_accessors.merge(names)
21
+ attr_accessor(*names)
22
+ end
23
+
24
+ def attr_payload(*names)
25
+ attr_payload_accessors.merge(names)
26
+ attr_accessor(*names)
27
+ end
28
+ end
29
+
30
+ def initialize(routables={}, payloads={}, connection=nil)
31
+ self.timestamp = Time.now.to_i
32
+ self.philotic_firehose = true
33
+ self.connection = connection
34
+
35
+ # dynamically insert any passed in routables into both attr_routable
36
+ # and attr_payload
37
+ # result: ability to arbitrarily send a easily routable hash
38
+ # over into the bus
39
+ _set_routables_or_payloads(:routable, routables)
40
+ _set_routables_or_payloads(:payload, payloads)
41
+
42
+ @published = false
43
+ end
44
+
45
+ def self.inherited(sub_class)
46
+ sub_class.attr_routable(*Philotic::PHILOTIC_HEADERS)
47
+ sub_class.attr_routable(*self.attr_routable_accessors.dup)
48
+ sub_class.attr_payload(*self.attr_payload_accessors.dup)
49
+ end
50
+
51
+ self.inherited(self)
52
+
53
+ Philotic::MESSAGE_OPTIONS.each do |message_option|
54
+ attr_reader message_option
55
+ define_method :"#{message_option}=" do |val|
56
+ instance_variable_set(:"@#{message_option}", val)
57
+ self.metadata[message_option] = val
58
+ end
59
+ end
60
+
61
+ def connection
62
+ @connection ||= Philotic.connection
63
+ end
64
+
65
+ def published?
66
+ !!@published
67
+ end
68
+
69
+ def publish
70
+ connection.publish self
71
+ end
72
+
73
+ def self.publish(*args)
74
+ self.new(*args).publish
75
+ end
76
+
77
+ def delivery_tag
78
+ delivery_info.try(:delivery_tag)
79
+ end
80
+
81
+ def payload
82
+ _payload_or_headers(:payload)
83
+ end
84
+
85
+ def headers
86
+ _payload_or_headers(:routable)
87
+ end
88
+
89
+ def attributes
90
+ payload.merge headers
91
+ end
92
+
93
+ def metadata
94
+ @metadata ||= {}
95
+ end
96
+
97
+ def metadata=(options)
98
+ @metadata ||= {}
99
+ @metadata.merge! options
100
+ end
101
+
102
+ private
103
+
104
+ def _payload_or_headers(payload_or_headers)
105
+ attribute_hash = {}
106
+ self.class.send("attr_#{payload_or_headers}_accessors").each do |attr|
107
+ attr = attr.to_sym
108
+ attribute_hash[attr] = send(attr)
109
+ end
110
+ attribute_hash
111
+ end
112
+
113
+ def _set_routables_or_payloads(type, attrs)
114
+ attrs.each do |key, value|
115
+ if self.respond_to?(:"#{key}=")
116
+ send(:"#{key}=", value)
117
+ elsif self.class == Philotic::Message
118
+ _set_message_attribute(type, key, value)
119
+ end
120
+ end
121
+ end
122
+
123
+ def _set_message_attribute(type, key, value)
124
+ self.class.send("attr_#{type}_accessors").merge([key])
125
+ _set_message_attribute_accessor(key, value)
126
+ end
127
+
128
+ def _set_message_attribute_accessor(attr, value)
129
+ _set_message_attribute_getter(attr)
130
+ _set_message_attribute_setter(attr)
131
+ self.send(:"#{attr}=", value)
132
+ end
133
+
134
+ def _set_message_attribute_getter(attr)
135
+ self.define_singleton_method :"#{attr}" do
136
+ instance_variable_get(:"@#{attr}")
137
+ end
138
+ end
139
+
140
+ def _set_message_attribute_setter(attr)
141
+ self.define_singleton_method :"#{attr}=" do |v|
142
+ instance_variable_set(:"@#{attr}", v)
143
+ end
144
+ end
145
+ end
146
+ end
@@ -4,7 +4,7 @@ module Philotic
4
4
  class Publisher
5
5
 
6
6
  attr_accessor :connection
7
- attr_accessor :log_event_handler
7
+ attr_accessor :log_message_handler
8
8
 
9
9
  def initialize(connection)
10
10
  @connection = connection
@@ -18,16 +18,16 @@ module Philotic
18
18
  connection.config
19
19
  end
20
20
 
21
- def publish(event)
22
- message_metadata = {headers: event.headers}
23
- message_metadata.merge!(event.message_metadata) if event.message_metadata
21
+ def publish(message)
22
+ metadata = {headers: message.headers}
23
+ metadata.merge!(message.metadata) if message.metadata
24
24
  begin
25
- event.published = _publish(event.payload, message_metadata)
25
+ message.published = _publish(message.payload, metadata)
26
26
  rescue => e
27
- event.publish_error = e
27
+ message.publish_error = e
28
28
  logger.error e.message
29
29
  end
30
- event
30
+ message
31
31
  end
32
32
 
33
33
  def normalize_payload_times(payload)
@@ -41,45 +41,45 @@ module Philotic
41
41
  end
42
42
 
43
43
  private
44
- def _publish(payload, message_metadata = {})
44
+ def _publish(payload, metadata = {})
45
45
  if config.disable_publish
46
- log_event_published(:warn, message_metadata, payload, 'attempted to publish a message when publishing is disabled.')
46
+ log_message_published(:warn, metadata, payload, 'attempted to publish a message when publishing is disabled.')
47
47
  return false
48
48
  end
49
49
  connection.connect!
50
50
  unless connection.connected?
51
- log_event_published(:error, message_metadata, payload, 'unable to publish event, not connected to RabbitMQ')
51
+ log_message_published(:error, metadata, payload, 'unable to publish message, not connected to RabbitMQ')
52
52
  return false
53
53
  end
54
- message_metadata = merge_metadata(message_metadata)
54
+ metadata = merge_metadata(metadata)
55
55
 
56
56
  payload = normalize_payload_times(payload)
57
57
 
58
- connection.exchange.publish(payload.to_json, message_metadata)
59
- log_event_published(:debug, message_metadata, payload, 'published event')
58
+ connection.exchange.publish(payload.to_json, metadata)
59
+ log_message_published(:debug, metadata, payload, 'published message')
60
60
  true
61
61
  end
62
62
 
63
- def merge_metadata(message_metadata)
63
+ def merge_metadata(metadata)
64
64
  publish_defaults = {}
65
65
  Philotic::MESSAGE_OPTIONS.each do |key|
66
66
  publish_defaults[key] = config.send(key.to_s)
67
67
  end
68
- message_metadata = publish_defaults.merge message_metadata
69
- message_metadata[:headers] ||= {}
70
- message_metadata[:headers] = {philotic_firehose: true}.merge(message_metadata[:headers])
71
- message_metadata
68
+ metadata = publish_defaults.merge metadata
69
+ metadata[:headers] ||= {}
70
+ metadata[:headers] = {philotic_firehose: true}.merge(metadata[:headers])
71
+ metadata
72
72
  end
73
73
 
74
- def on_publish_event(&block)
75
- @log_event_handler = block
74
+ def on_publish_message(&block)
75
+ @log_message_handler = block
76
76
  end
77
77
 
78
- def log_event_published(severity, metadata, payload, message)
79
- if @log_event_handler
80
- @log_event_handler.call(severity, metadata, payload, message)
78
+ def log_message_published(severity, metadata, payload, message)
79
+ if @log_message_handler
80
+ @log_message_handler.call(severity, metadata, payload, message)
81
81
  else
82
- logger.send(severity, "#{message}; message_metadata:#{metadata}, payload:#{payload.to_json}")
82
+ logger.send(severity, "#{message}; metadata:#{metadata}, payload:#{payload.to_json}")
83
83
  end
84
84
  end
85
85
  end
@@ -1,4 +1,5 @@
1
1
  require 'philotic/constants'
2
+ require 'philotic/message'
2
3
 
3
4
  module Philotic
4
5
  class Subscriber
@@ -17,18 +18,14 @@ module Philotic
17
18
  connection.config
18
19
  end
19
20
 
20
- def subscription_callback(queue, &block)
21
+ def subscription_callback(&block)
21
22
  lambda do |delivery_info, metadata, payload|
22
23
  hash_payload = JSON.parse payload
23
24
 
24
- event = {
25
- payload: hash_payload,
26
- headers: metadata[:headers],
27
- delivery_info: delivery_info,
28
- attributes: metadata[:headers] ? hash_payload.merge(metadata[:headers]) : hash_payload
29
- }
25
+ message = Philotic::Message.new(metadata[:headers], hash_payload)
26
+ message.delivery_info = delivery_info
30
27
 
31
- instance_exec(event, metadata, queue, &block)
28
+ instance_exec(message, &block)
32
29
  end
33
30
  end
34
31
 
@@ -40,7 +37,7 @@ module Philotic
40
37
 
41
38
  queue = initialize_queue(subscription_settings)
42
39
 
43
- queue.subscribe(subscription_settings[:subscribe_options], &subscription_callback(queue, &block))
40
+ queue.subscribe(subscription_settings[:subscribe_options], &subscription_callback(&block))
44
41
 
45
42
  end
46
43
 
@@ -53,7 +50,7 @@ module Philotic
53
50
 
54
51
  def get_subscription_settings(subscription, subscribe_options)
55
52
 
56
- if subscription.is_a? String
53
+ if [Symbol, String].include? subscription.class
57
54
  queue_name = subscription
58
55
  subscription = subscribe_options
59
56
  queue_options = Philotic::DEFAULT_NAMED_QUEUE_OPTIONS
@@ -79,11 +76,11 @@ module Philotic
79
76
  end
80
77
 
81
78
  def acknowledge(message, up_to_and_including=false)
82
- connection.channel.acknowledge(message[:delivery_info].delivery_tag, up_to_and_including)
79
+ connection.channel.acknowledge(message.delivery_tag, up_to_and_including)
83
80
  end
84
81
 
85
82
  def reject(message, requeue=true)
86
- connection.channel.reject(message[:delivery_info].delivery_tag, requeue)
83
+ connection.channel.reject(message.delivery_tag, requeue)
87
84
  end
88
85
 
89
86
  def subscribe_to_any(options = {})
@@ -1,3 +1,3 @@
1
1
  module Philotic
2
- VERSION = '0.8.1'
2
+ VERSION = '1.0.1'
3
3
  end
data/philotic.gemspec CHANGED
@@ -16,20 +16,17 @@ Gem::Specification.new do |gem|
16
16
  gem.version = Philotic::VERSION
17
17
  gem.licenses = ['MIT']
18
18
 
19
-
20
19
  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'
20
+ gem.add_development_dependency 'bundler', '>= 1.6'
21
+ gem.add_development_dependency 'pry', '>= 0.10'
22
+ gem.add_development_dependency 'rake', '>= 10.3'
23
+ gem.add_development_dependency 'rspec', '>= 3.1'
24
+ gem.add_development_dependency 'rspec-its', '>= 1.1'
25
+ gem.add_development_dependency 'timecop', '>= 0.7'
28
26
  gem.add_development_dependency 'simplecov'
29
27
 
30
28
  gem.add_dependency 'activesupport', '>= 3.2'
31
- gem.add_dependency 'activerecord', '>= 3.2'
32
- gem.add_dependency 'awesome_print', '~> 1.2'
29
+ gem.add_dependency 'awesome_print', '>= 1.2'
33
30
  gem.add_dependency 'bunny', '>= 1.6'
34
- gem.add_dependency 'json', '~> 1.8'
31
+ gem.add_dependency 'json', '>= 1.8'
35
32
  end
@@ -1,19 +1,19 @@
1
- available_or_male:
2
- gender: Male
1
+ available_or_mauve:
2
+ hue: Mauve
3
3
  available: true
4
4
  x-match: any
5
5
 
6
- available_male:
7
- gender: Male
6
+ available_mauve:
7
+ hue: Mauve
8
8
  available: true
9
9
  x-match: all
10
10
 
11
- available_or_female:
12
- gender: Female
11
+ available_or_fuchsia:
12
+ hue: Fuchsia
13
13
  available: true
14
14
  x-match: any
15
15
 
16
- available_female:
17
- gender: Female
16
+ available_fuchsia:
17
+ hue: Fuchsia
18
18
  available: true
19
19
  x-match: all
@@ -1,5 +1,7 @@
1
1
  require 'spec_helper'
2
2
 
3
+ require 'philotic/connection'
4
+
3
5
  describe Philotic::Connection do
4
6
 
5
7
 
@@ -0,0 +1,186 @@
1
+ require 'spec_helper'
2
+
3
+ require 'philotic/consumer'
4
+ require 'philotic/connection'
5
+ require 'philotic/subscriber'
6
+
7
+
8
+ describe Philotic::Consumer do
9
+ let(:named_queue) { :named_queue }
10
+ let(:anonymous_subscription) { {
11
+ header_1: :value_1,
12
+ header_2: :value_2,
13
+ header_3: :value_3,
14
+ } }
15
+ subject { Class.new Philotic::Consumer }
16
+
17
+ describe '.subscribe_to' do
18
+ it 'sets the class variable @subscription' do
19
+
20
+ expect(subject.subscription).not_to be
21
+
22
+ subject.subscribe_to named_queue
23
+ expect(subject.subscription).to eq named_queue
24
+
25
+ subject.subscribe_to anonymous_subscription
26
+ expect(subject.subscription).to eq anonymous_subscription
27
+ end
28
+ end
29
+
30
+ describe '.ack_messages' do
31
+ it 'sets the class variable @ack_messages' do
32
+ expect(subject.ack_messages?).not_to be true
33
+ subject.ack_messages
34
+ expect(subject.ack_messages?).to be true
35
+ end
36
+ end
37
+
38
+ describe '.exclusive' do
39
+ it 'sets the class variable @exclusive' do
40
+ expect(subject).not_to be_exclusive
41
+ subject.exclusive
42
+ expect(subject).to be_exclusive
43
+ end
44
+ end
45
+
46
+ describe '.requeueable_errors' do
47
+ it 'maintains a set of requeueable errors' do
48
+ expect(subject.requeueable_errors).to be_empty
49
+ expect(subject.requeueable_errors(RuntimeError).size).to be 1
50
+ expect(subject.requeueable_errors).to include(RuntimeError)
51
+
52
+ expect(subject.requeueable_errors(RuntimeError, NotImplementedError).size).to be 2
53
+ expect(subject.requeueable_errors).to include(NotImplementedError)
54
+
55
+
56
+ # don't allow dupes
57
+ expect(subject.requeueable_errors(RuntimeError, NotImplementedError).size).to be 2
58
+
59
+ end
60
+ end
61
+
62
+ describe '.rejectable_errors' do
63
+ it 'maintains a set of rejectable errors' do
64
+ expect(subject.rejectable_errors).to be_empty
65
+ expect(subject.rejectable_errors(RuntimeError).size).to be 1
66
+ expect(subject.rejectable_errors).to include(RuntimeError)
67
+
68
+ expect(subject.rejectable_errors(RuntimeError, NotImplementedError).size).to be 2
69
+ expect(subject.rejectable_errors).to include(NotImplementedError)
70
+
71
+
72
+ # don't allow dupes
73
+ expect(subject.rejectable_errors(RuntimeError, NotImplementedError).size).to be 2
74
+
75
+ end
76
+ end
77
+
78
+ describe '.subscribe' do
79
+ let (:connection) { instance_double Philotic::Connection }
80
+ let (:consumer_instance) { instance_double subject }
81
+ it 'proxies to, and returns, a new instance' do
82
+ expect(Philotic).to receive(:connection).and_return(connection)
83
+ expect(subject).to receive(:new).and_return(consumer_instance)
84
+ expect(consumer_instance).to receive(:subscribe)
85
+
86
+ expect(subject.subscribe).to be consumer_instance
87
+ end
88
+ end
89
+
90
+ describe '#subscription_options' do
91
+ it 'returns a hash with the exclusive and manual_ack options' do
92
+ expect(subject.subscription_options).to match({manual_ack: false, exclusive: false})
93
+
94
+ subject.ack_messages
95
+ expect(subject.subscription_options).to match({manual_ack: true, exclusive: false})
96
+
97
+ subject.exclusive
98
+ expect(subject.subscription_options).to match({manual_ack: true, exclusive: true})
99
+ end
100
+ end
101
+
102
+ describe '#subscribe' do
103
+ it 'proxies to Philotic::Subscriber#subscribe' do
104
+ subject.subscribe_to named_queue
105
+ subject.ack_messages
106
+ subject.exclusive
107
+
108
+ expect_any_instance_of(Philotic::Subscriber).to receive(:subscribe).with(named_queue, manual_ack: true, exclusive: true)
109
+
110
+ subject.subscribe
111
+ end
112
+ end
113
+
114
+ describe '#consume' do
115
+ subject { (Class.new(Philotic::Consumer)).new(nil) }
116
+
117
+ it 'raises an error unless the inheriting class redefines it' do
118
+ expect { subject.consume(nil) }.to raise_error(NotImplementedError)
119
+
120
+ subject.define_singleton_method :consume do |message|
121
+ # no op
122
+ end
123
+
124
+ expect { subject.consume(nil) }.to_not raise_error
125
+ end
126
+ end
127
+
128
+ describe '#_consume' do
129
+ subject { (Class.new(Philotic::Consumer)).new(nil) }
130
+ let(:message) { instance_double Philotic::Message }
131
+
132
+ it 'proxies to #consume' do
133
+ expect(subject).to receive(:consume).with(message)
134
+
135
+ subject.send(:_consume, message)
136
+ end
137
+
138
+ it 'acknowledges messages when @ack_messages is set' do
139
+ subject.class.ack_messages
140
+
141
+ subject.define_singleton_method :consume do |message|
142
+ # no op
143
+ end
144
+
145
+ expect(subject).to receive(:acknowledge).with(message)
146
+
147
+ subject.send(:_consume, message)
148
+ end
149
+
150
+ it 'requeues messages when @ack_messages is set and a requeueable error is thrown' do
151
+ subject.class.ack_messages
152
+ subject.class.requeueable_errors(RuntimeError)
153
+
154
+ subject.define_singleton_method :consume do |message|
155
+ raise RuntimeError.new 'oops'
156
+ end
157
+
158
+ expect(subject).to receive(:reject).with(message, true)
159
+
160
+ subject.send(:_consume, message)
161
+ end
162
+
163
+ it 'rejects messages when @ack_messages is set and a rejectable error is thrown' do
164
+ subject.class.ack_messages
165
+ subject.class.rejectable_errors(RuntimeError)
166
+
167
+ subject.define_singleton_method :consume do |message|
168
+ raise RuntimeError.new 'oops'
169
+ end
170
+
171
+ expect(subject).to receive(:reject).with(message, false)
172
+
173
+ subject.send(:_consume, message)
174
+ end
175
+
176
+ it 'raises all non-requeueable and non-rejectable errors' do
177
+ subject.class.ack_messages
178
+
179
+ subject.define_singleton_method :consume do |message|
180
+ raise RuntimeError.new 'oops'
181
+ end
182
+
183
+ expect {subject.send(:_consume, message)}.to raise_error RuntimeError
184
+ end
185
+ end
186
+ end