nats_wave 1.1.10 → 1.1.11

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: 6f2a585b3596818bf6c2af016223d821cdbd3560a9fb6442b5ce42f6553d7f8b
4
- data.tar.gz: 18d69e19889c450d47c30930cf7cf7f0e42a27855380c2cfa94f2d2765d2937a
3
+ metadata.gz: b32810b1ec7cd1601ddb675015fb161fce6d6bb5749dd19c40b0b928a94d0f5c
4
+ data.tar.gz: 4820121ee244b4192c372f0baa5abc2533f0b676b5066e48892ebcd305571b23
5
5
  SHA512:
6
- metadata.gz: 77dc3cbedd4d2415d2963d29ccad155e50ed1f93ee5ca7b984f6455217a27e24b0104422e2bd36f65cb498fea0576d4624f135042efecb4d75d5b9d49173e711
7
- data.tar.gz: e23ae34f7c8bab0ce7c1bc5483734a9d034f979ad7043c2d5112a638c6d80b94a31ec6ab4d1ee702d73997bbc675f69028c83ec8c4f36e35d9848f0fea2c3510
6
+ metadata.gz: bd66a5824fb58bc9abc27ce9f86aaa6ebd9ec95eb012c146ea2016b2a167c52ff1fbc596aa353167c4d66851aee352e9c534fe46259d9b4efdfd4fca01b9ddd2
7
+ data.tar.gz: a7413067598f7e155f38bc1a7ebadef0b24ba9bb2e3f3f146fbb9c34ef9bf6b99d4102a62698bdb6bc6042e8d861e86b9927659e432c35a8f996af100b6e02f8
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- nats_wave (1.1.10)
4
+ nats_wave (1.1.11)
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.10'
80
+ version: ENV['APP_VERSION'] || '1.1.11'
81
81
  }
82
82
  end
83
83
 
@@ -6,173 +6,358 @@
6
6
  # extend ActiveSupport::Concern
7
7
  #
8
8
  # included do
9
- # class_attribute :nats_wave_mapping_config, :nats_wave_subscription_config
10
- # self.nats_wave_mapping_config = {}
9
+ # class_attribute :nats_wave_subscription_config, :nats_wave_publishing_config
11
10
  # self.nats_wave_subscription_config = {}
11
+ # self.nats_wave_publishing_config = {}
12
12
  # end
13
13
  #
14
14
  # class_methods do
15
- # # Configure how this model maps to external models
16
- # def nats_wave_maps_to(external_models)
17
- # self.nats_wave_mapping_config = external_models
18
- #
19
- # # Register this mapping globally
20
- # NatsWave::ModelRegistry.register_mapping(self.name, external_models)
21
- # end
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)
22
18
  #
23
- # # Configure how external models map to this model AND what subjects to subscribe to
24
- # def nats_wave_maps_from(external_model, options = {})
25
- # Rails.logger.debug "🔄 #{self.name}: Setting up mapping from #{external_model} with options: #{options.inspect}" if defined?(Rails)
19
+ # handler = options[:handler] || block
26
20
  #
27
- # mapping = {
21
+ # subscription_config = {
22
+ # subjects: subjects.flatten,
28
23
  # field_mappings: options[:field_mappings] || {},
29
24
  # transformations: options[:transformations] || {},
30
- # conditions: options[:conditions] || {},
31
- # sync_strategy: options[:sync_strategy] || :upsert,
32
- # unique_fields: options[:unique_fields] || [:id],
33
25
  # skip_fields: options[:skip_fields] || [],
34
- # subjects: options[:subjects] || [],
35
- # handler: options[:handler],
36
- # queue_group: options[:queue_group]
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
37
31
  # }
38
32
  #
39
- # self.nats_wave_mapping_config[external_model] = mapping
40
- #
41
- # Rails.logger.debug "🔄 #{self.name}: Registering reverse mapping for #{external_model}" if defined?(Rails)
42
- # NatsWave::ModelRegistry.register_reverse_mapping(external_model, self.name, mapping)
43
- #
44
- # # Register subscription if subjects are provided
45
- # if mapping[:subjects].any?
46
- # Rails.logger.debug "🔄 #{self.name}: Registering subscription for subjects: #{mapping[:subjects]}" if defined?(Rails)
47
- # NatsWave::ModelRegistry.register_subscription(
48
- # subjects: mapping[:subjects],
49
- # model: self.name,
50
- # external_model: external_model,
51
- # handler: mapping[:handler],
52
- # queue_group: mapping[:queue_group]
53
- # )
54
- # else
55
- # Rails.logger.debug "🔄 #{self.name}: No subjects provided, skipping subscription registration" if defined?(Rails)
56
- # end
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)
57
49
  # end
58
50
  #
59
- # # Configure subscriptions without model mapping (for custom handlers)
60
- # def nats_wave_subscribes_to(*subjects, handler: nil, queue_group: nil, &block)
61
- # handler ||= block
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)
62
54
  #
