omnes 0.1.0 → 0.2.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 68a21237b0a6749b982b68b732e134e79198654e92bc92824053c103280dd2bc
4
- data.tar.gz: a282239e0f4eb501e3eb113066f1753ebfc859364cddbf07fc9200efbcad6bad
3
+ metadata.gz: 4d69bcb6ece79550d662ea1ed7cf734cc47940a39d432c58c849f8c15415472a
4
+ data.tar.gz: 459ff5514f310cc52492bb2fabc6bf8786e3a9119f760288aeef6329b609b4d3
5
5
  SHA512:
6
- metadata.gz: 929b7a25696e1eaeb3ece983d46ccada028530f859483ebf80a65ce0b603360696a7b8304dfb8d801d543f26206024163154baa6223991a79f06fa58329cf2eb
7
- data.tar.gz: fe5a4306002b44153992db90f13c54b76b211cfb7eb22975dfe2eb01a8fafd04bd7421dad414e2f82b38c5af00b0c466f52a624f0c0ce27f3ac1e7e711dbb382
6
+ metadata.gz: f395f3f775f8c44d54e17ad8502b0bf40c201edd861170242ac1ab29db90c2ace38273aceea5a257ed5e2910dcaca43370feec8e9563090847febe039ce99805
7
+ data.tar.gz: d2be4901c0fcc8bdd7bd8f1a1887c32445877300f1bf46f1396e7c16312c221f9c295fef1ee58756320a1be20c69f2a0e4dc4d0048451043998f51d89bb2c68c
@@ -12,7 +12,7 @@ jobs:
12
12
  strategy:
13
13
  fail-fast: false
14
14
  matrix:
15
- ruby-version: ['2.7', '3.0', '3.1']
15
+ ruby-version: ['2.5', '2.6', '2.7', '3.0', '3.1']
16
16
 
17
17
  steps:
18
18
  - name: Checkout repository
data/.gitignore CHANGED
@@ -6,6 +6,7 @@
6
6
  /pkg/
7
7
  /spec/reports/
8
8
  /tmp/
9
+ Gemfile.lock
9
10
 
10
11
  # rspec failure tracking
11
12
  .rspec_status
data/.rubocop.yml CHANGED
@@ -1,5 +1,5 @@
1
1
  AllCops:
2
- TargetRubyVersion: 2.7
2
+ TargetRubyVersion: 2.5
3
3
  NewCops: enable
4
4
  SuggestExtensions: false
5
5
 
@@ -29,7 +29,7 @@ Layout/LineLength:
29
29
  Max: 120
30
30
 
31
31
  Naming/MethodName:
32
- IgnoredPatterns:
32
+ AllowedPatterns:
33
33
  - '.*Type'
34
34
 
35
35
  Lint/ConstantDefinitionInBlock:
