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