philotic 0.8.1 → 1.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.
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