wisper-event 0.1.0
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 +7 -0
- data/CHANGELOG.md +3 -0
- data/README.md +310 -0
- data/lib/wisper/listener.rb +49 -0
- data/lib/wisper/rspec/event_matchers.rb +138 -0
- data/lib/wisper-event.rb +3 -0
- data/lib/wisper_event/patches/block_registration.rb +20 -0
- data/lib/wisper_event/patches/events.rb +36 -0
- data/lib/wisper_event/patches/object_registration.rb +21 -0
- data/lib/wisper_event/patches/publisher.rb +21 -0
- data/lib/wisper_event/patches.rb +6 -0
- data/lib/wisper_event/version.rb +5 -0
- data/lib/wisper_event.rb +13 -0
- metadata +66 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: e86546d0aef13dd7de8e6fb64877044ee9be46f00d5c32a6c63b6d3bbb66495f
|
4
|
+
data.tar.gz: 7956a180074ca8bce31ec686780e22e1fee8050f231336af39e77740d747f576
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f299ed79f231c972f086e1bb19fe0f527a5f497a099b96fb9b1cd9b00435b6c40ea43622030491a8632af8962e2b04b32fb29f680b8634174438fddc46ebccc8
|
7
|
+
data.tar.gz: 7b9164c5b3ec3e06d55630822e0879fbc9522d10930e658011eb79975d84089e257a399db5deb2e30518edbffbe9c77cf601c63d689cc7cf01c88822bfc952ae
|
data/CHANGELOG.md
ADDED
data/README.md
ADDED
@@ -0,0 +1,310 @@
|
|
1
|
+
# wisper-event
|
2
|
+
|
3
|
+
A structured event extension for the Wisper gem.
|
4
|
+
|
5
|
+
## Why wisper-event?
|
6
|
+
|
7
|
+
The [Wisper gem](https://github.com/krisleech/wisper) is a popular micro-library for implementing the publisher-subscriber pattern in Ruby. While powerful and flexible, Wisper's approach of broadcasting symbols or strings with unstructured arguments can lead to:
|
8
|
+
|
9
|
+
1. **Too loose coupling** - While loose coupling is generally desirable, Wisper's string/symbol-based events make it difficult to track relationships between publishers and subscribers as applications grow
|
10
|
+
2. **Unclear interfaces** - Without structured events, argument signatures can change unexpectedly, causing silent failures
|
11
|
+
3. **Poor discoverability** - It's challenging to find all handlers for a specific event across a large codebase
|
12
|
+
|
13
|
+
**wisper-event** provides a more structured approach by allowing you to use proper Ruby objects as events while maintaining backward compatibility with Wisper's string/symbol-based events. This lets you:
|
14
|
+
|
15
|
+
- Gradually migrate your codebase to structured events
|
16
|
+
- Have clear, well-defined event interfaces
|
17
|
+
- Implement compile-time checking of event handlers
|
18
|
+
- Easily find event usage with standard code search tools
|
19
|
+
|
20
|
+
This gem was inspired by the original Wisper author's other gems:
|
21
|
+
- [wisper_next](https://gitlab.com/kris.leech/wisper_next)
|
22
|
+
- [ma](https://gitlab.com/kris.leech/ma)
|
23
|
+
|
24
|
+
It solves very particular problem and might be not a good fit for your application.
|
25
|
+
|
26
|
+
## Installation
|
27
|
+
|
28
|
+
Add this line to your application's Gemfile:
|
29
|
+
|
30
|
+
```ruby
|
31
|
+
gem 'wisper-event'
|
32
|
+
```
|
33
|
+
|
34
|
+
And then execute:
|
35
|
+
|
36
|
+
```
|
37
|
+
bundle install
|
38
|
+
```
|
39
|
+
|
40
|
+
## Usage
|
41
|
+
|
42
|
+
### Enabling wisper-event
|
43
|
+
|
44
|
+
After installing the gem, you need to apply the monkey patches:
|
45
|
+
|
46
|
+
```ruby
|
47
|
+
WisperEvent.apply!
|
48
|
+
```
|
49
|
+
|
50
|
+
This is best done during your application's initialization.
|
51
|
+
|
52
|
+
### Creating structured events
|
53
|
+
|
54
|
+
Define your events as plain Ruby classes:
|
55
|
+
|
56
|
+
```ruby
|
57
|
+
module Event
|
58
|
+
class OrderCreated
|
59
|
+
attr_reader :order_id, :customer_id
|
60
|
+
|
61
|
+
def initialize(order_id:, customer_id:)
|
62
|
+
@order_id = order_id
|
63
|
+
@customer_id = customer_id
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
class OrderFailed
|
68
|
+
attr_reader :reason
|
69
|
+
|
70
|
+
def initialize(reason)
|
71
|
+
@reason = reason
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
```
|
76
|
+
|
77
|
+
### Publishing events
|
78
|
+
|
79
|
+
You can publish both traditional Wisper events and structured events from the same publisher:
|
80
|
+
|
81
|
+
```ruby
|
82
|
+
class OrderService
|
83
|
+
include Wisper::Publisher
|
84
|
+
|
85
|
+
def create(params)
|
86
|
+
# Business logic...
|
87
|
+
if order.save
|
88
|
+
# Traditional event with arguments
|
89
|
+
broadcast('order_created', order_id: order.id, customer_id: order.customer_id)
|
90
|
+
|
91
|
+
# Structured event
|
92
|
+
broadcast(Event::OrderCreated.new(order_id: order.id, customer_id: order.customer_id))
|
93
|
+
else
|
94
|
+
# Traditional event
|
95
|
+
broadcast('order_failed', order.errors.full_messages.join(", "))
|
96
|
+
|
97
|
+
# Structured event
|
98
|
+
broadcast(Event::OrderFailed.new(order.errors.full_messages.join(", ")))
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
```
|
103
|
+
|
104
|
+
### Handling events - traditional approach
|
105
|
+
|
106
|
+
The traditional Wisper approach still works:
|
107
|
+
|
108
|
+
```ruby
|
109
|
+
class OrderNotifier
|
110
|
+
def order_created(order_id:, customer_id:)
|
111
|
+
# Handle the event...
|
112
|
+
end
|
113
|
+
|
114
|
+
def order_failed(reason)
|
115
|
+
# Handle the failure...
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
service = OrderService.new
|
120
|
+
service.subscribe(OrderNotifier.new)
|
121
|
+
service.create(params)
|
122
|
+
```
|
123
|
+
|
124
|
+
### Handling events - structured approach
|
125
|
+
|
126
|
+
To handle structured events, include the `Wisper::Listener` module and define your handlers using the `on` class method:
|
127
|
+
|
128
|
+
```ruby
|
129
|
+
lass StructuredOrderHandler
|
130
|
+
include Wisper::Listener
|
131
|
+
|
132
|
+
def initialize(logger)
|
133
|
+
@logger = logger
|
134
|
+
end
|
135
|
+
|
136
|
+
on(Event::OrderCreated) do |event|
|
137
|
+
@logger.info("Order #{event.order_id} was created for customer #{event.customer_id}")
|
138
|
+
end
|
139
|
+
|
140
|
+
on(Event::OrderFailed) do |event|
|
141
|
+
@logger.error("Order creation failed: #{event.reason}")
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
service = OrderService.new
|
146
|
+
service.subscribe(StructuredOrderHandler.new(logger))
|
147
|
+
service.create(params)
|
148
|
+
```
|
149
|
+
|
150
|
+
### Subscribing with Blocks
|
151
|
+
|
152
|
+
You can also subscribe to structured events using blocks:
|
153
|
+
|
154
|
+
```ruby
|
155
|
+
service = OrderService.new
|
156
|
+
service.on(Event::OrderCreated) { |event| puts "Order created: #{event.order_id}" }
|
157
|
+
.on(Event::OrderFailed) { |event| puts "Order failed: #{event.reason}" }
|
158
|
+
service.create(params)
|
159
|
+
```
|
160
|
+
|
161
|
+
## Required event handling
|
162
|
+
|
163
|
+
When using `Wisper::Listener`, every structured event **must have** a corresponding handler. If an event is received that the listener doesn't handle, a `Wisper::Listener::UnhandledEventError` will be raised:
|
164
|
+
|
165
|
+
```ruby
|
166
|
+
class IncompleteListener
|
167
|
+
include Wisper::Listener
|
168
|
+
|
169
|
+
on(Event::OrderCreated) do |event|
|
170
|
+
# This handles Event::OrderCreated
|
171
|
+
end
|
172
|
+
|
173
|
+
# Missing handler for Event::OrderFailed!
|
174
|
+
end
|
175
|
+
|
176
|
+
# This will raise UnhandledEventError when Event::OrderFailed is broadcast
|
177
|
+
```
|
178
|
+
|
179
|
+
This helps ensure that your listeners are complete and don't silently ignore events.
|
180
|
+
|
181
|
+
## Testing
|
182
|
+
|
183
|
+
The gem includes RSpec matchers build on top of [wisper-rspec](https://github.com/krisleech/wisper-rspec) for testing broadcasted events:
|
184
|
+
|
185
|
+
```ruby
|
186
|
+
# Add this to your spec helper
|
187
|
+
require 'wisper/rspec/matchers'
|
188
|
+
require 'wisper/rspec/event_matchers'
|
189
|
+
|
190
|
+
RSpec.configure do |config|
|
191
|
+
config.include(Wisper::RSpec::BroadcastMatcher)
|
192
|
+
config.include(Wisper::RSpec::BroadcastEventMatcher)
|
193
|
+
end
|
194
|
+
|
195
|
+
# In your specs
|
196
|
+
it 'broadcasts the proper event' do
|
197
|
+
service = OrderService.new
|
198
|
+
|
199
|
+
expect { service.create(valid_params) }
|
200
|
+
.to broadcast_event(Event::OrderCreated).with(order_id: 123, customer_id: 456)
|
201
|
+
|
202
|
+
expect { service.create(invalid_params) }
|
203
|
+
.to broadcast_event(Event::OrderFailed).with(message: "Invalid params")
|
204
|
+
end
|
205
|
+
```
|
206
|
+
|
207
|
+
## Migrating to structured events
|
208
|
+
|
209
|
+
Here's a migration strategy:
|
210
|
+
|
211
|
+
1. Start by creating structured events that match your existing string/symbol events
|
212
|
+
1. Update your publishers to broadcast both formats
|
213
|
+
1. Create structured listeners for new code
|
214
|
+
1. Gradually convert existing listeners to the structured format
|
215
|
+
1. Once all listeners are updated to use structured events, you can remove the old string/symbol style broadcasts
|
216
|
+
|
217
|
+
### Example migration
|
218
|
+
|
219
|
+
Before:
|
220
|
+
|
221
|
+
```ruby
|
222
|
+
# Publisher
|
223
|
+
class OrderService
|
224
|
+
include Wisper::Publisher
|
225
|
+
|
226
|
+
def create(params)
|
227
|
+
if order.save
|
228
|
+
broadcast('order_created', order_id: order.id)
|
229
|
+
else
|
230
|
+
broadcast('order_failed', reason: order.errors.full_messages)
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
# Listener
|
236
|
+
class OrderNotifier
|
237
|
+
def order_created(order_id:)
|
238
|
+
# Handle event
|
239
|
+
end
|
240
|
+
|
241
|
+
def order_failed(reason:)
|
242
|
+
# Handle failure
|
243
|
+
end
|
244
|
+
end
|
245
|
+
```
|
246
|
+
|
247
|
+
After
|
248
|
+
|
249
|
+
```ruby
|
250
|
+
# Events
|
251
|
+
module Event
|
252
|
+
class OrderCreated
|
253
|
+
attr_reader :order_id
|
254
|
+
|
255
|
+
def initialize(order_id:)
|
256
|
+
@order_id = order_id
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
class OrderFailed
|
261
|
+
attr_reader :reason
|
262
|
+
|
263
|
+
def initialize(reason:)
|
264
|
+
@reason = reason
|
265
|
+
end
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
# Publisher (during migration)
|
270
|
+
class OrderService
|
271
|
+
include Wisper::Publisher
|
272
|
+
|
273
|
+
def create(params)
|
274
|
+
if order.save
|
275
|
+
# You can remove this line once all listeners are updated
|
276
|
+
broadcast('order_created', order_id: order.id)
|
277
|
+
broadcast(Event::OrderCreated.new(order_id: order.id))
|
278
|
+
else
|
279
|
+
# You can remove this line once all listeners are updated
|
280
|
+
broadcast('order_failed', reason: order.errors.full_messages)
|
281
|
+
broadcast(Event::OrderFailed.new(reason: order.errors.full_messages))
|
282
|
+
end
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
# Modified structured listener
|
287
|
+
class OrderNotifier
|
288
|
+
include Wisper::Listener
|
289
|
+
|
290
|
+
on(Event::OrderCreated) do |event|
|
291
|
+
# Handle event
|
292
|
+
end
|
293
|
+
|
294
|
+
on(Event::OrderFailed) do |event|
|
295
|
+
# Handle failure
|
296
|
+
end
|
297
|
+
|
298
|
+
# If listener is being re-used across different publishers you will be
|
299
|
+
# forced to broadcast structured events across those publishers and handle
|
300
|
+
# all those events in this listener - which is _desired_ behavior
|
301
|
+
end
|
302
|
+
```
|
303
|
+
|
304
|
+
## Limitations and assumptions
|
305
|
+
|
306
|
+
- This gem focuses on improving event structure, not on Wisper's delivery mechanisms
|
307
|
+
- Global listeners are not supported with structured events
|
308
|
+
- Asynchronous event handling is not directly supported - if you need to trigger async jobs, do so explicitly in your listeners
|
309
|
+
- Every structured event must be handled by listeners that include `Wisper::Listener`
|
310
|
+
- `on` / `with` events mapping is not supported as it makes no sense with structured events
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Wisper
|
4
|
+
module Listener
|
5
|
+
UnhandledEventError = Class.new(StandardError)
|
6
|
+
|
7
|
+
# NOTE: mostly copied from ActiveSupport#underscore
|
8
|
+
def self.generated_method_name(event_class)
|
9
|
+
class_name = event_class.gsub('::', '_')
|
10
|
+
name =
|
11
|
+
class_name
|
12
|
+
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
13
|
+
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
14
|
+
.downcase
|
15
|
+
|
16
|
+
"on_#{name}"
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.included(base)
|
20
|
+
base.extend(ClassMethods)
|
21
|
+
end
|
22
|
+
|
23
|
+
def _wisper_listener?
|
24
|
+
true
|
25
|
+
end
|
26
|
+
|
27
|
+
def trigger(event)
|
28
|
+
method_name = Wisper::Listener.generated_method_name(event.class.name)
|
29
|
+
if respond_to?(method_name)
|
30
|
+
public_send(method_name, event)
|
31
|
+
else
|
32
|
+
raise(
|
33
|
+
UnhandledEventError,
|
34
|
+
"Event #{event.class} not handled in #{self.class}"
|
35
|
+
)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
module ClassMethods
|
40
|
+
def on(event_class, &block)
|
41
|
+
method_name = Wisper::Listener.generated_method_name(event_class.name)
|
42
|
+
|
43
|
+
define_method(method_name) do |event|
|
44
|
+
instance_exec(event, &block)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Wisper
|
4
|
+
module RSpec
|
5
|
+
class EventObjectRecorder
|
6
|
+
attr_reader :captured_events
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@captured_events = []
|
10
|
+
end
|
11
|
+
|
12
|
+
def respond_to?(_method_name, _include_private = false)
|
13
|
+
true
|
14
|
+
end
|
15
|
+
|
16
|
+
def respond_to_missing?(*)
|
17
|
+
true
|
18
|
+
end
|
19
|
+
|
20
|
+
def _wisper_listener?
|
21
|
+
true
|
22
|
+
end
|
23
|
+
|
24
|
+
def method_missing(_method_name, *args)
|
25
|
+
@captured_events << args[0]
|
26
|
+
end
|
27
|
+
|
28
|
+
def trigger(event)
|
29
|
+
@captured_events << event
|
30
|
+
end
|
31
|
+
|
32
|
+
def received_event?(expected_event, expected_attributes = nil)
|
33
|
+
@captured_events.any? do |event|
|
34
|
+
expected_class = expected_event.is_a?(Class) ? expected_event : expected_event.class
|
35
|
+
|
36
|
+
if event.is_a?(expected_class)
|
37
|
+
if expected_attributes.nil?
|
38
|
+
expected_event.is_a?(Class) || event == expected_event
|
39
|
+
else
|
40
|
+
expected_attributes.all? do |key, value|
|
41
|
+
event.respond_to?(key) && event.public_send(key) == value
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
module BroadcastEventMatcher
|
50
|
+
class EventMatcher
|
51
|
+
include ::RSpec::Matchers::Composable
|
52
|
+
|
53
|
+
def initialize(event)
|
54
|
+
@expected_event = event
|
55
|
+
@expected_attributes = nil
|
56
|
+
@is_class = event.is_a?(Class)
|
57
|
+
end
|
58
|
+
|
59
|
+
def with(attributes)
|
60
|
+
@expected_attributes = attributes
|
61
|
+
self
|
62
|
+
end
|
63
|
+
|
64
|
+
def supports_block_expectations?
|
65
|
+
true
|
66
|
+
end
|
67
|
+
|
68
|
+
def matches?(block)
|
69
|
+
@recorder = EventObjectRecorder.new
|
70
|
+
|
71
|
+
Wisper.subscribe(@recorder) do
|
72
|
+
block.call
|
73
|
+
end
|
74
|
+
|
75
|
+
@recorder.received_event?(@expected_event, @expected_attributes)
|
76
|
+
end
|
77
|
+
|
78
|
+
def description
|
79
|
+
desc = + "broadcast event of type #{event_class_name}"
|
80
|
+
desc << " with attributes #{@expected_attributes.inspect}" if @expected_attributes
|
81
|
+
desc
|
82
|
+
end
|
83
|
+
|
84
|
+
def failure_message
|
85
|
+
msg = + "expected publisher to broadcast event of type #{event_class_name}"
|
86
|
+
msg << " with attributes #{@expected_attributes.inspect}" if @expected_attributes
|
87
|
+
msg << captured_events_list
|
88
|
+
msg
|
89
|
+
end
|
90
|
+
|
91
|
+
def failure_message_when_negated
|
92
|
+
msg = + "expected publisher not to broadcast event of type #{event_class_name}"
|
93
|
+
msg << " with attributes #{@expected_attributes.inspect}" if @expected_attributes
|
94
|
+
msg
|
95
|
+
end
|
96
|
+
|
97
|
+
def diffable?
|
98
|
+
true
|
99
|
+
end
|
100
|
+
|
101
|
+
def expected
|
102
|
+
@expected_event
|
103
|
+
end
|
104
|
+
|
105
|
+
def actual
|
106
|
+
@recorder.captured_events
|
107
|
+
end
|
108
|
+
|
109
|
+
private
|
110
|
+
|
111
|
+
def captured_events_list
|
112
|
+
if @recorder.captured_events.any?
|
113
|
+
events = @recorder.captured_events.map do |event|
|
114
|
+
event.is_a?(Array) ? "#{event[0]}(#{event[1..].join(', ')})" : event.inspect
|
115
|
+
end
|
116
|
+
" (actual events broadcast: #{events.join(', ')})"
|
117
|
+
else
|
118
|
+
' (no events broadcast)'
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def event_class_name
|
123
|
+
@is_class ? @expected_event.name : @expected_event.class.name
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def broadcast_event(event)
|
128
|
+
EventMatcher.new(event)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
::RSpec::Matchers.define_negated_matcher :not_broadcast_event, :broadcast_event
|
135
|
+
|
136
|
+
RSpec.configure do |config|
|
137
|
+
config.include Wisper::RSpec::BroadcastEventMatcher
|
138
|
+
end
|
data/lib/wisper-event.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module WisperEvent
|
4
|
+
module Patches
|
5
|
+
module BlockRegistration
|
6
|
+
def broadcast(event, _publisher, *args, **kwargs)
|
7
|
+
return unless should_broadcast?(event)
|
8
|
+
|
9
|
+
if event.is_a?(String) || event.is_a?(Symbol)
|
10
|
+
super
|
11
|
+
else
|
12
|
+
# Structured event
|
13
|
+
listener.call(event)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
Wisper::BlockRegistration.prepend(WisperEvent::Patches::BlockRegistration)
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module WisperEvent
|
4
|
+
module Patches
|
5
|
+
module Events
|
6
|
+
def include?(event)
|
7
|
+
if event.is_a?(String) || event.is_a?(Symbol)
|
8
|
+
super
|
9
|
+
# Structured event
|
10
|
+
elsif list.is_a?(Class)
|
11
|
+
event.is_a?(list)
|
12
|
+
elsif list.is_a?(Enumerable) && list.any? { |item| item.is_a?(Class) }
|
13
|
+
list.any? { |item| item.is_a?(Class) && event.is_a?(item) }
|
14
|
+
else
|
15
|
+
super
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def methods
|
22
|
+
{
|
23
|
+
NilClass => ->(_event) { true },
|
24
|
+
String => ->(event) { list == event },
|
25
|
+
Symbol => ->(event) { list.to_s == event },
|
26
|
+
Enumerable => ->(event) { list.map(&:to_s).include? event },
|
27
|
+
Regexp => ->(event) { list.match(event) || false },
|
28
|
+
# Structured event
|
29
|
+
Class => ->(event) { event.is_a?(list) }
|
30
|
+
}
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
Wisper::ValueObjects::Events.prepend(WisperEvent::Patches::Events)
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module WisperEvent
|
4
|
+
module Patches
|
5
|
+
module ObjectRegistration
|
6
|
+
def broadcast(event, publisher, *args, **kwargs)
|
7
|
+
if event.is_a?(String) || event.is_a?(Symbol)
|
8
|
+
super
|
9
|
+
# Structured event
|
10
|
+
# Wisper::Listeners are required to handle structured events
|
11
|
+
elsif listener.respond_to?(:_wisper_listener?)
|
12
|
+
listener.trigger(event)
|
13
|
+
# as method names for events are auto generated
|
14
|
+
# we might as well discard structured events for non-structured listeners
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
Wisper::ObjectRegistration.prepend(WisperEvent::Patches::ObjectRegistration)
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module WisperEvent
|
4
|
+
module Patches
|
5
|
+
module Publisher
|
6
|
+
def broadcast(event, *args, **kwargs)
|
7
|
+
registrations.each do |registration|
|
8
|
+
if event.is_a?(String) || event.is_a?(Symbol)
|
9
|
+
registration.broadcast(clean_event(event), self, *args, **kwargs)
|
10
|
+
else
|
11
|
+
# Structured event - pass them directly
|
12
|
+
registration.broadcast(event, self, *args, **kwargs)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
self
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
Wisper::Publisher.prepend(WisperEvent::Patches::Publisher)
|
data/lib/wisper_event.rb
ADDED
metadata
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: wisper-event
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Rafal Wojsznis
|
8
|
+
bindir: bin
|
9
|
+
cert_chain: []
|
10
|
+
date: 2025-04-22 00:00:00.000000000 Z
|
11
|
+
dependencies:
|
12
|
+
- !ruby/object:Gem::Dependency
|
13
|
+
name: wisper
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
15
|
+
requirements:
|
16
|
+
- - '='
|
17
|
+
- !ruby/object:Gem::Version
|
18
|
+
version: 3.0.0
|
19
|
+
type: :runtime
|
20
|
+
prerelease: false
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
22
|
+
requirements:
|
23
|
+
- - '='
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: 3.0.0
|
26
|
+
description: Structured events for Wisper
|
27
|
+
email:
|
28
|
+
- rafal.wojsznis@gmail.com
|
29
|
+
executables: []
|
30
|
+
extensions: []
|
31
|
+
extra_rdoc_files: []
|
32
|
+
files:
|
33
|
+
- CHANGELOG.md
|
34
|
+
- README.md
|
35
|
+
- lib/wisper-event.rb
|
36
|
+
- lib/wisper/listener.rb
|
37
|
+
- lib/wisper/rspec/event_matchers.rb
|
38
|
+
- lib/wisper_event.rb
|
39
|
+
- lib/wisper_event/patches.rb
|
40
|
+
- lib/wisper_event/patches/block_registration.rb
|
41
|
+
- lib/wisper_event/patches/events.rb
|
42
|
+
- lib/wisper_event/patches/object_registration.rb
|
43
|
+
- lib/wisper_event/patches/publisher.rb
|
44
|
+
- lib/wisper_event/version.rb
|
45
|
+
homepage: https://github.com/rwojsznis/wisper-event
|
46
|
+
licenses:
|
47
|
+
- MIT
|
48
|
+
metadata: {}
|
49
|
+
rdoc_options: []
|
50
|
+
require_paths:
|
51
|
+
- lib
|
52
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: '2.7'
|
57
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
requirements: []
|
63
|
+
rubygems_version: 3.6.1
|
64
|
+
specification_version: 4
|
65
|
+
summary: Backward-compatible support for structured events in Wisper
|
66
|
+
test_files: []
|