63
- # Rails.logger.debug "📡 #{self.name}: Setting up subscription to subjects: #{subjects.inspect}" if defined?(Rails)
55
+ # handler = options[:handler] || block
64
56
  #
65
- # subscription_config = {
57
+ # publishing_config = {
66
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],
67
64
  # handler: handler,
68
- # queue_group: queue_group,
69
- # model: self.name
65
+ # async: options[:async] != false, # Default to true
66
+ # enabled: options[:enabled] != false # Default to true
70
67
  # }
71
68
  #
72
- # self.nats_wave_subscription_config[:custom] = subscription_config
69
+ # # Store the config
70
+ # config_key = subjects.join(',')
71
+ # self.nats_wave_publishing_config[config_key] = publishing_config
73
72
  #
74
- # Rails.logger.debug "📡 #{self.name}: Registering subscription in ModelRegistry" if defined?(Rails)
75
- # NatsWave::ModelRegistry.register_subscription(
76
- # subjects: subjects.flatten,
77
- # model: self.name,
78
- # handler: handler,
79
- # queue_group: queue_group
80
- # )
73
+ # Rails.logger.debug "📤 #{self.name}: Registered publishing config for subjects: #{subjects}" if defined?(Rails)
81
74
  # end
82
75
  #
83
- # # Auto-generate subjects based on external model pattern
84
- # def nats_wave_auto_subjects_for(external_model, service_prefix: nil, actions: [:create, :update, :destroy])
85
- # model_name = external_model.underscore
76
+ # # Get all subscription configurations
77
+ # def nats_wave_subscription_configs
78
+ # nats_wave_subscription_config
79
+ # end
86
80
  #
87
- # if service_prefix
88
- # subjects = actions.map { |action| "#{service_prefix}.#{model_name}.#{action}" }
89
- # subjects << "#{service_prefix}.#{model_name}.*" # Wildcard for all actions
90
- # else
91
- # # Try to infer from common patterns
92
- # subjects = [
93
- # "#{model_name}.*", # Simple pattern
94
- # "events.#{model_name}.*", # Events pattern
95
- # "*.#{model_name}.*", # Service.model pattern
96
- # "*.events.#{model_name}.*" # Service.events.model pattern
97
- # ]
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
98
157
  # end
99
158
  #
100
- # subjects
159
+ # processed_data
101
160
  # end
102
161
  #
103
- # # Get mapping configuration for an external model
104
- # def nats_wave_mapping_for(external_model)
105
- # nats_wave_mapping_config[external_model]
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
106
185
  # end
107
186
  #
108
- # # Check if this model can sync from an external model
109
- # def nats_wave_can_sync_from?(external_model)
110
- # nats_wave_mapping_config.key?(external_model)
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
111
202
  # end
112
203
  #
113
- # # Get all external models this model can sync from
114
- # def nats_wave_external_models
115
- # nats_wave_mapping_config.keys
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
116
222
  # end
117
223
  #
118
- # # Get all subjects this model subscribes to
119
- # def nats_wave_subscribed_subjects
120
- # subjects = []
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
121
239
  #
122
- # # From model mappings
123
- # nats_wave_mapping_config.each do |_, config|
124
- # subjects.concat(config[:subjects] || [])
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)
125
247
  # end
248
+ # rescue => e
249
+ # Rails.logger.error "❌ Failed to delete #{self.name}: #{e.message}" if defined?(Rails)
250
+ # raise
251
+ # end
126
252
  #
127
- # # From custom subscriptions
128
- # if nats_wave_subscription_config[:custom]
129
- # subjects.concat(nats_wave_subscription_config[:custom][:subjects] || [])
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
130
262
  # end
131
263
  #
132
- # subjects.uniq
264
+ # return nil if conditions.empty?
265
+ # self.find_by(conditions)
133
266
  # end
134
267
  # end
135
268
  #
136
- # # Instance methods remain the same...
137
- # def nats_wave_mapped_attributes_for(target_model)
138
- # mapping = self.class.nats_wave_mapping_config[target_model]
139
- # return attributes unless mapping
140
- #
141
- # mapped_attrs = {}
142
- # field_mappings = mapping[:field_mappings] || {}
143
- # skip_fields = mapping[:skip_fields] || []
269
+ # # Instance methods for publishing
270
+ # def nats_wave_publish(action = nil)
271
+ # action ||= determine_action_from_context
144
272
  #
145
- # attributes.each do |key, value|
146
- # next if skip_fields.include?(key.to_s) || skip_fields.include?(key.to_sym)
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)
147
276
  #
148
- # mapped_key = field_mappings[key] || field_mappings[key.to_sym] || key
149
- # mapped_attrs[mapped_key] = transform_value(value, mapped_key, mapping[:transformations] || {})
150
- # end
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
151
282
  #
