pubsub_on_rails 0.0.7 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/README.md +61 -83
- data/lib/pub_sub/domain_event.rb +1 -25
- data/lib/pub_sub/domain_event_handler.rb +7 -3
- data/lib/pub_sub/emit.rb +3 -2
- data/lib/pub_sub/event_class_factory.rb +13 -5
- data/lib/pub_sub/event_emission.rb +54 -20
- data/lib/pub_sub/event_handler_builder.rb +30 -0
- data/lib/pub_sub/event_with_type.rb +17 -0
- data/lib/pub_sub/event_worker.rb +17 -0
- data/lib/pub_sub/subscriptions_linter.rb +42 -0
- data/lib/pub_sub/subscriptions_list.rb +62 -0
- data/lib/pub_sub/testing/event_data_helper.rb +9 -4
- data/lib/pub_sub/testing/rails_event_store.rb +9 -0
- data/lib/pub_sub/testing/subscribe_to.rb +32 -0
- data/lib/pub_sub/testing.rb +2 -2
- data/lib/pub_sub/version.rb +1 -1
- data/lib/pubsub_on_rails.rb +3 -5
- data/pubsub_on_rails-0.0.7.gem +0 -0
- data/pubsub_on_rails.gemspec +23 -0
- metadata +22 -21
- data/Gemfile.lock +0 -64
- data/lib/pub_sub/domain.rb +0 -25
- data/lib/pub_sub/event_trace.rb +0 -18
- data/lib/pub_sub/linter.rb +0 -39
- data/lib/pub_sub/pure_event.rb +0 -19
- data/lib/pub_sub/subscriptions.rb +0 -49
- data/lib/pub_sub/testing/subscription_helpers.rb +0 -24
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 610b5e8d1415af6aa00766ef49444e4a3889aa6e1d79c0da2a74a58a09726d0c
|
4
|
+
data.tar.gz: 57ac3717989db60def406d8f6a85aa4139bdffef3ee3d05b5d7dcae70823f6dc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 43d05d52a2f8dfbc52c9cca47a731acb8c98d2b36d2d490aef3f08cf9a902bf29e9dd394f441fcd914d74fcf53230ccaaf011c0ef64e5c1be546947e6a5ca7d1
|
7
|
+
data.tar.gz: 8c4fc34fc4404ff3476bef408760b10af12113720aadad1428566ab34e8350d902375022621148bf7e3016ae1cdc8ee682cdeacece0f95438d4a177a400bceec
|
data/README.md
CHANGED
@@ -16,12 +16,21 @@ This is especially true for applications covering many side effects, integration
|
|
16
16
|
```ruby
|
17
17
|
# Gemfile
|
18
18
|
|
19
|
-
gem 'pubsub_on_rails', '~> 0.0
|
19
|
+
gem 'pubsub_on_rails', '~> 1.0.0'
|
20
20
|
|
21
21
|
# config/initializers/pub_sub.rb
|
22
22
|
|
23
|
-
|
24
|
-
|
23
|
+
require 'pub_sub/subscriptions_list'
|
24
|
+
|
25
|
+
Rails.configuration.to_prepare do
|
26
|
+
Rails.configuration.event_store = event_store = RailsEventStore::Client.new(
|
27
|
+
repository: RailsEventStoreActiveRecord::EventRepository.new(serializer: RubyEventStore::NULL)
|
28
|
+
)
|
29
|
+
|
30
|
+
PubSub::SubscriptionsList.config_path =
|
31
|
+
Rails.root.join('config/subscriptions.yml')
|
32
|
+
PubSub::SubscriptionsList.load!(event_store)
|
33
|
+
end
|
25
34
|
```
|
26
35
|
|
27
36
|
## Entities
|
@@ -40,7 +49,6 @@ Domain example:
|
|
40
49
|
# app/domains/messaging.rb
|
41
50
|
|
42
51
|
module Messaging
|
43
|
-
extend PubSub::Domain
|
44
52
|
end
|
45
53
|
```
|
46
54
|
|
@@ -72,6 +80,26 @@ module Ordering
|
|
72
80
|
end
|
73
81
|
```
|
74
82
|
|
83
|
+
Since we are using Rails Event Store to handle events, it gives us a possibility to create **stream** of events. We can treat them as sub-list of events. To be able to use that functionality we need to declare which streams given event should be part of. By default we add event to stream based on its name. In case of our example it is `ordering__order_created`. We can provide also custom streams even based on some additional data from the event attributes (for example to group all events related to given order).
|
84
|
+
|
85
|
+
Event example:
|
86
|
+
|
87
|
+
```ruby
|
88
|
+
# app/events/rails_event_store/ordering/order_created_event.rb
|
89
|
+
|
90
|
+
module PubSub
|
91
|
+
module Ordering
|
92
|
+
class OrderCreatedEvent < PubSub::EventWithType
|
93
|
+
def stream_names
|
94
|
+
[
|
95
|
+
"order__#{data[:order_id]}"
|
96
|
+
]
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
```
|
102
|
+
|
75
103
|
### Event publisher
|
76
104
|
|
77
105
|
Event publisher is any class capable of emitting an event.
|
@@ -171,42 +199,41 @@ The recommended RSpec configuration is as follows:
|
|
171
199
|
|
172
200
|
```ruby
|
173
201
|
# spec/support/pub_sub.rb
|
174
|
-
|
202
|
+
|
175
203
|
require 'pub_sub/testing'
|
176
204
|
|
177
205
|
RSpec.configure do |config|
|
178
|
-
config.include
|
179
|
-
config.include PubSub::Testing::SubscriptionsHelper
|
206
|
+
config.include PubSub::Testing::RailsEventStore
|
180
207
|
config.include PubSub::Testing::EventDataHelper
|
181
208
|
|
182
|
-
config.
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
domain_name = example.metadata[:described_class].to_s.underscore
|
188
|
-
PubSub.subscriptions.register(domain_name)
|
209
|
+
config.around(:each, in_memory_res_client: true) do |example|
|
210
|
+
current_event_store = Rails.configuration.event_store
|
211
|
+
Rails.configuration.event_store = RubyEventStore::Client.new(
|
212
|
+
repository: RubyEventStore::InMemoryRepository.new
|
213
|
+
)
|
189
214
|
example.run
|
190
|
-
|
215
|
+
Rails.configuration.event_store = current_event_store
|
191
216
|
end
|
192
217
|
end
|
193
218
|
```
|
194
219
|
|
220
|
+
This will allow you to use `in_memory_res_client` which will not create object (event) in the database and do not call all dependent logic (handlers).
|
221
|
+
|
195
222
|
### Testing subscription
|
196
223
|
|
197
224
|
Testing subscription is as easy as telling what domains should subscribe to what event in what way.
|
198
|
-
|
225
|
+
|
199
226
|
Example:
|
200
227
|
|
201
228
|
```ruby
|
202
|
-
RSpec.describe Messaging
|
229
|
+
RSpec.describe Messaging do
|
203
230
|
it { is_expected.to subscribe_to(:ordering__order_created).asynchronously }
|
204
231
|
end
|
205
232
|
```
|
206
233
|
|
207
234
|
### Testing publishers
|
208
235
|
|
209
|
-
To test publisher it is crucial to test if event was emitted
|
236
|
+
To test publisher it is crucial to test if event was emitted under certain conditions (if any).
|
210
237
|
|
211
238
|
Example:
|
212
239
|
|
@@ -217,20 +244,21 @@ RSpec.describe Order do
|
|
217
244
|
customer = create(:customer)
|
218
245
|
line_items = create_list(:line_item, 2)
|
219
246
|
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
247
|
+
Order.create(
|
248
|
+
customer: customer,
|
249
|
+
total_amount: 100.99,
|
250
|
+
comment: 'Small order',
|
251
|
+
line_items: line_items
|
252
|
+
)
|
253
|
+
|
254
|
+
expect(event_store).to have_published(
|
255
|
+
an_event(PubSub::Ordering::OrderCreatedEvent).with_data(
|
256
|
+
order_id: fetch_next_id_for(Order),
|
257
|
+
total_amount: 100.99,
|
258
|
+
comment: 'Small order',
|
259
|
+
line_items: line_items
|
226
260
|
)
|
227
|
-
|
228
|
-
:ordering__order_created,
|
229
|
-
order_id: fetch_next_id_for(Order),
|
230
|
-
total_amount: 100.99,
|
231
|
-
comment: 'Small order',
|
232
|
-
line_items: line_items
|
233
|
-
)
|
261
|
+
).in_stream('ordering__order_created')
|
234
262
|
end
|
235
263
|
end
|
236
264
|
end
|
@@ -269,19 +297,6 @@ module Messaging
|
|
269
297
|
end
|
270
298
|
end
|
271
299
|
```
|
272
|
-
### Integration testing
|
273
|
-
|
274
|
-
By default all subscriptions are cleared in testing environment to enforce testing in isolation.
|
275
|
-
To enable integration testing, a test can be wrapped in block provided by `with_subscription_to` helper.
|
276
|
-
This way events emitted by code executed within this block will be handled by handlers from domains provided as arguments to `with_subscription_to` helper.
|
277
|
-
|
278
|
-
```ruby
|
279
|
-
it 'does something' do
|
280
|
-
with_subscription_to('messaging', 'logging') do
|
281
|
-
# setup, subject, assertions etc.
|
282
|
-
end
|
283
|
-
end
|
284
|
-
```
|
285
300
|
|
286
301
|
### Subscriptions linting
|
287
302
|
|
@@ -334,33 +349,6 @@ logging:
|
|
334
349
|
all_events: sync
|
335
350
|
```
|
336
351
|
|
337
|
-
# `emit` vs `broadcast`
|
338
|
-
|
339
|
-
PubSub on Rails leverages `wisper` and `wisper-sidekiq` under the hood.
|
340
|
-
This is why instead of using `emit`, you can broadcast events by using wisper's `broadcast` method.
|
341
|
-
|
342
|
-
```ruby
|
343
|
-
# app/models/order.rb
|
344
|
-
|
345
|
-
class Order < ApplicationRecord
|
346
|
-
include Wisper::Publisher
|
347
|
-
|
348
|
-
#...
|
349
|
-
|
350
|
-
after_create do
|
351
|
-
broadcast(
|
352
|
-
:ordering__guest_checked_in,
|
353
|
-
order_id: id,
|
354
|
-
line_items: line_items,
|
355
|
-
total_amount: total_amount,
|
356
|
-
comment: comment
|
357
|
-
)
|
358
|
-
end
|
359
|
-
end
|
360
|
-
```
|
361
|
-
|
362
|
-
Why `emit` then? `emit` provides a couple of simplifications that make emitting events easier and more reliable.
|
363
|
-
|
364
352
|
## Payload verification
|
365
353
|
|
366
354
|
Every time event is emitted, its payload is supplied to corresponding event class and is verified.
|
@@ -370,7 +358,7 @@ Example:
|
|
370
358
|
|
371
359
|
```ruby
|
372
360
|
module Accounts
|
373
|
-
class PersonCreatedEvent < DomainEvent
|
361
|
+
class PersonCreatedEvent < PubSub::DomainEvent
|
374
362
|
attribute :person_id, Types::Strict::Integer
|
375
363
|
end
|
376
364
|
end
|
@@ -401,7 +389,7 @@ end
|
|
401
389
|
|
402
390
|
## Automatic event payload population
|
403
391
|
|
404
|
-
Whenever you emit an event, it will try to populate its payload with data using public interface of object it is emitted from within.
|
392
|
+
Whenever you emit an event, it will try to populate its payload with data using public interface of object it is emitted from within.
|
405
393
|
|
406
394
|
```ruby
|
407
395
|
# app/models/oriering/order.rb
|
@@ -423,16 +411,6 @@ module Ordering
|
|
423
411
|
end
|
424
412
|
```
|
425
413
|
|
426
|
-
## Manually broadcasting events
|
427
|
-
|
428
|
-
If for some reason you need to safely broadcast some event, the best way is to instantiate its class and call `#broadcast!` on it.
|
429
|
-
|
430
|
-
Example:
|
431
|
-
|
432
|
-
```ruby
|
433
|
-
Ordering::OrderCreatedEvent.new(order_id: 1, total_amount: 25, line_items: []).broadcast!
|
434
|
-
```
|
435
|
-
|
436
414
|
# TODO
|
437
415
|
|
438
416
|
- Dynamic event classes
|
data/lib/pub_sub/domain_event.rb
CHANGED
@@ -1,28 +1,4 @@
|
|
1
|
-
require 'pub_sub/pure_event'
|
2
|
-
|
3
1
|
module PubSub
|
4
|
-
class DomainEvent <
|
5
|
-
attribute :event_trace_id, Types::Strict::String.default {
|
6
|
-
EventTrace.trace_id ||= SecureRandom.hex(8)
|
7
|
-
}
|
8
|
-
attribute :event_id, Types::Strict::String.default {
|
9
|
-
SecureRandom.hex(8)
|
10
|
-
}
|
11
|
-
attribute :trigger_id, Types::Strict::String.default {
|
12
|
-
EventTrace.last_event_id || 'ORIGIN'
|
13
|
-
}
|
14
|
-
|
15
|
-
def initialize(*args)
|
16
|
-
super(*args)
|
17
|
-
EventTrace.last_event_id = event_id
|
18
|
-
end
|
19
|
-
|
20
|
-
private
|
21
|
-
|
22
|
-
def attributes_to_broadcast
|
23
|
-
super.
|
24
|
-
except(:event_trace_id, :event_id, :trigger_id).
|
25
|
-
merge(event_uid: "#{event_trace_id}-#{trigger_id}-#{event_id}")
|
26
|
-
end
|
2
|
+
class DomainEvent < Dry::Struct
|
27
3
|
end
|
28
4
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module PubSub
|
2
2
|
class DomainEventHandler
|
3
|
-
def initialize(
|
4
|
-
@
|
3
|
+
def initialize(event)
|
4
|
+
@event = event
|
5
5
|
end
|
6
6
|
|
7
7
|
def call
|
@@ -14,7 +14,7 @@ module PubSub
|
|
14
14
|
|
15
15
|
private
|
16
16
|
|
17
|
-
attr_reader :
|
17
|
+
attr_reader :event
|
18
18
|
|
19
19
|
def process_event?
|
20
20
|
true
|
@@ -23,5 +23,9 @@ module PubSub
|
|
23
23
|
def event_data
|
24
24
|
@event_data ||= OpenStruct.new(event_data_hash)
|
25
25
|
end
|
26
|
+
|
27
|
+
def event_data_hash
|
28
|
+
event.data
|
29
|
+
end
|
26
30
|
end
|
27
31
|
end
|
data/lib/pub_sub/emit.rb
CHANGED
@@ -4,13 +4,14 @@ require 'pub_sub/event_emission'
|
|
4
4
|
module PubSub
|
5
5
|
module Emit
|
6
6
|
def emit(event_name, explicit_payload = {})
|
7
|
+
abstract_event_class = explicit_payload.delete(:abstract_event_class)
|
7
8
|
event_class = EventClassFactory.build(
|
8
9
|
event_name,
|
9
10
|
domain_name: self.class.name.deconstantize.demodulize,
|
10
|
-
abstract_event_class:
|
11
|
+
abstract_event_class:
|
11
12
|
)
|
12
13
|
|
13
|
-
EventEmission.new(event_class, explicit_payload, self).call
|
14
|
+
EventEmission.new(abstract_event_class, event_class, event_name, explicit_payload, self).call
|
14
15
|
end
|
15
16
|
end
|
16
17
|
end
|
@@ -5,8 +5,8 @@ module PubSub
|
|
5
5
|
def self.build(event_name, domain_name: nil, abstract_event_class: nil)
|
6
6
|
new(
|
7
7
|
event_name,
|
8
|
-
domain_name
|
9
|
-
abstract_event_class:
|
8
|
+
domain_name:,
|
9
|
+
abstract_event_class:
|
10
10
|
).build_event_class
|
11
11
|
end
|
12
12
|
|
@@ -17,9 +17,13 @@ module PubSub
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def build_event_class
|
20
|
+
event_class = res_event_class_name.safe_constantize
|
21
|
+
|
22
|
+
return event_class if event_class.present?
|
23
|
+
|
20
24
|
event_class = event_class_name.safe_constantize
|
21
25
|
|
22
|
-
return event_class
|
26
|
+
return event_class if event_class.present?
|
23
27
|
|
24
28
|
if abstract_event_class.nil?
|
25
29
|
raise(EventClassDoesNotExist, event_class_name)
|
@@ -46,12 +50,16 @@ module PubSub
|
|
46
50
|
|
47
51
|
def event_name_with_domain
|
48
52
|
if event_name_includes_domain?
|
49
|
-
event_name.to_s.sub('__', '/')
|
53
|
+
event_name.to_s.downcase.sub('__', '/')
|
50
54
|
else
|
51
|
-
|
55
|
+
[domain_name&.underscore, event_name].compact.join('/')
|
52
56
|
end
|
53
57
|
end
|
54
58
|
|
59
|
+
def res_event_class_name
|
60
|
+
@res_event_class_name ||= "pub_sub/#{event_name_with_domain}_event".classify
|
61
|
+
end
|
62
|
+
|
55
63
|
def event_class_name
|
56
64
|
@event_class_name ||= "#{event_name_with_domain}_event".classify
|
57
65
|
end
|
@@ -1,44 +1,78 @@
|
|
1
1
|
require 'pub_sub/payload_attribute'
|
2
2
|
|
3
3
|
module PubSub
|
4
|
-
|
5
|
-
|
4
|
+
Error = Class.new(StandardError)
|
5
|
+
EventMissing = Class.new(Error)
|
6
|
+
EventPayloadArgumentMissing = Class.new(Error)
|
6
7
|
|
7
|
-
|
8
|
+
class EventEmission
|
9
|
+
def initialize(abstract_event_class, event_class, event_name, explicit_payload, context)
|
10
|
+
@abstract_event_class = abstract_event_class
|
8
11
|
@event_class = event_class
|
12
|
+
@event_name = event_name
|
9
13
|
@explicit_payload = explicit_payload
|
10
14
|
@context = context
|
11
15
|
end
|
12
16
|
|
13
17
|
def call
|
14
|
-
event_class.
|
18
|
+
if event_class.ancestors.include?(PubSub::EventWithType)
|
19
|
+
event_store.publish(event, stream_name:)
|
20
|
+
else
|
21
|
+
raise(EventMissing, event_name)
|
22
|
+
end
|
15
23
|
end
|
16
24
|
|
17
25
|
private
|
18
26
|
|
19
|
-
attr_reader :event_class, :explicit_payload, :context
|
27
|
+
attr_reader :abstract_event_class, :event_class, :event_name, :explicit_payload, :context
|
28
|
+
|
29
|
+
def event
|
30
|
+
event_class.new(data: full_payload)
|
31
|
+
end
|
32
|
+
|
33
|
+
def event_name_includes_domain?
|
34
|
+
event_name.to_s.include?('__')
|
35
|
+
end
|
36
|
+
|
37
|
+
def stream_name
|
38
|
+
return event_name.to_s.downcase if event_name_includes_domain?
|
20
39
|
|
21
|
-
|
22
|
-
|
40
|
+
"#{domain}__#{event_name}"
|
41
|
+
end
|
42
|
+
|
43
|
+
def domain
|
44
|
+
if abstract_event_class
|
45
|
+
abstract_event_class.name.deconstantize.underscore
|
46
|
+
else
|
47
|
+
context.class.name.deconstantize.demodulize.underscore
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def dry_struct_event_class
|
52
|
+
@dry_struct_event_class ||= event_class.name.remove('PubSub').constantize
|
23
53
|
end
|
24
54
|
|
25
55
|
# rubocop:disable Metrics/MethodLength
|
26
56
|
def full_payload
|
27
|
-
|
28
|
-
result[attribute_name] = PayloadAttribute.new(
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
57
|
+
dry_struct_event_class.attribute_names.each_with_object({}) do |attribute_name, result|
|
58
|
+
result[attribute_name] = PayloadAttribute.new(
|
59
|
+
attribute_name, explicit_payload, context
|
60
|
+
).get
|
61
|
+
rescue PayloadAttribute::CannotEvaluate => e
|
62
|
+
next if dry_struct_event_class.schema.key(attribute_name).default?
|
63
|
+
|
64
|
+
raise(
|
65
|
+
EventPayloadArgumentMissing,
|
66
|
+
"Event [#{dry_struct_event_class.name}] expects [#{attribute_name}] " \
|
67
|
+
"payload attribute to be either exposed as [#{e.message}] method in emitting object " \
|
68
|
+
'or provided as argument'
|
69
|
+
)
|
40
70
|
end
|
41
71
|
end
|
42
72
|
# rubocop:enable Metrics/MethodLength
|
73
|
+
|
74
|
+
def event_store
|
75
|
+
Rails.configuration.event_store
|
76
|
+
end
|
43
77
|
end
|
44
78
|
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module PubSub
|
2
|
+
class EventHandlerBuilder
|
3
|
+
def initialize(class_name, subscription_type)
|
4
|
+
@class_name = class_name
|
5
|
+
@subscription_type = subscription_type.to_sym
|
6
|
+
end
|
7
|
+
|
8
|
+
def call(event)
|
9
|
+
if async?
|
10
|
+
EventWorker.perform_async(class_name.to_s, event.event_id)
|
11
|
+
else
|
12
|
+
class_name.new(event).call!
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
protected
|
17
|
+
|
18
|
+
attr_reader :class_name, :subscription_type
|
19
|
+
|
20
|
+
def ==(other)
|
21
|
+
class_name == other.class_name && subscription_type == other.subscription_type
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def async?
|
27
|
+
subscription_type == :async
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module PubSub
|
2
|
+
class EventWithType < RailsEventStore::Event
|
3
|
+
def initialize(event_id: SecureRandom.uuid, metadata: nil, data: {})
|
4
|
+
super(
|
5
|
+
event_id:,
|
6
|
+
metadata:,
|
7
|
+
data: self.class.name.remove('PubSub').constantize.new(
|
8
|
+
data.deep_symbolize_keys
|
9
|
+
).attributes
|
10
|
+
)
|
11
|
+
end
|
12
|
+
|
13
|
+
def stream_names
|
14
|
+
[]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module PubSub
|
2
|
+
class EventWorker
|
3
|
+
include Sidekiq::Job
|
4
|
+
|
5
|
+
def perform(class_name, event_id)
|
6
|
+
class_name.constantize.new(
|
7
|
+
event_store.read.event(event_id)
|
8
|
+
).call!
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def event_store
|
14
|
+
Rails.configuration.event_store
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module PubSub
|
2
|
+
class SubscriptionsLinter
|
3
|
+
MissingSubscriptions = Class.new(StandardError)
|
4
|
+
|
5
|
+
def initialize(subscriptions)
|
6
|
+
@subscriptions = subscriptions
|
7
|
+
end
|
8
|
+
|
9
|
+
def lint!
|
10
|
+
raise MissingSubscriptions, error_message if missing_subscriptions.present?
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
attr_reader :subscriptions
|
16
|
+
|
17
|
+
def error_message
|
18
|
+
"The following subscriptions are missing: \n#{missing_subscriptions.join("\n")}"
|
19
|
+
end
|
20
|
+
|
21
|
+
def missing_subscriptions
|
22
|
+
(handled_subscription_names - all_subscription_names)
|
23
|
+
end
|
24
|
+
|
25
|
+
# :reek:UtilityFunction, :reek:DuplicateMethodCall
|
26
|
+
def handled_subscription_names
|
27
|
+
Dir[Rails.root.join('app/event_handlers/*/*.rb')].map do |file_path|
|
28
|
+
file_path.
|
29
|
+
sub(Rails.root.join('app/event_handlers/').to_s, '').
|
30
|
+
sub('_handler.rb', '')
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def all_subscription_names
|
35
|
+
subscriptions.flat_map do |domain_name, subscriptions|
|
36
|
+
subscriptions.keys.map do |event_name|
|
37
|
+
"#{domain_name}/#{event_name.sub('__', '_')}"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'pub_sub/subscriptions_linter'
|
2
|
+
require 'pub_sub/event_handler_builder'
|
3
|
+
|
4
|
+
module PubSub
|
5
|
+
class SubscriptionsList
|
6
|
+
include Singleton
|
7
|
+
|
8
|
+
cattr_accessor :config_path
|
9
|
+
self.config_path = 'config/subscriptions.yml'
|
10
|
+
|
11
|
+
def self.load!(event_store)
|
12
|
+
instance.event_store = event_store
|
13
|
+
instance.load!
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.lint!
|
17
|
+
instance.lint!
|
18
|
+
end
|
19
|
+
|
20
|
+
attr_accessor :event_store
|
21
|
+
|
22
|
+
def load!
|
23
|
+
domain_subscriptions.each do |domain_name, subscriptions|
|
24
|
+
subscriptions.each do |event_name, subscription_type|
|
25
|
+
if event_name == 'all_events'
|
26
|
+
subscribe_to_all_events(domain_name, subscription_type)
|
27
|
+
else
|
28
|
+
subscribe_to_event(domain_name, event_name, subscription_type)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def lint!
|
35
|
+
SubscriptionsLinter.new(domain_subscriptions).lint!
|
36
|
+
end
|
37
|
+
|
38
|
+
def initialize
|
39
|
+
@domain_subscriptions = YAML.load_file(self.class.config_path)
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
attr_reader :domain_subscriptions
|
45
|
+
|
46
|
+
def subscribe_to_all_events(domain_name, subscription_type)
|
47
|
+
handler_class = "#{domain_name.camelize}Handler".constantize
|
48
|
+
event_store.subscribe_to_all_events(
|
49
|
+
EventHandlerBuilder.new(handler_class, subscription_type)
|
50
|
+
)
|
51
|
+
end
|
52
|
+
|
53
|
+
def subscribe_to_event(domain_name, event_name, subscription_type)
|
54
|
+
event_domain, name = event_name.split('__').map(&:camelize)
|
55
|
+
event_class = "PubSub::#{event_domain}::#{name}Event".constantize
|
56
|
+
handler_class = "#{domain_name.camelize}::#{event_domain}#{name}Handler".constantize
|
57
|
+
event_store.subscribe(
|
58
|
+
EventHandlerBuilder.new(handler_class, subscription_type), to: [event_class]
|
59
|
+
)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -2,10 +2,15 @@ module PubSub
|
|
2
2
|
module Testing
|
3
3
|
module EventDataHelper
|
4
4
|
def event_data_for(event_name, **payload)
|
5
|
-
EventClassFactory.
|
6
|
-
|
7
|
-
|
8
|
-
|
5
|
+
event_class = PubSub::EventClassFactory.build(
|
6
|
+
event_name, abstract_event_class: payload.delete(:abstract_event_class)
|
7
|
+
)
|
8
|
+
|
9
|
+
if event_class.ancestors.include?(PubSub::EventWithType)
|
10
|
+
event_class.new(data: payload)
|
11
|
+
else
|
12
|
+
event_class.new(payload).attributes
|
13
|
+
end
|
9
14
|
end
|
10
15
|
end
|
11
16
|
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
RSpec::Matchers.define :subscribe_to do |event_name|
|
2
|
+
match do |domain|
|
3
|
+
handler_class = build_handler_class(event_name, domain)
|
4
|
+
event_class = build_event_class(event_name)
|
5
|
+
subscription_type = async? ? :async : :sync
|
6
|
+
|
7
|
+
expect(
|
8
|
+
PubSub::EventHandlerBuilder.new(handler_class, subscription_type)
|
9
|
+
).to have_subscribed_to_events(event_class).in(event_store)
|
10
|
+
end
|
11
|
+
|
12
|
+
chain :asynchronously do
|
13
|
+
@asynchronously = true
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def build_handler_class(event_name, domain)
|
19
|
+
handler_name = event_name.to_s.sub('__', '/').camelize
|
20
|
+
handler_name.remove!('::')
|
21
|
+
"#{domain.name}::#{handler_name}Handler".constantize
|
22
|
+
end
|
23
|
+
|
24
|
+
def build_event_class(event_name)
|
25
|
+
event_class_name = event_name.to_s.sub('__', '/').camelize
|
26
|
+
"PubSub::#{event_class_name}Event".constantize
|
27
|
+
end
|
28
|
+
|
29
|
+
def async?
|
30
|
+
@asynchronously
|
31
|
+
end
|
32
|
+
end
|
data/lib/pub_sub/testing.rb
CHANGED
@@ -1,3 +1,3 @@
|
|
1
|
-
require '
|
1
|
+
require 'pub_sub/testing/rails_event_store'
|
2
2
|
require 'pub_sub/testing/event_data_helper'
|
3
|
-
require 'pub_sub/testing/
|
3
|
+
require 'pub_sub/testing/subscribe_to'
|
data/lib/pub_sub/version.rb
CHANGED
data/lib/pubsub_on_rails.rb
CHANGED
@@ -1,11 +1,9 @@
|
|
1
|
-
require '
|
2
|
-
require 'wisper/sidekiq'
|
1
|
+
require 'rails_event_store'
|
3
2
|
|
4
3
|
require 'pub_sub/dry'
|
5
4
|
require 'pub_sub'
|
6
5
|
require 'pub_sub/emit'
|
7
|
-
require 'pub_sub/
|
8
|
-
require 'pub_sub/
|
9
|
-
require 'pub_sub/domain'
|
6
|
+
require 'pub_sub/event_with_type'
|
7
|
+
require 'pub_sub/subscriptions_list'
|
10
8
|
require 'pub_sub/domain_event'
|
11
9
|
require 'pub_sub/domain_event_handler'
|
Binary file
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
$:.unshift File.expand_path('../lib', __FILE__)
|
4
|
+
require 'pub_sub/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = 'pubsub_on_rails'
|
8
|
+
s.version = PubSub::VERSION
|
9
|
+
s.authors = ['Stevo']
|
10
|
+
s.email = ['b.kosmowski@selleo.com']
|
11
|
+
s.homepage = 'https://github.com/Selleo/pubsub_on_rails'
|
12
|
+
s.licenses = ['MIT']
|
13
|
+
s.summary = 'Opinionated publish-subscribe pattern for ruby and rails'
|
14
|
+
s.description = 'Opinionated publish-subscribe pattern for ruby and rails'
|
15
|
+
|
16
|
+
s.files = Dir.glob('{bin/*,lib/**/*,[A-Z]*}')
|
17
|
+
s.platform = Gem::Platform::RUBY
|
18
|
+
s.require_paths = ['lib']
|
19
|
+
s.add_dependency 'dry-struct'
|
20
|
+
s.add_dependency 'sidekiq'
|
21
|
+
s.add_dependency 'rails_event_store'
|
22
|
+
s.add_dependency 'ruby_event_store-rspec'
|
23
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pubsub_on_rails
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Stevo
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-10-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: dry-struct
|
@@ -25,21 +25,21 @@ dependencies:
|
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: sidekiq
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - "
|
31
|
+
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
33
|
+
version: '0'
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- - "
|
38
|
+
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version:
|
40
|
+
version: '0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
42
|
+
name: rails_event_store
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
45
|
- - ">="
|
@@ -53,7 +53,7 @@ dependencies:
|
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
56
|
+
name: ruby_event_store-rspec
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
59
|
- - ">="
|
@@ -74,32 +74,34 @@ extensions: []
|
|
74
74
|
extra_rdoc_files: []
|
75
75
|
files:
|
76
76
|
- Gemfile
|
77
|
-
- Gemfile.lock
|
78
77
|
- LICENSE.md
|
79
78
|
- README.md
|
80
79
|
- lib/pub_sub.rb
|
81
|
-
- lib/pub_sub/domain.rb
|
82
80
|
- lib/pub_sub/domain_event.rb
|
83
81
|
- lib/pub_sub/domain_event_handler.rb
|
84
82
|
- lib/pub_sub/dry.rb
|
85
83
|
- lib/pub_sub/emit.rb
|
86
84
|
- lib/pub_sub/event_class_factory.rb
|
87
85
|
- lib/pub_sub/event_emission.rb
|
88
|
-
- lib/pub_sub/
|
89
|
-
- lib/pub_sub/
|
86
|
+
- lib/pub_sub/event_handler_builder.rb
|
87
|
+
- lib/pub_sub/event_with_type.rb
|
88
|
+
- lib/pub_sub/event_worker.rb
|
90
89
|
- lib/pub_sub/payload_attribute.rb
|
91
|
-
- lib/pub_sub/
|
92
|
-
- lib/pub_sub/
|
90
|
+
- lib/pub_sub/subscriptions_linter.rb
|
91
|
+
- lib/pub_sub/subscriptions_list.rb
|
93
92
|
- lib/pub_sub/testing.rb
|
94
93
|
- lib/pub_sub/testing/event_data_helper.rb
|
95
|
-
- lib/pub_sub/testing/
|
94
|
+
- lib/pub_sub/testing/rails_event_store.rb
|
95
|
+
- lib/pub_sub/testing/subscribe_to.rb
|
96
96
|
- lib/pub_sub/version.rb
|
97
97
|
- lib/pubsub_on_rails.rb
|
98
|
+
- pubsub_on_rails-0.0.7.gem
|
99
|
+
- pubsub_on_rails.gemspec
|
98
100
|
homepage: https://github.com/Selleo/pubsub_on_rails
|
99
101
|
licenses:
|
100
102
|
- MIT
|
101
103
|
metadata: {}
|
102
|
-
post_install_message:
|
104
|
+
post_install_message:
|
103
105
|
rdoc_options: []
|
104
106
|
require_paths:
|
105
107
|
- lib
|
@@ -114,9 +116,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
114
116
|
- !ruby/object:Gem::Version
|
115
117
|
version: '0'
|
116
118
|
requirements: []
|
117
|
-
|
118
|
-
|
119
|
-
signing_key:
|
119
|
+
rubygems_version: 3.4.12
|
120
|
+
signing_key:
|
120
121
|
specification_version: 4
|
121
122
|
summary: Opinionated publish-subscribe pattern for ruby and rails
|
122
123
|
test_files: []
|
data/Gemfile.lock
DELETED
@@ -1,64 +0,0 @@
|
|
1
|
-
PATH
|
2
|
-
remote: .
|
3
|
-
specs:
|
4
|
-
pubsub_on_rails (0.0.1)
|
5
|
-
dry-struct
|
6
|
-
wisper (~> 2.0.0)
|
7
|
-
wisper-rspec
|
8
|
-
wisper-sidekiq
|
9
|
-
|
10
|
-
GEM
|
11
|
-
remote: https://rubygems.org/
|
12
|
-
specs:
|
13
|
-
concurrent-ruby (1.1.5)
|
14
|
-
connection_pool (2.2.2)
|
15
|
-
dry-configurable (0.8.2)
|
16
|
-
concurrent-ruby (~> 1.0)
|
17
|
-
dry-core (~> 0.4, >= 0.4.7)
|
18
|
-
dry-container (0.7.0)
|
19
|
-
concurrent-ruby (~> 1.0)
|
20
|
-
dry-configurable (~> 0.1, >= 0.1.3)
|
21
|
-
dry-core (0.4.7)
|
22
|
-
concurrent-ruby (~> 1.0)
|
23
|
-
dry-equalizer (0.2.2)
|
24
|
-
dry-inflector (0.1.2)
|
25
|
-
dry-logic (1.0.0)
|
26
|
-
concurrent-ruby (~> 1.0)
|
27
|
-
dry-core (~> 0.2)
|
28
|
-
dry-equalizer (~> 0.2)
|
29
|
-
dry-struct (1.0.0)
|
30
|
-
dry-core (~> 0.4, >= 0.4.3)
|
31
|
-
dry-equalizer (~> 0.2)
|
32
|
-
dry-types (~> 1.0)
|
33
|
-
ice_nine (~> 0.11)
|
34
|
-
dry-types (1.0.0)
|
35
|
-
concurrent-ruby (~> 1.0)
|
36
|
-
dry-container (~> 0.3)
|
37
|
-
dry-core (~> 0.4, >= 0.4.4)
|
38
|
-
dry-equalizer (~> 0.2, >= 0.2.2)
|
39
|
-
dry-inflector (~> 0.1, >= 0.1.2)
|
40
|
-
dry-logic (~> 1.0)
|
41
|
-
ice_nine (0.11.2)
|
42
|
-
rack (2.0.7)
|
43
|
-
rack-protection (2.0.5)
|
44
|
-
rack
|
45
|
-
redis (4.1.1)
|
46
|
-
sidekiq (5.2.7)
|
47
|
-
connection_pool (~> 2.2, >= 2.2.2)
|
48
|
-
rack (>= 1.5.0)
|
49
|
-
rack-protection (>= 1.5.0)
|
50
|
-
redis (>= 3.3.5, < 5)
|
51
|
-
wisper (2.0.0)
|
52
|
-
wisper-rspec (1.1.0)
|
53
|
-
wisper-sidekiq (1.2.0)
|
54
|
-
sidekiq (>= 4.1)
|
55
|
-
wisper
|
56
|
-
|
57
|
-
PLATFORMS
|
58
|
-
ruby
|
59
|
-
|
60
|
-
DEPENDENCIES
|
61
|
-
pubsub_on_rails!
|
62
|
-
|
63
|
-
BUNDLED WITH
|
64
|
-
1.17.2
|
data/lib/pub_sub/domain.rb
DELETED
@@ -1,25 +0,0 @@
|
|
1
|
-
module PubSub
|
2
|
-
module Domain
|
3
|
-
def method_missing(event_or_method_name, *event_data)
|
4
|
-
if handler_name(event_or_method_name)
|
5
|
-
event_payload = event_data.extract_options!
|
6
|
-
EventTrace.load_from(event_payload.delete(:event_uid))
|
7
|
-
const_get(handler_name(event_or_method_name)).new(event_payload).call!
|
8
|
-
else
|
9
|
-
super
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
13
|
-
def handler_name(event_name)
|
14
|
-
return nil unless event_name.to_s.start_with?(/[a-z_]+__/)
|
15
|
-
"#{event_name.to_s.camelize}Handler"
|
16
|
-
end
|
17
|
-
|
18
|
-
def respond_to_missing?(event_or_method_name, include_private = false)
|
19
|
-
return super unless handler_name(event_or_method_name)
|
20
|
-
const_get(handler_name(event_or_method_name))
|
21
|
-
rescue NameError
|
22
|
-
super
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
data/lib/pub_sub/event_trace.rb
DELETED
@@ -1,18 +0,0 @@
|
|
1
|
-
module PubSub
|
2
|
-
class EventTrace < ActiveSupport::CurrentAttributes
|
3
|
-
EVENT_TRACE_UID_REGEX = /^(?<trace_id>\w+)-(?<trigger_id>\w+)-(?<event_id>\w+)$/
|
4
|
-
private_constant :EVENT_TRACE_UID_REGEX
|
5
|
-
|
6
|
-
attribute :trace_id
|
7
|
-
attribute :last_event_id
|
8
|
-
|
9
|
-
def self.load_from(event_trace_uid)
|
10
|
-
match_data = event_trace_uid&.match(EVENT_TRACE_UID_REGEX)
|
11
|
-
|
12
|
-
if match_data
|
13
|
-
self.trace_id = match_data['trace_id']
|
14
|
-
self.last_event_id = match_data['event_id']
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
data/lib/pub_sub/linter.rb
DELETED
@@ -1,39 +0,0 @@
|
|
1
|
-
class Linter
|
2
|
-
MissingSubscriptions = Class.new(StandardError)
|
3
|
-
|
4
|
-
def initialize(config)
|
5
|
-
@config = config
|
6
|
-
end
|
7
|
-
|
8
|
-
def lint!
|
9
|
-
raise MissingSubscriptions, error_message if missing_subscriptions.present?
|
10
|
-
end
|
11
|
-
|
12
|
-
private
|
13
|
-
|
14
|
-
def error_message
|
15
|
-
"The following subscriptions are missing: \n#{missing_subscriptions.join("\n")}"
|
16
|
-
end
|
17
|
-
|
18
|
-
def missing_subscriptions
|
19
|
-
(handlers_list - subscriptions_list)
|
20
|
-
end
|
21
|
-
|
22
|
-
attr_reader :config
|
23
|
-
|
24
|
-
def subscriptions_list
|
25
|
-
config.flat_map do |domain_name, subscriptions|
|
26
|
-
subscriptions.keys.map do |event_name|
|
27
|
-
"#{domain_name}/#{event_name.sub('__', '_')}"
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
def handlers_list
|
33
|
-
Dir[Rails.root.join('app/event_handlers/*/*.rb')].map do |file_path|
|
34
|
-
file_path.
|
35
|
-
sub("#{Rails.root}/app/event_handlers/", '').
|
36
|
-
sub('_handler.rb', '')
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
data/lib/pub_sub/pure_event.rb
DELETED
@@ -1,19 +0,0 @@
|
|
1
|
-
module PubSub
|
2
|
-
class PureEvent < Dry::Struct
|
3
|
-
include Wisper::Publisher
|
4
|
-
|
5
|
-
def broadcast!
|
6
|
-
broadcast(event_name, attributes_to_broadcast)
|
7
|
-
end
|
8
|
-
|
9
|
-
private
|
10
|
-
|
11
|
-
def attributes_to_broadcast
|
12
|
-
attributes
|
13
|
-
end
|
14
|
-
|
15
|
-
def event_name
|
16
|
-
self.class.name.underscore.sub('/', '__').chomp('_event')
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
@@ -1,49 +0,0 @@
|
|
1
|
-
require 'pub_sub/linter'
|
2
|
-
|
3
|
-
module PubSub
|
4
|
-
mattr_accessor :subscriptions
|
5
|
-
|
6
|
-
class Subscriptions
|
7
|
-
include Singleton
|
8
|
-
|
9
|
-
cattr_accessor :subscriptions_path
|
10
|
-
self.subscriptions_path = 'config/subscriptions.yml'
|
11
|
-
|
12
|
-
def self.load!
|
13
|
-
PubSub.subscriptions = Subscriptions.instance
|
14
|
-
PubSub.subscriptions.register(:all)
|
15
|
-
end
|
16
|
-
|
17
|
-
def self.lint!
|
18
|
-
instance.lint!
|
19
|
-
end
|
20
|
-
|
21
|
-
def lint!
|
22
|
-
Linter.new(config).lint!
|
23
|
-
end
|
24
|
-
|
25
|
-
def initialize
|
26
|
-
@config = YAML.load_file(self.class.subscriptions_path)
|
27
|
-
end
|
28
|
-
|
29
|
-
def register(scope = :all)
|
30
|
-
(scope == :all ? config : config.slice(scope.to_s)).each do |domain_name, subscriptions|
|
31
|
-
subscriptions.each do |event_name, subscription_type|
|
32
|
-
options = {}
|
33
|
-
options[:on] = event_name unless event_name == 'all_events'
|
34
|
-
options[:broadcaster] = subscription_type == 'sync' ? :default : subscription_type.to_sym
|
35
|
-
|
36
|
-
Wisper.subscribe("::#{domain_name.camelize}".constantize, options)
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
def clear!
|
42
|
-
Wisper.clear
|
43
|
-
end
|
44
|
-
|
45
|
-
private
|
46
|
-
|
47
|
-
attr_reader :config
|
48
|
-
end
|
49
|
-
end
|
@@ -1,24 +0,0 @@
|
|
1
|
-
module PubSub
|
2
|
-
module Testing
|
3
|
-
module SubscriptionsHelper
|
4
|
-
def with_subscription_to(*domains)
|
5
|
-
domains.each do |domain|
|
6
|
-
PubSub.subscriptions.register(domain)
|
7
|
-
end
|
8
|
-
yield
|
9
|
-
clear_wisper_subscriptions!
|
10
|
-
end
|
11
|
-
|
12
|
-
def subscribe_logger!
|
13
|
-
PubSub.subscriptions.register(:logging)
|
14
|
-
end
|
15
|
-
module_function :subscribe_logger!
|
16
|
-
|
17
|
-
def clear_wisper_subscriptions!
|
18
|
-
PubSub.subscriptions.clear!
|
19
|
-
subscribe_logger!
|
20
|
-
end
|
21
|
-
module_function :clear_wisper_subscriptions!
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|