pub_sub_model_sync 0.5.0 → 0.5.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: edcb2024203ae281bbc642825d0f5361205afe787c4a2ebb735cefaa1d98b80c
4
- data.tar.gz: 1d6bb21f6808f9595d0bb842a997457097bb0dbc25deb500bb67d73f229b2480
3
+ metadata.gz: 608c6430e2e37529a6a219d09db34b7a540d89bed73da7e272c1624ad7ba821f
4
+ data.tar.gz: 99eaf893d6c66d3e7429d6ba8758b7e44d9ca0a29069b72f260bccecd7a207f0
5
5
  SHA512:
6
- metadata.gz: c6fd7fd582186a12564a41314ba85b198011213b79e6c18af24a542652d30b2ab2f44126f501608a37cdd7e1714cba84cb174b1ae63a14086d0a691acee1a4d4
7
- data.tar.gz: 6ed6f1c39199582db6e7b51468e4da9ff98b55c8245df64971bc76bf510bf2f68e15109104074fbb5a3e34a1534c1711990f514b12b22d1e6504560c4a19612f
6
+ metadata.gz: c9a0d769a2c18b9097d7f9d4b10f8f11567e743a39bcf10fe39ed4bd3daf156fb028685be4f8100d92d9bb0fd8ea742a1c0a5dca8fe0743c0c4e21b3b23fd552
7
+ data.tar.gz: 4ff19cc02656fd8ddae04e061808c89ec61eae3e86cd889a3c2abc3fed0c2975ed8eed995363ad834d294f75ac340fdeb454c1de21e05e2f4080458664f8d181
@@ -1,5 +1,23 @@
1
1
  # Change Log
2
2
 
3
+ # 0.5.3 (December 30, 2020)
4
+ - fix: kafka consume all messages from different apps
5
+ - style: use the correct consumer key
6
+
7
+ # 0.5.2 (December 30, 2020)
8
+ - fix: rabbitmq deliver messages to all subscribers
9
+ - fix: rabbitmq persist messages to recover after restarting
10
+
11
+ # 0.5.1.1 (December 29, 2020)
12
+ - Hotfix: auto convert class name into string
13
+
14
+ # 0.5.1 (December 24, 2020)
15
+ - feat: rename publisher callbacks to be more understandable
16
+ - feat: add callbacks to listen when processing a message (before saving sync)
17
+
18
+ # 0.5.0.1 (December 22, 2020)
19
+ - fix: add missing rabbit mock method
20
+
3
21
  # 0.5.0 (December 22, 2020)
4
22
  - feat: add :publish! and :process! methods to payloads
5
23
  - feat: add ability to disable publisher globally
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- pub_sub_model_sync (0.5.0)
4
+ pub_sub_model_sync (0.5.3)
5
5
  rails
6
6
 
7
7
  GEM
@@ -77,7 +77,7 @@ GEM
77
77
  database_cleaner (~> 1.8.0)
78
78
  diff-lcs (1.3)
79
79
  digest-crc (0.5.1)
80
- erubi (1.9.0)
80
+ erubi (1.10.0)
81
81
  faraday (0.17.3)
82
82
  multipart-post (>= 1.2, < 3)
83
83
  globalid (0.4.2)
@@ -121,7 +121,7 @@ GEM
121
121
  i18n (1.8.2)
122
122
  concurrent-ruby (~> 1.0)
123
123
  jwt (2.2.1)
124
- loofah (2.6.0)
124
+ loofah (2.8.0)
125
125
  crass (~> 1.0.2)
126
126
  nokogiri (>= 1.5.9)
127
127
  mail (2.7.1)
@@ -136,7 +136,7 @@ GEM
136
136
  minitest (5.14.0)
137
137
  multi_json (1.14.1)
138
138
  multipart-post (2.1.1)
139
- nio4r (2.5.2)
139
+ nio4r (2.5.4)
140
140
  nokogiri (1.10.10)
141
141
  mini_portile2 (~> 2.4.0)
142
142
  os (1.0.1)
@@ -213,7 +213,7 @@ GEM
213
213
  sprockets (4.0.2)
214
214
  concurrent-ruby (~> 1.0)
215
215
  rack (> 1, < 3)
216
- sprockets-rails (3.2.1)
216
+ sprockets-rails (3.2.2)
217
217
  actionpack (>= 4.0)
218
218
  activesupport (>= 4.0)
219
219
  sprockets (>= 3.0.0)
