nats_wave 1.1.8 → 1.1.9
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/Gemfile.lock +1 -1
- data/lib/nats_wave/active_record_extension.rb +93 -0
- data/lib/nats_wave/adapters/active_record.rb +207 -0
- data/lib/nats_wave/adapters/datadog_metrics.rb +1 -1
- data/lib/nats_wave/client.rb +411 -154
- data/lib/nats_wave/concerns/mappable.rb +481 -117
- data/lib/nats_wave/configuration.rb +1 -1
- data/lib/nats_wave/database_connector.rb +51 -0
- data/lib/nats_wave/publisher.rb +142 -39
- data/lib/nats_wave/railtie.rb +126 -6
- data/lib/nats_wave/subscriber.rb +391 -1
- data/lib/nats_wave/version.rb +1 -1
- data/lib/nats_wave.rb +99 -0
- metadata +3 -3
- data/lib/nats_wave/concerns/publishable.rb +0 -216
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 76f94cf28e8dfa1742a78ef25bdd1531c4424fdde3b61dfbc61daca7b361bd79
|
4
|
+
data.tar.gz: 0015c75cb71bcae3e0e5ea48c12b7953b838b3d2eb47303a5a5ecc885de5e313
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c015a2d561b28c7526a55cc8e1af696f4df70281e9d82377d878e11fa11b28181323296843484f47bdea3527ebb55881f9a96c62195323d0ebbf79d94903b4d9
|
7
|
+
data.tar.gz: 8dbcbfde92b22e044e187a10ec94d29ff8d3db227015a61bbd0b56f2622de7dba6a01db81a3fa83d45bd4b5b0b737e566434395340333b460ce6d3799cdc49da
|
data/Gemfile.lock
CHANGED
@@ -0,0 +1,93 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module NatsWave
|
4
|
+
module ActiveRecordExtension
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
def nats_publishable(options = {})
|
9
|
+
include NatsWave::NatsPublishable
|
10
|
+
class_attribute :nats_publishing_options
|
11
|
+
self.nats_publishing_options = {
|
12
|
+
enabled: true,
|
13
|
+
actions: %i[create update destroy],
|
14
|
+
skip_attributes: [],
|
15
|
+
include_associations: [],
|
16
|
+
async: true
|
17
|
+
}.merge(options)
|
18
|
+
|
19
|
+
# Register this model for publishing
|
20
|
+
NatsWave.configuration&.add_publication(name, nats_publishing_options[:actions])
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
module NatsPublishable
|
26
|
+
extend ActiveSupport::Concern
|
27
|
+
|
28
|
+
included do
|
29
|
+
after_commit :trigger_nats_wave_publish_on_create, on: :create, if: :should_publish_create?
|
30
|
+
after_commit :trigger_nats_wave_publish_on_update, on: :update, if: :should_publish_update?
|
31
|
+
after_commit :trigger_nats_wave_publish_on_destroy, on: :destroy, if: :should_publish_destroy?
|
32
|
+
end
|
33
|
+
|
34
|
+
class_methods do
|
35
|
+
def nats_wave_enabled?
|
36
|
+
nats_publishing_options[:enabled]
|
37
|
+
end
|
38
|
+
|
39
|
+
def nats_wave_actions
|
40
|
+
nats_publishing_options[:actions]
|
41
|
+
end
|
42
|
+
|
43
|
+
def nats_wave_skip_attributes
|
44
|
+
nats_publishing_options[:skip_attributes]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def should_publish_create?
|
51
|
+
should_publish_action?(:create)
|
52
|
+
end
|
53
|
+
|
54
|
+
def should_publish_update?
|
55
|
+
should_publish_action?(:update) && has_significant_changes?
|
56
|
+
end
|
57
|
+
|
58
|
+
def should_publish_destroy?
|
59
|
+
should_publish_action?(:destroy)
|
60
|
+
end
|
61
|
+
|
62
|
+
def should_publish_action?(action)
|
63
|
+
return false unless self.class.nats_wave_enabled?
|
64
|
+
return false unless self.class.nats_wave_actions.include?(action)
|
65
|
+
return false if Thread.current[:skip_nats_wave_publishing]
|
66
|
+
return false if @skip_nats_wave_publishing
|
67
|
+
|
68
|
+
# Check conditional options
|
69
|
+
return false if nats_publishing_options[:if] && !instance_eval(&nats_publishing_options[:if])
|
70
|
+
return false if nats_publishing_options[:unless] && instance_eval(&nats_publishing_options[:unless])
|
71
|
+
|
72
|
+
true
|
73
|
+
end
|
74
|
+
|
75
|
+
def has_significant_changes?
|
76
|
+
skip_attrs = self.class.nats_wave_skip_attributes.map(&:to_s)
|
77
|
+
significant_changes = previous_changes.except(*skip_attrs, 'updated_at')
|
78
|
+
significant_changes.any?
|
79
|
+
end
|
80
|
+
|
81
|
+
def trigger_nats_wave_publish_on_create
|
82
|
+
nats_wave_publish('create')
|
83
|
+
end
|
84
|
+
|
85
|
+
def trigger_nats_wave_publish_on_update
|
86
|
+
nats_wave_publish('update')
|
87
|
+
end
|
88
|
+
|
89
|
+
def trigger_nats_wave_publish_on_destroy
|
90
|
+
nats_wave_publish('destroy')
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -1,3 +1,210 @@
|
|
1
|
+
# # frozen_string_literal: true
|
2
|
+
#
|
3
|
+
# module NatsWave
|
4
|
+
# module Adapters
|
5
|
+
# class ActiveRecord
|
6
|
+
# def initialize(config)
|
7
|
+
# @config = config
|
8
|
+
# end
|
9
|
+
#
|
10
|
+
# def create_from_external(external_model, data, metadata = {})
|
11
|
+
# mapping = ModelRegistry.local_models_for(external_model)
|
12
|
+
#
|
13
|
+
# mapping.each do |local_model_name, config|
|
14
|
+
# create_or_update_local_record(local_model_name, external_model, data, config, 'create')
|
15
|
+
# end
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# def update_from_external(external_model, data, metadata = {})
|
19
|
+
# mapping = ModelRegistry.local_models_for(external_model)
|
20
|
+
#
|
21
|
+
# mapping.each do |local_model_name, config|
|
22
|
+
# create_or_update_local_record(local_model_name, external_model, data, config, 'update')
|
23
|
+
# end
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# def destroy_from_external(external_model, data, metadata = {})
|
27
|
+
# mapping = ModelRegistry.local_models_for(external_model)
|
28
|
+
#
|
29
|
+
# mapping.each do |local_model_name, config|
|
30
|
+
# destroy_local_record(local_model_name, external_model, data, config)
|
31
|
+
# end
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
# # Legacy methods for backward compatibility
|
35
|
+
# def create(model_name, data, metadata = {})
|
36
|
+
# model_class = get_model_class(model_name)
|
37
|
+
# clean_data = data.except('created_at', 'updated_at', :created_at, :updated_at)
|
38
|
+
#
|
39
|
+
# with_nats_publishing_disabled do
|
40
|
+
# record = model_class.create!(clean_data)
|
41
|
+
# NatsWave.logger.debug("Created #{model_name} with ID: #{record.id}")
|
42
|
+
# record
|
43
|
+
# end
|
44
|
+
# rescue => e
|
45
|
+
# raise DatabaseError, "Failed to create #{model_name}: #{e.message}"
|
46
|
+
# end
|
47
|
+
#
|
48
|
+
# def connected?
|
49
|
+
# return false unless defined?(::ActiveRecord)
|
50
|
+
# ::ActiveRecord::Base.connected?
|
51
|
+
# rescue
|
52
|
+
# false
|
53
|
+
# end
|
54
|
+
#
|
55
|
+
# private
|
56
|
+
#
|
57
|
+
# def create_or_update_local_record(local_model_name, external_model, external_data, config, action)
|
58
|
+
# model_class = get_model_class(local_model_name)
|
59
|
+
#
|
60
|
+
# # Transform the data using field mappings and transformations
|
61
|
+
# transformed_data = transform_external_data(external_data, config)
|
62
|
+
#
|
63
|
+
# # Find existing record based on unique fields
|
64
|
+
# existing_record = find_existing_record(model_class, transformed_data, config[:unique_fields])
|
65
|
+
#
|
66
|
+
# with_nats_publishing_disabled do
|
67
|
+
# case config[:sync_strategy]
|
68
|
+
# when :create_only
|
69
|
+
# create_record_if_not_exists(model_class, existing_record, transformed_data)
|
70
|
+
# when :update_only
|
71
|
+
# update_record_if_exists(existing_record, transformed_data) if existing_record
|
72
|
+
# when :upsert, nil
|
73
|
+
# upsert_record(model_class, existing_record, transformed_data, config)
|
74
|
+
# end
|
75
|
+
# end
|
76
|
+
#
|
77
|
+
# # Track the sync operation
|
78
|
+
# NatsWave::Metrics.track_sync_operation(
|
79
|
+
# local_model_name,
|
80
|
+
# action,
|
81
|
+
# 0, # duration - you could track this
|
82
|
+
# success: true
|
83
|
+
# )
|
84
|
+
#
|
85
|
+
# rescue => e
|
86
|
+
# NatsWave.logger.error("Failed to sync #{external_model} to #{local_model_name}: #{e.message}")
|
87
|
+
#
|
88
|
+
# NatsWave::Metrics.track_sync_operation(
|
89
|
+
# local_model_name,
|
90
|
+
# action,
|
91
|
+
# 0,
|
92
|
+
# success: false
|
93
|
+
# )
|
94
|
+
#
|
95
|
+
# raise DatabaseError, "Sync failed for #{local_model_name}: #{e.message}"
|
96
|
+
# end
|
97
|
+
#
|
98
|
+
# def transform_external_data(external_data, config)
|
99
|
+
# field_mappings = config[:field_mappings] || {}
|
100
|
+
# transformations = config[:transformations] || {}
|
101
|
+
# skip_fields = config[:skip_fields] || []
|
102
|
+
#
|
103
|
+
# transformed = {}
|
104
|
+
#
|
105
|
+
# external_data.each do |key, value|
|
106
|
+
# next if skip_fields.include?(key.to_s) || skip_fields.include?(key.to_sym)
|
107
|
+
#
|
108
|
+
# # Map field name
|
109
|
+
# local_field = field_mappings[key] || field_mappings[key.to_sym] || key
|
110
|
+
#
|
111
|
+
# # Apply transformation
|
112
|
+
# if transformations[local_field]
|
113
|
+
# transformation = transformations[local_field]
|
114
|
+
# value = case transformation
|
115
|
+
# when Proc
|
116
|
+
# transformation.arity == 1 ? transformation.call(value) : transformation.call(value, external_data)
|
117
|
+
# when Symbol
|
118
|
+
# # Would need to be called on the model instance
|
119
|
+
# value
|
120
|
+
# else
|
121
|
+
# value
|
122
|
+
# end
|
123
|
+
# end
|
124
|
+
#
|
125
|
+
# transformed[local_field] = value
|
126
|
+
# end
|
127
|
+
#
|
128
|
+
# # Remove Rails timestamp fields that might conflict
|
129
|
+
# transformed.except('created_at', 'updated_at', :created_at, :updated_at)
|
130
|
+
# end
|
131
|
+
#
|
132
|
+
# def find_existing_record(model_class, data, unique_fields)
|
133
|
+
# unique_fields ||= [:id]
|
134
|
+
#
|
135
|
+
# unique_fields.each do |field|
|
136
|
+
# value = data[field] || data[field.to_sym]
|
137
|
+
# next unless value
|
138
|
+
#
|
139
|
+
# record = model_class.find_by(field => value)
|
140
|
+
# return record if record
|
141
|
+
# end
|
142
|
+
#
|
143
|
+
# nil
|
144
|
+
# end
|
145
|
+
#
|
146
|
+
# def create_record_if_not_exists(model_class, existing_record, data)
|
147
|
+
# return existing_record if existing_record
|
148
|
+
#
|
149
|
+
# record = model_class.create!(data)
|
150
|
+
# NatsWave.logger.info("Created #{model_class.name} with ID: #{record.id}")
|
151
|
+
# record
|
152
|
+
# end
|
153
|
+
#
|
154
|
+
# def update_record_if_exists(existing_record, data)
|
155
|
+
# return unless existing_record
|
156
|
+
#
|
157
|
+
# existing_record.update!(data)
|
158
|
+
# NatsWave.logger.info("Updated #{existing_record.class.name} ID: #{existing_record.id}")
|
159
|
+
# existing_record
|
160
|
+
# end
|
161
|
+
#
|
162
|
+
# def upsert_record(model_class, existing_record, data, config)
|
163
|
+
# if existing_record
|
164
|
+
# existing_record.update!(data)
|
165
|
+
# NatsWave.logger.info("Updated #{model_class.name} ID: #{existing_record.id}")
|
166
|
+
# existing_record
|
167
|
+
# else
|
168
|
+
# record = model_class.create!(data)
|
169
|
+
# NatsWave.logger.info("Created #{model_class.name} with ID: #{record.id}")
|
170
|
+
# record
|
171
|
+
# end
|
172
|
+
# end
|
173
|
+
#
|
174
|
+
# def destroy_local_record(local_model_name, external_model, external_data, config)
|
175
|
+
# model_class = get_model_class(local_model_name)
|
176
|
+
# transformed_data = transform_external_data(external_data, config)
|
177
|
+
#
|
178
|
+
# existing_record = find_existing_record(model_class, transformed_data, config[:unique_fields])
|
179
|
+
# return unless existing_record
|
180
|
+
#
|
181
|
+
# with_nats_publishing_disabled do
|
182
|
+
# if existing_record.respond_to?(:soft_delete)
|
183
|
+
# existing_record.soft_delete
|
184
|
+
# else
|
185
|
+
# existing_record.destroy!
|
186
|
+
# end
|
187
|
+
#
|
188
|
+
# NatsWave.logger.info("Destroyed #{local_model_name} ID: #{existing_record.id}")
|
189
|
+
# end
|
190
|
+
# end
|
191
|
+
#
|
192
|
+
# def get_model_class(model_name)
|
193
|
+
# model_name.constantize
|
194
|
+
# rescue NameError
|
195
|
+
# raise DatabaseError, "Model class '#{model_name}' not found"
|
196
|
+
# end
|
197
|
+
#
|
198
|
+
# def with_nats_publishing_disabled(&block)
|
199
|
+
# Thread.current[:skip_nats_wave_publishing] = true
|
200
|
+
# yield
|
201
|
+
# ensure
|
202
|
+
# Thread.current[:skip_nats_wave_publishing] = false
|
203
|
+
# end
|
204
|
+
# end
|
205
|
+
# end
|
206
|
+
# end
|
207
|
+
|
1
208
|
# frozen_string_literal: true
|
2
209
|
|
3
210
|
module NatsWave
|