nats_wave 1.1.13 → 1.1.15
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/.idea/nats_wave.iml +5 -5
- data/Gemfile.lock +1 -1
- data/README.md +934 -1246
- data/lib/generators/nats_wave/templates/initializer.rb +14 -14
- data/lib/nats_wave/adapters/datadog_metrics.rb +1 -1
- data/lib/nats_wave/auto_registration.rb +10 -10
- data/lib/nats_wave/client.rb +0 -764
- data/lib/nats_wave/concerns/mappable.rb +380 -681
- data/lib/nats_wave/configuration.rb +1 -1
- data/lib/nats_wave/metrics.rb +3 -3
- data/lib/nats_wave/model_registry.rb +3 -3
- data/lib/nats_wave/railtie.rb +2 -2
- data/lib/nats_wave/subscriber.rb +2 -2
- data/lib/nats_wave/version.rb +1 -1
- data/lib/nats_wave.rb +0 -99
- metadata +2 -2
@@ -1,367 +1,3 @@
|
|
1
|
-
# # # # frozen_string_literal: true
|
2
|
-
# # #
|
3
|
-
# # # module NatsWave
|
4
|
-
# # # module Concerns
|
5
|
-
# # # module Mappable
|
6
|
-
# # # extend ActiveSupport::Concern
|
7
|
-
# # #
|
8
|
-
# # # included do
|
9
|
-
# # # class_attribute :nats_wave_subscription_config, :nats_wave_publishing_config
|
10
|
-
# # # self.nats_wave_subscription_config = {}
|
11
|
-
# # # self.nats_wave_publishing_config = {}
|
12
|
-
# # # end
|
13
|
-
# # #
|
14
|
-
# # # class_methods do
|
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)
|
18
|
-
# # #
|
19
|
-
# # # handler = options[:handler] || block
|
20
|
-
# # #
|
21
|
-
# # # subscription_config = {
|
22
|
-
# # # subjects: subjects.flatten,
|
23
|
-
# # # field_mappings: options[:field_mappings] || {},
|
24
|
-
# # # transformations: options[:transformations] || {},
|
25
|
-
# # # skip_fields: options[:skip_fields] || [],
|
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
|
31
|
-
# # # }
|
32
|
-
# # #
|
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)
|
49
|
-
# # # end
|
50
|
-
# # #
|
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)
|
54
|
-
# # #
|
55
|
-
# # # handler = options[:handler] || block
|
56
|
-
# # #
|
57
|
-
# # # publishing_config = {
|
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],
|
64
|
-
# # # handler: handler,
|
65
|
-
# # # async: options[:async] != false, # Default to true
|
66
|
-
# # # enabled: options[:enabled] != false # Default to true
|
67
|
-
# # # }
|
68
|
-
# # #
|
69
|
-
# # # # Store the config
|
70
|
-
# # # config_key = subjects.join(',')
|
71
|
-
# # # self.nats_wave_publishing_config[config_key] = publishing_config
|
72
|
-
# # #
|
73
|
-
# # # Rails.logger.debug "📤 #{self.name}: Registered publishing config for subjects: #{subjects}" if defined?(Rails)
|
74
|
-
# # # end
|
75
|
-
# # #
|
76
|
-
# # # # Get all subscription configurations
|
77
|
-
# # # def nats_wave_subscription_configs
|
78
|
-
# # # nats_wave_subscription_config
|
79
|
-
# # # end
|
80
|
-
# # #
|
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
|
157
|
-
# # # end
|
158
|
-
# # #
|
159
|
-
# # # processed_data
|
160
|
-
# # # end
|
161
|
-
# # #
|
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
|
185
|
-
# # # end
|
186
|
-
# # #
|
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
|
202
|
-
# # # end
|
203
|
-
# # #
|
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
|
222
|
-
# # # end
|
223
|
-
# # #
|
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
|
239
|
-
# # #
|
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)
|
247
|
-
# # # end
|
248
|
-
# # # rescue => e
|
249
|
-
# # # Rails.logger.error "❌ Failed to delete #{self.name}: #{e.message}" if defined?(Rails)
|
250
|
-
# # # raise
|
251
|
-
# # # end
|
252
|
-
# # #
|
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
|
262
|
-
# # # end
|
263
|
-
# # #
|
264
|
-
# # # return nil if conditions.empty?
|
265
|
-
# # # self.find_by(conditions)
|
266
|
-
# # # end
|
267
|
-
# # # end
|
268
|
-
# # #
|
269
|
-
# # # # Instance methods for publishing
|
270
|
-
# # # def nats_wave_publish(action = nil)
|
271
|
-
# # # action ||= determine_action_from_context
|
272
|
-
# # #
|
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)
|
276
|
-
# # #
|
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
|
282
|
-
# # #
|
283
|
-
# # # # Get the raw data (current model attributes)
|
284
|
-
# # # raw_data = get_raw_attributes
|
285
|
-
# # #
|
286
|
-
# # # # Process the data through mappings and transformations (same as subscription)
|
287
|
-
# # # processed_data = self.class.process_data(raw_data, config)
|
288
|
-
# # #
|
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
|
311
|
-
# # # end
|
312
|
-
# # #
|
313
|
-
# # # private
|
314
|
-
# # #
|
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
|
322
|
-
# # #
|
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
|
333
|
-
# # # end
|
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
|
361
|
-
# # # end
|
362
|
-
# # # end
|
363
|
-
# # # end
|
364
|
-
# #
|
365
1
|
# # # frozen_string_literal: true
|
366
2
|
# #
|
367
3
|
# # module NatsWave
|
@@ -378,9 +14,10 @@
|
|
378
14
|
# # class_methods do
|
379
15
|
# # # Configure subscriptions with optional custom handler
|
380
16
|
# # def nats_wave_subscribes_to(*subjects, **options, &block)
|
381
|
-
# #
|
17
|
+
# # NatsWave.logger.debug "📡 #{self.name}: Setting up subscription to subjects: #{subjects.inspect}"
|
382
18
|
# #
|
383
|
-
# #
|
19
|
+
# # # Custom handler is now optional and runs AFTER default behavior
|
20
|
+
# # custom_handler = options[:handler] || block
|
384
21
|
# #
|
385
22
|
# # subscription_config = {
|
386
23
|
# # subjects: subjects.flatten,
|
@@ -389,7 +26,7 @@
|
|
389
26
|
# # skip_fields: options[:skip_fields] || [],
|
390
27
|
# # unique_fields: options[:unique_fields] || [:id],
|
391
28
|
# # sync_strategy: options[:sync_strategy] || :upsert,
|
392
|
-
# #
|
29
|
+
# # custom_handler: custom_handler, # Renamed to be clear it's optional
|
393
30
|
# # queue_group: options[:queue_group],
|
394
31
|
# # auto_sync: options[:auto_sync] != false # Default to true, can be disabled
|
395
32
|
# # }
|
@@ -409,14 +46,15 @@
|
|
409
46
|
# # queue_group: subscription_config[:queue_group]
|
410
47
|
# # )
|
411
48
|
# #
|
412
|
-
# #
|
49
|
+
# # NatsWave.logger.debug "📡 #{self.name}: Registered subscription for subjects: #{subjects}"
|
413
50
|
# # end
|
414
51
|
# #
|
415
52
|
# # # Configure publishing with optional custom handler AND auto-publishing callbacks
|
416
53
|
# # def nats_wave_publishes_to(*subjects, **options, &block)
|
417
|
-
# #
|
54
|
+
# # NatsWave.logger.debug "📤 #{self.name}: Setting up publishing to subjects: #{subjects.inspect}"
|
418
55
|
# #
|
419
|
-
# #
|
56
|
+
# # # Custom handler is now optional and runs AFTER default behavior
|
57
|
+
# # custom_handler = options[:handler] || block
|
420
58
|
# #
|
421
59
|
# # publishing_config = {
|
422
60
|
# # subjects: subjects.flatten,
|
@@ -425,19 +63,20 @@
|
|
425
63
|
# # skip_fields: options[:skip_fields] || [],
|
426
64
|
# # conditions: options[:conditions] || {},
|
427
65
|
# # actions: options[:actions] || [:create, :update, :destroy],
|
428
|
-
# #
|
66
|
+
# # custom_handler: custom_handler, # Renamed to be clear it's optional
|
429
67
|
# # async: options[:async] != false, # Default to true
|
430
|
-
# # enabled: options[:enabled] != false # Default to true
|
68
|
+
# # enabled: options[:enabled] != false, # Default to true
|
69
|
+
# # only_mapped_fields: options[:only_mapped_fields] != false, # Default to true
|
431
70
|
# # }
|
432
71
|
# #
|
433
72
|
# # # Store the config
|
434
73
|
# # config_key = subjects.join(',')
|
435
74
|
# # self.nats_wave_publishing_config[config_key] = publishing_config
|
436
75
|
# #
|
437
|
-
# # # Add ActiveRecord callbacks for auto-publishing
|
438
|
-
# #
|
76
|
+
# # # Add ActiveRecord callbacks for auto-publishing (only once per model)
|
77
|
+
# # setup_publishing_callbacks_once
|
439
78
|
# #
|
440
|
-
# #
|
79
|
+
# # NatsWave.logger.debug "📤 #{self.name}: Registered publishing config for subjects: #{subjects}"
|
441
80
|
# # end
|
442
81
|
# #
|
443
82
|
# # # Get all subscription configurations
|
@@ -450,45 +89,29 @@
|
|
450
89
|
# # nats_wave_publishing_config
|
451
90
|
# # end
|
452
91
|
# #
|
453
|
-
# # # Get all subjects this model subscribes to
|
454
|
-
# # def nats_wave_subscribed_subjects
|
455
|
-
# # nats_wave_subscription_config.values.flat_map { |config| config[:subjects] }.uniq
|
456
|
-
# # end
|
457
|
-
# #
|
458
|
-
# # # Get all subjects this model publishes to
|
459
|
-
# # def nats_wave_published_subjects
|
460
|
-
# # nats_wave_publishing_config.values.flat_map { |config| config[:subjects] }.uniq
|
461
|
-
# # end
|
462
|
-
# #
|
463
92
|
# # private
|
464
93
|
# #
|
465
|
-
# # # Setup ActiveRecord callbacks for auto-publishing
|
466
|
-
# # def
|
467
|
-
# # # Only add callbacks if we're in ActiveRecord and
|
94
|
+
# # # Setup ActiveRecord callbacks for auto-publishing (only once per model)
|
95
|
+
# # def setup_publishing_callbacks_once
|
96
|
+
# # # Only add callbacks if we're in ActiveRecord and haven't added them yet
|
468
97
|
# # return unless defined?(ActiveRecord::Base) && self < ActiveRecord::Base
|
98
|
+
# # return if @nats_wave_callbacks_added
|
469
99
|
# #
|
470
|
-
# #
|
471
|
-
# #
|
472
|
-
# #
|
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
|
100
|
+
# # after_commit :trigger_nats_wave_auto_publish_on_create, on: :create
|
101
|
+
# # after_commit :trigger_nats_wave_auto_publish_on_update, on: :update
|
102
|
+
# # after_commit :trigger_nats_wave_auto_publish_on_destroy, on: :destroy
|
479
103
|
# #
|
480
|
-
# #
|
481
|
-
# #
|
482
|
-
# # end
|
104
|
+
# # @nats_wave_callbacks_added = true
|
105
|
+
# # NatsWave.logger.debug "📤 #{self.name}: Added publishing callbacks"
|
483
106
|
# # end
|
484
107
|
# #
|
485
|
-
# # # Create a subscription handler that processes data and calls custom handler
|
108
|
+
# # # Create a subscription handler that processes data and calls custom handler AFTER auto-sync
|
486
109
|
# # def create_subscription_handler(config)
|
487
110
|
# # model_class = self
|
488
111
|
# #
|
489
112
|
# # lambda do |message|
|
490
113
|
# # begin
|
491
|
-
# #
|
114
|
+
# # NatsWave.logger.debug "📨 Processing subscription message for #{model_class.name}"
|
492
115
|
# #
|
493
116
|
# # # Extract the raw data
|
494
117
|
# # raw_data = message['data'] || {}
|
@@ -496,29 +119,29 @@
|
|
496
119
|
# # action = message['action']
|
497
120
|
# #
|
498
121
|
# # # Process the data through mappings and transformations
|
499
|
-
# # processed_data = process_subscription_data(raw_data, config)
|
500
|
-
# #
|
501
|
-
# # #
|
502
|
-
# # if config[:
|
503
|
-
# #
|
504
|
-
# # config[:handler].call(model_name, action, processed_data, message)
|
505
|
-
# # elsif config[:auto_sync]
|
506
|
-
# # Rails.logger.debug "📨 Performing auto-sync" if defined?(Rails)
|
507
|
-
# # # Default auto-sync behavior
|
122
|
+
# # processed_data = model_class.process_subscription_data(raw_data, config)
|
123
|
+
# #
|
124
|
+
# # # Always perform auto-sync first (if enabled)
|
125
|
+
# # if config[:auto_sync]
|
126
|
+
# # NatsWave.logger.debug "📨 Performing auto-sync first"
|
508
127
|
# # model_class.perform_auto_sync(model_name, action, processed_data, config)
|
509
|
-
# #
|
510
|
-
# #
|
128
|
+
# # end
|
129
|
+
# #
|
130
|
+
# # # Then call custom handler if provided (optional)
|
131
|
+
# # if config[:custom_handler]
|
132
|
+
# # NatsWave.logger.debug "📨 Calling custom subscription handler after auto-sync"
|
133
|
+
# # config[:custom_handler].call(model_name, action, processed_data, message)
|
511
134
|
# # end
|
512
135
|
# #
|
513
136
|
# # rescue => e
|
514
|
-
# #
|
515
|
-
# #
|
137
|
+
# # NatsWave.logger.error "Error in subscription handler for #{model_class.name}: #{e.message}"
|
138
|
+
# # NatsWave.logger.error e.backtrace.join("\n")
|
516
139
|
# # raise
|
517
140
|
# # end
|
518
141
|
# # end
|
519
142
|
# # end
|
520
143
|
# #
|
521
|
-
# # # Process subscription data through field mappings and transformations (CLASS METHOD)
|
144
|
+
# # # Process subscription data through field mappings and transformations (PUBLIC CLASS METHOD)
|
522
145
|
# # def process_subscription_data(raw_data, config)
|
523
146
|
# # processed_data = {}
|
524
147
|
# # field_mappings = config[:field_mappings]
|
@@ -532,7 +155,7 @@
|
|
532
155
|
# # # Skip if in skip_fields
|
533
156
|
# # next if skip_fields.include?(field_str) || skip_fields.include?(field_sym)
|
534
157
|
# #
|
535
|
-
# # # Apply field mapping
|
158
|
+
# # # Apply field mapping (external -> local)
|
536
159
|
# # mapped_field = field_mappings[field_str] ||
|
537
160
|
# # field_mappings[field_sym] ||
|
538
161
|
# # field
|
@@ -546,7 +169,7 @@
|
|
546
169
|
# # processed_data
|
547
170
|
# # end
|
548
171
|
# #
|
549
|
-
# # # Apply transformations to field values (CLASS METHOD)
|
172
|
+
# # # Apply transformations to field values (PUBLIC CLASS METHOD)
|
550
173
|
# # def apply_transformation(value, field, transformations, full_record)
|
551
174
|
# # transformation = transformations[field] || transformations[field.to_sym]
|
552
175
|
# #
|
@@ -563,7 +186,7 @@
|
|
563
186
|
# # elsif value.respond_to?(transformation)
|
564
187
|
# # value.send(transformation)
|
565
188
|
# # else
|
566
|
-
# #
|
189
|
+
# # NatsWave.logger.warn "Transformation method #{transformation} not found"
|
567
190
|
# # value
|
568
191
|
# # end
|
569
192
|
# # else
|
@@ -571,7 +194,7 @@
|
|
571
194
|
# # end
|
572
195
|
# # end
|
573
196
|
# #
|
574
|
-
# # # Default auto-sync behavior (CLASS METHOD)
|
197
|
+
# # # Default auto-sync behavior (PUBLIC CLASS METHOD)
|
575
198
|
# # def perform_auto_sync(model_name, action, processed_data, config)
|
576
199
|
# # unique_fields = config[:unique_fields]
|
577
200
|
# # sync_strategy = config[:sync_strategy]
|
@@ -584,7 +207,7 @@
|
|
584
207
|
# # when 'delete', 'deleted', 'destroy', 'destroyed'
|
585
208
|
# # handle_auto_delete(processed_data, unique_fields)
|
586
209
|
# # else
|
587
|
-
# #
|
210
|
+
# # NatsWave.logger.warn "Unknown action for auto-sync: #{action}"
|
588
211
|
# # end
|
589
212
|
# # end
|
590
213
|
# #
|
@@ -594,17 +217,17 @@
|
|
594
217
|
# # existing = find_by_unique_fields(data, unique_fields)
|
595
218
|
# # if existing
|
596
219
|
# # existing.update!(data)
|
597
|
-
# #
|
220
|
+
# # NatsWave.logger.info "✅ Updated existing #{self.name}: #{existing.id}"
|
598
221
|
# # else
|
599
222
|
# # record = self.create!(data)
|
600
|
-
# #
|
223
|
+
# # NatsWave.logger.info "✅ Created new #{self.name}: #{record.id}"
|
601
224
|
# # end
|
602
225
|
# # when :create_only
|
603
226
|
# # record = self.create!(data)
|
604
|
-
# #
|
227
|
+
# # NatsWave.logger.info "✅ Created new #{self.name}: #{record.id}"
|
605
228
|
# # end
|
606
229
|
# # rescue => e
|
607
|
-
# #
|
230
|
+
# # NatsWave.logger.error "❌ Failed to create #{self.name}: #{e.message}"
|
608
231
|
# # raise
|
609
232
|
# # end
|
610
233
|
# #
|
@@ -612,15 +235,15 @@
|
|
612
235
|
# # existing = find_by_unique_fields(data, unique_fields)
|
613
236
|
# # if existing
|
614
237
|
# # existing.update!(data)
|
615
|
-
# #
|
238
|
+
# # NatsWave.logger.info "✅ Updated #{self.name}: #{existing.id}"
|
616
239
|
# # elsif sync_strategy == :upsert
|
617
240
|
# # record = self.create!(data)
|
618
|
-
# #
|
241
|
+
# # NatsWave.logger.info "✅ Created new #{self.name} during update: #{record.id}"
|
619
242
|
# # else
|
620
|
-
# #
|
243
|
+
# # NatsWave.logger.warn "⚠️ Record not found for update: #{data.slice(*unique_fields.map(&:to_s))}"
|
621
244
|
# # end
|
622
245
|
# # rescue => e
|
623
|
-
# #
|
246
|
+
# # NatsWave.logger.error "❌ Failed to update #{self.name}: #{e.message}"
|
624
247
|
# # raise
|
625
248
|
# # end
|
626
249
|
# #
|
@@ -628,12 +251,12 @@
|
|
628
251
|
# # existing = find_by_unique_fields(data, unique_fields)
|
629
252
|
# # if existing
|
630
253
|
# # existing.destroy!
|
631
|
-
# #
|
254
|
+
# # NatsWave.logger.info "✅ Deleted #{self.name}: #{existing.id}"
|
632
255
|
# # else
|
633
|
-
# #
|
256
|
+
# # NatsWave.logger.warn "⚠️ Record not found for deletion: #{data.slice(*unique_fields.map(&:to_s))}"
|
634
257
|
# # end
|
635
258
|
# # rescue => e
|
636
|
-
# #
|
259
|
+
# # NatsWave.logger.error "❌ Failed to delete #{self.name}: #{e.message}"
|
637
260
|
# # raise
|
638
261
|
# # end
|
639
262
|
# #
|
@@ -663,7 +286,7 @@
|
|
663
286
|
# #
|
664
287
|
# # # Check conditions (only for publishing)
|
665
288
|
# # if config[:conditions].any? && !evaluate_conditions(config[:conditions])
|
666
|
-
# #
|
289
|
+
# # NatsWave.logger.debug "📤 Skipping publish - conditions not met"
|
667
290
|
# # next
|
668
291
|
# # end
|
669
292
|
# #
|
@@ -673,44 +296,62 @@
|
|
673
296
|
# # # Process the data through mappings and transformations
|
674
297
|
# # processed_data = process_publishing_data(raw_data, config)
|
675
298
|
# #
|
676
|
-
# # #
|
677
|
-
# #
|
678
|
-
# #
|
679
|
-
# #
|
680
|
-
# #
|
681
|
-
# #
|
682
|
-
# #
|
683
|
-
# #
|
684
|
-
# #
|
685
|
-
# #
|
686
|
-
# #
|
687
|
-
# #
|
688
|
-
# #
|
689
|
-
# #
|
690
|
-
# #
|
691
|
-
# #
|
692
|
-
# #
|
299
|
+
# # # Determine which subjects to publish to based on action
|
300
|
+
# # subjects_to_publish = determine_subjects_for_action(config[:subjects], action)
|
301
|
+
# #
|
302
|
+
# # # Always publish first (default behavior)
|
303
|
+
# # subjects_to_publish.each do |subject|
|
304
|
+
# # NatsWave.client.publish(
|
305
|
+
# # subject: subject,
|
306
|
+
# # model: self.class.name,
|
307
|
+
# # action: action,
|
308
|
+
# # data: processed_data, # This is the processed data, not raw attributes
|
309
|
+
# # metadata: build_publishing_metadata
|
310
|
+
# # )
|
311
|
+
# #
|
312
|
+
# # NatsWave.logger.info "📤 Published #{self.class.name} to #{subject}"
|
313
|
+
# # end
|
314
|
+
# #
|
315
|
+
# # # Then call custom handler if provided (optional)
|
316
|
+
# # if config[:custom_handler]
|
317
|
+
# # NatsWave.logger.debug "📤 Calling custom publishing handler after publishing"
|
318
|
+
# # config[:custom_handler].call(self.class.name, action, processed_data, self)
|
693
319
|
# # end
|
694
320
|
# # end
|
695
321
|
# # rescue StandardError => e
|
696
|
-
# #
|
322
|
+
# # NatsWave.logger.error("Failed to publish: #{e.message}")
|
697
323
|
# # # Don't re-raise to avoid breaking transactions
|
698
324
|
# # end
|
699
325
|
# #
|
700
|
-
# # # Auto-publishing callback methods
|
326
|
+
# # # Auto-publishing callback methods (only called once per action)
|
701
327
|
# # def trigger_nats_wave_auto_publish_on_create
|
702
|
-
# #
|
328
|
+
# # return if @nats_wave_publishing_in_progress
|
329
|
+
# # @nats_wave_publishing_in_progress = true
|
330
|
+
# #
|
331
|
+
# # NatsWave.logger.debug "🚀 Auto-publishing on create"
|
703
332
|
# # nats_wave_publish('create')
|
333
|
+
# #
|
334
|
+
# # @nats_wave_publishing_in_progress = false
|
704
335
|
# # end
|
705
336
|
# #
|
706
337
|
# # def trigger_nats_wave_auto_publish_on_update
|
707
|
-
# #
|
338
|
+
# # return if @nats_wave_publishing_in_progress
|
339
|
+
# # @nats_wave_publishing_in_progress = true
|
340
|
+
# #
|
341
|
+
# # NatsWave.logger.debug "🚀 Auto-publishing on update"
|
708
342
|
# # nats_wave_publish('update')
|
343
|
+
# #
|
344
|
+
# # @nats_wave_publishing_in_progress = false
|
709
345
|
# # end
|
710
346
|
# #
|
711
347
|
# # def trigger_nats_wave_auto_publish_on_destroy
|
712
|
-
# #
|
348
|
+
# # return if @nats_wave_publishing_in_progress
|
349
|
+
# # @nats_wave_publishing_in_progress = true
|
350
|
+
# #
|
351
|
+
# # NatsWave.logger.debug "🚀 Auto-publishing on destroy"
|
713
352
|
# # nats_wave_publish('destroy')
|
353
|
+
# #
|
354
|
+
# # @nats_wave_publishing_in_progress = false
|
714
355
|
# # end
|
715
356
|
# #
|
716
357
|
# # private
|
@@ -736,31 +377,49 @@
|
|
736
377
|
# # end
|
737
378
|
# # end
|
738
379
|
# #
|
739
|
-
# # # Process publishing data through field mappings and transformations (INSTANCE METHOD)
|
740
380
|
# # def process_publishing_data(raw_data, config)
|
741
381
|
# # processed_data = {}
|
742
|
-
# # field_mappings = config[:field_mappings]
|
743
|
-
# # transformations = config[:transformations]
|
744
|
-
# # skip_fields = config[:skip_fields]
|
382
|
+
# # field_mappings = config[:field_mappings] || {}
|
383
|
+
# # transformations = config[:transformations] || {}
|
384
|
+
# # skip_fields = config[:skip_fields] || []
|
385
|
+
# # only_mapped_fields = config[:only_mapped_fields]
|
745
386
|
# #
|
746
|
-
# #
|
747
|
-
# #
|
748
|
-
# #
|
387
|
+
# # # If only_mapped_fields is true, only process fields that have explicit mappings
|
388
|
+
# # if only_mapped_fields && field_mappings.any?
|
389
|
+
# # field_mappings.each do |local_field, external_field|
|
390
|
+
# # local_field_str = local_field.to_s
|
749
391
|
# #
|
750
|
-
# #
|
751
|
-
# #
|
392
|
+
# # # Skip if this field is in skip_fields
|
393
|
+
# # next if skip_fields.include?(local_field_str) || skip_fields.include?(local_field.to_sym)
|
752
394
|
# #
|
753
|
-
# #
|
754
|
-
# #
|
755
|
-
# # field_mappings[field_sym] ||
|
756
|
-
# # field
|
395
|
+
# # # Get the value from raw_data
|
396
|
+
# # value = raw_data[local_field_str] || raw_data[local_field.to_sym]
|
757
397
|
# #
|
758
|
-
# #
|
759
|
-
# #
|
398
|
+
# # # Apply transformation if any (use external field name for transformation lookup)
|
399
|
+
# # transformed_value = apply_publishing_transformation(value, external_field, transformations, raw_data)
|
760
400
|
# #
|
761
|
-
# #
|
762
|
-
# #
|
401
|
+
# # processed_data[external_field.to_s] = transformed_value
|
402
|
+
# # end
|
403
|
+
# # else
|
404
|
+
# # # Original behavior - process all fields
|
405
|
+
# # raw_data.each do |field, value|
|
406
|
+
# # field_str = field.to_s
|
407
|
+
# # field_sym = field.to_sym
|
408
|
+
# #
|
409
|
+
# # # Skip if in skip_fields
|
410
|
+
# # next if skip_fields.include?(field_str) || skip_fields.include?(field_sym)
|
763
411
|
# #
|
412
|
+
# # # Apply field mapping (local -> external) - ONLY if mapping exists
|
413
|
+
# # mapped_field = field_mappings[field_str] ||
|
414
|
+
# # field_mappings[field_sym] ||
|
415
|
+
# # field # Use original field name if no mapping
|
416
|
+
# #
|
417
|
+
# # # Apply transformation if any
|
418
|
+
# # transformed_value = apply_publishing_transformation(value, mapped_field, transformations, raw_data)
|
419
|
+
# #
|
420
|
+
# # processed_data[mapped_field.to_s] = transformed_value
|
421
|
+
# # end
|
422
|
+
# # end
|
764
423
|
# # processed_data
|
765
424
|
# # end
|
766
425
|
# #
|
@@ -780,7 +439,7 @@
|
|
780
439
|
# # elsif value.respond_to?(transformation)
|
781
440
|
# # value.send(transformation)
|
782
441
|
# # else
|
783
|
-
# #
|
442
|
+
# # NatsWave.logger.warn "Publishing transformation method #{transformation} not found"
|
784
443
|
# # value
|
785
444
|
# # end
|
786
445
|
# # else
|
@@ -788,6 +447,20 @@
|
|
788
447
|
# # end
|
789
448
|
# # end
|
790
449
|
# #
|
450
|
+
# # def determine_subjects_for_action(configured_subjects, action)
|
451
|
+
# # # If subjects contain placeholders like {action}, replace them
|
452
|
+
# # configured_subjects.map do |subject|
|
453
|
+
# # if subject.include?('{action}')
|
454
|
+
# # subject.gsub('{action}', action.to_s)
|
455
|
+
# # elsif subject.include?('*')
|
456
|
+
# # # Replace wildcard with specific action
|
457
|
+
# # subject.gsub('*', action.to_s)
|
458
|
+
# # else
|
459
|
+
# # subject
|
460
|
+
# # end
|
461
|
+
# # end
|
462
|
+
# # end
|
463
|
+
# #
|
791
464
|
# # def evaluate_conditions(conditions)
|
792
465
|
# # conditions.all? do |condition, expected_value|
|
793
466
|
# # case condition
|
@@ -833,7 +506,7 @@
|
|
833
506
|
# class_methods do
|
834
507
|
# # Configure subscriptions with optional custom handler
|
835
508
|
# def nats_wave_subscribes_to(*subjects, **options, &block)
|
836
|
-
#
|
509
|
+
# NatsWave.logger.debug "📡 #{self.name}: Setting up subscription to subjects: #{subjects.inspect}"
|
837
510
|
#
|
838
511
|
# # Custom handler is now optional and runs AFTER default behavior
|
839
512
|
# custom_handler = options[:handler] || block
|
@@ -865,12 +538,12 @@
|
|
865
538
|
# queue_group: subscription_config[:queue_group]
|
866
539
|
# )
|
867
540
|
#
|
868
|
-
#
|
541
|
+
# NatsWave.logger.debug "📡 #{self.name}: Registered subscription for subjects: #{subjects}"
|
869
542
|
# end
|
870
543
|
#
|
871
544
|
# # Configure publishing with optional custom handler AND auto-publishing callbacks
|
872
545
|
# def nats_wave_publishes_to(*subjects, **options, &block)
|
873
|
-
#
|
546
|
+
# NatsWave.logger.debug "📤 #{self.name}: Setting up publishing to subjects: #{subjects.inspect}"
|
874
547
|
#
|
875
548
|
# # Custom handler is now optional and runs AFTER default behavior
|
876
549
|
# custom_handler = options[:handler] || block
|
@@ -885,7 +558,7 @@
|
|
885
558
|
# custom_handler: custom_handler, # Renamed to be clear it's optional
|
886
559
|
# async: options[:async] != false, # Default to true
|
887
560
|
# enabled: options[:enabled] != false, # Default to true
|
888
|
-
# only_mapped_fields: options[:only_mapped_fields]
|
561
|
+
# only_mapped_fields: options[:only_mapped_fields] # Changed: don't default to true
|
889
562
|
# }
|
890
563
|
#
|
891
564
|
# # Store the config
|
@@ -895,7 +568,7 @@
|
|
895
568
|
# # Add ActiveRecord callbacks for auto-publishing (only once per model)
|
896
569
|
# setup_publishing_callbacks_once
|
897
570
|
#
|
898
|
-
#
|
571
|
+
# NatsWave.logger.debug "📤 #{self.name}: Registered publishing config for subjects: #{subjects}"
|
899
572
|
# end
|
900
573
|
#
|
901
574
|
# # Get all subscription configurations
|
@@ -908,59 +581,9 @@
|
|
908
581
|
# nats_wave_publishing_config
|
909
582
|
# end
|
910
583
|
#
|
911
|
-
#
|
912
|
-
#
|
913
|
-
# # Setup ActiveRecord callbacks for auto-publishing (only once per model)
|
914
|
-
# def setup_publishing_callbacks_once
|
915
|
-
# # Only add callbacks if we're in ActiveRecord and haven't added them yet
|
916
|
-
# return unless defined?(ActiveRecord::Base) && self < ActiveRecord::Base
|
917
|
-
# return if @nats_wave_callbacks_added
|
918
|
-
#
|
919
|
-
# after_commit :trigger_nats_wave_auto_publish_on_create, on: :create
|
920
|
-
# after_commit :trigger_nats_wave_auto_publish_on_update, on: :update
|
921
|
-
# after_commit :trigger_nats_wave_auto_publish_on_destroy, on: :destroy
|
922
|
-
#
|
923
|
-
# @nats_wave_callbacks_added = true
|
924
|
-
# Rails.logger.debug "📤 #{self.name}: Added publishing callbacks" if defined?(Rails)
|
925
|
-
# end
|
926
|
-
#
|
927
|
-
# # Create a subscription handler that processes data and calls custom handler AFTER auto-sync
|
928
|
-
# def create_subscription_handler(config)
|
929
|
-
# model_class = self
|
584
|
+
# # PUBLIC METHODS - These need to be accessible from lambda context
|
930
585
|
#
|
931
|
-
#
|
932
|
-
# begin
|
933
|
-
# Rails.logger.debug "📨 Processing subscription message for #{model_class.name}" if defined?(Rails)
|
934
|
-
#
|
935
|
-
# # Extract the raw data
|
936
|
-
# raw_data = message['data'] || {}
|
937
|
-
# model_name = message['model']
|
938
|
-
# action = message['action']
|
939
|
-
#
|
940
|
-
# # Process the data through mappings and transformations
|
941
|
-
# processed_data = process_subscription_data(raw_data, config)
|
942
|
-
#
|
943
|
-
# # Always perform auto-sync first (if enabled)
|
944
|
-
# if config[:auto_sync]
|
945
|
-
# Rails.logger.debug "📨 Performing auto-sync first" if defined?(Rails)
|
946
|
-
# model_class.perform_auto_sync(model_name, action, processed_data, config)
|
947
|
-
# end
|
948
|
-
#
|
949
|
-
# # Then call custom handler if provided (optional)
|
950
|
-
# if config[:custom_handler]
|
951
|
-
# Rails.logger.debug "📨 Calling custom subscription handler after auto-sync" if defined?(Rails)
|
952
|
-
# config[:custom_handler].call(model_name, action, processed_data, message)
|
953
|
-
# end
|
954
|
-
#
|
955
|
-
# rescue => e
|
956
|
-
# Rails.logger.error "Error in subscription handler for #{model_class.name}: #{e.message}" if defined?(Rails)
|
957
|
-
# Rails.logger.error e.backtrace.join("\n") if defined?(Rails)
|
958
|
-
# raise
|
959
|
-
# end
|
960
|
-
# end
|
961
|
-
# end
|
962
|
-
#
|
963
|
-
# # Process subscription data through field mappings and transformations (CLASS METHOD)
|
586
|
+
# # Process subscription data through field mappings and transformations
|
964
587
|
# def process_subscription_data(raw_data, config)
|
965
588
|
# processed_data = {}
|
966
589
|
# field_mappings = config[:field_mappings]
|
@@ -974,7 +597,7 @@
|
|
974
597
|
# # Skip if in skip_fields
|
975
598
|
# next if skip_fields.include?(field_str) || skip_fields.include?(field_sym)
|
976
599
|
#
|
977
|
-
# # Apply field mapping
|
600
|
+
# # Apply field mapping (external -> local)
|
978
601
|
# mapped_field = field_mappings[field_str] ||
|
979
602
|
# field_mappings[field_sym] ||
|
980
603
|
# field
|
@@ -988,7 +611,7 @@
|
|
988
611
|
# processed_data
|
989
612
|
# end
|
990
613
|
#
|
991
|
-
# # Apply transformations to field values
|
614
|
+
# # Apply transformations to field values
|
992
615
|
# def apply_transformation(value, field, transformations, full_record)
|
993
616
|
# transformation = transformations[field] || transformations[field.to_sym]
|
994
617
|
#
|
@@ -1005,7 +628,7 @@
|
|
1005
628
|
# elsif value.respond_to?(transformation)
|
1006
629
|
# value.send(transformation)
|
1007
630
|
# else
|
1008
|
-
#
|
631
|
+
# NatsWave.logger.warn "Transformation method #{transformation} not found"
|
1009
632
|
# value
|
1010
633
|
# end
|
1011
634
|
# else
|
@@ -1013,7 +636,7 @@
|
|
1013
636
|
# end
|
1014
637
|
# end
|
1015
638
|
#
|
1016
|
-
# # Default auto-sync behavior
|
639
|
+
# # Default auto-sync behavior
|
1017
640
|
# def perform_auto_sync(model_name, action, processed_data, config)
|
1018
641
|
# unique_fields = config[:unique_fields]
|
1019
642
|
# sync_strategy = config[:sync_strategy]
|
@@ -1026,7 +649,7 @@
|
|
1026
649
|
# when 'delete', 'deleted', 'destroy', 'destroyed'
|
1027
650
|
# handle_auto_delete(processed_data, unique_fields)
|
1028
651
|
# else
|
1029
|
-
#
|
652
|
+
# NatsWave.logger.warn "Unknown action for auto-sync: #{action}"
|
1030
653
|
# end
|
1031
654
|
# end
|
1032
655
|
#
|
@@ -1036,17 +659,17 @@
|
|
1036
659
|
# existing = find_by_unique_fields(data, unique_fields)
|
1037
660
|
# if existing
|
1038
661
|
# existing.update!(data)
|
1039
|
-
#
|
662
|
+
# NatsWave.logger.info "✅ Updated existing #{self.name}: #{existing.id}"
|
1040
663
|
# else
|
1041
664
|
# record = self.create!(data)
|
1042
|
-
#
|
665
|
+
# NatsWave.logger.info "✅ Created new #{self.name}: #{record.id}"
|
1043
666
|
# end
|
1044
667
|
# when :create_only
|
1045
668
|
# record = self.create!(data)
|
1046
|
-
#
|
669
|
+
# NatsWave.logger.info "✅ Created new #{self.name}: #{record.id}"
|
1047
670
|
# end
|
1048
671
|
# rescue => e
|
1049
|
-
#
|
672
|
+
# NatsWave.logger.error "❌ Failed to create #{self.name}: #{e.message}"
|
1050
673
|
# raise
|
1051
674
|
# end
|
1052
675
|
#
|
@@ -1054,15 +677,15 @@
|
|
1054
677
|
# existing = find_by_unique_fields(data, unique_fields)
|
1055
678
|
# if existing
|
1056
679
|
# existing.update!(data)
|
1057
|
-
#
|
680
|
+
# NatsWave.logger.info "✅ Updated #{self.name}: #{existing.id}"
|
1058
681
|
# elsif sync_strategy == :upsert
|
1059
682
|
# record = self.create!(data)
|
1060
|
-
#
|
683
|
+
# NatsWave.logger.info "✅ Created new #{self.name} during update: #{record.id}"
|
1061
684
|
# else
|
1062
|
-
#
|
685
|
+
# NatsWave.logger.warn "⚠️ Record not found for update: #{data.slice(*unique_fields.map(&:to_s))}"
|
1063
686
|
# end
|
1064
687
|
# rescue => e
|
1065
|
-
#
|
688
|
+
# NatsWave.logger.error "❌ Failed to update #{self.name}: #{e.message}"
|
1066
689
|
# raise
|
1067
690
|
# end
|
1068
691
|
#
|
@@ -1070,12 +693,12 @@
|
|
1070
693
|
# existing = find_by_unique_fields(data, unique_fields)
|
1071
694
|
# if existing
|
1072
695
|
# existing.destroy!
|
1073
|
-
#
|
696
|
+
# NatsWave.logger.info "✅ Deleted #{self.name}: #{existing.id}"
|
1074
697
|
# else
|
1075
|
-
#
|
698
|
+
# NatsWave.logger.warn "⚠️ Record not found for deletion: #{data.slice(*unique_fields.map(&:to_s))}"
|
1076
699
|
# end
|
1077
700
|
# rescue => e
|
1078
|
-
#
|
701
|
+
# NatsWave.logger.error "❌ Failed to delete #{self.name}: #{e.message}"
|
1079
702
|
# raise
|
1080
703
|
# end
|
1081
704
|
#
|
@@ -1093,6 +716,58 @@
|
|
1093
716
|
# return nil if conditions.empty?
|
1094
717
|
# self.find_by(conditions)
|
1095
718
|
# end
|
719
|
+
#
|
720
|
+
# private
|
721
|
+
#
|
722
|
+
# # Setup ActiveRecord callbacks for auto-publishing (only once per model)
|
723
|
+
# def setup_publishing_callbacks_once
|
724
|
+
# # Only add callbacks if we're in ActiveRecord and haven't added them yet
|
725
|
+
# return unless defined?(ActiveRecord::Base) && self < ActiveRecord::Base
|
726
|
+
# return if @nats_wave_callbacks_added
|
727
|
+
#
|
728
|
+
# after_commit :trigger_nats_wave_auto_publish_on_create, on: :create
|
729
|
+
# after_commit :trigger_nats_wave_auto_publish_on_update, on: :update
|
730
|
+
# after_commit :trigger_nats_wave_auto_publish_on_destroy, on: :destroy
|
731
|
+
#
|
732
|
+
# @nats_wave_callbacks_added = true
|
733
|
+
# NatsWave.logger.debug "📤 #{self.name}: Added publishing callbacks"
|
734
|
+
# end
|
735
|
+
#
|
736
|
+
# # Create a subscription handler that processes data and calls custom handler AFTER auto-sync
|
737
|
+
# def create_subscription_handler(config)
|
738
|
+
# model_class = self
|
739
|
+
#
|
740
|
+
# lambda do |message|
|
741
|
+
# begin
|
742
|
+
# NatsWave.logger.debug "📨 Processing subscription message for #{model_class.name}"
|
743
|
+
#
|
744
|
+
# # Extract the raw data
|
745
|
+
# raw_data = message['data'] || {}
|
746
|
+
# model_name = message['model']
|
747
|
+
# action = message['action']
|
748
|
+
#
|
749
|
+
# # Process the data through mappings and transformations
|
750
|
+
# processed_data = model_class.process_subscription_data(raw_data, config)
|
751
|
+
#
|
752
|
+
# # Always perform auto-sync first (if enabled)
|
753
|
+
# if config[:auto_sync]
|
754
|
+
# NatsWave.logger.debug "📨 Performing auto-sync first"
|
755
|
+
# model_class.perform_auto_sync(model_name, action, processed_data, config)
|
756
|
+
# end
|
757
|
+
#
|
758
|
+
# # Then call custom handler if provided (optional)
|
759
|
+
# if config[:custom_handler]
|
760
|
+
# NatsWave.logger.debug "📨 Calling custom subscription handler after auto-sync"
|
761
|
+
# config[:custom_handler].call(model_name, action, processed_data, message)
|
762
|
+
# end
|
763
|
+
#
|
764
|
+
# rescue => e
|
765
|
+
# NatsWave.logger.error "Error in subscription handler for #{model_class.name}: #{e.message}"
|
766
|
+
# NatsWave.logger.error e.backtrace.join("\n")
|
767
|
+
# raise
|
768
|
+
# end
|
769
|
+
# end
|
770
|
+
# end
|
1096
771
|
# end
|
1097
772
|
#
|
1098
773
|
# # Instance methods for publishing
|
@@ -1105,7 +780,7 @@
|
|
1105
780
|
#
|
1106
781
|
# # Check conditions (only for publishing)
|
1107
782
|
# if config[:conditions].any? && !evaluate_conditions(config[:conditions])
|
1108
|
-
#
|
783
|
+
# NatsWave.logger.debug "📤 Skipping publish - conditions not met"
|
1109
784
|
# next
|
1110
785
|
# end
|
1111
786
|
#
|
@@ -1128,17 +803,17 @@
|
|
1128
803
|
# metadata: build_publishing_metadata
|
1129
804
|
# )
|
1130
805
|
#
|
1131
|
-
#
|
806
|
+
# NatsWave.logger.info "📤 Published #{self.class.name} to #{subject}"
|
1132
807
|
# end
|
1133
808
|
#
|
1134
809
|
# # Then call custom handler if provided (optional)
|
1135
810
|
# if config[:custom_handler]
|
1136
|
-
#
|
811
|
+
# NatsWave.logger.debug "📤 Calling custom publishing handler after publishing"
|
1137
812
|
# config[:custom_handler].call(self.class.name, action, processed_data, self)
|
1138
813
|
# end
|
1139
814
|
# end
|
1140
815
|
# rescue StandardError => e
|
1141
|
-
#
|
816
|
+
# NatsWave.logger.error("Failed to publish: #{e.message}")
|
1142
817
|
# # Don't re-raise to avoid breaking transactions
|
1143
818
|
# end
|
1144
819
|
#
|
@@ -1147,7 +822,7 @@
|
|
1147
822
|
# return if @nats_wave_publishing_in_progress
|
1148
823
|
# @nats_wave_publishing_in_progress = true
|
1149
824
|
#
|
1150
|
-
#
|
825
|
+
# NatsWave.logger.debug "🚀 Auto-publishing on create"
|
1151
826
|
# nats_wave_publish('create')
|
1152
827
|
#
|
1153
828
|
# @nats_wave_publishing_in_progress = false
|
@@ -1157,7 +832,7 @@
|
|
1157
832
|
# return if @nats_wave_publishing_in_progress
|
1158
833
|
# @nats_wave_publishing_in_progress = true
|
1159
834
|
#
|
1160
|
-
#
|
835
|
+
# NatsWave.logger.debug "🚀 Auto-publishing on update"
|
1161
836
|
# nats_wave_publish('update')
|
1162
837
|
#
|
1163
838
|
# @nats_wave_publishing_in_progress = false
|
@@ -1167,7 +842,7 @@
|
|
1167
842
|
# return if @nats_wave_publishing_in_progress
|
1168
843
|
# @nats_wave_publishing_in_progress = true
|
1169
844
|
#
|
1170
|
-
#
|
845
|
+
# NatsWave.logger.debug "🚀 Auto-publishing on destroy"
|
1171
846
|
# nats_wave_publish('destroy')
|
1172
847
|
#
|
1173
848
|
# @nats_wave_publishing_in_progress = false
|
@@ -1196,45 +871,49 @@
|
|
1196
871
|
# end
|
1197
872
|
# end
|
1198
873
|
#
|
1199
|
-
# # Process publishing data through field mappings and transformations (INSTANCE METHOD)
|
1200
874
|
# def process_publishing_data(raw_data, config)
|
1201
875
|
# processed_data = {}
|
1202
|
-
# field_mappings = config[:field_mappings]
|
1203
|
-
# transformations = config[:transformations]
|
1204
|
-
# skip_fields = config[:skip_fields]
|
876
|
+
# field_mappings = config[:field_mappings] || {}
|
877
|
+
# transformations = config[:transformations] || {}
|
878
|
+
# skip_fields = config[:skip_fields] || []
|
1205
879
|
# only_mapped_fields = config[:only_mapped_fields]
|
1206
880
|
#
|
1207
|
-
# # If only_mapped_fields is true, only process fields that have mappings
|
1208
|
-
#
|
1209
|
-
#
|
1210
|
-
#
|
1211
|
-
# transformation_fields = transformations.keys.map(&:to_s)
|
1212
|
-
# all_relevant_fields = (mapped_source_fields + transformation_fields).uniq
|
881
|
+
# # If only_mapped_fields is true, only process fields that have explicit mappings
|
882
|
+
# if only_mapped_fields && field_mappings.any?
|
883
|
+
# field_mappings.each do |local_field, external_field|
|
884
|
+
# local_field_str = local_field.to_s
|
1213
885
|
#
|
1214
|
-
#
|
1215
|
-
#
|
1216
|
-
# # Process all fields (existing behavior)
|
1217
|
-
# raw_data
|
1218
|
-
# end
|
886
|
+
# # Skip if this field is in skip_fields
|
887
|
+
# next if skip_fields.include?(local_field_str) || skip_fields.include?(local_field.to_sym)
|
1219
888
|
#
|
1220
|
-
#
|
1221
|
-
#
|
1222
|
-
# field_sym = field.to_sym
|
889
|
+
# # Get the value from raw_data
|
890
|
+
# value = raw_data[local_field_str] || raw_data[local_field.to_sym]
|
1223
891
|
#
|
1224
|
-
#
|
1225
|
-
#
|
892
|
+
# # Apply transformation if any (use external field name for transformation lookup)
|
893
|
+
# transformed_value = apply_publishing_transformation(value, external_field, transformations, raw_data)
|
1226
894
|
#
|
1227
|
-
#
|
1228
|
-
#
|
1229
|
-
#
|
1230
|
-
#
|
895
|
+
# processed_data[external_field.to_s] = transformed_value
|
896
|
+
# end
|
897
|
+
# else
|
898
|
+
# # Original behavior - process all fields
|
899
|
+
# raw_data.each do |field, value|
|
900
|
+
# field_str = field.to_s
|
901
|
+
# field_sym = field.to_sym
|
1231
902
|
#
|
1232
|
-
#
|
1233
|
-
#
|
903
|
+
# # Skip if in skip_fields
|
904
|
+
# next if skip_fields.include?(field_str) || skip_fields.include?(field_sym)
|
1234
905
|
#
|
1235
|
-
#
|
1236
|
-
#
|
906
|
+
# # Apply field mapping (local -> external) - ONLY if mapping exists
|
907
|
+
# mapped_field = field_mappings[field_str] ||
|
908
|
+
# field_mappings[field_sym] ||
|
909
|
+
# field # Use original field name if no mapping
|
910
|
+
#
|
911
|
+
# # Apply transformation if any
|
912
|
+
# transformed_value = apply_publishing_transformation(value, mapped_field, transformations, raw_data)
|
1237
913
|
#
|
914
|
+
# processed_data[mapped_field.to_s] = transformed_value
|
915
|
+
# end
|
916
|
+
# end
|
1238
917
|
# processed_data
|
1239
918
|
# end
|
1240
919
|
#
|
@@ -1254,7 +933,7 @@
|
|
1254
933
|
# elsif value.respond_to?(transformation)
|
1255
934
|
# value.send(transformation)
|
1256
935
|
# else
|
1257
|
-
#
|
936
|
+
# NatsWave.logger.warn "Publishing transformation method #{transformation} not found"
|
1258
937
|
# value
|
1259
938
|
# end
|
1260
939
|
# else
|
@@ -1304,6 +983,7 @@
|
|
1304
983
|
# end
|
1305
984
|
# end
|
1306
985
|
# end
|
986
|
+
|
1307
987
|
# frozen_string_literal: true
|
1308
988
|
|
1309
989
|
module NatsWave
|
@@ -1320,7 +1000,7 @@ module NatsWave
|
|
1320
1000
|
class_methods do
|
1321
1001
|
# Configure subscriptions with optional custom handler
|
1322
1002
|
def nats_wave_subscribes_to(*subjects, **options, &block)
|
1323
|
-
|
1003
|
+
NatsWave.logger.debug "📡 #{self.name}: Setting up subscription to subjects: #{subjects.inspect}"
|
1324
1004
|
|
1325
1005
|
# Custom handler is now optional and runs AFTER default behavior
|
1326
1006
|
custom_handler = options[:handler] || block
|
@@ -1352,12 +1032,12 @@ module NatsWave
|
|
1352
1032
|
queue_group: subscription_config[:queue_group]
|
1353
1033
|
)
|
1354
1034
|
|
1355
|
-
|
1035
|
+
NatsWave.logger.debug "📡 #{self.name}: Registered subscription for subjects: #{subjects}"
|
1356
1036
|
end
|
1357
1037
|
|
1358
1038
|
# Configure publishing with optional custom handler AND auto-publishing callbacks
|
1359
1039
|
def nats_wave_publishes_to(*subjects, **options, &block)
|
1360
|
-
|
1040
|
+
NatsWave.logger.debug "📤 #{self.name}: Setting up publishing to subjects: #{subjects.inspect}"
|
1361
1041
|
|
1362
1042
|
# Custom handler is now optional and runs AFTER default behavior
|
1363
1043
|
custom_handler = options[:handler] || block
|
@@ -1371,7 +1051,8 @@ module NatsWave
|
|
1371
1051
|
actions: options[:actions] || [:create, :update, :destroy],
|
1372
1052
|
custom_handler: custom_handler, # Renamed to be clear it's optional
|
1373
1053
|
async: options[:async] != false, # Default to true
|
1374
|
-
enabled: options[:enabled] != false # Default to true
|
1054
|
+
enabled: options[:enabled] != false, # Default to true
|
1055
|
+
only_mapped_fields: options[:only_mapped_fields] != false, # Default to true
|
1375
1056
|
}
|
1376
1057
|
|
1377
1058
|
# Store the config
|
@@ -1381,7 +1062,7 @@ module NatsWave
|
|
1381
1062
|
# Add ActiveRecord callbacks for auto-publishing (only once per model)
|
1382
1063
|
setup_publishing_callbacks_once
|
1383
1064
|
|
1384
|
-
|
1065
|
+
NatsWave.logger.debug "📤 #{self.name}: Registered publishing config for subjects: #{subjects}"
|
1385
1066
|
end
|
1386
1067
|
|
1387
1068
|
# Get all subscription configurations
|
@@ -1394,58 +1075,6 @@ module NatsWave
|
|
1394
1075
|
nats_wave_publishing_config
|
1395
1076
|
end
|
1396
1077
|
|
1397
|
-
private
|
1398
|
-
|
1399
|
-
# Setup ActiveRecord callbacks for auto-publishing (only once per model)
|
1400
|
-
def setup_publishing_callbacks_once
|
1401
|
-
# Only add callbacks if we're in ActiveRecord and haven't added them yet
|
1402
|
-
return unless defined?(ActiveRecord::Base) && self < ActiveRecord::Base
|
1403
|
-
return if @nats_wave_callbacks_added
|
1404
|
-
|
1405
|
-
after_commit :trigger_nats_wave_auto_publish_on_create, on: :create
|
1406
|
-
after_commit :trigger_nats_wave_auto_publish_on_update, on: :update
|
1407
|
-
after_commit :trigger_nats_wave_auto_publish_on_destroy, on: :destroy
|
1408
|
-
|
1409
|
-
@nats_wave_callbacks_added = true
|
1410
|
-
Rails.logger.debug "📤 #{self.name}: Added publishing callbacks" if defined?(Rails)
|
1411
|
-
end
|
1412
|
-
|
1413
|
-
# Create a subscription handler that processes data and calls custom handler AFTER auto-sync
|
1414
|
-
def create_subscription_handler(config)
|
1415
|
-
model_class = self
|
1416
|
-
|
1417
|
-
lambda do |message|
|
1418
|
-
begin
|
1419
|
-
Rails.logger.debug "📨 Processing subscription message for #{model_class.name}" if defined?(Rails)
|
1420
|
-
|
1421
|
-
# Extract the raw data
|
1422
|
-
raw_data = message['data'] || {}
|
1423
|
-
model_name = message['model']
|
1424
|
-
action = message['action']
|
1425
|
-
|
1426
|
-
# Process the data through mappings and transformations
|
1427
|
-
processed_data = model_class.process_subscription_data(raw_data, config)
|
1428
|
-
|
1429
|
-
# Always perform auto-sync first (if enabled)
|
1430
|
-
if config[:auto_sync]
|
1431
|
-
Rails.logger.debug "📨 Performing auto-sync first" if defined?(Rails)
|
1432
|
-
model_class.perform_auto_sync(model_name, action, processed_data, config)
|
1433
|
-
end
|
1434
|
-
|
1435
|
-
# Then call custom handler if provided (optional)
|
1436
|
-
if config[:custom_handler]
|
1437
|
-
Rails.logger.debug "📨 Calling custom subscription handler after auto-sync" if defined?(Rails)
|
1438
|
-
config[:custom_handler].call(model_name, action, processed_data, message)
|
1439
|
-
end
|
1440
|
-
|
1441
|
-
rescue => e
|
1442
|
-
Rails.logger.error "Error in subscription handler for #{model_class.name}: #{e.message}" if defined?(Rails)
|
1443
|
-
Rails.logger.error e.backtrace.join("\n") if defined?(Rails)
|
1444
|
-
raise
|
1445
|
-
end
|
1446
|
-
end
|
1447
|
-
end
|
1448
|
-
|
1449
1078
|
# Process subscription data through field mappings and transformations (PUBLIC CLASS METHOD)
|
1450
1079
|
def process_subscription_data(raw_data, config)
|
1451
1080
|
processed_data = {}
|
@@ -1491,7 +1120,7 @@ module NatsWave
|
|
1491
1120
|
elsif value.respond_to?(transformation)
|
1492
1121
|
value.send(transformation)
|
1493
1122
|
else
|
1494
|
-
|
1123
|
+
NatsWave.logger.warn "Transformation method #{transformation} not found"
|
1495
1124
|
value
|
1496
1125
|
end
|
1497
1126
|
else
|
@@ -1512,7 +1141,7 @@ module NatsWave
|
|
1512
1141
|
when 'delete', 'deleted', 'destroy', 'destroyed'
|
1513
1142
|
handle_auto_delete(processed_data, unique_fields)
|
1514
1143
|
else
|
1515
|
-
|
1144
|
+
NatsWave.logger.warn "Unknown action for auto-sync: #{action}"
|
1516
1145
|
end
|
1517
1146
|
end
|
1518
1147
|
|
@@ -1522,17 +1151,17 @@ module NatsWave
|
|
1522
1151
|
existing = find_by_unique_fields(data, unique_fields)
|
1523
1152
|
if existing
|
1524
1153
|
existing.update!(data)
|
1525
|
-
|
1154
|
+
NatsWave.logger.info "✅ Updated existing #{self.name}: #{existing.id}"
|
1526
1155
|
else
|
1527
1156
|
record = self.create!(data)
|
1528
|
-
|
1157
|
+
NatsWave.logger.info "✅ Created new #{self.name}: #{record.id}"
|
1529
1158
|
end
|
1530
1159
|
when :create_only
|
1531
1160
|
record = self.create!(data)
|
1532
|
-
|
1161
|
+
NatsWave.logger.info "✅ Created new #{self.name}: #{record.id}"
|
1533
1162
|
end
|
1534
1163
|
rescue => e
|
1535
|
-
|
1164
|
+
NatsWave.logger.error "❌ Failed to create #{self.name}: #{e.message}"
|
1536
1165
|
raise
|
1537
1166
|
end
|
1538
1167
|
|
@@ -1540,15 +1169,15 @@ module NatsWave
|
|
1540
1169
|
existing = find_by_unique_fields(data, unique_fields)
|
1541
1170
|
if existing
|
1542
1171
|
existing.update!(data)
|
1543
|
-
|
1172
|
+
NatsWave.logger.info "✅ Updated #{self.name}: #{existing.id}"
|
1544
1173
|
elsif sync_strategy == :upsert
|
1545
1174
|
record = self.create!(data)
|
1546
|
-
|
1175
|
+
NatsWave.logger.info "✅ Created new #{self.name} during update: #{record.id}"
|
1547
1176
|
else
|
1548
|
-
|
1177
|
+
NatsWave.logger.warn "⚠️ Record not found for update: #{data.slice(*unique_fields.map(&:to_s))}"
|
1549
1178
|
end
|
1550
1179
|
rescue => e
|
1551
|
-
|
1180
|
+
NatsWave.logger.error "❌ Failed to update #{self.name}: #{e.message}"
|
1552
1181
|
raise
|
1553
1182
|
end
|
1554
1183
|
|
@@ -1556,12 +1185,12 @@ module NatsWave
|
|
1556
1185
|
existing = find_by_unique_fields(data, unique_fields)
|
1557
1186
|
if existing
|
1558
1187
|
existing.destroy!
|
1559
|
-
|
1188
|
+
NatsWave.logger.info "✅ Deleted #{self.name}: #{existing.id}"
|
1560
1189
|
else
|
1561
|
-
|
1190
|
+
NatsWave.logger.warn "⚠️ Record not found for deletion: #{data.slice(*unique_fields.map(&:to_s))}"
|
1562
1191
|
end
|
1563
1192
|
rescue => e
|
1564
|
-
|
1193
|
+
NatsWave.logger.error "❌ Failed to delete #{self.name}: #{e.message}"
|
1565
1194
|
raise
|
1566
1195
|
end
|
1567
1196
|
|
@@ -1579,6 +1208,58 @@ module NatsWave
|
|
1579
1208
|
return nil if conditions.empty?
|
1580
1209
|
self.find_by(conditions)
|
1581
1210
|
end
|
1211
|
+
|
1212
|
+
private
|
1213
|
+
|
1214
|
+
# Setup ActiveRecord callbacks for auto-publishing (only once per model)
|
1215
|
+
def setup_publishing_callbacks_once
|
1216
|
+
# Only add callbacks if we're in ActiveRecord and haven't added them yet
|
1217
|
+
return unless defined?(ActiveRecord::Base) && self < ActiveRecord::Base
|
1218
|
+
return if @nats_wave_callbacks_added
|
1219
|
+
|
1220
|
+
after_commit :trigger_nats_wave_auto_publish_on_create, on: :create
|
1221
|
+
after_commit :trigger_nats_wave_auto_publish_on_update, on: :update
|
1222
|
+
after_commit :trigger_nats_wave_auto_publish_on_destroy, on: :destroy
|
1223
|
+
|
1224
|
+
@nats_wave_callbacks_added = true
|
1225
|
+
NatsWave.logger.debug "📤 #{self.name}: Added publishing callbacks"
|
1226
|
+
end
|
1227
|
+
|
1228
|
+
# Create a subscription handler that processes data and calls custom handler AFTER auto-sync
|
1229
|
+
def create_subscription_handler(config)
|
1230
|
+
model_class = self
|
1231
|
+
|
1232
|
+
lambda do |message|
|
1233
|
+
begin
|
1234
|
+
NatsWave.logger.debug "📨 Processing subscription message for #{model_class.name}"
|
1235
|
+
|
1236
|
+
# Extract the raw data
|
1237
|
+
raw_data = message['data'] || {}
|
1238
|
+
model_name = message['model']
|
1239
|
+
action = message['action']
|
1240
|
+
|
1241
|
+
# Process the data through mappings and transformations
|
1242
|
+
processed_data = model_class.process_subscription_data(raw_data, config)
|
1243
|
+
|
1244
|
+
# Always perform auto-sync first (if enabled)
|
1245
|
+
if config[:auto_sync]
|
1246
|
+
NatsWave.logger.debug "📨 Performing auto-sync first"
|
1247
|
+
model_class.perform_auto_sync(model_name, action, processed_data, config)
|
1248
|
+
end
|
1249
|
+
|
1250
|
+
# Then call custom handler if provided (optional)
|
1251
|
+
if config[:custom_handler]
|
1252
|
+
NatsWave.logger.debug "📨 Calling custom subscription handler after auto-sync"
|
1253
|
+
config[:custom_handler].call(model_name, action, processed_data, message)
|
1254
|
+
end
|
1255
|
+
|
1256
|
+
rescue => e
|
1257
|
+
NatsWave.logger.error "Error in subscription handler for #{model_class.name}: #{e.message}"
|
1258
|
+
NatsWave.logger.error e.backtrace.join("\n")
|
1259
|
+
raise
|
1260
|
+
end
|
1261
|
+
end
|
1262
|
+
end
|
1582
1263
|
end
|
1583
1264
|
|
1584
1265
|
# Instance methods for publishing
|
@@ -1591,7 +1272,7 @@ module NatsWave
|
|
1591
1272
|
|
1592
1273
|
# Check conditions (only for publishing)
|
1593
1274
|
if config[:conditions].any? && !evaluate_conditions(config[:conditions])
|
1594
|
-
|
1275
|
+
NatsWave.logger.debug "📤 Skipping publish - conditions not met"
|
1595
1276
|
next
|
1596
1277
|
end
|
1597
1278
|
|
@@ -1614,17 +1295,17 @@ module NatsWave
|
|
1614
1295
|
metadata: build_publishing_metadata
|
1615
1296
|
)
|
1616
1297
|
|
1617
|
-
|
1298
|
+
NatsWave.logger.info "📤 Published #{self.class.name} to #{subject}"
|
1618
1299
|
end
|
1619
1300
|
|
1620
1301
|
# Then call custom handler if provided (optional)
|
1621
1302
|
if config[:custom_handler]
|
1622
|
-
|
1303
|
+
NatsWave.logger.debug "📤 Calling custom publishing handler after publishing"
|
1623
1304
|
config[:custom_handler].call(self.class.name, action, processed_data, self)
|
1624
1305
|
end
|
1625
1306
|
end
|
1626
1307
|
rescue StandardError => e
|
1627
|
-
|
1308
|
+
NatsWave.logger.error("Failed to publish: #{e.message}")
|
1628
1309
|
# Don't re-raise to avoid breaking transactions
|
1629
1310
|
end
|
1630
1311
|
|
@@ -1633,7 +1314,7 @@ module NatsWave
|
|
1633
1314
|
return if @nats_wave_publishing_in_progress
|
1634
1315
|
@nats_wave_publishing_in_progress = true
|
1635
1316
|
|
1636
|
-
|
1317
|
+
NatsWave.logger.debug "🚀 Auto-publishing on create"
|
1637
1318
|
nats_wave_publish('create')
|
1638
1319
|
|
1639
1320
|
@nats_wave_publishing_in_progress = false
|
@@ -1643,7 +1324,7 @@ module NatsWave
|
|
1643
1324
|
return if @nats_wave_publishing_in_progress
|
1644
1325
|
@nats_wave_publishing_in_progress = true
|
1645
1326
|
|
1646
|
-
|
1327
|
+
NatsWave.logger.debug "🚀 Auto-publishing on update"
|
1647
1328
|
nats_wave_publish('update')
|
1648
1329
|
|
1649
1330
|
@nats_wave_publishing_in_progress = false
|
@@ -1653,7 +1334,7 @@ module NatsWave
|
|
1653
1334
|
return if @nats_wave_publishing_in_progress
|
1654
1335
|
@nats_wave_publishing_in_progress = true
|
1655
1336
|
|
1656
|
-
|
1337
|
+
NatsWave.logger.debug "🚀 Auto-publishing on destroy"
|
1657
1338
|
nats_wave_publish('destroy')
|
1658
1339
|
|
1659
1340
|
@nats_wave_publishing_in_progress = false
|
@@ -1682,31 +1363,49 @@ module NatsWave
|
|
1682
1363
|
end
|
1683
1364
|
end
|
1684
1365
|
|
1685
|
-
# Process publishing data - much simpler now! (INSTANCE METHOD)
|
1686
1366
|
def process_publishing_data(raw_data, config)
|
1687
1367
|
processed_data = {}
|
1688
|
-
field_mappings = config[:field_mappings]
|
1689
|
-
transformations = config[:transformations]
|
1690
|
-
skip_fields = config[:skip_fields]
|
1368
|
+
field_mappings = config[:field_mappings] || {}
|
1369
|
+
transformations = config[:transformations] || {}
|
1370
|
+
skip_fields = config[:skip_fields] || []
|
1371
|
+
only_mapped_fields = config[:only_mapped_fields]
|
1691
1372
|
|
1692
|
-
|
1693
|
-
|
1694
|
-
|
1695
|
-
|
1696
|
-
|
1697
|
-
|
1698
|
-
|
1699
|
-
|
1700
|
-
|
1701
|
-
|
1702
|
-
|
1703
|
-
|
1704
|
-
|
1705
|
-
|
1706
|
-
|
1707
|
-
|
1373
|
+
# If only_mapped_fields is true, only process fields that have explicit mappings
|
1374
|
+
if only_mapped_fields && field_mappings.any?
|
1375
|
+
field_mappings.each do |local_field, external_field|
|
1376
|
+
local_field_str = local_field.to_s
|
1377
|
+
|
1378
|
+
# Skip if this field is in skip_fields
|
1379
|
+
next if skip_fields.include?(local_field_str) || skip_fields.include?(local_field.to_sym)
|
1380
|
+
|
1381
|
+
# Get the value from raw_data
|
1382
|
+
value = raw_data[local_field_str] || raw_data[local_field.to_sym]
|
1383
|
+
|
1384
|
+
# Apply transformation if any (use external field name for transformation lookup)
|
1385
|
+
transformed_value = apply_publishing_transformation(value, external_field, transformations, raw_data)
|
1386
|
+
|
1387
|
+
processed_data[external_field.to_s] = transformed_value
|
1388
|
+
end
|
1389
|
+
else
|
1390
|
+
# Original behavior - process all fields
|
1391
|
+
raw_data.each do |field, value|
|
1392
|
+
field_str = field.to_s
|
1393
|
+
field_sym = field.to_sym
|
1394
|
+
|
1395
|
+
# Skip if in skip_fields
|
1396
|
+
next if skip_fields.include?(field_str) || skip_fields.include?(field_sym)
|
1397
|
+
|
1398
|
+
# Apply field mapping (local -> external) - ONLY if mapping exists
|
1399
|
+
mapped_field = field_mappings[field_str] ||
|
1400
|
+
field_mappings[field_sym] ||
|
1401
|
+
field # Use original field name if no mapping
|
1402
|
+
|
1403
|
+
# Apply transformation if any
|
1404
|
+
transformed_value = apply_publishing_transformation(value, mapped_field, transformations, raw_data)
|
1405
|
+
|
1406
|
+
processed_data[mapped_field.to_s] = transformed_value
|
1407
|
+
end
|
1708
1408
|
end
|
1709
|
-
|
1710
1409
|
processed_data
|
1711
1410
|
end
|
1712
1411
|
|
@@ -1726,7 +1425,7 @@ module NatsWave
|
|
1726
1425
|
elsif value.respond_to?(transformation)
|
1727
1426
|
value.send(transformation)
|
1728
1427
|
else
|
1729
|
-
|
1428
|
+
NatsWave.logger.warn "Publishing transformation method #{transformation} not found"
|
1730
1429
|
value
|
1731
1430
|
end
|
1732
1431
|
else
|