nats_wave 1.1.9 → 1.1.11
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/adapters/datadog_metrics.rb +1 -1
- data/lib/nats_wave/concerns/mappable.rb +398 -122
- data/lib/nats_wave/configuration.rb +1 -1
- data/lib/nats_wave/railtie.rb +26 -13
- data/lib/nats_wave/version.rb +1 -1
- data/lib/nats_wave.rb +15 -8
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b32810b1ec7cd1601ddb675015fb161fce6d6bb5749dd19c40b0b928a94d0f5c
|
4
|
+
data.tar.gz: 4820121ee244b4192c372f0baa5abc2533f0b676b5066e48892ebcd305571b23
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bd66a5824fb58bc9abc27ce9f86aaa6ebd9ec95eb012c146ea2016b2a167c52ff1fbc596aa353167c4d66851aee352e9c534fe46259d9b4efdfd4fca01b9ddd2
|
7
|
+
data.tar.gz: a7413067598f7e155f38bc1a7ebadef0b24ba9bb2e3f3f146fbb9c34ef9bf6b99d4102a62698bdb6bc6042e8d861e86b9927659e432c35a8f996af100b6e02f8
|
data/Gemfile.lock
CHANGED
@@ -6,173 +6,358 @@
|
|
6
6
|
# extend ActiveSupport::Concern
|
7
7
|
#
|
8
8
|
# included do
|
9
|
-
# class_attribute :
|
10
|
-
# self.nats_wave_mapping_config = {}
|
9
|
+
# class_attribute :nats_wave_subscription_config, :nats_wave_publishing_config
|
11
10
|
# self.nats_wave_subscription_config = {}
|
11
|
+
# self.nats_wave_publishing_config = {}
|
12
12
|
# end
|
13
13
|
#
|
14
14
|
# class_methods do
|
15
|
-
# # Configure
|
16
|
-
# def
|
17
|
-
# self.
|
18
|
-
#
|
19
|
-
# # Register this mapping globally
|
20
|
-
# NatsWave::ModelRegistry.register_mapping(self.name, external_models)
|
21
|
-
# end
|
15
|
+
# # Configure subscriptions with optional custom handler
|
16
|
+
# def nats_wave_subscribes_to(*subjects, **options, &block)
|
17
|
+
# Rails.logger.debug "📡 #{self.name}: Setting up subscription to subjects: #{subjects.inspect}" if defined?(Rails)
|
22
18
|
#
|
23
|
-
#
|
24
|
-
# def nats_wave_maps_from(external_model, options = {})
|
25
|
-
# Rails.logger.debug "🔄 #{self.name}: Setting up mapping from #{external_model} with options: #{options.inspect}" if defined?(Rails)
|
19
|
+
# handler = options[:handler] || block
|
26
20
|
#
|
27
|
-
#
|
21
|
+
# subscription_config = {
|
22
|
+
# subjects: subjects.flatten,
|
28
23
|
# field_mappings: options[:field_mappings] || {},
|
29
24
|
# transformations: options[:transformations] || {},
|
30
|
-
# conditions: options[:conditions] || {},
|
31
|
-
# sync_strategy: options[:sync_strategy] || :upsert,
|
32
|
-
# unique_fields: options[:unique_fields] || [:id],
|
33
25
|
# skip_fields: options[:skip_fields] || [],
|
34
|
-
#
|
35
|
-
#
|
36
|
-
#
|
26
|
+
# unique_fields: options[:unique_fields] || [:id],
|
27
|
+
# sync_strategy: options[:sync_strategy] || :upsert,
|
28
|
+
# handler: handler,
|
29
|
+
# queue_group: options[:queue_group],
|
30
|
+
# auto_sync: options[:auto_sync] != false # Default to true, can be disabled
|
37
31
|
# }
|
38
32
|
#
|
39
|
-
#
|
40
|
-
#
|
41
|
-
#
|
42
|
-
#
|
43
|
-
#
|
44
|
-
#
|
45
|
-
#
|
46
|
-
#
|
47
|
-
#
|
48
|
-
#
|
49
|
-
#
|
50
|
-
#
|
51
|
-
#
|
52
|
-
#
|
53
|
-
#
|
54
|
-
#
|
55
|
-
# Rails.logger.debug "🔄 #{self.name}: No subjects provided, skipping subscription registration" if defined?(Rails)
|
56
|
-
# end
|
33
|
+
# # Store the config
|
34
|
+
# config_key = subjects.join(',')
|
35
|
+
# self.nats_wave_subscription_config[config_key] = subscription_config
|
36
|
+
#
|
37
|
+
# # Create the subscription handler
|
38
|
+
# processed_handler = create_subscription_handler(subscription_config)
|
39
|
+
#
|
40
|
+
# # Register the subscription
|
41
|
+
# NatsWave::ModelRegistry.register_subscription(
|
42
|
+
# subjects: subjects.flatten,
|
43
|
+
# model: self.name,
|
44
|
+
# handler: processed_handler,
|
45
|
+
# queue_group: subscription_config[:queue_group]
|
46
|
+
# )
|
47
|
+
#
|
48
|
+
# Rails.logger.debug "📡 #{self.name}: Registered subscription for subjects: #{subjects}" if defined?(Rails)
|
57
49
|
# end
|
58
50
|
#
|
59
|
-
# # Configure
|
60
|
-
# def
|
61
|
-
#
|
51
|
+
# # Configure publishing with optional custom handler
|
52
|
+
# def nats_wave_publishes_to(*subjects, **options, &block)
|
53
|
+
# Rails.logger.debug "📤 #{self.name}: Setting up publishing to subjects: #{subjects.inspect}" if defined?(Rails)
|
62
54
|
#
|
63
|
-
#
|
55
|
+
# handler = options[:handler] || block
|
64
56
|
#
|
65
|
-
#
|
57
|
+
# publishing_config = {
|
66
58
|
# subjects: subjects.flatten,
|
59
|
+
# field_mappings: options[:field_mappings] || {},
|
60
|
+
# transformations: options[:transformations] || {},
|
61
|
+
# skip_fields: options[:skip_fields] || [],
|
62
|
+
# conditions: options[:conditions] || {},
|
63
|
+
# actions: options[:actions] || [:create, :update, :destroy],
|
67
64
|
# handler: handler,
|
68
|
-
#
|
69
|
-
#
|
65
|
+
# async: options[:async] != false, # Default to true
|
66
|
+
# enabled: options[:enabled] != false # Default to true
|
70
67
|
# }
|
71
68
|
#
|
72
|
-
#
|
69
|
+
# # Store the config
|
70
|
+
# config_key = subjects.join(',')
|
71
|
+
# self.nats_wave_publishing_config[config_key] = publishing_config
|
73
72
|
#
|
74
|
-
# Rails.logger.debug "
|
75
|
-
# NatsWave::ModelRegistry.register_subscription(
|
76
|
-
# subjects: subjects.flatten,
|
77
|
-
# model: self.name,
|
78
|
-
# handler: handler,
|
79
|
-
# queue_group: queue_group
|
80
|
-
# )
|
73
|
+
# Rails.logger.debug "📤 #{self.name}: Registered publishing config for subjects: #{subjects}" if defined?(Rails)
|
81
74
|
# end
|
82
75
|
#
|
83
|
-
# #
|
84
|
-
# def
|
85
|
-
#
|
76
|
+
# # Get all subscription configurations
|
77
|
+
# def nats_wave_subscription_configs
|
78
|
+
# nats_wave_subscription_config
|
79
|
+
# end
|
86
80
|
#
|
87
|
-
#
|
88
|
-
#
|
89
|
-
#
|
90
|
-
#
|
91
|
-
#
|
92
|
-
#
|
93
|
-
#
|
94
|
-
#
|
95
|
-
#
|
96
|
-
#
|
97
|
-
#
|
81
|
+
# # Get all publishing configurations
|
82
|
+
# def nats_wave_publishing_configs
|
83
|
+
# nats_wave_publishing_config
|
84
|
+
# end
|
85
|
+
#
|
86
|
+
# # Get all subjects this model subscribes to
|
87
|
+
# def nats_wave_subscribed_subjects
|
88
|
+
# nats_wave_subscription_config.values.flat_map { |config| config[:subjects] }.uniq
|
89
|
+
# end
|
90
|
+
#
|
91
|
+
# # Get all subjects this model publishes to
|
92
|
+
# def nats_wave_published_subjects
|
93
|
+
# nats_wave_publishing_config.values.flat_map { |config| config[:subjects] }.uniq
|
94
|
+
# end
|
95
|
+
#
|
96
|
+
# private
|
97
|
+
#
|
98
|
+
# # Create a subscription handler that processes data and calls custom handler or auto-sync
|
99
|
+
# def create_subscription_handler(config)
|
100
|
+
# model_class = self
|
101
|
+
#
|
102
|
+
# lambda do |message|
|
103
|
+
# begin
|
104
|
+
# Rails.logger.debug "📨 Processing subscription message for #{model_class.name}" if defined?(Rails)
|
105
|
+
#
|
106
|
+
# # Extract the raw data
|
107
|
+
# raw_data = message['data'] || {}
|
108
|
+
# model_name = message['model']
|
109
|
+
# action = message['action']
|
110
|
+
#
|
111
|
+
# # Process the data through mappings and transformations
|
112
|
+
# processed_data = model_class.process_data(raw_data, config)
|
113
|
+
#
|
114
|
+
# # If custom handler is provided, call it with model_name, action, processed_data
|
115
|
+
# if config[:handler]
|
116
|
+
# Rails.logger.debug "📨 Calling custom subscription handler" if defined?(Rails)
|
117
|
+
# config[:handler].call(model_name, action, processed_data, message)
|
118
|
+
# elsif config[:auto_sync]
|
119
|
+
# Rails.logger.debug "📨 Performing auto-sync" if defined?(Rails)
|
120
|
+
# # Default auto-sync behavior
|
121
|
+
# model_class.perform_auto_sync(model_name, action, processed_data, config)
|
122
|
+
# else
|
123
|
+
# Rails.logger.debug "📨 No handler provided and auto_sync disabled" if defined?(Rails)
|
124
|
+
# end
|
125
|
+
#
|
126
|
+
# rescue => e
|
127
|
+
# Rails.logger.error "Error in subscription handler for #{model_class.name}: #{e.message}" if defined?(Rails)
|
128
|
+
# Rails.logger.error e.backtrace.join("\n") if defined?(Rails)
|
129
|
+
# raise
|
130
|
+
# end
|
131
|
+
# end
|
132
|
+
# end
|
133
|
+
#
|
134
|
+
# # Process data through field mappings and transformations (used by both subscription and publishing)
|
135
|
+
# def process_data(raw_data, config)
|
136
|
+
# processed_data = {}
|
137
|
+
# field_mappings = config[:field_mappings]
|
138
|
+
# transformations = config[:transformations]
|
139
|
+
# skip_fields = config[:skip_fields]
|
140
|
+
#
|
141
|
+
# raw_data.each do |field, value|
|
142
|
+
# field_str = field.to_s
|
143
|
+
# field_sym = field.to_sym
|
144
|
+
#
|
145
|
+
# # Skip if in skip_fields
|
146
|
+
# next if skip_fields.include?(field_str) || skip_fields.include?(field_sym)
|
147
|
+
#
|
148
|
+
# # Apply field mapping
|
149
|
+
# mapped_field = field_mappings[field_str] ||
|
150
|
+
# field_mappings[field_sym] ||
|
151
|
+
# field
|
152
|
+
#
|
153
|
+
# # Apply transformation if any
|
154
|
+
# transformed_value = apply_transformation(value, mapped_field, transformations, raw_data)
|
155
|
+
#
|
156
|
+
# processed_data[mapped_field.to_s] = transformed_value
|
98
157
|
# end
|
99
158
|
#
|
100
|
-
#
|
159
|
+
# processed_data
|
101
160
|
# end
|
102
161
|
#
|
103
|
-
# #
|
104
|
-
# def
|
105
|
-
#
|
162
|
+
# # Apply transformations to field values
|
163
|
+
# def apply_transformation(value, field, transformations, full_record)
|
164
|
+
# transformation = transformations[field] || transformations[field.to_sym]
|
165
|
+
#
|
166
|
+
# case transformation
|
167
|
+
# when Proc
|
168
|
+
# if transformation.arity == 2 || transformation.arity < 0
|
169
|
+
# transformation.call(value, full_record)
|
170
|
+
# else
|
171
|
+
# transformation.call(value)
|
172
|
+
# end
|
173
|
+
# when Symbol
|
174
|
+
# if self.respond_to?(transformation, true)
|
175
|
+
# self.send(transformation, value)
|
176
|
+
# elsif value.respond_to?(transformation)
|
177
|
+
# value.send(transformation)
|
178
|
+
# else
|
179
|
+
# Rails.logger.warn "Transformation method #{transformation} not found" if defined?(Rails)
|
180
|
+
# value
|
181
|
+
# end
|
182
|
+
# else
|
183
|
+
# value
|
184
|
+
# end
|
106
185
|
# end
|
107
186
|
#
|
108
|
-
# #
|
109
|
-
# def
|
110
|
-
#
|
187
|
+
# # Default auto-sync behavior
|
188
|
+
# def perform_auto_sync(model_name, action, processed_data, config)
|
189
|
+
# unique_fields = config[:unique_fields]
|
190
|
+
# sync_strategy = config[:sync_strategy]
|
191
|
+
#
|
192
|
+
# case action.to_s.downcase
|
193
|
+
# when 'create', 'created'
|
194
|
+
# handle_auto_create(processed_data, unique_fields, sync_strategy)
|
195
|
+
# when 'update', 'updated'
|
196
|
+
# handle_auto_update(processed_data, unique_fields, sync_strategy)
|
197
|
+
# when 'delete', 'deleted', 'destroy', 'destroyed'
|
198
|
+
# handle_auto_delete(processed_data, unique_fields)
|
199
|
+
# else
|
200
|
+
# Rails.logger.warn "Unknown action for auto-sync: #{action}" if defined?(Rails)
|
201
|
+
# end
|
111
202
|
# end
|
112
203
|
#
|
113
|
-
#
|
114
|
-
#
|
115
|
-
#
|
204
|
+
# def handle_auto_create(data, unique_fields, sync_strategy)
|
205
|
+
# case sync_strategy
|
206
|
+
# when :upsert
|
207
|
+
# existing = find_by_unique_fields(data, unique_fields)
|
208
|
+
# if existing
|
209
|
+
# existing.update!(data)
|
210
|
+
# Rails.logger.info "✅ Updated existing #{self.name}: #{existing.id}" if defined?(Rails)
|
211
|
+
# else
|
212
|
+
# record = self.create!(data)
|
213
|
+
# Rails.logger.info "✅ Created new #{self.name}: #{record.id}" if defined?(Rails)
|
214
|
+
# end
|
215
|
+
# when :create_only
|
216
|
+
# record = self.create!(data)
|
217
|
+
# Rails.logger.info "✅ Created new #{self.name}: #{record.id}" if defined?(Rails)
|
218
|
+
# end
|
219
|
+
# rescue => e
|
220
|
+
# Rails.logger.error "❌ Failed to create #{self.name}: #{e.message}" if defined?(Rails)
|
221
|
+
# raise
|
116
222
|
# end
|
117
223
|
#
|
118
|
-
#
|
119
|
-
#
|
120
|
-
#
|
224
|
+
# def handle_auto_update(data, unique_fields, sync_strategy)
|
225
|
+
# existing = find_by_unique_fields(data, unique_fields)
|
226
|
+
# if existing
|
227
|
+
# existing.update!(data)
|
228
|
+
# Rails.logger.info "✅ Updated #{self.name}: #{existing.id}" if defined?(Rails)
|
229
|
+
# elsif sync_strategy == :upsert
|
230
|
+
# record = self.create!(data)
|
231
|
+
# Rails.logger.info "✅ Created new #{self.name} during update: #{record.id}" if defined?(Rails)
|
232
|
+
# else
|
233
|
+
# Rails.logger.warn "⚠️ Record not found for update: #{data.slice(*unique_fields.map(&:to_s))}" if defined?(Rails)
|
234
|
+
# end
|
235
|
+
# rescue => e
|
236
|
+
# Rails.logger.error "❌ Failed to update #{self.name}: #{e.message}" if defined?(Rails)
|
237
|
+
# raise
|
238
|
+
# end
|
121
239
|
#
|
122
|
-
#
|
123
|
-
#
|
124
|
-
#
|
240
|
+
# def handle_auto_delete(data, unique_fields)
|
241
|
+
# existing = find_by_unique_fields(data, unique_fields)
|
242
|
+
# if existing
|
243
|
+
# existing.destroy!
|
244
|
+
# Rails.logger.info "✅ Deleted #{self.name}: #{existing.id}" if defined?(Rails)
|
245
|
+
# else
|
246
|
+
# Rails.logger.warn "⚠️ Record not found for deletion: #{data.slice(*unique_fields.map(&:to_s))}" if defined?(Rails)
|
125
247
|
# end
|
248
|
+
# rescue => e
|
249
|
+
# Rails.logger.error "❌ Failed to delete #{self.name}: #{e.message}" if defined?(Rails)
|
250
|
+
# raise
|
251
|
+
# end
|
126
252
|
#
|
127
|
-
#
|
128
|
-
#
|
129
|
-
#
|
253
|
+
# def find_by_unique_fields(data, unique_fields)
|
254
|
+
# conditions = {}
|
255
|
+
# unique_fields.each do |field|
|
256
|
+
# field_str = field.to_s
|
257
|
+
# if data.key?(field_str)
|
258
|
+
# conditions[field] = data[field_str]
|
259
|
+
# elsif data.key?(field.to_sym)
|
260
|
+
# conditions[field] = data[field.to_sym]
|
261
|
+
# end
|
130
262
|
# end
|
131
263
|
#
|
132
|
-
#
|
264
|
+
# return nil if conditions.empty?
|
265
|
+
# self.find_by(conditions)
|
133
266
|
# end
|
134
267
|
# end
|
135
268
|
#
|
136
|
-
# # Instance methods
|
137
|
-
# def
|
138
|
-
#
|
139
|
-
# return attributes unless mapping
|
140
|
-
#
|
141
|
-
# mapped_attrs = {}
|
142
|
-
# field_mappings = mapping[:field_mappings] || {}
|
143
|
-
# skip_fields = mapping[:skip_fields] || []
|
269
|
+
# # Instance methods for publishing
|
270
|
+
# def nats_wave_publish(action = nil)
|
271
|
+
# action ||= determine_action_from_context
|
144
272
|
#
|
145
|
-
#
|
146
|
-
# next
|
273
|
+
# self.class.nats_wave_publishing_configs.each do |subjects_key, config|
|
274
|
+
# next unless config[:enabled]
|
275
|
+
# next unless config[:actions].include?(action.to_sym)
|
147
276
|
#
|
148
|
-
#
|
149
|
-
#
|
150
|
-
#
|
277
|
+
# # Check conditions (only for publishing)
|
278
|
+
# if config[:conditions].any? && !evaluate_conditions(config[:conditions])
|
279
|
+
# Rails.logger.debug "📤 Skipping publish - conditions not met" if defined?(Rails)
|
280
|
+
# next
|
281
|
+
# end
|
151
282
|
#
|
152
|
-
#
|
153
|
-
#
|
283
|
+
# # Get the raw data (current model attributes)
|
284
|
+
# raw_data = get_raw_attributes
|
154
285
|
#
|
155
|
-
#
|
156
|
-
#
|
157
|
-
# unique_fields = mapping&.dig(:unique_fields) || [:id]
|
286
|
+
# # Process the data through mappings and transformations (same as subscription)
|
287
|
+
# processed_data = self.class.process_data(raw_data, config)
|
158
288
|
#
|
159
|
-
#
|
289
|
+
# # If custom handler is provided, call it
|
290
|
+
# if config[:handler]
|
291
|
+
# Rails.logger.debug "📤 Calling custom publishing handler" if defined?(Rails)
|
292
|
+
# config[:handler].call(self.class.name, action, processed_data, self)
|
293
|
+
# else
|
294
|
+
# # Default publishing behavior - publish the processed data
|
295
|
+
# config[:subjects].each do |subject|
|
296
|
+
# NatsWave.client.publish(
|
297
|
+
# subject: subject,
|
298
|
+
# model: self.class.name,
|
299
|
+
# action: action,
|
300
|
+
# data: processed_data, # This is the processed data, not raw attributes
|
301
|
+
# metadata: build_publishing_metadata
|
302
|
+
# )
|
303
|
+
#
|
304
|
+
# Rails.logger.info "📤 Published #{self.class.name}##{id} to #{subject} (#{action})" if defined?(Rails)
|
305
|
+
# end
|
306
|
+
# end
|
307
|
+
# end
|
308
|
+
# rescue StandardError => e
|
309
|
+
# Rails.logger.error("Failed to publish: #{e.message}") if defined?(Rails)
|
310
|
+
# # Don't re-raise to avoid breaking transactions
|
160
311
|
# end
|
161
312
|
#
|
162
313
|
# private
|
163
314
|
#
|
164
|
-
# def
|
165
|
-
#
|
315
|
+
# def determine_action_from_context
|
316
|
+
# # Try to determine action from ActiveRecord context
|
317
|
+
# if defined?(ActiveRecord) && is_a?(ActiveRecord::Base)
|
318
|
+
# return 'create' if previously_new_record?
|
319
|
+
# return 'update' if saved_changes.any?
|
320
|
+
# return 'destroy' if destroyed?
|
321
|
+
# end
|
166
322
|
#
|
167
|
-
#
|
168
|
-
#
|
169
|
-
#
|
170
|
-
#
|
171
|
-
#
|
172
|
-
#
|
173
|
-
#
|
323
|
+
# 'update' # Default fallback
|
324
|
+
# end
|
325
|
+
#
|
326
|
+
# def get_raw_attributes
|
327
|
+
# # Get attributes (works for both ActiveRecord and other objects)
|
328
|
+
# if respond_to?(:attributes)
|
329
|
+
# attributes
|
330
|
+
# else
|
331
|
+
# # For non-ActiveRecord objects, get instance variables
|
332
|
+
# instance_variables.map { |v| [v.to_s.delete('@'), instance_variable_get(v)] }.to_h
|
174
333
|
# end
|
175
334
|
# end
|
335
|
+
#
|
336
|
+
# def evaluate_conditions(conditions)
|
337
|
+
# conditions.all? do |condition, expected_value|
|
338
|
+
# case condition
|
339
|
+
# when Proc
|
340
|
+
# condition.call(self)
|
341
|
+
# when Symbol, String
|
342
|
+
# if respond_to?(condition, true)
|
343
|
+
# result = send(condition)
|
344
|
+
# expected_value.nil? ? result : result == expected_value
|
345
|
+
# else
|
346
|
+
# false
|
347
|
+
# end
|
348
|
+
# else
|
349
|
+
# false
|
350
|
+
# end
|
351
|
+
# end
|
352
|
+
# end
|
353
|
+
#
|
354
|
+
# def build_publishing_metadata
|
355
|
+
# {
|
356
|
+
# source_model: self.class.name,
|
357
|
+
# source_id: respond_to?(:id) ? id : object_id,
|
358
|
+
# published_at: Time.current.iso8601
|
359
|
+
# }
|
360
|
+
# end
|
176
361
|
# end
|
177
362
|
# end
|
178
363
|
# end
|
@@ -227,7 +412,7 @@ module NatsWave
|
|
227
412
|
Rails.logger.debug "📡 #{self.name}: Registered subscription for subjects: #{subjects}" if defined?(Rails)
|
228
413
|
end
|
229
414
|
|
230
|
-
# Configure publishing with optional custom handler
|
415
|
+
# Configure publishing with optional custom handler AND auto-publishing callbacks
|
231
416
|
def nats_wave_publishes_to(*subjects, **options, &block)
|
232
417
|
Rails.logger.debug "📤 #{self.name}: Setting up publishing to subjects: #{subjects.inspect}" if defined?(Rails)
|
233
418
|
|
@@ -249,6 +434,9 @@ module NatsWave
|
|
249
434
|
config_key = subjects.join(',')
|
250
435
|
self.nats_wave_publishing_config[config_key] = publishing_config
|
251
436
|
|
437
|
+
# Add ActiveRecord callbacks for auto-publishing
|
438
|
+
setup_publishing_callbacks(publishing_config)
|
439
|
+
|
252
440
|
Rails.logger.debug "📤 #{self.name}: Registered publishing config for subjects: #{subjects}" if defined?(Rails)
|
253
441
|
end
|
254
442
|
|
@@ -274,6 +462,26 @@ module NatsWave
|
|
274
462
|
|
275
463
|
private
|
276
464
|
|
465
|
+
# Setup ActiveRecord callbacks for auto-publishing
|
466
|
+
def setup_publishing_callbacks(config)
|
467
|
+
# Only add callbacks if we're in ActiveRecord and actions are specified
|
468
|
+
return unless defined?(ActiveRecord::Base) && self < ActiveRecord::Base
|
469
|
+
|
470
|
+
actions = config[:actions]
|
471
|
+
|
472
|
+
if actions.include?(:create)
|
473
|
+
after_commit :trigger_nats_wave_auto_publish_on_create, on: :create
|
474
|
+
end
|
475
|
+
|
476
|
+
if actions.include?(:update)
|
477
|
+
after_commit :trigger_nats_wave_auto_publish_on_update, on: :update
|
478
|
+
end
|
479
|
+
|
480
|
+
if actions.include?(:destroy)
|
481
|
+
after_commit :trigger_nats_wave_auto_publish_on_destroy, on: :destroy
|
482
|
+
end
|
483
|
+
end
|
484
|
+
|
277
485
|
# Create a subscription handler that processes data and calls custom handler or auto-sync
|
278
486
|
def create_subscription_handler(config)
|
279
487
|
model_class = self
|
@@ -288,7 +496,7 @@ module NatsWave
|
|
288
496
|
action = message['action']
|
289
497
|
|
290
498
|
# Process the data through mappings and transformations
|
291
|
-
processed_data =
|
499
|
+
processed_data = process_subscription_data(raw_data, config)
|
292
500
|
|
293
501
|
# If custom handler is provided, call it with model_name, action, processed_data
|
294
502
|
if config[:handler]
|
@@ -310,8 +518,8 @@ module NatsWave
|
|
310
518
|
end
|
311
519
|
end
|
312
520
|
|
313
|
-
# Process data through field mappings and transformations (
|
314
|
-
def
|
521
|
+
# Process subscription data through field mappings and transformations (CLASS METHOD)
|
522
|
+
def process_subscription_data(raw_data, config)
|
315
523
|
processed_data = {}
|
316
524
|
field_mappings = config[:field_mappings]
|
317
525
|
transformations = config[:transformations]
|
@@ -338,7 +546,7 @@ module NatsWave
|
|
338
546
|
processed_data
|
339
547
|
end
|
340
548
|
|
341
|
-
# Apply transformations to field values
|
549
|
+
# Apply transformations to field values (CLASS METHOD)
|
342
550
|
def apply_transformation(value, field, transformations, full_record)
|
343
551
|
transformation = transformations[field] || transformations[field.to_sym]
|
344
552
|
|
@@ -363,7 +571,7 @@ module NatsWave
|
|
363
571
|
end
|
364
572
|
end
|
365
573
|
|
366
|
-
# Default auto-sync behavior
|
574
|
+
# Default auto-sync behavior (CLASS METHOD)
|
367
575
|
def perform_auto_sync(model_name, action, processed_data, config)
|
368
576
|
unique_fields = config[:unique_fields]
|
369
577
|
sync_strategy = config[:sync_strategy]
|
@@ -462,8 +670,8 @@ module NatsWave
|
|
462
670
|
# Get the raw data (current model attributes)
|
463
671
|
raw_data = get_raw_attributes
|
464
672
|
|
465
|
-
# Process the data through mappings and transformations
|
466
|
-
processed_data =
|
673
|
+
# Process the data through mappings and transformations
|
674
|
+
processed_data = process_publishing_data(raw_data, config)
|
467
675
|
|
468
676
|
# If custom handler is provided, call it
|
469
677
|
if config[:handler]
|
@@ -489,6 +697,22 @@ module NatsWave
|
|
489
697
|
# Don't re-raise to avoid breaking transactions
|
490
698
|
end
|
491
699
|
|
700
|
+
# Auto-publishing callback methods
|
701
|
+
def trigger_nats_wave_auto_publish_on_create
|
702
|
+
Rails.logger.debug "🚀 Auto-publishing on create" if defined?(Rails)
|
703
|
+
nats_wave_publish('create')
|
704
|
+
end
|
705
|
+
|
706
|
+
def trigger_nats_wave_auto_publish_on_update
|
707
|
+
Rails.logger.debug "🚀 Auto-publishing on update" if defined?(Rails)
|
708
|
+
nats_wave_publish('update')
|
709
|
+
end
|
710
|
+
|
711
|
+
def trigger_nats_wave_auto_publish_on_destroy
|
712
|
+
Rails.logger.debug "🚀 Auto-publishing on destroy" if defined?(Rails)
|
713
|
+
nats_wave_publish('destroy')
|
714
|
+
end
|
715
|
+
|
492
716
|
private
|
493
717
|
|
494
718
|
def determine_action_from_context
|
@@ -512,6 +736,58 @@ module NatsWave
|
|
512
736
|
end
|
513
737
|
end
|
514
738
|
|
739
|
+
# Process publishing data through field mappings and transformations (INSTANCE METHOD)
|
740
|
+
def process_publishing_data(raw_data, config)
|
741
|
+
processed_data = {}
|
742
|
+
field_mappings = config[:field_mappings]
|
743
|
+
transformations = config[:transformations]
|
744
|
+
skip_fields = config[:skip_fields]
|
745
|
+
|
746
|
+
raw_data.each do |field, value|
|
747
|
+
field_str = field.to_s
|
748
|
+
field_sym = field.to_sym
|
749
|
+
|
750
|
+
# Skip if in skip_fields
|
751
|
+
next if skip_fields.include?(field_str) || skip_fields.include?(field_sym)
|
752
|
+
|
753
|
+
# Apply field mapping (local -> external)
|
754
|
+
mapped_field = field_mappings[field_str] ||
|
755
|
+
field_mappings[field_sym] ||
|
756
|
+
field
|
757
|
+
|
758
|
+
# Apply transformation if any
|
759
|
+
transformed_value = apply_publishing_transformation(value, mapped_field, transformations, raw_data)
|
760
|
+
|
761
|
+
processed_data[mapped_field.to_s] = transformed_value
|
762
|
+
end
|
763
|
+
|
764
|
+
processed_data
|
765
|
+
end
|
766
|
+
|
767
|
+
def apply_publishing_transformation(value, field, transformations, full_record)
|
768
|
+
transformation = transformations[field] || transformations[field.to_sym]
|
769
|
+
|
770
|
+
case transformation
|
771
|
+
when Proc
|
772
|
+
if transformation.arity == 2 || transformation.arity < 0
|
773
|
+
transformation.call(value, full_record)
|
774
|
+
else
|
775
|
+
transformation.call(value)
|
776
|
+
end
|
777
|
+
when Symbol
|
778
|
+
if self.respond_to?(transformation, true)
|
779
|
+
self.send(transformation, value)
|
780
|
+
elsif value.respond_to?(transformation)
|
781
|
+
value.send(transformation)
|
782
|
+
else
|
783
|
+
Rails.logger.warn "Publishing transformation method #{transformation} not found" if defined?(Rails)
|
784
|
+
value
|
785
|
+
end
|
786
|
+
else
|
787
|
+
value
|
788
|
+
end
|
789
|
+
end
|
790
|
+
|
515
791
|
def evaluate_conditions(conditions)
|
516
792
|
conditions.all? do |condition, expected_value|
|
517
793
|
case condition
|
@@ -18,7 +18,7 @@ module NatsWave
|
|
18
18
|
def initialize(options = {})
|
19
19
|
@nats_url = ENV['NATS_URL'] || "nats://localhost:4222"
|
20
20
|
@service_name = ENV['NATS_SERVICE_NAME'] || "purplewave"
|
21
|
-
@version = ENV['NATS_SERVICE_VERSION'] || "1.1.
|
21
|
+
@version = ENV['NATS_SERVICE_VERSION'] || "1.1.11"
|
22
22
|
@instance_id = ENV['NATS_INSTANCE_ID'] || Socket.gethostname
|
23
23
|
@database_url = ENV['NATS_DATABASE_URL'] || nil
|
24
24
|
@connection_pool_size = (ENV['NATS_CONNECTION_POOL_SIZE'] || 10).to_i
|
data/lib/nats_wave/railtie.rb
CHANGED
@@ -77,12 +77,13 @@
|
|
77
77
|
# end
|
78
78
|
# end
|
79
79
|
#
|
80
|
-
# initializer "nats_wave.active_record" do
|
81
|
-
#
|
82
|
-
#
|
83
|
-
#
|
84
|
-
#
|
85
|
-
# end
|
80
|
+
# # initializer "nats_wave.active_record" do
|
81
|
+
# # ActiveSupport.on_load(:active_record) do
|
82
|
+
# # # Load the ActiveRecord extension (this replaces the old publishable)
|
83
|
+
# # require 'nats_wave/adapters/active_record'
|
84
|
+
# # include NatsWave::ActiveRecord
|
85
|
+
# # end
|
86
|
+
# # end
|
86
87
|
#
|
87
88
|
# rake_tasks do
|
88
89
|
# load "tasks/nats_wave.rake"
|
@@ -101,6 +102,12 @@
|
|
101
102
|
# begin
|
102
103
|
# NatsWave.client
|
103
104
|
# NatsWave.logger.info "NatsWave client initialized"
|
105
|
+
#
|
106
|
+
# # Start subscriber if enabled
|
107
|
+
# if NatsWave.configuration&.subscription_enabled
|
108
|
+
# NatsWave.client.start_subscriber
|
109
|
+
# NatsWave.logger.info "NatsWave subscriber started"
|
110
|
+
# end
|
104
111
|
# rescue => e
|
105
112
|
# NatsWave.logger.error "Failed to initialize NatsWave client: #{e.message}"
|
106
113
|
# end
|
@@ -190,13 +197,19 @@ module NatsWave
|
|
190
197
|
end
|
191
198
|
end
|
192
199
|
|
193
|
-
#
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
+
# Load extensions before ActiveRecord loads models
|
201
|
+
initializer "nats_wave.load_extensions", before: :load_config_initializers do
|
202
|
+
# Load the extensions early so they're available when models are loaded
|
203
|
+
require 'nats_wave/active_record_extension'
|
204
|
+
require 'nats_wave/concerns/mappable'
|
205
|
+
end
|
206
|
+
|
207
|
+
initializer "nats_wave.active_record" do
|
208
|
+
ActiveSupport.on_load(:active_record) do
|
209
|
+
# The modules are already loaded, just include them
|
210
|
+
include NatsWave::ActiveRecordExtension
|
211
|
+
end
|
212
|
+
end
|
200
213
|
|
201
214
|
rake_tasks do
|
202
215
|
load "tasks/nats_wave.rake"
|
data/lib/nats_wave/version.rb
CHANGED
data/lib/nats_wave.rb
CHANGED
@@ -108,29 +108,36 @@ require 'securerandom'
|
|
108
108
|
require_relative "nats_wave/version"
|
109
109
|
require_relative "nats_wave/errors"
|
110
110
|
require_relative "nats_wave/configuration"
|
111
|
-
|
112
|
-
|
113
|
-
require_relative "nats_wave/subscriber"
|
111
|
+
|
112
|
+
# Load core components first
|
114
113
|
require_relative "nats_wave/message_transformer"
|
115
114
|
require_relative "nats_wave/model_mapper"
|
116
115
|
require_relative "nats_wave/model_registry"
|
117
|
-
require_relative "nats_wave/concerns/mappable"
|
118
|
-
require_relative "nats_wave/adapters/datadog_metrics"
|
119
116
|
require_relative "nats_wave/database_connector"
|
120
117
|
require_relative "nats_wave/schema_registry"
|
121
118
|
require_relative "nats_wave/dead_letter_queue"
|
122
119
|
require_relative "nats_wave/metrics"
|
123
120
|
|
124
|
-
#
|
121
|
+
# Load middleware
|
125
122
|
require_relative "nats_wave/middleware/base"
|
126
123
|
require_relative "nats_wave/middleware/authentication"
|
127
124
|
require_relative "nats_wave/middleware/validation"
|
128
125
|
require_relative "nats_wave/middleware/logging"
|
129
126
|
|
130
|
-
#
|
127
|
+
# Load adapters
|
128
|
+
require_relative "nats_wave/adapters/datadog_metrics"
|
131
129
|
require_relative "nats_wave/adapters/active_record"
|
132
130
|
|
133
|
-
#
|
131
|
+
# Load NATS components
|
132
|
+
require_relative "nats_wave/publisher"
|
133
|
+
require_relative "nats_wave/subscriber"
|
134
|
+
require_relative "nats_wave/client"
|
135
|
+
|
136
|
+
# Load ActiveRecord extensions early (before models are loaded)
|
137
|
+
require_relative "nats_wave/active_record_extension"
|
138
|
+
require_relative "nats_wave/concerns/mappable"
|
139
|
+
|
140
|
+
# Rails integration (loads last)
|
134
141
|
require_relative "nats_wave/railtie" if defined?(Rails)
|
135
142
|
|
136
143
|
module NatsWave
|