nats_wave 1.1.4 → 1.1.5

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: 9bfb2eac0b9830d1f8dc6d13b2393760070b2736255d79625c650c11bda480f3
4
- data.tar.gz: 391d0c565b7cb0e3000d36820558ac538db59f4ba6199b23dc1c5e6248d4952a
3
+ metadata.gz: 87bd7c130badc871675b1dee2a91bafc98c8d0f64c16fe114a6a4bb6b97387e3
4
+ data.tar.gz: 0e2ff399e1b408d64d832488f3a2c73316e58216ca4deda02282cde3de223134
5
5
  SHA512:
6
- metadata.gz: 5e9cf9c585178821e015b57661ecee82b87d97672b0abeb72df096fcb00764ebc2d4f249c66fcdb183347f27b0d9b15de9bc7c01d804d61257bc9c51467e7d7e
7
- data.tar.gz: 6be91c8236c2c23236408d0a3400516db0f8f5bd4d80b843a181a9d449dd75a64b2bf4a5d9ea851df7bdbf7cbb9a03deefc67fb7c32dae8cd3d0b9843c505384
6
+ metadata.gz: 01400fba320e5d11427dc593f6f15730627137c84aa4b320ddcd1a6dda5bb33f0257c46d5fd64455f0c205c74321bcbc5190759722fb52e8f606c1bb28283800
7
+ data.tar.gz: 33795a3661cfd98b0c24e68f0c95164e4b79c838822c3f345e66c03b3702c867e40f39bba29d9515d65d0b3de4780d3e3561d0dd3fedc35d746a7b482b1c9e19
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- nats_wave (1.1.4)
4
+ nats_wave (1.1.5)
5
5
  activerecord (>= 6.1, < 8.0)
6
6
  activesupport (>= 6.1, < 8.0)
7
7
  concurrent-ruby (~> 1.1)
@@ -1,64 +1,56 @@
1
1
  # frozen_string_literal: true
2
- #
3
- NatsWave.configure do |config|
4
- # Basic configuration
5
- config.nats_url = ENV.fetch('NATS_URL', 'nats://localhost:4222')
6
- config.service_name = ENV.fetch('NATS_SERVICE_NAME',
7
- Rails.application.class.name.deconstantize.underscore)
8
- config.environment = Rails.env
9
-
10
- # Publishing configuration
11
- config.default_subject_prefix = "#{config.service_name}.events"
12
- config.publishing_enabled = true
13
- config.async_publishing = true
14
-
15
- # Subscription configuration
16
- config.subscription_enabled = true
17
- config.queue_group = "#{config.service_name}_consumers"
18
-
19
- # Model mappings (example)
20
- # config.add_model_mapping('ExternalAsset', 'Catalog', {
21
- # 'name' => 'title',
22
- # 'description' => 'body',
23
- # 'hammer_estimate' => 'cost'
24
- # })
25
-
26
- # Subscriptions (example)
27
- config.add_subscription('external.assets.*')
28
- config.add_subscription('catalog.items.*')
29
-
30
- # Custom subscription with handler
31
- # config.add_subscription('webhooks.external.*') do |message|
32
- # WebhookProcessor.process(message)
33
- # end
34
-
35
- # Middleware
36
- config.middleware_logging_enabled = true
37
- config.log_level = Rails.env.production? ? 'info' : 'debug'
38
-
39
- # Authentication (optional)
40
- # config.middleware_authentication_enabled = true
41
- # config.auth_secret_key = ENV['NATS_AUTH_SECRET']
2
+
3
+ begin
4
+ Rails.logger.info "Initializing NATS Wave with URL: #{ENV['NATS_URL']}"
5
+
6
+ NatsWave.configure do |config|
7
+ config.nats_url = ENV.fetch('NATS_URL', 'nats://localhost:4222')
8
+ config.service_name = ENV.fetch('NATS_SERVICE_NAME',
9
+ Rails.application.class.name.deconstantize.underscore)
10
+ config.connection_pool_size = 10
11
+ config.reconnect_attempts = 3
12
+
13
+ Rails.logger.info "NATS URL: #{config.nats_url}"
14
+ Rails.logger.info "Service Name: #{config.service_name}"
15
+
16
+ # Publishing
17
+ config.publishing_enabled = !Rails.env.test?
18
+ config.async_publishing = Rails.env.production?
19
+ config.default_subject_prefix = config.service_name
20
+
21
+ # Subscription
22
+ config.subscription_enabled = !Rails.env.test?
23
+ config.queue_group = "#{config.service_name}_consumers"
24
+
25
+ # Error Handling
26
+ config.max_retries = 3
27
+ config.retry_delay = 5
28
+ config.dead_letter_queue = "failed_messages"
29
+
30
+ # Authentication (optional)
31
+ # config.middleware_authentication_enabled = true
32
+ # config.auth_secret_key = ENV['NATS_AUTH_SECRET']
33
+
34
+ # Validation (optional)
35
+ # config.middleware_validation_enabled = true
36
+ # config.schema_registry_url = ENV['SCHEMA_REGISTRY_URL']
37
+ end
42
38
 
