smart_message 0.0.2 → 0.0.4
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 +4 -4
- data/CHANGELOG.md +31 -1
- data/Gemfile.lock +6 -1
- data/README.md +92 -14
- data/docs/README.md +1 -0
- data/docs/architecture.md +41 -8
- data/docs/dispatcher.md +52 -16
- data/docs/getting-started.md +64 -2
- data/docs/logging.md +452 -0
- data/docs/message_processing.md +423 -0
- data/docs/proc_handlers_summary.md +247 -0
- data/docs/transports.md +202 -8
- data/examples/.gitignore +2 -0
- data/examples/04_redis_smart_home_iot.rb +649 -0
- data/examples/05_proc_handlers.rb +181 -0
- data/examples/06_custom_logger_example.rb +620 -0
- data/examples/README.md +118 -3
- data/examples/smart_home_iot_dataflow.md +257 -0
- data/lib/smart_message/base.rb +94 -4
- data/lib/smart_message/dispatcher.rb +22 -6
- data/lib/smart_message/logger/default.rb +217 -0
- data/lib/smart_message/logger.rb +9 -1
- data/lib/smart_message/transport/redis_transport.rb +190 -0
- data/lib/smart_message/transport/registry.rb +1 -0
- data/lib/smart_message/transport.rb +1 -0
- data/lib/smart_message/version.rb +1 -1
- data/smart_message.gemspec +1 -0
- metadata +25 -1
data/examples/README.md
CHANGED
@@ -11,6 +11,8 @@ cd examples
|
|
11
11
|
ruby 01_point_to_point_orders.rb
|
12
12
|
ruby 02_publish_subscribe_events.rb
|
13
13
|
ruby 03_many_to_many_chat.rb
|
14
|
+
ruby 04_redis_smart_home_iot.rb
|
15
|
+
ruby 05_proc_handlers.rb
|
14
16
|
```
|
15
17
|
|
16
18
|
## Examples Overview
|
@@ -97,6 +99,105 @@ ruby 03_many_to_many_chat.rb
|
|
97
99
|
- Service capabilities and discovery
|
98
100
|
- Complex subscription management
|
99
101
|
|
102
|
+
---
|
103
|
+
|
104
|
+
### 4. Redis Transport IoT Example (Production Messaging)
|
105
|
+
**File:** `04_redis_smart_home_iot.rb`
|
106
|
+
|
107
|
+
**Scenario:** Smart home IoT dashboard with multiple device types communicating through Redis pub/sub channels, demonstrating production-ready messaging patterns.
|
108
|
+
|
109
|
+
**Key Features:**
|
110
|
+
- Real Redis pub/sub transport (falls back to memory if Redis unavailable)
|
111
|
+
- Multiple device types with realistic sensor data
|
112
|
+
- Automatic Redis channel routing using message class names
|
113
|
+
- Real-time monitoring and alerting system
|
114
|
+
- Production-ready error handling and reconnection
|
115
|
+
|
116
|
+
**Messages Used:**
|
117
|
+
- `SensorDataMessage` - IoT device sensor readings and status
|
118
|
+
- `DeviceCommandMessage` - Commands sent to control devices
|
119
|
+
- `AlertMessage` - Critical notifications and warnings
|
120
|
+
- `DashboardStatusMessage` - System-wide status updates
|
121
|
+
|
122
|
+
**Services:**
|
123
|
+
- `SmartThermostat` - Temperature monitoring and control
|
124
|
+
- `SecurityCamera` - Motion detection and recording
|
125
|
+
- `SmartDoorLock` - Access control and status monitoring
|
126
|
+
- `IoTDashboard` - Centralized monitoring and status aggregation
|
127
|
+
|
128
|
+
**What You'll Learn:**
|
129
|
+
- Production Redis transport configuration and usage
|
130
|
+
- Automatic Redis channel routing (each message type → separate channel)
|
131
|
+
- IoT device simulation and real-time data streaming
|
132
|
+
- Event-driven alert systems
|
133
|
+
- Scalable pub/sub architecture for distributed systems
|
134
|
+
- Error handling and graceful fallbacks
|
135
|
+
|
136
|
+
**Redis Channels Created:**
|
137
|
+
- `SensorDataMessage` - Device sensor readings
|
138
|
+
- `DeviceCommandMessage` - Device control commands
|
139
|
+
- `AlertMessage` - System alerts and notifications
|
140
|
+
- `DashboardStatusMessage` - Dashboard status updates
|
141
|
+
|
142
|
+
---
|
143
|
+
|
144
|
+
### 5. Proc and Block Handler Example (Flexible Message Processing)
|
145
|
+
**File:** `05_proc_handlers.rb`
|
146
|
+
|
147
|
+
**Scenario:** Notification system demonstrating all available message handler types in SmartMessage, showcasing the flexibility of the new proc and block subscription patterns.
|
148
|
+
|
149
|
+
**Key Features:**
|
150
|
+
- Multiple handler types in a single application
|
151
|
+
- Default method handlers alongside new proc/block handlers
|
152
|
+
- Dynamic handler management (subscription and unsubscription)
|
153
|
+
- Practical comparison of different handler approaches
|
154
|
+
|
155
|
+
**Messages Used:**
|
156
|
+
- `NotificationMessage` - System notifications with type, title, message, and user info
|
157
|
+
|
158
|
+
**Handler Types Demonstrated:**
|
159
|
+
- `Default Handler` - Traditional `self.process` method
|
160
|
+
- `Block Handler` - Inline logic using `subscribe do |h,p|...end`
|
161
|
+
- `Proc Handler` - Reusable proc objects for cross-cutting concerns
|
162
|
+
- `Lambda Handler` - Strict parameter validation with functional style
|
163
|
+
- `Method Handler` - Organized service class methods
|
164
|
+
|
165
|
+
**What You'll Learn:**
|
166
|
+
- How to choose the right handler type for different use cases
|
167
|
+
- Block handlers for simple, subscription-specific logic
|
168
|
+
- Proc handlers for reusable cross-message functionality
|
169
|
+
- Lambda handlers for strict functional programming patterns
|
170
|
+
- Handler lifecycle management and cleanup
|
171
|
+
- Performance characteristics of different handler types
|
172
|
+
|
173
|
+
**Handler Patterns Shown:**
|
174
|
+
```ruby
|
175
|
+
# Default handler
|
176
|
+
class NotificationMessage < SmartMessage::Base
|
177
|
+
def self.process(header, payload)
|
178
|
+
# Built-in processing
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
# Block handler - inline logic
|
183
|
+
NotificationMessage.subscribe do |header, payload|
|
184
|
+
# Simple, specific processing
|
185
|
+
end
|
186
|
+
|
187
|
+
# Proc handler - reusable across message types
|
188
|
+
audit_logger = proc { |header, payload| log_audit(payload) }
|
189
|
+
NotificationMessage.subscribe(audit_logger)
|
190
|
+
|
191
|
+
# Method handler - organized service logic
|
192
|
+
NotificationMessage.subscribe("NotificationService.handle")
|
193
|
+
```
|
194
|
+
|
195
|
+
**Benefits Demonstrated:**
|
196
|
+
- **Flexibility**: Multiple ways to handle the same message type
|
197
|
+
- **Reusability**: Proc handlers can be shared across message classes
|
198
|
+
- **Maintainability**: Choose the right abstraction level for each need
|
199
|
+
- **Performance**: Understand overhead of different handler approaches
|
200
|
+
|
100
201
|
## Message Patterns Demonstrated
|
101
202
|
|
102
203
|
### Request-Response Pattern
|
@@ -129,7 +230,7 @@ alice.leave_room('general') # Stop receiving messages
|
|
129
230
|
|
130
231
|
## Transport Configurations
|
131
232
|
|
132
|
-
|
233
|
+
Most examples use `StdoutTransport` with loopback enabled for demonstration purposes:
|
133
234
|
|
134
235
|
```ruby
|
135
236
|
config do
|
@@ -138,8 +239,21 @@ config do
|
|
138
239
|
end
|
139
240
|
```
|
140
241
|
|
242
|
+
**Exception:** The IoT example (`04_redis_smart_home_iot.rb`) uses real Redis transport:
|
243
|
+
|
244
|
+
```ruby
|
245
|
+
config do
|
246
|
+
transport SmartMessage::Transport.create(:redis,
|
247
|
+
url: 'redis://localhost:6379',
|
248
|
+
db: 1,
|
249
|
+
auto_subscribe: true
|
250
|
+
)
|
251
|
+
serializer SmartMessage::Serializer::JSON.new
|
252
|
+
end
|
253
|
+
```
|
254
|
+
|
141
255
|
**For Production Use:**
|
142
|
-
-
|
256
|
+
- Use production transports like Redis (see example #4), RabbitMQ, or Kafka
|
143
257
|
- Configure appropriate serializers for your data needs
|
144
258
|
- Add proper error handling and logging
|
145
259
|
- Implement monitoring and metrics
|
@@ -242,6 +356,7 @@ Each example includes:
|
|
242
356
|
ruby examples/01_point_to_point_orders.rb
|
243
357
|
ruby examples/02_publish_subscribe_events.rb
|
244
358
|
ruby examples/03_many_to_many_chat.rb
|
359
|
+
ruby examples/04_redis_smart_home_iot.rb # Requires Redis server
|
245
360
|
```
|
246
361
|
|
247
362
|
3. **Expected output:**
|
@@ -293,7 +408,7 @@ end
|
|
293
408
|
When adapting these examples for production:
|
294
409
|
|
295
410
|
1. **Transport Selection:**
|
296
|
-
- Use production message brokers (Redis, RabbitMQ, Kafka)
|
411
|
+
- Use production message brokers (Redis is built-in - see example #4, RabbitMQ, Kafka)
|
297
412
|
- Configure connection pooling and failover
|
298
413
|
- Implement proper error handling
|
299
414
|
|
@@ -0,0 +1,257 @@
|
|
1
|
+
# Smart Home IoT Data Flow - Redis Pub/Sub Transport
|
2
|
+
|
3
|
+
This document describes the data flow architecture for the Smart Home IoT example using SmartMessage's Redis transport. The system demonstrates how multiple IoT devices communicate through Redis pub/sub channels with targeted message routing.
|
4
|
+
|
5
|
+
## System Architecture Overview
|
6
|
+
|
7
|
+
The smart home system consists of three types of IoT devices and a central dashboard, all communicating through Redis pub/sub channels. Each message type uses its own Redis channel for efficient routing and scaling.
|
8
|
+
|
9
|
+
```mermaid
|
10
|
+
graph TB
|
11
|
+
%% IoT Devices
|
12
|
+
THERM["🌡️ Smart Thermostat<br/>THERM-001<br/>Living Room"]
|
13
|
+
CAM["📹 Security Camera<br/>CAM-001<br/>Front Door"]
|
14
|
+
LOCK["🚪 Smart Door Lock<br/>LOCK-001<br/>Main Entrance"]
|
15
|
+
DASH["📊 IoT Dashboard<br/>System Monitor"]
|
16
|
+
|
17
|
+
%% Redis Channels
|
18
|
+
subgraph REDIS ["Redis Pub/Sub Channels"]
|
19
|
+
SENSOR["SensorDataMessage<br/>Temperature, Motion, Status<br/>Battery Levels"]
|
20
|
+
COMMAND["DeviceCommandMessage<br/>set_temperature, start_recording<br/>lock/unlock, get_status"]
|
21
|
+
ALERT["AlertMessage<br/>Motion Detected, Battery Low<br/>High Temperature"]
|
22
|
+
STATUS["DashboardStatusMessage<br/>System Status, Device Counts<br/>Alert Summaries"]
|
23
|
+
end
|
24
|
+
|
25
|
+
%% Device Publishing
|
26
|
+
THERM -.->|"Temperature Data"| SENSOR
|
27
|
+
CAM -.->|"Motion Data"| SENSOR
|
28
|
+
LOCK -.->|"Lock Status"| SENSOR
|
29
|
+
CAM -.->|"Motion Alerts"| ALERT
|
30
|
+
THERM -.->|"High Temp Alerts"| ALERT
|
31
|
+
LOCK -.->|"Battery Low Alerts"| ALERT
|
32
|
+
DASH -.->|"System Status"| STATUS
|
33
|
+
DASH -.->|"Device Commands"| COMMAND
|
34
|
+
|
35
|
+
%% Device Subscribing
|
36
|
+
COMMAND -->|"set_temperature"| THERM
|
37
|
+
COMMAND -->|"start/stop recording"| CAM
|
38
|
+
COMMAND -->|"lock/unlock"| LOCK
|
39
|
+
SENSOR -->|"All sensor data"| DASH
|
40
|
+
COMMAND -->|"Command logging"| DASH
|
41
|
+
ALERT -->|"All alerts"| DASH
|
42
|
+
STATUS -->|"Status updates"| DASH
|
43
|
+
|
44
|
+
%% Styling
|
45
|
+
classDef deviceStyle fill:#ff6b6b,stroke:#c0392b,stroke-width:2px,color:#fff
|
46
|
+
classDef cameraStyle fill:#4ecdc4,stroke:#16a085,stroke-width:2px,color:#fff
|
47
|
+
classDef lockStyle fill:#ffe66d,stroke:#f39c12,stroke-width:2px,color:#333
|
48
|
+
classDef dashStyle fill:#a8e6cf,stroke:#27ae60,stroke-width:2px,color:#333
|
49
|
+
classDef redisStyle fill:#dc143c,stroke:#a00,stroke-width:2px,color:#fff
|
50
|
+
|
51
|
+
class THERM deviceStyle
|
52
|
+
class CAM cameraStyle
|
53
|
+
class LOCK lockStyle
|
54
|
+
class DASH dashStyle
|
55
|
+
class SENSOR,COMMAND,ALERT,STATUS redisStyle
|
56
|
+
```
|
57
|
+
|
58
|
+
## Message Flow Details
|
59
|
+
|
60
|
+
### 1. Sensor Data Flow
|
61
|
+
All IoT devices continuously publish sensor readings to the `SensorDataMessage` Redis channel:
|
62
|
+
|
63
|
+
- **🌡️ Thermostat**: Temperature readings, battery level
|
64
|
+
- **📹 Camera**: Motion detection status, battery level
|
65
|
+
- **🚪 Door Lock**: Lock/unlock status, battery level
|
66
|
+
|
67
|
+
**Example SensorDataMessage:**
|
68
|
+
```json
|
69
|
+
{
|
70
|
+
"device_id": "THERM-001",
|
71
|
+
"device_type": "thermostat",
|
72
|
+
"location": "living_room",
|
73
|
+
"sensor_type": "temperature",
|
74
|
+
"value": 22.5,
|
75
|
+
"unit": "celsius",
|
76
|
+
"timestamp": "2025-08-18T10:30:00Z",
|
77
|
+
"battery_level": 85.2
|
78
|
+
}
|
79
|
+
```
|
80
|
+
|
81
|
+
### 2. Device Command Flow
|
82
|
+
The dashboard and external systems send commands to specific devices via the `DeviceCommandMessage` channel:
|
83
|
+
|
84
|
+
```mermaid
|
85
|
+
sequenceDiagram
|
86
|
+
participant App as Mobile App
|
87
|
+
participant Redis as Redis Channel
|
88
|
+
participant Therm as Thermostat
|
89
|
+
participant Cam as Camera
|
90
|
+
participant Lock as Door Lock
|
91
|
+
|
92
|
+
App->>Redis: DeviceCommandMessage<br/>device_id THERM-001 set_temperature
|
93
|
+
Redis->>Therm: ✅ Processes THERM prefix match
|
94
|
+
Redis->>Cam: ❌ Ignores not CAM prefix
|
95
|
+
Redis->>Lock: ❌ Ignores not LOCK prefix
|
96
|
+
|
97
|
+
App->>Redis: DeviceCommandMessage<br/>device_id CAM-001 start_recording
|
98
|
+
Redis->>Therm: ❌ Ignores not THERM prefix
|
99
|
+
Redis->>Cam: ✅ Processes CAM prefix match
|
100
|
+
Redis->>Lock: ❌ Ignores not LOCK prefix
|
101
|
+
```
|
102
|
+
|
103
|
+
**Device Command Filtering Rules:**
|
104
|
+
- **THERM-*** devices: Accept `set_temperature`, `get_status`
|
105
|
+
- **CAM-*** devices: Accept `start_recording`, `stop_recording`, `get_status`
|
106
|
+
- **LOCK-*** devices: Accept `lock`, `unlock`, `get_status`
|
107
|
+
|
108
|
+
### 3. Alert System Flow
|
109
|
+
Devices publish critical notifications to the `AlertMessage` channel when conditions are detected:
|
110
|
+
|
111
|
+
```mermaid
|
112
|
+
flowchart LR
|
113
|
+
subgraph Triggers
|
114
|
+
T1[High Temperature > 28°C]
|
115
|
+
T2[Motion Detected]
|
116
|
+
T3[Battery < 20%]
|
117
|
+
T4[Device Offline > 30s]
|
118
|
+
end
|
119
|
+
|
120
|
+
subgraph Devices
|
121
|
+
THERM2[🌡️ Thermostat]
|
122
|
+
CAM2[📹 Camera]
|
123
|
+
LOCK2[🚪 Door Lock]
|
124
|
+
end
|
125
|
+
|
126
|
+
subgraph AlertChannel [AlertMessage Channel]
|
127
|
+
A1[Motion Alert]
|
128
|
+
A2[High Temp Alert]
|
129
|
+
A3[Battery Low Alert]
|
130
|
+
A4[Device Offline Alert]
|
131
|
+
end
|
132
|
+
|
133
|
+
T1 --> THERM2 --> A2
|
134
|
+
T2 --> CAM2 --> A1
|
135
|
+
T3 --> LOCK2 --> A3
|
136
|
+
T4 --> A4
|
137
|
+
|
138
|
+
AlertChannel --> DASH2[📊 Dashboard]
|
139
|
+
```
|
140
|
+
|
141
|
+
### 4. Dashboard Status Flow
|
142
|
+
The dashboard aggregates all system data and publishes periodic status updates:
|
143
|
+
|
144
|
+
```mermaid
|
145
|
+
graph LR
|
146
|
+
subgraph DataCollection [Data Collection]
|
147
|
+
D1[Device Last Seen Times]
|
148
|
+
D2[Alert Counts]
|
149
|
+
D3[Battery Levels]
|
150
|
+
D4[System Health]
|
151
|
+
end
|
152
|
+
|
153
|
+
subgraph Processing [Status Processing]
|
154
|
+
P1[Count Active Devices<br/>last_seen < 30s]
|
155
|
+
P2[Count Recent Alerts<br/>last 5 minutes]
|
156
|
+
P3[Calculate Averages]
|
157
|
+
end
|
158
|
+
|
159
|
+
subgraph Output [Status Output]
|
160
|
+
O1[DashboardStatusMessage<br/>Every 10 seconds]
|
161
|
+
end
|
162
|
+
|
163
|
+
DataCollection --> Processing --> Output
|
164
|
+
```
|
165
|
+
|
166
|
+
## Channel-Based Architecture Benefits
|
167
|
+
|
168
|
+
### 1. **Efficient Message Routing**
|
169
|
+
Each message type uses its own Redis channel, preventing unnecessary message processing:
|
170
|
+
|
171
|
+
| Channel | Publishers | Subscribers | Purpose |
|
172
|
+
|---------|------------|-------------|---------|
|
173
|
+
| `SensorDataMessage` | All Devices | Dashboard | Real-time sensor readings |
|
174
|
+
| `DeviceCommandMessage` | Dashboard, Apps | All Devices | Device control commands |
|
175
|
+
| `AlertMessage` | All Devices | Dashboard | Critical notifications |
|
176
|
+
| `DashboardStatusMessage` | Dashboard | Dashboard, Apps | System status updates |
|
177
|
+
|
178
|
+
### 2. **Device-Specific Command Filtering**
|
179
|
+
Devices use prefix-based filtering to process only relevant commands:
|
180
|
+
|
181
|
+
```ruby
|
182
|
+
# Example: Thermostat command filtering
|
183
|
+
def self.handle_command(message_header, message_payload)
|
184
|
+
command_data = JSON.parse(message_payload)
|
185
|
+
|
186
|
+
# Only process commands for thermostats
|
187
|
+
return unless command_data['device_id']&.start_with?('THERM-')
|
188
|
+
return unless ['set_temperature', 'get_status'].include?(command_data['command'])
|
189
|
+
|
190
|
+
# Process the command...
|
191
|
+
end
|
192
|
+
```
|
193
|
+
|
194
|
+
### 3. **Scalable Pub/Sub Pattern**
|
195
|
+
The architecture supports easy scaling:
|
196
|
+
|
197
|
+
- ✅ **Add new device types**: Just define new device ID prefixes
|
198
|
+
- ✅ **Add new message types**: Create new Redis channels as needed
|
199
|
+
- ✅ **Multiple instances**: Each device can have multiple instances
|
200
|
+
- ✅ **Load balancing**: Redis handles distribution automatically
|
201
|
+
|
202
|
+
## Running the Example
|
203
|
+
|
204
|
+
To see this data flow in action:
|
205
|
+
|
206
|
+
```bash
|
207
|
+
# Ensure Redis is running
|
208
|
+
redis-server
|
209
|
+
|
210
|
+
# Run the IoT example
|
211
|
+
cd examples
|
212
|
+
ruby 04_redis_smart_home_iot.rb
|
213
|
+
```
|
214
|
+
|
215
|
+
**What you'll observe:**
|
216
|
+
1. **Device initialization** and Redis connection setup
|
217
|
+
2. **Sensor data publishing** every 3-5 seconds per device
|
218
|
+
3. **Command routing** with device-specific responses
|
219
|
+
4. **Alert generation** when motion is detected or conditions change
|
220
|
+
5. **Dashboard status updates** every 10 seconds showing active device counts
|
221
|
+
|
222
|
+
## Redis Channel Monitoring
|
223
|
+
|
224
|
+
You can monitor the Redis channels directly:
|
225
|
+
|
226
|
+
```bash
|
227
|
+
# View active channels
|
228
|
+
redis-cli PUBSUB CHANNELS
|
229
|
+
|
230
|
+
# Monitor all channel activity
|
231
|
+
redis-cli MONITOR
|
232
|
+
|
233
|
+
# Subscribe to specific channels
|
234
|
+
redis-cli SUBSCRIBE SensorDataMessage
|
235
|
+
redis-cli SUBSCRIBE DeviceCommandMessage
|
236
|
+
redis-cli SUBSCRIBE AlertMessage
|
237
|
+
redis-cli SUBSCRIBE DashboardStatusMessage
|
238
|
+
```
|
239
|
+
|
240
|
+
## Key Design Patterns Demonstrated
|
241
|
+
|
242
|
+
### 1. **Message Class as Channel Name**
|
243
|
+
SmartMessage automatically uses the message class name as the Redis channel name, providing clean separation.
|
244
|
+
|
245
|
+
### 2. **Device ID-Based Routing**
|
246
|
+
Commands are filtered by device ID prefixes, ensuring only intended devices process commands.
|
247
|
+
|
248
|
+
### 3. **Centralized Monitoring**
|
249
|
+
The dashboard subscribes to all channels, providing comprehensive system visibility.
|
250
|
+
|
251
|
+
### 4. **Event-Driven Alerts**
|
252
|
+
Devices autonomously generate alerts based on sensor readings and conditions.
|
253
|
+
|
254
|
+
### 5. **Graceful Degradation**
|
255
|
+
System falls back to memory transport if Redis is unavailable, ensuring development continues.
|
256
|
+
|
257
|
+
This architecture demonstrates production-ready IoT messaging patterns using Redis pub/sub for efficient, scalable device communication.
|
data/lib/smart_message/base.rb
CHANGED
@@ -16,6 +16,9 @@ module SmartMessage
|
|
16
16
|
@@transport = nil
|
17
17
|
@@serializer = nil
|
18
18
|
@@logger = nil
|
19
|
+
|
20
|
+
# Registry for proc-based message handlers
|
21
|
+
@@proc_handlers = {}
|
19
22
|
|
20
23
|
include Hashie::Extensions::Dash::PropertyTranslation
|
21
24
|
|
@@ -184,6 +187,44 @@ module SmartMessage
|
|
184
187
|
end
|
185
188
|
|
186
189
|
|
190
|
+
#########################################################
|
191
|
+
## proc handler management
|
192
|
+
|
193
|
+
# Register a proc handler and return a unique identifier for it
|
194
|
+
# @param message_class [String] The message class name
|
195
|
+
# @param handler_proc [Proc] The proc to register
|
196
|
+
# @return [String] Unique identifier for this handler
|
197
|
+
def register_proc_handler(message_class, handler_proc)
|
198
|
+
handler_id = "#{message_class}.proc_#{SecureRandom.hex(8)}"
|
199
|
+
@@proc_handlers[handler_id] = handler_proc
|
200
|
+
handler_id
|
201
|
+
end
|
202
|
+
|
203
|
+
# Call a registered proc handler
|
204
|
+
# @param handler_id [String] The handler identifier
|
205
|
+
# @param message_header [SmartMessage::Header] The message header
|
206
|
+
# @param message_payload [String] The message payload
|
207
|
+
def call_proc_handler(handler_id, message_header, message_payload)
|
208
|
+
handler_proc = @@proc_handlers[handler_id]
|
209
|
+
return unless handler_proc
|
210
|
+
|
211
|
+
handler_proc.call(message_header, message_payload)
|
212
|
+
end
|
213
|
+
|
214
|
+
# Remove a proc handler from the registry
|
215
|
+
# @param handler_id [String] The handler identifier to remove
|
216
|
+
def unregister_proc_handler(handler_id)
|
217
|
+
@@proc_handlers.delete(handler_id)
|
218
|
+
end
|
219
|
+
|
220
|
+
# Check if a handler ID refers to a proc handler
|
221
|
+
# @param handler_id [String] The handler identifier
|
222
|
+
# @return [Boolean] True if this is a proc handler
|
223
|
+
def proc_handler?(handler_id)
|
224
|
+
@@proc_handlers.key?(handler_id)
|
225
|
+
end
|
226
|
+
|
227
|
+
|
187
228
|
#########################################################
|
188
229
|
## class-level transport configuration
|
189
230
|
|
@@ -226,25 +267,74 @@ module SmartMessage
|
|
226
267
|
# Add this message class to the transport's catalog of
|
227
268
|
# subscribed messages. If the transport is missing, raise
|
228
269
|
# an exception.
|
229
|
-
|
230
|
-
|
231
|
-
|
270
|
+
#
|
271
|
+
# @param process_method [String, Proc, nil] The processing method:
|
272
|
+
# - String: Method name like "MyService.handle_message"
|
273
|
+
# - Proc: A proc/lambda that accepts (message_header, message_payload)
|
274
|
+
# - nil: Uses default "MessageClass.process" method
|
275
|
+
# @param block [Proc] Alternative way to pass a processing block
|
276
|
+
# @return [String] The identifier used for this subscription
|
277
|
+
#
|
278
|
+
# @example Using default handler
|
279
|
+
# MyMessage.subscribe
|
280
|
+
#
|
281
|
+
# @example Using custom method name
|
282
|
+
# MyMessage.subscribe("MyService.handle_message")
|
283
|
+
#
|
284
|
+
# @example Using a block
|
285
|
+
# MyMessage.subscribe do |header, payload|
|
286
|
+
# data = JSON.parse(payload)
|
287
|
+
# puts "Received: #{data}"
|
288
|
+
# end
|
289
|
+
#
|
290
|
+
# @example Using a proc
|
291
|
+
# handler = proc { |header, payload| puts "Processing..." }
|
292
|
+
# MyMessage.subscribe(handler)
|
293
|
+
def subscribe(process_method = nil, &block)
|
294
|
+
message_class = whoami
|
295
|
+
|
296
|
+
# Handle different parameter types
|
297
|
+
if block_given?
|
298
|
+
# Block was passed - use it as the handler
|
299
|
+
handler_proc = block
|
300
|
+
process_method = register_proc_handler(message_class, handler_proc)
|
301
|
+
elsif process_method.respond_to?(:call)
|
302
|
+
# Proc/lambda was passed as first parameter
|
303
|
+
handler_proc = process_method
|
304
|
+
process_method = register_proc_handler(message_class, handler_proc)
|
305
|
+
elsif process_method.nil?
|
306
|
+
# Use default handler
|
307
|
+
process_method = message_class + '.process'
|
308
|
+
end
|
309
|
+
# If process_method is a String, use it as-is
|
232
310
|
|
233
311
|
# TODO: Add proper logging here
|
234
312
|
|
235
313
|
raise Errors::TransportNotConfigured if transport_missing?
|
236
314
|
transport.subscribe(message_class, process_method)
|
315
|
+
|
316
|
+
process_method
|
237
317
|
end
|
238
318
|
|
239
319
|
|
240
320
|
# Remove this process_method for this message class from the
|
241
321
|
# subscribers list.
|
322
|
+
# @param process_method [String, nil] The processing method identifier to remove
|
323
|
+
# - String: Method name like "MyService.handle_message" or proc handler ID
|
324
|
+
# - nil: Uses default "MessageClass.process" method
|
242
325
|
def unsubscribe(process_method = nil)
|
243
326
|
message_class = whoami
|
244
327
|
process_method = message_class + '.process' if process_method.nil?
|
245
328
|
# TODO: Add proper logging here
|
246
329
|
|
247
|
-
|
330
|
+
if transport_configured?
|
331
|
+
transport.unsubscribe(message_class, process_method)
|
332
|
+
|
333
|
+
# If this was a proc handler, clean it up from the registry
|
334
|
+
if proc_handler?(process_method)
|
335
|
+
unregister_proc_handler(process_method)
|
336
|
+
end
|
337
|
+
end
|
248
338
|
end
|
249
339
|
|
250
340
|
|
@@ -118,13 +118,20 @@ module SmartMessage
|
|
118
118
|
@subscribers[message_klass].each do |message_processor|
|
119
119
|
SS.add(message_klass, message_processor, 'routed' )
|
120
120
|
@router_pool.post do
|
121
|
-
parts = message_processor.split('.')
|
122
|
-
target_klass = parts[0]
|
123
|
-
class_method = parts[1]
|
124
121
|
begin
|
125
|
-
|
126
|
-
|
127
|
-
|
122
|
+
# Check if this is a proc handler or a regular method call
|
123
|
+
if proc_handler?(message_processor)
|
124
|
+
# Call the proc handler via SmartMessage::Base
|
125
|
+
SmartMessage::Base.call_proc_handler(message_processor, message_header, message_payload)
|
126
|
+
else
|
127
|
+
# Original method call logic
|
128
|
+
parts = message_processor.split('.')
|
129
|
+
target_klass = parts[0]
|
130
|
+
class_method = parts[1]
|
131
|
+
target_klass.constantize
|
132
|
+
.method(class_method)
|
133
|
+
.call(message_header, message_payload)
|
134
|
+
end
|
128
135
|
rescue Exception => e
|
129
136
|
# TODO: Add proper exception logging
|
130
137
|
# Exception details: #{e.message}
|
@@ -135,6 +142,15 @@ module SmartMessage
|
|
135
142
|
end
|
136
143
|
end
|
137
144
|
|
145
|
+
private
|
146
|
+
|
147
|
+
# Check if a message processor is a proc handler
|
148
|
+
# @param message_processor [String] The message processor identifier
|
149
|
+
# @return [Boolean] True if this is a proc handler
|
150
|
+
def proc_handler?(message_processor)
|
151
|
+
SmartMessage::Base.proc_handler?(message_processor)
|
152
|
+
end
|
153
|
+
|
138
154
|
|
139
155
|
#######################################################
|
140
156
|
## Class methods
|