nats_wave 1.1.13 → 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 +72 -1359
- 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 +1 -1
@@ -1,1309 +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
|
-
# # frozen_string_literal: true
|
821
|
-
#
|
822
|
-
# module NatsWave
|
823
|
-
# module Concerns
|
824
|
-
# module Mappable
|
825
|
-
# extend ActiveSupport::Concern
|
826
|
-
#
|
827
|
-
# included do
|
828
|
-
# class_attribute :nats_wave_subscription_config, :nats_wave_publishing_config
|
829
|
-
# self.nats_wave_subscription_config = {}
|
830
|
-
# self.nats_wave_publishing_config = {}
|
831
|
-
# end
|
832
|
-
#
|
833
|
-
# class_methods do
|
834
|
-
# # Configure subscriptions with optional custom handler
|
835
|
-
# def nats_wave_subscribes_to(*subjects, **options, &block)
|
836
|
-
# Rails.logger.debug "📡 #{self.name}: Setting up subscription to subjects: #{subjects.inspect}" if defined?(Rails)
|
837
|
-
#
|
838
|
-
# # Custom handler is now optional and runs AFTER default behavior
|
839
|
-
# custom_handler = options[:handler] || block
|
840
|
-
#
|
841
|
-
# subscription_config = {
|
842
|
-
# subjects: subjects.flatten,
|
843
|
-
# field_mappings: options[:field_mappings] || {},
|
844
|
-
# transformations: options[:transformations] || {},
|
845
|
-
# skip_fields: options[:skip_fields] || [],
|
846
|
-
# unique_fields: options[:unique_fields] || [:id],
|
847
|
-
# sync_strategy: options[:sync_strategy] || :upsert,
|
848
|
-
# custom_handler: custom_handler, # Renamed to be clear it's optional
|
849
|
-
# queue_group: options[:queue_group],
|
850
|
-
# auto_sync: options[:auto_sync] != false # Default to true, can be disabled
|
851
|
-
# }
|
852
|
-
#
|
853
|
-
# # Store the config
|
854
|
-
# config_key = subjects.join(',')
|
855
|
-
# self.nats_wave_subscription_config[config_key] = subscription_config
|
856
|
-
#
|
857
|
-
# # Create the subscription handler
|
858
|
-
# processed_handler = create_subscription_handler(subscription_config)
|
859
|
-
#
|
860
|
-
# # Register the subscription
|
861
|
-
# NatsWave::ModelRegistry.register_subscription(
|
862
|
-
# subjects: subjects.flatten,
|
863
|
-
# model: self.name,
|
864
|
-
# handler: processed_handler,
|
865
|
-
# queue_group: subscription_config[:queue_group]
|
866
|
-
# )
|
867
|
-
#
|
868
|
-
# Rails.logger.debug "📡 #{self.name}: Registered subscription for subjects: #{subjects}" if defined?(Rails)
|
869
|
-
# end
|
870
|
-
#
|
871
|
-
# # Configure publishing with optional custom handler AND auto-publishing callbacks
|
872
|
-
# def nats_wave_publishes_to(*subjects, **options, &block)
|
873
|
-
# Rails.logger.debug "📤 #{self.name}: Setting up publishing to subjects: #{subjects.inspect}" if defined?(Rails)
|
874
|
-
#
|
875
|
-
# # Custom handler is now optional and runs AFTER default behavior
|
876
|
-
# custom_handler = options[:handler] || block
|
877
|
-
#
|
878
|
-
# publishing_config = {
|
879
|
-
# subjects: subjects.flatten,
|
880
|
-
# field_mappings: options[:field_mappings] || {},
|
881
|
-
# transformations: options[:transformations] || {},
|
882
|
-
# skip_fields: options[:skip_fields] || [],
|
883
|
-
# conditions: options[:conditions] || {},
|
884
|
-
# actions: options[:actions] || [:create, :update, :destroy],
|
885
|
-
# custom_handler: custom_handler, # Renamed to be clear it's optional
|
886
|
-
# async: options[:async] != false, # Default to true
|
887
|
-
# enabled: options[:enabled] != false, # Default to true
|
888
|
-
# only_mapped_fields: options[:only_mapped_fields] != false # Default to true - only send mapped fields
|
889
|
-
# }
|
890
|
-
#
|
891
|
-
# # Store the config
|
892
|
-
# config_key = subjects.join(',')
|
893
|
-
# self.nats_wave_publishing_config[config_key] = publishing_config
|
894
|
-
#
|
895
|
-
# # Add ActiveRecord callbacks for auto-publishing (only once per model)
|
896
|
-
# setup_publishing_callbacks_once
|
897
|
-
#
|
898
|
-
# Rails.logger.debug "📤 #{self.name}: Registered publishing config for subjects: #{subjects}" if defined?(Rails)
|
899
|
-
# end
|
900
|
-
#
|
901
|
-
# # Get all subscription configurations
|
902
|
-
# def nats_wave_subscription_configs
|
903
|
-
# nats_wave_subscription_config
|
904
|
-
# end
|
905
|
-
#
|
906
|
-
# # Get all publishing configurations
|
907
|
-
# def nats_wave_publishing_configs
|
908
|
-
# nats_wave_publishing_config
|
909
|
-
# end
|
910
|
-
#
|
911
|
-
# private
|
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
|
930
|
-
#
|
931
|
-
# lambda do |message|
|
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)
|
964
|
-
# def process_subscription_data(raw_data, config)
|
965
|
-
# processed_data = {}
|
966
|
-
# field_mappings = config[:field_mappings]
|
967
|
-
# transformations = config[:transformations]
|
968
|
-
# skip_fields = config[:skip_fields]
|
969
|
-
#
|
970
|
-
# raw_data.each do |field, value|
|
971
|
-
# field_str = field.to_s
|
972
|
-
# field_sym = field.to_sym
|
973
|
-
#
|
974
|
-
# # Skip if in skip_fields
|
975
|
-
# next if skip_fields.include?(field_str) || skip_fields.include?(field_sym)
|
976
|
-
#
|
977
|
-
# # Apply field mapping
|
978
|
-
# mapped_field = field_mappings[field_str] ||
|
979
|
-
# field_mappings[field_sym] ||
|
980
|
-
# field
|
981
|
-
#
|
982
|
-
# # Apply transformation if any
|
983
|
-
# transformed_value = apply_transformation(value, mapped_field, transformations, raw_data)
|
984
|
-
#
|
985
|
-
# processed_data[mapped_field.to_s] = transformed_value
|
986
|
-
# end
|
987
|
-
#
|
988
|
-
# processed_data
|
989
|
-
# end
|
990
|
-
#
|
991
|
-
# # Apply transformations to field values (CLASS METHOD)
|
992
|
-
# def apply_transformation(value, field, transformations, full_record)
|
993
|
-
# transformation = transformations[field] || transformations[field.to_sym]
|
994
|
-
#
|
995
|
-
# case transformation
|
996
|
-
# when Proc
|
997
|
-
# if transformation.arity == 2 || transformation.arity < 0
|
998
|
-
# transformation.call(value, full_record)
|
999
|
-
# else
|
1000
|
-
# transformation.call(value)
|
1001
|
-
# end
|
1002
|
-
# when Symbol
|
1003
|
-
# if self.respond_to?(transformation, true)
|
1004
|
-
# self.send(transformation, value)
|
1005
|
-
# elsif value.respond_to?(transformation)
|
1006
|
-
# value.send(transformation)
|
1007
|
-
# else
|
1008
|
-
# Rails.logger.warn "Transformation method #{transformation} not found" if defined?(Rails)
|
1009
|
-
# value
|
1010
|
-
# end
|
1011
|
-
# else
|
1012
|
-
# value
|
1013
|
-
# end
|
1014
|
-
# end
|
1015
|
-
#
|
1016
|
-
# # Default auto-sync behavior (CLASS METHOD)
|
1017
|
-
# def perform_auto_sync(model_name, action, processed_data, config)
|
1018
|
-
# unique_fields = config[:unique_fields]
|
1019
|
-
# sync_strategy = config[:sync_strategy]
|
1020
|
-
#
|
1021
|
-
# case action.to_s.downcase
|
1022
|
-
# when 'create', 'created'
|
1023
|
-
# handle_auto_create(processed_data, unique_fields, sync_strategy)
|
1024
|
-
# when 'update', 'updated'
|
1025
|
-
# handle_auto_update(processed_data, unique_fields, sync_strategy)
|
1026
|
-
# when 'delete', 'deleted', 'destroy', 'destroyed'
|
1027
|
-
# handle_auto_delete(processed_data, unique_fields)
|
1028
|
-
# else
|
1029
|
-
# Rails.logger.warn "Unknown action for auto-sync: #{action}" if defined?(Rails)
|
1030
|
-
# end
|
1031
|
-
# end
|
1032
|
-
#
|
1033
|
-
# def handle_auto_create(data, unique_fields, sync_strategy)
|
1034
|
-
# case sync_strategy
|
1035
|
-
# when :upsert
|
1036
|
-
# existing = find_by_unique_fields(data, unique_fields)
|
1037
|
-
# if existing
|
1038
|
-
# existing.update!(data)
|
1039
|
-
# Rails.logger.info "✅ Updated existing #{self.name}: #{existing.id}" if defined?(Rails)
|
1040
|
-
# else
|
1041
|
-
# record = self.create!(data)
|
1042
|
-
# Rails.logger.info "✅ Created new #{self.name}: #{record.id}" if defined?(Rails)
|
1043
|
-
# end
|
1044
|
-
# when :create_only
|
1045
|
-
# record = self.create!(data)
|
1046
|
-
# Rails.logger.info "✅ Created new #{self.name}: #{record.id}" if defined?(Rails)
|
1047
|
-
# end
|
1048
|
-
# rescue => e
|
1049
|
-
# Rails.logger.error "❌ Failed to create #{self.name}: #{e.message}" if defined?(Rails)
|
1050
|
-
# raise
|
1051
|
-
# end
|
1052
|
-
#
|
1053
|
-
# def handle_auto_update(data, unique_fields, sync_strategy)
|
1054
|
-
# existing = find_by_unique_fields(data, unique_fields)
|
1055
|
-
# if existing
|
1056
|
-
# existing.update!(data)
|
1057
|
-
# Rails.logger.info "✅ Updated #{self.name}: #{existing.id}" if defined?(Rails)
|
1058
|
-
# elsif sync_strategy == :upsert
|
1059
|
-
# record = self.create!(data)
|
1060
|
-
# Rails.logger.info "✅ Created new #{self.name} during update: #{record.id}" if defined?(Rails)
|
1061
|
-
# else
|
1062
|
-
# Rails.logger.warn "⚠️ Record not found for update: #{data.slice(*unique_fields.map(&:to_s))}" if defined?(Rails)
|
1063
|
-
# end
|
1064
|
-
# rescue => e
|
1065
|
-
# Rails.logger.error "❌ Failed to update #{self.name}: #{e.message}" if defined?(Rails)
|
1066
|
-
# raise
|
1067
|
-
# end
|
1068
|
-
#
|
1069
|
-
# def handle_auto_delete(data, unique_fields)
|
1070
|
-
# existing = find_by_unique_fields(data, unique_fields)
|
1071
|
-
# if existing
|
1072
|
-
# existing.destroy!
|
1073
|
-
# Rails.logger.info "✅ Deleted #{self.name}: #{existing.id}" if defined?(Rails)
|
1074
|
-
# else
|
1075
|
-
# Rails.logger.warn "⚠️ Record not found for deletion: #{data.slice(*unique_fields.map(&:to_s))}" if defined?(Rails)
|
1076
|
-
# end
|
1077
|
-
# rescue => e
|
1078
|
-
# Rails.logger.error "❌ Failed to delete #{self.name}: #{e.message}" if defined?(Rails)
|
1079
|
-
# raise
|
1080
|
-
# end
|
1081
|
-
#
|
1082
|
-
# def find_by_unique_fields(data, unique_fields)
|
1083
|
-
# conditions = {}
|
1084
|
-
# unique_fields.each do |field|
|
1085
|
-
# field_str = field.to_s
|
1086
|
-
# if data.key?(field_str)
|
1087
|
-
# conditions[field] = data[field_str]
|
1088
|
-
# elsif data.key?(field.to_sym)
|
1089
|
-
# conditions[field] = data[field.to_sym]
|
1090
|
-
# end
|
1091
|
-
# end
|
1092
|
-
#
|
1093
|
-
# return nil if conditions.empty?
|
1094
|
-
# self.find_by(conditions)
|
1095
|
-
# end
|
1096
|
-
# end
|
1097
|
-
#
|
1098
|
-
# # Instance methods for publishing
|
1099
|
-
# def nats_wave_publish(action = nil)
|
1100
|
-
# action ||= determine_action_from_context
|
1101
|
-
#
|
1102
|
-
# self.class.nats_wave_publishing_configs.each do |subjects_key, config|
|
1103
|
-
# next unless config[:enabled]
|
1104
|
-
# next unless config[:actions].include?(action.to_sym)
|
1105
|
-
#
|
1106
|
-
# # Check conditions (only for publishing)
|
1107
|
-
# if config[:conditions].any? && !evaluate_conditions(config[:conditions])
|
1108
|
-
# Rails.logger.debug "📤 Skipping publish - conditions not met" if defined?(Rails)
|
1109
|
-
# next
|
1110
|
-
# end
|
1111
|
-
#
|
1112
|
-
# # Get the raw data (current model attributes)
|
1113
|
-
# raw_data = get_raw_attributes
|
1114
|
-
#
|
1115
|
-
# # Process the data through mappings and transformations
|
1116
|
-
# processed_data = process_publishing_data(raw_data, config)
|
1117
|
-
#
|
1118
|
-
# # Determine which subjects to publish to based on action
|
1119
|
-
# subjects_to_publish = determine_subjects_for_action(config[:subjects], action)
|
1120
|
-
#
|
1121
|
-
# # Always publish first (default behavior)
|
1122
|
-
# subjects_to_publish.each do |subject|
|
1123
|
-
# NatsWave.client.publish(
|
1124
|
-
# subject: subject,
|
1125
|
-
# model: self.class.name,
|
1126
|
-
# action: action,
|
1127
|
-
# data: processed_data, # This is the processed data, not raw attributes
|
1128
|
-
# metadata: build_publishing_metadata
|
1129
|
-
# )
|
1130
|
-
#
|
1131
|
-
# Rails.logger.info "📤 Published #{self.class.name}##{id} to #{subject} (#{action})" if defined?(Rails)
|
1132
|
-
# end
|
1133
|
-
#
|
1134
|
-
# # Then call custom handler if provided (optional)
|
1135
|
-
# if config[:custom_handler]
|
1136
|
-
# Rails.logger.debug "📤 Calling custom publishing handler after publishing" if defined?(Rails)
|
1137
|
-
# config[:custom_handler].call(self.class.name, action, processed_data, self)
|
1138
|
-
# end
|
1139
|
-
# end
|
1140
|
-
# rescue StandardError => e
|
1141
|
-
# Rails.logger.error("Failed to publish: #{e.message}") if defined?(Rails)
|
1142
|
-
# # Don't re-raise to avoid breaking transactions
|
1143
|
-
# end
|
1144
|
-
#
|
1145
|
-
# # Auto-publishing callback methods (only called once per action)
|
1146
|
-
# def trigger_nats_wave_auto_publish_on_create
|
1147
|
-
# return if @nats_wave_publishing_in_progress
|
1148
|
-
# @nats_wave_publishing_in_progress = true
|
1149
|
-
#
|
1150
|
-
# Rails.logger.debug "🚀 Auto-publishing on create" if defined?(Rails)
|
1151
|
-
# nats_wave_publish('create')
|
1152
|
-
#
|
1153
|
-
# @nats_wave_publishing_in_progress = false
|
1154
|
-
# end
|
1155
|
-
#
|
1156
|
-
# def trigger_nats_wave_auto_publish_on_update
|
1157
|
-
# return if @nats_wave_publishing_in_progress
|
1158
|
-
# @nats_wave_publishing_in_progress = true
|
1159
|
-
#
|
1160
|
-
# Rails.logger.debug "🚀 Auto-publishing on update" if defined?(Rails)
|
1161
|
-
# nats_wave_publish('update')
|
1162
|
-
#
|
1163
|
-
# @nats_wave_publishing_in_progress = false
|
1164
|
-
# end
|
1165
|
-
#
|
1166
|
-
# def trigger_nats_wave_auto_publish_on_destroy
|
1167
|
-
# return if @nats_wave_publishing_in_progress
|
1168
|
-
# @nats_wave_publishing_in_progress = true
|
1169
|
-
#
|
1170
|
-
# Rails.logger.debug "🚀 Auto-publishing on destroy" if defined?(Rails)
|
1171
|
-
# nats_wave_publish('destroy')
|
1172
|
-
#
|
1173
|
-
# @nats_wave_publishing_in_progress = false
|
1174
|
-
# end
|
1175
|
-
#
|
1176
|
-
# private
|
1177
|
-
#
|
1178
|
-
# def determine_action_from_context
|
1179
|
-
# # Try to determine action from ActiveRecord context
|
1180
|
-
# if defined?(ActiveRecord) && is_a?(ActiveRecord::Base)
|
1181
|
-
# return 'create' if previously_new_record?
|
1182
|
-
# return 'update' if saved_changes.any?
|
1183
|
-
# return 'destroy' if destroyed?
|
1184
|
-
# end
|
1185
|
-
#
|
1186
|
-
# 'update' # Default fallback
|
1187
|
-
# end
|
1188
|
-
#
|
1189
|
-
# def get_raw_attributes
|
1190
|
-
# # Get attributes (works for both ActiveRecord and other objects)
|
1191
|
-
# if respond_to?(:attributes)
|
1192
|
-
# attributes
|
1193
|
-
# else
|
1194
|
-
# # For non-ActiveRecord objects, get instance variables
|
1195
|
-
# instance_variables.map { |v| [v.to_s.delete('@'), instance_variable_get(v)] }.to_h
|
1196
|
-
# end
|
1197
|
-
# end
|
1198
|
-
#
|
1199
|
-
# # Process publishing data through field mappings and transformations (INSTANCE METHOD)
|
1200
|
-
# def process_publishing_data(raw_data, config)
|
1201
|
-
# processed_data = {}
|
1202
|
-
# field_mappings = config[:field_mappings]
|
1203
|
-
# transformations = config[:transformations]
|
1204
|
-
# skip_fields = config[:skip_fields]
|
1205
|
-
# only_mapped_fields = config[:only_mapped_fields]
|
1206
|
-
#
|
1207
|
-
# # If only_mapped_fields is true, only process fields that have mappings or transformations
|
1208
|
-
# fields_to_process = if only_mapped_fields && field_mappings.any?
|
1209
|
-
# # Only process fields that are explicitly mapped or have transformations
|
1210
|
-
# mapped_source_fields = field_mappings.keys.map(&:to_s)
|
1211
|
-
# transformation_fields = transformations.keys.map(&:to_s)
|
1212
|
-
# all_relevant_fields = (mapped_source_fields + transformation_fields).uniq
|
1213
|
-
#
|
1214
|
-
# raw_data.select { |field, _| all_relevant_fields.include?(field.to_s) }
|
1215
|
-
# else
|
1216
|
-
# # Process all fields (existing behavior)
|
1217
|
-
# raw_data
|
1218
|
-
# end
|
1219
|
-
#
|
1220
|
-
# fields_to_process.each do |field, value|
|
1221
|
-
# field_str = field.to_s
|
1222
|
-
# field_sym = field.to_sym
|
1223
|
-
#
|
1224
|
-
# # Skip if in skip_fields
|
1225
|
-
# next if skip_fields.include?(field_str) || skip_fields.include?(field_sym)
|
1226
|
-
#
|
1227
|
-
# # Apply field mapping (local -> external)
|
1228
|
-
# mapped_field = field_mappings[field_str] ||
|
1229
|
-
# field_mappings[field_sym] ||
|
1230
|
-
# field
|
1231
|
-
#
|
1232
|
-
# # Apply transformation if any
|
1233
|
-
# transformed_value = apply_publishing_transformation(value, mapped_field, transformations, raw_data)
|
1234
|
-
#
|
1235
|
-
# processed_data[mapped_field.to_s] = transformed_value
|
1236
|
-
# end
|
1237
|
-
#
|
1238
|
-
# processed_data
|
1239
|
-
# end
|
1240
|
-
#
|
1241
|
-
# def apply_publishing_transformation(value, field, transformations, full_record)
|
1242
|
-
# transformation = transformations[field] || transformations[field.to_sym]
|
1243
|
-
#
|
1244
|
-
# case transformation
|
1245
|
-
# when Proc
|
1246
|
-
# if transformation.arity == 2 || transformation.arity < 0
|
1247
|
-
# transformation.call(value, full_record)
|
1248
|
-
# else
|
1249
|
-
# transformation.call(value)
|
1250
|
-
# end
|
1251
|
-
# when Symbol
|
1252
|
-
# if self.respond_to?(transformation, true)
|
1253
|
-
# self.send(transformation, value)
|
1254
|
-
# elsif value.respond_to?(transformation)
|
1255
|
-
# value.send(transformation)
|
1256
|
-
# else
|
1257
|
-
# Rails.logger.warn "Publishing transformation method #{transformation} not found" if defined?(Rails)
|
1258
|
-
# value
|
1259
|
-
# end
|
1260
|
-
# else
|
1261
|
-
# value
|
1262
|
-
# end
|
1263
|
-
# end
|
1264
|
-
#
|
1265
|
-
# def determine_subjects_for_action(configured_subjects, action)
|
1266
|
-
# # If subjects contain placeholders like {action}, replace them
|
1267
|
-
# configured_subjects.map do |subject|
|
1268
|
-
# if subject.include?('{action}')
|
1269
|
-
# subject.gsub('{action}', action.to_s)
|
1270
|
-
# elsif subject.include?('*')
|
1271
|
-
# # Replace wildcard with specific action
|
1272
|
-
# subject.gsub('*', action.to_s)
|
1273
|
-
# else
|
1274
|
-
# subject
|
1275
|
-
# end
|
1276
|
-
# end
|
1277
|
-
# end
|
1278
|
-
#
|
1279
|
-
# def evaluate_conditions(conditions)
|
1280
|
-
# conditions.all? do |condition, expected_value|
|
1281
|
-
# case condition
|
1282
|
-
# when Proc
|
1283
|
-
# condition.call(self)
|
1284
|
-
# when Symbol, String
|
1285
|
-
# if respond_to?(condition, true)
|
1286
|
-
# result = send(condition)
|
1287
|
-
# expected_value.nil? ? result : result == expected_value
|
1288
|
-
# else
|
1289
|
-
# false
|
1290
|
-
# end
|
1291
|
-
# else
|
1292
|
-
# false
|
1293
|
-
# end
|
1294
|
-
# end
|
1295
|
-
# end
|
1296
|
-
#
|
1297
|
-
# def build_publishing_metadata
|
1298
|
-
# {
|
1299
|
-
# source_model: self.class.name,
|
1300
|
-
# source_id: respond_to?(:id) ? id : object_id,
|
1301
|
-
# published_at: Time.current.iso8601
|
1302
|
-
# }
|
1303
|
-
# end
|
1304
|
-
# end
|
1305
|
-
# end
|
1306
|
-
# end
|
1307
1
|
# frozen_string_literal: true
|
1308
2
|
|
1309
3
|
module NatsWave
|
@@ -1320,7 +14,7 @@ module NatsWave
|
|
1320
14
|
class_methods do
|
1321
15
|
# Configure subscriptions with optional custom handler
|
1322
16
|
def nats_wave_subscribes_to(*subjects, **options, &block)
|
1323
|
-
|
17
|
+
NatsWave.logger.debug "📡 #{self.name}: Setting up subscription to subjects: #{subjects.inspect}"
|
1324
18
|
|
1325
19
|
# Custom handler is now optional and runs AFTER default behavior
|
1326
20
|
custom_handler = options[:handler] || block
|
@@ -1352,12 +46,12 @@ module NatsWave
|
|
1352
46
|
queue_group: subscription_config[:queue_group]
|
1353
47
|
)
|
1354
48
|
|
1355
|
-
|
49
|
+
NatsWave.logger.debug "📡 #{self.name}: Registered subscription for subjects: #{subjects}"
|
1356
50
|
end
|
1357
51
|
|
1358
52
|
# Configure publishing with optional custom handler AND auto-publishing callbacks
|
1359
53
|
def nats_wave_publishes_to(*subjects, **options, &block)
|
1360
|
-
|
54
|
+
NatsWave.logger.debug "📤 #{self.name}: Setting up publishing to subjects: #{subjects.inspect}"
|
1361
55
|
|
1362
56
|
# Custom handler is now optional and runs AFTER default behavior
|
1363
57
|
custom_handler = options[:handler] || block
|
@@ -1371,7 +65,8 @@ module NatsWave
|
|
1371
65
|
actions: options[:actions] || [:create, :update, :destroy],
|
1372
66
|
custom_handler: custom_handler, # Renamed to be clear it's optional
|
1373
67
|
async: options[:async] != false, # Default to true
|
1374
|
-
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
|
1375
70
|
}
|
1376
71
|
|
1377
72
|
# Store the config
|
@@ -1381,7 +76,7 @@ module NatsWave
|
|
1381
76
|
# Add ActiveRecord callbacks for auto-publishing (only once per model)
|
1382
77
|
setup_publishing_callbacks_once
|
1383
78
|
|
1384
|
-
|
79
|
+
NatsWave.logger.debug "📤 #{self.name}: Registered publishing config for subjects: #{subjects}"
|
1385
80
|
end
|
1386
81
|
|
1387
82
|
# Get all subscription configurations
|
@@ -1407,7 +102,7 @@ module NatsWave
|
|
1407
102
|
after_commit :trigger_nats_wave_auto_publish_on_destroy, on: :destroy
|
1408
103
|
|
1409
104
|
@nats_wave_callbacks_added = true
|
1410
|
-
|
105
|
+
NatsWave.logger.debug "📤 #{self.name}: Added publishing callbacks"
|
1411
106
|
end
|
1412
107
|
|
1413
108
|
# Create a subscription handler that processes data and calls custom handler AFTER auto-sync
|
@@ -1416,7 +111,7 @@ module NatsWave
|
|
1416
111
|
|
1417
112
|
lambda do |message|
|
1418
113
|
begin
|
1419
|
-
|
114
|
+
NatsWave.logger.debug "📨 Processing subscription message for #{model_class.name}"
|
1420
115
|
|
1421
116
|
# Extract the raw data
|
1422
117
|
raw_data = message['data'] || {}
|
@@ -1428,19 +123,19 @@ module NatsWave
|
|
1428
123
|
|
1429
124
|
# Always perform auto-sync first (if enabled)
|
1430
125
|
if config[:auto_sync]
|
1431
|
-
|
126
|
+
NatsWave.logger.debug "📨 Performing auto-sync first"
|
1432
127
|
model_class.perform_auto_sync(model_name, action, processed_data, config)
|
1433
128
|
end
|
1434
129
|
|
1435
130
|
# Then call custom handler if provided (optional)
|
1436
131
|
if config[:custom_handler]
|
1437
|
-
|
132
|
+
NatsWave.logger.debug "📨 Calling custom subscription handler after auto-sync"
|
1438
133
|
config[:custom_handler].call(model_name, action, processed_data, message)
|
1439
134
|
end
|
1440
135
|
|
1441
136
|
rescue => e
|
1442
|
-
|
1443
|
-
|
137
|
+
NatsWave.logger.error "Error in subscription handler for #{model_class.name}: #{e.message}"
|
138
|
+
NatsWave.logger.error e.backtrace.join("\n")
|
1444
139
|
raise
|
1445
140
|
end
|
1446
141
|
end
|
@@ -1491,7 +186,7 @@ module NatsWave
|
|
1491
186
|
elsif value.respond_to?(transformation)
|
1492
187
|
value.send(transformation)
|
1493
188
|
else
|
1494
|
-
|
189
|
+
NatsWave.logger.warn "Transformation method #{transformation} not found"
|
1495
190
|
value
|
1496
191
|
end
|
1497
192
|
else
|
@@ -1512,7 +207,7 @@ module NatsWave
|
|
1512
207
|
when 'delete', 'deleted', 'destroy', 'destroyed'
|
1513
208
|
handle_auto_delete(processed_data, unique_fields)
|
1514
209
|
else
|
1515
|
-
|
210
|
+
NatsWave.logger.warn "Unknown action for auto-sync: #{action}"
|
1516
211
|
end
|
1517
212
|
end
|
1518
213
|
|
@@ -1522,17 +217,17 @@ module NatsWave
|
|
1522
217
|
existing = find_by_unique_fields(data, unique_fields)
|
1523
218
|
if existing
|
1524
219
|
existing.update!(data)
|
1525
|
-
|
220
|
+
NatsWave.logger.info "✅ Updated existing #{self.name}: #{existing.id}"
|
1526
221
|
else
|
1527
222
|
record = self.create!(data)
|
1528
|
-
|
223
|
+
NatsWave.logger.info "✅ Created new #{self.name}: #{record.id}"
|
1529
224
|
end
|
1530
225
|
when :create_only
|
1531
226
|
record = self.create!(data)
|
1532
|
-
|
227
|
+
NatsWave.logger.info "✅ Created new #{self.name}: #{record.id}"
|
1533
228
|
end
|
1534
229
|
rescue => e
|
1535
|
-
|
230
|
+
NatsWave.logger.error "❌ Failed to create #{self.name}: #{e.message}"
|
1536
231
|
raise
|
1537
232
|
end
|
1538
233
|
|
@@ -1540,15 +235,15 @@ module NatsWave
|
|
1540
235
|
existing = find_by_unique_fields(data, unique_fields)
|
1541
236
|
if existing
|
1542
237
|
existing.update!(data)
|
1543
|
-
|
238
|
+
NatsWave.logger.info "✅ Updated #{self.name}: #{existing.id}"
|
1544
239
|
elsif sync_strategy == :upsert
|
1545
240
|
record = self.create!(data)
|
1546
|
-
|
241
|
+
NatsWave.logger.info "✅ Created new #{self.name} during update: #{record.id}"
|
1547
242
|
else
|
1548
|
-
|
243
|
+
NatsWave.logger.warn "⚠️ Record not found for update: #{data.slice(*unique_fields.map(&:to_s))}"
|
1549
244
|
end
|
1550
245
|
rescue => e
|
1551
|
-
|
246
|
+
NatsWave.logger.error "❌ Failed to update #{self.name}: #{e.message}"
|
1552
247
|
raise
|
1553
248
|
end
|
1554
249
|
|
@@ -1556,12 +251,12 @@ module NatsWave
|
|
1556
251
|
existing = find_by_unique_fields(data, unique_fields)
|
1557
252
|
if existing
|
1558
253
|
existing.destroy!
|
1559
|
-
|
254
|
+
NatsWave.logger.info "✅ Deleted #{self.name}: #{existing.id}"
|
1560
255
|
else
|
1561
|
-
|
256
|
+
NatsWave.logger.warn "⚠️ Record not found for deletion: #{data.slice(*unique_fields.map(&:to_s))}"
|
1562
257
|
end
|
1563
258
|
rescue => e
|
1564
|
-
|
259
|
+
NatsWave.logger.error "❌ Failed to delete #{self.name}: #{e.message}"
|
1565
260
|
raise
|
1566
261
|
end
|
1567
262
|
|
@@ -1591,7 +286,7 @@ module NatsWave
|
|
1591
286
|
|
1592
287
|
# Check conditions (only for publishing)
|
1593
288
|
if config[:conditions].any? && !evaluate_conditions(config[:conditions])
|
1594
|
-
|
289
|
+
NatsWave.logger.debug "📤 Skipping publish - conditions not met"
|
1595
290
|
next
|
1596
291
|
end
|
1597
292
|
|
@@ -1614,17 +309,17 @@ module NatsWave
|
|
1614
309
|
metadata: build_publishing_metadata
|
1615
310
|
)
|
1616
311
|
|
1617
|
-
|
312
|
+
NatsWave.logger.info "📤 Published #{self.class.name} to #{subject}"
|
1618
313
|
end
|
1619
314
|
|
1620
315
|
# Then call custom handler if provided (optional)
|
1621
316
|
if config[:custom_handler]
|
1622
|
-
|
317
|
+
NatsWave.logger.debug "📤 Calling custom publishing handler after publishing"
|
1623
318
|
config[:custom_handler].call(self.class.name, action, processed_data, self)
|
1624
319
|
end
|
1625
320
|
end
|
1626
321
|
rescue StandardError => e
|
1627
|
-
|
322
|
+
NatsWave.logger.error("Failed to publish: #{e.message}")
|
1628
323
|
# Don't re-raise to avoid breaking transactions
|
1629
324
|
end
|
1630
325
|
|
@@ -1633,7 +328,7 @@ module NatsWave
|
|
1633
328
|
return if @nats_wave_publishing_in_progress
|
1634
329
|
@nats_wave_publishing_in_progress = true
|
1635
330
|
|
1636
|
-
|
331
|
+
NatsWave.logger.debug "🚀 Auto-publishing on create"
|
1637
332
|
nats_wave_publish('create')
|
1638
333
|
|
1639
334
|
@nats_wave_publishing_in_progress = false
|
@@ -1643,7 +338,7 @@ module NatsWave
|
|
1643
338
|
return if @nats_wave_publishing_in_progress
|
1644
339
|
@nats_wave_publishing_in_progress = true
|
1645
340
|
|
1646
|
-
|
341
|
+
NatsWave.logger.debug "🚀 Auto-publishing on update"
|
1647
342
|
nats_wave_publish('update')
|
1648
343
|
|
1649
344
|
@nats_wave_publishing_in_progress = false
|
@@ -1653,7 +348,7 @@ module NatsWave
|
|
1653
348
|
return if @nats_wave_publishing_in_progress
|
1654
349
|
@nats_wave_publishing_in_progress = true
|
1655
350
|
|
1656
|
-
|
351
|
+
NatsWave.logger.debug "🚀 Auto-publishing on destroy"
|
1657
352
|
nats_wave_publish('destroy')
|
1658
353
|
|
1659
354
|
@nats_wave_publishing_in_progress = false
|
@@ -1682,31 +377,49 @@ module NatsWave
|
|
1682
377
|
end
|
1683
378
|
end
|
1684
379
|
|
1685
|
-
# Process publishing data - much simpler now! (INSTANCE METHOD)
|
1686
380
|
def process_publishing_data(raw_data, config)
|
1687
381
|
processed_data = {}
|
1688
|
-
field_mappings = config[:field_mappings]
|
1689
|
-
transformations = config[:transformations]
|
1690
|
-
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]
|
1691
386
|
|
1692
|
-
|
1693
|
-
|
1694
|
-
|
1695
|
-
|
1696
|
-
|
1697
|
-
|
1698
|
-
|
1699
|
-
|
1700
|
-
|
1701
|
-
|
1702
|
-
|
1703
|
-
|
1704
|
-
|
1705
|
-
|
1706
|
-
|
1707
|
-
|
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
|
1708
422
|
end
|
1709
|
-
|
1710
423
|
processed_data
|
1711
424
|
end
|
1712
425
|
|
@@ -1726,7 +439,7 @@ module NatsWave
|
|
1726
439
|
elsif value.respond_to?(transformation)
|
1727
440
|
value.send(transformation)
|
1728
441
|
else
|
1729
|
-
|
442
|
+
NatsWave.logger.warn "Publishing transformation method #{transformation} not found"
|
1730
443
|
value
|
1731
444
|
end
|
1732
445
|
else
|