nats_wave 1.1.12 → 1.1.14
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/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 +75 -890
- 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,822 +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
|
-
# # frozen_string_literal: true
|
366
|
-
#
|
367
|
-
# module NatsWave
|
368
|
-
# module Concerns
|
369
|
-
# module Mappable
|
370
|
-
# extend ActiveSupport::Concern
|
371
|
-
#
|
372
|
-
# included do
|
373
|
-
# class_attribute :nats_wave_subscription_config, :nats_wave_publishing_config
|
374
|
-
# self.nats_wave_subscription_config = {}
|
375
|
-
# self.nats_wave_publishing_config = {}
|
376
|
-
# end
|
377
|
-
#
|
378
|
-
# class_methods do
|
379
|
-
# # Configure subscriptions with optional custom handler
|
380
|
-
# def nats_wave_subscribes_to(*subjects, **options, &block)
|
381
|
-
# Rails.logger.debug "📡 #{self.name}: Setting up subscription to subjects: #{subjects.inspect}" if defined?(Rails)
|
382
|
-
#
|
383
|
-
# handler = options[:handler] || block
|
384
|
-
#
|
385
|
-
# subscription_config = {
|
386
|
-
# subjects: subjects.flatten,
|
387
|
-
# field_mappings: options[:field_mappings] || {},
|
388
|
-
# transformations: options[:transformations] || {},
|
389
|
-
# skip_fields: options[:skip_fields] || [],
|
390
|
-
# unique_fields: options[:unique_fields] || [:id],
|
391
|
-
# sync_strategy: options[:sync_strategy] || :upsert,
|
392
|
-
# handler: handler,
|
393
|
-
# queue_group: options[:queue_group],
|
394
|
-
# auto_sync: options[:auto_sync] != false # Default to true, can be disabled
|
395
|
-
# }
|
396
|
-
#
|
397
|
-
# # Store the config
|
398
|
-
# config_key = subjects.join(',')
|
399
|
-
# self.nats_wave_subscription_config[config_key] = subscription_config
|
400
|
-
#
|
401
|
-
# # Create the subscription handler
|
402
|
-
# processed_handler = create_subscription_handler(subscription_config)
|
403
|
-
#
|
404
|
-
# # Register the subscription
|
405
|
-
# NatsWave::ModelRegistry.register_subscription(
|
406
|
-
# subjects: subjects.flatten,
|
407
|
-
# model: self.name,
|
408
|
-
# handler: processed_handler,
|
409
|
-
# queue_group: subscription_config[:queue_group]
|
410
|
-
# )
|
411
|
-
#
|
412
|
-
# Rails.logger.debug "📡 #{self.name}: Registered subscription for subjects: #{subjects}" if defined?(Rails)
|
413
|
-
# end
|
414
|
-
#
|
415
|
-
# # Configure publishing with optional custom handler AND auto-publishing callbacks
|
416
|
-
# def nats_wave_publishes_to(*subjects, **options, &block)
|
417
|
-
# Rails.logger.debug "📤 #{self.name}: Setting up publishing to subjects: #{subjects.inspect}" if defined?(Rails)
|
418
|
-
#
|
419
|
-
# handler = options[:handler] || block
|
420
|
-
#
|
421
|
-
# publishing_config = {
|
422
|
-
# subjects: subjects.flatten,
|
423
|
-
# field_mappings: options[:field_mappings] || {},
|
424
|
-
# transformations: options[:transformations] || {},
|
425
|
-
# skip_fields: options[:skip_fields] || [],
|
426
|
-
# conditions: options[:conditions] || {},
|
427
|
-
# actions: options[:actions] || [:create, :update, :destroy],
|
428
|
-
# handler: handler,
|
429
|
-
# async: options[:async] != false, # Default to true
|
430
|
-
# enabled: options[:enabled] != false # Default to true
|
431
|
-
# }
|
432
|
-
#
|
433
|
-
# # Store the config
|
434
|
-
# config_key = subjects.join(',')
|
435
|
-
# self.nats_wave_publishing_config[config_key] = publishing_config
|
436
|
-
#
|
437
|
-
# # Add ActiveRecord callbacks for auto-publishing
|
438
|
-
# setup_publishing_callbacks(publishing_config)
|
439
|
-
#
|
440
|
-
# Rails.logger.debug "📤 #{self.name}: Registered publishing config for subjects: #{subjects}" if defined?(Rails)
|
441
|
-
# end
|
442
|
-
#
|
443
|
-
# # Get all subscription configurations
|
444
|
-
# def nats_wave_subscription_configs
|
445
|
-
# nats_wave_subscription_config
|
446
|
-
# end
|
447
|
-
#
|
448
|
-
# # Get all publishing configurations
|
449
|
-
# def nats_wave_publishing_configs
|
450
|
-
# nats_wave_publishing_config
|
451
|
-
# end
|
452
|
-
#
|
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
|
-
# private
|
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
|
-
#
|
485
|
-
# # Create a subscription handler that processes data and calls custom handler or auto-sync
|
486
|
-
# def create_subscription_handler(config)
|
487
|
-
# model_class = self
|
488
|
-
#
|
489
|
-
# lambda do |message|
|
490
|
-
# begin
|
491
|
-
# Rails.logger.debug "📨 Processing subscription message for #{model_class.name}" if defined?(Rails)
|
492
|
-
#
|
493
|
-
# # Extract the raw data
|
494
|
-
# raw_data = message['data'] || {}
|
495
|
-
# model_name = message['model']
|
496
|
-
# action = message['action']
|
497
|
-
#
|
498
|
-
# # Process the data through mappings and transformations
|
499
|
-
# processed_data = process_subscription_data(raw_data, config)
|
500
|
-
#
|
501
|
-
# # If custom handler is provided, call it with model_name, action, processed_data
|
502
|
-
# if config[:handler]
|
503
|
-
# Rails.logger.debug "📨 Calling custom subscription handler" if defined?(Rails)
|
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
|
508
|
-
# model_class.perform_auto_sync(model_name, action, processed_data, config)
|
509
|
-
# else
|
510
|
-
# Rails.logger.debug "📨 No handler provided and auto_sync disabled" if defined?(Rails)
|
511
|
-
# end
|
512
|
-
#
|
513
|
-
# rescue => e
|
514
|
-
# Rails.logger.error "Error in subscription handler for #{model_class.name}: #{e.message}" if defined?(Rails)
|
515
|
-
# Rails.logger.error e.backtrace.join("\n") if defined?(Rails)
|
516
|
-
# raise
|
517
|
-
# end
|
518
|
-
# end
|
519
|
-
# end
|
520
|
-
#
|
521
|
-
# # Process subscription data through field mappings and transformations (CLASS METHOD)
|
522
|
-
# def process_subscription_data(raw_data, config)
|
523
|
-
# processed_data = {}
|
524
|
-
# field_mappings = config[:field_mappings]
|
525
|
-
# transformations = config[:transformations]
|
526
|
-
# skip_fields = config[:skip_fields]
|
527
|
-
#
|
528
|
-
# raw_data.each do |field, value|
|
529
|
-
# field_str = field.to_s
|
530
|
-
# field_sym = field.to_sym
|
531
|
-
#
|
532
|
-
# # Skip if in skip_fields
|
533
|
-
# next if skip_fields.include?(field_str) || skip_fields.include?(field_sym)
|
534
|
-
#
|
535
|
-
# # Apply field mapping
|
536
|
-
# mapped_field = field_mappings[field_str] ||
|
537
|
-
# field_mappings[field_sym] ||
|
538
|
-
# field
|
539
|
-
#
|
540
|
-
# # Apply transformation if any
|
541
|
-
# transformed_value = apply_transformation(value, mapped_field, transformations, raw_data)
|
542
|
-
#
|
543
|
-
# processed_data[mapped_field.to_s] = transformed_value
|
544
|
-
# end
|
545
|
-
#
|
546
|
-
# processed_data
|
547
|
-
# end
|
548
|
-
#
|
549
|
-
# # Apply transformations to field values (CLASS METHOD)
|
550
|
-
# def apply_transformation(value, field, transformations, full_record)
|
551
|
-
# transformation = transformations[field] || transformations[field.to_sym]
|
552
|
-
#
|
553
|
-
# case transformation
|
554
|
-
# when Proc
|
555
|
-
# if transformation.arity == 2 || transformation.arity < 0
|
556
|
-
# transformation.call(value, full_record)
|
557
|
-
# else
|
558
|
-
# transformation.call(value)
|
559
|
-
# end
|
560
|
-
# when Symbol
|
561
|
-
# if self.respond_to?(transformation, true)
|
562
|
-
# self.send(transformation, value)
|
563
|
-
# elsif value.respond_to?(transformation)
|
564
|
-
# value.send(transformation)
|
565
|
-
# else
|
566
|
-
# Rails.logger.warn "Transformation method #{transformation} not found" if defined?(Rails)
|
567
|
-
# value
|
568
|
-
# end
|
569
|
-
# else
|
570
|
-
# value
|
571
|
-
# end
|
572
|
-
# end
|
573
|
-
#
|
574
|
-
# # Default auto-sync behavior (CLASS METHOD)
|
575
|
-
# def perform_auto_sync(model_name, action, processed_data, config)
|
576
|
-
# unique_fields = config[:unique_fields]
|
577
|
-
# sync_strategy = config[:sync_strategy]
|
578
|
-
#
|
579
|
-
# case action.to_s.downcase
|
580
|
-
# when 'create', 'created'
|
581
|
-
# handle_auto_create(processed_data, unique_fields, sync_strategy)
|
582
|
-
# when 'update', 'updated'
|
583
|
-
# handle_auto_update(processed_data, unique_fields, sync_strategy)
|
584
|
-
# when 'delete', 'deleted', 'destroy', 'destroyed'
|
585
|
-
# handle_auto_delete(processed_data, unique_fields)
|
586
|
-
# else
|
587
|
-
# Rails.logger.warn "Unknown action for auto-sync: #{action}" if defined?(Rails)
|
588
|
-
# end
|
589
|
-
# end
|
590
|
-
#
|
591
|
-
# def handle_auto_create(data, unique_fields, sync_strategy)
|
592
|
-
# case sync_strategy
|
593
|
-
# when :upsert
|
594
|
-
# existing = find_by_unique_fields(data, unique_fields)
|
595
|
-
# if existing
|
596
|
-
# existing.update!(data)
|
597
|
-
# Rails.logger.info "✅ Updated existing #{self.name}: #{existing.id}" if defined?(Rails)
|
598
|
-
# else
|
599
|
-
# record = self.create!(data)
|
600
|
-
# Rails.logger.info "✅ Created new #{self.name}: #{record.id}" if defined?(Rails)
|
601
|
-
# end
|
602
|
-
# when :create_only
|
603
|
-
# record = self.create!(data)
|
604
|
-
# Rails.logger.info "✅ Created new #{self.name}: #{record.id}" if defined?(Rails)
|
605
|
-
# end
|
606
|
-
# rescue => e
|
607
|
-
# Rails.logger.error "❌ Failed to create #{self.name}: #{e.message}" if defined?(Rails)
|
608
|
-
# raise
|
609
|
-
# end
|
610
|
-
#
|
611
|
-
# def handle_auto_update(data, unique_fields, sync_strategy)
|
612
|
-
# existing = find_by_unique_fields(data, unique_fields)
|
613
|
-
# if existing
|
614
|
-
# existing.update!(data)
|
615
|
-
# Rails.logger.info "✅ Updated #{self.name}: #{existing.id}" if defined?(Rails)
|
616
|
-
# elsif sync_strategy == :upsert
|
617
|
-
# record = self.create!(data)
|
618
|
-
# Rails.logger.info "✅ Created new #{self.name} during update: #{record.id}" if defined?(Rails)
|
619
|
-
# else
|
620
|
-
# Rails.logger.warn "⚠️ Record not found for update: #{data.slice(*unique_fields.map(&:to_s))}" if defined?(Rails)
|
621
|
-
# end
|
622
|
-
# rescue => e
|
623
|
-
# Rails.logger.error "❌ Failed to update #{self.name}: #{e.message}" if defined?(Rails)
|
624
|
-
# raise
|
625
|
-
# end
|
626
|
-
#
|
627
|
-
# def handle_auto_delete(data, unique_fields)
|
628
|
-
# existing = find_by_unique_fields(data, unique_fields)
|
629
|
-
# if existing
|
630
|
-
# existing.destroy!
|
631
|
-
# Rails.logger.info "✅ Deleted #{self.name}: #{existing.id}" if defined?(Rails)
|
632
|
-
# else
|
633
|
-
# Rails.logger.warn "⚠️ Record not found for deletion: #{data.slice(*unique_fields.map(&:to_s))}" if defined?(Rails)
|
634
|
-
# end
|
635
|
-
# rescue => e
|
636
|
-
# Rails.logger.error "❌ Failed to delete #{self.name}: #{e.message}" if defined?(Rails)
|
637
|
-
# raise
|
638
|
-
# end
|
639
|
-
#
|
640
|
-
# def find_by_unique_fields(data, unique_fields)
|
641
|
-
# conditions = {}
|
642
|
-
# unique_fields.each do |field|
|
643
|
-
# field_str = field.to_s
|
644
|
-
# if data.key?(field_str)
|
645
|
-
# conditions[field] = data[field_str]
|
646
|
-
# elsif data.key?(field.to_sym)
|
647
|
-
# conditions[field] = data[field.to_sym]
|
648
|
-
# end
|
649
|
-
# end
|
650
|
-
#
|
651
|
-
# return nil if conditions.empty?
|
652
|
-
# self.find_by(conditions)
|
653
|
-
# end
|
654
|
-
# end
|
655
|
-
#
|
656
|
-
# # Instance methods for publishing
|
657
|
-
# def nats_wave_publish(action = nil)
|
658
|
-
# action ||= determine_action_from_context
|
659
|
-
#
|
660
|
-
# self.class.nats_wave_publishing_configs.each do |subjects_key, config|
|
661
|
-
# next unless config[:enabled]
|
662
|
-
# next unless config[:actions].include?(action.to_sym)
|
663
|
-
#
|
664
|
-
# # Check conditions (only for publishing)
|
665
|
-
# if config[:conditions].any? && !evaluate_conditions(config[:conditions])
|
666
|
-
# Rails.logger.debug "📤 Skipping publish - conditions not met" if defined?(Rails)
|
667
|
-
# next
|
668
|
-
# end
|
669
|
-
#
|
670
|
-
# # Get the raw data (current model attributes)
|
671
|
-
# raw_data = get_raw_attributes
|
672
|
-
#
|
673
|
-
# # Process the data through mappings and transformations
|
674
|
-
# processed_data = process_publishing_data(raw_data, config)
|
675
|
-
#
|
676
|
-
# # If custom handler is provided, call it
|
677
|
-
# if config[:handler]
|
678
|
-
# Rails.logger.debug "📤 Calling custom publishing handler" if defined?(Rails)
|
679
|
-
# config[:handler].call(self.class.name, action, processed_data, self)
|
680
|
-
# else
|
681
|
-
# # Default publishing behavior - publish the processed data
|
682
|
-
# config[:subjects].each do |subject|
|
683
|
-
# NatsWave.client.publish(
|
684
|
-
# subject: subject,
|
685
|
-
# model: self.class.name,
|
686
|
-
# action: action,
|
687
|
-
# data: processed_data, # This is the processed data, not raw attributes
|
688
|
-
# metadata: build_publishing_metadata
|
689
|
-
# )
|
690
|
-
#
|
691
|
-
# Rails.logger.info "📤 Published #{self.class.name}##{id} to #{subject} (#{action})" if defined?(Rails)
|
692
|
-
# end
|
693
|
-
# end
|
694
|
-
# end
|
695
|
-
# rescue StandardError => e
|
696
|
-
# Rails.logger.error("Failed to publish: #{e.message}") if defined?(Rails)
|
697
|
-
# # Don't re-raise to avoid breaking transactions
|
698
|
-
# end
|
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
|
-
#
|
716
|
-
# private
|
717
|
-
#
|
718
|
-
# def determine_action_from_context
|
719
|
-
# # Try to determine action from ActiveRecord context
|
720
|
-
# if defined?(ActiveRecord) && is_a?(ActiveRecord::Base)
|
721
|
-
# return 'create' if previously_new_record?
|
722
|
-
# return 'update' if saved_changes.any?
|
723
|
-
# return 'destroy' if destroyed?
|
724
|
-
# end
|
725
|
-
#
|
726
|
-
# 'update' # Default fallback
|
727
|
-
# end
|
728
|
-
#
|
729
|
-
# def get_raw_attributes
|
730
|
-
# # Get attributes (works for both ActiveRecord and other objects)
|
731
|
-
# if respond_to?(:attributes)
|
732
|
-
# attributes
|
733
|
-
# else
|
734
|
-
# # For non-ActiveRecord objects, get instance variables
|
735
|
-
# instance_variables.map { |v| [v.to_s.delete('@'), instance_variable_get(v)] }.to_h
|
736
|
-
# end
|
737
|
-
# end
|
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
|
-
#
|
791
|
-
# def evaluate_conditions(conditions)
|
792
|
-
# conditions.all? do |condition, expected_value|
|
793
|
-
# case condition
|
794
|
-
# when Proc
|
795
|
-
# condition.call(self)
|
796
|
-
# when Symbol, String
|
797
|
-
# if respond_to?(condition, true)
|
798
|
-
# result = send(condition)
|
799
|
-
# expected_value.nil? ? result : result == expected_value
|
800
|
-
# else
|
801
|
-
# false
|
802
|
-
# end
|
803
|
-
# else
|
804
|
-
# false
|
805
|
-
# end
|
806
|
-
# end
|
807
|
-
# end
|
808
|
-
#
|
809
|
-
# def build_publishing_metadata
|
810
|
-
# {
|
811
|
-
# source_model: self.class.name,
|
812
|
-
# source_id: respond_to?(:id) ? id : object_id,
|
813
|
-
# published_at: Time.current.iso8601
|
814
|
-
# }
|
815
|
-
# end
|
816
|
-
# end
|
817
|
-
# end
|
818
|
-
# end
|
819
|
-
|
820
1
|
# frozen_string_literal: true
|
821
2
|
|
822
3
|
module NatsWave
|
@@ -833,7 +14,7 @@ module NatsWave
|
|
833
14
|
class_methods do
|
834
15
|
# Configure subscriptions with optional custom handler
|
835
16
|
def nats_wave_subscribes_to(*subjects, **options, &block)
|
836
|
-
|
17
|
+
NatsWave.logger.debug "📡 #{self.name}: Setting up subscription to subjects: #{subjects.inspect}"
|
837
18
|
|
838
19
|
# Custom handler is now optional and runs AFTER default behavior
|
839
20
|
custom_handler = options[:handler] || block
|
@@ -865,12 +46,12 @@ module NatsWave
|
|
865
46
|
queue_group: subscription_config[:queue_group]
|
866
47
|
)
|
867
48
|
|
868
|
-
|
49
|
+
NatsWave.logger.debug "📡 #{self.name}: Registered subscription for subjects: #{subjects}"
|
869
50
|
end
|
870
51
|
|
871
52
|
# Configure publishing with optional custom handler AND auto-publishing callbacks
|
872
53
|
def nats_wave_publishes_to(*subjects, **options, &block)
|
873
|
-
|
54
|
+
NatsWave.logger.debug "📤 #{self.name}: Setting up publishing to subjects: #{subjects.inspect}"
|
874
55
|
|
875
56
|
# Custom handler is now optional and runs AFTER default behavior
|
876
57
|
custom_handler = options[:handler] || block
|
@@ -885,7 +66,7 @@ module NatsWave
|
|
885
66
|
custom_handler: custom_handler, # Renamed to be clear it's optional
|
886
67
|
async: options[:async] != false, # Default to true
|
887
68
|
enabled: options[:enabled] != false, # Default to true
|
888
|
-
only_mapped_fields: options[:only_mapped_fields] != false # Default to true
|
69
|
+
only_mapped_fields: options[:only_mapped_fields] != false, # Default to true
|
889
70
|
}
|
890
71
|
|
891
72
|
# Store the config
|
@@ -895,7 +76,7 @@ module NatsWave
|
|
895
76
|
# Add ActiveRecord callbacks for auto-publishing (only once per model)
|
896
77
|
setup_publishing_callbacks_once
|
897
78
|
|
898
|
-
|
79
|
+
NatsWave.logger.debug "📤 #{self.name}: Registered publishing config for subjects: #{subjects}"
|
899
80
|
end
|
900
81
|
|
901
82
|
# Get all subscription configurations
|
@@ -921,7 +102,7 @@ module NatsWave
|
|
921
102
|
after_commit :trigger_nats_wave_auto_publish_on_destroy, on: :destroy
|
922
103
|
|
923
104
|
@nats_wave_callbacks_added = true
|
924
|
-
|
105
|
+
NatsWave.logger.debug "📤 #{self.name}: Added publishing callbacks"
|
925
106
|
end
|
926
107
|
|
927
108
|
# Create a subscription handler that processes data and calls custom handler AFTER auto-sync
|
@@ -930,7 +111,7 @@ module NatsWave
|
|
930
111
|
|
931
112
|
lambda do |message|
|
932
113
|
begin
|
933
|
-
|
114
|
+
NatsWave.logger.debug "📨 Processing subscription message for #{model_class.name}"
|
934
115
|
|
935
116
|
# Extract the raw data
|
936
117
|
raw_data = message['data'] || {}
|
@@ -938,29 +119,29 @@ module NatsWave
|
|
938
119
|
action = message['action']
|
939
120
|
|
940
121
|
# Process the data through mappings and transformations
|
941
|
-
processed_data = process_subscription_data(raw_data, config)
|
122
|
+
processed_data = model_class.process_subscription_data(raw_data, config)
|
942
123
|
|
943
124
|
# Always perform auto-sync first (if enabled)
|
944
125
|
if config[:auto_sync]
|
945
|
-
|
126
|
+
NatsWave.logger.debug "📨 Performing auto-sync first"
|
946
127
|
model_class.perform_auto_sync(model_name, action, processed_data, config)
|
947
128
|
end
|
948
129
|
|
949
130
|
# Then call custom handler if provided (optional)
|
950
131
|
if config[:custom_handler]
|
951
|
-
|
132
|
+
NatsWave.logger.debug "📨 Calling custom subscription handler after auto-sync"
|
952
133
|
config[:custom_handler].call(model_name, action, processed_data, message)
|
953
134
|
end
|
954
135
|
|
955
136
|
rescue => e
|
956
|
-
|
957
|
-
|
137
|
+
NatsWave.logger.error "Error in subscription handler for #{model_class.name}: #{e.message}"
|
138
|
+
NatsWave.logger.error e.backtrace.join("\n")
|
958
139
|
raise
|
959
140
|
end
|
960
141
|
end
|
961
142
|
end
|
962
143
|
|
963
|
-
# Process subscription data through field mappings and transformations (CLASS METHOD)
|
144
|
+
# Process subscription data through field mappings and transformations (PUBLIC CLASS METHOD)
|
964
145
|
def process_subscription_data(raw_data, config)
|
965
146
|
processed_data = {}
|
966
147
|
field_mappings = config[:field_mappings]
|
@@ -974,7 +155,7 @@ module NatsWave
|
|
974
155
|
# Skip if in skip_fields
|
975
156
|
next if skip_fields.include?(field_str) || skip_fields.include?(field_sym)
|
976
157
|
|
977
|
-
# Apply field mapping
|
158
|
+
# Apply field mapping (external -> local)
|
978
159
|
mapped_field = field_mappings[field_str] ||
|
979
160
|
field_mappings[field_sym] ||
|
980
161
|
field
|
@@ -988,7 +169,7 @@ module NatsWave
|
|
988
169
|
processed_data
|
989
170
|
end
|
990
171
|
|
991
|
-
# Apply transformations to field values (CLASS METHOD)
|
172
|
+
# Apply transformations to field values (PUBLIC CLASS METHOD)
|
992
173
|
def apply_transformation(value, field, transformations, full_record)
|
993
174
|
transformation = transformations[field] || transformations[field.to_sym]
|
994
175
|
|
@@ -1005,7 +186,7 @@ module NatsWave
|
|
1005
186
|
elsif value.respond_to?(transformation)
|
1006
187
|
value.send(transformation)
|
1007
188
|
else
|
1008
|
-
|
189
|
+
NatsWave.logger.warn "Transformation method #{transformation} not found"
|
1009
190
|
value
|
1010
191
|
end
|
1011
192
|
else
|
@@ -1013,7 +194,7 @@ module NatsWave
|
|
1013
194
|
end
|
1014
195
|
end
|
1015
196
|
|
1016
|
-
# Default auto-sync behavior (CLASS METHOD)
|
197
|
+
# Default auto-sync behavior (PUBLIC CLASS METHOD)
|
1017
198
|
def perform_auto_sync(model_name, action, processed_data, config)
|
1018
199
|
unique_fields = config[:unique_fields]
|
1019
200
|
sync_strategy = config[:sync_strategy]
|
@@ -1026,7 +207,7 @@ module NatsWave
|
|
1026
207
|
when 'delete', 'deleted', 'destroy', 'destroyed'
|
1027
208
|
handle_auto_delete(processed_data, unique_fields)
|
1028
209
|
else
|
1029
|
-
|
210
|
+
NatsWave.logger.warn "Unknown action for auto-sync: #{action}"
|
1030
211
|
end
|
1031
212
|
end
|
1032
213
|
|
@@ -1036,17 +217,17 @@ module NatsWave
|
|
1036
217
|
existing = find_by_unique_fields(data, unique_fields)
|
1037
218
|
if existing
|
1038
219
|
existing.update!(data)
|
1039
|
-
|
220
|
+
NatsWave.logger.info "✅ Updated existing #{self.name}: #{existing.id}"
|
1040
221
|
else
|
1041
222
|
record = self.create!(data)
|
1042
|
-
|
223
|
+
NatsWave.logger.info "✅ Created new #{self.name}: #{record.id}"
|
1043
224
|
end
|
1044
225
|
when :create_only
|
1045
226
|
record = self.create!(data)
|
1046
|
-
|
227
|
+
NatsWave.logger.info "✅ Created new #{self.name}: #{record.id}"
|
1047
228
|
end
|
1048
229
|
rescue => e
|
1049
|
-
|
230
|
+
NatsWave.logger.error "❌ Failed to create #{self.name}: #{e.message}"
|
1050
231
|
raise
|
1051
232
|
end
|
1052
233
|
|
@@ -1054,15 +235,15 @@ module NatsWave
|
|
1054
235
|
existing = find_by_unique_fields(data, unique_fields)
|
1055
236
|
if existing
|
1056
237
|
existing.update!(data)
|
1057
|
-
|
238
|
+
NatsWave.logger.info "✅ Updated #{self.name}: #{existing.id}"
|
1058
239
|
elsif sync_strategy == :upsert
|
1059
240
|
record = self.create!(data)
|
1060
|
-
|
241
|
+
NatsWave.logger.info "✅ Created new #{self.name} during update: #{record.id}"
|
1061
242
|
else
|
1062
|
-
|
243
|
+
NatsWave.logger.warn "⚠️ Record not found for update: #{data.slice(*unique_fields.map(&:to_s))}"
|
1063
244
|
end
|
1064
245
|
rescue => e
|
1065
|
-
|
246
|
+
NatsWave.logger.error "❌ Failed to update #{self.name}: #{e.message}"
|
1066
247
|
raise
|
1067
248
|
end
|
1068
249
|
|
@@ -1070,12 +251,12 @@ module NatsWave
|
|
1070
251
|
existing = find_by_unique_fields(data, unique_fields)
|
1071
252
|
if existing
|
1072
253
|
existing.destroy!
|
1073
|
-
|
254
|
+
NatsWave.logger.info "✅ Deleted #{self.name}: #{existing.id}"
|
1074
255
|
else
|
1075
|
-
|
256
|
+
NatsWave.logger.warn "⚠️ Record not found for deletion: #{data.slice(*unique_fields.map(&:to_s))}"
|
1076
257
|
end
|
1077
258
|
rescue => e
|
1078
|
-
|
259
|
+
NatsWave.logger.error "❌ Failed to delete #{self.name}: #{e.message}"
|
1079
260
|
raise
|
1080
261
|
end
|
1081
262
|
|
@@ -1105,7 +286,7 @@ module NatsWave
|
|
1105
286
|
|
1106
287
|
# Check conditions (only for publishing)
|
1107
288
|
if config[:conditions].any? && !evaluate_conditions(config[:conditions])
|
1108
|
-
|
289
|
+
NatsWave.logger.debug "📤 Skipping publish - conditions not met"
|
1109
290
|
next
|
1110
291
|
end
|
1111
292
|
|
@@ -1128,17 +309,17 @@ module NatsWave
|
|
1128
309
|
metadata: build_publishing_metadata
|
1129
310
|
)
|
1130
311
|
|
1131
|
-
|
312
|
+
NatsWave.logger.info "📤 Published #{self.class.name} to #{subject}"
|
1132
313
|
end
|
1133
314
|
|
1134
315
|
# Then call custom handler if provided (optional)
|
1135
316
|
if config[:custom_handler]
|
1136
|
-
|
317
|
+
NatsWave.logger.debug "📤 Calling custom publishing handler after publishing"
|
1137
318
|
config[:custom_handler].call(self.class.name, action, processed_data, self)
|
1138
319
|
end
|
1139
320
|
end
|
1140
321
|
rescue StandardError => e
|
1141
|
-
|
322
|
+
NatsWave.logger.error("Failed to publish: #{e.message}")
|
1142
323
|
# Don't re-raise to avoid breaking transactions
|
1143
324
|
end
|
1144
325
|
|
@@ -1147,7 +328,7 @@ module NatsWave
|
|
1147
328
|
return if @nats_wave_publishing_in_progress
|
1148
329
|
@nats_wave_publishing_in_progress = true
|
1149
330
|
|
1150
|
-
|
331
|
+
NatsWave.logger.debug "🚀 Auto-publishing on create"
|
1151
332
|
nats_wave_publish('create')
|
1152
333
|
|
1153
334
|
@nats_wave_publishing_in_progress = false
|
@@ -1157,7 +338,7 @@ module NatsWave
|
|
1157
338
|
return if @nats_wave_publishing_in_progress
|
1158
339
|
@nats_wave_publishing_in_progress = true
|
1159
340
|
|
1160
|
-
|
341
|
+
NatsWave.logger.debug "🚀 Auto-publishing on update"
|
1161
342
|
nats_wave_publish('update')
|
1162
343
|
|
1163
344
|
@nats_wave_publishing_in_progress = false
|
@@ -1167,7 +348,7 @@ module NatsWave
|
|
1167
348
|
return if @nats_wave_publishing_in_progress
|
1168
349
|
@nats_wave_publishing_in_progress = true
|
1169
350
|
|
1170
|
-
|
351
|
+
NatsWave.logger.debug "🚀 Auto-publishing on destroy"
|
1171
352
|
nats_wave_publish('destroy')
|
1172
353
|
|
1173
354
|
@nats_wave_publishing_in_progress = false
|
@@ -1196,45 +377,49 @@ module NatsWave
|
|
1196
377
|
end
|
1197
378
|
end
|
1198
379
|
|
1199
|
-
# Process publishing data through field mappings and transformations (INSTANCE METHOD)
|
1200
380
|
def process_publishing_data(raw_data, config)
|
1201
381
|
processed_data = {}
|
1202
|
-
field_mappings = config[:field_mappings]
|
1203
|
-
transformations = config[:transformations]
|
1204
|
-
skip_fields = config[:skip_fields]
|
382
|
+
field_mappings = config[:field_mappings] || {}
|
383
|
+
transformations = config[:transformations] || {}
|
384
|
+
skip_fields = config[:skip_fields] || []
|
1205
385
|
only_mapped_fields = config[:only_mapped_fields]
|
1206
386
|
|
1207
|
-
# If only_mapped_fields is true, only process fields that have mappings
|
1208
|
-
|
1209
|
-
|
1210
|
-
|
1211
|
-
|
1212
|
-
|
1213
|
-
|
1214
|
-
|
1215
|
-
|
1216
|
-
|
1217
|
-
|
1218
|
-
|
1219
|
-
|
1220
|
-
|
1221
|
-
|
1222
|
-
|
1223
|
-
|
1224
|
-
#
|
1225
|
-
|
1226
|
-
|
1227
|
-
|
1228
|
-
|
1229
|
-
|
1230
|
-
|
1231
|
-
|
1232
|
-
|
1233
|
-
|
1234
|
-
|
1235
|
-
|
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
|
391
|
+
|
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)
|
394
|
+
|
395
|
+
# Get the value from raw_data
|
396
|
+
value = raw_data[local_field_str] || raw_data[local_field.to_sym]
|
397
|
+
|
398
|
+
# Apply transformation if any (use external field name for transformation lookup)
|
399
|
+
transformed_value = apply_publishing_transformation(value, external_field, transformations, raw_data)
|
400
|
+
|
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)
|
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
|
1236
422
|
end
|
1237
|
-
|
1238
423
|
processed_data
|
1239
424
|
end
|
1240
425
|
|
@@ -1254,7 +439,7 @@ module NatsWave
|
|
1254
439
|
elsif value.respond_to?(transformation)
|
1255
440
|
value.send(transformation)
|
1256
441
|
else
|
1257
|
-
|
442
|
+
NatsWave.logger.warn "Publishing transformation method #{transformation} not found"
|
1258
443
|
value
|
1259
444
|
end
|
1260
445
|
else
|