pub_sub_model_sync 1.0.beta2 → 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 (109) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/release.yml +43 -0
  3. data/CHANGELOG.md +7 -4
  4. data/Gemfile.lock +3 -1
  5. data/README.md +174 -105
  6. data/docs/notifications-diagram.png +0 -0
  7. data/lib/pub_sub_model_sync.rb +1 -1
  8. data/lib/pub_sub_model_sync/base.rb +0 -20
  9. data/lib/pub_sub_model_sync/config.rb +1 -1
  10. data/lib/pub_sub_model_sync/initializers/before_commit.rb +3 -3
  11. data/lib/pub_sub_model_sync/message_processor.rb +32 -9
  12. data/lib/pub_sub_model_sync/message_publisher.rb +14 -10
  13. data/lib/pub_sub_model_sync/payload.rb +15 -12
  14. data/lib/pub_sub_model_sync/{publisher.rb → payload_builder.rb} +15 -10
  15. data/lib/pub_sub_model_sync/publisher_concern.rb +27 -16
  16. data/lib/pub_sub_model_sync/run_subscriber.rb +17 -13
  17. data/lib/pub_sub_model_sync/service_base.rb +5 -32
  18. data/lib/pub_sub_model_sync/service_google.rb +1 -1
  19. data/lib/pub_sub_model_sync/subscriber_concern.rb +6 -4
  20. data/lib/pub_sub_model_sync/transaction.rb +6 -2
  21. data/lib/pub_sub_model_sync/version.rb +1 -1
  22. data/samples/README.md +50 -0
  23. data/samples/app1/.gitattributes +8 -0
  24. data/samples/app1/.gitignore +28 -0
  25. data/samples/app1/Dockerfile +13 -0
  26. data/samples/app1/Gemfile +37 -0
  27. data/samples/app1/Gemfile.lock +171 -0
  28. data/samples/app1/README.md +24 -0
  29. data/samples/app1/Rakefile +6 -0
  30. data/samples/app1/app/models/application_record.rb +3 -0
  31. data/samples/app1/app/models/concerns/.keep +0 -0
  32. data/samples/app1/app/models/post.rb +19 -0
  33. data/samples/app1/app/models/user.rb +29 -0
  34. data/samples/app1/bin/bundle +114 -0
  35. data/samples/app1/bin/rails +5 -0
  36. data/samples/app1/bin/rake +5 -0
  37. data/samples/app1/bin/setup +33 -0
  38. data/samples/app1/bin/spring +14 -0
  39. data/samples/app1/config.ru +6 -0
  40. data/samples/app1/config/application.rb +40 -0
  41. data/samples/app1/config/boot.rb +4 -0
  42. data/samples/app1/config/credentials.yml.enc +1 -0
  43. data/samples/app1/config/database.yml +25 -0
  44. data/samples/app1/config/environment.rb +5 -0
  45. data/samples/app1/config/environments/development.rb +63 -0
  46. data/samples/app1/config/environments/production.rb +105 -0
  47. data/samples/app1/config/environments/test.rb +57 -0
  48. data/samples/app1/config/initializers/application_controller_renderer.rb +8 -0
  49. data/samples/app1/config/initializers/backtrace_silencers.rb +8 -0
  50. data/samples/app1/config/initializers/cors.rb +16 -0
  51. data/samples/app1/config/initializers/filter_parameter_logging.rb +6 -0
  52. data/samples/app1/config/initializers/inflections.rb +16 -0
  53. data/samples/app1/config/initializers/mime_types.rb +4 -0
  54. data/samples/app1/config/initializers/pubsub.rb +4 -0
  55. data/samples/app1/config/initializers/wrap_parameters.rb +14 -0
  56. data/samples/app1/config/locales/en.yml +33 -0
  57. data/samples/app1/config/puma.rb +43 -0
  58. data/samples/app1/config/routes.rb +3 -0
  59. data/samples/app1/config/spring.rb +6 -0
  60. data/samples/app1/db/migrate/20210513080700_create_users.rb +12 -0
  61. data/samples/app1/db/migrate/20210513134332_create_posts.rb +11 -0
  62. data/samples/app1/db/schema.rb +34 -0
  63. data/samples/app1/db/seeds.rb +7 -0
  64. data/samples/app1/docker-compose.yml +32 -0
  65. data/samples/app1/log/.keep +0 -0
  66. data/samples/app2/.gitattributes +8 -0
  67. data/samples/app2/.gitignore +28 -0
  68. data/samples/app2/Dockerfile +13 -0
  69. data/samples/app2/Gemfile +37 -0
  70. data/samples/app2/Gemfile.lock +171 -0
  71. data/samples/app2/README.md +24 -0
  72. data/samples/app2/Rakefile +6 -0
  73. data/samples/app2/app/models/application_record.rb +9 -0
  74. data/samples/app2/app/models/concerns/.keep +0 -0
  75. data/samples/app2/app/models/customer.rb +28 -0
  76. data/samples/app2/app/models/post.rb +10 -0
  77. data/samples/app2/bin/bundle +114 -0
  78. data/samples/app2/bin/rails +5 -0
  79. data/samples/app2/bin/rake +5 -0
  80. data/samples/app2/bin/setup +33 -0
  81. data/samples/app2/bin/spring +14 -0
  82. data/samples/app2/config.ru +6 -0
  83. data/samples/app2/config/application.rb +40 -0
  84. data/samples/app2/config/boot.rb +4 -0
  85. data/samples/app2/config/credentials.yml.enc +1 -0
  86. data/samples/app2/config/database.yml +25 -0
  87. data/samples/app2/config/environment.rb +5 -0
  88. data/samples/app2/config/environments/development.rb +63 -0
  89. data/samples/app2/config/environments/production.rb +105 -0
  90. data/samples/app2/config/environments/test.rb +57 -0
  91. data/samples/app2/config/initializers/application_controller_renderer.rb +8 -0
  92. data/samples/app2/config/initializers/backtrace_silencers.rb +8 -0
  93. data/samples/app2/config/initializers/cors.rb +16 -0
  94. data/samples/app2/config/initializers/filter_parameter_logging.rb +6 -0
  95. data/samples/app2/config/initializers/inflections.rb +16 -0
  96. data/samples/app2/config/initializers/mime_types.rb +4 -0
  97. data/samples/app2/config/initializers/pubsub.rb +4 -0
  98. data/samples/app2/config/initializers/wrap_parameters.rb +14 -0
  99. data/samples/app2/config/locales/en.yml +33 -0
  100. data/samples/app2/config/puma.rb +43 -0
  101. data/samples/app2/config/routes.rb +3 -0
  102. data/samples/app2/config/spring.rb +6 -0
  103. data/samples/app2/db/migrate/20210513080956_create_customers.rb +10 -0
  104. data/samples/app2/db/migrate/20210513135203_create_posts.rb +10 -0
  105. data/samples/app2/db/schema.rb +31 -0
  106. data/samples/app2/db/seeds.rb +7 -0
  107. data/samples/app2/docker-compose.yml +20 -0
  108. data/samples/app2/log/.keep +0 -0
  109. metadata +93 -5
