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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b52406b98b5156e9768ed65c62a10231ad0ab91a18ff73d61204e4ff5cb6749b
4
- data.tar.gz: 6d4e71c9e9c91ed92644a61f9a2a1ca3ef1a92f7f7de193c86c58a34d3cfb445
3
+ metadata.gz: 56f43d30e884588fb8db2ecbf73b1c7e39aa7d63fbfbec563c156f6f13f9ee0c
4
+ data.tar.gz: c07acd7e6f1e7acc05a72d13eee5640c7fa5cdda91adc135fe81f4eb51262ae3
5
5
  SHA512:
6
- metadata.gz: 2b72a3a616956bf8175c19f7fea105ef5f7f2684fddcc17027a256c05331af7f02fd858e502079b40da46964bd6d4b665b6c3ffd628320b43d695fbd194135ad
7
- data.tar.gz: d3ea18ccf932f8800a2a061244c4a4012a256f4505b4426f951544d03c4d4b1bd03344a1d6b0190df144e3d912a333dddea72152faf59544e017cf22f71339c9
6
+ metadata.gz: 07dbf0eec97fbed551525feb102d4c101f7cfc6dd2f608ccb635e8a0b9248d64b8b66d610df63ae3f9b6a6d2cb9e66c2293485522230f5653ab9e0ae212b2451
7
+ data.tar.gz: 855b54e2f045c8d0c12dbad5bb0a553a44e9ef8d0c7b6ba33997c25f25997bee9a397276a61ea8ca510f0500c5138048c233fc65169a6133d90133e8925636d2
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- nats_wave (1.1.19)
4
+ nats_wave (1.1.20)
5
5
  activerecord (>= 6.1, < 8.0)
6
6
  activesupport (>= 6.1, < 8.0)
7
7
  concurrent-ruby (~> 1.1)
data/README.md CHANGED
@@ -174,7 +174,7 @@ rails console
174
174
  # This automatically publishes via nats_wave_publishes_to
175
175
  ```
176
176
 
177
- ## ⚙️ Configuration
177
+ ## ⚙️ Configuration
178
178
 
179
179
  ### Environment Variables
180
180
 
@@ -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.19'
80
+ version: ENV['APP_VERSION'] || '1.1.20'
81
81
  }
82
82
  end
83
83
 
@@ -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 "⚙️ #{self.name}: Setting up publishing to subjects: #{subjects.inspect}"
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 "⚙️ #{self.name}: Registered publishing config for subjects: #{subjects}"
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 "⚙️ Only processing mapped fields: #{field_mappings.keys}"
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 "⚙️ #{self.name}: Added publishing callbacks"
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 "⚙️ Processing subscription message for #{model_class.name}"
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 "✅ Extracted action: '#{action}' from message"
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 "⚙️ Using nested data structure"
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 "⚙️ Performing auto-sync first"
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 "⚙️ Calling custom subscription handler after auto-sync"
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 "⚙️ Skipping publish - conditions not met"
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 "⚙️ Calling custom publishing handler after publishing"
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.19"
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module NatsWave
4
- VERSION = '1.1.19'
4
+ VERSION = '1.1.20'
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.19
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-28 00:00:00.000000000 Z
11
+ date: 2025-07-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nats-pure