nats_wave 1.1.12 → 1.1.13

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b0f72efa07e504282e4a42fd5a21d526272266d2fa119b1032d867f27847e1eb
4
- data.tar.gz: 656647fe9ab1c21470c06240f3a6448e97749855ed939557ccd9df58fb684a1c
3
+ metadata.gz: 8ddbe5c156fb09eac8b27bc139e2508dc5de435c3c9a1346c33ed9fc7c4d1ba4
4
+ data.tar.gz: 8ad21def0299f83bf996e9e462b7465aa46527382e1b93b70a36558a2922dfb1
5
5
  SHA512:
6
- metadata.gz: 74f5fe30f7d8271bb8de826057a9b8cdc67e4af796f60e56178259fa7bfa71feae3f116d2d1ed0e7ff5bc004256340d8845bbe4229f9cadf85827a7112678a16
7
- data.tar.gz: 429da1483506f2665b940f0864bcf22f0699c90879ad937c844c9fc071eeb9557576b2dfbc3fa9dc3124b04e98d71c152f40282f96269d2f25a089d0ce123be3
6
+ metadata.gz: 76761c8ea270e13c253be663049e085fd7928d6d6d42ca3c7e3afaa6eee9906c879a7cf476976e1d2c2a3e4ceb09ddd8bc24b0f007d56e2f0f8bd577aaebdf98
7
+ data.tar.gz: 5fa9fa21df8c670492e99e3bb70974c47bf0428e4ca50c151970772597d6c927e7fe241e51bbec0371f6432924f2114418a7e48895b8b2f28ea66fdfce7f44ad
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- nats_wave (1.1.12)
4
+ nats_wave (1.1.13)
5
5
  activerecord (>= 6.1, < 8.0)
6
6
  activesupport (>= 6.1, < 8.0)
7
7
  concurrent-ruby (~> 1.1)
@@ -77,7 +77,7 @@ module NatsWave
77
77
  {
78
78
  service: @service_name,
79
79
  environment: ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'unknown',
80
- version: ENV['APP_VERSION'] || '1.1.12'
80
+ version: ENV['APP_VERSION'] || '1.1.13'
81
81
  }
82
82
  end
83
83
 
@@ -1,3 +1,367 @@
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
+ # #
1
365
  # # # frozen_string_literal: true
2
366
  # #
3
367
  # # module NatsWave
@@ -48,7 +412,7 @@
48
412
  # # Rails.logger.debug "📡 #{self.name}: Registered subscription for subjects: #{subjects}" if defined?(Rails)
49
413
  # # end
50
414
  # #
51
- # # # Configure publishing with optional custom handler
415
+ # # # Configure publishing with optional custom handler AND auto-publishing callbacks
52
416
  # # def nats_wave_publishes_to(*subjects, **options, &block)
53
417
  # # Rails.logger.debug "📤 #{self.name}: Setting up publishing to subjects: #{subjects.inspect}" if defined?(Rails)
54
418
  # #
@@ -70,6 +434,9 @@
70
434
  # # config_key = subjects.join(',')
71
435
  # # self.nats_wave_publishing_config[config_key] = publishing_config
72
436
  # #
437
+ # # # Add ActiveRecord callbacks for auto-publishing
438
+ # # setup_publishing_callbacks(publishing_config)
439
+ # #
73
440
  # # Rails.logger.debug "📤 #{self.name}: Registered publishing config for subjects: #{subjects}" if defined?(Rails)
74
441
  # # end
75
442
  # #
@@ -95,6 +462,26 @@
95
462
  # #
96
463
  # # private
97
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
+ # #
98
485
  # # # Create a subscription handler that processes data and calls custom handler or auto-sync
99
486
  # # def create_subscription_handler(config)
100
487
  # # model_class = self
@@ -109,7 +496,7 @@
109
496
  # # action = message['action']
110
497
  # #
111
498
  # # # Process the data through mappings and transformations
112
- # # processed_data = model_class.process_data(raw_data, config)
499
+ # # processed_data = process_subscription_data(raw_data, config)
113
500
  # #
114
501
  # # # If custom handler is provided, call it with model_name, action, processed_data
115
502
  # # if config[:handler]
@@ -131,8 +518,8 @@
131
518
  # # end
132
519
  # # end
133
520
  # #