data/CHANGELOG.md CHANGED
@@ -6,6 +6,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.2.2] - 2022-05-03
10
+
11
+ ### Added
12
+ - Support Ruby 2.5 & 2.6 [#6](https://github.com/nebulab/omnes/pull/6).
13
+
14
+ ## [0.2.1] - 2022-04-19
15
+
16
+ ### Added
17
+ - Added `Omnes::Bus#clear` for autoloading [#4](https://github.com/nebulab/omnes/pull/4).
18
+
19
+ ### Changed
20
+ - Fix re-adding autodiscovered subscriptions on subsequent calls [#5](https://github.com/nebulab/omnes/pull/5).
21
+
22
+ ## [0.2.0] - 2022-04-15
23
+
24
+ ### Added
25
+ - Be able to fetch subscriptions by id from the bus [#1](https://github.com/nebulab/omnes/pull/1).
26
+ - Use ad-hoc configuration system (and make Omnes zero-deps) [#2](https://github.com/nebulab/omnes/pull/2).
27
+ - Bind a publication context to subscriptions [#3](https://github.com/nebulab/omnes/pull/3).
28
+
9
29
  ## [0.1.0] - 2022-03-23
10
30
 
11
- [Unreleased]: https://github.com/nebulab/omnes/compare/v0.1.0...HEAD
31
+ [Unreleased]: https://github.com/nebulab/omnes/compare/v0.2.2...HEAD
32
+ [0.2.1]: https://github.com/nebulab/omnes/compare/v0.2.1...v0.2.2
33
+ [0.2.1]: https://github.com/nebulab/omnes/compare/v0.2.0...v0.2.1
34
+ [0.2.0]: https://github.com/nebulab/omnes/compare/v0.1.0...v0.2.0
35
+ [0.1.0]: https://github.com/nebulab/omnes/releases/tag/v0.1.0
data/README.md CHANGED
@@ -37,7 +37,7 @@ The following examples will use the direct `Omnes::Bus` instance. The only
37
37
  difference for the mixing use case is that the methods are directly called in
38
38
  the including instance.
39
39
 
40
- ### Registering events
40
+ ## Registering events
41
41
 
42
42
  Before being able to work with a given event, its name (which must be a
43
43
  `Symbol`) must be registered:
@@ -46,9 +46,9 @@ Before being able to work with a given event, its name (which must be a
46
46
  bus.register(:order_created)
47
47
  ```
48
48
 
49
- ### Publishing events
49
+ ## Publishing events
50
50
 
51
- An event can be anything responding to a method `:name`, which must match with a
51
+ An event can be anything responding to a method `:omnes_event_name`, which must match with a
52
52
  registered name.
53
53
 
54
54
  Typically, there're two main ways to generate events.
@@ -73,8 +73,7 @@ features, such as event persistence, can't be reliably built on top of them.
73
73
 
74
74
  You can also publish an instance of a class including
75
75
  [`Omnes::Event`](lib/omnes/event.rb). The only fancy thing it provides is an
76
- OOTB event name generated based on the class name. In fact, you can use
77
- anything responding to `#omnes_event_name`.
76
+ OOTB event name generated based on the class name.
78
77
 
79
78
  ```ruby
80
79
  class OrderCreatedEvent
@@ -105,7 +104,7 @@ Omnes.config.event.name_builder = event_name_as_class
105
104
  Instance-backed events provide a well-defined structure, and other features,
106
105
  like event persistence, can be added on top of them.
107
106
 
108
- ### Subscribing to events
107
+ ## Subscribing to events
109
108
 
110
109
  You can subscribe to a specific event to run some code whenever it's published.
111
110
  The event is yielded to the subscription block:
@@ -150,7 +149,7 @@ bus.subscribe(:order_created, OrderCreationEmailSubscription.new)
150
149
  However, see [Event subscribers](#event-subscribers) section bellow for a more powerful way
151
150
  to define standalone event handlers.
152
151
 
153
- #### Global subscriptions
152
+ ### Global subscriptions
154
153
 
155
154
  You can also create a subscription that will run for all events:
156
155
 
@@ -163,27 +162,43 @@ class LogEventsSubscription
163
162
  end
164
163
 
165
164
  def call(event)
166
- logger.info("Event #{event.name} published")
165
+ logger.info("Event #{event.omnes_event_name} published")
167
166
  end
168
167
  end
169
168
 
170
169
  bus.subscribe_to_all(LogEventsSubscription.new)
171
170
  ```
172
171
 
173
- #### Custom matcher subscriptions
172
+ ### Custom matcher subscriptions
174
173
 
175
174
  Custom event matchers can be defined. A matcher is something responding to
176
175
  `#call` and taking the event as an argument. It must return `true` or `false`
177
176
  to match or ignore the event.
178
177
 
179
178
  ```ruby
180
- ORDER_EVENTS_MATCHER = ->(event) { event.name.start_with?(:order) }
179
+ ORDER_EVENTS_MATCHER = ->(event) { event.omnes_event_name.start_with?(:order) }
181
180
 
182
181
  bus.subscribe_with_matcher(ORDER_EVENTS_MATCHER) do |event|
183
182
  # ...
184
183
  end
185
184
  ```
186
185
 
186
+ ### Referencing subscriptions
187
+
188
+ For all subscription methods we've seen, an `Omnes::Subscription` instance is
189
+ returned. Holding that reference can be useful for [debugging](#debugging) and
190
+ [testing](#testing) purposes.
191
+
192
+ Often though, you won't have the reference at hand when you need it.
193
+ Thankfully, you can provide a subscription identifier on subscription time and
194
+ use it later to fetch the subscription instance from the bus. A subscription
195
+ identifier needs to be a `Symbol`:
196
+
197
+ ```ruby
198
+ bus.subscribe(:order_created, OrderCreationEmailSubscription.new, id: :order_created_email)
199
+ subscription = bus.subscription(:send_confirmation_email)
200
+ ```
201
+
187
202
  ## Event subscribers
188
203
 
189
204
  Events subscribers offer a way to define event subscriptions from a custom
@@ -207,7 +222,11 @@ class OrderCreationEmailSubscriber
207
222
  service.send(number: event.number, email: event.user_email)
208
223
  end
209
224
  end
225
+ ```
226
+
227
+ You add the subscriptions by calling the `#subscribe_to` method on an instance:
210
228
 
229
+ ```ruby
211
230
  OrderCreationEmailSubscriber.new.subscribe_to(bus)
212
231
  ```
213
232
 
@@ -227,7 +246,7 @@ class LogEventsSubscriber
227
246
  end
228
247
 
229
248
  def log_event(event)
230
- logger.info("Event #{event.name} published")
249
+ logger.info("Event #{event.omnes_event_name} published")
231
250
  end
232
251
  end
233
252
  ```
@@ -246,6 +265,21 @@ class OrderSubscriber
246
265
  end
247
266
  ```
248
267
 
268
+ Likewise, you can provide [identifiers to reference
269
+ subscriptions](#referencing-subscriptions):
270
+
271
+ ```ruby
272
+ handle :order_created, with: :send_confirmation_email, id: :order_creation_email_subscriber
273
+ ```
274
+
275
+ As you can subscribe multiple instances of a subscriber to the same bus, you
276
+ might need to create a different identifier for each of them. For those cases,
277
+ you can pass a lambda taking the subscriber instance:
278
+
279
+ ```ruby
280
+ handle :order_created, with: :send_confirmation_email, id: ->(subscriber) { :"#{subscriber.id}_order_creation_email_subscriber" }
281
+ ```
282
+
249
283
  ### Autodiscovering event handlers
250
284
 
251
285
  You can let the event handlers to be automatically discovered.You need to
@@ -292,6 +326,12 @@ class OrderCreationEmailSubscriber
292
326
  end
293
327
  ```
294
328
 
329
+ The strategy can also be globally set:
330
+
331
+ ```ruby
332
+ Omnes.config.subscriber.autodiscover_strategy = AUTODISCOVER_STRATEGY
333
+ ```
334
+
295
335
  ### Adapters
296
336
 
297
337
  Subscribers are not limited to use a method as event handler. They can interact
@@ -389,7 +429,8 @@ Omnes.config.subscriber.adapter.active_job.serializer = :serialized_payload.to_p
389
429
  #### Custom adapters
390
430
 
391
431
  Custom adapters can be built. They need to implement a method `#call` taking
392
- the instance of `Omnes::Subscriber` and the event.
432
+ the instance of `Omnes::Subscriber`, the event and, optionally, the publication
433
+ context (see [debugging subscriptions](#subscription)).
393
434
 
394
435
  Here's a custom adapter executing a subscriber method in a different
395
436
  thread (we add an extra argument for the method name, and we partially apply it
@@ -402,7 +443,6 @@ end
402
443
 
403
444
  class OrderCreationEmailSubscriber
404
445
  include Omnes::Subscriber
405
- include Sidekiq::Job
406
446
 
407
447
  handle :order_created, with: THREAD_ADAPTER.curry[:order_created]
408
448
 
@@ -413,8 +453,8 @@ end
413
453
  ```
414
454
 
415
455
  Alternatively, adapters can be curried and only take the instance as an
416
- argument, returning a one-argument callable taking the event. For instance, we
417
- could also have defined the thread adapter like this:
456
+ argument, returning a callable taking the event. For instance, we could also
457
+ have defined the thread adapter like this:
418
458
 
419
459
  ```ruby
420
460
  class ThreadAdapter
@@ -436,20 +476,29 @@ handle :order_created, with: ThreadAdapter.new(:order_created)
436
476
  # ...
437
477
  ```
438
478
 
439
- ## Debugging
440
-
441
- ### Unsubscribing
479
+ ## Unsubscribing & clearing
442
480
 
443
- When you create a subscription, an instance of
444
- [`Omnes::Subscription`](lib/omnes/subscription.rb) is returned (or an array for
445
- `Omnes::Subscriber`). It can be used to unsubscribe it in case you need to
446
- debug some behavior.
481
+ You can unsubscribe a given subscription by passing its
482
+ [reference](#referencing-subscriptions) to `Omnes::Bus#unsubscribe` (see how to
483
+ [reference subscriptions](#referencing-subscriptions)):
447
484
 
448
485
  ```ruby
449
486
  subscription = bus.subscribe(:order_created, OrderCreationEmailSubscription.new)
450
487
  bus.unsubscribe(subscription)
451
488
  ```
452
489
 
490
+ Sometimes you might need to leave your bus in a pristine state, with no events
491
+ registered or active subscriptions. That can be useful for autoloading in
492
+ development:
493
+
494
+ ```ruby
495
+ bus.clear
496
+ bus.registry.event_names # => []
497
+ bus.subscriptions # => []
498
+ ```
499
+
500
+ ## Debugging
501
+
453
502
  ### Registration
454
503
 
455
504
  Whenever you register an event, you get back an [`Omnes::Registry::Registration`](lib/omnes/registry.rb)
@@ -478,10 +527,10 @@ When you publish an event, you get back an
478
527
  attributes that allow observing what happened:
479
528
 
480
529
  - `#event` contains the event instance that has been published.
481
- - `#caller_location` refers to the publication caller.
482
- - `#time` is the time stamp for the publication.
483
530
  - `#executions` contains an array of
484
531
  `Omnes::Execution`(lib/omnes/execution.rb). Read more below.
532
+ - `#context` is an instance of
533
+ [`Omnes::PublicationContext`](lib/omnes/publication_context.rb).
485
534
 
486
535
  `Omnes::Execution` represents a subscription individual execution. It contains
487
536
  the following attributes:
@@ -491,6 +540,40 @@ the following attributes:
491
540
  - `#benchmark` of the operation.
492
541
  - `#time` is the time where the execution started.
493
542
 
543
+ `Omnes::PublicationContext` represents the shared context for all triggered
544
+ executions. See [Subscription][#subscription] for details.
545
+
546
+ ### Subscription
547
+
548
+ If your subscription block or callable object takes a second argument, it'll
549
+ contain an instance of an
550
+ [`Omnes::PublicationContext`](lib/omnes/publication_context.rb). It allows you
551
+ to inspect what triggered a given execution from within that execution code. It
552
+ contains:
553
+
554
+ - `#caller_location` refers to the publication caller.
555
+ - `#time` is the time stamp for the publication.
556
+
557
+ ```ruby
558
+ class OrderCreationEmailSubscriber
559
+ include Omnes::Subscriber
560
+
561
+ handle :order_created, with: :send_confirmation_email
562
+
563
+ def send_confirmation_email(event, publication_context)
564
+ # debugging
565
+ abort(publication_context.caller_location.inspect)
566
+
567
+ OrderCreationEmail.send(number: event.number, email: event.user_email)
568
+ end
569
+ end
570
+ ```
571
+
572
+ In case you're developing your own async adapter, you can call `#serialized` on
573
+ an instance of `Omnes::PublicationContext` to get a serialized version of it.
574
+ It'll return a `Hash` with `"caller_location"` and `"time"` keys, and the
575
+ respective `String` representations as values.
576
+
494
577
  ## Testing
495
578
 
496
579
  Ideally, you wouldn't need big setups to test your event-driven behavior. You
@@ -521,11 +604,30 @@ end
521
604
  bus.publish(:order_deleted, number: order.number) # `deletion_subscription` will run
522
605
  ```
523
606
 
524
- Remember that the array of created subscriptions is returned on `Omnes::Subscriber#subscribe_to`.
607
+ Remember that you can get previous [subscription
608
+ references](#referencing-subscriptions) thanks to
609
+ subscription identifiers.
525
610
 
526
611
  There's also a specialized `Omnes::Bus#performing_nothing` method that runs no
527
612
  subscriptions for the duration of the block.
528
613
 
614
+ ## Configuration
615
+
616
+ We've seen the relevant configurable settings in the corresponding sections.
617
+ You can also access the configuration in the habitual block syntax:
618
+
619
+ ```ruby
620
+ Omnes.configure do |config|
621
+ config.subscriber.adapter.sidekiq.serializer = :serialized_payload.to_proc
622
+ end
623
+ ```
624
+
625
+ Finally, nested settings can also be set directly from the affected class. E.g.:
626
+
627
+ ```ruby
628
+ Omnes::Subscriber::Adapter::Sidekiq.config.serializer = :serialized_payload.to_proc
629
+ ```
630
+
529
631
  ## Recipes
530
632
 
531
633
  ### Rails
@@ -538,9 +640,14 @@ require "omnes"
538
640
  Omnes.config.subscriber.autodiscover = true
539
641
 
540
642
  Bus = Omnes::Bus.new
541
- Bus.register(:order_created)
542
643
 
543
- OrderCreationEmailSubscriber.new.subscribe_to(Bus)
644
+ Rails.application.config.to_prepare do
645
+ Bus.clear
646
+
647
+ Bus.register(:order_created)
648
+
649
+ OrderCreationEmailSubscriber.new.subscribe_to(Bus)
650
+ end
544
651
  ```
545
652
 
546
653
  We can define `OrderCreationEmailSubscriber` in
data/lib/omnes/bus.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "omnes/publication"
4
+ require "omnes/publication_context"
4
5
  require "omnes/registry"
5
6
  require "omnes/subscription"
6
7
  require "omnes/unstructured_event"
@@ -22,8 +23,8 @@ module Omnes
22
23
  # bus.register(:foo)
23
24
  # ```
24
25
  #
25
- # An event can be anything responding to a method `:name` which, needless to
26
- # say, must match with a registered name.
26
+ # An event can be anything responding to a method `:omes_event_name` which,
27
+ # needless to say, must match with a registered name.
27
28
  #
28
29
  # Typically, there're two main ways to generate events.
29
30
  #
@@ -104,6 +105,21 @@ module Omnes
104
105
  #
105
106
  # bus.subscribe_with_matcher(matcher, MySubscription.new)
106
107
  # ```
108
+ #
109
+ # For all previous subscription methods, a subscription object is returned.
110
+ # You can supply a subscription id to it to be able to fetch it from the bus
111
+ # later on:
112
+ #
113
+ # ```
114
+ # subscription = bus.subscribe(:foo, MySubscription.new, id: :foo_sub)
115
+ # bus.subscription(:foo_sub) == subscription #=> true
116
+ # ```
117
+ #
118
+ # A subscription can be referenced when you want to unsubscribe:
119
+ #
120
+ # ```
121
+ # bus.unsubscribe(subscription)
122
+ # ```
107
123
  class Bus
108
124
  # @api private
109
125
  def self.EventType(value, **payload)
@@ -168,13 +184,13 @@ module Omnes
168
184
  publication_time = Time.now.utc
169
185
  event = self.class.EventType(event, **payload)
170
186
  registry.check_event_name(event.omnes_event_name)
171
- executions = execute_subscriptions_for_event(event)
187
+ publication_context = PublicationContext.new(caller_location: caller_location, time: publication_time)
188
+ executions = execute_subscriptions_for_event(event, publication_context)
172
189
 
173
190
  Publication.new(
174
191
  event: event,
175
192
  executions: executions,
176
- caller_location: caller_location,
177
- time: publication_time
193
+ context: publication_context
178
194
  )
179
195
  end
180
196
 
@@ -182,37 +198,42 @@ module Omnes
182
198
  #
183
199
  # @param event_name [Symbol] Name of the event
184
200
  # @param callable [#call] Subscription callback taking the event
201
+ # @param id [Symbol] Unique identifier for the subscription
185
202
  # @yield [event] Subscription callback if callable is not given
186
203
  #
187
204
  # @return [Omnes::Subscription]
188
205
  #
189
206
  # @raise [Omnes::UnknownEventError] When event name has not been registered
190
- def subscribe(event_name, callable = nil, &block)
207
+ def subscribe(event_name, callable = nil, id: Subscription.random_id, &block)
191
208
  registry.check_event_name(event_name)
192
209
 
193
- subscribe_with_matcher(Subscription::SINGLE_EVENT_MATCHER.curry[event_name], callable, &block)
210
+ subscribe_with_matcher(Subscription::SINGLE_EVENT_MATCHER.curry[event_name], callable, id: id, &block)
194
211
  end
195
212
 
196
213
  # Adds a subscription for all events
197
214
  #
198
215
  # @param callable [#call] Subscription callback taking the event
216
+ # @param id [Symbol] Unique identifier for the subscription
199
217
  # @yield [event] Subscription callback if callable is not given
200
218
  #
201
219
  # @return [Omnes::Subscription]
202
- def subscribe_to_all(callable = nil, &block)
203
- subscribe_with_matcher(Subscription::ALL_EVENTS_MATCHER, callable, &block)
220
+ def subscribe_to_all(callable = nil, id: Subscription.random_id, &block)
221
+ subscribe_with_matcher(Subscription::ALL_EVENTS_MATCHER, callable, id: id, &block)
204
222
  end
205
223
 
206
224
  # Adds a subscription with given matcher
207
225
  #
208
226
  # @param matcher [#call] Callable taking the event and returning a boolean
209
227
  # @param callable [#call] Subscription callback taking the event
228
+ # @param id [Symbol] Unique identifier for the subscription
210
229
  # @yield [event] Subscription callback if callable is not given
211
230
  #
212
231
  # @return [Omnes::Subscription]
213
- def subscribe_with_matcher(matcher, callable = nil, &block)
232
+ def subscribe_with_matcher(matcher, callable = nil, id: Subscription.random_id, &block)
233
+ raise DuplicateSubscriptionIdError.new(id: id, bus: self) if subscription(id)
234
+
214
235
  callback = callable || block
215
- Subscription.new(matcher: matcher, callback: callback).tap do |subscription|
236
+ Subscription.new(matcher: matcher, callback: callback, id: id).tap do |subscription|
216
237
  @subscriptions << subscription
217
238
  end
218
239
  end
@@ -257,11 +278,32 @@ module Omnes
257
278
  performing_only(&block)
258
279
  end
259
280
 
281
+ # Fetch a subscription by its identifier
282
+ #
283
+ # @param id [Symbol] Subscription identifier
284
+ #
285
+ # @return [Omnes::Subscription]
286
+ def subscription(id)
287
+ subscriptions.find { |subscription| subscription.id == id }
288
+ end
289
+
290
+ # Clears all registered events and subscriptions
291
+ #
292
+ # Useful for code reloading.
293
+ #
294
+ # @return [Omnes::Bus]
295
+ def clear
296
+ tap do
297
+ @subscriptions = []
298
+ @registry = Registry.new
299
+ end
300
+ end
301
+
260
302
  private
261
303
 
262
- def execute_subscriptions_for_event(event)
304
+ def execute_subscriptions_for_event(event, publication_context)
263
305
  subscriptions_for_event(event).map do |subscription|
264
- subscription.(event)
306
+ subscription.(event, publication_context)
265
307
  end
266
308
  end
267
309
 
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Omnes
4
+ # Ad-hoc configurable behavior for Omnes
5
+ #
6
+ # Example:
7
+ #
8
+ # ```
9
+ # Omnes.configure do |config|
10
+ # config.event.name_builder = MY_NAME_BUILDER
11
+ # end
12
+ # ```
13
+ #
14
+ # or
15
+ #
16
+ # ```
17
+ # Omnes::Event.config.name_builder = MY_NAME_BUILDER
18
+ # ```
19
+ module Configurable
20
+ # Class where readers and writers are defined
21
+ class Config
22
+ # @api private
23
+ attr_reader :settings
24
+
25
+ # @api private
26
+ def initialize
27
+ @_mutex = Mutex.new
28
+ @settings = {}
29
+ end
30
+
31
+ # @api private
32
+ def add_setting(name, default)
33
+ @_mutex.synchronize do
34
+ @settings[name] = default
35
+ define_setting_reader(name)
36
+ define_setting_writter(name)
37
+ end
38
+ end
39
+
40
+ # @api private
41
+ def add_nesting(constant, name = default_nesting_name(constant))
42
+ @_mutex.synchronize do
43
+ define_nesting_reader(constant, name)
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ def define_setting_reader(name)
50
+ define_singleton_method(name) { @settings[name] }
51
+ end
52
+
53
+ def define_setting_writter(name)
54
+ define_singleton_method(:"#{name}=") do |value|
55
+ @_mutex.synchronize do
56
+ @settings[name] = value
57
+ end
58
+ end
59
+ end
60
+
61
+ def define_nesting_reader(constant, name)
62
+ define_singleton_method(name) { constant.config }
63
+ end
64
+ end
65
+
66
+ # @api private
67
+ def self.extended(klass)
68
+ klass.instance_variable_set(:@config, Config.new)
69
+ end
70
+
71
+ # Returns the configuration class
72
+ #
73
+ # Use this class to access readers and writers for the defined settings or
74
+ # nested configurations
75
+ #
76
+ # @return [Configurable::Config]
77
+ def config
78
+ @config
79
+ end
80
+
81
+ # Yields the configuration class
82
+ #
83
+ # @see #config
84
+ def configure
85
+ yield @config
86
+ end
87
+
88
+ # @api private
89
+ def setting(name, default:)
90
+ config.add_setting(name, default)
91
+ end
92
+
93
+ # @api private
94
+ def nest_config(constant, name: default_nesting_name(constant))
95
+ config.add_nesting(constant, name)
96
+ end
97
+
98
+ private
99
+
100
+ def default_nesting_name(constant)
101
+ constant.name
102
+ .split("::")
103
+ .last
104
+ .gsub(/([[:alpha:]])([[:upper:]])/, '\1_\2')
105
+ .downcase
106
+ end
107
+ end
108
+ end
data/lib/omnes/errors.rb CHANGED
@@ -18,7 +18,7 @@ module Omnes
18
18
  def default_message
19
19
  <<~MSG
20
20
  '#{event_name}' event is not registered.
21
- #{suggestions_message}
21
+ #{suggestions_message if defined?(DidYouMean::PlainFormatter)}
22
22
 
23
23
  All known events are:
24
24
 
@@ -98,4 +98,42 @@ module Omnes
98
98
  MSG
99
99
  end
100
100
  end
101
+
102
+ # Raised when given subscription id is already in use
103
+ class DuplicateSubscriptionIdError < Error
104
+ attr_reader :id, :bus
105
+
106
+ def initialize(id:, bus:)
107
+ @id = id
108
+ @bus = bus
109
+ super(default_message)
110
+ end
111
+
112
+ private
113
+
114
+ def default_message
115
+ <<~MSG
116
+ #{id} has already been used as a subscription identifier
117
+ MSG
118
+ end
119
+ end
120
+
121
+ # Raised when trying to set an invalid subscription identifier
122
+ class InvalidSubscriptionNameError < Error
123
+ attr_reader :id
124
+
125
+ def initialize(id:)
126
+ @id = id
127
+ super(default_message)
128
+ end
129
+
130
+ private
131
+
132
+ def default_message
133
+ <<~MSG
134
+ #{id.inspect} is not a valid subscription identifier. Only symbols are
135
+ #allowed.
136
+ MSG
137
+ end
138
+ end
101
139
  end