u-observers 2.1.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +110 -7
- data/README.pt-BR.md +110 -7
- data/lib/micro/observers.rb +2 -0
- data/lib/micro/observers/broadcast.rb +73 -0
- data/lib/micro/observers/for/active_model.rb +1 -1
- data/lib/micro/observers/set.rb +24 -102
- data/lib/micro/observers/subscribers.rb +110 -0
- data/lib/micro/observers/utils.rb +11 -1
- data/lib/micro/observers/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e7da28034b714bb64baf033cf43705ed585fa007659964dc36bec440436292f0
|
4
|
+
data.tar.gz: cd2b40e92cdfd12708ab36f1ed8a7726ecfbc2c6e96223ca090aedc7758f44f8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 51be920a8f6f0123e0a94fbf180fb85517ddccb107f729f6eaa38ac840e14a9d207cb82e8f3f76e018e3836f4a7cc0b47b7ea6320ffd0615d22df49c6b693d73
|
7
|
+
data.tar.gz: 66c73487b724149f640e096b24a1e9ecf5e5d8780e9679f1d182823eda377e8b289ed88c98d73f6a81147e945eba7a8ce24278244ab50c1a191ebba1b912e265
|
data/README.md
CHANGED
@@ -42,6 +42,10 @@ Because of this issue, I decided to create a gem that encapsulates the pattern w
|
|
42
42
|
- [Using a callable as an observer](#using-a-callable-as-an-observer)
|
43
43
|
- [Calling the observers](#calling-the-observers)
|
44
44
|
- [Notifying observers without marking them as changed](#notifying-observers-without-marking-them-as-changed)
|
45
|
+
- [Defining observers that execute only once](#defining-observers-that-execute-only-once)
|
46
|
+
- [`observers.attach(*args, perform_once: true)`](#observersattachargs-perform_once-true)
|
47
|
+
- [`observers.once(event:, call:, ...)`](#observersonceevent-call-)
|
48
|
+
- [Detaching observers](#detaching-observers)
|
45
49
|
- [ActiveRecord and ActiveModel integrations](#activerecord-and-activemodel-integrations)
|
46
50
|
- [notify_observers_on()](#notify_observers_on)
|
47
51
|
- [notify_observers()](#notify_observers)
|
@@ -63,7 +67,7 @@ gem 'u-observers'
|
|
63
67
|
| u-observers | branch | ruby | activerecord |
|
64
68
|
| ----------- | ------- | -------- | ------------- |
|
65
69
|
| unreleased | main | >= 2.2.0 | >= 3.2, < 6.1 |
|
66
|
-
| 2.
|
70
|
+
| 2.2.0 | v2.x | >= 2.2.0 | >= 3.2, < 6.1 |
|
67
71
|
| 1.0.0 | v1.x | >= 2.2.0 | >= 3.2, < 6.1 |
|
68
72
|
|
69
73
|
> **Note**: The ActiveRecord isn't a dependency, but you could add a module to enable some static methods that were designed to be used with its [callbacks](https://guides.rubyonrails.org/active_record_callbacks.html).
|
@@ -109,7 +113,7 @@ end
|
|
109
113
|
order = Order.new
|
110
114
|
#<Order:0x00007fb5dd8fce70 @code="X0o9yf1GsdQFvLR4", @status=:draft>
|
111
115
|
|
112
|
-
order.observers.attach(OrderEvents)
|
116
|
+
order.observers.attach(OrderEvents) # attaching multiple observers. e.g. observers.attach(A, B, C)
|
113
117
|
# <#Micro::Observers::Set @subject=#<Order:0x00007fb5dd8fce70> @subject_changed=false @subscribers=[OrderEvents]>
|
114
118
|
|
115
119
|
order.canceled?
|
@@ -122,7 +126,7 @@ order.cancel!
|
|
122
126
|
order.canceled?
|
123
127
|
# true
|
124
128
|
|
125
|
-
order.observers.detach(OrderEvents)
|
129
|
+
order.observers.detach(OrderEvents) # detaching multiple observers. e.g. observers.detach(A, B, C)
|
126
130
|
# <#Micro::Observers::Set @subject=#<Order:0x00007fb5dd8fce70> @subject_changed=false @subscribers=[]>
|
127
131
|
|
128
132
|
order.canceled?
|
@@ -286,12 +290,12 @@ class Order
|
|
286
290
|
end
|
287
291
|
end
|
288
292
|
|
289
|
-
|
293
|
+
NotifyAfterCancel = -> (order) { puts "The order #(#{order.object_id}) has been canceled." }
|
290
294
|
|
291
295
|
order = Order.new
|
292
|
-
order.observers.attach(
|
296
|
+
order.observers.attach(NotifyAfterCancel)
|
293
297
|
order.cancel!
|
294
|
-
# The message below will be printed by the observer (
|
298
|
+
# The message below will be printed by the observer (NotifyAfterCancel):
|
295
299
|
# The order #(70196221441820) has been canceled.
|
296
300
|
```
|
297
301
|
|
@@ -307,6 +311,106 @@ If you use the methods `#notify!` or `#call!` you won't need to mark observers w
|
|
307
311
|
|
308
312
|
[⬆️ Back to Top](#table-of-contents-)
|
309
313
|
|
314
|
+
### Defining observers that execute only once
|
315
|
+
|
316
|
+
There are two ways to attach an observer and define it to be performed only once.
|
317
|
+
|
318
|
+
The first way to do this is passing the `perform_once: true` option to the `observers.attach()` method. e.g.
|
319
|
+
|
320
|
+
#### `observers.attach(*args, perform_once: true)`
|
321
|
+
|
322
|
+
```ruby
|
323
|
+
class Order
|
324
|
+
include Micro::Observers
|
325
|
+
|
326
|
+
def cancel!
|
327
|
+
observers.notify!(:canceled)
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
module OrderNotifications
|
332
|
+
def self.canceled(order)
|
333
|
+
puts "The order #(#{order.object_id}) has been canceled."
|
334
|
+
end
|
335
|
+
end
|
336
|
+
|
337
|
+
order = Order.new
|
338
|
+
order.observers.attach(OrderNotifications, perform_once: true) # you can also pass an array of observers with this option
|
339
|
+
|
340
|
+
order.observers.some? # true
|
341
|
+
order.cancel! # The order #(70291642071660) has been canceled.
|
342
|
+
|
343
|
+
order.observers.some? # false
|
344
|
+
order.cancel! # Nothing will happen because there aren't observers.
|
345
|
+
```
|
346
|
+
|
347
|
+
#### `observers.once(event:, call:, ...)`
|
348
|
+
|
349
|
+
The second way to achieve this is using `observers.once()` that has the same API of [`observers.on()`](#using-a-callable-as-an-observer). But the difference of the `#once()` method is that it will remove the observer after its execution.
|
350
|
+
|
351
|
+
```ruby
|
352
|
+
class Order
|
353
|
+
include Micro::Observers
|
354
|
+
|
355
|
+
def cancel!
|
356
|
+
observers.notify!(:canceled)
|
357
|
+
end
|
358
|
+
end
|
359
|
+
|
360
|
+
module NotifyAfterCancel
|
361
|
+
def self.call(event)
|
362
|
+
puts "The order #(#{event.subject.object_id}) has been canceled."
|
363
|
+
end
|
364
|
+
end
|
365
|
+
|
366
|
+
order = Order.new
|
367
|
+
order.observers.once(event: :canceled, call: NotifyAfterCancel)
|
368
|
+
|
369
|
+
order.observers.some? # true
|
370
|
+
order.cancel! # The order #(70301497466060) has been canceled.
|
371
|
+
|
372
|
+
order.observers.some? # false
|
373
|
+
order.cancel! # Nothing will happen because there aren't observers.
|
374
|
+
```
|
375
|
+
|
376
|
+
[⬆️ Back to Top](#table-of-contents-)
|
377
|
+
|
378
|
+
### Detaching observers
|
379
|
+
|
380
|
+
As shown in the first example, you can use the `observers.detach()` to remove observers.
|
381
|
+
|
382
|
+
But, there is an alternative method to remove observer objects or remove callables by their event names. The method to do this is: `observers.off()`.
|
383
|
+
|
384
|
+
```ruby
|
385
|
+
class Order
|
386
|
+
include Micro::Observers
|
387
|
+
end
|
388
|
+
|
389
|
+
NotifyAfterCancel = -> {}
|
390
|
+
|
391
|
+
module OrderNotifications
|
392
|
+
def self.canceled(_order)
|
393
|
+
end
|
394
|
+
end
|
395
|
+
|
396
|
+
order = Order.new
|
397
|
+
order.observers.on(event: :canceled, call: NotifyAfterCancel)
|
398
|
+
order.observers.attach(OrderNotifications)
|
399
|
+
|
400
|
+
order.observers.some? # true
|
401
|
+
order.observers.count # 2
|
402
|
+
|
403
|
+
order.observers.off(:canceled) # removing the callable (NotifyAfterCancel).
|
404
|
+
order.observers.some? # true
|
405
|
+
order.observers.count # 1
|
406
|
+
|
407
|
+
order.observers.off(OrderNotifications)
|
408
|
+
order.observers.some? # false
|
409
|
+
order.observers.count # 0
|
410
|
+
```
|
411
|
+
|
412
|
+
[⬆️ Back to Top](#table-of-contents-)
|
413
|
+
|
310
414
|
### ActiveRecord and ActiveModel integrations
|
311
415
|
|
312
416
|
To make use of this feature you need to require an additional module.
|
@@ -318,7 +422,6 @@ gem 'u-observers', require: 'u-observers/for/active_record'
|
|
318
422
|
|
319
423
|
This feature will expose modules that could be used to add macros (static methods) that were designed to work with `ActiveModel`/`ActiveRecord` callbacks. e.g:
|
320
424
|
|
321
|
-
|
322
425
|
#### notify_observers_on()
|
323
426
|
|
324
427
|
The `notify_observers_on` allows you to define one or more `ActiveModel`/`ActiveRecord` callbacks, that will be used to notify your object observers.
|
data/README.pt-BR.md
CHANGED
@@ -38,9 +38,13 @@ Por causa desse problema, decidi criar uma gem que encapsula o padrão sem alter
|
|
38
38
|
- [Compartilhando um contexto com seus observadores](#compartilhando-um-contexto-com-seus-observadores)
|
39
39
|
- [Compartilhando dados ao notificar os observadores](#compartilhando-dados-ao-notificar-os-observadores)
|
40
40
|
- [O que é `Micro::Observers::Event`?](#o-que-é-microobserversevent)
|
41
|
-
- [Usando um
|
41
|
+
- [Usando um callable como um observador](#usando-um-callable-como-um-observador)
|
42
42
|
- [Chamando os observadores](#chamando-os-observadores)
|
43
43
|
- [Notificar observadores sem marcá-los como alterados](#notificar-observadores-sem-marcá-los-como-alterados)
|
44
|
+
- [Definindo observers que executam apenas uma vez](#definindo-observers-que-executam-apenas-uma-vez)
|
45
|
+
- [`observers.attach(*args, perform_once: true)`](#observersattachargs-perform_once-true)
|
46
|
+
- [`observers.once(event:, call:, ...)`](#observersonceevent-call-)
|
47
|
+
- [Desanexando observers](#desanexando-observers)
|
44
48
|
- [Integrações ActiveRecord e ActiveModel](#integrações-activerecord-e-activemodel)
|
45
49
|
- [notify_observers_on()](#notify_observers_on)
|
46
50
|
- [notify_observers()](#notify_observers)
|
@@ -62,7 +66,7 @@ gem 'u-observers'
|
|
62
66
|
| u-observers | branch | ruby | activerecord |
|
63
67
|
| ----------- | ------- | -------- | ------------- |
|
64
68
|
| unreleased | main | >= 2.2.0 | >= 3.2, < 6.1 |
|
65
|
-
| 2.
|
69
|
+
| 2.2.0 | v2.x | >= 2.2.0 | >= 3.2, < 6.1 |
|
66
70
|
| 1.0.0 | v1.x | >= 2.2.0 | >= 3.2, < 6.1 |
|
67
71
|
|
68
72
|
> **Nota**: O ActiveRecord não é uma dependência, mas você pode adicionar um módulo para habilitar alguns métodos estáticos que foram projetados para serem usados com seus [callbacks](https://guides.rubyonrails.org/active_record_callbacks.html).
|
@@ -218,19 +222,17 @@ O `Micro::Observers::Event` é o payload do evento. Veja abaixo todas as suas pr
|
|
218
222
|
|
219
223
|
[⬆️ Voltar para o índice](#índice-)
|
220
224
|
|
221
|
-
### Usando um
|
225
|
+
### Usando um callable como um observador
|
222
226
|
|
223
227
|
O método `observers.on()` permite que você anexe um callable (objeto que responda ao método `call`) como um observador.
|
224
228
|
|
225
|
-
Um callable tende a ter uma responsabilidade bem definida, promovendo assim o uso de [SRP (Single-responsibility principle).
|
226
|
-
|
227
229
|
Normalmente, um callable tem uma responsabilidade bem definida (faz apenas uma coisa), por isso, tende a ser mais amigável com o [SRP (princípio de responsabilidade única)](https://en.wikipedia.org/wiki/Single-responsibility_principle) do que um observador convencional (que poderia ter N métodos para responder a diferentes tipos de notificação).
|
228
230
|
|
229
231
|
Este método recebe as opções abaixo:
|
230
232
|
1. `:event` o nome do evento esperado.
|
231
233
|
2. `:call` o próprio callable.
|
232
234
|
3. `:with` (opcional) pode definir o valor que será usado como argumento do objeto callable. Portanto, se for um `Proc`, uma instância de `Micro::Observers::Event` será recebida como o argumento `Proc` e sua saída será o argumento que pode ser chamado. Mas se essa opção não for definida, a instância `Micro::Observers::Event` será o argumento do callable.
|
233
|
-
4.
|
235
|
+
4. `:context` serão os dados de contexto que foram definidos no momento em que você anexa o *observer*.
|
234
236
|
|
235
237
|
```ruby
|
236
238
|
class Person
|
@@ -260,7 +262,8 @@ person = Person.new('Aristóteles')
|
|
260
262
|
person.observers.on(
|
261
263
|
event: :name_has_been_changed,
|
262
264
|
call: PrintPersonName,
|
263
|
-
with: -> event { {person: event.subject, number:
|
265
|
+
with: -> event { {person: event.subject, number: event.context} },
|
266
|
+
context: rand
|
264
267
|
)
|
265
268
|
|
266
269
|
person.name = 'Coutinho'
|
@@ -308,6 +311,106 @@ Se você usar os métodos `#notify!` ou `#call!` você não precisará marcar ob
|
|
308
311
|
|
309
312
|
[⬆️ Voltar para o índice](#índice-)
|
310
313
|
|
314
|
+
### Definindo observers que executam apenas uma vez
|
315
|
+
|
316
|
+
Existem duas formas de anexar um observer e definir que ele executará apenas uma vez.
|
317
|
+
|
318
|
+
A primeira forma de fazer isso é passando a opção `perform_once: true` para o método `observers.attach()`. Exemplo:
|
319
|
+
|
320
|
+
#### `observers.attach(*args, perform_once: true)`
|
321
|
+
|
322
|
+
```ruby
|
323
|
+
class Order
|
324
|
+
include Micro::Observers
|
325
|
+
|
326
|
+
def cancel!
|
327
|
+
observers.notify!(:canceled)
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
module OrderNotifications
|
332
|
+
def self.canceled(order)
|
333
|
+
puts "The order #(#{order.object_id}) has been canceled."
|
334
|
+
end
|
335
|
+
end
|
336
|
+
|
337
|
+
order = Order.new
|
338
|
+
order.observers.attach(OrderNotifications, perform_once: true) # you can also pass an array of observers with this option
|
339
|
+
|
340
|
+
order.observers.some? # true
|
341
|
+
order.cancel! # The order #(70291642071660) has been canceled.
|
342
|
+
|
343
|
+
order.observers.some? # false
|
344
|
+
order.cancel! # Nothing will happen because there aren't observers.
|
345
|
+
```
|
346
|
+
|
347
|
+
#### `observers.once(event:, call:, ...)`
|
348
|
+
|
349
|
+
A segunda forma de conseguir isso é usando o método `observers.once()` que tem a mesma API do [`observers.on()`](#usando-um-callable-como-um-observador). Mas a diferença é que o método `#once()` removerá o observer após a sua execução.
|
350
|
+
|
351
|
+
```ruby
|
352
|
+
class Order
|
353
|
+
include Micro::Observers
|
354
|
+
|
355
|
+
def cancel!
|
356
|
+
observers.notify!(:canceled)
|
357
|
+
end
|
358
|
+
end
|
359
|
+
|
360
|
+
module NotifyAfterCancel
|
361
|
+
def self.call(event)
|
362
|
+
puts "The order #(#{event.subject.object_id}) has been canceled."
|
363
|
+
end
|
364
|
+
end
|
365
|
+
|
366
|
+
order = Order.new
|
367
|
+
order.observers.once(event: :canceled, call: NotifyAfterCancel)
|
368
|
+
|
369
|
+
order.observers.some? # true
|
370
|
+
order.cancel! # The order #(70301497466060) has been canceled.
|
371
|
+
|
372
|
+
order.observers.some? # false
|
373
|
+
order.cancel! # Nothing will happen because there aren't observers.
|
374
|
+
```
|
375
|
+
|
376
|
+
[⬆️ Back to Top](#table-of-contents-)
|
377
|
+
|
378
|
+
### Desanexando observers
|
379
|
+
|
380
|
+
Como mostrado no primeiro exemplo, você pode usar o `observers.detach()` para remove observers.
|
381
|
+
|
382
|
+
Mas, existe uma alternativa a esse método que permite remover objetos observers ou remover callables pelo nome de seus eventos. O método para fazer isso é: `observers.off()`.
|
383
|
+
|
384
|
+
```ruby
|
385
|
+
class Order
|
386
|
+
include Micro::Observers
|
387
|
+
end
|
388
|
+
|
389
|
+
NotifyAfterCancel = -> {}
|
390
|
+
|
391
|
+
module OrderNotifications
|
392
|
+
def self.canceled(_order)
|
393
|
+
end
|
394
|
+
end
|
395
|
+
|
396
|
+
order = Order.new
|
397
|
+
order.observers.on(event: :canceled, call: NotifyAfterCancel)
|
398
|
+
order.observers.attach(OrderNotifications)
|
399
|
+
|
400
|
+
order.observers.some? # true
|
401
|
+
order.observers.count # 2
|
402
|
+
|
403
|
+
order.observers.off(:canceled) # removing the callable (NotifyAfterCancel).
|
404
|
+
order.observers.some? # true
|
405
|
+
order.observers.count # 1
|
406
|
+
|
407
|
+
order.observers.off(OrderNotifications)
|
408
|
+
order.observers.some? # false
|
409
|
+
order.observers.count # 0
|
410
|
+
```
|
411
|
+
|
412
|
+
[⬆️ Back to Top](#table-of-contents-)
|
413
|
+
|
311
414
|
### Integrações ActiveRecord e ActiveModel
|
312
415
|
|
313
416
|
Para fazer uso deste recurso, você precisa de um módulo adicional.
|
data/lib/micro/observers.rb
CHANGED
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'set'
|
4
|
+
|
5
|
+
module Micro
|
6
|
+
module Observers
|
7
|
+
|
8
|
+
module Broadcast
|
9
|
+
extend self
|
10
|
+
|
11
|
+
def call(subscribers, subject, data, event_names)
|
12
|
+
subscribers_list = subscribers.list
|
13
|
+
subscribers_to_be_deleted = ::Set.new
|
14
|
+
|
15
|
+
event_names.each(&BroadcastWith[subscribers_list, subject, data, subscribers_to_be_deleted])
|
16
|
+
|
17
|
+
subscribers_to_be_deleted.each { |subscriber| subscribers_list.delete(subscriber) }
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
BroadcastWith = -> (subscribers, subject, data, subscribers_to_be_deleted) do
|
23
|
+
-> (event_name) do
|
24
|
+
subscribers.each do |subscriber|
|
25
|
+
notified = NotifySubscriber.(subscriber, subject, data, event_name)
|
26
|
+
perform_once = subscriber[3]
|
27
|
+
|
28
|
+
subscribers_to_be_deleted.add(subscriber) if notified && perform_once
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
NotifySubscriber = -> (subscriber, subject, data, event_name) do
|
34
|
+
NotifyObserver.(subscriber, subject, data, event_name) ||
|
35
|
+
NotifyCallable.(subscriber, subject, data, event_name) ||
|
36
|
+
false
|
37
|
+
end
|
38
|
+
|
39
|
+
NotifyObserver = -> (subscriber, subject, data, event_name) do
|
40
|
+
strategy, observer, context = subscriber
|
41
|
+
|
42
|
+
return unless strategy == :observer && observer.respond_to?(event_name)
|
43
|
+
|
44
|
+
event = Event.new(event_name, subject, context, data)
|
45
|
+
|
46
|
+
handler = observer.is_a?(Proc) ? observer : observer.method(event.name)
|
47
|
+
handler.arity == 1 ? handler.call(event.subject) : handler.call(event.subject, event)
|
48
|
+
|
49
|
+
true
|
50
|
+
end
|
51
|
+
|
52
|
+
NotifyCallable = -> (subscriber, subject, data, event_name) do
|
53
|
+
strategy, observer, options = subscriber
|
54
|
+
|
55
|
+
return unless strategy == :callable && observer == event_name
|
56
|
+
|
57
|
+
callable, with, context = options[0], options[1], options[2]
|
58
|
+
|
59
|
+
return callable.call(with) if with && !with.is_a?(Proc)
|
60
|
+
|
61
|
+
event = Event.new(event_name, subject, context, data)
|
62
|
+
|
63
|
+
callable.call(with.is_a?(Proc) ? with.call(event) : event)
|
64
|
+
|
65
|
+
true
|
66
|
+
end
|
67
|
+
|
68
|
+
private_constant :BroadcastWith, :NotifySubscriber, :NotifyCallable, :NotifyObserver
|
69
|
+
end
|
70
|
+
|
71
|
+
private_constant :Broadcast
|
72
|
+
end
|
73
|
+
end
|
@@ -18,7 +18,7 @@ module Micro
|
|
18
18
|
end
|
19
19
|
|
20
20
|
def notify_observers_on(*callback_methods)
|
21
|
-
Utils::Arrays.
|
21
|
+
Utils::Arrays.fetch_from_args(callback_methods).each do |callback_method|
|
22
22
|
self.public_send(callback_method, ¬ify_observers!([callback_method]))
|
23
23
|
end
|
24
24
|
end
|
data/lib/micro/observers/set.rb
CHANGED
@@ -4,42 +4,19 @@ module Micro
|
|
4
4
|
module Observers
|
5
5
|
|
6
6
|
class Set
|
7
|
-
EMPTY_HASH = {}.freeze
|
8
|
-
|
9
|
-
MapSubscriber = -> (observer, options) { [:observer, observer, options[:context]] }
|
10
|
-
|
11
|
-
MapSubscribers = -> (value) do
|
12
|
-
array = Utils::Arrays.flatten_and_compact(value.kind_of?(Array) ? value : [])
|
13
|
-
array.map { |observer| MapSubscriber[observer, EMPTY_HASH] }
|
14
|
-
end
|
15
|
-
|
16
|
-
GetObserver = -> subscriber { subscriber[0] == :observer ? subscriber[1] : subscriber[2][0] }
|
17
|
-
|
18
|
-
EqualTo = -> (observer) { -> subscriber { GetObserver[subscriber] == observer } }
|
19
|
-
|
20
7
|
def self.for(subject)
|
21
8
|
new(subject)
|
22
9
|
end
|
23
10
|
|
24
11
|
def initialize(subject, subscribers: nil)
|
25
12
|
@subject = subject
|
26
|
-
|
27
13
|
@subject_changed = false
|
28
|
-
|
29
|
-
@subscribers = MapSubscribers.call(subscribers)
|
30
|
-
end
|
31
|
-
|
32
|
-
def count
|
33
|
-
@subscribers.size
|
14
|
+
@subscribers = Subscribers.new(subscribers)
|
34
15
|
end
|
35
16
|
|
36
|
-
def
|
37
|
-
|
38
|
-
end
|
39
|
-
|
40
|
-
def some?
|
41
|
-
!none?
|
42
|
-
end
|
17
|
+
def count; @subscribers.count; end
|
18
|
+
def none?; @subscribers.none?; end
|
19
|
+
def some?; !none?; end
|
43
20
|
|
44
21
|
def subject_changed?
|
45
22
|
@subject_changed
|
@@ -57,121 +34,66 @@ module Micro
|
|
57
34
|
subject_changed(true)
|
58
35
|
end
|
59
36
|
|
60
|
-
def
|
61
|
-
@subscribers.
|
62
|
-
end
|
63
|
-
|
64
|
-
def attach(*args)
|
65
|
-
options = args.last.is_a?(Hash) ? args.pop : EMPTY_HASH
|
66
|
-
|
67
|
-
Utils::Arrays.flatten_and_compact(args).each do |observer|
|
68
|
-
@subscribers << MapSubscriber[observer, options] unless included?(observer)
|
69
|
-
end
|
70
|
-
|
71
|
-
self
|
72
|
-
end
|
73
|
-
|
74
|
-
def detach(*args)
|
75
|
-
Utils::Arrays.flatten_and_compact(args).each do |observer|
|
76
|
-
@subscribers.delete_if(&EqualTo[observer])
|
77
|
-
end
|
78
|
-
|
79
|
-
self
|
37
|
+
def include?(observer)
|
38
|
+
@subscribers.include?(observer)
|
80
39
|
end
|
40
|
+
alias included? include?
|
81
41
|
|
82
|
-
def
|
83
|
-
|
84
|
-
|
85
|
-
return self unless event.is_a?(Symbol) && callable.respond_to?(:call)
|
42
|
+
def attach(*args); @subscribers.attach(args) and self; end
|
43
|
+
def detach(*args); @subscribers.detach(args) and self; end
|
86
44
|
|
87
|
-
|
45
|
+
def on(options = Utils::EMPTY_HASH); @subscribers.on(options) and self; end
|
46
|
+
def once(options = Utils::EMPTY_HASH); @subscribers.once(options) and self; end
|
88
47
|
|
89
|
-
|
48
|
+
def off(*args)
|
49
|
+
@subscribers.off(args) and self
|
90
50
|
end
|
91
51
|
|
92
52
|
def notify(*events, data: nil)
|
93
53
|
broadcast_if_subject_changed(Event::Names.fetch(events), data)
|
94
|
-
|
95
|
-
self
|
96
54
|
end
|
97
55
|
|
98
56
|
def notify!(*events, data: nil)
|
99
57
|
broadcast(Event::Names.fetch(events), data)
|
100
|
-
|
101
|
-
self
|
102
58
|
end
|
103
59
|
|
104
60
|
CALL_EVENT = [:call].freeze
|
105
61
|
|
106
62
|
def call(*events, data: nil)
|
107
63
|
broadcast_if_subject_changed(Event::Names[events, default: CALL_EVENT], data)
|
108
|
-
|
109
|
-
self
|
110
64
|
end
|
111
65
|
|
112
66
|
def call!(*events, data: nil)
|
113
67
|
broadcast(Event::Names[events, default: CALL_EVENT], data)
|
114
|
-
|
115
|
-
self
|
116
68
|
end
|
117
69
|
|
118
70
|
def inspect
|
119
|
-
subs = @subscribers.
|
71
|
+
subs = @subscribers.to_inspect
|
120
72
|
|
121
|
-
'
|
73
|
+
'#<%s @subject=%s @subject_changed=%p @subscribers=%p>' % [self.class, @subject, @subject_changed, subs]
|
122
74
|
end
|
123
75
|
|
124
76
|
private
|
125
77
|
|
126
|
-
def broadcast_if_subject_changed(
|
127
|
-
return
|
78
|
+
def broadcast_if_subject_changed(event_names, data = nil)
|
79
|
+
return self if none? || !subject_changed?
|
128
80
|
|
129
|
-
broadcast(
|
81
|
+
broadcast(event_names, data)
|
130
82
|
|
131
83
|
subject_changed(false)
|
132
|
-
end
|
133
84
|
|
134
|
-
|
135
|
-
return if @subscribers.empty?
|
136
|
-
|
137
|
-
event_names.each do |event_name|
|
138
|
-
@subscribers.each do |strategy, observer, context|
|
139
|
-
case strategy
|
140
|
-
when :observer then notify_observer(observer, event_name, context, data)
|
141
|
-
when :callable then notify_callable(observer, event_name, context, data)
|
142
|
-
end
|
143
|
-
end
|
144
|
-
end
|
85
|
+
self
|
145
86
|
end
|
146
87
|
|
147
|
-
def
|
148
|
-
return
|
149
|
-
|
150
|
-
handler = observer.is_a?(Proc) ? observer : observer.method(event_name)
|
151
|
-
|
152
|
-
return handler.call(@subject) if handler.arity == 1
|
153
|
-
|
154
|
-
handler.call(@subject, Event.new(event_name, @subject, context, data))
|
155
|
-
end
|
156
|
-
|
157
|
-
def notify_callable(expected_event_name, event_name, opt, data)
|
158
|
-
return if expected_event_name != event_name
|
159
|
-
|
160
|
-
callable, with, context = opt[0], opt[1], opt[2]
|
161
|
-
callable_arg =
|
162
|
-
if with && !with.is_a?(Proc)
|
163
|
-
with
|
164
|
-
else
|
165
|
-
event = Event.new(event_name, @subject, context, data)
|
88
|
+
def broadcast(event_names, data)
|
89
|
+
return self if none?
|
166
90
|
|
167
|
-
|
168
|
-
end
|
91
|
+
Broadcast.call(@subscribers, @subject, data, event_names)
|
169
92
|
|
170
|
-
|
93
|
+
self
|
171
94
|
end
|
172
95
|
|
173
|
-
private_constant :
|
174
|
-
private_constant :MapSubscriber, :MapSubscribers, :GetObserver, :EqualTo
|
96
|
+
private_constant :INVALID_BOOLEAN_MSG, :CALL_EVENT
|
175
97
|
end
|
176
98
|
|
177
99
|
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Micro
|
4
|
+
module Observers
|
5
|
+
|
6
|
+
class Subscribers
|
7
|
+
EqualTo = -> (observer) { -> subscriber { GetObserver[subscriber] == observer } }
|
8
|
+
GetObserver = -> subscriber { subscriber[0] == :observer ? subscriber[1] : subscriber[2][0] }
|
9
|
+
MapObserver = -> (observer, options, once) { [:observer, observer, options[:context], once] }
|
10
|
+
MapObserversToInitialize = -> arg do
|
11
|
+
Utils::Arrays.flatten_and_compact(arg).map do |observer|
|
12
|
+
MapObserver[observer, Utils::EMPTY_HASH, false]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
attr_reader :list
|
17
|
+
|
18
|
+
def initialize(arg)
|
19
|
+
@list = arg.is_a?(Array) ? MapObserversToInitialize[arg] : []
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_inspect
|
23
|
+
none? ? @list : @list.map(&GetObserver)
|
24
|
+
end
|
25
|
+
|
26
|
+
def count
|
27
|
+
@list.size
|
28
|
+
end
|
29
|
+
|
30
|
+
def none?
|
31
|
+
@list.empty?
|
32
|
+
end
|
33
|
+
|
34
|
+
def include?(subscriber)
|
35
|
+
@list.any?(&EqualTo[subscriber])
|
36
|
+
end
|
37
|
+
|
38
|
+
def attach(args)
|
39
|
+
options = args.last.is_a?(Hash) ? args.pop : Utils::EMPTY_HASH
|
40
|
+
|
41
|
+
once = options.frozen? ? false : options.delete(:perform_once)
|
42
|
+
|
43
|
+
Utils::Arrays.fetch_from_args(args).each do |observer|
|
44
|
+
@list << MapObserver[observer, options, once] unless include?(observer)
|
45
|
+
end
|
46
|
+
|
47
|
+
true
|
48
|
+
end
|
49
|
+
|
50
|
+
def detach(args)
|
51
|
+
Utils::Arrays.fetch_from_args(args).each do |observer|
|
52
|
+
delete_observer(observer)
|
53
|
+
end
|
54
|
+
|
55
|
+
true
|
56
|
+
end
|
57
|
+
|
58
|
+
def on(options)
|
59
|
+
on!(options, once: false)
|
60
|
+
end
|
61
|
+
|
62
|
+
def once(options)
|
63
|
+
on!(options, once: true)
|
64
|
+
end
|
65
|
+
|
66
|
+
EventNameToCall = -> event_name { -> subscriber { subscriber[0] == :callable && subscriber[1] == event_name } }
|
67
|
+
|
68
|
+
def off(args)
|
69
|
+
Utils::Arrays.fetch_from_args(args).each do |value|
|
70
|
+
if value.is_a?(Symbol)
|
71
|
+
@list.delete_if(&EventNameToCall[value])
|
72
|
+
else
|
73
|
+
delete_observer(value)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
def delete_observer(observer)
|
81
|
+
@list.delete_if(&EqualTo[observer])
|
82
|
+
end
|
83
|
+
|
84
|
+
def on!(options, once:)
|
85
|
+
event, callable, with, context = options[:event], options[:call], options[:with], options[:context]
|
86
|
+
|
87
|
+
return true unless event.is_a?(Symbol) && callable.respond_to?(:call)
|
88
|
+
|
89
|
+
observer = [callable, with, context]
|
90
|
+
|
91
|
+
@list << [:callable, event, observer, once] unless include_callable?(event, observer)
|
92
|
+
|
93
|
+
true
|
94
|
+
end
|
95
|
+
|
96
|
+
CallableHaving = -> (event, observer) do
|
97
|
+
-> subs { subs[0] == :callable && subs[1] == event && subs[2] == observer }
|
98
|
+
end
|
99
|
+
|
100
|
+
def include_callable?(event, observer)
|
101
|
+
@list.any?(&CallableHaving[event, observer])
|
102
|
+
end
|
103
|
+
|
104
|
+
private_constant :EqualTo, :EventNameToCall, :CallableHaving
|
105
|
+
private_constant :GetObserver, :MapObserver, :MapObserversToInitialize
|
106
|
+
end
|
107
|
+
|
108
|
+
private_constant :Subscribers
|
109
|
+
end
|
110
|
+
end
|
@@ -4,9 +4,19 @@ module Micro
|
|
4
4
|
module Observers
|
5
5
|
|
6
6
|
module Utils
|
7
|
+
EMPTY_HASH = {}.freeze
|
8
|
+
|
7
9
|
module Arrays
|
10
|
+
def self.fetch_from_args(args)
|
11
|
+
args.size == 1 && (first = args[0]).is_a?(::Array) ? first : args
|
12
|
+
end
|
13
|
+
|
8
14
|
def self.flatten_and_compact(value)
|
9
|
-
|
15
|
+
return [] unless value
|
16
|
+
|
17
|
+
array = Array(value).flatten
|
18
|
+
array.compact!
|
19
|
+
array
|
10
20
|
end
|
11
21
|
end
|
12
22
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: u-observers
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rodrigo Serradura
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-11-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -58,11 +58,13 @@ files:
|
|
58
58
|
- bin/console
|
59
59
|
- bin/setup
|
60
60
|
- lib/micro/observers.rb
|
61
|
+
- lib/micro/observers/broadcast.rb
|
61
62
|
- lib/micro/observers/event.rb
|
62
63
|
- lib/micro/observers/event/names.rb
|
63
64
|
- lib/micro/observers/for/active_model.rb
|
64
65
|
- lib/micro/observers/for/active_record.rb
|
65
66
|
- lib/micro/observers/set.rb
|
67
|
+
- lib/micro/observers/subscribers.rb
|
66
68
|
- lib/micro/observers/utils.rb
|
67
69
|
- lib/micro/observers/version.rb
|
68
70
|
- lib/u-observers.rb
|