pub_sub_model_sync 0.5.8.2 → 1.0.beta
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 +4 -4
- data/.github/workflows/ruby.yml +1 -1
- data/.rubocop.yml +2 -0
- data/CHANGELOG.md +44 -0
- data/Dockerfile +6 -0
- data/Gemfile.lock +144 -135
- data/README.md +370 -194
- data/docker-compose.yaml +12 -0
- data/docs/notifications-diagram.png +0 -0
- data/lib/pub_sub_model_sync.rb +2 -0
- data/lib/pub_sub_model_sync/base.rb +22 -5
- data/lib/pub_sub_model_sync/config.rb +15 -7
- data/lib/pub_sub_model_sync/message_processor.rb +15 -10
- data/lib/pub_sub_model_sync/message_publisher.rb +92 -20
- data/lib/pub_sub_model_sync/mock_google_service.rb +4 -0
- data/lib/pub_sub_model_sync/mock_kafka_service.rb +13 -0
- data/lib/pub_sub_model_sync/payload.rb +32 -16
- data/lib/pub_sub_model_sync/publisher.rb +43 -21
- data/lib/pub_sub_model_sync/publisher_concern.rb +54 -44
- data/lib/pub_sub_model_sync/run_subscriber.rb +104 -0
- data/lib/pub_sub_model_sync/service_base.rb +47 -13
- data/lib/pub_sub_model_sync/service_google.rb +53 -17
- data/lib/pub_sub_model_sync/service_kafka.rb +40 -13
- data/lib/pub_sub_model_sync/service_rabbit.rb +41 -33
- data/lib/pub_sub_model_sync/subscriber.rb +14 -61
- data/lib/pub_sub_model_sync/subscriber_concern.rb +21 -28
- data/lib/pub_sub_model_sync/tasks/worker.rake +11 -0
- data/lib/pub_sub_model_sync/transaction.rb +57 -0
- data/lib/pub_sub_model_sync/version.rb +1 -1
- metadata +9 -4
@@ -1,35 +1,57 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module PubSubModelSync
|
4
|
-
class Publisher
|
5
|
-
attr_accessor :
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
@
|
4
|
+
class Publisher < PubSubModelSync::Base
|
5
|
+
attr_accessor :model, :action, :data, :mapping, :headers, :as_klass
|
6
|
+
|
7
|
+
# @param model (ActiveRecord::Base)
|
8
|
+
# @param action (@see PublishConcern::ps_publish)
|
9
|
+
# @param settings (@see PublishConcern::ps_publish): { data:, mapping:, headers:, as_klass: }
|
10
|
+
def initialize(model, action, settings = {})
|
11
|
+
@model = model
|
12
|
+
@action = action
|
13
|
+
@data = settings[:data] || {}
|
14
|
+
@mapping = settings[:mapping] || []
|
15
|
+
@headers = settings[:headers] || {}
|
16
|
+
@as_klass = settings[:as_klass] || model.class.name
|
17
|
+
end
|
18
|
+
|
19
|
+
# @return (Payload)
|
20
|
+
def payload
|
21
|
+
values = compute_value(data)
|
22
|
+
values = mapping_data.merge(values)
|
23
|
+
PubSubModelSync::Payload.new(values, settings_data, headers_data)
|
12
24
|
end
|
13
25
|
|
14
|
-
def
|
15
|
-
|
26
|
+
def self.ordering_key_for(model)
|
27
|
+
[model.class.name, model.id || SecureRandom.uuid].join('/')
|
16
28
|
end
|
17
29
|
|
18
30
|
private
|
19
31
|
|
20
|
-
def
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
source, target = prop.to_s.split(':')
|
26
|
-
data[target] = data.delete(source)
|
27
|
-
end
|
28
|
-
data.symbolize_keys
|
32
|
+
def headers_data
|
33
|
+
klass_name = model.class.name
|
34
|
+
key = [klass_name, action, model.id || SecureRandom.uuid].join('/')
|
35
|
+
def_data = { ordering_key: self.class.ordering_key_for(model), key: key }
|
36
|
+
def_data.merge(compute_value(headers))
|
29
37
|
end
|
30
38
|
|
31
|
-
def
|
32
|
-
|
39
|
+
def compute_value(value)
|
40
|
+
res = value
|
41
|
+
res = model.send(value, action) if value.is_a?(Symbol) # method name
|
42
|
+
res = value.call(model, action) if value.is_a?(Proc)
|
43
|
+
res
|
44
|
+
end
|
45
|
+
|
46
|
+
def settings_data
|
47
|
+
{ klass: as_klass, action: action }
|
48
|
+
end
|
49
|
+
|
50
|
+
def mapping_data
|
51
|
+
mapping.map do |prop|
|
52
|
+
source, target = prop.to_s.split(':')
|
53
|
+
[target || source, model.send(source.to_sym)]
|
54
|
+
end.to_h.symbolize_keys
|
33
55
|
end
|
34
56
|
end
|
35
57
|
end
|
@@ -2,73 +2,83 @@
|
|
2
2
|
|
3
3
|
module PubSubModelSync
|
4
4
|
module PublisherConcern
|
5
|
-
|
6
|
-
base.extend(ClassMethods)
|
7
|
-
end
|
5
|
+
extend ActiveSupport::Concern
|
8
6
|
|
9
|
-
|
10
|
-
|
11
|
-
|
7
|
+
included do
|
8
|
+
extend ClassMethods
|
9
|
+
ps_init_transaction_callbacks if self <= ActiveRecord::Base
|
12
10
|
end
|
13
11
|
|
14
12
|
# before preparing data to sync
|
15
|
-
def
|
13
|
+
def ps_skip_publish?(_action)
|
16
14
|
false
|
17
15
|
end
|
16
|
+
alias ps_skip_sync? ps_skip_publish? # @deprecated
|
18
17
|
|
19
18
|
# before delivering data (return :cancel to cancel sync)
|
20
|
-
def
|
19
|
+
def ps_before_publish(_action, _payload); end
|
20
|
+
alias ps_before_sync ps_before_publish # @deprecated
|
21
21
|
|
22
22
|
# after delivering data
|
23
|
-
def
|
23
|
+
def ps_after_publish(_action, _payload); end
|
24
|
+
alias ps_after_sync ps_after_publish # @deprecated
|
24
25
|
|
25
|
-
#
|
26
|
-
# @param
|
27
|
-
# @param
|
28
|
-
#
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
26
|
+
# Delivers a notification via pubsub
|
27
|
+
# @param action (Sym|String) Sample: create|update|save|destroy|<any_other_key>
|
28
|
+
# @param mapping? (Array<String>) If present will generate data using the mapping and added to the payload.
|
29
|
+
# Sample: ["id", "full_name:name"]
|
30
|
+
# @param data? (Hash|Symbol|Proc)
|
31
|
+
# Hash: Data to be added to the payload
|
32
|
+
# Symbol: Method name to be called to retrieve payload data (must return a hash value, receives :action name)
|
33
|
+
# Proc: Block to be called to retrieve payload data
|
34
|
+
# @param headers? (Hash|Symbol|Proc): (All available attributes in Payload.headers)
|
35
|
+
# Hash: Data that will be merged with default header values
|
36
|
+
# Symbol: Method name that will be called to retrieve header values (must return a hash, receives :action name)
|
37
|
+
# Proc: Block to be called to retrieve header values
|
38
|
+
# @param as_klass? (String): Output class name used instead of current class name
|
39
|
+
def ps_publish(action, data: {}, mapping: [], headers: {}, as_klass: self.class.name)
|
40
|
+
p_klass = PubSubModelSync::MessagePublisher
|
41
|
+
p_klass.publish_model(self, action, data: data, mapping: mapping, headers: headers, as_klass: as_klass)
|
35
42
|
end
|
43
|
+
delegate :ps_class_publish, to: :class
|
36
44
|
|
37
45
|
module ClassMethods
|
38
|
-
#
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
ps_register_callback(action.to_sym, publisher)
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
# On demand class level publisher
|
49
|
-
def ps_class_publish(data, action:, as_klass: nil)
|
50
|
-
as_klass = (as_klass || name).to_s
|
46
|
+
# Publishes a class level notification via pubsub
|
47
|
+
# @param data (Hash): Data of the notification
|
48
|
+
# @param action (Symbol): action name of the notification
|
49
|
+
# @param as_klass (String, default current class name): Class name of the notification
|
50
|
+
# @param headers (Hash, optional): header settings (More in Payload.headers)
|
51
|
+
def ps_class_publish(data, action:, as_klass: nil, headers: {})
|
51
52
|
klass = PubSubModelSync::MessagePublisher
|
52
|
-
klass.publish_data(as_klass, data, action.to_sym)
|
53
|
+
klass.publish_data((as_klass || name).to_s, data, action.to_sym, headers: headers)
|
53
54
|
end
|
54
55
|
|
55
|
-
#
|
56
|
-
|
57
|
-
|
58
|
-
|
56
|
+
# @param crud_actions (Symbol|Array<Symbol>): :create, :update, :destroy
|
57
|
+
# @param method_name (Symbol, optional) method to be called
|
58
|
+
def ps_on_crud_event(crud_actions, method_name = nil, &block)
|
59
|
+
crud_actions = Array(crud_actions)
|
60
|
+
callback = ->(action) { method_name ? send(method_name, action) : instance_exec(action, &block) }
|
61
|
+
commit_name = respond_to?(:before_commit) ? :before_commit : :after_commit
|
62
|
+
crud_actions.each do |action|
|
63
|
+
send(commit_name, on: :create) { instance_exec(action, &callback) } if action == :create
|
64
|
+
send(commit_name, on: :update) { instance_exec(action, &callback) } if action == :update
|
65
|
+
after_destroy { instance_exec(action, &callback) } if action == :destroy
|
59
66
|
end
|
60
67
|
end
|
61
68
|
|
62
69
|
private
|
63
70
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
klass.publish_model(model, action.to_sym, publisher)
|
70
|
-
end
|
71
|
+
# Initialize calls to start and end pub_sub transactions and deliver all them in the same order
|
72
|
+
def ps_init_transaction_callbacks
|
73
|
+
start_transaction = lambda do
|
74
|
+
key = id ? PubSubModelSync::Publisher.ordering_key_for(self) : nil
|
75
|
+
@ps_transaction = PubSubModelSync::MessagePublisher.init_transaction(key)
|
71
76
|
end
|
77
|
+
after_create start_transaction, prepend: true # wait for ID
|
78
|
+
before_update start_transaction, prepend: true
|
79
|
+
before_destroy start_transaction, prepend: true
|
80
|
+
after_commit { @ps_transaction.deliver_all }
|
81
|
+
after_rollback(prepend: true) { @ps_transaction.rollback }
|
72
82
|
end
|
73
83
|
end
|
74
84
|
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PubSubModelSync
|
4
|
+
class RunSubscriber < Base
|
5
|
+
attr_accessor :subscriber, :payload, :model
|
6
|
+
|
7
|
+
delegate :settings, to: :subscriber
|
8
|
+
|
9
|
+
# @param subscriber(Subscriber)
|
10
|
+
# @param payload(Payload)
|
11
|
+
def initialize(subscriber, payload)
|
12
|
+
@subscriber = subscriber
|
13
|
+
@payload = payload
|
14
|
+
end
|
15
|
+
|
16
|
+
def call
|
17
|
+
klass_subscription? ? run_class_message : run_model_message
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def klass_subscription?
|
23
|
+
subscriber.settings[:mode] == :klass
|
24
|
+
end
|
25
|
+
|
26
|
+
def run_class_message
|
27
|
+
model_class = subscriber.klass.constantize
|
28
|
+
model_class.ps_processing_payload = payload # TODO: review for parallel notifications
|
29
|
+
call_action(model_class, payload.data) if ensure_sync(model_class)
|
30
|
+
end
|
31
|
+
|
32
|
+
# support for: create, update, destroy
|
33
|
+
def run_model_message
|
34
|
+
@model = find_model
|
35
|
+
model.ps_processing_payload = payload
|
36
|
+
return unless ensure_sync(model)
|
37
|
+
|
38
|
+
populate_model
|
39
|
+
model.send(:ps_before_save_sync) if model.respond_to?(:ps_before_save_sync)
|
40
|
+
call_action(model)
|
41
|
+
end
|
42
|
+
|
43
|
+
def ensure_sync(object)
|
44
|
+
res = true
|
45
|
+
res = false if settings[:if] && !parse_condition(settings[:if], object)
|
46
|
+
res = false if settings[:unless] && parse_condition(settings[:unless], object)
|
47
|
+
log("Cancelled save sync by subscriber condition : #{[payload]}") if !res && debug?
|
48
|
+
res
|
49
|
+
end
|
50
|
+
|
51
|
+
def call_action(object, *args)
|
52
|
+
action_name = settings[:to_action]
|
53
|
+
if action_name.is_a?(Proc)
|
54
|
+
args.prepend(object) unless klass_subscription?
|
55
|
+
action_name.call(*args)
|
56
|
+
else # method name
|
57
|
+
action_name = :save if %i[create update].include?(action_name.to_sym)
|
58
|
+
object.send(action_name, *args)
|
59
|
+
end
|
60
|
+
raise(object.errors) if object.respond_to?(:errors) && object.errors.any?
|
61
|
+
end
|
62
|
+
|
63
|
+
def parse_condition(condition, object)
|
64
|
+
proc_args = klass_subscription? ? [] : [object]
|
65
|
+
case condition
|
66
|
+
when Proc then condition.call(*proc_args)
|
67
|
+
when Array then condition.all? { |method_name| object.send(method_name) }
|
68
|
+
else # method name
|
69
|
+
object.send(condition)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def find_model
|
74
|
+
model_class = subscriber.klass.constantize
|
75
|
+
return model_class.ps_find_model(payload.data) if model_class.respond_to?(:ps_find_model)
|
76
|
+
|
77
|
+
model_class.where(model_identifiers).first_or_initialize
|
78
|
+
end
|
79
|
+
|
80
|
+
# @param mappings (Array<String>) supports aliasing, sample: ["id", "full_name:name"]
|
81
|
+
# @return (Hash) hash with the correct attr names and its values
|
82
|
+
def parse_mapping(mappings)
|
83
|
+
mappings.map do |prop|
|
84
|
+
source, target = prop.to_s.split(':')
|
85
|
+
key = (target || source).to_sym
|
86
|
+
next unless payload.data.key?(source.to_sym)
|
87
|
+
|
88
|
+
[key, payload.data[source.to_sym]]
|
89
|
+
end.compact.to_h.symbolize_keys
|
90
|
+
end
|
91
|
+
|
92
|
+
# @return (Hash) hash including identifiers and its values
|
93
|
+
def model_identifiers
|
94
|
+
@model_identifiers ||= parse_mapping(Array(settings[:id]).map(&:to_s))
|
95
|
+
end
|
96
|
+
|
97
|
+
def populate_model
|
98
|
+
values = parse_mapping(subscriber.mapping).except(model_identifiers.keys)
|
99
|
+
values.each do |attr, value|
|
100
|
+
model.send("#{attr}=", value)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -4,8 +4,6 @@ require 'pub_sub_model_sync/payload'
|
|
4
4
|
module PubSubModelSync
|
5
5
|
class ServiceBase < PubSubModelSync::Base
|
6
6
|
SERVICE_KEY = 'service_model_sync'
|
7
|
-
PUBLISH_SETTINGS = {}.freeze
|
8
|
-
LISTEN_SETTINGS = {}.freeze
|
9
7
|
|
10
8
|
def listen_messages
|
11
9
|
raise 'method :listen_messages must be defined in service'
|
@@ -22,23 +20,45 @@ module PubSubModelSync
|
|
22
20
|
|
23
21
|
private
|
24
22
|
|
23
|
+
# @param payload (Payload)
|
24
|
+
# @return (String): Json Format
|
25
|
+
def encode_payload(payload)
|
26
|
+
data = payload.to_h
|
27
|
+
not_important_keys = %i[ordering_key topic_name forced_ordering_key]
|
28
|
+
reduce_payload_size = !config.debug
|
29
|
+
data[:headers].except!(*not_important_keys) if reduce_payload_size
|
30
|
+
data.to_json
|
31
|
+
end
|
32
|
+
|
25
33
|
# @param (String: Payload in json format)
|
26
34
|
def process_message(payload_info)
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
35
|
+
retries ||= 0
|
36
|
+
payload = decode_payload(payload_info)
|
37
|
+
return payload.process unless same_app_message?(payload)
|
38
|
+
|
39
|
+
log("Skipping message from same origin: #{[payload]}") if config.debug
|
40
|
+
rescue => e
|
41
|
+
retry if can_retry_process_message?(e, payload, retries += 1)
|
42
|
+
end
|
43
|
+
|
44
|
+
def can_retry_process_message?(error, payload, retries)
|
45
|
+
error_payload = [payload, error.message, error.backtrace]
|
46
|
+
if retries <= 5
|
47
|
+
sleep(retries)
|
48
|
+
log("Error while starting to process a message (retrying #{retries} retries...): #{error_payload}", :error)
|
49
|
+
rescue_database_connection if lost_db_connection_err?(error)
|
50
|
+
true
|
31
51
|
else
|
32
|
-
|
52
|
+
log("Retried 5 times and error persists, exiting...: #{error_payload}", :error)
|
53
|
+
Process.exit!(true)
|
33
54
|
end
|
34
|
-
rescue => e
|
35
|
-
error = [payload, e.message, e.backtrace]
|
36
|
-
log("Error parsing received message: #{error}", :error)
|
37
55
|
end
|
38
56
|
|
39
|
-
|
40
|
-
|
41
|
-
::PubSubModelSync::Payload.
|
57
|
+
# @return Payload
|
58
|
+
def decode_payload(payload_info)
|
59
|
+
payload = ::PubSubModelSync::Payload.from_payload_data(JSON.parse(payload_info))
|
60
|
+
log("Received message: #{[payload]}") if config.debug
|
61
|
+
payload
|
42
62
|
end
|
43
63
|
|
44
64
|
# @param payload (Payload)
|
@@ -46,5 +66,19 @@ module PubSubModelSync
|
|
46
66
|
key = payload.headers[:app_key]
|
47
67
|
key && key == config.subscription_key
|
48
68
|
end
|
69
|
+
|
70
|
+
def lost_db_connection_err?(error)
|
71
|
+
return true if error.class.name == 'PG::UnableToSend' # rubocop:disable Style/ClassEqualityComparison
|
72
|
+
|
73
|
+
error.message.match?(/lost connection/i)
|
74
|
+
end
|
75
|
+
|
76
|
+
def rescue_database_connection
|
77
|
+
log('Lost DB connection. Attempting to reconnect...', :warn)
|
78
|
+
ActiveRecord::Base.connection.reconnect!
|
79
|
+
rescue
|
80
|
+
log('Cannot reconnect to database, exiting...', :error)
|
81
|
+
Process.exit!(true)
|
82
|
+
end
|
49
83
|
end
|
50
84
|
end
|
@@ -7,50 +7,86 @@ end
|
|
7
7
|
|
8
8
|
module PubSubModelSync
|
9
9
|
class ServiceGoogle < ServiceBase
|
10
|
-
LISTEN_SETTINGS = {
|
10
|
+
LISTEN_SETTINGS = { message_ordering: true }.freeze
|
11
|
+
PUBLISH_SETTINGS = {}.freeze
|
11
12
|
TOPIC_SETTINGS = {}.freeze
|
12
13
|
SUBSCRIPTION_SETTINGS = { message_ordering: true }.freeze
|
13
|
-
|
14
|
+
|
15
|
+
# @!attribute topics (Hash): { key: Topic1, ... }
|
16
|
+
# @!attribute publish_topics (Hash): { key: Topic1, ... }
|
17
|
+
attr_accessor :service, :topics, :subscribers, :publish_topics
|
14
18
|
|
15
19
|
def initialize
|
16
20
|
@service = Google::Cloud::Pubsub.new(project: config.project,
|
17
21
|
credentials: config.credentials)
|
18
|
-
|
19
|
-
service.create_topic(config.topic_name, TOPIC_SETTINGS)
|
20
|
-
topic.enable_message_ordering!
|
22
|
+
Array(config.topic_name || 'model_sync').each(&method(:init_topic))
|
21
23
|
end
|
22
24
|
|
23
25
|
def listen_messages
|
24
|
-
@subscription = subscribe_to_topic
|
25
|
-
@subscriber = subscription.listen(LISTEN_SETTINGS, &method(:process_message))
|
26
26
|
log('Listener starting...')
|
27
|
-
|
27
|
+
@subscribers = subscribe_to_topics
|
28
28
|
log('Listener started')
|
29
29
|
sleep
|
30
|
-
subscriber.stop.wait!
|
30
|
+
subscribers.each { |subscriber| subscriber.stop.wait! }
|
31
31
|
log('Listener stopped')
|
32
32
|
end
|
33
33
|
|
34
|
+
# @param payload (PubSubModelSync::Payload)
|
34
35
|
def publish(payload)
|
35
|
-
|
36
|
-
|
36
|
+
p_topic_names = Array(payload.headers[:topic_name] || config.default_topic_name)
|
37
|
+
message_topics = p_topic_names.map(&method(:find_topic))
|
38
|
+
message_topics.each do |topic|
|
39
|
+
topic.publish_async(encode_payload(payload), message_headers(payload)) do |res|
|
40
|
+
raise 'Failed to publish the message.' unless res.succeeded?
|
41
|
+
end
|
37
42
|
end
|
38
43
|
end
|
39
44
|
|
40
45
|
def stop
|
41
46
|
log('Listener stopping...')
|
42
|
-
|
47
|
+
subscribers.each(&:stop!)
|
43
48
|
end
|
44
49
|
|
45
50
|
private
|
46
51
|
|
47
|
-
def
|
48
|
-
|
52
|
+
def find_topic(topic_name)
|
53
|
+
topic_name = topic_name.to_s
|
54
|
+
return topics.values.first unless topic_name.present?
|
55
|
+
|
56
|
+
topics[topic_name] || publish_topics[topic_name] || init_topic(topic_name, only_publish: true)
|
49
57
|
end
|
50
58
|
|
51
|
-
|
52
|
-
|
53
|
-
|
59
|
+
# @param only_publish (Boolean): if false is used to listen and publish messages
|
60
|
+
# @return (Topic): returns created or loaded topic
|
61
|
+
def init_topic(topic_name, only_publish: false)
|
62
|
+
topic_name = topic_name.to_s
|
63
|
+
@topics ||= {}
|
64
|
+
@publish_topics ||= {}
|
65
|
+
topic = service.topic(topic_name) || service.create_topic(topic_name, TOPIC_SETTINGS)
|
66
|
+
topic.enable_message_ordering!
|
67
|
+
publish_topics[topic_name] = topic if only_publish
|
68
|
+
topics[topic_name] = topic unless only_publish
|
69
|
+
topic
|
70
|
+
end
|
71
|
+
|
72
|
+
# @param payload (PubSubModelSync::Payload)
|
73
|
+
def message_headers(payload)
|
74
|
+
{
|
75
|
+
SERVICE_KEY => true,
|
76
|
+
ordering_key: payload.headers[:ordering_key]
|
77
|
+
}.merge(PUBLISH_SETTINGS)
|
78
|
+
end
|
79
|
+
|
80
|
+
# @return [Subscriber]
|
81
|
+
def subscribe_to_topics
|
82
|
+
topics.map do |key, topic|
|
83
|
+
subs_name = "#{config.subscription_key}_#{key}"
|
84
|
+
subscription = topic.subscription(subs_name) || topic.subscribe(subs_name, SUBSCRIPTION_SETTINGS)
|
85
|
+
subscriber = subscription.listen(LISTEN_SETTINGS, &method(:process_message))
|
86
|
+
subscriber.start
|
87
|
+
log("Subscribed to topic: #{topic.name} as: #{subs_name}")
|
88
|
+
subscriber
|
89
|
+
end
|
54
90
|
end
|
55
91
|
|
56
92
|
def process_message(received_message)
|