152
- # mapped_attrs
153
- # end
283
+ # # Get the raw data (current model attributes)
284
+ # raw_data = get_raw_attributes
154
285
  #
155
- # def nats_wave_unique_identifier_for(external_model)
156
- # mapping = self.class.nats_wave_mapping_config[external_model]
157
- # unique_fields = mapping&.dig(:unique_fields) || [:id]
286
+ # # Process the data through mappings and transformations (same as subscription)
287
+ # processed_data = self.class.process_data(raw_data, config)
158
288
  #
159
- # unique_fields.map { |field| [field, send(field)] }.to_h
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
160
311
  # end
161
312
  #
162
313
  # private
163
314
  #
164
- # def transform_value(value, field, transformations)
165
- # transformation = transformations[field] || transformations[field.to_sym]
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
166
322
  #
167
- # case transformation
168
- # when Proc
169
- # transformation.call(value)
170
- # when Symbol
171
- # send(transformation, value) if respond_to?(transformation, true)
172
- # else
173
- # value
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
174
333
  # end
175
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
176
361
  # end
177
362
  # end
178
363
  # end
@@ -227,7 +412,7 @@ module NatsWave
227
412
  Rails.logger.debug "📡 #{self.name}: Registered subscription for subjects: #{subjects}" if defined?(Rails)
228
413
  end
229
414
 
230
- # Configure publishing with optional custom handler
415
+ # Configure publishing with optional custom handler AND auto-publishing callbacks
231
416
  def nats_wave_publishes_to(*subjects, **options, &block)
232
417
  Rails.logger.debug "📤 #{self.name}: Setting up publishing to subjects: #{subjects.inspect}" if defined?(Rails)
233
418
 
@@ -249,6 +434,9 @@ module NatsWave
249
434
  config_key = subjects.join(',')
250
435
  self.nats_wave_publishing_config[config_key] = publishing_config
251
436
 
437
+ # Add ActiveRecord callbacks for auto-publishing
438
+ setup_publishing_callbacks(publishing_config)
439
+
252
440
  Rails.logger.debug "📤 #{self.name}: Registered publishing config for subjects: #{subjects}" if defined?(Rails)
253
441
  end
254
442
 
@@ -274,6 +462,26 @@ module NatsWave
274
462
 
275
463
  private
276
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
+
277
485
  # Create a subscription handler that processes data and calls custom handler or auto-sync
278
486
  def create_subscription_handler(config)
279
487
  model_class = self
@@ -288,7 +496,7 @@ module NatsWave
288
496
  action = message['action']
289
497
 
290
498
  # Process the data through mappings and transformations
291
- processed_data = model_class.process_data(raw_data, config)
499
+ processed_data = process_subscription_data(raw_data, config)
292
500
 
293
501
  # If custom handler is provided, call it with model_name, action, processed_data
294
502
  if config[:handler]
@@ -310,8 +518,8 @@ module NatsWave
310
518
  end
311
519
  end
312
520
 
313
- # Process data through field mappings and transformations (used by both subscription and publishing)
314
- 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)
315
523
  processed_data = {}
316
524
  field_mappings = config[:field_mappings]
317
525
  transformations = config[:transformations]
@@ -338,7 +546,7 @@ module NatsWave
338
546
  processed_data
339
547
  end
340
548
 
341
- # Apply transformations to field values
549
+ # Apply transformations to field values (CLASS METHOD)
342
550
  def apply_transformation(value, field, transformations, full_record)
343
551
  transformation = transformations[field] || transformations[field.to_sym]
344
552
 
@@ -363,7 +571,7 @@ module NatsWave
363
571
  end
364
572
  end
365
573
 
366
- # Default auto-sync behavior
574
+ # Default auto-sync behavior (CLASS METHOD)
367
575
  def perform_auto_sync(model_name, action, processed_data, config)
368
576
  unique_fields = config[:unique_fields]
369
577
  sync_strategy = config[:sync_strategy]
@@ -462,8 +670,8 @@ module NatsWave
462
670
  # Get the raw data (current model attributes)
463
671
  raw_data = get_raw_attributes
464
672
 
465
- # Process the data through mappings and transformations (same as subscription)
466
- 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)
467
675
 
468
676
  # If custom handler is provided, call it
469
677
  if config[:handler]
@@ -489,6 +697,22 @@ module NatsWave
489
697
  # Don't re-raise to avoid breaking transactions
490
698
  end
491
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
+
492
716
  private
493
717
 
494
718
  def determine_action_from_context
@@ -512,6 +736,58 @@ module NatsWave
512
736
  end
513
737
  end
514
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
+
515
791
  def evaluate_conditions(conditions)
516
792
  conditions.all? do |condition, expected_value|
517
793
  case condition
@@ -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.10"
21
+ @version = ENV['NATS_SERVICE_VERSION'] || "1.1.11"
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.10'
4
+ VERSION = '1.1.11'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nats_wave
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.10
4
+ version: 1.1.11
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeffrey Dabo