Binary file
@@ -15,7 +15,7 @@ require 'pub_sub_model_sync/connector'
15
15
  require 'pub_sub_model_sync/message_processor'
16
16
  require 'pub_sub_model_sync/run_subscriber'
17
17
 
18
- require 'pub_sub_model_sync/publisher'
18
+ require 'pub_sub_model_sync/payload_builder'
19
19
  require 'pub_sub_model_sync/subscriber'
20
20
 
21
21
  require 'pub_sub_model_sync/service_base'
@@ -17,25 +17,5 @@ module PubSubModelSync
17
17
  config.debug
18
18
  end
19
19
  end
20
-
21
- # @param errors (Array(Class|String))
22
- def retry_error(errors, qty: 2, &block)
23
- retries ||= 0
24
- block.call
25
- rescue => e
26
- retries += 1
27
- res = errors.find { |e_type| match_error?(e, e_type) }
28
- raise if !res || retries > qty
29
-
30
- sleep(qty * 0.1) && retry
31
- end
32
-
33
- private
34
-
35
- # @param error (Exception)
36
- # @param error_type (Class|String)
37
- def match_error?(error, error_type)
38
- error_type.is_a?(String) ? error.message.include?(error_type) : error.is_a?(error_type)
39
- end
40
20
  end
41
21
  end