data/README.md CHANGED
@@ -28,10 +28,9 @@ And then execute: $ bundle install
28
28
  ```ruby
29
29
  # initializers/pub_sub_config.rb
30
30
  PubSubModelSync::Config.service_name = :google
31
- PubSubModelSync::Config.project = 'project-id'
31
+ PubSubModelSync::Config.project = 'google-project-id'
32
32
  PubSubModelSync::Config.credentials = 'path-to-the-config'
33
33
  PubSubModelSync::Config.topic_name = 'sample-topic'
34
- PubSubModelSync::Config.subscription_name = 'p1-subscriber'
35
34
  ```
36
35
  See details here:
37
36
  https://github.com/googleapis/google-cloud-ruby/tree/master/google-cloud-pubsub
@@ -40,7 +39,7 @@ And then execute: $ bundle install
40
39
  ```ruby
41
40
  PubSubModelSync::Config.service_name = :rabbitmq
42
41
  PubSubModelSync::Config.bunny_connection = 'amqp://guest:guest@localhost'
43
- PubSubModelSync::Config.queue_name = ''
42
+ PubSubModelSync::Config.queue_name = 'model-sync'
44
43
  PubSubModelSync::Config.topic_name = 'sample-topic'
45
44
  ```
46
45
  See details here: https://github.com/ruby-amqp/bunny
@@ -48,7 +47,7 @@ And then execute: $ bundle install
48
47
  - configuration for Apache Kafka (You need kafka installed)
49
48
  ```ruby
50
49
  PubSubModelSync::Config.service_name = :kafka
51
- PubSubModelSync::Config.kafka_connection = [["kafka1:9092", "localhost:2121"], logger: Rails.logger]
50
+ PubSubModelSync::Config.kafka_connection = [["kafka1:9092", "localhost:2121"], { logger: Rails.logger }]
52
51
  PubSubModelSync::Config.topic_name = 'sample-topic'
53
52
  ```
54
53
  See details here: https://github.com/zendesk/ruby-kafka
@@ -59,7 +58,13 @@ And then execute: $ bundle install
59
58
  ```ruby
60
59
  rake pub_sub_model_sync:start
61
60
  ```
62
- Note: Publishers do not need todo this
61
+ Note: Publishers do not need todo this
62
+ Note2 (Rails 6+): Due to Zeitwerk, you need to load listeners manually when syncing outside ```rake pub_sub_model_sync:start```
63
+ ```ruby
64
+ # PubSubModelSync::Config.subscribers ==> []
65
+ Rails.application.try(:eager_load!)
66
+ # PubSubModelSync::Config.subscribers ==> [#<PubSubModelSync::Subscriber:0x000.. @klass="Article", @action=:create..., ....]
67
+ ```
63
68
 
64
69
  - Check the service status with:
65
70
  ```PubSubModelSync::MessagePublisher.publish_data('Test message', {sample_value: 10}, :create)```
@@ -163,6 +168,9 @@ Note: Be careful with collision of names
163
168
  ```.ps_subscriber_changed?(data)```
164
169
  By default: ```model.changed?```
165
170
 
171
+ - Permit to perform custom actions before saving sync of the model (:cancel can be returned to skip sync)
172
+ ```.ps_before_save_sync(payload)```
173
+
166
174
  ### Publishers
167
175
  - Permit to configure crud publishers
168
176
  ```ps_publish(attrs, actions: nil, as_klass: nil)```
@@ -274,21 +282,25 @@ config = PubSubModelSync::Config
274
282
  config.debug = true
