omnes 0.1.0 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
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