43
- # Validation (optional)
44
- # config.middleware_validation_enabled = true
45
- # config.schema_registry_url = ENV['SCHEMA_REGISTRY_URL']
39
+ Rails.logger.info "NATS Wave configuration completed successfully"
40
+ rescue => e
41
+ Rails.logger.error "Failed to configure NATS Wave: #{e.message}"
42
+ Rails.logger.error e.backtrace.join("\n")
46
43
  end
47
44
 
48
- # Configure metrics backend if available
49
- NatsWave::Metrics.metrics_backend = StatsD if defined?(StatsD)
50
-
51
- # Auto-start subscriber in development
52
- if Rails.env.development?
53
- Rails.application.config.after_initialize do
54
- Thread.new do
55
- sleep 2 # Give Rails time to boot
56
- begin
57
- NatsWave.start_subscriber
58
- Rails.logger.info 'NatsWave subscriber started automatically'
59
- rescue StandardError => e
60
- Rails.logger.error "Failed to start NatsWave subscriber: #{e.message}"
61
- end
45
+ Rails.application.config.after_initialize do
46
+ Thread.new do
47
+ sleep 2 # Give Rails time to boot
48
+ begin
49
+ NatsWave.start_subscriber
50
+ Rails.logger.info "✅ NatsWave subscriber started successfully"
51
+ rescue StandardError => e
52
+ Rails.logger.error "Failed to start NatsWave subscriber: #{e.message}"
53
+ Rails.logger.error e.backtrace.join("\n")
62
54
  end
63
55
  end
64
- end
56
+ end
@@ -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.4'
80
+ version: ENV['APP_VERSION'] || '1.1.5'
81
81
  }
82
82
  end
83
83
 
@@ -37,7 +37,7 @@ module NatsWave
37
37
  @subscriber.subscribe(
38
38
  subjects: subjects,
39
39
  model_mappings: model_mappings,
40
- handler: block
40
+ &block
41
41
  )
42
42
  end
43
43
 
@@ -16,9 +16,9 @@ module NatsWave
16
16
  :subscriptions, :publications
17
17
 
18
18
  def initialize(options = {})
19
- @nats_url = ENV['NATS_URL']
20
- @service_name = ENV['NATS_NATS_SERVICE_NAME']
21
- @version = ENV['NATS_SERVICE_VERSION'] || "1.1.4"
19
+ @nats_url = ENV['NATS_URL'] || "nats://localhost:4222"
20
+ @service_name = ENV['NATS_SERVICE_NAME'] || "purplewave"
21
+ @version = ENV['NATS_SERVICE_VERSION'] || "1.1.5"
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
@@ -80,6 +80,7 @@ module NatsWave
80
80
 
81
81
  def nats_server_url
82
82
  # This is the URL other teams can use to subscribe to your events
83
+ return nil if @nats_url.nil?
83
84
  uri = URI.parse(@nats_url)
84
85
  "#{uri.scheme}://#{uri.host}:#{uri.port}"
85
86
  end
@@ -44,10 +44,9 @@ module NatsWave
44
44
  class Publisher
45
45
  attr_reader :config, :nats_client
46
46
 
