nats_wave 1.1.19 → 1.1.20
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 +1 -1
- data/lib/nats_wave/adapters/datadog_metrics.rb +1 -1
- data/lib/nats_wave/concerns/mappable.rb +11 -997
- data/lib/nats_wave/configuration.rb +1 -1
- data/lib/nats_wave/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 56f43d30e884588fb8db2ecbf73b1c7e39aa7d63fbfbec563c156f6f13f9ee0c
|
4
|
+
data.tar.gz: c07acd7e6f1e7acc05a72d13eee5640c7fa5cdda91adc135fe81f4eb51262ae3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 07dbf0eec97fbed551525feb102d4c101f7cfc6dd2f608ccb635e8a0b9248d64b8b66d610df63ae3f9b6a6d2cb9e66c2293485522230f5653ab9e0ae212b2451
|
7
|
+
data.tar.gz: 855b54e2f045c8d0c12dbad5bb0a553a44e9ef8d0c7b6ba33997c25f25997bee9a397276a61ea8ca510f0500c5138048c233fc65169a6133d90133e8925636d2
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -1,989 +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
|
-
# # NatsWave.logger.debug "📡 #{self.name}: Setting up subscription to subjects: #{subjects.inspect}"
|
18
|
-
# #
|
19
|
-
# # # Custom handler is now optional and runs AFTER default behavior
|
20
|
-
# # custom_handler = options[:handler] || block
|
21
|
-
# #
|
22
|
-
# # subscription_config = {
|
23
|
-
# # subjects: subjects.flatten,
|
24
|
-
# # field_mappings: options[:field_mappings] || {},
|
25
|
-
# # transformations: options[:transformations] || {},
|
26
|
-
# # skip_fields: options[:skip_fields] || [],
|
27
|
-
# # unique_fields: options[:unique_fields] || [:id],
|
28
|
-
# # sync_strategy: options[:sync_strategy] || :upsert,
|
29
|
-
# # custom_handler: custom_handler, # Renamed to be clear it's optional
|
30
|
-
# # queue_group: options[:queue_group],
|
31
|
-
# # auto_sync: options[:auto_sync] != false # Default to true, can be disabled
|
32
|
-
# # }
|
33
|
-
# #
|
34
|
-
# # # Store the config
|
35
|
-
# # config_key = subjects.join(',')
|
36
|
-
# # self.nats_wave_subscription_config[config_key] = subscription_config
|
37
|
-
# #
|
38
|
-
# # # Create the subscription handler
|
39
|
-
# # processed_handler = create_subscription_handler(subscription_config)
|
40
|
-
# #
|
41
|
-
# # # Register the subscription
|
42
|
-
# # NatsWave::ModelRegistry.register_subscription(
|
43
|
-
# # subjects: subjects.flatten,
|
44
|
-
# # model: self.name,
|
45
|
-
# # handler: processed_handler,
|
46
|
-
# # queue_group: subscription_config[:queue_group]
|
47
|
-
# # )
|
48
|
-
# #
|
49
|
-
# # NatsWave.logger.debug "📡 #{self.name}: Registered subscription for subjects: #{subjects}"
|
50
|
-
# # end
|
51
|
-
# #
|
52
|
-
# # # Configure publishing with optional custom handler AND auto-publishing callbacks
|
53
|
-
# # def nats_wave_publishes_to(*subjects, **options, &block)
|
54
|
-
# # NatsWave.logger.debug "📤 #{self.name}: Setting up publishing to subjects: #{subjects.inspect}"
|
55
|
-
# #
|
56
|
-
# # # Custom handler is now optional and runs AFTER default behavior
|
57
|
-
# # custom_handler = options[:handler] || block
|
58
|
-
# #
|
59
|
-
# # publishing_config = {
|
60
|
-
# # subjects: subjects.flatten,
|
61
|
-
# # field_mappings: options[:field_mappings] || {},
|
62
|
-
# # transformations: options[:transformations] || {},
|
63
|
-
# # skip_fields: options[:skip_fields] || [],
|
64
|
-
# # conditions: options[:conditions] || {},
|
65
|
-
# # actions: options[:actions] || [:create, :update, :destroy],
|
66
|
-
# # custom_handler: custom_handler, # Renamed to be clear it's optional
|
67
|
-
# # async: options[:async] != false, # Default to true
|
68
|
-
# # enabled: options[:enabled] != false, # Default to true
|
69
|
-
# # only_mapped_fields: options[:only_mapped_fields] != false, # Default to true
|
70
|
-
# # }
|
71
|
-
# #
|
72
|
-
# # # Store the config
|
73
|
-
# # config_key = subjects.join(',')
|
74
|
-
# # self.nats_wave_publishing_config[config_key] = publishing_config
|
75
|
-
# #
|
76
|
-
# # # Add ActiveRecord callbacks for auto-publishing (only once per model)
|
77
|
-
# # setup_publishing_callbacks_once
|
78
|
-
# #
|
79
|
-
# # NatsWave.logger.debug "📤 #{self.name}: Registered publishing config for subjects: #{subjects}"
|
80
|
-
# # end
|
81
|
-
# #
|
82
|
-
# # # Get all subscription configurations
|
83
|
-
# # def nats_wave_subscription_configs
|
84
|
-
# # nats_wave_subscription_config
|
85
|
-
# # end
|
86
|
-
# #
|
87
|
-
# # # Get all publishing configurations
|
88
|
-
# # def nats_wave_publishing_configs
|
89
|
-
# # nats_wave_publishing_config
|
90
|
-
# # end
|
91
|
-
# #
|
92
|
-
# # private
|
93
|
-
# #
|
94
|
-
# # # Setup ActiveRecord callbacks for auto-publishing (only once per model)
|
95
|
-
# # def setup_publishing_callbacks_once
|
96
|
-
# # # Only add callbacks if we're in ActiveRecord and haven't added them yet
|
97
|
-
# # return unless defined?(ActiveRecord::Base) && self < ActiveRecord::Base
|
98
|
-
# # return if @nats_wave_callbacks_added
|
99
|
-
# #
|
100
|
-
# # after_commit :trigger_nats_wave_auto_publish_on_create, on: :create
|
101
|
-
# # after_commit :trigger_nats_wave_auto_publish_on_update, on: :update
|
102
|
-
# # after_commit :trigger_nats_wave_auto_publish_on_destroy, on: :destroy
|
103
|
-
# #
|
104
|
-
# # @nats_wave_callbacks_added = true
|
105
|
-
# # NatsWave.logger.debug "📤 #{self.name}: Added publishing callbacks"
|
106
|
-
# # end
|
107
|
-
# #
|
108
|
-
# # # Create a subscription handler that processes data and calls custom handler AFTER auto-sync
|
109
|
-
# # def create_subscription_handler(config)
|
110
|
-
# # model_class = self
|
111
|
-
# #
|
112
|
-
# # lambda do |message|
|
113
|
-
# # begin
|
114
|
-
# # NatsWave.logger.debug "📨 Processing subscription message for #{model_class.name}"
|
115
|
-
# #
|
116
|
-
# # # Extract the raw data
|
117
|
-
# # raw_data = message['data'] || {}
|
118
|
-
# # model_name = message['model']
|
119
|
-
# # action = message['action']
|
120
|
-
# #
|
121
|
-
# # # Process the data through mappings and transformations
|
122
|
-
# # processed_data = model_class.process_subscription_data(raw_data, config)
|
123
|
-
# #
|
124
|
-
# # # Always perform auto-sync first (if enabled)
|
125
|
-
# # if config[:auto_sync]
|
126
|
-
# # NatsWave.logger.debug "📨 Performing auto-sync first"
|
127
|
-
# # model_class.perform_auto_sync(model_name, action, processed_data, config)
|
128
|
-
# # end
|
129
|
-
# #
|
130
|
-
# # # Then call custom handler if provided (optional)
|
131
|
-
# # if config[:custom_handler]
|
132
|
-
# # NatsWave.logger.debug "📨 Calling custom subscription handler after auto-sync"
|
133
|
-
# # config[:custom_handler].call(model_name, action, processed_data, message)
|
134
|
-
# # end
|
135
|
-
# #
|
136
|
-
# # rescue => e
|
137
|
-
# # NatsWave.logger.error "Error in subscription handler for #{model_class.name}: #{e.message}"
|
138
|
-
# # NatsWave.logger.error e.backtrace.join("\n")
|
139
|
-
# # raise
|
140
|
-
# # end
|
141
|
-
# # end
|
142
|
-
# # end
|
143
|
-
# #
|
144
|
-
# # # Process subscription data through field mappings and transformations (PUBLIC CLASS METHOD)
|
145
|
-
# # def process_subscription_data(raw_data, config)
|
146
|
-
# # processed_data = {}
|
147
|
-
# # field_mappings = config[:field_mappings]
|
148
|
-
# # transformations = config[:transformations]
|
149
|
-
# # skip_fields = config[:skip_fields]
|
150
|
-
# #
|
151
|
-
# # raw_data.each do |field, value|
|
152
|
-
# # field_str = field.to_s
|
153
|
-
# # field_sym = field.to_sym
|
154
|
-
# #
|
155
|
-
# # # Skip if in skip_fields
|
156
|
-
# # next if skip_fields.include?(field_str) || skip_fields.include?(field_sym)
|
157
|
-
# #
|
158
|
-
# # # Apply field mapping (external -> local)
|
159
|
-
# # mapped_field = field_mappings[field_str] ||
|
160
|
-
# # field_mappings[field_sym] ||
|
161
|
-
# # field
|
162
|
-
# #
|
163
|
-
# # # Apply transformation if any
|
164
|
-
# # transformed_value = apply_transformation(value, mapped_field, transformations, raw_data)
|
165
|
-
# #
|
166
|
-
# # processed_data[mapped_field.to_s] = transformed_value
|
167
|
-
# # end
|
168
|
-
# #
|
169
|
-
# # processed_data
|
170
|
-
# # end
|
171
|
-
# #
|
172
|
-
# # # Apply transformations to field values (PUBLIC CLASS METHOD)
|
173
|
-
# # def apply_transformation(value, field, transformations, full_record)
|
174
|
-
# # transformation = transformations[field] || transformations[field.to_sym]
|
175
|
-
# #
|
176
|
-
# # case transformation
|
177
|
-
# # when Proc
|
178
|
-
# # if transformation.arity == 2 || transformation.arity < 0
|
179
|
-
# # transformation.call(value, full_record)
|
180
|
-
# # else
|
181
|
-
# # transformation.call(value)
|
182
|
-
# # end
|
183
|
-
# # when Symbol
|
184
|
-
# # if self.respond_to?(transformation, true)
|
185
|
-
# # self.send(transformation, value)
|
186
|
-
# # elsif value.respond_to?(transformation)
|
187
|
-
# # value.send(transformation)
|
188
|
-
# # else
|
189
|
-
# # NatsWave.logger.warn "Transformation method #{transformation} not found"
|
190
|
-
# # value
|
191
|
-
# # end
|
192
|
-
# # else
|
193
|
-
# # value
|
194
|
-
# # end
|
195
|
-
# # end
|
196
|
-
# #
|
197
|
-
# # # Default auto-sync behavior (PUBLIC CLASS METHOD)
|
198
|
-
# # def perform_auto_sync(model_name, action, processed_data, config)
|
199
|
-
# # unique_fields = config[:unique_fields]
|
200
|
-
# # sync_strategy = config[:sync_strategy]
|
201
|
-
# #
|
202
|
-
# # case action.to_s.downcase
|
203
|
-
# # when 'create', 'created'
|
204
|
-
# # handle_auto_create(processed_data, unique_fields, sync_strategy)
|
205
|
-
# # when 'update', 'updated'
|
206
|
-
# # handle_auto_update(processed_data, unique_fields, sync_strategy)
|
207
|
-
# # when 'delete', 'deleted', 'destroy', 'destroyed'
|
208
|
-
# # handle_auto_delete(processed_data, unique_fields)
|
209
|
-
# # else
|
210
|
-
# # NatsWave.logger.warn "Unknown action for auto-sync: #{action}"
|
211
|
-
# # end
|
212
|
-
# # end
|
213
|
-
# #
|
214
|
-
# # def handle_auto_create(data, unique_fields, sync_strategy)
|
215
|
-
# # case sync_strategy
|
216
|
-
# # when :upsert
|
217
|
-
# # existing = find_by_unique_fields(data, unique_fields)
|
218
|
-
# # if existing
|
219
|
-
# # existing.update!(data)
|
220
|
-
# # NatsWave.logger.info "✅ Updated existing #{self.name}: #{existing.id}"
|
221
|
-
# # else
|
222
|
-
# # record = self.create!(data)
|
223
|
-
# # NatsWave.logger.info "✅ Created new #{self.name}: #{record.id}"
|
224
|
-
# # end
|
225
|
-
# # when :create_only
|
226
|
-
# # record = self.create!(data)
|
227
|
-
# # NatsWave.logger.info "✅ Created new #{self.name}: #{record.id}"
|
228
|
-
# # end
|
229
|
-
# # rescue => e
|
230
|
-
# # NatsWave.logger.error "❌ Failed to create #{self.name}: #{e.message}"
|
231
|
-
# # raise
|
232
|
-
# # end
|
233
|
-
# #
|
234
|
-
# # def handle_auto_update(data, unique_fields, sync_strategy)
|
235
|
-
# # existing = find_by_unique_fields(data, unique_fields)
|
236
|
-
# # if existing
|
237
|
-
# # existing.update!(data)
|
238
|
-
# # NatsWave.logger.info "✅ Updated #{self.name}: #{existing.id}"
|
239
|
-
# # elsif sync_strategy == :upsert
|
240
|
-
# # record = self.create!(data)
|
241
|
-
# # NatsWave.logger.info "✅ Created new #{self.name} during update: #{record.id}"
|
242
|
-
# # else
|
243
|
-
# # NatsWave.logger.warn "⚠️ Record not found for update: #{data.slice(*unique_fields.map(&:to_s))}"
|
244
|
-
# # end
|
245
|
-
# # rescue => e
|
246
|
-
# # NatsWave.logger.error "❌ Failed to update #{self.name}: #{e.message}"
|
247
|
-
# # raise
|
248
|
-
# # end
|
249
|
-
# #
|
250
|
-
# # def handle_auto_delete(data, unique_fields)
|
251
|
-
# # existing = find_by_unique_fields(data, unique_fields)
|
252
|
-
# # if existing
|
253
|
-
# # existing.destroy!
|
254
|
-
# # NatsWave.logger.info "✅ Deleted #{self.name}: #{existing.id}"
|
255
|
-
# # else
|
256
|
-
# # NatsWave.logger.warn "⚠️ Record not found for deletion: #{data.slice(*unique_fields.map(&:to_s))}"
|
257
|
-
# # end
|
258
|
-
# # rescue => e
|
259
|
-
# # NatsWave.logger.error "❌ Failed to delete #{self.name}: #{e.message}"
|
260
|
-
# # raise
|
261
|
-
# # end
|
262
|
-
# #
|
263
|
-
# # def find_by_unique_fields(data, unique_fields)
|
264
|
-
# # conditions = {}
|
265
|
-
# # unique_fields.each do |field|
|
266
|
-
# # field_str = field.to_s
|
267
|
-
# # if data.key?(field_str)
|
268
|
-
# # conditions[field] = data[field_str]
|
269
|
-
# # elsif data.key?(field.to_sym)
|
270
|
-
# # conditions[field] = data[field.to_sym]
|
271
|
-
# # end
|
272
|
-
# # end
|
273
|
-
# #
|
274
|
-
# # return nil if conditions.empty?
|
275
|
-
# # self.find_by(conditions)
|
276
|
-
# # end
|
277
|
-
# # end
|
278
|
-
# #
|
279
|
-
# # # Instance methods for publishing
|
280
|
-
# # def nats_wave_publish(action = nil)
|
281
|
-
# # action ||= determine_action_from_context
|
282
|
-
# #
|
283
|
-
# # self.class.nats_wave_publishing_configs.each do |subjects_key, config|
|
284
|
-
# # next unless config[:enabled]
|
285
|
-
# # next unless config[:actions].include?(action.to_sym)
|
286
|
-
# #
|
287
|
-
# # # Check conditions (only for publishing)
|
288
|
-
# # if config[:conditions].any? && !evaluate_conditions(config[:conditions])
|
289
|
-
# # NatsWave.logger.debug "📤 Skipping publish - conditions not met"
|
290
|
-
# # next
|
291
|
-
# # end
|
292
|
-
# #
|
293
|
-
# # # Get the raw data (current model attributes)
|
294
|
-
# # raw_data = get_raw_attributes
|
295
|
-
# #
|
296
|
-
# # # Process the data through mappings and transformations
|
297
|
-
# # processed_data = process_publishing_data(raw_data, config)
|
298
|
-
# #
|
299
|
-
# # # Determine which subjects to publish to based on action
|
300
|
-
# # subjects_to_publish = determine_subjects_for_action(config[:subjects], action)
|
301
|
-
# #
|
302
|
-
# # # Always publish first (default behavior)
|
303
|
-
# # subjects_to_publish.each do |subject|
|
304
|
-
# # NatsWave.client.publish(
|
305
|
-
# # subject: subject,
|
306
|
-
# # model: self.class.name,
|
307
|
-
# # action: action,
|
308
|
-
# # data: processed_data, # This is the processed data, not raw attributes
|
309
|
-
# # metadata: build_publishing_metadata
|
310
|
-
# # )
|
311
|
-
# #
|
312
|
-
# # NatsWave.logger.info "📤 Published #{self.class.name} to #{subject}"
|
313
|
-
# # end
|
314
|
-
# #
|
315
|
-
# # # Then call custom handler if provided (optional)
|
316
|
-
# # if config[:custom_handler]
|
317
|
-
# # NatsWave.logger.debug "📤 Calling custom publishing handler after publishing"
|
318
|
-
# # config[:custom_handler].call(self.class.name, action, processed_data, self)
|
319
|
-
# # end
|
320
|
-
# # end
|
321
|
-
# # rescue StandardError => e
|
322
|
-
# # NatsWave.logger.error("Failed to publish: #{e.message}")
|
323
|
-
# # # Don't re-raise to avoid breaking transactions
|
324
|
-
# # end
|
325
|
-
# #
|
326
|
-
# # # Auto-publishing callback methods (only called once per action)
|
327
|
-
# # def trigger_nats_wave_auto_publish_on_create
|
328
|
-
# # return if @nats_wave_publishing_in_progress
|
329
|
-
# # @nats_wave_publishing_in_progress = true
|
330
|
-
# #
|
331
|
-
# # NatsWave.logger.debug "🚀 Auto-publishing on create"
|
332
|
-
# # nats_wave_publish('create')
|
333
|
-
# #
|
334
|
-
# # @nats_wave_publishing_in_progress = false
|
335
|
-
# # end
|
336
|
-
# #
|
337
|
-
# # def trigger_nats_wave_auto_publish_on_update
|
338
|
-
# # return if @nats_wave_publishing_in_progress
|
339
|
-
# # @nats_wave_publishing_in_progress = true
|
340
|
-
# #
|
341
|
-
# # NatsWave.logger.debug "🚀 Auto-publishing on update"
|
342
|
-
# # nats_wave_publish('update')
|
343
|
-
# #
|
344
|
-
# # @nats_wave_publishing_in_progress = false
|
345
|
-
# # end
|
346
|
-
# #
|
347
|
-
# # def trigger_nats_wave_auto_publish_on_destroy
|
348
|
-
# # return if @nats_wave_publishing_in_progress
|
349
|
-
# # @nats_wave_publishing_in_progress = true
|
350
|
-
# #
|
351
|
-
# # NatsWave.logger.debug "🚀 Auto-publishing on destroy"
|
352
|
-
# # nats_wave_publish('destroy')
|
353
|
-
# #
|
354
|
-
# # @nats_wave_publishing_in_progress = false
|
355
|
-
# # end
|
356
|
-
# #
|
357
|
-
# # private
|
358
|
-
# #
|
359
|
-
# # def determine_action_from_context
|
360
|
-
# # # Try to determine action from ActiveRecord context
|
361
|
-
# # if defined?(ActiveRecord) && is_a?(ActiveRecord::Base)
|
362
|
-
# # return 'create' if previously_new_record?
|
363
|
-
# # return 'update' if saved_changes.any?
|
364
|
-
# # return 'destroy' if destroyed?
|
365
|
-
# # end
|
366
|
-
# #
|
367
|
-
# # 'update' # Default fallback
|
368
|
-
# # end
|
369
|
-
# #
|
370
|
-
# # def get_raw_attributes
|
371
|
-
# # # Get attributes (works for both ActiveRecord and other objects)
|
372
|
-
# # if respond_to?(:attributes)
|
373
|
-
# # attributes
|
374
|
-
# # else
|
375
|
-
# # # For non-ActiveRecord objects, get instance variables
|
376
|
-
# # instance_variables.map { |v| [v.to_s.delete('@'), instance_variable_get(v)] }.to_h
|
377
|
-
# # end
|
378
|
-
# # end
|
379
|
-
# #
|
380
|
-
# # def process_publishing_data(raw_data, config)
|
381
|
-
# # processed_data = {}
|
382
|
-
# # field_mappings = config[:field_mappings] || {}
|
383
|
-
# # transformations = config[:transformations] || {}
|
384
|
-
# # skip_fields = config[:skip_fields] || []
|
385
|
-
# # only_mapped_fields = config[:only_mapped_fields]
|
386
|
-
# #
|
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
|
422
|
-
# # end
|
423
|
-
# # processed_data
|
424
|
-
# # end
|
425
|
-
# #
|
426
|
-
# # def apply_publishing_transformation(value, field, transformations, full_record)
|
427
|
-
# # transformation = transformations[field] || transformations[field.to_sym]
|
428
|
-
# #
|
429
|
-
# # case transformation
|
430
|
-
# # when Proc
|
431
|
-
# # if transformation.arity == 2 || transformation.arity < 0
|
432
|
-
# # transformation.call(value, full_record)
|
433
|
-
# # else
|
434
|
-
# # transformation.call(value)
|
435
|
-
# # end
|
436
|
-
# # when Symbol
|
437
|
-
# # if self.respond_to?(transformation, true)
|
438
|
-
# # self.send(transformation, value)
|
439
|
-
# # elsif value.respond_to?(transformation)
|
440
|
-
# # value.send(transformation)
|
441
|
-
# # else
|
442
|
-
# # NatsWave.logger.warn "Publishing transformation method #{transformation} not found"
|
443
|
-
# # value
|
444
|
-
# # end
|
445
|
-
# # else
|
446
|
-
# # value
|
447
|
-
# # end
|
448
|
-
# # end
|
449
|
-
# #
|
450
|
-
# # def determine_subjects_for_action(configured_subjects, action)
|
451
|
-
# # # If subjects contain placeholders like {action}, replace them
|
452
|
-
# # configured_subjects.map do |subject|
|
453
|
-
# # if subject.include?('{action}')
|
454
|
-
# # subject.gsub('{action}', action.to_s)
|
455
|
-
# # elsif subject.include?('*')
|
456
|
-
# # # Replace wildcard with specific action
|
457
|
-
# # subject.gsub('*', action.to_s)
|
458
|
-
# # else
|
459
|
-
# # subject
|
460
|
-
# # end
|
461
|
-
# # end
|
462
|
-
# # end
|
463
|
-
# #
|
464
|
-
# # def evaluate_conditions(conditions)
|
465
|
-
# # conditions.all? do |condition, expected_value|
|
466
|
-
# # case condition
|
467
|
-
# # when Proc
|
468
|
-
# # condition.call(self)
|
469
|
-
# # when Symbol, String
|
470
|
-
# # if respond_to?(condition, true)
|
471
|
-
# # result = send(condition)
|
472
|
-
# # expected_value.nil? ? result : result == expected_value
|
473
|
-
# # else
|
474
|
-
# # false
|
475
|
-
# # end
|
476
|
-
# # else
|
477
|
-
# # false
|
478
|
-
# # end
|
479
|
-
# # end
|
480
|
-
# # end
|
481
|
-
# #
|
482
|
-
# # def build_publishing_metadata
|
483
|
-
# # {
|
484
|
-
# # source_model: self.class.name,
|
485
|
-
# # source_id: respond_to?(:id) ? id : object_id,
|
486
|
-
# # published_at: Time.current.iso8601
|
487
|
-
# # }
|
488
|
-
# # end
|
489
|
-
# # end
|
490
|
-
# # end
|
491
|
-
# # end
|
492
|
-
#
|
493
|
-
# # frozen_string_literal: true
|
494
|
-
#
|
495
|
-
# module NatsWave
|
496
|
-
# module Concerns
|
497
|
-
# module Mappable
|
498
|
-
# extend ActiveSupport::Concern
|
499
|
-
#
|
500
|
-
# included do
|
501
|
-
# class_attribute :nats_wave_subscription_config, :nats_wave_publishing_config
|
502
|
-
# self.nats_wave_subscription_config = {}
|
503
|
-
# self.nats_wave_publishing_config = {}
|
504
|
-
# end
|
505
|
-
#
|
506
|
-
# class_methods do
|
507
|
-
# # Configure subscriptions with optional custom handler
|
508
|
-
# def nats_wave_subscribes_to(*subjects, **options, &block)
|
509
|
-
# NatsWave.logger.debug "📡 #{self.name}: Setting up subscription to subjects: #{subjects.inspect}"
|
510
|
-
#
|
511
|
-
# # Custom handler is now optional and runs AFTER default behavior
|
512
|
-
# custom_handler = options[:handler] || block
|
513
|
-
#
|
514
|
-
# subscription_config = {
|
515
|
-
# subjects: subjects.flatten,
|
516
|
-
# field_mappings: options[:field_mappings] || {},
|
517
|
-
# transformations: options[:transformations] || {},
|
518
|
-
# skip_fields: options[:skip_fields] || [],
|
519
|
-
# unique_fields: options[:unique_fields] || [:id],
|
520
|
-
# sync_strategy: options[:sync_strategy] || :upsert,
|
521
|
-
# custom_handler: custom_handler, # Renamed to be clear it's optional
|
522
|
-
# queue_group: options[:queue_group],
|
523
|
-
# auto_sync: options[:auto_sync] != false # Default to true, can be disabled
|
524
|
-
# }
|
525
|
-
#
|
526
|
-
# # Store the config
|
527
|
-
# config_key = subjects.join(',')
|
528
|
-
# self.nats_wave_subscription_config[config_key] = subscription_config
|
529
|
-
#
|
530
|
-
# # Create the subscription handler
|
531
|
-
# processed_handler = create_subscription_handler(subscription_config)
|
532
|
-
#
|
533
|
-
# # Register the subscription
|
534
|
-
# NatsWave::ModelRegistry.register_subscription(
|
535
|
-
# subjects: subjects.flatten,
|
536
|
-
# model: self.name,
|
537
|
-
# handler: processed_handler,
|
538
|
-
# queue_group: subscription_config[:queue_group]
|
539
|
-
# )
|
540
|
-
#
|
541
|
-
# NatsWave.logger.debug "📡 #{self.name}: Registered subscription for subjects: #{subjects}"
|
542
|
-
# end
|
543
|
-
#
|
544
|
-
# # Configure publishing with optional custom handler AND auto-publishing callbacks
|
545
|
-
# def nats_wave_publishes_to(*subjects, **options, &block)
|
546
|
-
# NatsWave.logger.debug "📤 #{self.name}: Setting up publishing to subjects: #{subjects.inspect}"
|
547
|
-
#
|
548
|
-
# # Custom handler is now optional and runs AFTER default behavior
|
549
|
-
# custom_handler = options[:handler] || block
|
550
|
-
#
|
551
|
-
# publishing_config = {
|
552
|
-
# subjects: subjects.flatten,
|
553
|
-
# field_mappings: options[:field_mappings] || {},
|
554
|
-
# transformations: options[:transformations] || {},
|
555
|
-
# skip_fields: options[:skip_fields] || [],
|
556
|
-
# conditions: options[:conditions] || {},
|
557
|
-
# actions: options[:actions] || [:create, :update, :destroy],
|
558
|
-
# custom_handler: custom_handler, # Renamed to be clear it's optional
|
559
|
-
# async: options[:async] != false, # Default to true
|
560
|
-
# enabled: options[:enabled] != false, # Default to true
|
561
|
-
# only_mapped_fields: options[:only_mapped_fields] # Changed: don't default to true
|
562
|
-
# }
|
563
|
-
#
|
564
|
-
# # Store the config
|
565
|
-
# config_key = subjects.join(',')
|
566
|
-
# self.nats_wave_publishing_config[config_key] = publishing_config
|
567
|
-
#
|
568
|
-
# # Add ActiveRecord callbacks for auto-publishing (only once per model)
|
569
|
-
# setup_publishing_callbacks_once
|
570
|
-
#
|
571
|
-
# NatsWave.logger.debug "📤 #{self.name}: Registered publishing config for subjects: #{subjects}"
|
572
|
-
# end
|
573
|
-
#
|
574
|
-
# # Get all subscription configurations
|
575
|
-
# def nats_wave_subscription_configs
|
576
|
-
# nats_wave_subscription_config
|
577
|
-
# end
|
578
|
-
#
|
579
|
-
# # Get all publishing configurations
|
580
|
-
# def nats_wave_publishing_configs
|
581
|
-
# nats_wave_publishing_config
|
582
|
-
# end
|
583
|
-
#
|
584
|
-
# # PUBLIC METHODS - These need to be accessible from lambda context
|
585
|
-
#
|
586
|
-
# # Process subscription data through field mappings and transformations
|
587
|
-
# def process_subscription_data(raw_data, config)
|
588
|
-
# processed_data = {}
|
589
|
-
# field_mappings = config[:field_mappings]
|
590
|
-
# transformations = config[:transformations]
|
591
|
-
# skip_fields = config[:skip_fields]
|
592
|
-
#
|
593
|
-
# raw_data.each do |field, value|
|
594
|
-
# field_str = field.to_s
|
595
|
-
# field_sym = field.to_sym
|
596
|
-
#
|
597
|
-
# # Skip if in skip_fields
|
598
|
-
# next if skip_fields.include?(field_str) || skip_fields.include?(field_sym)
|
599
|
-
#
|
600
|
-
# # Apply field mapping (external -> local)
|
601
|
-
# mapped_field = field_mappings[field_str] ||
|
602
|
-
# field_mappings[field_sym] ||
|
603
|
-
# field
|
604
|
-
#
|
605
|
-
# # Apply transformation if any
|
606
|
-
# transformed_value = apply_transformation(value, mapped_field, transformations, raw_data)
|
607
|
-
#
|
608
|
-
# processed_data[mapped_field.to_s] = transformed_value
|
609
|
-
# end
|
610
|
-
#
|
611
|
-
# processed_data
|
612
|
-
# end
|
613
|
-
#
|
614
|
-
# # Apply transformations to field values
|
615
|
-
# def apply_transformation(value, field, transformations, full_record)
|
616
|
-
# transformation = transformations[field] || transformations[field.to_sym]
|
617
|
-
#
|
618
|
-
# case transformation
|
619
|
-
# when Proc
|
620
|
-
# if transformation.arity == 2 || transformation.arity < 0
|
621
|
-
# transformation.call(value, full_record)
|
622
|
-
# else
|
623
|
-
# transformation.call(value)
|
624
|
-
# end
|
625
|
-
# when Symbol
|
626
|
-
# if self.respond_to?(transformation, true)
|
627
|
-
# self.send(transformation, value)
|
628
|
-
# elsif value.respond_to?(transformation)
|
629
|
-
# value.send(transformation)
|
630
|
-
# else
|
631
|
-
# NatsWave.logger.warn "Transformation method #{transformation} not found"
|
632
|
-
# value
|
633
|
-
# end
|
634
|
-
# else
|
635
|
-
# value
|
636
|
-
# end
|
637
|
-
# end
|
638
|
-
#
|
639
|
-
# # Default auto-sync behavior
|
640
|
-
# def perform_auto_sync(model_name, action, processed_data, config)
|
641
|
-
# unique_fields = config[:unique_fields]
|
642
|
-
# sync_strategy = config[:sync_strategy]
|
643
|
-
#
|
644
|
-
# case action.to_s.downcase
|
645
|
-
# when 'create', 'created'
|
646
|
-
# handle_auto_create(processed_data, unique_fields, sync_strategy)
|
647
|
-
# when 'update', 'updated'
|
648
|
-
# handle_auto_update(processed_data, unique_fields, sync_strategy)
|
649
|
-
# when 'delete', 'deleted', 'destroy', 'destroyed'
|
650
|
-
# handle_auto_delete(processed_data, unique_fields)
|
651
|
-
# else
|
652
|
-
# NatsWave.logger.warn "Unknown action for auto-sync: #{action}"
|
653
|
-
# end
|
654
|
-
# end
|
655
|
-
#
|
656
|
-
# def handle_auto_create(data, unique_fields, sync_strategy)
|
657
|
-
# case sync_strategy
|
658
|
-
# when :upsert
|
659
|
-
# existing = find_by_unique_fields(data, unique_fields)
|
660
|
-
# if existing
|
661
|
-
# existing.update!(data)
|
662
|
-
# NatsWave.logger.info "✅ Updated existing #{self.name}: #{existing.id}"
|
663
|
-
# else
|
664
|
-
# record = self.create!(data)
|
665
|
-
# NatsWave.logger.info "✅ Created new #{self.name}: #{record.id}"
|
666
|
-
# end
|
667
|
-
# when :create_only
|
668
|
-
# record = self.create!(data)
|
669
|
-
# NatsWave.logger.info "✅ Created new #{self.name}: #{record.id}"
|
670
|
-
# end
|
671
|
-
# rescue => e
|
672
|
-
# NatsWave.logger.error "❌ Failed to create #{self.name}: #{e.message}"
|
673
|
-
# raise
|
674
|
-
# end
|
675
|
-
#
|
676
|
-
# def handle_auto_update(data, unique_fields, sync_strategy)
|
677
|
-
# existing = find_by_unique_fields(data, unique_fields)
|
678
|
-
# if existing
|
679
|
-
# existing.update!(data)
|
680
|
-
# NatsWave.logger.info "✅ Updated #{self.name}: #{existing.id}"
|
681
|
-
# elsif sync_strategy == :upsert
|
682
|
-
# record = self.create!(data)
|
683
|
-
# NatsWave.logger.info "✅ Created new #{self.name} during update: #{record.id}"
|
684
|
-
# else
|
685
|
-
# NatsWave.logger.warn "⚠️ Record not found for update: #{data.slice(*unique_fields.map(&:to_s))}"
|
686
|
-
# end
|
687
|
-
# rescue => e
|
688
|
-
# NatsWave.logger.error "❌ Failed to update #{self.name}: #{e.message}"
|
689
|
-
# raise
|
690
|
-
# end
|
691
|
-
#
|
692
|
-
# def handle_auto_delete(data, unique_fields)
|
693
|
-
# existing = find_by_unique_fields(data, unique_fields)
|
694
|
-
# if existing
|
695
|
-
# existing.destroy!
|
696
|
-
# NatsWave.logger.info "✅ Deleted #{self.name}: #{existing.id}"
|
697
|
-
# else
|
698
|
-
# NatsWave.logger.warn "⚠️ Record not found for deletion: #{data.slice(*unique_fields.map(&:to_s))}"
|
699
|
-
# end
|
700
|
-
# rescue => e
|
701
|
-
# NatsWave.logger.error "❌ Failed to delete #{self.name}: #{e.message}"
|
702
|
-
# raise
|
703
|
-
# end
|
704
|
-
#
|
705
|
-
# def find_by_unique_fields(data, unique_fields)
|
706
|
-
# conditions = {}
|
707
|
-
# unique_fields.each do |field|
|
708
|
-
# field_str = field.to_s
|
709
|
-
# if data.key?(field_str)
|
710
|
-
# conditions[field] = data[field_str]
|
711
|
-
# elsif data.key?(field.to_sym)
|
712
|
-
# conditions[field] = data[field.to_sym]
|
713
|
-
# end
|
714
|
-
# end
|
715
|
-
#
|
716
|
-
# return nil if conditions.empty?
|
717
|
-
# self.find_by(conditions)
|
718
|
-
# end
|
719
|
-
#
|
720
|
-
# private
|
721
|
-
#
|
722
|
-
# # Setup ActiveRecord callbacks for auto-publishing (only once per model)
|
723
|
-
# def setup_publishing_callbacks_once
|
724
|
-
# # Only add callbacks if we're in ActiveRecord and haven't added them yet
|
725
|
-
# return unless defined?(ActiveRecord::Base) && self < ActiveRecord::Base
|
726
|
-
# return if @nats_wave_callbacks_added
|
727
|
-
#
|
728
|
-
# after_commit :trigger_nats_wave_auto_publish_on_create, on: :create
|
729
|
-
# after_commit :trigger_nats_wave_auto_publish_on_update, on: :update
|
730
|
-
# after_commit :trigger_nats_wave_auto_publish_on_destroy, on: :destroy
|
731
|
-
#
|
732
|
-
# @nats_wave_callbacks_added = true
|
733
|
-
# NatsWave.logger.debug "📤 #{self.name}: Added publishing callbacks"
|
734
|
-
# end
|
735
|
-
#
|
736
|
-
# # Create a subscription handler that processes data and calls custom handler AFTER auto-sync
|
737
|
-
# def create_subscription_handler(config)
|
738
|
-
# model_class = self
|
739
|
-
#
|
740
|
-
# lambda do |message|
|
741
|
-
# begin
|
742
|
-
# NatsWave.logger.debug "📨 Processing subscription message for #{model_class.name}"
|
743
|
-
#
|
744
|
-
# # Extract the raw data
|
745
|
-
# raw_data = message['data'] || {}
|
746
|
-
# model_name = message['model']
|
747
|
-
# action = message['action']
|
748
|
-
#
|
749
|
-
# # Process the data through mappings and transformations
|
750
|
-
# processed_data = model_class.process_subscription_data(raw_data, config)
|
751
|
-
#
|
752
|
-
# # Always perform auto-sync first (if enabled)
|
753
|
-
# if config[:auto_sync]
|
754
|
-
# NatsWave.logger.debug "📨 Performing auto-sync first"
|
755
|
-
# model_class.perform_auto_sync(model_name, action, processed_data, config)
|
756
|
-
# end
|
757
|
-
#
|
758
|
-
# # Then call custom handler if provided (optional)
|
759
|
-
# if config[:custom_handler]
|
760
|
-
# NatsWave.logger.debug "📨 Calling custom subscription handler after auto-sync"
|
761
|
-
# config[:custom_handler].call(model_name, action, processed_data, message)
|
762
|
-
# end
|
763
|
-
#
|
764
|
-
# rescue => e
|
765
|
-
# NatsWave.logger.error "Error in subscription handler for #{model_class.name}: #{e.message}"
|
766
|
-
# NatsWave.logger.error e.backtrace.join("\n")
|
767
|
-
# raise
|
768
|
-
# end
|
769
|
-
# end
|
770
|
-
# end
|
771
|
-
# end
|
772
|
-
#
|
773
|
-
# # Instance methods for publishing
|
774
|
-
# def nats_wave_publish(action = nil)
|
775
|
-
# action ||= determine_action_from_context
|
776
|
-
#
|
777
|
-
# self.class.nats_wave_publishing_configs.each do |subjects_key, config|
|
778
|
-
# next unless config[:enabled]
|
779
|
-
# next unless config[:actions].include?(action.to_sym)
|
780
|
-
#
|
781
|
-
# # Check conditions (only for publishing)
|
782
|
-
# if config[:conditions].any? && !evaluate_conditions(config[:conditions])
|
783
|
-
# NatsWave.logger.debug "📤 Skipping publish - conditions not met"
|
784
|
-
# next
|
785
|
-
# end
|
786
|
-
#
|
787
|
-
# # Get the raw data (current model attributes)
|
788
|
-
# raw_data = get_raw_attributes
|
789
|
-
#
|
790
|
-
# # Process the data through mappings and transformations
|
791
|
-
# processed_data = process_publishing_data(raw_data, config)
|
792
|
-
#
|
793
|
-
# # Determine which subjects to publish to based on action
|
794
|
-
# subjects_to_publish = determine_subjects_for_action(config[:subjects], action)
|
795
|
-
#
|
796
|
-
# # Always publish first (default behavior)
|
797
|
-
# subjects_to_publish.each do |subject|
|
798
|
-
# NatsWave.client.publish(
|
799
|
-
# subject: subject,
|
800
|
-
# model: self.class.name,
|
801
|
-
# action: action,
|
802
|
-
# data: processed_data, # This is the processed data, not raw attributes
|
803
|
-
# metadata: build_publishing_metadata
|
804
|
-
# )
|
805
|
-
#
|
806
|
-
# NatsWave.logger.info "📤 Published #{self.class.name} to #{subject}"
|
807
|
-
# end
|
808
|
-
#
|
809
|
-
# # Then call custom handler if provided (optional)
|
810
|
-
# if config[:custom_handler]
|
811
|
-
# NatsWave.logger.debug "📤 Calling custom publishing handler after publishing"
|
812
|
-
# config[:custom_handler].call(self.class.name, action, processed_data, self)
|
813
|
-
# end
|
814
|
-
# end
|
815
|
-
# rescue StandardError => e
|
816
|
-
# NatsWave.logger.error("Failed to publish: #{e.message}")
|
817
|
-
# # Don't re-raise to avoid breaking transactions
|
818
|
-
# end
|
819
|
-
#
|
820
|
-
# # Auto-publishing callback methods (only called once per action)
|
821
|
-
# def trigger_nats_wave_auto_publish_on_create
|
822
|
-
# return if @nats_wave_publishing_in_progress
|
823
|
-
# @nats_wave_publishing_in_progress = true
|
824
|
-
#
|
825
|
-
# NatsWave.logger.debug "🚀 Auto-publishing on create"
|
826
|
-
# nats_wave_publish('create')
|
827
|
-
#
|
828
|
-
# @nats_wave_publishing_in_progress = false
|
829
|
-
# end
|
830
|
-
#
|
831
|
-
# def trigger_nats_wave_auto_publish_on_update
|
832
|
-
# return if @nats_wave_publishing_in_progress
|
833
|
-
# @nats_wave_publishing_in_progress = true
|
834
|
-
#
|
835
|
-
# NatsWave.logger.debug "🚀 Auto-publishing on update"
|
836
|
-
# nats_wave_publish('update')
|
837
|
-
#
|
838
|
-
# @nats_wave_publishing_in_progress = false
|
839
|
-
# end
|
840
|
-
#
|
841
|
-
# def trigger_nats_wave_auto_publish_on_destroy
|
842
|
-
# return if @nats_wave_publishing_in_progress
|
843
|
-
# @nats_wave_publishing_in_progress = true
|
844
|
-
#
|
845
|
-
# NatsWave.logger.debug "🚀 Auto-publishing on destroy"
|
846
|
-
# nats_wave_publish('destroy')
|
847
|
-
#
|
848
|
-
# @nats_wave_publishing_in_progress = false
|
849
|
-
# end
|
850
|
-
#
|
851
|
-
# private
|
852
|
-
#
|
853
|
-
# def determine_action_from_context
|
854
|
-
# # Try to determine action from ActiveRecord context
|
855
|
-
# if defined?(ActiveRecord) && is_a?(ActiveRecord::Base)
|
856
|
-
# return 'create' if previously_new_record?
|
857
|
-
# return 'update' if saved_changes.any?
|
858
|
-
# return 'destroy' if destroyed?
|
859
|
-
# end
|
860
|
-
#
|
861
|
-
# 'update' # Default fallback
|
862
|
-
# end
|
863
|
-
#
|
864
|
-
# def get_raw_attributes
|
865
|
-
# # Get attributes (works for both ActiveRecord and other objects)
|
866
|
-
# if respond_to?(:attributes)
|
867
|
-
# attributes
|
868
|
-
# else
|
869
|
-
# # For non-ActiveRecord objects, get instance variables
|
870
|
-
# instance_variables.map { |v| [v.to_s.delete('@'), instance_variable_get(v)] }.to_h
|
871
|
-
# end
|
872
|
-
# end
|
873
|
-
#
|
874
|
-
# def process_publishing_data(raw_data, config)
|
875
|
-
# processed_data = {}
|
876
|
-
# field_mappings = config[:field_mappings] || {}
|
877
|
-
# transformations = config[:transformations] || {}
|
878
|
-
# skip_fields = config[:skip_fields] || []
|
879
|
-
# only_mapped_fields = config[:only_mapped_fields]
|
880
|
-
#
|
881
|
-
# # If only_mapped_fields is true, only process fields that have explicit mappings
|
882
|
-
# if only_mapped_fields && field_mappings.any?
|
883
|
-
# field_mappings.each do |local_field, external_field|
|
884
|
-
# local_field_str = local_field.to_s
|
885
|
-
#
|
886
|
-
# # Skip if this field is in skip_fields
|
887
|
-
# next if skip_fields.include?(local_field_str) || skip_fields.include?(local_field.to_sym)
|
888
|
-
#
|
889
|
-
# # Get the value from raw_data
|
890
|
-
# value = raw_data[local_field_str] || raw_data[local_field.to_sym]
|
891
|
-
#
|
892
|
-
# # Apply transformation if any (use external field name for transformation lookup)
|
893
|
-
# transformed_value = apply_publishing_transformation(value, external_field, transformations, raw_data)
|
894
|
-
#
|
895
|
-
# processed_data[external_field.to_s] = transformed_value
|
896
|
-
# end
|
897
|
-
# else
|
898
|
-
# # Original behavior - process all fields
|
899
|
-
# raw_data.each do |field, value|
|
900
|
-
# field_str = field.to_s
|
901
|
-
# field_sym = field.to_sym
|
902
|
-
#
|
903
|
-
# # Skip if in skip_fields
|
904
|
-
# next if skip_fields.include?(field_str) || skip_fields.include?(field_sym)
|
905
|
-
#
|
906
|
-
# # Apply field mapping (local -> external) - ONLY if mapping exists
|
907
|
-
# mapped_field = field_mappings[field_str] ||
|
908
|
-
# field_mappings[field_sym] ||
|
909
|
-
# field # Use original field name if no mapping
|
910
|
-
#
|
911
|
-
# # Apply transformation if any
|
912
|
-
# transformed_value = apply_publishing_transformation(value, mapped_field, transformations, raw_data)
|
913
|
-
#
|
914
|
-
# processed_data[mapped_field.to_s] = transformed_value
|
915
|
-
# end
|
916
|
-
# end
|
917
|
-
# processed_data
|
918
|
-
# end
|
919
|
-
#
|
920
|
-
# def apply_publishing_transformation(value, field, transformations, full_record)
|
921
|
-
# transformation = transformations[field] || transformations[field.to_sym]
|
922
|
-
#
|
923
|
-
# case transformation
|
924
|
-
# when Proc
|
925
|
-
# if transformation.arity == 2 || transformation.arity < 0
|
926
|
-
# transformation.call(value, full_record)
|
927
|
-
# else
|
928
|
-
# transformation.call(value)
|
929
|
-
# end
|
930
|
-
# when Symbol
|
931
|
-
# if self.respond_to?(transformation, true)
|
932
|
-
# self.send(transformation, value)
|
933
|
-
# elsif value.respond_to?(transformation)
|
934
|
-
# value.send(transformation)
|
935
|
-
# else
|
936
|
-
# NatsWave.logger.warn "Publishing transformation method #{transformation} not found"
|
937
|
-
# value
|
938
|
-
# end
|
939
|
-
# else
|
940
|
-
# value
|
941
|
-
# end
|
942
|
-
# end
|
943
|
-
#
|
944
|
-
# def determine_subjects_for_action(configured_subjects, action)
|
945
|
-
# # If subjects contain placeholders like {action}, replace them
|
946
|
-
# configured_subjects.map do |subject|
|
947
|
-
# if subject.include?('{action}')
|
948
|
-
# subject.gsub('{action}', action.to_s)
|
949
|
-
# elsif subject.include?('*')
|
950
|
-
# # Replace wildcard with specific action
|
951
|
-
# subject.gsub('*', action.to_s)
|
952
|
-
# else
|
953
|
-
# subject
|
954
|
-
# end
|
955
|
-
# end
|
956
|
-
# end
|
957
|
-
#
|
958
|
-
# def evaluate_conditions(conditions)
|
959
|
-
# conditions.all? do |condition, expected_value|
|
960
|
-
# case condition
|
961
|
-
# when Proc
|
962
|
-
# condition.call(self)
|
963
|
-
# when Symbol, String
|
964
|
-
# if respond_to?(condition, true)
|
965
|
-
# result = send(condition)
|
966
|
-
# expected_value.nil? ? result : result == expected_value
|
967
|
-
# else
|
968
|
-
# false
|
969
|
-
# end
|
970
|
-
# else
|
971
|
-
# false
|
972
|
-
# end
|
973
|
-
# end
|
974
|
-
# end
|
975
|
-
#
|
976
|
-
# def build_publishing_metadata
|
977
|
-
# {
|
978
|
-
# source_model: self.class.name,
|
979
|
-
# source_id: respond_to?(:id) ? id : object_id,
|
980
|
-
# published_at: Time.current.iso8601
|
981
|
-
# }
|
982
|
-
# end
|
983
|
-
# end
|
984
|
-
# end
|
985
|
-
# end
|
986
|
-
|
987
1
|
# frozen_string_literal: true
|
988
2
|
|
989
3
|
module NatsWave
|
@@ -1038,7 +52,7 @@ module NatsWave
|
|
1038
52
|
|
1039
53
|
# Configure publishing with optional custom handler AND auto-publishing callbacks
|
1040
54
|
def nats_wave_publishes_to(*subjects, **options, &block)
|
1041
|
-
NatsWave.logger.debug "⚙️
|
55
|
+
NatsWave.logger.debug "⚙️ #{self.name}: Setting up publishing to subjects: #{subjects.inspect}"
|
1042
56
|
|
1043
57
|
# Custom handler is now optional and runs AFTER default behavior
|
1044
58
|
custom_handler = options[:handler] || block
|
@@ -1063,7 +77,7 @@ module NatsWave
|
|
1063
77
|
# Add ActiveRecord callbacks for auto-publishing (only once per model)
|
1064
78
|
setup_publishing_callbacks_once
|
1065
79
|
|
1066
|
-
NatsWave.logger.debug "⚙️
|
80
|
+
NatsWave.logger.debug "⚙️ #{self.name}: Registered publishing config for subjects: #{subjects}"
|
1067
81
|
end
|
1068
82
|
|
1069
83
|
# Get all subscription configurations
|
@@ -1085,7 +99,7 @@ module NatsWave
|
|
1085
99
|
|
1086
100
|
# If only_mapped_fields is true, only process fields that have explicit mappings
|
1087
101
|
if only_mapped_fields && field_mappings.any?
|
1088
|
-
NatsWave.logger.debug "⚙️
|
102
|
+
NatsWave.logger.debug "⚙️ Only processing mapped fields: #{field_mappings.keys}"
|
1089
103
|
|
1090
104
|
field_mappings.each do |external_field, local_field|
|
1091
105
|
external_field_str = external_field.to_s
|
@@ -1241,7 +255,7 @@ module NatsWave
|
|
1241
255
|
after_commit :trigger_nats_wave_auto_publish_on_destroy, on: :destroy
|
1242
256
|
|
1243
257
|
@nats_wave_callbacks_added = true
|
1244
|
-
NatsWave.logger.debug "⚙️
|
258
|
+
NatsWave.logger.debug "⚙️ #{self.name}: Added publishing callbacks"
|
1245
259
|
end
|
1246
260
|
|
1247
261
|
# Create a subscription handler that processes data and calls custom handler AFTER auto-sync
|
@@ -1250,7 +264,7 @@ module NatsWave
|
|
1250
264
|
|
1251
265
|
lambda do |message|
|
1252
266
|
begin
|
1253
|
-
NatsWave.logger.debug "⚙️
|
267
|
+
NatsWave.logger.debug "⚙️ Processing subscription message for #{model_class.name}"
|
1254
268
|
|
1255
269
|
# Extract the raw data
|
1256
270
|
raw_data = message['data'] || {}
|
@@ -1264,12 +278,12 @@ module NatsWave
|
|
1264
278
|
action = subject_parts.last if subject_parts.any?
|
1265
279
|
end
|
1266
280
|
|
1267
|
-
NatsWave.logger.debug "✅
|
281
|
+
NatsWave.logger.debug "✅ Extracted action: '#{action}' from message"
|
1268
282
|
|
1269
283
|
# Handle nested data structure
|
1270
284
|
if raw_data['data'].is_a?(Hash)
|
1271
285
|
raw_data = raw_data['data']
|
1272
|
-
NatsWave.logger.debug "⚙️
|
286
|
+
NatsWave.logger.debug "⚙️ Using nested data structure"
|
1273
287
|
end
|
1274
288
|
|
1275
289
|
# Process the data through mappings and transformations
|
@@ -1277,13 +291,13 @@ module NatsWave
|
|
1277
291
|
|
1278
292
|
# Always perform auto-sync first (if enabled)
|
1279
293
|
if config[:auto_sync]
|
1280
|
-
NatsWave.logger.debug "⚙️
|
294
|
+
NatsWave.logger.debug "⚙️ Performing auto-sync first with #{processed_data}"
|
1281
295
|
model_class.perform_auto_sync(model_name, action, processed_data, config)
|
1282
296
|
end
|
1283
297
|
|
1284
298
|
# Then call custom handler if provided (optional)
|
1285
299
|
if config[:custom_handler]
|
1286
|
-
NatsWave.logger.debug "⚙️
|
300
|
+
NatsWave.logger.debug "⚙️ Calling custom subscription handler after auto-sync"
|
1287
301
|
config[:custom_handler].call(model_name, action, processed_data, message)
|
1288
302
|
end
|
1289
303
|
|
@@ -1306,7 +320,7 @@ module NatsWave
|
|
1306
320
|
|
1307
321
|
# Check conditions (only for publishing)
|
1308
322
|
if config[:conditions].any? && !evaluate_conditions(config[:conditions])
|
1309
|
-
NatsWave.logger.debug "⚙️
|
323
|
+
NatsWave.logger.debug "⚙️ Skipping publish - conditions not met"
|
1310
324
|
next
|
1311
325
|
end
|
1312
326
|
|
@@ -1334,7 +348,7 @@ module NatsWave
|
|
1334
348
|
|
1335
349
|
# Then call custom handler if provided (optional)
|
1336
350
|
if config[:custom_handler]
|
1337
|
-
NatsWave.logger.debug "⚙️
|
351
|
+
NatsWave.logger.debug "⚙️ Calling custom publishing handler after publishing"
|
1338
352
|
config[:custom_handler].call(self.class.name, action, processed_data, self)
|
1339
353
|
end
|
1340
354
|
end
|
@@ -18,7 +18,7 @@ module NatsWave
|
|
18
18
|
def initialize(options = {})
|
19
19
|
@nats_url = ENV['NATS_URL'] || "nats://localhost:4222"
|
20
20
|
@service_name = ENV['NATS_SERVICE_NAME'] || "purplewave"
|
21
|
-
@version = ENV['NATS_SERVICE_VERSION'] || "1.1.
|
21
|
+
@version = ENV['NATS_SERVICE_VERSION'] || "1.1.20"
|
22
22
|
@instance_id = ENV['NATS_INSTANCE_ID'] || Socket.gethostname
|
23
23
|
@database_url = ENV['NATS_DATABASE_URL'] || nil
|
24
24
|
@connection_pool_size = (ENV['NATS_CONNECTION_POOL_SIZE'] || 10).to_i
|
data/lib/nats_wave/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: nats_wave
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.1.
|
4
|
+
version: 1.1.20
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jeffrey Dabo
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-07-
|
11
|
+
date: 2025-07-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: nats-pure
|