@@ -30,7 +30,7 @@ module PubSubModelSync
30
30
  def self.log(msg, kind = :info)
31
31
  msg = "PS_MSYNC ==> #{msg}"
32
32
  if logger == :raise_error
33
- kind == :error ? raise(msg) : puts(msg)
33
+ kind == :error ? raise(StandardError, msg) : puts(msg)
34
34
  else
35
35
  logger ? logger.send(kind, msg) : puts(msg)
36
36
  end
@@ -2,11 +2,11 @@
2
2
 
3
3
  # Rails 4 backward compatibility (Add "simple" ps_before_*_commit callbacks)
4
4
  ActiveRecord::ConnectionAdapters::RealTransaction.class_eval do
5
- alias_method :commit_with_before_commit, :commit
5
+ alias_method :commit_without_before_commit, :commit
6
6
 
7
7
  def commit
8
- call_before_commit_records if Rails::VERSION::MAJOR == 4
9
- commit_with_before_commit
8
+ call_before_commit_records if PubSubModelSync::Config.enable_rails4_before_commit
9
+ commit_without_before_commit
10
10
  end
11
11
 
12
12
  private
@@ -16,13 +16,17 @@ module PubSubModelSync
16
16
  end
17
17
 
18
18
  def process!
19
- filter_subscribers.each(&method(:run_subscriber))
19
+ subscribers = filter_subscribers
20
+ payload_info = { klass: payload.klass, action: payload.action, mode: payload.mode }
21
+ log("No subscribers found for #{payload_info}", :warn) if config.debug && subscribers.empty?
22
+ subscribers.each(&method(:run_subscriber))
20
23
  end
21
24
 
22
25
  def process
26
+ retries ||= 0
23
27
  process!
24
28
  rescue => e
25
- notify_error(e)
29
+ retry_process?(e, retries += 1) ? retry : notify_error(e)
26
30
  end
27
31
 
28
32
  private
@@ -31,12 +35,10 @@ module PubSubModelSync
31
35
  processor = PubSubModelSync::RunSubscriber.new(subscriber, payload)
32
36
  return unless processable?(subscriber)
33
37
 
34
- errors = [ActiveRecord::ConnectionTimeoutError, 'deadlock detected', 'could not serialize access']
35
- retry_error(errors, qty: 5) do
36
- processor.call
37
- res = config.on_success_processing.call(payload, { subscriber: subscriber })
38
- log "processed message with: #{payload.inspect}" if res != :skip_log
39
- end
38
+ log("Processing message #{[subscriber, payload]}...") if config.debug
39
+ processor.call
40
+ res = config.on_success_processing.call(payload, { subscriber: subscriber })
41
+ log "processed message with: #{payload.inspect}" if res != :skip_log
40
42
  end
41
43
 
42
44
  def processable?(subscriber)
@@ -45,13 +47,34 @@ module PubSubModelSync
45
47
  !cancel
46
48
  end
47
49
 
48
- # @param error (Error)
50
+ # @param error (StandardError)
49
51
  def notify_error(error)
50
52
  info = [payload, error.message, error.backtrace]
51
53
  res = config.on_error_processing.call(error, { payload: payload })
52
54
  log("Error processing message: #{info}", :error) if res != :skip_log
53
55
  end
54
56
 