47
- def initialize(config, nats_client, middleware_stack)
47
+ def initialize(config, nats_client, middleware_stack = [])
48
48
  @config = config
49
49
  @nats_client = nats_client
50
- @middleware_stack = middleware_stack
51
50
  @message_transformer = MessageTransformer.new(config)
52
51
  @dead_letter_queue = DeadLetterQueue.new(config) if config.dead_letter_queue
53
52
  end
@@ -56,17 +55,15 @@ module NatsWave
56
55
  return unless @config.publishing_enabled
57
56
 
58
57
  message = build_message(subject, model, action, data, metadata)
59
- processed_message = apply_middleware(message)
60
58
  full_subject = build_full_subject(subject)
61
59
 
62
60
  if @config.async_publishing && defined?(Concurrent)
63
- publish_async(full_subject, processed_message)
61
+ publish_async(full_subject, message)
64
62
  else
65
- publish_sync(full_subject, processed_message)
63
+ publish_sync(full_subject, message)
66
64
  end
67
65
 
68
- Metrics.increment_published_messages(full_subject)
69
- NatsWave.logger.debug("Published message to #{full_subject}")
66
+ # Metrics.increment_published_messages(full_subject)
70
67
  rescue => e
71
68
  NatsWave.logger.error("Failed to publish message: #{e.message}")
72
69
  @dead_letter_queue&.store_failed_message(message, e, 0)
@@ -81,12 +78,11 @@ module NatsWave
81
78
  source: build_source_info
82
79
  }
83
80
 
84
- processed_message = apply_middleware(batch_message)
85
81
  subject = "#{@config.default_subject_prefix}.batch"
86
82
 
87
- @nats_client.publish(subject, processed_message.to_json)
83
+ @nats_client.publish(subject, batch_message.to_json)
88
84
 
89
- Metrics.increment_published_messages(subject)
85
+ # Metrics.increment_published_messages(subject)
90
86
  NatsWave.logger.info("Published batch with #{events.size} events")
91
87
  end
92
88
 
@@ -128,20 +124,16 @@ module NatsWave
128
124
  end
129
125
  end
130
126
 
131
- def apply_middleware(message)
132
- @middleware_stack.reduce(message) do |msg, middleware|
133
- middleware.call(msg)
134
- end
135
- end
136
-
137
127
  def publish_sync(subject, message)
138
128
  @nats_client.publish(subject, message.to_json)
129
+ NatsWave.logger.debug("Published sync message to #{subject}")
139
130
  end
140
131
 
141
132
  def publish_async(subject, message)
142
133
  if defined?(Concurrent)
143
134
  Concurrent::Future.execute do
144
135
  @nats_client.publish(subject, message.to_json)
136
+ NatsWave.logger.debug("Published async message to #{subject}")
145
137
  end
146
138
  else
147
139
  publish_sync(subject, message)
@@ -20,7 +20,9 @@ module NatsWave
20
20
  config_file = Rails.root.join('config', 'nats_wave.yml')
21
21
  if File.exist?(config_file)
22
22
  begin
23
- yaml_config = YAML.load_file(config_file)[Rails.env]
23
+ erb_content = ERB.new(File.read(config_file)).result
24
+ yaml_data = YAML.safe_load(erb_content)
25
+ yaml_config = yaml_data&.dig(Rails.env.to_s) || yaml_data&.dig(Rails.env) || {}
24
26
 
25
27
  NatsWave.configure do |config|
26
28
  # NATS Configuration
@@ -95,11 +97,10 @@ module NatsWave
95
97
  Rails.application.config.after_initialize do
96
98
  if NatsWave.configuration&.publishing_enabled || NatsWave.configuration&.subscription_enabled
97
99
  Thread.new do
98
- sleep 1 # Give Rails time to fully boot
100
+ sleep 2 # Give Rails time to fully boot
99
101
  begin
100
102
  NatsWave.client
101
103
  NatsWave.logger.info "NatsWave client initialized"
102
- NatsWave.logger.info "NATS URL #{@nats_url}"
103
104
  rescue => e
104
105
  NatsWave.logger.error "Failed to initialize NatsWave client: #{e.message}"
