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.
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
- All examples use `StdoutTransport` with loopback enabled for demonstration purposes:
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
- - Replace `StdoutTransport` with production transports (Redis, RabbitMQ, Kafka)
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.
@@ -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
- def subscribe(process_method = nil)
230
- message_class = whoami
231
- process_method = message_class + '.process' if process_method.nil?
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
- transport.unsubscribe(message_class, process_method) if transport_configured?
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
- target_klass.constantize
126
- .method(class_method)
127
- .call(message_header, message_payload)
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