57
+ def lost_db_connection?(error)
58
+ connection_lost_classes = %w[ActiveRecord::ConnectionTimeoutError PG::UnableToSend]
59
+ connection_lost_classes.include?(error.class.name) || error.message.match?(/lost connection/i)
60
+ end
61
+
62
+ def retry_process?(error, retries) # rubocop:disable Metrics/MethodLength
63
+ error_payload = [payload, error.message, error.backtrace]
64
+ return false unless lost_db_connection?(error)
65
+
66
+ if retries <= 5
67
+ sleep(retries)
68
+ log("Error processing message: (retrying #{retries}/5): #{error_payload}", :error)
69
+ ActiveRecord::Base.connection.reconnect! rescue nil # rubocop:disable Style/RescueModifier
70
+ true
71
+ else
72
+ log("Retried 5 times and error persists, exiting...: #{error_payload}", :error)
73
+ Process.exit!(true)
74
+ end
75
+ end
76
+
77
+ # @return (Array<PubSubModelSync::Subscriber>)
55
78
  def filter_subscribers
56
79
  config.subscribers.select do |subscriber|
57
80
  subscriber.from_klass == payload.klass && subscriber.action == payload.action && payload.mode == subscriber.mode
@@ -31,7 +31,7 @@ module PubSubModelSync
31
31
  end
32
32
 
33
33
  # Starts a new transaction
34
- # @param key (@transaction_key)
34
+ # @param key (String|Nil)
35
35
  # @return (Transaction)
36
36
  def init_transaction(key, settings = {})
37
37
  new_transaction = PubSubModelSync::Transaction.new(key, settings)
@@ -47,20 +47,20 @@ module PubSubModelSync
47
47
  # @refer PublisherConcern.ps_class_publish
48
48
  # @return Payload
49
49
  def publish_data(klass, data, action, headers: {})
50
- attrs = { klass: klass.to_s, action: action.to_sym, mode: :klass }
51
- payload = PubSubModelSync::Payload.new(data, attrs, headers)
50
+ info = { klass: klass.to_s, action: action.to_sym, mode: :klass }
51
+ log("Building payload for: #{info.inspect}") if config.debug
52
+ payload = PubSubModelSync::Payload.new(data, info, headers)
53
+ define_transaction_key(payload)
52
54
  publish(payload)
53
55
  end
54
56
 
55
57
  # @param model (ActiveRecord::Base)
56
58
  # @param action (Symbol: @see PublishConcern::ps_publish)
57
- # @param settings (Hash: @see Publisher.new.settings)
59
+ # @param settings (Hash: @see PayloadBuilder.settings)
58
60
  def publish_model(model, action, settings = {})
59
- return if model.ps_skip_publish?(action)
60
-
61
- publisher = PubSubModelSync::Publisher.new(model, action, settings)
62
- payload = publisher.payload
63
-
61
+ log("Building payload for: #{[model, action].inspect}") if config.debug
62
+ payload = PubSubModelSync::PayloadBuilder.new(model, action, settings).call
63
+ define_transaction_key(payload)
64
64
  transaction(payload.headers[:ordering_key]) do # catch and group all :ps_before_publish syncs
65
65
  publish(payload) { model.ps_after_publish(action, payload) } if ensure_model_publish(model, action, payload)
66
66
  end
@@ -80,6 +80,7 @@ module PubSubModelSync
80
80
  end
81
81
 
82
82
  def connector_publish(payload)
83
+ log("Publishing message #{payload.inspect}...") if config.debug
83
84
  connector.publish(payload)
84
85
  log("Published message: #{[payload]}")
85
86
  config.on_after_publish.call(payload)
@@ -103,7 +104,6 @@ module PubSubModelSync
103
104
  end
104
105
 
105
106
  def ordering_key_for(payload)
106
- current_transaction&.key ||= payload.headers[:ordering_key]
107
107
  payload.headers[:forced_ordering_key] || current_transaction&.key || payload.headers[:ordering_key]