105
106
  end
@@ -4,15 +4,17 @@ module NatsWave
4
4
  class Subscriber
5
5
  attr_reader :config, :nats_client
6
6
 
7
- def initialize(config, nats_client, middleware_stack)
7
+ def initialize(config, nats_client, middleware_stack = [])
8
8
  @config = config
9
9
  @nats_client = nats_client
10
- @middleware_stack = middleware_stack
11
10
  @database_connector = DatabaseConnector.new(config)
12
11
  @model_mapper = ModelMapper.new(config)
13
12
  @message_transformer = MessageTransformer.new(config)
14
13
  @dead_letter_queue = DeadLetterQueue.new(config)
15
- @subscriptions = []
14
+
15
+ # Separate the two types of subscriptions
16
+ @registry_subscriptions = ModelRegistry.subscriptions # Hash objects
17
+ @nats_subscriptions = [] # NATS::Subscription objects
16
18
  @running = false
17
19
  end
18
20
 
@@ -23,26 +25,29 @@ module NatsWave
23
25
  @running = true
24
26
  NatsWave.logger.info "Starting NATS subscriber for #{@config.service_name}"
25
27
 
26
- @config.subscriptions.each do |subscription|
27
- subscribe_to_subject(subscription[:subject],
28
- subscription[:handler])
28
+ # Use ModelRegistry subscriptions
29
+ @registry_subscriptions.each do |subscription|
30
+ subscription[:subjects].each do |subject|
31
+ subscribe_to_subject(subject, subscription[:handler])
32
+ end
29
33
  end
30
34
 
31
- NatsWave.logger.info "Subscribed to #{@config.subscriptions.size} subjects"
35
+ NatsWave.logger.info "Started #{@registry_subscriptions.size} subscriptions from ModelRegistry"
32
36
  end
33
37
 
34
38
  def subscribe(subjects:, model_mappings: {}, handler: nil)
35
39
  subjects = Array(subjects)
36
40
 
37
41
  subjects.each do |subject|
38
- subscribe_to_subject(subject, handler,
39
- model_mappings)
42
+ subscribe_to_subject(subject, handler, model_mappings)
43
+ NatsWave.logger.info "Subscribed to #{subject}"
40
44
  end
41
45
  end
42
46
 
43
47
  def unsubscribe_all
44
- @subscriptions.each(&:unsubscribe)
45
- @subscriptions.clear
48
+ # Unsubscribe from NATS subscriptions (the actual NATS::Subscription objects)
49
+ @nats_subscriptions.each(&:unsubscribe)
50
+ @nats_subscriptions.clear
46
51
  @running = false
47
52
  end
48
53
 
@@ -57,33 +62,29 @@ module NatsWave
57
62
  private
58
63
 
59
64
  def subscribe_to_subject(subject_pattern, custom_handler = nil, model_mappings = {})
60
- NatsWave.logger.info "Subscribing to: #{subject_pattern}"
65
+ NatsWave.logger.info "🔍 Attempting to subscribe to: #{subject_pattern}"
61
66
 