134
- # # # Process data through field mappings and transformations (used by both subscription and publishing)
135
- # # def process_data(raw_data, config)
521
+ # # # Process subscription data through field mappings and transformations (CLASS METHOD)
522
+ # # def process_subscription_data(raw_data, config)
136
523
  # # processed_data = {}
137
524
  # # field_mappings = config[:field_mappings]
138
525
  # # transformations = config[:transformations]
@@ -159,7 +546,7 @@
159
546
  # # processed_data
160
547
  # # end
161
548
  # #
162
- # # # Apply transformations to field values
549
+ # # # Apply transformations to field values (CLASS METHOD)
163
550
  # # def apply_transformation(value, field, transformations, full_record)
164
551
  # # transformation = transformations[field] || transformations[field.to_sym]
165
552
  # #
@@ -184,7 +571,7 @@
184
571
  # # end
185
572
  # # end
186
573
  # #
187
- # # # Default auto-sync behavior
574
+ # # # Default auto-sync behavior (CLASS METHOD)
188
575
  # # def perform_auto_sync(model_name, action, processed_data, config)
189
576
  # # unique_fields = config[:unique_fields]
190
577
  # # sync_strategy = config[:sync_strategy]
@@ -283,8 +670,8 @@
283
670
  # # # Get the raw data (current model attributes)
284
671
  # # raw_data = get_raw_attributes
285
672
  # #
286
- # # # Process the data through mappings and transformations (same as subscription)
287
- # # processed_data = self.class.process_data(raw_data, config)
673
+ # # # Process the data through mappings and transformations
674
+ # # processed_data = process_publishing_data(raw_data, config)
288
675
  # #
289
676
  # # # If custom handler is provided, call it
290
677
  # # if config[:handler]
@@ -310,6 +697,22 @@
310
697
  # # # Don't re-raise to avoid breaking transactions
311
698
  # # end
312
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
+ # #
313
716
  # # private
314
717
  # #
315
718
  # # def determine_action_from_context
@@ -333,6 +736,58 @@
333
736
  # # end
334
737
  # # end
335
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
+ # #
336
791
  # # def evaluate_conditions(conditions)
337
792
  # # conditions.all? do |condition, expected_value|
338
793
  # # case condition
@@ -380,7 +835,8 @@
380
835
  # def nats_wave_subscribes_to(*subjects, **options, &block)
381
836
  # Rails.logger.debug "📡 #{self.name}: Setting up subscription to subjects: #{subjects.inspect}" if defined?(Rails)
382
837
  #
383
- # handler = options[:handler] || block
838
+ # # Custom handler is now optional and runs AFTER default behavior
839
+ # custom_handler = options[:handler] || block
384
840
  #
385
841
  # subscription_config = {
386
842
  # subjects: subjects.flatten,
@@ -389,7 +845,7 @@
389
845
  # skip_fields: options[:skip_fields] || [],
390
846
  # unique_fields: options[:unique_fields] || [:id],
391
847
  # sync_strategy: options[:sync_strategy] || :upsert,
392
- # handler: handler,
848
+ # custom_handler: custom_handler, # Renamed to be clear it's optional
393
849
  # queue_group: options[:queue_group],
394
850
  # auto_sync: options[:auto_sync] != false # Default to true, can be disabled
395
851
  # }
@@ -416,7 +872,8 @@
416
872
  # def nats_wave_publishes_to(*subjects, **options, &block)
417
873
  # Rails.logger.debug "📤 #{self.name}: Setting up publishing to subjects: #{subjects.inspect}" if defined?(Rails)
418
874
  #
419
- # handler = options[:handler] || block
875
+ # # Custom handler is now optional and runs AFTER default behavior
876
+ # custom_handler = options[:handler] || block
420
877
  #
421
878
  # publishing_config = {
422
879
  # subjects: subjects.flatten,
@@ -425,17 +882,18 @@
425
882
  # skip_fields: options[:skip_fields] || [],
426
883
  # conditions: options[:conditions] || {},
427
884
  # actions: options[:actions] || [:create, :update, :destroy],
428
- # handler: handler,
885
+ # custom_handler: custom_handler, # Renamed to be clear it's optional
429
886
  # async: options[:async] != false, # Default to true
430
- # enabled: options[:enabled] != 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
431
889
  # }
432
890
  #
433
891
  # # Store the config