108
108
  end
109
109
 
@@ -119,6 +119,10 @@ module PubSubModelSync
119
119
  res = config.on_error_publish.call(exception, { payload: payload })
120
120
  log("Error publishing: #{info}", :error) if res != :skip_log
121
121
  end
122
+
123
+ def define_transaction_key(payload)
124
+ current_transaction&.key ||= payload.headers[:ordering_key]
125
+ end
122
126
  end
123
127
  end
124
128
  end
@@ -6,30 +6,33 @@ module PubSubModelSync
6
6
  attr_reader :data, :info, :headers
7
7
 
8
8
  # @param data (Hash: { any value }):
9
- # @param info (Hash: { klass*: string, action*: :sym, mode?: :klass|:model }):
9
+ # @param info (Hash):
10
+ # klass: (String, required) Notification class name
11
+ # action: (Symbol, required) Notification action name
12
+ # mode: (:model|:klass, default :model): :model for instance and :klass for class notifications
10
13
  # @param headers (Hash):
11
14
  # key (String): identifier of the payload, default:
12
- # klass/action: when class message
13
- # klass/action/model.id: when model message
15
+ # <klass/action>: when class message
16
+ # <klass/action/model.id>: when model message
14
17
  # ordering_key (String): messages with the same key are processed in the same order they
15
18
  # were delivered, default:
16
- # klass: when class message
17
- # klass/id: when model message
19
+ # <klass>: when class message
20
+ # <klass/id>: when model message
18
21
  # topic_name (String|Array<String>): Specific topic name to be used when delivering the
19
22
  # message (default first topic)
20
23
  # forced_ordering_key (String, optional): Will force to use this value as the ordering_key,
21
24
  # even withing transactions. Default nil.
22
25
  def initialize(data, info, headers = {})
23
- @data = data
24
- @info = { mode: :model }.merge(info)
25
- @headers = headers
26
+ @data = data.deep_symbolize_keys
27
+ @info = info.deep_symbolize_keys
28
+ @headers = headers.deep_symbolize_keys
26
29
  build_headers
27
30
  validate!
28
31
  end
29
32
 
30
33
  # @return Hash: payload data
31
34
  def to_h
32
- { data: data, info: info, headers: headers }
35
+ { data: data.clone, info: info.clone, headers: headers.clone }
33
36
  end
34
37
 
35
38
  def klass
@@ -41,7 +44,7 @@ module PubSubModelSync
41
44
  end
42
45
 
43
46
  def mode
44
- info[:mode].to_sym
47
+ (info[:mode] || :model).to_sym
45
48
  end
46
49
 
47
50
  # Process payload data
@@ -75,8 +78,8 @@ module PubSubModelSync
75
78
  # convert payload data into Payload
76
79
  # @param data [Hash]: payload data (:data, :info, :headers)
77
80
  def self.from_payload_data(data)
78
- data = data.deep_symbolize_keys
79
- new(data[:data], data[:info], data[:headers])
81
+ data = data.symbolize_keys
82
+ new(data[:data], data[:info] || data[:attributes], data[:headers])
80
83
  end
81
84
 
82
85
  private
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PubSubModelSync
4
- class Publisher < PubSubModelSync::Base
4
+ class PayloadBuilder < PubSubModelSync::Base
5
5
  attr_accessor :model, :action, :data, :mapping, :headers, :as_klass
6
6
 
7
7
  # @param model (ActiveRecord::Base)
@@ -17,9 +17,9 @@ module PubSubModelSync
17
17
  end
18
18
 
19
19
  # @return (Payload)
20
- def payload
20
+ def call
21
21
  values = compute_value(data)
22
- values = mapping_data.merge(values)
22
+ values = self.class.parse_mapping_for(model, mapping).merge(values)
23
23
  PubSubModelSync::Payload.new(values, settings_data, headers_data)
24
24
  end
25
25
 
@@ -27,6 +27,18 @@ module PubSubModelSync
27
27
  [model.class.name, model.id || SecureRandom.uuid].join('/')
