pubsub_on_rails 0.0.7 → 1.1.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 +146 -93
- 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 +58 -20
- data/lib/pub_sub/event_handler_builder.rb +30 -0
- data/lib/pub_sub/event_with_type.rb +21 -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-1.0.0.gem +0 -0
- data/pubsub_on_rails.gemspec +23 -0
- metadata +23 -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: 665f27ae9c49ccdd0f1b87c8f5b788a558ebee2a22848f3ab04e272528df7e17
|
4
|
+
data.tar.gz: c7107b6d788c1ac9a0069b104b6e4db211a692ef4a6345c613a62fcbcd306859
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4a0f58bbe6dd2ff109934781c580c3ac5b1a711db532a4f0a61f2a005b3b6fee0d4c89391cf53f6d33c0dc65972ccddb34843e551121053641781499fb7d0d7e
|
7
|
+
data.tar.gz: c1895dffa35a785cd78ff6ab62e2e5c5fb10c4937d96b81f7d44cfe63908d101eb30073054ef1dfd17829ebfa72a47f9ee8275e1df813c89ad1d40a7a29c6fb2
|
data/README.md
CHANGED
@@ -16,14 +16,90 @@ This is especially true for applications covering many side effects, integration
|
|
16
16
|
```ruby
|
17
17
|
# Gemfile
|
18
18
|
|
19
|
-
gem 'pubsub_on_rails', '~>
|
19
|
+
gem 'pubsub_on_rails', '~> 1.1.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
|
34
|
+
```
|
35
|
+
|
36
|
+
## Migrating from 0.0.7 to 1.0.0
|
37
|
+
|
38
|
+
1. Update gem to version `1.0.0`
|
39
|
+
|
40
|
+
```ruby
|
41
|
+
# Gemfile
|
42
|
+
|
43
|
+
gem 'pubsub_on_rails', '~> 1.0.0'
|
25
44
|
```
|
26
45
|
|
46
|
+
2. Run Rails Event Store migrations
|
47
|
+
|
48
|
+
**MySQL**
|
49
|
+
```
|
50
|
+
bin/rails generate rails_event_store_active_record:migration
|
51
|
+
bin/rails db:migrate
|
52
|
+
```
|
53
|
+
|
54
|
+
**PostgreSQL**
|
55
|
+
```
|
56
|
+
bin/rails generate rails_event_store_active_record:migration --data-type=jsonb
|
57
|
+
bin/rails db:migrate
|
58
|
+
```
|
59
|
+
|
60
|
+
3. Update initializer to use Rails Event Store Client
|
61
|
+
|
62
|
+
```ruby
|
63
|
+
# config/initializers/pub_sub.rb
|
64
|
+
|
65
|
+
require 'pub_sub/subscriptions_list'
|
66
|
+
|
67
|
+
Rails.configuration.to_prepare do
|
68
|
+
Rails.configuration.event_store = event_store = RailsEventStore::Client.new(
|
69
|
+
repository: RailsEventStoreActiveRecord::EventRepository.new(serializer: RubyEventStore::NULL)
|
70
|
+
)
|
71
|
+
|
72
|
+
PubSub::SubscriptionsList.config_path =
|
73
|
+
Rails.root.join('config/subscriptions.yml')
|
74
|
+
PubSub::SubscriptionsList.load!(event_store)
|
75
|
+
end
|
76
|
+
```
|
77
|
+
|
78
|
+
4. Override `EventWorker` or override `EventHandlerBuilder` if needed
|
79
|
+
|
80
|
+
For example when you want to have different workers for different events:
|
81
|
+
|
82
|
+
```ruby
|
83
|
+
# config/initializers/pub_sub.rb
|
84
|
+
|
85
|
+
PubSub::EventHandlerBuilder.class_eval do
|
86
|
+
def call(event)
|
87
|
+
if async?
|
88
|
+
if class_name.to_s.include?('MyType')
|
89
|
+
SingleThreadEventWorker.perform_in(2.seconds, class_name.to_s, event.event_id)
|
90
|
+
else
|
91
|
+
EventWorker.perform_in(2.seconds, class_name.to_s, event.event_id)
|
92
|
+
end
|
93
|
+
else
|
94
|
+
class_name.new(event).call!
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
```
|
99
|
+
|
100
|
+
5. Add event objects for Rails Event Store streams. Check [Event](README.md#Event) section.
|
101
|
+
6. Update test cases to use new matchers. Check [Testing](README.md#Testing) section.
|
102
|
+
|
27
103
|
## Entities
|
28
104
|
|
29
105
|
There are five entities that are core to PubSub on Rails: domains, events, event publishers, event handlers and subscriptions.
|
@@ -40,7 +116,6 @@ Domain example:
|
|
40
116
|
# app/domains/messaging.rb
|
41
117
|
|
42
118
|
module Messaging
|
43
|
-
extend PubSub::Domain
|
44
119
|
end
|
45
120
|
```
|
46
121
|
|
@@ -61,13 +136,37 @@ Event example:
|
|
61
136
|
```ruby
|
62
137
|
# app/events/ordering/order_created_event.rb
|
63
138
|
|
64
|
-
module
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
139
|
+
module PubSub
|
140
|
+
module Ordering
|
141
|
+
class OrderCreatedEvent < PubSub::EventWithType
|
142
|
+
schema do
|
143
|
+
attribute :order_id, Types::Strict::Integer
|
144
|
+
attribute :customer_id, Types::Strict::Integer
|
145
|
+
attribute :line_items, Types::Strict::Array
|
146
|
+
attribute :total_amount, Types::Strict::Float
|
147
|
+
attribute :comment, Types::Strict::String.optional
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
```
|
153
|
+
|
154
|
+
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).
|
155
|
+
|
156
|
+
Event example:
|
157
|
+
|
158
|
+
```ruby
|
159
|
+
# app/events/rails_event_store/ordering/order_created_event.rb
|
160
|
+
|
161
|
+
module PubSub
|
162
|
+
module Ordering
|
163
|
+
class OrderCreatedEvent < PubSub::EventWithType
|
164
|
+
def stream_names
|
165
|
+
[
|
166
|
+
"order__#{data[:order_id]}"
|
167
|
+
]
|
168
|
+
end
|
169
|
+
end
|
71
170
|
end
|
72
171
|
end
|
73
172
|
```
|
@@ -171,42 +270,41 @@ The recommended RSpec configuration is as follows:
|
|
171
270
|
|
172
271
|
```ruby
|
173
272
|
# spec/support/pub_sub.rb
|
174
|
-
|
273
|
+
|
175
274
|
require 'pub_sub/testing'
|
176
275
|
|
177
276
|
RSpec.configure do |config|
|
178
|
-
config.include
|
179
|
-
config.include PubSub::Testing::SubscriptionsHelper
|
277
|
+
config.include PubSub::Testing::RailsEventStore
|
180
278
|
config.include PubSub::Testing::EventDataHelper
|
181
279
|
|
182
|
-
config.
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
domain_name = example.metadata[:described_class].to_s.underscore
|
188
|
-
PubSub.subscriptions.register(domain_name)
|
280
|
+
config.around(:each, in_memory_res_client: true) do |example|
|
281
|
+
current_event_store = Rails.configuration.event_store
|
282
|
+
Rails.configuration.event_store = RubyEventStore::Client.new(
|
283
|
+
repository: RubyEventStore::InMemoryRepository.new
|
284
|
+
)
|
189
285
|
example.run
|
190
|
-
|
286
|
+
Rails.configuration.event_store = current_event_store
|
191
287
|
end
|
192
288
|
end
|
193
289
|
```
|
194
290
|
|
291
|
+
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).
|
292
|
+
|
195
293
|
### Testing subscription
|
196
294
|
|
197
295
|
Testing subscription is as easy as telling what domains should subscribe to what event in what way.
|
198
|
-
|
296
|
+
|
199
297
|
Example:
|
200
298
|
|
201
299
|
```ruby
|
202
|
-
RSpec.describe Messaging
|
300
|
+
RSpec.describe Messaging do
|
203
301
|
it { is_expected.to subscribe_to(:ordering__order_created).asynchronously }
|
204
302
|
end
|
205
303
|
```
|
206
304
|
|
207
305
|
### Testing publishers
|
208
306
|
|
209
|
-
To test publisher it is crucial to test if event was emitted
|
307
|
+
To test publisher it is crucial to test if event was emitted under certain conditions (if any).
|
210
308
|
|
211
309
|
Example:
|
212
310
|
|
@@ -217,20 +315,21 @@ RSpec.describe Order do
|
|
217
315
|
customer = create(:customer)
|
218
316
|
line_items = create_list(:line_item, 2)
|
219
317
|
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
318
|
+
Order.create(
|
319
|
+
customer: customer,
|
320
|
+
total_amount: 100.99,
|
321
|
+
comment: 'Small order',
|
322
|
+
line_items: line_items
|
323
|
+
)
|
324
|
+
|
325
|
+
expect(event_store).to have_published(
|
326
|
+
an_event(PubSub::Ordering::OrderCreatedEvent).with_data(
|
327
|
+
order_id: fetch_next_id_for(Order),
|
328
|
+
total_amount: 100.99,
|
329
|
+
comment: 'Small order',
|
330
|
+
line_items: line_items
|
226
331
|
)
|
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
|
-
)
|
332
|
+
).in_stream('ordering__order_created')
|
234
333
|
end
|
235
334
|
end
|
236
335
|
end
|
@@ -269,19 +368,6 @@ module Messaging
|
|
269
368
|
end
|
270
369
|
end
|
271
370
|
```
|
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
371
|
|
286
372
|
### Subscriptions linting
|
287
373
|
|
@@ -334,44 +420,21 @@ logging:
|
|
334
420
|
all_events: sync
|
335
421
|
```
|
336
422
|
|
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
423
|
## Payload verification
|
365
424
|
|
366
|
-
Every time event is emitted, its payload is supplied to corresponding event class and is verified.
|
425
|
+
Every time event is emitted, its payload is supplied to corresponding `Dry::Struct` event class and is verified.
|
367
426
|
This ensures that whenever we emit event we can be sure its payload is matching specification.
|
368
427
|
|
369
428
|
Example:
|
370
429
|
|
371
430
|
```ruby
|
372
|
-
module
|
373
|
-
|
374
|
-
|
431
|
+
module PubSub
|
432
|
+
module Accounts
|
433
|
+
class PersonCreatedEvent < PubSub::EventWithType
|
434
|
+
schema do
|
435
|
+
attribute :person_id, Types::Strict::Integer
|
436
|
+
end
|
437
|
+
end
|
375
438
|
end
|
376
439
|
end
|
377
440
|
```
|
@@ -401,7 +464,7 @@ end
|
|
401
464
|
|
402
465
|
## Automatic event payload population
|
403
466
|
|
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.
|
467
|
+
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
468
|
|
406
469
|
```ruby
|
407
470
|
# app/models/oriering/order.rb
|
@@ -423,16 +486,6 @@ module Ordering
|
|
423
486
|
end
|
424
487
|
```
|
425
488
|
|
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
489
|
# TODO
|
437
490
|
|
438
491
|
- 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,82 @@
|
|
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?
|
39
|
+
|
40
|
+
"#{domain}__#{event_name}"
|
41
|
+
end
|
20
42
|
|
21
|
-
def
|
22
|
-
|
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
|
23
49
|
end
|
24
50
|
|
25
51
|
# rubocop:disable Metrics/MethodLength
|
26
52
|
def full_payload
|
27
|
-
|
28
|
-
result[attribute_name] = PayloadAttribute.new(
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
53
|
+
attribute_names.each_with_object({}) do |attribute_name, result|
|
54
|
+
result[attribute_name] = PayloadAttribute.new(
|
55
|
+
attribute_name, explicit_payload, context
|
56
|
+
).get
|
57
|
+
rescue PayloadAttribute::CannotEvaluate => e
|
58
|
+
next if schema.key(attribute_name).default?
|
59
|
+
|
60
|
+
raise(
|
61
|
+
EventPayloadArgumentMissing,
|
62
|
+
"Event [#{event_class.name}] expects [#{attribute_name}] " \
|
63
|
+
"payload attribute to be either exposed as [#{e.message}] method in emitting object " \
|
64
|
+
'or provided as argument'
|
65
|
+
)
|
40
66
|
end
|
41
67
|
end
|
42
68
|
# rubocop:enable Metrics/MethodLength
|
69
|
+
|
70
|
+
def attribute_names
|
71
|
+
(abstract_event_class || event_class.instance_variable_get(:@schema_validator)).attribute_names
|
72
|
+
end
|
73
|
+
|
74
|
+
def schema
|
75
|
+
(abstract_event_class || event_class.instance_variable_get(:@schema_validator)).schema
|
76
|
+
end
|
77
|
+
|
78
|
+
def event_store
|
79
|
+
Rails.configuration.event_store
|
80
|
+
end
|
43
81
|
end
|
44
82
|
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,21 @@
|
|
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.instance_variable_get(:@schema_validator).new(
|
8
|
+
data.deep_symbolize_keys
|
9
|
+
).attributes
|
10
|
+
)
|
11
|
+
end
|
12
|
+
|
13
|
+
def stream_names
|
14
|
+
[]
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.schema(&block)
|
18
|
+
instance_variable_set(:@schema_validator, Class.new(Dry::Struct, &block))
|
19
|
+
end
|
20
|
+
end
|
21
|
+
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
|
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:
|
4
|
+
version: 1.1.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-12-11 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,35 @@ 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-1.0.0.gem
|
100
|
+
- pubsub_on_rails.gemspec
|
98
101
|
homepage: https://github.com/Selleo/pubsub_on_rails
|
99
102
|
licenses:
|
100
103
|
- MIT
|
101
104
|
metadata: {}
|
102
|
-
post_install_message:
|
105
|
+
post_install_message:
|
103
106
|
rdoc_options: []
|
104
107
|
require_paths:
|
105
108
|
- lib
|
@@ -114,9 +117,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
114
117
|
- !ruby/object:Gem::Version
|
115
118
|
version: '0'
|
116
119
|
requirements: []
|
117
|
-
|
118
|
-
|
119
|
-
signing_key:
|
120
|
+
rubygems_version: 3.4.12
|
121
|
+
signing_key:
|
120
122
|
specification_version: 4
|
121
123
|
summary: Opinionated publish-subscribe pattern for ruby and rails
|
122
124
|
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
|