434
892
  # config_key = subjects.join(',')
435
893
  # self.nats_wave_publishing_config[config_key] = publishing_config
436
894
  #
437
- # # Add ActiveRecord callbacks for auto-publishing
438
- # setup_publishing_callbacks(publishing_config)
895
+ # # Add ActiveRecord callbacks for auto-publishing (only once per model)
896
+ # setup_publishing_callbacks_once
439
897
  #
440
898
  # Rails.logger.debug "📤 #{self.name}: Registered publishing config for subjects: #{subjects}" if defined?(Rails)
441
899
  # end
@@ -450,39 +908,23 @@
450
908
  # nats_wave_publishing_config
451
909
  # end
452
910
  #
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
911
  # private
464
912
  #
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
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
468
916
  # return unless defined?(ActiveRecord::Base) && self < ActiveRecord::Base
917
+ # return if @nats_wave_callbacks_added
469
918
  #
470
- # actions = config[:actions]
471
- #
472
- # if actions.include?(:create)
473
- # after_commit :trigger_nats_wave_auto_publish_on_create, on: :create
474
- # end
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
475
922
  #
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
923
+ # @nats_wave_callbacks_added = true
924
+ # Rails.logger.debug "📤 #{self.name}: Added publishing callbacks" if defined?(Rails)
483
925
  # end
484
926
  #
485
- # # Create a subscription handler that processes data and calls custom handler or auto-sync
927
+ # # Create a subscription handler that processes data and calls custom handler AFTER auto-sync
486
928
  # def create_subscription_handler(config)
487
929
  # model_class = self
488
930
  #
@@ -498,16 +940,16 @@
498
940
  # # Process the data through mappings and transformations
499
941
  # processed_data = process_subscription_data(raw_data, config)
500
942
  #
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
943
+ # # Always perform auto-sync first (if enabled)
944
+ # if config[:auto_sync]
945
+ # Rails.logger.debug "📨 Performing auto-sync first" if defined?(Rails)
508
946
  # 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)
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)
511
953
  # end
512
954
  #
513
955
  # rescue => e
@@ -673,23 +1115,26 @@
673
1115
  # # Process the data through mappings and transformations
674
1116
  # processed_data = process_publishing_data(raw_data, config)
675
1117
  #
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
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)
693
1138
  # end
694
1139
  # end
695
1140
  # rescue StandardError => e
@@ -697,20 +1142,35 @@
697
1142
  # # Don't re-raise to avoid breaking transactions
698
1143
  # end
699
1144
  #
700
- # # Auto-publishing callback methods
1145
+ # # Auto-publishing callback methods (only called once per action)
701
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
+ #
702
1150
  # Rails.logger.debug "🚀 Auto-publishing on create" if defined?(Rails)
703
1151
  # nats_wave_publish('create')
1152
+ #
1153
+ # @nats_wave_publishing_in_progress = false
704
1154
  # end
705
1155
  #
706
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
+ #
707
1160
  # Rails.logger.debug "🚀 Auto-publishing on update" if defined?(Rails)
708
1161
  # nats_wave_publish('update')
1162
+ #
1163
+ # @nats_wave_publishing_in_progress = false
709
1164
  # end
710
1165
  #
711
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
+ #
712
1170
  # Rails.logger.debug "🚀 Auto-publishing on destroy" if defined?(Rails)
713
1171
  # nats_wave_publish('destroy')
1172
+ #
1173
+ # @nats_wave_publishing_in_progress = false
714
1174
  # end
715
1175
  #
716
1176
  # private
@@ -742,8 +1202,22 @@
742
1202
  # field_mappings = config[:field_mappings]
743
1203
  # transformations = config[:transformations]
744
1204
  # skip_fields = config[:skip_fields]
745
- #
746
- # raw_data.each do |field, value|
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|
747
1221
  # field_str = field.to_s
748
1222
  # field_sym = field.to_sym
749
1223
  #
@@ -788,6 +1262,20 @@
788
1262
  # end
789
1263
  # end
790
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
+ #
791
1279
  # def evaluate_conditions(conditions)
792
1280
  # conditions.all? do |condition, expected_value|
793
1281
  # case condition
@@ -816,7 +1304,6 @@
816
1304
  # end
817
1305
  # end
818
1306
  # end
819
-
820
1307
  # frozen_string_literal: true
821
1308
 