28
28
  end
29
29
 
30
+ # @param model (ActiveRecord::Base)
31
+ # @param mapping (@see PublishConcern::ps_publish -> mapping)
32
+ # @return (Hash) Hash with the corresponding values for each attribute
33
+ # Sample: parse_mapping_for(my_model, %w[id name:full_name])
34
+ # ==> { id: 10, full_name: 'model.name value' }
35
+ def self.parse_mapping_for(model, mapping)
36
+ mapping.map do |prop|
37
+ source, target = prop.to_s.split(':')
38
+ [target || source, model.send(source.to_sym)]
39
+ end.to_h.symbolize_keys
40
+ end
41
+
30
42
  private
31
43
 
32
44
  def headers_data
@@ -46,12 +58,5 @@ module PubSubModelSync
46
58
  def settings_data
47
59
  { klass: as_klass, action: action }
48
60
  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
55
- end
56
61
  end
57
62
  end
@@ -9,12 +9,6 @@ module PubSubModelSync
9
9
  ps_init_transaction_callbacks if self <= ActiveRecord::Base
10
10
  end
11
11
 
12
- # before preparing data to sync
13
- def ps_skip_publish?(_action)
14
- false
15
- end
16
- alias ps_skip_sync? ps_skip_publish? # @deprecated
17
-
18
12
  # before delivering data (return :cancel to cancel sync)
19
13
  def ps_before_publish(_action, _payload); end
20
14
  alias ps_before_sync ps_before_publish # @deprecated
@@ -42,6 +36,16 @@ module PubSubModelSync
42
36
  end
43
37
  delegate :ps_class_publish, to: :class
44
38
 
39
+ # Permits to perform manually the callback for a specific action
40
+ # @param action (Symbol, default: :create) Only :create|:update|:destroy
41
+ def ps_perform_publish(action = :create)
42
+ items = self.class.ps_cache_publish_callbacks.select { |item| item[:actions].include?(action) }
43
+ raise(StandardError, "No callback found for action :#{action}") if items.empty?
44
+
45
+ items.each { |item| instance_exec(action, &item[:callback]) }
46
+ self
47
+ end
48
+
45
49
  module ClassMethods
46
50
  # Publishes a class level notification via pubsub
47
51
  # @param data (Hash): Data of the notification
@@ -55,24 +59,32 @@ module PubSubModelSync
55
59
 
56
60
  # @param crud_actions (Symbol|Array<Symbol>): :create, :update, :destroy
57
61
  # @param method_name (Symbol, optional) method to be called
58
- def ps_on_crud_event(crud_actions, method_name = nil, &block)
62
+ def ps_after_action(crud_actions, method_name = nil, &block)
63
+ actions = Array(crud_actions).map(&:to_sym)
59
64
  callback = ->(action) { method_name ? send(method_name, action) : instance_exec(action, &block) }
60
- Array(crud_actions).each do |action|
65
+ ps_cache_publish_callbacks({ actions: actions, callback: callback })
66
+ actions.each do |action|
61
67
  if action == :destroy
62
68
  after_destroy { instance_exec(action, &callback) }
63
69
  else
64
- ps_crud_define_commit_action(action, callback)
70
+ ps_define_commit_action(action, callback)
65
71
  end
66
72
  end
67
73
  end
68
74
 
75
+ def ps_cache_publish_callbacks(new_value = nil)
76
+ @ps_cache_publish_callbacks ||= []
77
+ @ps_cache_publish_callbacks << new_value if new_value
78
+ @ps_cache_publish_callbacks
79
+ end
80
+
69
81
  private
70
82
 
71
- def ps_crud_define_commit_action(action, callback)
72
- commit_name = respond_to?(:before_commit) ? :before_commit : :after_commit
83
+ def ps_define_commit_action(action, callback)
73
84
  if PubSubModelSync::Config.enable_rails4_before_commit # rails 4 compatibility