62
- subscription = @nats_client.subscribe(
67
+ # Create the NATS subscription
68
+ nats_subscription = @nats_client.subscribe(
63
69
  subject_pattern,
64
70
  queue: @config.queue_group
65
71
  ) do |msg|
66
- process_message(msg,
67
- custom_handler, model_mappings)
72
+ NatsWave.logger.info "📨 Received message on #{msg.subject}: #{msg.data}"
73
+ process_message(msg.data, custom_handler, model_mappings)
68
74
  end
69
75
 
70
- @subscriptions << subscription
76
+ # Add to NATS subscriptions array (not the registry subscriptions)
77
+ @nats_subscriptions << nats_subscription
78
+ NatsWave.logger.info "✅ Successfully subscribed to #{subject_pattern} (total: #{@nats_subscriptions.size})"
71
79
  end
72
80
 
73
81
  def process_message(raw_message, custom_handler, model_mappings)
74
- return unless should_process_message?(raw_message)
75
-
76
- Metrics.track_processing_time(raw_message) do
77
- message = parse_message(raw_message)
78
- processed_message = apply_middleware(message)
79
-
80
- if custom_handler
81
- custom_handler.call(processed_message)
82
- else
83
- handle_model_sync(
84
- processed_message, model_mappings
85
- )
86
- end
82
+ message = parse_message(raw_message)
83
+
84
+ if custom_handler
85
+ custom_handler.call(message)
86
+ else
87
+ handle_model_sync(message, model_mappings)
87
88
  end
88
89
 
89
90
  NatsWave.logger.debug('Successfully processed message')
@@ -91,29 +92,10 @@ module NatsWave
91
92
  handle_error(e, raw_message, message)
92
93
  end
93
94
 
94
- def should_process_message?(raw_message)
95
- # Skip messages from same service instance
96
- message = JSON.parse(raw_message)
97
- source = message['source'] || {}
98
-
99
- return false if source['service'] == @config.service_name &&
100
- source['instance_id'] == @config.instance_id
101
-
102
- true
103
- rescue JSON::ParserError
104
- false
105
- end
106
-
107
95
  def parse_message(raw_message)
108
96
  @message_transformer.parse_message(raw_message)
109
97
  end
110
98
 
111
- def apply_middleware(message)
112
- @middleware_stack.reduce(message) do |msg, middleware|
113
- middleware.call(msg)
114
- end
115
- end
116
-
117
99
  def handle_model_sync(message, model_mappings)
118
100
  source_model = message['model']
119
101
  mapping = model_mappings[source_model] || @config.model_mappings[source_model]
@@ -158,4 +140,4 @@ module NatsWave
158
140
  # Continue processing - don't raise to avoid breaking subscription
159
141
  end
160
142
  end
161
- end
143
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module NatsWave
4
- VERSION = '1.1.4'
4
+ VERSION = '1.1.5'
5
5
  end
data/lib/nats_wave.rb CHANGED
@@ -82,8 +82,9 @@ module NatsWave
82
82
  client.subscribe(
83
83
  subjects: subjects,
84
84
  model_mappings: model_mappings,
85
- handler: block
85
+ &block
86
86
  )
87
+ NatsWave.logger.info "Completely subscribed to #{subjects.size} subjects"
87
88
  end
88
89
 
89
90
  def start_subscriber
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nats_wave
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.4
4
+ version: 1.1.5
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-17 00:00:00.000000000 Z
11
+ date: 2025-07-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nats-pure
@@ -318,9 +318,6 @@ files:
318
318
  - README.md
319
319
  - Rakefile
320
320
  - config/nats_wave.yml
321
- - examples/catalog_model.rb
322
- - examples/configuration_examples.rb
323
- - examples/user_model.rb
324
321
  - lib/generators/nats_wave/install_generator.rb
325
322
  - lib/generators/nats_wave/templates/README
326
323
  - lib/generators/nats_wave/templates/create_nats_wave_failed_messages.rb
@@ -1,36 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class Catalog < ApplicationRecord
4
- include NatsWave::NatsPublishable
5
-
6
- nats_publishable(
7
- actions: %i[create update], # Don't publish deletes
8
- skip_attributes: %i[view_count search_vector internal_notes],
9
- include_associations: %i[category variants]
10
- )
11
-
12
- # Custom serializer support
13
- def self.nats_wave_attribute_transformations
14
- {
15
- hammer_estimate: ->(value, _action) { value.to_f.round(2) },
16
- name: :sanitize_name,
17
- description: lambda { |value, _action|
18
- ActionView::Base.full_sanitizer.sanitize(value)
19
- }
20
- }
21
- end
22
-
23
- private
24
-
25
- def sanitize_name(name, _action)
26
- name&.strip&.titleize
27
- end
28
-
29
- def nats_wave_metadata
30
- {
31
- organization: organization&.name,
32
- make:,
33
- catalog_tracked: track_catalog?
34
- }
35
- end
36
- end
@@ -1,68 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Basic configuration
4
- NatsWave.configure do |config|
5
- config.nats_url = 'nats://localhost:4222'
6
- config.service_name = 'ecommerce_api'
7
- config.default_subject_prefix = 'ecommerce.events'
8
- end
9
-
10
- # Advanced configuration with model mappings
11
- NatsWave.configure do |config|
12
- config.nats_url = ENV.fetch('NATS_URL', 'nats://localhost:4222')
13
- config.service_name = 'catalog_service'
14
- config.environment = Rails.env
15
-
16
- # Subscribe to other team's events
17
- config.add_subscription('warehouse.assets.*')
18
- config.add_subscription('external.catalogs.*')
19
- config.add_subscription('payments.transactions.*')
20
-
21
- # Map external models to local models
22
- config.add_model_mapping('WarehouseAsset', 'Catalog', {
23
- 'asset_name' => 'name',
24
- 'asset_description' => 'description',
25
- 'asset_value' => 'hammer_estimate',
26
- 'asset_id' => 'external_icn',
27
- 'model' => 'category'
28
- })
29
-
30
- config.add_model_mapping('ExternalCatalog', 'Catalog', {
31
- 'package_name' => 'name',
32
- 'package_cost' => 'hammer_estimate',
33
- 'external_vin' => 'vin'
34
- })
35
-
36
- # Custom transformation rules
37
- config.transformation_rules = {
38
- 'hammer_estimate' => ->(value) { value.to_f.round(2) },
39
- 'name' => ->(value) { value&.titleize },
40
- 'email' => ->(value) { value&.downcase }
41
- }
42
-
43
- # Custom subscription with handler
44
- config.add_subscription('payments.failed.*') do |message|
45
- PaymentFailureHandler.process(message)
46
- end
47
-
48
- # Middleware configuration
49
- config.middleware_authentication_enabled = Rails.env.production?
50
- config.auth_secret_key = ENV['NATS_AUTH_SECRET']
51
- config.middleware_validation_enabled = true
52
- config.middleware_logging_enabled = true
53
- config.log_level = Rails.env.production? ? 'info' : 'debug'
54
- end
55
-
56
- # Catalogion configuration
57
- if Rails.env.production?
58
- NatsWave.configure do |config|
59
- config.nats_url = ENV.fetch('NATS_CLUSTER_URL')
60
- config.connection_pool_size = 20
61
- config.async_publishing = true
62
- config.middleware_authentication_enabled = true
63
- config.auth_secret_key = ENV.fetch('NATS_AUTH_SECRET')
64
- config.schema_registry_url = ENV['SCHEMA_REGISTRY_URL']
65
- config.max_retries = 5
66
- config.retry_delay = 10
67
- end
68
- end
@@ -1,58 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class User < ApplicationRecord
4
- include NatsWave::NatsPublishable
5
-
6
- # Configure NATS publishing
7
- nats_publishable(
8
- actions: %i[create update destroy],
9
- skip_attributes: %i[password_digest remember_token password_reset_token],
10
- include_associations: %i[profile organization],
11
- subject_prefix: 'users',
12
- metadata: { tenant_id: 'main' },
13
- if: -> { active? },
14
- unless: -> { system_user? }
15
- )
16
-
17
- # Define unique attributes for syncing
18
- def self.nats_wave_unique_attributes
19
- %i[email external_icn username]
20
- end
21
-
22
- # Define attributes that shouldn't be synced from other instances
23
- def self.nats_wave_skip_sync_attributes
24
- %i[last_login_at login_count password_digest]
25
- end
26
-
27
- # Custom attribute transformations
28
- def self.nats_wave_attribute_transformations
29
- {
30
- email: ->(value, _action) { value&.downcase&.strip },
31
- phone: :format_phone_number
32
- }
33
- end
34
-
35
- private
36
-
37
- # Custom metadata for NATS messages
38
- def nats_wave_metadata
39
- {
40
- tenant_id: organization&.tenant_id,
41
- user_type: account_type,
42
- subscription_level: subscription&.level
43
- }
44
- end
45
-
46
- def format_phone_number(phone, _action)
47
- # Custom transformation logic
48
- phone&.gsub(/\D/, '')
49
- end
50
-
51
- def active?
52
- status == 'active'
53
- end
54
-
55
- def system_user?
56
- email&.ends_with?('@system.internal')
57
- end
58
- end