822
1309
  module NatsWave
@@ -884,8 +1371,7 @@ module NatsWave
884
1371
  actions: options[:actions] || [:create, :update, :destroy],
885
1372
  custom_handler: custom_handler, # Renamed to be clear it's optional
886
1373
  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
1374
+ enabled: options[:enabled] != false # Default to true
889
1375
  }
890
1376
 
891
1377
  # Store the config
@@ -938,7 +1424,7 @@ module NatsWave
938
1424
  action = message['action']
939
1425
 
940
1426
  # Process the data through mappings and transformations
941
- processed_data = process_subscription_data(raw_data, config)
1427
+ processed_data = model_class.process_subscription_data(raw_data, config)
942
1428
 
943
1429
  # Always perform auto-sync first (if enabled)
944
1430
  if config[:auto_sync]
@@ -960,7 +1446,7 @@ module NatsWave
960
1446
  end
961
1447
  end
962
1448
 
963
- # Process subscription data through field mappings and transformations (CLASS METHOD)
1449
+ # Process subscription data through field mappings and transformations (PUBLIC CLASS METHOD)
964
1450
  def process_subscription_data(raw_data, config)
965
1451
  processed_data = {}
966
1452
  field_mappings = config[:field_mappings]
@@ -974,7 +1460,7 @@ module NatsWave
974
1460
  # Skip if in skip_fields
975
1461
  next if skip_fields.include?(field_str) || skip_fields.include?(field_sym)
976
1462
 
977
- # Apply field mapping
1463
+ # Apply field mapping (external -> local)
978
1464
  mapped_field = field_mappings[field_str] ||
979
1465
  field_mappings[field_sym] ||
980
1466
  field
@@ -988,7 +1474,7 @@ module NatsWave
988
1474
  processed_data
989
1475
  end
990
1476
 
991
- # Apply transformations to field values (CLASS METHOD)
1477
+ # Apply transformations to field values (PUBLIC CLASS METHOD)
992
1478
  def apply_transformation(value, field, transformations, full_record)
993
1479
  transformation = transformations[field] || transformations[field.to_sym]
994
1480
 
@@ -1013,7 +1499,7 @@ module NatsWave
1013
1499
  end
1014
1500
  end
1015
1501
 
1016
- # Default auto-sync behavior (CLASS METHOD)
1502
+ # Default auto-sync behavior (PUBLIC CLASS METHOD)
1017
1503
  def perform_auto_sync(model_name, action, processed_data, config)
1018
1504
  unique_fields = config[:unique_fields]
1019
1505
  sync_strategy = config[:sync_strategy]
@@ -1196,38 +1682,24 @@ module NatsWave
1196
1682
  end
1197
1683
  end
1198
1684
 
1199
- # Process publishing data through field mappings and transformations (INSTANCE METHOD)
1685
+ # Process publishing data - much simpler now! (INSTANCE METHOD)
1200
1686
  def process_publishing_data(raw_data, config)
1201
1687
  processed_data = {}
1202
1688
  field_mappings = config[:field_mappings]
1203
1689
  transformations = config[:transformations]
1204
1690
  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
1691
 
1220
- fields_to_process.each do |field, value|
1692
+ raw_data.each do |field, value|
1221
1693
  field_str = field.to_s
1222
1694
  field_sym = field.to_sym
1223
1695
 
1224
1696
  # Skip if in skip_fields
1225
1697
  next if skip_fields.include?(field_str) || skip_fields.include?(field_sym)
1226
1698
 
1227
- # Apply field mapping (local -> external)
1699
+ # Apply field mapping (local -> external) - ONLY if mapping exists
1228
1700
  mapped_field = field_mappings[field_str] ||
1229
1701
  field_mappings[field_sym] ||
1230
- field
1702
+ field # Use original field name if no mapping
1231
1703
 
1232
1704
  # Apply transformation if any
1233
1705
  transformed_value = apply_publishing_transformation(value, mapped_field, transformations, raw_data)
@@ -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.12"
21
+ @version = ENV['NATS_SERVICE_VERSION'] || "1.1.13"
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module NatsWave
4
- VERSION = '1.1.12'
4
+ VERSION = '1.1.13'
5
5
  end
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.12
4
+ version: 1.1.13
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-25 00:00:00.000000000 Z
11
+ date: 2025-07-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nats-pure