74
85
  define_method("ps_before_#{action}_commit") { instance_exec(action, &callback) }
75
86
  else
87
+ commit_name = respond_to?(:before_commit) ? :before_commit : :after_commit
76
88
  send(commit_name, on: action) { instance_exec(action, &callback) }
77
89
  end
78
90
  end
@@ -80,14 +92,13 @@ module PubSubModelSync
80
92
  # Initialize calls to start and end pub_sub transactions and deliver all them in the same order
81
93
  def ps_init_transaction_callbacks
82
94
  start_transaction = lambda do
83
- key = id ? PubSubModelSync::Publisher.ordering_key_for(self) : nil
84
- @ps_transaction = PubSubModelSync::MessagePublisher.init_transaction(key)
95
+ @ps_transaction = PubSubModelSync::MessagePublisher.init_transaction(nil)
85
96
  end
86
- after_create start_transaction, prepend: true # wait for ID
97
+ before_create start_transaction, prepend: true
87
98
  before_update start_transaction, prepend: true
88
99
  before_destroy start_transaction, prepend: true
89
- after_commit { @ps_transaction.finish }
90
- after_rollback(prepend: true) { @ps_transaction.rollback }
100
+ after_commit { @ps_transaction&.finish }
101
+ after_rollback(prepend: true) { @ps_transaction&.rollback }
91
102
  end
92
103
  end
93
104
  end
@@ -26,7 +26,7 @@ module PubSubModelSync
26
26
  def run_class_message
27
27
  model_class = subscriber.klass.constantize
28
28
  model_class.ps_processing_payload = payload # TODO: review for parallel notifications
29
- call_action(model_class, payload.data) if ensure_sync(model_class)
29
+ call_action(model_class) if ensure_sync(model_class)
30
30
  end
31
31
 
32
32
  # support for: create, update, destroy
@@ -48,16 +48,17 @@ module PubSubModelSync
48
48
  res
49
49
  end
50
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?
51
+ def call_action(object)
52
+ callback = settings[:to_action]
53
+ callback.is_a?(Proc) ? object.instance_exec(payload.data, &callback) : call_action_method(object)
54
+ end
55
+
56
+ def call_action_method(object)
57
+ method_name = settings[:to_action]
58
+ method_name = :save! if %i[create update].include?(method_name.to_sym)
59
+ method_name = :destroy! if method_name.to_sym == :destroy
60
+ is_crud_action = %i[save! destroy!].include?(method_name)
61
+ is_crud_action ? object.send(method_name) : object.send(method_name, payload.data)
61
62
  end
62
63
 
63
64
  def parse_condition(condition, object)
@@ -74,10 +75,13 @@ module PubSubModelSync
74
75
  model_class = subscriber.klass.constantize
75
76
  return model_class.ps_find_model(payload.data) if model_class.respond_to?(:ps_find_model)
76
77
 
78
+ error_msg = 'No values provided for identifiers:'
79
+ raise(StandardError, "#{error_msg} #{[settings[:id], payload]}") if model_identifiers.empty?
80
+
77
81
  model_class.where(model_identifiers).first_or_initialize
78
82
  end
79
83
 
80
- # @param mappings (Array<String>) supports aliasing, sample: ["id", "full_name:name"]
84
+ # @param mappings (Array<String,Symbol>) supports aliasing, sample: ["id", "full_name:name"]
81
85
  # @return (Hash) hash with the correct attr names and its values
82
86
  def parse_mapping(mappings)
83
87
  mappings.map do |prop|
@@ -91,7 +95,7 @@ module PubSubModelSync
91
95
 
92
96
  # @return (Hash) hash including identifiers and its values
93
97
  def model_identifiers
94
- @model_identifiers ||= parse_mapping(Array(settings[:id]).map(&:to_s))
98
+ @model_identifiers ||= parse_mapping(Array(settings[:id]))
95
99
  end
96
100
 
97
101
  def populate_model