smart_message 0.0.5 → 0.0.7

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.
@@ -40,6 +40,11 @@ class WelcomeMessage < SmartMessage::Base
40
40
  # Add a description for the message class
41
41
  description "Welcomes new users after successful signup"
42
42
 
43
+ # Configure entity addressing
44
+ from 'user-service' # Required: identifies sender
45
+ to 'notification-service' # Optional: specific recipient
46
+ reply_to 'user-service' # Optional: where responses go
47
+
43
48
  # Define message properties
44
49
  property :user_name
45
50
  property :email
@@ -153,6 +158,9 @@ puts message._sm_header.uuid # Unique identifier
153
158
  puts message._sm_header.message_class # "WelcomeMessage"
154
159
  puts message._sm_header.published_at # Timestamp when published
155
160
  puts message._sm_header.publisher_pid # Process ID of publisher
161
+ puts message._sm_header.from # 'user-service'
162
+ puts message._sm_header.to # 'notification-service'
163
+ puts message._sm_header.reply_to # 'user-service'
156
164
  ```
157
165
 
158
166
  ### Transports
@@ -222,6 +230,36 @@ OrderMessage.subscribe("OrderService.process_order")
222
230
  - **Proc**: Reusable handlers that work across multiple message types
223
231
  - **Method**: Complex business logic organized in service classes
224
232
 
233
+ ### Entity Addressing
234
+
235
+ SmartMessage supports entity-to-entity addressing for sophisticated messaging patterns:
236
+
237
+ ```ruby
238
+ # Point-to-point messaging
239
+ class PaymentMessage < SmartMessage::Base
240
+ from 'payment-service' # Required: sender identity
241
+ to 'bank-gateway' # Optional: specific recipient
242
+ reply_to 'payment-service' # Optional: where responses go
243
+
244
+ property :amount, required: true
245
+ end
246
+
247
+ # Broadcast messaging (no 'to' field)
248
+ class AnnouncementMessage < SmartMessage::Base
249
+ from 'admin-service' # Required sender
250
+ # No 'to' = broadcast to all subscribers
251
+
252
+ property :message, required: true
253
+ end
254
+
255
+ # Instance-level addressing override
256
+ payment = PaymentMessage.new(amount: 100.00)
257
+ payment.to('backup-gateway') # Override destination
258
+ payment.publish
259
+ ```
260
+
261
+ For more details, see [Entity Addressing](addressing.md).
262
+
225
263
  ## Next Steps
226
264
 
227
265
  Now that you have the basics working, explore:
@@ -92,7 +92,8 @@ class OrderService
92
92
  customer_id: customer_id,
93
93
  amount: amount,
94
94
  payment_method: payment_method,
95
- items: items
95
+ items: items,
96
+ from: 'OrderService'
96
97
  )
97
98
 
98
99
  puts "🏪 OrderService: Sending order to payment processing..."
@@ -132,7 +133,8 @@ class PaymentService
132
133
  payment_id: payment_id,
133
134
  status: success ? 'success' : 'failed',
134
135
  message: success ? 'Payment processed successfully' : 'Insufficient funds',
135
- processed_at: Time.now.iso8601
136
+ processed_at: Time.now.iso8601,
137
+ from: 'PaymentService'
136
138
  )
137
139
 
138
140
  puts "💳 PaymentService: Sending payment response..."
@@ -270,7 +270,8 @@ class UserManager
270
270
  user_email: user_email,
271
271
  user_name: user_name,
272
272
  timestamp: Time.now.iso8601,
273
- metadata: metadata
273
+ metadata: metadata,
274
+ from: 'UserManager'
274
275
  )
275
276
 
276
277
  puts "👤 UserManager: Publishing #{event_type} event..."
@@ -164,7 +164,8 @@ class HumanChatAgent
164
164
  message_type: 'user',
165
165
  timestamp: Time.now.iso8601,
166
166
  mentions: mentions,
167
- metadata: { client: 'human_agent' }
167
+ metadata: { client: 'human_agent' },
168
+ from: @user_id
168
169
  )
169
170
 
170
171
  message.publish
@@ -228,7 +229,8 @@ class HumanChatAgent
228
229
  user_id: @user_id,
229
230
  command: command,
230
231
  parameters: parameters,
231
- timestamp: Time.now.iso8601
232
+ timestamp: Time.now.iso8601,
233
+ from: @user_id
232
234
  )
233
235
 
234
236
  bot_command.publish
@@ -259,7 +261,8 @@ class HumanChatAgent
259
261
  notification_type: notification_type,
260
262
  content: content,
261
263
  timestamp: Time.now.iso8601,
262
- metadata: { triggered_by: @user_id }
264
+ metadata: { triggered_by: @user_id },
265
+ from: @user_id
263
266
  )
264
267
 
265
268
  notification.publish
@@ -444,7 +447,8 @@ class BotAgent
444
447
  message_type: 'bot',
445
448
  timestamp: Time.now.iso8601,
446
449
  mentions: [],
447
- metadata: { bot_type: 'service_bot' }
450
+ metadata: { bot_type: 'service_bot' },
451
+ from: @bot_id
448
452
  )
449
453
 
450
454
  message.publish
@@ -480,7 +484,8 @@ class RoomManager
480
484
  notification_type: 'room_created',
481
485
  content: "Room '#{name}' was created",
482
486
  timestamp: Time.now.iso8601,
483
- metadata: { description: description }
487
+ metadata: { description: description },
488
+ from: 'RoomManager'
484
489
  )
485
490
 
486
491
  notification.publish
@@ -234,7 +234,7 @@ class SmartThermostat
234
234
  @battery_level -= 0.1 if rand < 0.1 # Occasional battery drain
235
235
 
236
236
  # Publish sensor data
237
- SensorDataMessage.new(
237
+ sensor_msg = SensorDataMessage.new(
238
238
  device_id: @device_id,
239
239
  device_type: 'thermostat',
240
240
  location: @location,
@@ -242,8 +242,11 @@ class SmartThermostat
242
242
  value: @current_temp.round(1),
243
243
  unit: 'celsius',
244
244
  timestamp: Time.now.iso8601,
245
- battery_level: @battery_level.round(1)
246
- ).publish
245
+ battery_level: @battery_level.round(1),
246
+ from: @device_id
247
+ )
248
+ puts "🌡️ #{@device_id}: Publishing temperature #{@current_temp.round(1)}°C"
249
+ sensor_msg.publish
247
250
 
248
251
  # Check for alerts
249
252
  if @current_temp > 28.0
@@ -255,7 +258,8 @@ class SmartThermostat
255
258
  location: @location,
256
259
  message: "Temperature too high: #{@current_temp.round(1)}°C",
257
260
  timestamp: Time.now.iso8601,
258
- requires_action: true
261
+ requires_action: true,
262
+ from: @device_id
259
263
  ).publish
260
264
  end
261
265
 
@@ -325,7 +329,8 @@ class SecurityCamera
325
329
  value: true,
326
330
  unit: 'boolean',
327
331
  timestamp: Time.now.iso8601,
328
- battery_level: @battery_level.round(1)
332
+ battery_level: @battery_level.round(1),
333
+ from: @device_id
329
334
  ).publish
330
335
 
331
336
  # Send security alert
@@ -337,7 +342,8 @@ class SecurityCamera
337
342
  location: @location,
338
343
  message: "Motion detected in #{@location}",
339
344
  timestamp: Time.now.iso8601,
340
- requires_action: false
345
+ requires_action: false,
346
+ from: @device_id
341
347
  ).publish
342
348
 
343
349
  # Auto-start recording
@@ -346,7 +352,8 @@ class SecurityCamera
346
352
  command: 'start_recording',
347
353
  parameters: { duration: 30 },
348
354
  requested_by: 'motion_detector',
349
- timestamp: Time.now.iso8601
355
+ timestamp: Time.now.iso8601,
356
+ from: @device_id
350
357
  ).publish
351
358
 
352
359
  elsif !motion_detected && @motion_detected
@@ -361,7 +368,8 @@ class SecurityCamera
361
368
  value: false,
362
369
  unit: 'boolean',
363
370
  timestamp: Time.now.iso8601,
364
- battery_level: @battery_level.round(1)
371
+ battery_level: @battery_level.round(1),
372
+ from: @device_id
365
373
  ).publish
366
374
  end
367
375
 
@@ -424,7 +432,8 @@ class SmartDoorLock
424
432
  value: @locked ? 'locked' : 'unlocked',
425
433
  unit: 'string',
426
434
  timestamp: Time.now.iso8601,
427
- battery_level: @battery_level.round(1)
435
+ battery_level: @battery_level.round(1),
436
+ from: @device_id
428
437
  ).publish
429
438
 
430
439
  # Check for low battery
@@ -437,7 +446,8 @@ class SmartDoorLock
437
446
  location: @location,
438
447
  message: "Door lock battery low: #{@battery_level.round(1)}%",
439
448
  timestamp: Time.now.iso8601,
440
- requires_action: true
449
+ requires_action: true,
450
+ from: @device_id
441
451
  ).publish
442
452
  end
443
453
 
@@ -528,7 +538,8 @@ class IoTDashboard
528
538
  summary_stats: {
529
539
  total_devices: @@devices.size,
530
540
  avg_battery: @@devices.values.map { |d| d[:battery_level] || 100 }.sum / [@@devices.size, 1].max
531
- }
541
+ },
542
+ from: 'IoTDashboard'
532
543
  ).publish
533
544
  end
534
545
  end
@@ -592,13 +603,16 @@ class SmartHomeDemo
592
603
  puts "\n🎛️ Sending some manual commands..."
593
604
  sleep(2)
594
605
 
595
- DeviceCommandMessage.new(
606
+ cmd_msg = DeviceCommandMessage.new(
596
607
  device_id: "THERM-001",
597
608
  command: "set_temperature",
598
609
  parameters: { target: 24.0 },
599
610
  requested_by: "mobile_app",
600
- timestamp: Time.now.iso8601
601
- ).publish
611
+ timestamp: Time.now.iso8601,
612
+ from: 'SmartHomeDemo'
613
+ )
614
+ puts "🎛️ Publishing command: set_temperature for THERM-001"
615
+ cmd_msg.publish
602
616
 
603
617
  sleep(3)
604
618
 
@@ -607,7 +621,8 @@ class SmartHomeDemo
607
621
  command: "start_recording",
608
622
  parameters: { duration: 60 },
609
623
  requested_by: "security_schedule",
610
- timestamp: Time.now.iso8601
624
+ timestamp: Time.now.iso8601,
625
+ from: 'SmartHomeDemo'
611
626
  ).publish
612
627
 
613
628
  sleep(2)
@@ -617,7 +632,8 @@ class SmartHomeDemo
617
632
  command: "get_status",
618
633
  parameters: {},
619
634
  requested_by: "mobile_app",
620
- timestamp: Time.now.iso8601
635
+ timestamp: Time.now.iso8601,
636
+ from: 'SmartHomeDemo'
621
637
  ).publish
622
638
 
623
639
  puts "\n⏳ Monitoring for 30 seconds (watch the Redis channels!)..."
@@ -137,7 +137,7 @@ notifications = [
137
137
  notifications.each_with_index do |notification_data, index|
138
138
  puts "\n📤 Publishing notification #{index + 1}: #{notification_data[:title]}"
139
139
 
140
- notification = NotificationMessage.new(**notification_data)
140
+ notification = NotificationMessage.new(**notification_data, from: 'ProcHandlerDemo')
141
141
  notification.publish
142
142
 
143
143
  # Give time for all handlers to process
@@ -161,7 +161,8 @@ error_notification = NotificationMessage.new(
161
161
  title: 'Another Error',
162
162
  message: 'This error won\'t trigger the block handler',
163
163
  user_id: 'test_user',
164
- timestamp: Time.now.iso8601
164
+ timestamp: Time.now.iso8601,
165
+ from: 'ProcHandlerDemo'
165
166
  )
166
167
 
167
168
  error_notification.publish
@@ -470,7 +470,7 @@ class PriorityOrderService
470
470
 
471
471
  def process_priority_order(order_data)
472
472
  # Create message with instance-level logger override
473
- message = OrderProcessingMessage.new(**order_data)
473
+ message = OrderProcessingMessage.new(**order_data, from: 'PriorityOrderService')
474
474
 
475
475
  # Override the logger at instance level
476
476
  message.logger(@priority_logger)
@@ -506,7 +506,8 @@ class LoggerDemo
506
506
  DefaultLoggerMessage.subscribe
507
507
  default_msg = DefaultLoggerMessage.new(
508
508
  message: "Testing the built-in default logger",
509
- level: "info"
509
+ level: "info",
510
+ from: 'LoggerDemo'
510
511
  )
511
512
  default_msg.publish
512
513
  sleep(0.5)
@@ -518,7 +519,8 @@ class LoggerDemo
518
519
  customer_id: "CUST-123",
519
520
  amount: 99.99,
520
521
  status: "pending",
521
- items: ["Widget A", "Widget B"]
522
+ items: ["Widget A", "Widget B"],
523
+ from: 'LoggerDemo'
522
524
  )
523
525
  order1.publish
524
526
  sleep(0.5)
@@ -529,7 +531,8 @@ class LoggerDemo
529
531
  recipient: "customer@example.com",
530
532
  subject: "Order Confirmation",
531
533
  body: "Your order has been received",
532
- priority: "normal"
534
+ priority: "normal",
535
+ from: 'LoggerDemo'
533
536
  )
534
537
  notification.publish
535
538
  sleep(0.5)
@@ -542,7 +545,8 @@ class LoggerDemo
542
545
  customer_id: "VIP-456",
543
546
  amount: 299.99,
544
547
  status: "urgent",
545
- items: ["Premium Widget", "Express Shipping"]
548
+ items: ["Premium Widget", "Express Shipping"],
549
+ from: 'PriorityOrderService'
546
550
  )
547
551
  sleep(0.5)
548
552
 
@@ -555,7 +559,8 @@ class LoggerDemo
555
559
  # Create and send a message - watch for the Ruby logger output
556
560
  msg = StandardLoggerMessage.new(
557
561
  content: "Testing with Ruby's standard logger",
558
- level: "info"
562
+ level: "info",
563
+ from: 'LoggerDemo'
559
564
  )
560
565
 
561
566
  # The logger will output to STDOUT using Ruby's standard format
@@ -573,7 +578,8 @@ class LoggerDemo
573
578
  order_id: nil, # This might cause issues
574
579
  customer_id: "ERROR-TEST",
575
580
  amount: "invalid_amount",
576
- status: "error_demo"
581
+ status: "error_demo",
582
+ from: 'LoggerDemo'
577
583
  )
578
584
 
579
585
  # Simulate an error during processing
@@ -167,7 +167,8 @@ class ErrorDemonstrator
167
167
  missing_user_id = UserRegistrationMessage.new(
168
168
  email: "john@example.com",
169
169
  age: 25,
170
- username: "johndoe"
170
+ username: "johndoe",
171
+ from: 'ErrorDemonstrator'
171
172
  )
172
173
  puts "❌ ERROR: Should have failed but didn't!"
173
174
  rescue => e
@@ -181,7 +182,8 @@ class ErrorDemonstrator
181
182
  begin
182
183
  missing_multiple = UserRegistrationMessage.new(
183
184
  user_id: "USER-123",
184
- username: "johndoe"
185
+ username: "johndoe",
186
+ from: 'ErrorDemonstrator'
185
187
  )
186
188
  puts "❌ ERROR: Should have failed but didn't!"
187
189
  rescue => e
@@ -197,7 +199,8 @@ class ErrorDemonstrator
197
199
  user_id: "USER-123",
198
200
  email: "john@example.com",
199
201
  age: 25,
200
- username: "johndoe"
202
+ username: "johndoe",
203
+ from: 'ErrorDemonstrator'
201
204
  )
202
205
  puts "✅ Message created successfully"
203
206
  puts " User: #{valid_user.username} (#{valid_user.email})"
@@ -227,7 +230,8 @@ class ErrorDemonstrator
227
230
  user_id: "USER-124",
228
231
  email: "not-an-email", # Invalid format
229
232
  age: 25,
230
- username: "janedoe"
233
+ username: "janedoe",
234
+ from: 'ErrorDemonstrator'
231
235
  )
232
236
  puts " Message created, now validating..."
233
237
  invalid_email.validate! # Explicitly trigger validation
@@ -245,7 +249,8 @@ class ErrorDemonstrator
245
249
  user_id: "USER-125",
246
250
  email: "kid@example.com",
247
251
  age: 10, # Too young (< 13)
248
- username: "kiduser"
252
+ username: "kiduser",
253
+ from: 'ErrorDemonstrator'
249
254
  )
250
255
  puts " Message created, now validating..."
251
256
  invalid_age.validate! # Explicitly trigger validation
@@ -263,7 +268,8 @@ class ErrorDemonstrator
263
268
  user_id: "USER-126",
264
269
  email: "user@example.com",
265
270
  age: 30,
266
- username: "user@123!" # Contains invalid characters
271
+ username: "user@123!", # Contains invalid characters
272
+ from: 'ErrorDemonstrator'
267
273
  )
268
274
  puts " Message created, now validating..."
269
275
  invalid_username.validate! # Explicitly trigger validation
@@ -282,7 +288,8 @@ class ErrorDemonstrator
282
288
  email: "user@example.com",
283
289
  age: 30,
284
290
  username: "validuser",
285
- subscription_type: "platinum" # Not in allowed list
291
+ subscription_type: "platinum", # Not in allowed list
292
+ from: 'ErrorDemonstrator'
286
293
  )
287
294
  puts " Message created, now validating..."
288
295
  invalid_subscription.validate! # Explicitly trigger validation
@@ -301,7 +308,8 @@ class ErrorDemonstrator
301
308
  email: "valid@example.com",
302
309
  age: 25,
303
310
  username: "validuser123",
304
- subscription_type: "premium"
311
+ subscription_type: "premium",
312
+ from: 'ErrorDemonstrator'
305
313
  )
306
314
  puts " Message created, now validating..."
307
315
  valid_user.validate! # Explicitly trigger validation
@@ -339,7 +347,8 @@ class ErrorDemonstrator
339
347
  user_id: "USER-V1-001",
340
348
  email: "v1user@example.com",
341
349
  age: 28,
342
- username: "v1user"
350
+ username: "v1user",
351
+ from: 'ErrorDemonstrator'
343
352
  )
344
353
  puts "✅ V1 Message created (version #{v1_message._sm_header.version})"
345
354
  v1_message.publish
@@ -356,7 +365,8 @@ class ErrorDemonstrator
356
365
  email: "v2user@example.com",
357
366
  age: 32,
358
367
  username: "v2user",
359
- phone_number: "+1234567890"
368
+ phone_number: "+1234567890",
369
+ from: 'ErrorDemonstrator'
360
370
  )
361
371
  puts "✅ V2 Message created (version #{v2_message._sm_header.version})"
362
372
  v2_message.publish
@@ -374,7 +384,8 @@ class ErrorDemonstrator
374
384
  user_id: "USER-MISMATCH-001",
375
385
  email: "mismatch@example.com",
376
386
  age: 35,
377
- username: "mismatchuser"
387
+ username: "mismatchuser",
388
+ from: 'ErrorDemonstrator'
378
389
  )
379
390
 
380
391
  puts "✅ Original message created with version #{version_mismatch_message._sm_header.version}"
@@ -396,8 +407,8 @@ class ErrorDemonstrator
396
407
 
397
408
  # Test Case 4: Show version information
398
409
  puts "📊 Test Case 3D: Version information display"
399
- v1_msg = UserRegistrationMessage.new(user_id: "INFO-V1", email: "info@example.com", age: 25, username: "infouser")
400
- v2_msg = UserRegistrationMessageV2.new(user_id: "INFO-V2", email: "info@example.com", age: 25, username: "infouser", phone_number: "+1234567890")
410
+ v1_msg = UserRegistrationMessage.new(user_id: "INFO-V1", email: "info@example.com", age: 25, username: "infouser", from: 'ErrorDemonstrator')
411
+ v2_msg = UserRegistrationMessageV2.new(user_id: "INFO-V2", email: "info@example.com", age: 25, username: "infouser", phone_number: "+1234567890", from: 'ErrorDemonstrator')
401
412
 
402
413
  puts "📋 Version Information:"
403
414
  puts " UserRegistrationMessage (V1):"
@@ -424,7 +435,7 @@ class ErrorDemonstrator
424
435
  puts "Expected: Ideally should report all missing fields"
425
436
  puts "Actual Hashie::Dash behavior:"
426
437
  begin
427
- message = MultiRequiredMessage.new(optional_field: "present")
438
+ message = MultiRequiredMessage.new(optional_field: "present", from: 'ErrorDemonstrator')
428
439
  puts "❌ ERROR: Should have failed but didn't!"
429
440
  rescue => e
430
441
  puts "✅ Error caught: #{e.class.name}"
@@ -443,7 +454,7 @@ class ErrorDemonstrator
443
454
  test_data.each_with_index do |test_case, index|
444
455
  puts " #{index + 1}. #{test_case[:name]}:"
445
456
  begin
446
- message = MultiRequiredMessage.new(test_case[:data])
457
+ message = MultiRequiredMessage.new(test_case[:data].merge(from: 'ErrorDemonstrator'))
447
458
  puts " ✅ Success: Message created"
448
459
  rescue => e
449
460
  field_name = e.message.match(/property '([^']+)'/)[1] rescue 'unknown'