275
283
  ```
276
284
 
277
- - ```debug = true```
285
+ - ```.subscription_name = 'app-2'```
286
+ Permit to define a custom consumer identifier (Default: Rails application name)
287
+ - ```.debug = true```
278
288
  (true/false*) => show advanced log messages
279
- - ```logger = Rails.logger```
289
+ - ```.logger = Rails.logger```
280
290
  (Logger) => define custom logger
281
- - ```disabled = true```
282
- (true/false*) => if true, does not publish model messages (Create/Update/Destroy)
283
- - ```on_process_success = ->(payload, subscriber) { puts payload }```
291
+ - ```.disabled_callback_publisher = ->(_model, _action) { false }```
292
+ (true/false*) => if true, does not listen model callbacks for auto sync (Create/Update/Destroy)
293
+ - ```.on_before_processing = ->(payload, subscriber) { puts payload }```
294
+ (Proc) => called before processing received message (:cancel can be returned to skip processing)
295
+ - ```.on_success_processing = ->(payload, subscriber) { puts payload }```
284
296
  (Proc) => called when a message was successfully processed
285
- - ```on_process_error = ->(exception, payload) { sleep 1; payload.process! }```
297
+ - ```.on_error_processing = ->(exception, payload) { sleep 1; payload.process! }```
286
298
  (Proc) => called when a message failed when processing
287
- - ```on_before_publish = ->(payload) { puts payload }```
288
- (Proc) => called before publishing a message
289
- - ```on_after_publish = ->(payload) { puts payload }```
299
+ - ```.on_before_publish = ->(payload) { puts payload }```
300
+ (Proc) => called before publishing a message (:cancel can be returned to skip publishing)
301
+ - ```.on_after_publish = ->(payload) { puts payload }```
290
302
  (Proc) => called after publishing a message
291
- - ```on_publish_error = ->(exception, payload) { sleep 1; payload.publish! }```
303
+ - ```.on_error_publish = ->(exception, payload) { sleep 1; payload.publish! }```
292
304
  (Proc) => called when failed publishing a message
293
305
 
294
306
  ## TODO
@@ -10,12 +10,13 @@ module PubSubModelSync
10
10
  cattr_accessor(:debug) { false }
11
11
  cattr_accessor :logger # LoggerInst
12
12
 
13
- cattr_accessor(:on_process_success) { ->(_payload, _subscriber) {} }
14
- cattr_accessor(:on_process_error) { ->(_exception, _payload) {} }
15
- cattr_accessor(:on_before_publish) { ->(_payload) {} }
13
+ cattr_accessor(:on_before_processing) { ->(_payload, _subscriber) {} } # return :cancel to skip
14
+ cattr_accessor(:on_success_processing) { ->(_payload, _subscriber) {} }
15
+ cattr_accessor(:on_error_processing) { ->(_exception, _payload) {} }
16
+ cattr_accessor(:on_before_publish) { ->(_payload) {} } # return :cancel to skip
16
17
  cattr_accessor(:on_after_publish) { ->(_payload) {} }
17
- cattr_accessor(:on_publish_error) { ->(_exception, _payload) {} }
18
- cattr_accessor(:disabled) { false }
18
+ cattr_accessor(:on_error_publish) { ->(_exception, _payload) {} }
19
+ cattr_accessor(:disabled_callback_publisher) { ->(_model, _action) { false } }
19
20
 
20
21
  # google service
21
22
  cattr_accessor :project, :credentials, :topic_name, :subscription_name
@@ -22,17 +22,25 @@ module PubSubModelSync
22
22
  private
23
23
 
24
24
  def run_subscriber(subscriber)
25
- subscriber.eval_message(payload.data)
26
- config.on_process_success.call(payload, subscriber)
27
- log "processed message with: #{payload}"
25
+ return unless processable?(subscriber)
26
+
27
+ subscriber.process!(payload)
28
+ res = config.on_success_processing.call(payload, subscriber)
29
+ log "processed message with: #{payload.inspect}" if res != :skip_log
28
30
  rescue => e
29
31
  print_subscriber_error(e)
30
32
  end
31
33
 
34
+ def processable?(subscriber)
35
+ cancel = config.on_before_processing.call(payload, subscriber) == :cancel
36
+ log("process message cancelled: #{payload}") if cancel && config.debug
37
+ !cancel
38
+ end
39
+
32
40
  # @param error (Error)
33
41
  def print_subscriber_error(error)
34
42
  info = [payload, error.message, error.backtrace]
35
- res = config.on_process_error.call(error, payload)
43
+ res = config.on_error_processing.call(error, payload)
36
44
  log("Error processing message: #{info}", :error) if res != :skip_log
37
45
  end
38
46
 
@@ -8,7 +8,7 @@ module PubSubModelSync
8
8
  end
9
9
 
10
10
  def publish_data(klass, data, action)
11
- payload = PubSubModelSync::Payload.new(data, { klass: klass, action: action.to_sym })
11
+ payload = PubSubModelSync::Payload.new(data, { klass: klass.to_s, action: action.to_sym })
12
12
  publish(payload)
13
13
  end
14
14
 
@@ -19,8 +19,7 @@ module PubSubModelSync
19
19
  return if model.ps_skip_sync?(action)
20
20
 
21
21
  publisher ||= model.class.ps_publisher(action)
22
- payload_info = publisher.payload(model, action)
23
- payload = PubSubModelSync::Payload.new(payload_info[:data], payload_info[:attrs])
22
+ payload = publisher.payload(model, action)
24
23
  res_before = model.ps_before_sync(action, payload.data)
25
24
  return if res_before == :cancel
26
25
 
@@ -29,8 +28,12 @@ module PubSubModelSync
29
28
  end
30
29
 
31
30
  def publish(payload)
32
- log("Publishing message: #{[payload]}") if config.debug
33
- config.on_before_publish.call(payload)
31
+ if config.on_before_publish.call(payload) == :cancel
32
+ log("Publish message cancelled: #{payload}") if config.debug
33
+ return
34
+ end
35
+
36
+ log("Publishing message: #{[payload]}")
34
37
  connector.publish(payload)
35
38
  config.on_after_publish.call(payload)
36
39
  rescue => e
@@ -41,7 +44,7 @@ module PubSubModelSync
41
44
 
42
45
  def notify_error(exception, payload)
43
46
  info = [payload, exception.message, exception.backtrace]
44
- res = config.on_publish_error.call(exception, payload)
47
+ res = config.on_error_publish.call(exception, payload)
45
48
  log("Error publishing: #{info}", :error) if res != :skip_log
46
49
  end
47
50
  end
@@ -20,6 +20,10 @@ module PubSubModelSync
20
20
  def name
21
21
  'name'
22
22
  end
23
+
24
+ def publish(*_args)
25
+ true
26
+ end
23
27
  end
24
28
 
25
29
  class MockChannel
@@ -12,7 +12,7 @@ module PubSubModelSync
12
12
  end
13
13
 
14
14
  def payload(model, action)
15
- { data: payload_data(model), attrs: payload_attrs(model, action) }
15
+ PubSubModelSync::Payload.new(payload_data(model), payload_attrs(model, action))
16
16
  end
17
17
 
18
18
  private
@@ -11,13 +11,12 @@ module PubSubModelSync
11
11
  false
12
12
  end
13
13
 
14
- # TODO: make it using respond_to?(:ps_skip_sync?)
15
14
  # before preparing data to sync
16
15
  def ps_skip_sync?(_action)
17
16
  false
18
17
  end
19
18
 
20
- # before delivering data
19
+ # before delivering data (return :cancel to cancel sync)
21
20
  def ps_before_sync(_action, _data); end
22
21
 
23
22
  # after delivering data
@@ -64,7 +63,7 @@ module PubSubModelSync
64
63
 
65
64
  def ps_register_callback(action, publisher)
66
65
  after_commit(on: action) do |model|
67
- disabled = PubSubModelSync::Config.disabled
66
+ disabled = PubSubModelSync::Config.disabled_callback_publisher.call(model, action)
68
67
  if !disabled && !model.ps_skip_callback?(action)
69
68
  klass = PubSubModelSync::MessagePublisher
70
69
  klass.publish_model(model, action.to_sym, publisher)
@@ -39,8 +39,8 @@ module PubSubModelSync
39
39
  private
40
40
 
41
41
  def subscribe_to_topic
42
- topic.subscription(config.subscription_name) ||
43
- topic.subscribe(config.subscription_name)
42
+ topic.subscription(config.subscription_key) ||
43
+ topic.subscribe(config.subscription_key)
44
44
  end
45
45
 
46
46
  def process_message(received_message)
@@ -10,11 +10,11 @@ module PubSubModelSync
10
10
  cattr_accessor :producer
11
11
  attr_accessor :config, :service, :consumer
12
12
 
13
- CONSUMER_GROUP = 'service_model_sync'
14
-
15
13
  def initialize
16
14
  @config = PubSubModelSync::Config
17
- @service = Kafka.new(*config.kafka_connection)
15
+ settings = config.kafka_connection
16
+ settings[1][:client_id] ||= config.subscription_key
17
+ @service = Kafka.new(*settings)
18
18
  end
19
19
 
20
20
  def listen_messages
@@ -44,7 +44,7 @@ module PubSubModelSync
44
44
  end
45
45
 
46
46
  def start_consumer
47
- @consumer = service.consumer(group_id: CONSUMER_GROUP)
47
+ @consumer = service.consumer(group_id: config.subscription_key)
48
48
  consumer.subscribe(config.topic_name)
49
49
  end
50
50
 
@@ -40,6 +40,7 @@ module PubSubModelSync
40
40
 
41
41
  def stop
42
42
  log('Listener stopping...')
43
+ channel&.close
43
44
  service.close
44
45
  end
45
46
 
@@ -48,10 +49,15 @@ module PubSubModelSync
48
49
  def message_settings
49
50
  {
50
51
  routing_key: queue.name,
51
- type: SERVICE_KEY
52
+ type: SERVICE_KEY,
53
+ persistent: true
52
54
  }
53
55
  end
54
56
 
57
+ def queue_settings
58
+ { durable: true, auto_delete: false }
59
+ end
60
+
55
61
  def subscribe_settings
56
62
  { manual_ack: false }
57
63
  end
@@ -65,8 +71,7 @@ module PubSubModelSync
65
71
  def subscribe_to_queue
66
72
  service.start
67
73
  @channel = service.create_channel
68
- queue_settings = { durable: true, auto_delete: false }
69
- @queue = channel.queue(config.queue_name, queue_settings)
74
+ @queue = channel.queue(config.subscription_key, queue_settings)
70
75
  subscribe_to_exchange
71
76
  end
72
77
 
@@ -3,6 +3,7 @@
3
3
  module PubSubModelSync
4
4
  class Subscriber
5
5
  attr_accessor :klass, :action, :attrs, :settings
6
+ attr_reader :payload
6
7
 
7
8
  # @param settings: (Hash) { id: :id, direct_mode: false,
8
9
  # from_klass: klass, from_action: action }
@@ -15,48 +16,51 @@ module PubSubModelSync
15
16
  @settings = def_settings.merge(settings)
16
17
  end
17
18
 
18
- def eval_message(message)
19
+ def process!(payload)
20
+ @payload = payload
19
21
  if settings[:direct_mode]
20
- run_class_message(message)
22
+ run_class_message
21
23
  else
22
- run_model_message(message)
24
+ run_model_message
23
25
  end
24
26
  end
25
27
 
26
28
  private
27
29
 
28
- def run_class_message(message)
30
+ def run_class_message
29
31
  model_class = klass.constantize
30
- model_class.send(action, message)
32
+ model_class.send(action, payload.data)
31
33
  end
32
34
 
33
35
  # support for: create, update, destroy
34
- def run_model_message(message)
35
- model = find_model(message)
36
+ def run_model_message
37
+ model = find_model
38
+ return if model.ps_before_save_sync(payload) == :cancel
39
+
36
40
  if action == :destroy
37
41
  model.destroy!
38
42
  else
39
- populate_model(model, message)
40
- return if action == :update && !model.ps_subscriber_changed?(message)
43
+ populate_model(model)
44
+ return if action == :update && !model.ps_subscriber_changed?(payload.data)
41
45
 
42
46
  model.save!
43
47
  end
44
48
  end
45
49
 
46
- def find_model(message)
50
+ def find_model
47
51
  model_class = klass.constantize
48
- return model_class.ps_find_model(message) if model_class.respond_to?(:ps_find_model)
52
+ return model_class.ps_find_model(payload.data) if model_class.respond_to?(:ps_find_model)
49
53
 
50
- model_class.where(model_identifiers(message)).first_or_initialize
54
+ model_class.where(model_identifiers).first_or_initialize
51
55
  end
52
56
 
53
- def model_identifiers(message)
57
+ def model_identifiers
54
58
  identifiers = Array(settings[:id])
55
- identifiers.map { |key| [key, message[key.to_sym]] }.to_h
59
+ identifiers.map { |key| [key, payload.data[key.to_sym]] }.to_h
56
60
  end
57
61
 
58
- def populate_model(model, message)
59
- values = message.slice(*attrs)
62
+ def populate_model(model)
63
+ values = payload.data.slice(*attrs)
60
64
  values.each do |attr, value|
61
65
  model.send("#{attr}=", value)
62
66
  end
@@ -12,6 +12,10 @@ module PubSubModelSync
12
12
  changed?
13
13
  end
14
14
 
15
+ # permit to apply custom actions before applying sync
16
+ # @return (nil|:cancel): nil to continue sync OR :cancel to skip sync
17
+ def ps_before_save_sync(_payload); end
18
+
15
19
  module ClassMethods
16
20
  def ps_subscribe(attrs, actions: nil, from_klass: name, id: :id)
17
21
  settings = { id: id, from_klass: from_klass }
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PubSubModelSync
4
- VERSION = '0.5.0'
4
+ VERSION = '0.5.3'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pub_sub_model_sync
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.5.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Owen
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-12-22 00:00:00.000000000 Z
11
+ date: 2020-12-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails